# Copyright 2016, 2017 IBM Corp.
#
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

from oslo_config import cfg

CONF = cfg.CONF

powervm_group = cfg.OptGroup(
    'powervm',
    title='PowerVM Options')


powervm_opts = [
    cfg.FloatOpt('proc_units_factor',
                 default=0.1,
                 help='Factor used to calculate the processor units per vcpu. '
                 'Valid values are: 0.05 - 1.0'),
    cfg.IntOpt('uncapped_proc_weight',
               default=64, min=1, max=255,
               help='The processor weight to assign to newly created VMs.  '
                    'Value should be between 1 and 255.  Represents how '
                    'aggressively LPARs grab CPU when unused cycles are '
                    'available.'),
    cfg.StrOpt('vopt_media_volume_group',
               default='rootvg',
               help='The volume group on the system that should be used '
                    'to store the config drive metadata that will be attached '
                    'to VMs.  If not specified and no media repository '
                    'exists, rootvg will be used.  This option is ignored if '
                    'a media repository already exists.'),
    cfg.IntOpt('vopt_media_rep_size',
               default=1, min=1,
               help='The size of the media repository (in GB) for the '
                    'metadata for config drive.  Only used if the media '
                    'repository needs to be created.'),
    cfg.StrOpt('image_meta_local_path',
               default='/tmp/cfgdrv/',
               help='The location where the config drive ISO files should be '
                    'built.'),
    cfg.StrOpt('disk_driver',
               choices=['localdisk', 'ssp'], ignore_case=True,
               default='localdisk',
               help='The disk driver to use for PowerVM disks. '
               'Valid options are: localdisk, ssp'),
    cfg.StrOpt('volume_adapter',
               choices=['fibre_channel', 'iscsi'], ignore_case=True,
               default='fibre_channel',
               help='The volume adapter to use for PowerVM volumes. '
               'Valid options are: fibre_channel, iscsi'),
    cfg.StrOpt('pvm_vswitch_for_novalink_io',
               default='NovaLinkVEABridge',
               help="Name of the PowerVM virtual switch to be used when "
                    "mapping Linux based network ports to PowerVM virtual "
                    "Ethernet devices"),
    cfg.BoolOpt('remove_vopt_media_on_boot',
                default=False,
                help="If enabled, tells the PowerVM driver to trigger the "
                     "removal of the media from the virtual optical device "
                     "used for initialization of VMs on spawn after "
                     "'remove_vopt_media_time' minutes."),
    cfg.IntOpt('remove_vopt_media_time',
               default=60, min=0,
               help="The amount of time in minutes after a VM has been "
                    "created for the virtual optical media to be removed."),
    cfg.BoolOpt('use_rmc_mgmt_vif',
                default=True,
                help="If enabled, tells the PowerVM Driver to create an RMC "
                     "network interface on the deploy of a VM.  This is an "
                     "adapter that can only talk to the NovaLink partition "
                     "and enables DLPAR actions."),
    cfg.BoolOpt('use_rmc_ipv6_scheme',
                default=True,
                help="Only used if use_rmc_mgmt_vif is True and config drive "
                     "is being used.  If set, the system will configure the "
                     "RMC network interface with an IPv6 link local address. "
                     "This is generally set to True, but users may wish to "
                     "turn this off if their operating system has "
                     "compatibility issues."),
    cfg.IntOpt('vios_active_wait_timeout',
               default=300,
               help="Default time in seconds to wait for Virtual I/O Server "
                    "to be up and running.")
]

localdisk_opts = [
    cfg.StrOpt('volume_group_name',
               default='',
               help='Volume Group to use for block device operations.  If '
                    'disk_driver is localdisk, then this attribute must be '
                    'specified.  It is strongly recommended NOT to use '
                    'rootvg.'),
]

ssp_opts = [
    cfg.StrOpt('cluster_name',
               default='',
               help='Cluster hosting the Shared Storage Pool to use for '
                    'storage operations.  If none specified, the host is '
                    'queried; if a single Cluster is found, it is used. '
                    'Not used unless disk_driver option is set to ssp.')
]

