#   Copyright 2017 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.
#
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography import x509
from cryptography.x509.oid import NameOID
from datetime import datetime
from datetime import timedelta
import fixtures
import mock
import os
import tempfile
import yaml

from oslo_config import cfg
from oslo_config import fixture as oslo_fixture

from tripleo_common.image import kolla_builder

from tripleoclient import exceptions
from tripleoclient.tests import base
from tripleoclient.v1 import undercloud_config


class TestProcessDriversAndHardwareTypes(base.TestCase):
    def setUp(self):
        super(TestProcessDriversAndHardwareTypes, self).setUp()
        self.conf = mock.Mock(**{key: getattr(undercloud_config.CONF, key)
                                 for key in (
                                     'enabled_hardware_types',
                                     'enable_node_discovery',
                                     'discovery_default_driver',
                                     'ironic_enabled_network_interfaces',
                                     'ironic_default_network_interface')})

    def test_defaults(self):
        env = {}
        undercloud_config._process_drivers_and_hardware_types(self.conf, env)
        self.assertEqual({
            'IronicEnabledNetworkInterfaces': ['flat'],
            'IronicDefaultNetworkInterface': 'flat',
            'IronicEnabledHardwareTypes': ['idrac', 'ilo', 'ipmi', 'redfish'],
            'IronicEnabledBootInterfaces': ['ilo-pxe', 'ipxe', 'pxe'],
            'IronicEnabledBiosInterfaces': ['ilo', 'no-bios', 'redfish'],
            'IronicEnabledDeployInterfaces': ['ansible', 'direct', 'iscsi'],
            'IronicEnabledInspectInterfaces': ['idrac', 'ilo', 'inspector',
                                               'no-inspect', 'redfish'],
            'IronicEnabledManagementInterfaces': ['fake', 'idrac', 'ilo',
                                                  'ipmitool', 'noop',
                                                  'redfish'],
            'IronicEnabledPowerInterfaces': ['fake', 'idrac', 'ilo',
                                             'ipmitool', 'redfish'],
            'IronicEnabledRaidInterfaces': ['idrac', 'no-raid'],
            'IronicEnabledVendorInterfaces': ['idrac', 'ipmitool', 'no-vendor']
        }, env)

    def test_one_hardware_type_with_discovery(self):
        env = {}
        self.conf.enabled_hardware_types = ['redfish']
        self.conf.enable_node_discovery = True

        undercloud_config._process_drivers_and_hardware_types(self.conf, env)
        self.assertEqual({
            'IronicEnabledNetworkInterfaces': ['flat'],
            'IronicDefaultNetworkInterface': 'flat',
            # ipmi added because it's the default discovery driver
            'IronicEnabledHardwareTypes': ['ipmi', 'redfish'],
            'IronicEnabledBootInterfaces': ['ipxe', 'pxe'],
            'IronicEnabledBiosInterfaces': ['no-bios', 'redfish'],
            'IronicEnabledDeployInterfaces': ['ansible', 'direct', 'iscsi'],
            'IronicEnabledInspectInterfaces': ['inspector', 'no-inspect',
                                               'redfish'],
            'IronicEnabledManagementInterfaces': ['fake', 'ipmitool',
                                                  'noop', 'redfish'],
            'IronicEnabledPowerInterfaces': ['fake', 'ipmitool', 'redfish'],
            'IronicEnabledRaidInterfaces': ['no-raid'],
            'IronicEnabledVendorInterfaces': ['ipmitool', 'no-vendor'],
            'IronicInspectorDiscoveryDefaultDriver': 'ipmi',
            'IronicInspectorEnableNodeDiscovery': True
        }, env)

    def test_all_hardware_types(self):
        env = {}
        self.conf.enabled_hardware_types = (
            self.conf.enabled_hardware_types + ['staging-ovirt', 'snmp',
                                                'irmc', 'xclarity',
                                                'fake-hardware']
        )

        undercloud_config._process_drivers_and_hardware_types(self.conf, env)
        self.assertEqual({
            'IronicEnabledNetworkInterfaces': ['flat'],
            'IronicDefaultNetworkInterface': 'flat',
            'IronicEnabledHardwareTypes': ['fake-hardware', 'idrac', 'ilo',
                                           'ipmi', 'irmc', 'redfish', 'snmp',
                                           'staging-ovirt', 'xclarity'],
            'IronicEnabledBootInterfaces': ['fake', 'ilo-pxe', 'ipxe',
                                            'irmc-pxe', 'pxe'],
            'IronicEnabledBiosInterfaces': ['ilo', 'irmc',
                                            'no-bios', 'redfish'],
            'IronicEnabledDeployInterfaces': ['ansible', 'direct', 'fake',
                                              'iscsi'],
            'IronicEnabledInspectInterfaces': ['idrac', 'ilo', 'inspector',
                                               'irmc', 'no-inspect',
                                               'redfish'],
            'IronicEnabledManagementInterfaces': ['fake', 'idrac',
                                                  'ilo', 'ipmitool', 'irmc',
                                                  'noop', 'redfish',
                                                  'staging-ovirt', 'xclarity'],
            'IronicEnabledPowerInterfaces': ['fake', 'idrac',
                                             'ilo', 'ipmitool', 'irmc',
                                             'redfish', 'snmp',
                                             'staging-ovirt', 'xclarity'],
            'IronicEnabledRaidInterfaces': ['idrac', 'no-raid'],
            'IronicEnabledVendorInterfaces': ['idrac', 'ipmitool', 'no-vendor']
        }, env)


class TestBaseNetworkSettings(base.TestCase):
    def setUp(self):
        super(TestBaseNetworkSettings, self).setUp()
        self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF))
        # don't actually load config from ~/undercloud.conf
        self.mock_config_load = self.useFixture(
            fixtures.MockPatch('tripleoclient.utils.load_config'))
        self.conf.config(local_ip='192.168.24.1/24',
                         undercloud_admin_host='192.168.24.3',
                         undercloud_public_host='192.168.24.2',
                         undercloud_nameservers=['10.10.10.10', '10.10.10.11'])
        # ctlplane network - config group options
        self.grp0 = cfg.OptGroup(name='ctlplane-subnet',
                                 title='ctlplane-subnet')
        self.opts = [cfg.StrOpt('cidr'),
                     cfg.ListOpt('dhcp_start'),
                     cfg.ListOpt('dhcp_end'),
                     cfg.ListOpt('dhcp_exclude'),
                     cfg.StrOpt('inspection_iprange'),
                     cfg.StrOpt('gateway'),
                     cfg.BoolOpt('masquerade'),
                     cfg.ListOpt('host_routes',
                                 item_type=cfg.types.Dict(bounds=True),
                                 bounds=True,),
                     cfg.ListOpt('dns_nameservers')]
        self.conf.register_opts(self.opts, group=self.grp0)
        self.grp1 = cfg.OptGroup(name='subnet1', title='subnet1')
        self.grp2 = cfg.OptGroup(name='subnet2', title='subnet2')
        self.conf.config(cidr='192.168.24.0/24',
                         dhcp_start='192.168.24.5',
                         dhcp_end='192.168.24.24',
                         dhcp_exclude=[],
                         inspection_iprange='192.168.24.100,192.168.24.120',
                         gateway='192.168.24.1',
                         masquerade=False,
                         host_routes=[],
                         dns_nameservers=[],
                         group='ctlplane-subnet')


