#   Copyright 2018 Red Hat, Inc.
#
#   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.
#

import copy

from osc_lib.i18n import _
from oslo_config import cfg

from tripleoclient import constants

from tripleoclient.config.standalone import StandaloneConfig

CONF = cfg.CONF

# Control plane network name
SUBNETS_DEFAULT = ['ctlplane-subnet']

CIDR_HELP_STR = _(
    'Network CIDR for the Neutron-managed subnet for Overcloud instances.')
DHCP_START_HELP_STR = _(
    'Start of DHCP allocation range for PXE and DHCP of Overcloud instances '
    'on this network.')
DHCP_END_HELP_STR = _(
    'End of DHCP allocation range for PXE and DHCP of Overcloud instances on '
    'this network.')
DHCP_EXCLUDE_HELP_STR = _(
    'List of IP addresses or IP ranges to exclude from the subnets allocation '
    'pool. Example: 192.168.24.50,192.168.24.80-192.168.24.90')
INSPECTION_IPRANGE_HELP_STR = _(
    'Temporary IP range that will be given to nodes on this network during '
    'the inspection process. Should not overlap with the range defined by '
    'dhcp_start and dhcp_end, but should be in the same ip subnet.')
GATEWAY_HELP_STR = _(
    'Network gateway for the Neutron-managed network for Overcloud instances '
    'on this network.')
MASQUERADE_HELP_STR = _(
    'The network will be masqueraded for external access.')
HOST_ROUTES_HELP_STR = _(
    'Host routes for the Neutron-managed subnet for the Overcloud instances '
    'on this network. The host routes on the local_subnet will also be '
    'configured on the undercloud.')
DNS_NAMESERVERS_HELP_STR = _(
    'DNS nameservers for the Neutron-managed subnet for the Overcloud '
    'instances on this network. If no nameservers are defined for the subnet, '
    'the nameservers defined for undercloud_nameservers will be used.')

# Deprecated options
_deprecated_opt_network_gateway = [cfg.DeprecatedOpt(
    'network_gateway', group='DEFAULT')]
_deprecated_opt_network_cidr = [cfg.DeprecatedOpt(
    'network_cidr', group='DEFAULT')]
_deprecated_opt_dhcp_start = [cfg.DeprecatedOpt(
    'dhcp_start', group='DEFAULT')]
_deprecated_opt_dhcp_end = [cfg.DeprecatedOpt('dhcp_end', group='DEFAULT')]
_deprecated_opt_inspection_iprange = [cfg.DeprecatedOpt(
    'inspection_iprange', group='DEFAULT')]