vol_adapter_opts = [
    cfg.StrOpt('fc_attach_strategy',
               choices=['vscsi', 'npiv'], ignore_case=True,
               default='vscsi', mutable=True,
               help='The Fibre Channel Volume Strategy defines how FC Cinder '
                    'volumes should be attached to the Virtual Machine.  The '
                    'options are: npiv or vscsi. If npiv is selected then '
                    'the ports_per_fabric and fabrics option should be '
                    'specified and at least one fabric_X_port_wwpns option '
                    '(where X corresponds to the fabric name) must be '
                    'specified.'),
    cfg.StrOpt('network_attach_strategy',
               choices=['iscsi'], ignore_case=True,
               default='iscsi',
               help='The iSCSI Volume Strategy defines how iSCSI Cinder '
                    'volumes should be attached to the Virtual Machine. The '
                    'option is: iscsi.'),
    cfg.StrOpt('fc_npiv_adapter_api',
               default='nova_powervm.virt.powervm.volume.npiv.'
               'NPIVVolumeAdapter',
               help='Volume Adapter API to connect FC volumes using NPIV'
                    'connection mechanism.'),
    cfg.StrOpt('fc_vscsi_adapter_api',
               default='nova_powervm.virt.powervm.volume.vscsi.'
               'PVVscsiFCVolumeAdapter',
               help='Volume Adapter API to connect FC volumes through Virtual '
                    'I/O Server using PowerVM vSCSI connection mechanism.'),
    cfg.IntOpt('vscsi_vios_connections_required',
               default=1, min=1,
               help='Indicates a minimum number of Virtual I/O Servers that '
                    'are required to support a Cinder volume attach with the '
                    'vSCSI volume connector.'),
    cfg.BoolOpt('volume_use_multipath',
                default=False,
                help="Use multipath connections when attaching iSCSI or FC"),
    cfg.StrOpt('iscsi_iface',
               default='default',
               help="The iSCSI transport iface to use to connect to target in "
                    "case offload support is desired. Do not confuse the "
                    "iscsi_iface parameter to be provided here with the "
                    "actual transport name.")
]

# NPIV Options.  Only applicable if the 'fc_attach_strategy' is set to 'npiv'.
# Otherwise this section can be ignored.
npiv_opts = [
    cfg.IntOpt('ports_per_fabric',
               default=1, min=1,
               help='The number of physical ports that should be connected '
                    'directly to the Virtual Machine, per fabric.  '
                    'Example: 2 fabrics and ports_per_fabric set to 2 will '
                    'result in 4 NPIV ports being created, two per fabric. '
                    'If multiple Virtual I/O Servers are available, will '
                    'attempt to span ports across I/O Servers.'),
    cfg.StrOpt('fabrics', default='A',
               help='Unique identifier for each physical FC fabric that is '
                    'available.  This is a comma separated list.  If there '
                    'are two fabrics for multi-pathing, then this could be '
                    'set to A,B.'
                    'The fabric identifiers are used for the '
                    '\'fabric_<identifier>_port_wwpns\' key.')
]

remote_restart_opts = [
    cfg.StrOpt('nvram_store',
               choices=['none', 'swift'], ignore_case=True,
               default='none',
               help='The NVRAM store to use to hold the PowerVM NVRAM for '
               'virtual machines.'),
]

swift_opts = [
    cfg.StrOpt('swift_container', default='powervm_nvram',
               help='The Swift container to store the PowerVM NVRAM in. This '
               'must be configured the same value for all compute hosts.'),
    cfg.StrOpt('swift_username', default='powervm',
               help='The Swift user name to use for operations that use '
               'the Swift store.'),
    cfg.StrOpt('swift_user_domain_name', default='powervm',
               help='The Swift domain the user is a member of.'),
    cfg.StrOpt('swift_password', secret=True,
               help='The password for the Swift user.'),
    cfg.StrOpt('swift_project_name', default='powervm',
               help='The Swift project.'),
    cfg.StrOpt('swift_project_domain_name', default='powervm',
               help='The Swift project domain.'),
    cfg.StrOpt('swift_auth_version', default='3', help='The Keystone API '
               'version.'),
    cfg.StrOpt('swift_auth_url', help='The Keystone authorization url. '
               'Example: "http://keystone-hostname:5000/v3"'),
    cfg.StrOpt('swift_cacert', required=False, help='Path to CA certificate '
               'file.  Example: /etc/swiftclient/myca.pem'),
    cfg.StrOpt('swift_endpoint_type', help='The endpoint/interface type for '
               'the Swift client to select from the Keystone Service Catalog '
               'for the connection URL.  Swift defaults to "publicURL".')
]