class TestNetworkSettings(TestBaseNetworkSettings):
    def test_default(self):
        env = {}
        undercloud_config._process_network_args(env)
        expected = {
            'ControlPlaneStaticRoutes': [],
            'IronicInspectorSubnets': [
                {'gateway': '192.168.24.1',
                 'host_routes': [],
                 'ip_range': '192.168.24.100,192.168.24.120',
                 'netmask': '255.255.255.0',
                 'tag': 'ctlplane-subnet',
                 'mtu': 1500}],
            'MasqueradeNetworks': {},
            'PortPhysnetCidrMap': {'192.168.24.0/24': 'ctlplane'},
            'UndercloudCtlplaneSubnets': {
                'ctlplane-subnet': {
                    'AllocationPools': [
                        {'start': '192.168.24.5', 'end': '192.168.24.24'}],
                    'DnsNameServers': ['10.10.10.10', '10.10.10.11'],
                    'HostRoutes': [],
                    'NetworkCidr': '192.168.24.0/24',
                    'NetworkGateway': '192.168.24.1'}},
            'UndercloudCtlplaneIPv6AddressMode': 'dhcpv6-stateless',
        }
        self.assertEqual(expected, env)

    def test_ipv6_control_plane_stateless_default(self):
        env = {}
        self.conf.config(local_ip='fd12:3456:789a:1::2/64',
                         undercloud_admin_host='fd12:3456:789a:1::3',
                         undercloud_public_host='fd12:3456:789a:1::4')
        self.conf.config(cidr='fd12:3456:789a:1::/64',
                         dhcp_start='fd12:3456:789a:1::10',
                         dhcp_end='fd12:3456:789a:1::20',
                         dhcp_exclude=[],
                         dns_nameservers=['fd12:3456:789a:1::5',
                                          'fd12:3456:789a:1::6'],
                         inspection_iprange=('fd12:3456:789a:1::30,'
                                             'fd12:3456:789a:1::40'),
                         gateway='fd12:3456:789a:1::1',
                         masquerade=False,
                         host_routes=[],
                         group='ctlplane-subnet')
        undercloud_config._process_network_args(env)
        expected = {
            'NovaIPv6': True,
            'RabbitIPv6': True,
            'MemcachedIPv6': True,
            'RedisIPv6': True,
            'MysqlIPv6': True,
            'IronicIpVersion': '6',
            'ControlPlaneStaticRoutes': [],
            'IronicInspectorSubnets': [
                {'gateway': 'fd12:3456:789a:1::1',
                 'host_routes': [],
                 'ip_range': 'fd12:3456:789a:1::,static',
                 'netmask': 'ffff:ffff:ffff:ffff::',
                 'tag': 'ctlplane-subnet',
                 'mtu': 1500}],
            'MasqueradeNetworks': {},
            'PortPhysnetCidrMap': {'fd12:3456:789a:1::/64': 'ctlplane'},
            'UndercloudCtlplaneSubnets': {
                'ctlplane-subnet': {
                    'AllocationPools': [
                        {'start': 'fd12:3456:789a:1::10',
                         'end': 'fd12:3456:789a:1::20'}],
                    'DnsNameServers': ['fd12:3456:789a:1::5',
                                       'fd12:3456:789a:1::6'],
                    'HostRoutes': [],
                    'NetworkCidr': 'fd12:3456:789a:1::/64',
                    'NetworkGateway': 'fd12:3456:789a:1::1'}},
            'UndercloudCtlplaneIPv6AddressMode': 'dhcpv6-stateless',
        }
        self.assertEqual(expected, env)

    def test_ipv6_control_plane_stateful(self):
        env = {}
        self.conf.config(local_ip='fd12:3456:789a:1::2/64',
                         undercloud_admin_host='fd12:3456:789a:1::3',
                         undercloud_public_host='fd12:3456:789a:1::4',
                         ipv6_address_mode='dhcpv6-stateful')
        self.conf.config(cidr='fd12:3456:789a:1::/64',
                         dhcp_start='fd12:3456:789a:1::10',
                         dhcp_end='fd12:3456:789a:1::20',
                         dhcp_exclude=[],
                         dns_nameservers=['fd12:3456:789a:1::5',
                                          'fd12:3456:789a:1::6'],
                         inspection_iprange=('fd12:3456:789a:1::30,'
                                             'fd12:3456:789a:1::40'),
                         gateway='fd12:3456:789a:1::1',
                         masquerade=False,
                         host_routes=[],
                         group='ctlplane-subnet')
        undercloud_config._process_network_args(env)
        expected = {
            'NovaIPv6': True,
            'RabbitIPv6': True,
            'MemcachedIPv6': True,
            'RedisIPv6': True,
            'MysqlIPv6': True,
            'IronicIpVersion': '6',
            'ControlPlaneStaticRoutes': [],
            'IronicInspectorSubnets': [
                {'gateway': 'fd12:3456:789a:1::1',
                 'host_routes': [],
                 'ip_range': 'fd12:3456:789a:1::30,fd12:3456:789a:1::40',
                 'netmask': 'ffff:ffff:ffff:ffff::',
                 'tag': 'ctlplane-subnet',
                 'mtu': 1500}],
            'MasqueradeNetworks': {},
            'PortPhysnetCidrMap': {'fd12:3456:789a:1::/64': 'ctlplane'},
            'UndercloudCtlplaneSubnets': {
                'ctlplane-subnet': {
                    'AllocationPools': [
                        {'start': 'fd12:3456:789a:1::10',
                         'end': 'fd12:3456:789a:1::20'}],
                    'DnsNameServers': ['fd12:3456:789a:1::5',
                                       'fd12:3456:789a:1::6'],
                    'HostRoutes': [],
                    'NetworkCidr': 'fd12:3456:789a:1::/64',
                    'NetworkGateway': 'fd12:3456:789a:1::1'}},
            'UndercloudCtlplaneIPv6AddressMode': 'dhcpv6-stateful',
        }
        self.assertEqual(expected, env)

    def test_nameserver_toomany_fail(self):
        env = {}
        self.conf.config(undercloud_nameservers=['1.1.1.1', '1.1.1.2',
                                                 '1.1.1.3', '1.1.1.4',
                                                 '1.1.1.5', '1.1.1.6'])
        self.assertRaises(exceptions.InvalidConfiguration,
                          undercloud_config._process_network_args,
                          env)

    def test_undercloud_ips_duplicated_fail(self):
        env = {}

        # local_ip == undercloud_admin_host
        self.conf.config(local_ip='192.168.24.1/24',
                         undercloud_admin_host='192.168.24.1',
                         undercloud_public_host='192.168.24.2',
                         generate_service_certificate=True)
        self.assertRaises(exceptions.InvalidConfiguration,
                          undercloud_config._process_network_args,
                          env)

        # local_ip == undercloud_public_host
        self.conf.config(local_ip='192.168.24.1/24',
                         undercloud_admin_host='192.168.24.3',
                         undercloud_public_host='192.168.24.1',
                         generate_service_certificate=True)
        undercloud_config._process_network_args(env)

        # undercloud_admin_host == undercloud_public_host
        self.conf.config(local_ip='192.168.24.1/24',
                         undercloud_admin_host='192.168.24.2',
                         undercloud_public_host='192.168.24.2',
                         generate_service_certificate=True)
        undercloud_config._process_network_args(env)

        # We do not care about ip duplication when ssl is disabled
        self.conf.config(local_ip='192.168.24.1/24',
                         undercloud_admin_host='192.168.24.1',
                         undercloud_public_host='192.168.24.2',
                         generate_service_certificate=False,
                         undercloud_service_certificate='')
        undercloud_config._process_network_args(env)

    def test_start_end_all_addresses(self):
        self.conf.config(dhcp_start='192.168.24.0',
                         dhcp_end='192.168.24.255',
                         group='ctlplane-subnet')
        env = {}
        undercloud_config._process_network_args(env)
        expected = {
            'ControlPlaneStaticRoutes': [],
            'IronicInspectorSubnets': [
                {'gateway': '192.168.24.1',
                 'host_routes': [],
                 'ip_range': '192.168.24.100,192.168.24.120',
                 'netmask': '255.255.255.0',
                 'tag': 'ctlplane-subnet',
                 'mtu': 1500}],
            'MasqueradeNetworks': {},
            'PortPhysnetCidrMap': {'192.168.24.0/24': 'ctlplane'},
            'UndercloudCtlplaneSubnets': {
                'ctlplane-subnet': {
                    'AllocationPools': [
                        {'start': '192.168.24.4', 'end': '192.168.24.99'},
                        {'start': '192.168.24.121', 'end': '192.168.24.254'}],
                    'DnsNameServers': ['10.10.10.10', '10.10.10.11'],
                    'HostRoutes': [],
                    'NetworkCidr': '192.168.24.0/24',
                    'NetworkGateway': '192.168.24.1'}},
            'UndercloudCtlplaneIPv6AddressMode': 'dhcpv6-stateless',
        }
        self.assertEqual(expected, env)

    def test_ignore_dhcp_start_end_if_default_but_cidr_not_default(self):
        self.conf.config(cidr='192.168.10.0/24',
                         inspection_iprange='192.168.10.100,192.168.10.120',
                         gateway='192.168.10.1',
                         group='ctlplane-subnet')
        env = {}
        undercloud_config._process_network_args(env)
        expected = {
            'ControlPlaneStaticRoutes': [],
            'IronicInspectorSubnets': [
                {'gateway': '192.168.10.1',
                 'host_routes': [],
                 'ip_range': '192.168.10.100,192.168.10.120',
                 'netmask': '255.255.255.0',
                 'tag': 'ctlplane-subnet',
                 'mtu': 1500}],
            'MasqueradeNetworks': {},
            'PortPhysnetCidrMap': {'192.168.10.0/24': 'ctlplane'},
            'UndercloudCtlplaneSubnets': {
                'ctlplane-subnet': {
                    'AllocationPools': [
                        {'start': '192.168.10.2', 'end': '192.168.10.99'},
                        {'start': '192.168.10.121', 'end': '192.168.10.254'}],
                    'DnsNameServers': ['10.10.10.10', '10.10.10.11'],
                    'HostRoutes': [],
                    'NetworkCidr': '192.168.10.0/24',
                    'NetworkGateway': '192.168.10.1'}},
            'UndercloudCtlplaneIPv6AddressMode': 'dhcpv6-stateless',
        }
        self.assertEqual(expected, env)

    def test_dhcp_exclude(self):
        self.conf.config(cidr='192.168.10.0/24',
                         inspection_iprange='192.168.10.100,192.168.10.120',
                         gateway='192.168.10.1',
                         dhcp_exclude=['192.168.10.50',
                                       '192.168.10.80-192.168.10.89'],
                         group='ctlplane-subnet')
        env = {}
        undercloud_config._process_network_args(env)
        expected = {
            'ControlPlaneStaticRoutes': [],
            'IronicInspectorSubnets': [
                {'gateway': '192.168.10.1',
                 'host_routes': [],
                 'ip_range': '192.168.10.100,192.168.10.120',
                 'netmask': '255.255.255.0',
                 'tag': 'ctlplane-subnet',
                 'mtu': 1500}],
            'MasqueradeNetworks': {},
            'PortPhysnetCidrMap': {'192.168.10.0/24': 'ctlplane'},
            'UndercloudCtlplaneSubnets': {
                'ctlplane-subnet': {
                    'AllocationPools': [
                        {'start': '192.168.10.2', 'end': '192.168.10.49'},
                        {'start': '192.168.10.51', 'end': '192.168.10.79'},
                        {'start': '192.168.10.90', 'end': '192.168.10.99'},
                        {'start': '192.168.10.121', 'end': '192.168.10.254'}],
                    'DnsNameServers': ['10.10.10.10', '10.10.10.11'],
                    'HostRoutes': [],
                    'NetworkCidr': '192.168.10.0/24',
                    'NetworkGateway': '192.168.10.1'}},
            'UndercloudCtlplaneIPv6AddressMode': 'dhcpv6-stateless',
        }
        self.assertEqual(expected, env)

    def test_no_dhcp_start_no_dhcp_end(self):
        self.conf.config(dhcp_start=[],
                         dhcp_end=[],
                         group='ctlplane-subnet')
        env = {}
        undercloud_config._process_network_args(env)
        expected = {
            'ControlPlaneStaticRoutes': [],
            'IronicInspectorSubnets': [
                {'gateway': '192.168.24.1',
                 'host_routes': [],
                 'ip_range': '192.168.24.100,192.168.24.120',
                 'netmask': '255.255.255.0',
                 'tag': 'ctlplane-subnet',
                 'mtu': 1500}],
            'MasqueradeNetworks': {},
            'PortPhysnetCidrMap': {'192.168.24.0/24': 'ctlplane'},
            'UndercloudCtlplaneSubnets': {
                'ctlplane-subnet': {
                    'AllocationPools': [
                        {'start': '192.168.24.4', 'end': '192.168.24.99'},
                        {'start': '192.168.24.121', 'end': '192.168.24.254'}],
                    'DnsNameServers': ['10.10.10.10', '10.10.10.11'],
                    'HostRoutes': [],
                    'NetworkCidr': '192.168.24.0/24',
                    'NetworkGateway': '192.168.24.1'}},
            'UndercloudCtlplaneIPv6AddressMode': 'dhcpv6-stateless',
        }
        self.assertEqual(expected, env)

    def test_dhcp_start_no_dhcp_end(self):
        self.conf.config(dhcp_start='192.168.24.10',
                         dhcp_end=[],
                         group='ctlplane-subnet')
        env = {}
        undercloud_config._process_network_args(env)
        expected = {
            'ControlPlaneStaticRoutes': [],
            'IronicInspectorSubnets': [
                {'gateway': '192.168.24.1',
                 'host_routes': [],
                 'ip_range': '192.168.24.100,192.168.24.120',
                 'netmask': '255.255.255.0',
                 'tag': 'ctlplane-subnet',
                 'mtu': 1500}],
            'MasqueradeNetworks': {},
            'PortPhysnetCidrMap': {'192.168.24.0/24': 'ctlplane'},
            'UndercloudCtlplaneSubnets': {
                'ctlplane-subnet': {
                    'AllocationPools': [
                        {'start': '192.168.24.10', 'end': '192.168.24.99'},
                        {'start': '192.168.24.121', 'end': '192.168.24.254'}],
                    'DnsNameServers': ['10.10.10.10', '10.10.10.11'],
                    'HostRoutes': [],
                    'NetworkCidr': '192.168.24.0/24',
                    'NetworkGateway': '192.168.24.1'},
            },
            'UndercloudCtlplaneIPv6AddressMode': 'dhcpv6-stateless',
        }
        self.assertEqual(expected, env)

    def test_dhcp_end_no_dhcp_start(self):
        self.conf.config(dhcp_start=[],
                         dhcp_end='192.168.24.220',
                         group='ctlplane-subnet')
        env = {}
        undercloud_config._process_network_args(env)
        expected = {
            'ControlPlaneStaticRoutes': [],
            'IronicInspectorSubnets': [
                {'gateway': '192.168.24.1',
                 'host_routes': [],
                 'ip_range': '192.168.24.100,192.168.24.120',
                 'netmask': '255.255.255.0',
                 'tag': 'ctlplane-subnet',
                 'mtu': 1500}],
            'MasqueradeNetworks': {},
            'PortPhysnetCidrMap': {'192.168.24.0/24': 'ctlplane'},
            'UndercloudCtlplaneSubnets': {
                'ctlplane-subnet': {
                    'AllocationPools': [
                        {'start': '192.168.24.4', 'end': '192.168.24.99'},
                        {'start': '192.168.24.121', 'end': '192.168.24.220'}],
                    'DnsNameServers': ['10.10.10.10', '10.10.10.11'],
                    'HostRoutes': [],
                    'NetworkCidr': '192.168.24.0/24',
                    'NetworkGateway': '192.168.24.1'},
            },
            'UndercloudCtlplaneIPv6AddressMode': 'dhcpv6-stateless',
        }
        self.assertEqual(expected, env)

    def test_routed_network(self):
        self.conf.config(subnets=['ctlplane-subnet', 'subnet1', 'subnet2'])
        self.conf.register_opts(self.opts, group=self.grp1)
        self.conf.register_opts(self.opts, group=self.grp2)
        self.conf.config(masquerade=True,
                         dns_nameservers=['10.1.1.100', '10.1.1.101'],
                         group='ctlplane-subnet')
        self.conf.config(cidr='192.168.10.0/24',
                         dhcp_start='192.168.10.10',
                         dhcp_end='192.168.10.99',
                         dhcp_exclude=[],
                         inspection_iprange='192.168.10.100,192.168.10.189',
                         gateway='192.168.10.254',
                         dns_nameservers=['10.2.2.100', '10.2.2.101'],
                         host_routes=[],
                         masquerade=True,
                         group='subnet1')
        self.conf.config(cidr='192.168.20.0/24',
                         dhcp_start='192.168.20.10',
                         dhcp_end='192.168.20.99',
                         dhcp_exclude=[],
                         inspection_iprange='192.168.20.100,192.168.20.189',
                         gateway='192.168.20.254',
                         dns_nameservers=['10.3.3.100', '10.3.3.101'],
                         host_routes=[],
                         masquerade=True,
                         group='subnet2')
        env = {}
        undercloud_config._process_network_args(env)
        expected = {
            'ControlPlaneStaticRoutes': [
                {'destination': '192.168.10.0/24', 'nexthop': '192.168.24.1'},
                {'destination': '192.168.20.0/24', 'nexthop': '192.168.24.1'}],
            'IronicInspectorSubnets': [
                {'gateway': '192.168.24.1',
                 'host_routes': [],
                 'ip_range': '192.168.24.100,192.168.24.120',
                 'netmask': '255.255.255.0',
                 'tag': 'ctlplane-subnet',
                 'mtu': 1500},
                {'gateway': '192.168.10.254',
                 'host_routes': [],
                 'ip_range': '192.168.10.100,192.168.10.189',
                 'netmask': '255.255.255.0',
                 'tag': 'subnet1',
                 'mtu': 1500},
                {'gateway': '192.168.20.254',
                 'host_routes': [],
                 'ip_range': '192.168.20.100,192.168.20.189',
                 'netmask': '255.255.255.0',
                 'tag': 'subnet2',
                 'mtu': 1500}
            ],
            'MasqueradeNetworks': {
                '192.168.10.0/24': ['192.168.24.0/24',
                                    '192.168.10.0/24',
                                    '192.168.20.0/24'],
                '192.168.20.0/24': ['192.168.24.0/24',
                                    '192.168.10.0/24',
                                    '192.168.20.0/24'],
                '192.168.24.0/24': ['192.168.24.0/24',
                                    '192.168.10.0/24',
                                    '192.168.20.0/24']},
            'PortPhysnetCidrMap': {'192.168.10.0/24': 'subnet1',
                                   '192.168.20.0/24': 'subnet2',
                                   '192.168.24.0/24': 'ctlplane'},
            'UndercloudCtlplaneSubnets': {
                # The ctlplane-subnet subnet have defaults
                'ctlplane-subnet': {
                    'AllocationPools': [
                        {'start': '192.168.24.5', 'end': '192.168.24.24'}],
                    'DnsNameServers': ['10.1.1.100', '10.1.1.101'],
                    'HostRoutes': [],
                    'NetworkCidr': '192.168.24.0/24',
                    'NetworkGateway': '192.168.24.1'},
                'subnet1': {
                    'AllocationPools': [
                        {'start': '192.168.10.10', 'end': '192.168.10.99'}],
                    'DnsNameServers': ['10.2.2.100', '10.2.2.101'],
                    'HostRoutes': [],
                    'NetworkCidr': '192.168.10.0/24',
                    'NetworkGateway': '192.168.10.254'},
                'subnet2': {
                    'AllocationPools': [
                        {'start': '192.168.20.10', 'end': '192.168.20.99'}],
                    'DnsNameServers': ['10.3.3.100', '10.3.3.101'],
                    'HostRoutes': [],
                    'NetworkCidr': '192.168.20.0/24',
                    'NetworkGateway': '192.168.20.254'}
            },
            'UndercloudCtlplaneIPv6AddressMode': 'dhcpv6-stateless',
        }
        self.assertEqual(expected, env)

    def test_routed_network_no_masquerading(self):
        self.conf.config(subnets=['ctlplane-subnet', 'subnet1', 'subnet2'])
        self.conf.register_opts(self.opts, group=self.grp1)
        self.conf.register_opts(self.opts, group=self.grp2)
        self.conf.config(cidr='192.168.10.0/24',
                         dhcp_start='192.168.10.10',
                         dhcp_end='192.168.10.99',
                         dhcp_exclude=[],
                         inspection_iprange='192.168.10.100,192.168.10.189',
                         gateway='192.168.10.254',
                         dns_nameservers=[],
                         host_routes=[],
                         group='subnet1')
        self.conf.config(cidr='192.168.20.0/24',
                         dhcp_start='192.168.20.10',
                         dhcp_end='192.168.20.99',
                         dhcp_exclude=[],
                         inspection_iprange='192.168.20.100,192.168.20.189',
                         gateway='192.168.20.254',
                         dns_nameservers=[],
                         host_routes=[],
                         group='subnet2')
        env = {}
        undercloud_config._process_network_args(env)
        expected = {
            'ControlPlaneStaticRoutes': [
                {'destination': '192.168.10.0/24', 'nexthop': '192.168.24.1'},
                {'destination': '192.168.20.0/24', 'nexthop': '192.168.24.1'}],
            'IronicInspectorSubnets': [
                {'gateway': '192.168.24.1',
                 'host_routes': [],
                 'ip_range': '192.168.24.100,192.168.24.120',
                 'netmask': '255.255.255.0',
                 'tag': 'ctlplane-subnet',
                 'mtu': 1500},
                {'gateway': '192.168.10.254',
                 'host_routes': [],
                 'ip_range': '192.168.10.100,192.168.10.189',
                 'netmask': '255.255.255.0',
                 'tag': 'subnet1',
                 'mtu': 1500},
                {'gateway': '192.168.20.254',
                 'host_routes': [],
                 'ip_range': '192.168.20.100,192.168.20.189',
                 'netmask': '255.255.255.0',
                 'tag': 'subnet2',
                 'mtu': 1500}
            ],
            'MasqueradeNetworks': {},
            'PortPhysnetCidrMap': {'192.168.10.0/24': 'subnet1',
                                   '192.168.20.0/24': 'subnet2',
                                   '192.168.24.0/24': 'ctlplane'},
            'UndercloudCtlplaneSubnets': {
                # The ctlplane-subnet subnet have defaults
                'ctlplane-subnet': {
                    'AllocationPools': [
                        {'start': '192.168.24.5', 'end': '192.168.24.24'}],
                    'DnsNameServers': ['10.10.10.10', '10.10.10.11'],
                    'HostRoutes': [],
                    'NetworkCidr': '192.168.24.0/24',
                    'NetworkGateway': '192.168.24.1'},
                'subnet1': {
                    'AllocationPools': [
                        {'start': '192.168.10.10', 'end': '192.168.10.99'}],
                    'DnsNameServers': ['10.10.10.10', '10.10.10.11'],
                    'HostRoutes': [],
                    'NetworkCidr': '192.168.10.0/24',
                    'NetworkGateway': '192.168.10.254'},
                'subnet2': {
                    'AllocationPools': [
                        {'start': '192.168.20.10', 'end': '192.168.20.99'}],
                    'DnsNameServers': ['10.10.10.10', '10.10.10.11'],
                    'HostRoutes': [],
                    'NetworkCidr': '192.168.20.0/24',
                    'NetworkGateway': '192.168.20.254'}
            },
            'UndercloudCtlplaneIPv6AddressMode': 'dhcpv6-stateless',
        }
        self.assertEqual(expected, env)

    def test_no_allocation_pool_on_remote_network(self):
        self.conf.config(subnets=['ctlplane-subnet', 'subnet1'])
        self.conf.register_opts(self.opts, group=self.grp1)
        self.conf.config(cidr='192.168.10.0/24',
                         dhcp_exclude=[],
                         inspection_iprange='192.168.10.200,192.168.10.254',
                         gateway='192.168.10.254',
                         dns_nameservers=[],
                         host_routes=[],
                         masquerade=False,
                         group='subnet1')
        env = {}
        undercloud_config._process_network_args(env)
        expected = {
            'ControlPlaneStaticRoutes': [
                {'destination': '192.168.10.0/24', 'nexthop': '192.168.24.1'}],
            'IronicInspectorSubnets': [
                {'gateway': '192.168.24.1',
                 'host_routes': [],
                 'ip_range': '192.168.24.100,192.168.24.120',
                 'netmask': '255.255.255.0',
                 'tag': 'ctlplane-subnet',
                 'mtu': 1500},
                {'gateway': '192.168.10.254',
                 'host_routes': [],
                 'ip_range': '192.168.10.200,192.168.10.254',
                 'netmask': '255.255.255.0',
                 'tag': 'subnet1',
                 'mtu': 1500},
            ],
            'MasqueradeNetworks': {},
            'PortPhysnetCidrMap': {'192.168.10.0/24': 'subnet1',
                                   '192.168.24.0/24': 'ctlplane'},
            'UndercloudCtlplaneSubnets': {
                # The ctlplane-subnet subnet have defaults
                'ctlplane-subnet': {
                    'AllocationPools': [
                        {'start': '192.168.24.5', 'end': '192.168.24.24'}],
                    'DnsNameServers': ['10.10.10.10', '10.10.10.11'],
                    'HostRoutes': [],
                    'NetworkCidr': '192.168.24.0/24',
                    'NetworkGateway': '192.168.24.1'},
                'subnet1': {
                    'AllocationPools': [
                        {'start': '192.168.10.1', 'end': '192.168.10.199'}],
                    'DnsNameServers': ['10.10.10.10', '10.10.10.11'],
                    'HostRoutes': [],
                    'NetworkCidr': '192.168.10.0/24',
                    'NetworkGateway': '192.168.10.254'}
            },
            'UndercloudCtlplaneIPv6AddressMode': 'dhcpv6-stateless',
        }
        self.assertEqual(expected, env)

    def test_no_allocation_pool_on_remote_network_three_pools(self):
        self.conf.config(subnets=['ctlplane-subnet', 'subnet1'])
        self.conf.register_opts(self.opts, group=self.grp1)
        self.conf.config(cidr='192.168.10.0/24',
                         dhcp_exclude=[],
                         inspection_iprange='192.168.10.100,192.168.10.199',
                         gateway='192.168.10.222',
                         dns_nameservers=[],
                         host_routes=[],
                         masquerade=False,
                         group='subnet1')
        env = {}
        undercloud_config._process_network_args(env)
        expected = {
            'ControlPlaneStaticRoutes': [
                {'destination': '192.168.10.0/24', 'nexthop': '192.168.24.1'}],
            'IronicInspectorSubnets': [
                {'gateway': '192.168.24.1',
                 'host_routes': [],
                 'ip_range': '192.168.24.100,192.168.24.120',
                 'netmask': '255.255.255.0',
                 'tag': 'ctlplane-subnet',
                 'mtu': 1500},
                {'gateway': '192.168.10.222',
                 'host_routes': [],
                 'ip_range': '192.168.10.100,192.168.10.199',
                 'netmask': '255.255.255.0',
                 'tag': 'subnet1',
                 'mtu': 1500},
            ],
            'MasqueradeNetworks': {},
            'PortPhysnetCidrMap': {'192.168.10.0/24': 'subnet1',
                                   '192.168.24.0/24': 'ctlplane'},
            'UndercloudCtlplaneSubnets': {
                # The ctlplane-subnet subnet have defaults
                'ctlplane-subnet': {
                    'AllocationPools': [
                        {'start': '192.168.24.5', 'end': '192.168.24.24'}],
                    'DnsNameServers': ['10.10.10.10', '10.10.10.11'],
                    'HostRoutes': [],
                    'NetworkCidr': '192.168.24.0/24',
                    'NetworkGateway': '192.168.24.1'},
                'subnet1': {
                    'AllocationPools': [
                        {'start': '192.168.10.1', 'end': '192.168.10.99'},
                        {'start': '192.168.10.200', 'end': '192.168.10.221'},
                        {'start': '192.168.10.223', 'end': '192.168.10.254'}],
                    'DnsNameServers': ['10.10.10.10', '10.10.10.11'],
                    'HostRoutes': [],
                    'NetworkCidr': '192.168.10.0/24',
                    'NetworkGateway': '192.168.10.222'}
            },
            'UndercloudCtlplaneIPv6AddressMode': 'dhcpv6-stateless',
        }
        self.assertEqual(expected, env)

    def test_additional_host_routes(self):
        self.conf.config(subnets=['ctlplane-subnet', 'subnet1', 'subnet2'])
        self.conf.config(host_routes=[{'destination': '10.10.10.254/32',
                                       'nexthop': '192.168.24.1'}],
                         group='ctlplane-subnet')
        self.conf.register_opts(self.opts, group=self.grp1)
        self.conf.register_opts(self.opts, group=self.grp2)
        self.conf.config(cidr='192.168.10.0/24',
                         dhcp_start='192.168.10.10',
                         dhcp_end='192.168.10.99',
                         dhcp_exclude=[],
                         inspection_iprange='192.168.10.100,192.168.10.189',
                         gateway='192.168.10.254',
                         dns_nameservers=[],
                         host_routes=[{'destination': '10.10.10.254/32',
                                       'nexthop': '192.168.10.254'}],
                         group='subnet1')
        self.conf.config(cidr='192.168.20.0/24',
                         dhcp_start='192.168.20.10',
                         dhcp_end='192.168.20.99',
                         dhcp_exclude=[],
                         inspection_iprange='192.168.20.100,192.168.20.189',
                         gateway='192.168.20.254',
                         dns_nameservers=[],
                         host_routes=[{'destination': '10.10.10.254/32',
                                       'nexthop': '192.168.20.254'}],
                         group='subnet2')
        env = {}
        undercloud_config._process_network_args(env)
        expected = {
            'ControlPlaneStaticRoutes': [
                {'destination': '192.168.10.0/24', 'nexthop': '192.168.24.1'},
                {'destination': '192.168.20.0/24', 'nexthop': '192.168.24.1'},
                {'destination': '10.10.10.254/32', 'nexthop': '192.168.24.1'}],
            'IronicInspectorSubnets': [
                {'gateway': '192.168.24.1',
                 'host_routes': [{'destination': '10.10.10.254/32',
                                  'nexthop': '192.168.24.1'}],
                 'ip_range': '192.168.24.100,192.168.24.120',
                 'netmask': '255.255.255.0',
                 'tag': 'ctlplane-subnet',
                 'mtu': 1500},
                {'gateway': '192.168.10.254',
                 'host_routes': [{'destination': '10.10.10.254/32',
                                  'nexthop': '192.168.10.254'}],
                 'ip_range': '192.168.10.100,192.168.10.189',
                 'netmask': '255.255.255.0',
                 'tag': 'subnet1',
                 'mtu': 1500},
                {'gateway': '192.168.20.254',
                 'host_routes': [{'destination': '10.10.10.254/32',
                                  'nexthop': '192.168.20.254'}],
                 'ip_range': '192.168.20.100,192.168.20.189',
                 'netmask': '255.255.255.0',
                 'tag': 'subnet2',
                 'mtu': 1500}
            ],
            'MasqueradeNetworks': {},
            'PortPhysnetCidrMap': {'192.168.10.0/24': 'subnet1',
                                   '192.168.20.0/24': 'subnet2',
                                   '192.168.24.0/24': 'ctlplane'},
            'UndercloudCtlplaneSubnets': {
                # The ctlplane-subnet subnet have defaults
                'ctlplane-subnet': {
                    'AllocationPools': [
                        {'start': '192.168.24.5', 'end': '192.168.24.24'}],
                    'DnsNameServers': ['10.10.10.10', '10.10.10.11'],
                    'HostRoutes': [{'destination': '10.10.10.254/32',
                                    'nexthop': '192.168.24.1'}],
                    'NetworkCidr': '192.168.24.0/24',
                    'NetworkGateway': '192.168.24.1'},
                'subnet1': {
                    'AllocationPools': [
                        {'start': '192.168.10.10', 'end': '192.168.10.99'}],
                    'DnsNameServers': ['10.10.10.10', '10.10.10.11'],
                    'HostRoutes': [{'destination': '10.10.10.254/32',
                                    'nexthop': '192.168.10.254'}],
                    'NetworkCidr': '192.168.10.0/24',
                    'NetworkGateway': '192.168.10.254'},
                'subnet2': {
                    'AllocationPools': [
                        {'start': '192.168.20.10', 'end': '192.168.20.99'}],
                    'DnsNameServers': ['10.10.10.10', '10.10.10.11'],
                    'HostRoutes': [{'destination': '10.10.10.254/32',
                                    'nexthop': '192.168.20.254'}],
                    'NetworkCidr': '192.168.20.0/24',
                    'NetworkGateway': '192.168.20.254'}
            },
            'UndercloudCtlplaneIPv6AddressMode': 'dhcpv6-stateless'
        }
        self.assertEqual(expected, env)

    def test_generate_inspection_subnets(self):
        result = undercloud_config._generate_inspection_subnets()
        expected = [{'gateway': '192.168.24.1',
                     'host_routes': [],
                     'ip_range': '192.168.24.100,192.168.24.120',
                     'mtu': 1500,
                     'netmask': '255.255.255.0',
                     'tag': 'ctlplane-subnet'}]
        self.assertEqual(expected, result)

    def test_generate_inspection_subnets_invalid(self):
        self.conf.config(subnets=['ctlplane-subnet', 'subnet1'])
        self.conf.config(host_routes=[{'destination': '10.10.10.254/32',
                                       'nexthop': '192.168.24.1'}],
                         group='ctlplane-subnet')
        self.conf.register_opts(self.opts, group=self.grp1)
        self.conf.config(group='subnet1')
        self.assertRaises(exceptions.DeploymentError,
                          undercloud_config._generate_inspection_subnets)

    def test__env_set_undercloud_ctlplane_networks_attribues(self):
        self.conf.config(local_subnet='ctlplane-subnet',
                         local_mtu=1444,
                         undercloud_nameservers=['192.168.24.253',
                                                 '192.168.24.252'])
        self.conf.config(cidr='192.168.24.0/24',
                         gateway='192.168.24.254',
                         host_routes=[{'destination': '10.10.10.254/32',
                                       'nexthop': '192.168.24.1'}],
                         group='ctlplane-subnet')
        env = {}
        undercloud_config._env_set_undercloud_ctlplane_networks_attribues(env)
        expected = {
            'CtlplaneNetworkAttributes': {
                'network': {'mtu': 1444},
                'subnets': {
                    'ctlplane-subnet': {
                        'cidr': '192.168.24.0/24',
                        'dns_nameservers': ['192.168.24.253',
                                            '192.168.24.252'],
                        'gateway_ip': '192.168.24.254',
                        'host_routes': [{'destination': '10.10.10.254/32',
                                         'nexthop': '192.168.24.1'}],
                        'tags': []}}}}
        self.assertEqual(expected, env)

    def test__env_set_undercloud_ctlplane_networks_attribues_routed(self):
        self.conf.config(subnets=['ctlplane-subnet', 'subnet1', 'subnet2'])
        self.conf.config(host_routes=[{'destination': '10.10.10.254/32',
                                       'nexthop': '192.168.24.1'}],
                         group='ctlplane-subnet')
        self.conf.register_opts(self.opts, group=self.grp1)
        self.conf.register_opts(self.opts, group=self.grp2)
        self.conf.config(cidr='192.168.10.0/24',
                         dhcp_start='192.168.10.10',
                         dhcp_end='192.168.10.99',
                         dhcp_exclude=[],
                         inspection_iprange='192.168.10.100,192.168.10.189',
                         gateway='192.168.10.254',
                         dns_nameservers=[],
                         host_routes=[{'destination': '10.10.10.254/32',
                                       'nexthop': '192.168.10.254'}],
                         group='subnet1')
        self.conf.config(cidr='192.168.20.0/24',
                         dhcp_start='192.168.20.10',
                         dhcp_end='192.168.20.99',
                         dhcp_exclude=[],
                         inspection_iprange='192.168.20.100,192.168.20.189',
                         gateway='192.168.20.254',
                         dns_nameservers=[],
                         host_routes=[{'destination': '10.10.10.254/32',
                                       'nexthop': '192.168.20.254'}],
                         group='subnet2')
        env = {}
        undercloud_config._env_set_undercloud_ctlplane_networks_attribues(env)
        expected = {
            'CtlplaneNetworkAttributes': {
                'network': {'mtu': 1500},
                'subnets': {
                    'ctlplane-subnet': {
                        'cidr': '192.168.24.0/24',
                        'dns_nameservers': ['10.10.10.10', '10.10.10.11'],
                        'gateway_ip': '192.168.24.1',
                        'host_routes': [{'destination': '192.168.10.0/24',
                                         'nexthop': '192.168.24.1'},
                                        {'destination': '192.168.20.0/24',
                                         'nexthop': '192.168.24.1'},
                                        {'destination': '10.10.10.254/32',
                                         'nexthop': '192.168.24.1'}],
                        'tags': []}}}}
        self.assertEqual(expected, env)