class UndercloudConfig(StandaloneConfig):
    def get_undercloud_service_opts(self):
        return super(UndercloudConfig, self).get_enable_service_opts(
            cinder=False,
            ironic=True,
            ironic_inspector=True,
            mistral=False,
            nova=False,
            novajoin=False,
            swift=True,
            tempest=True,
            telemetry=False,
            validations=True,
            zaqar=False)

    def get_base_opts(self):
        _base_opts = super(UndercloudConfig, self).get_base_opts()
        _opts = [
            cfg.StrOpt('undercloud_log_file',
                       default=constants.UNDERCLOUD_LOG_FILE,
                       help=_(
                           'The path to a log file to store the '
                           'undercloud install/upgrade logs.'),
                       ),
            cfg.StrOpt('undercloud_hostname',
                       help=_(
                           'Fully qualified hostname (including domain) to '
                           'set on the Undercloud. If left unset, the current '
                           'hostname will be used, but the user is '
                           'responsible for configuring all system hostname '
                           'settings appropriately.  If set, the undercloud '
                           'install will configure all system hostname '
                           'settings.'),
                       ),
            cfg.StrOpt('local_ip',
                       default='192.168.24.1/24',
                       help=_(
                           'IP information for the interface on the '
                           'Undercloud that will be handling the PXE boots '
                           'and DHCP for Overcloud instances.  The IP portion '
                           'of the value will be assigned to the network '
                           'interface defined by local_interface, with the '
                           'netmask defined by the prefix portion of the '
                           'value.')
                       ),
            cfg.StrOpt('undercloud_public_host',
                       deprecated_name='undercloud_public_vip',
                       default='192.168.24.2',
                       help=_(
                           'Virtual IP or DNS address to use for the public '
                           'endpoints of Undercloud services. Only used '
                           'with SSL.')
                       ),
            cfg.StrOpt('undercloud_admin_host',
                       deprecated_name='undercloud_admin_vip',
                       default='192.168.24.3',
                       help=_(
                           'Virtual IP or DNS address to use for the admin '
                           'endpoints of Undercloud services. Only used '
                           'with SSL.')
                       ),
            cfg.ListOpt('undercloud_nameservers',
                        default=[],
                        help=_(
                            'DNS nameserver(s). Use for the undercloud '
                            'node and for the overcloud nodes. (NOTE: To use '
                            'different nameserver(s) for the overcloud, '
                            'override the DnsServers parameter in overcloud '
                            'environment.)'),
                        ),
            cfg.ListOpt('undercloud_ntp_servers',
                        default=['0.pool.ntp.org', '1.pool.ntp.org',
                                 '2.pool.ntp.org', '3.pool.ntp.org'],
                        help=_('List of ntp servers to use.')),
            cfg.StrOpt('undercloud_timezone', default=None,
                       help=_('Host timezone to be used. If no timezone is '
                              'specified, the existing timezone configuration '
                              'is used.')),
            cfg.StrOpt('overcloud_domain_name',
                       default='localdomain',
                       help=_(
                           'DNS domain name to use when deploying the '
                           'overcloud. The overcloud parameter "CloudDomain" '
                           'must be set to a matching value.')
                       ),
            cfg.ListOpt('subnets',
                        default=SUBNETS_DEFAULT,
                        help=_(
                            'List of routed network subnets for '
                            'provisioning and introspection. Comma '
                            'separated list of names/tags. For each network '
                            'a section/group needs to be added to the '
                            'configuration file with these parameters set: '
                            'cidr, dhcp_start, dhcp_end, inspection_iprange, '
                            'gateway and masquerade_network. Note: The '
                            'section/group must be placed before or after '
                            'any other section. (See the example section '
                            '[ctlplane-subnet] in the sample configuration '
                            'file.)')),
            cfg.StrOpt('local_subnet',
                       default=SUBNETS_DEFAULT[0],
                       help=_(
                           'Name of the local subnet, where the PXE boot and '
                           'DHCP interfaces for overcloud instances is '
                           'located. The IP address of the '
                           'local_ip/local_interface should reside '
                           'in this subnet.')),
            cfg.StrOpt('undercloud_service_certificate',
                       default='',
                       help=_(
                           'Certificate file to use for OpenStack service SSL '
                           'connections.  Setting this enables SSL for the '
                           'OpenStack API endpoints, leaving it unset '
                           'disables SSL.')
                       ),
            cfg.BoolOpt('generate_service_certificate',
                        default=True,
                        help=_(
                            'When set to True, an SSL certificate will be '
                            'generated as part of the undercloud install and '
                            'this certificate will be used in place of the '
                            'value for undercloud_service_certificate.  The '
                            'resulting certificate will be written to '
                            '/etc/pki/tls/private/overcloud_endpoint.pem. This'
                            ' certificate is signed by CA selected by the '
                            '"certificate_generation_ca" option.')
                        ),
            cfg.StrOpt('certificate_generation_ca',
                       default='local',
                       help=_(
                           'The certmonger nickname of the CA from which '
                           'the certificate will be requested. This is used '
                           'only if the generate_service_certificate option '
                           'is set. Note that if the "local" CA is selected '
                           'the certmonger\'s local CA certificate will be '
                           'extracted to /etc/pki/ca-trust/source/anchors/'
                           'cm-local-ca.pem and subsequently added to the '
                           'trust chain.')
                       ),
            cfg.StrOpt('service_principal',
                       default='',
                       help=_(
                           'The kerberos principal for the service that will '
                           'use the certificate. This is only needed if your '
                           'CA requires a kerberos principal. e.g. with '
                           'FreeIPA.')
                       ),
            cfg.StrOpt('local_interface',
                       default='eth1',
                       help=_('Network interface on the Undercloud that will '
                              'be handling the PXE boots and DHCP for '
                              'Overcloud instances.')
                       ),
            cfg.IntOpt('local_mtu',
                       default=1500,
                       help=_('MTU to use for the local_interface.')
                       ),
            cfg.StrOpt('inspection_interface',
                       default='br-ctlplane',
                       deprecated_name='discovery_interface',
                       help=_(
                           'Network interface on which inspection dnsmasq '
                           'will listen.  If in doubt, use the default value.')
                       ),
            cfg.BoolOpt('inspection_extras',
                        default=True,
                        help=_(
                            'Whether to enable extra hardware collection '
                            'during the inspection process. Requires '
                            'python-hardware or python-hardware-detect '
                            'package on the introspection image.')),
            cfg.BoolOpt('inspection_runbench',
                        default=False,
                        deprecated_name='discovery_runbench',
                        help=_(
                            'Whether to run benchmarks when inspecting '
                            'nodes. Requires inspection_extras set to True.')
                        ),
            cfg.BoolOpt('enable_node_discovery',
                        default=False,
                        help=_(
                            'Makes ironic-inspector enroll any unknown node '
                            'that PXE-boots introspection ramdisk in Ironic. '
                            'By default, the "fake" driver is used for new '
                            'nodes (it is automatically enabled when this '
                            'option is set to True). Set '
                            'discovery_default_driver to override. '
                            'Introspection rules can also be used to specify '
                            'driver information for newly enrolled nodes.')
                        ),
            cfg.StrOpt('discovery_default_driver',
                       default='ipmi',
                       help=_(
                           'The default driver or hardware type to use for '
                           'newly discovered nodes (requires '
                           'enable_node_discovery set to True). It is '
                           'automatically added to enabled_hardware_types.')
                       ),
            cfg.BoolOpt('undercloud_debug',
                        default=True,
                        help=_(
                            'Whether to enable the debug log level for '
                            'Undercloud OpenStack services and Container '
                            'Image Prepare step.')
                        ),
            cfg.BoolOpt('undercloud_enable_selinux',
                        default=True,
                        help=_('Enable or disable SELinux during the '
                               'deployment.')),
            cfg.StrOpt('ipa_otp',
                       default='',
                       help=_(
                           'One Time Password to register Undercloud node '
                           'with an IPA server.')
                       ),
            cfg.BoolOpt('ipxe_enabled',
                        default=True,
                        help=_('Whether to use iPXE for deploy and '
                               'inspection.'),
                        deprecated_name='ipxe_deploy',
                        ),
            cfg.IntOpt('scheduler_max_attempts',
                       default=30, min=1,
                       help=_(
                           'Maximum number of attempts the scheduler will '
                           'make when deploying the instance. You should keep '
                           'it greater or equal to the number of bare metal '
                           'nodes you expect to deploy at once to work around '
                           'potential race condition when scheduling.')),
            cfg.BoolOpt('clean_nodes',
                        default=False,
                        help=_(
                            'Whether to clean overcloud nodes (wipe the hard '
                            'drive) between deployments and after the '
                            'introspection.')),
            cfg.ListOpt('enabled_hardware_types',
                        default=['ipmi', 'redfish', 'ilo', 'idrac'],
                        help=_('List of enabled bare metal hardware types '
                               '(next generation drivers).')),
            cfg.BoolOpt('enable_routed_networks',
                        default=False,
                        help=_(
                            'Enable support for routed ctlplane networks.')),
            cfg.BoolOpt('enable_swift_encryption',
                        default=False,
                        help=_(
                            'Whether to enable Swift encryption at-rest or '
                            'not.'
                        )),
            cfg.ListOpt('additional_architectures',
                        default=[],
                        help=(_(
                              'List of additional architectures enabled in '
                              'your cloud environment. The list of supported '
                              'values is: %s') %
                              ' '.join(constants.ADDITIONAL_ARCHITECTURES))
                        ),
            cfg.StrOpt('ipv6_address_mode',
                       default='dhcpv6-stateless',
                       choices=[
                           ('dhcpv6-stateless', 'Address configuration using '
                                                'RA and optional information '
                                                'using DHCPv6.'),
                           ('dhcpv6-stateful', 'Address configuration and '
                                               'optional information using '
                                               'DHCPv6.')
                       ],
                       help=(_('IPv6 address configuration mode for the '
                               'undercloud provisioning network.'))
                       ),
            cfg.ListOpt('ironic_enabled_network_interfaces',
                        default=['flat'],
                        help=(_('Enabled ironic network interface '
                                'implementations. Each hardware type must '
                                'have at least one valid implementation '
                                'enabled.'))
                        ),
            cfg.StrOpt('ironic_default_network_interface',
                       default='flat',
                       choices=[
                           ('flat', 'Use one flat provider network.'),
                           ('neutron', 'Ironic interacts with Neutron to '
                                       'enable other network types and '
                                       'advanced networking features.')
                       ],
                       help=(_('Ironic network interface implementation to '
                               'use by default.'))
                       ),
        ]
        return self.sort_opts(_base_opts + _opts)

    def get_opts(self):
        _base_opts = self.get_base_opts()
        _service_opts = self.get_undercloud_service_opts()
        return self.sort_opts(_base_opts + _service_opts)

    def get_local_subnet_opts(self):
        _subnets_opts = [
            cfg.StrOpt('cidr',
                       default=constants.CTLPLANE_CIDR_DEFAULT,
                       deprecated_opts=_deprecated_opt_network_cidr,
                       help=CIDR_HELP_STR),
            cfg.ListOpt('dhcp_start',
                        default=constants.CTLPLANE_DHCP_START_DEFAULT,
                        deprecated_opts=_deprecated_opt_dhcp_start,
                        help=DHCP_START_HELP_STR),
            cfg.ListOpt('dhcp_end',
                        default=constants.CTLPLANE_DHCP_END_DEFAULT,
                        deprecated_opts=_deprecated_opt_dhcp_end,
                        help=DHCP_END_HELP_STR),
            cfg.ListOpt('dhcp_exclude',
                        default=[],
                        help=DHCP_EXCLUDE_HELP_STR),
            cfg.StrOpt('inspection_iprange',
                       default=constants.CTLPLANE_INSPECTION_IPRANGE_DEFAULT,
                       deprecated_opts=_deprecated_opt_inspection_iprange,
                       help=INSPECTION_IPRANGE_HELP_STR),
            cfg.StrOpt('gateway',
                       default=constants.CTLPLANE_GATEWAY_DEFAULT,
                       deprecated_opts=_deprecated_opt_network_gateway,
                       help=GATEWAY_HELP_STR),
            cfg.BoolOpt('masquerade',
                        default=False,
                        help=MASQUERADE_HELP_STR),
            cfg.ListOpt('host_routes',
                        item_type=cfg.types.Dict(bounds=True),
                        bounds=True,
                        default=[],
                        sample_default=('[{destination: 10.10.10.0/24, '
                                        'nexthop: 192.168.24.1}]'),
                        help=HOST_ROUTES_HELP_STR),
            cfg.ListOpt('dns_nameservers',
                        default=constants.CTLPLANE_DNS_NAMESERVERS_DEFAULT,
                        help=DNS_NAMESERVERS_HELP_STR),
        ]
        return self.sort_opts(_subnets_opts)

    def get_remote_subnet_opts(self):
        _subnets_opts = [
            cfg.StrOpt('cidr',
                       help=CIDR_HELP_STR),
            cfg.ListOpt('dhcp_start',
                        default=[],
                        help=DHCP_START_HELP_STR),
            cfg.ListOpt('dhcp_end',
                        default=[],
                        help=DHCP_END_HELP_STR),
            cfg.ListOpt('dhcp_exclude',
                        default=[],
                        help=DHCP_EXCLUDE_HELP_STR),
            cfg.StrOpt('inspection_iprange',
                       help=INSPECTION_IPRANGE_HELP_STR),
            cfg.StrOpt('gateway',
                       help=GATEWAY_HELP_STR),
            cfg.BoolOpt('masquerade',
                        default=False,
                        help=MASQUERADE_HELP_STR),
            cfg.ListOpt('host_routes',
                        item_type=cfg.types.Dict(bounds=True),
                        bounds=True,
                        default=[],
                        help=HOST_ROUTES_HELP_STR),
            cfg.ListOpt('dns_nameservers',
                        default=constants.CTLPLANE_DNS_NAMESERVERS_DEFAULT,
                        help=DNS_NAMESERVERS_HELP_STR),
        ]
        return self.sort_opts(_subnets_opts)


def list_opts():
    """List config opts for oslo config generator"""
    config = UndercloudConfig()
    _opts = config.get_opts()
    return [(None, copy.deepcopy(_opts)),
            (SUBNETS_DEFAULT[0],
             copy.deepcopy(config.get_local_subnet_opts()))]


def load_global_config():
    """Register UndercloudConfig options into global config"""
    _opts = UndercloudConfig().get_opts()
    CONF.register_opts(_opts)