vnc_opts = [
    cfg.BoolOpt('vnc_use_x509_auth', default=False,
                help='If enabled, uses X509 Authentication for the '
                'VNC sessions started for each VM.'),
    cfg.StrOpt('vnc_ca_certs', help='Path to CA certificate '
               'to use for verifying VNC X509 Authentication.'),
    cfg.StrOpt('vnc_server_cert', help='Path to Server certificate '
               'to use for verifying VNC X509 Authentication.'),
    cfg.StrOpt('vnc_server_key', help='Path to Server private key '
               'to use for verifying VNC X509 Authentication.')
]

STATIC_OPTIONS = (powervm_opts + localdisk_opts + ssp_opts + vol_adapter_opts
                  + npiv_opts + remote_restart_opts + swift_opts + vnc_opts)

# Dictionary where the key is the NPIV Fabric Name, and the value is a list of
# Physical WWPNs that match the key.
NPIV_FABRIC_WWPNS = {}
FABRIC_WWPN_HELP = ('A comma delimited list of all the physical FC port '
                    'WWPNs that support the specified fabric.  Is tied to '
                    'the NPIV fabrics key.')
# This is only used to provide a sample for the list_opt() method
fabric_sample = [
    cfg.StrOpt('fabric_A_port_wwpns', default='', help=FABRIC_WWPN_HELP),
    cfg.StrOpt('fabric_B_port_wwpns', default='', help=FABRIC_WWPN_HELP),
]


def _register_fabrics(conf, fabric_mapping):
    """Registers the fabrics to WWPNs options and builds a mapping.

    This method registers the 'fabric_X_port_wwpns' (where X is determined by
    the 'fabrics' option values) and then builds a dictionary that mapps the
    fabrics to the WWPNs.  This mapping can then be later used without having
    to reparse the options.
    """
    # At this point, the fabrics should be specified.  Iterate over those to
    # determine the port_wwpns per fabric.
    if conf.powervm.fabrics is not None:
        port_wwpn_keys = []
        fabrics = conf.powervm.fabrics.split(',')
        for fabric in fabrics:
            opt = cfg.StrOpt('fabric_%s_port_wwpns' % fabric,
                             default='', help=FABRIC_WWPN_HELP)
            port_wwpn_keys.append(opt)

        conf.register_opts(port_wwpn_keys, group='powervm')

        # Now that we've registered the fabrics, saturate the NPIV dictionary
        for fabric in fabrics:
            key = 'fabric_%s_port_wwpns' % fabric
            wwpns = conf.powervm[key].split(',')
            wwpns = [x.upper().strip(':') for x in wwpns]
            fabric_mapping[fabric] = wwpns


def register_opts(conf):
    conf.register_group(powervm_group)
    conf.register_opts(STATIC_OPTIONS, group=powervm_group)
    _register_fabrics(conf, NPIV_FABRIC_WWPNS)


# To generate a sample config run:
# $ oslo-config-generator --namespace nova_powervm > nova_powervm_sample.conf
def list_opts():
    # The nova conf tooling expects each module to return a dict of options.
    # When powervm is pulled into nova proper the return value would be in
    # this form:
    # return {powervm_group.name: STATIC_OPTIONS + fabric_sample}
    #
    # The oslo-config-generator tooling expects a tuple:
    return [(powervm_group.name, STATIC_OPTIONS + fabric_sample)]