class TestChronySettings(TestBaseNetworkSettings):
    def test_default(self):
        env = {}
        undercloud_config._process_chrony_acls(env)
        expected = {
            'ChronyAclRules': ['allow 192.168.24.0/24'],
        }
        self.assertEqual(expected, env)


class TestTLSSettings(base.TestCase):
    def test_public_host_with_ip_should_give_ip_endpoint_environment(self):
        expected_env_file = os.path.join(
            undercloud_config.THT_HOME,
            "environments/ssl/tls-endpoints-public-ip.yaml")

        resulting_env_file1 = undercloud_config._get_tls_endpoint_environment(
            '127.0.0.1', undercloud_config.THT_HOME)

        self.assertEqual(expected_env_file, resulting_env_file1)

        resulting_env_file2 = undercloud_config._get_tls_endpoint_environment(
            '192.168.1.1', undercloud_config.THT_HOME)

        self.assertEqual(expected_env_file, resulting_env_file2)

    def test_public_host_with_fqdn_should_give_dns_endpoint_environment(self):
        expected_env_file = os.path.join(
            undercloud_config.THT_HOME,
            "environments/ssl/tls-endpoints-public-dns.yaml")

        resulting_env_file1 = undercloud_config._get_tls_endpoint_environment(
            'controller-1', undercloud_config.THT_HOME)

        self.assertEqual(expected_env_file, resulting_env_file1)

        resulting_env_file2 = undercloud_config._get_tls_endpoint_environment(
            'controller-1.tripleodomain.com', undercloud_config.THT_HOME)

        self.assertEqual(expected_env_file, resulting_env_file2)

    def get_certificate_and_private_key(self):
        private_key = rsa.generate_private_key(public_exponent=3,
                                               key_size=1024,
                                               backend=default_backend())
        issuer = x509.Name([
            x509.NameAttribute(NameOID.COUNTRY_NAME, u"FI"),
            x509.NameAttribute(NameOID.LOCALITY_NAME, u"Helsinki"),
            x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"Some Company"),
            x509.NameAttribute(NameOID.COMMON_NAME, u"Test Certificate"),
        ])
        cert_builder = x509.CertificateBuilder(
            issuer_name=issuer, subject_name=issuer,
            public_key=private_key.public_key(),
            serial_number=x509.random_serial_number(),
            not_valid_before=datetime.utcnow(),
            not_valid_after=datetime.utcnow() + timedelta(days=10)
        )
        cert = cert_builder.sign(private_key,
                                 hashes.SHA256(),
                                 default_backend())
        cert_pem = cert.public_bytes(encoding=serialization.Encoding.PEM)
        key_pem = private_key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.TraditionalOpenSSL,
            encryption_algorithm=serialization.NoEncryption())
        return cert_pem, key_pem

    def test_get_dict_with_cert_and_key_from_bundled_pem(self):
        cert_pem, key_pem = self.get_certificate_and_private_key()

        with tempfile.NamedTemporaryFile() as tempbundle:
            tempbundle.write(cert_pem)
            tempbundle.write(key_pem)
            tempbundle.seek(0)

            tls_parameters = undercloud_config._get_public_tls_parameters(
                tempbundle.name)

        self.assertEqual(cert_pem, tls_parameters['SSLCertificate'])
        self.assertEqual(key_pem, tls_parameters['SSLKey'])

    def test_get_tls_parameters_fails_cause_of_missing_cert(self):
        _, key_pem = self.get_certificate_and_private_key()

        with tempfile.NamedTemporaryFile() as tempbundle:
            tempbundle.write(key_pem)
            tempbundle.seek(0)

            self.assertRaises(ValueError,
                              undercloud_config._get_public_tls_parameters,
                              tempbundle.name)

    def test_get_tls_parameters_fails_cause_of_missing_key(self):
        cert_pem, _ = self.get_certificate_and_private_key()

        with tempfile.NamedTemporaryFile() as tempbundle:
            tempbundle.write(cert_pem)
            tempbundle.seek(0)

            self.assertRaises(ValueError,
                              undercloud_config._get_public_tls_parameters,
                              tempbundle.name)

    def test_get_tls_parameters_fails_cause_of_unexistent_file(self):
        self.assertRaises(IOError,
                          undercloud_config._get_public_tls_parameters,
                          '/tmp/unexistent-file-12345.pem')


class TestContainerImageConfig(base.TestCase):
    def setUp(self):
        super(TestContainerImageConfig, self).setUp()
        conf_keys = (
            'container_images_file',
        )
        self.conf = mock.Mock(**{key: getattr(undercloud_config.CONF, key)
                                 for key in conf_keys})

    @mock.patch('shutil.copy')
    def test_defaults(self, mock_copy):
        env = {}
        deploy_args = []
        cip_default = getattr(kolla_builder,
                              'CONTAINER_IMAGE_PREPARE_PARAM', None)
        self.addCleanup(setattr, kolla_builder,
                        'CONTAINER_IMAGE_PREPARE_PARAM', cip_default)

        setattr(kolla_builder, 'CONTAINER_IMAGE_PREPARE_PARAM', [{
            'set': {
                'namespace': 'one',
                'name_prefix': 'two',
                'name_suffix': 'three',
                'tag': 'four',
            },
            'tag_from_label': 'five',
        }])

        undercloud_config._container_images_config(self.conf, deploy_args,
                                                   env, None)
        self.assertEqual([], deploy_args)
        cip = env['ContainerImagePrepare'][0]
        set = cip['set']

        self.assertEqual(
            'one', set['namespace'])
        self.assertEqual(
            'two', set['name_prefix'])
        self.assertEqual(
            'three', set['name_suffix'])
        self.assertEqual(
            'four', set['tag'])
        self.assertEqual(
            'five', cip['tag_from_label'])

    @mock.patch('shutil.copy')
    def test_container_images_file(self, mock_copy):
        env = {}
        deploy_args = []
        self.conf.container_images_file = '/tmp/container_images_file.yaml'
        undercloud_config._container_images_config(self.conf, deploy_args,
                                                   env, None)
        self.assertEqual(['-e', '/tmp/container_images_file.yaml'],
                         deploy_args)
        self.assertEqual({}, env)

    @mock.patch('shutil.copy')
    def test_custom(self, mock_copy):
        env = {}
        deploy_args = []
        with tempfile.NamedTemporaryFile(mode='w') as f:
            yaml.dump({
                'parameter_defaults': {'ContainerImagePrepare': [{
                    'set': {
                        'namespace': 'one',
                        'name_prefix': 'two',
                        'name_suffix': 'three',
                        'tag': 'four',
                    },
                    'tag_from_label': 'five',
                }]}
            }, f)
            self.conf.container_images_file = f.name
            cif_name = f.name

            undercloud_config._container_images_config(
                self.conf, deploy_args, env, None)
        self.assertEqual(['-e', cif_name], deploy_args)
