text: # Copyright 2019 Red Hat, Inc.
  # 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.
  
  import copy
  import mock
  import yaml
  
  import openstack
  
  from tripleo_ansible.tests import base as tests_base
  
  from tripleo_ansible.ansible_plugins.module_utils import network_data_v2
  from tripleo_ansible.tests import stubs
  
  
  NET_DATA = yaml.safe_load('''
  ---
  name: Storage
  name_lower: storage
  admin_state_up: false
  dns_domain: storage.localdomain.
  mtu: 1442
  shared: false
  service_net_map_replace: storage
  ipv6: true
  vip: true
  subnets:
    subnet01:
      ip_subnet: 172.18.1.0/24
      gateway_ip: 172.18.1.254
      allocation_pools:
        - start: 172.18.1.1
          end: 172.18.1.250
      routes:
        - destination: 172.18.0.0/24
          nexthop: 172.18.1.254
      ipv6_subnet: 2001:db8:a::/64
      gateway_ipv6: 2001:db8:a::1
      ipv6_allocation_pools:
        - start: 2001:db8:a::0010
          end: 2001:db8:a::fff9
      routes_ipv6:
        - destination: 2001:db8:b::/64
          nexthop: 2001:db8:a::1
      ipv6_address_mode: null
      ipv6_ra_mode: null
      enable_dhcp: false
      physical_network: storage_subnet01
      network_type: flat
      segmentation_id: 21
      vlan: 21
    subnet02:
      ip_subnet: 172.18.0.0/24
      gateway_ip: 172.18.0.254
      allocation_pools:
        - start: 172.18.0.10
          end: 172.18.0.250
      routes:
        - destination: 172.18.1.0/24
          nexthop: 172.18.0.254
      ipv6_subnet: 2001:db8:b::/64
      gateway_ipv6: 2001:db8:b::1
      ipv6_allocation_pools:
        - start: 2001:db8:b::0010
          end: 2001:db8:b::fff9
      routes_ipv6:
        - destination: 2001:db8:a::/64
          nexthop: 2001:db8:b::1
      ipv6_address_mode: null
      ipv6_ra_mode: null
      enable_dhcp: false
      physical_network: storage_subnet01
      network_type: flat
      segmentation_id: 21
      vlan: 20
  ''')
  
  IPV4_SUBNET_KEYS = {'ip_subnet', 'allocation_pools', 'routes', 'gateway_ip'}
  IPV6_SUBNET_KEYS = {'ipv6_subnet', 'ipv6_allocation_pools', 'routes_ipv6',
                      'gateway_ipv6', 'ipv6_address_mode', 'ipv6_ra_mode'}
  
  
  class TestNetworkDataV2(tests_base.TestCase):
  
      def setUp(self):
          super(TestNetworkDataV2, self).setUp()
          # Helper function to convert array to generator
          self.a2g = lambda x: (n for n in x)
  
      def test_validator_ok(self):
          ipv4_only = copy.deepcopy(NET_DATA)
          ipv6_only = copy.deepcopy(NET_DATA)
          ipv4_only.pop('ipv6')
          for name, subnet in ipv4_only['subnets'].items():
              [subnet.pop(k) for k in IPV6_SUBNET_KEYS]
          for name, subnet in ipv6_only['subnets'].items():
              [subnet.pop(k) for k in IPV4_SUBNET_KEYS]
  
          error_messages = network_data_v2.validate_json_schema(NET_DATA)
          self.assertEqual([], error_messages)
          error_messages = network_data_v2.validate_json_schema(ipv4_only)
          self.assertEqual([], error_messages)
          error_messages = network_data_v2.validate_json_schema(ipv6_only)
          self.assertEqual([], error_messages)
  
      def test_validator_fail(self):
          dual = copy.deepcopy(NET_DATA)
          dual.pop('name')  # Required
          dual['mtu'] = 400  # too low
          dual['ipv6'] = 'not_bool'
          dual['vip'] = 'not_bool'
          dual['admin_state_up'] = 'not_bool'
          dual['shared'] = 'not_bool'
          dual['invalid_key'] = 'foo'
          s02 = dual['subnets']['subnet02']
          s02['ip_subnet'] = 'invalid'
          s02['gateway_ip'] = '2001:db8:a::1'  # Wrong ip version
          s02['allocation_pools'][0]['foo'] = 'foo'  # Invalid key
          s02['allocation_pools'][0]['start'] = '2001:db8:a::1'
          s02['routes'][0]['foo'] = 'foo'  # Invalid key
          s02['routes'][0]['nexthop'] = '172222.18.1.254'
          s02['routes'][0]['destination'] = '172.18.0.0/99'  # netmask error
          s02['enable_dhcp'] = 'not_a_bool'
          s02['physical_network'] = dict()  # Invalid, should be string
          s02['network_type'] = 'invalid'
          s02['vlan'] = 'not_an_int'
          s02['ipv6_subnet'] = 'invalid'
          s02['gateway_ipv6'] = '172.18.1.254'  # Wrong ip version
          s02['ipv6_allocation_pools'][0]['v6_invalid_key'] = 'foo'
          s02['ipv6_allocation_pools'][0]['end'] = '172.18.1.20'
          s02['routes_ipv6'][0]['v6_invalid_key'] = 'foo'
          s02['routes_ipv6'][0]['destination'] = '2001:XXX8:X::/64'
          s02['ipv6_address_mode'] = 'invalid'
          s02['ipv6_ra_mode'] = 'invalid'
  
          ipv4_only = copy.deepcopy(dual)
          ipv6_only = copy.deepcopy(dual)
          for name, subnet in ipv4_only['subnets'].items():
              [subnet.pop(k) for k in IPV6_SUBNET_KEYS]
          for name, subnet in ipv6_only['subnets'].items():
              [subnet.pop(k) for k in IPV4_SUBNET_KEYS]
          ipv4_only['subnets']['subnet01'].pop('ip_subnet')  # Required
          ipv6_only['subnets']['subnet01'].pop('ipv6_subnet')  # Required
  
          error_messages_dual = network_data_v2.validate_json_schema(dual)
          error_messages_dual = '\n'.join(error_messages_dual)
          error_messages_ipv4 = network_data_v2.validate_json_schema(ipv4_only)
          error_messages_ipv4 = '\n'.join(error_messages_ipv4)
          error_messages_ipv6 = network_data_v2.validate_json_schema(ipv6_only)
          error_messages_ipv6 = '\n'.join(error_messages_ipv6)
  
          self.assertRegex(error_messages_dual,
                           (r"Failed schema validation at admin_state_up:\n"
                            r"    'not_bool' is not of type 'boolean'\n"))
          self.assertRegex(error_messages_dual,
                           (r"Failed schema validation at mtu:\n"
                            r"    400 is less than the minimum of 1000\n"))
          self.assertRegex(error_messages_dual,
                           (r"Failed schema validation at shared:\n"
                            r"    'not_bool' is not of type 'boolean'\n"))
          self.assertRegex(error_messages_dual,
                           (r"Failed schema validation at ipv6:\n"
                            r"    'not_bool' is not of type 'boolean'\n"))
          self.assertRegex(error_messages_dual,
                           (r"Failed schema validation at vip:\n"
                            r"    'not_bool' is not of type 'boolean'\n"))
          self.assertRegex(error_messages_dual,
                           (r"Failed schema validation at :\n"
                            r".*Additional properties are not allowed "
                            r"\('invalid_key' was unexpected\)\n"))
          self.assertRegex(error_messages_dual,
                           (r"Failed schema validation at :\n"
                            r".*'name' is a required property"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/dual_subnet"
                            r"/allocation_pools/items/additionalProperties: "
                            r"Additional properties are not allowed \('foo' was "
                            r"unexpected\)\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/dual_subnet"
                            r"/allocation_pools/items/start/ip_address_version: "
                            r"2001:db8:a::1 does not appear to be an IPv4 "
                            r"address\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/dual_subnet"
                            r"/enable_dhcp/type: 'not_a_bool' is not of type "
                            r"'boolean'\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/dual_subnet"
                            r"/gateway_ip/ip_address_version: 2001:db8:a::1 "
                            r"does not appear to be an IPv4 address\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/dual_subnet"
                            r"/gateway_ipv6/ip_address_version: 172.18.1.254 "
                            r"does not appear to be an IPv6 address\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/dual_subnet"
                            r"/ip_subnet/ip_subnet_version: invalid does not "
                            r"appear to be an IPv4 subnet\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/dual_subnet"
                            r"/ipv6_address_mode/enum: 'invalid' is not one of "
                            r"\[None, 'dhcpv6-stateful', 'dhcpv6-stateless'\]"
                            r"\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/dual_subnet"
                            r"/ipv6_allocation_pools/items"
                            r"/additionalProperties: Additional properties are "
                            r"not allowed \('v6_invalid_key' was unexpected\)"
                            r"\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/dual_subnet"
                            r"/ipv6_allocation_pools/items/end"
                            r"/ip_address_version: 172.18.1.20 does not appear "
                            r"to be an IPv6 address\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/dual_subnet"
                            r"/ipv6_ra_mode/enum: 'invalid' is not one of "
                            r"\[None, 'dhcpv6-stateful', 'dhcpv6-stateless'\]\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/dual_subnet"
                            r"/ipv6_subnet/ip_subnet_version: invalid does not "
                            r"appear to be an IPv6 subnet\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/dual_subnet"
                            r"/network_type/enum: 'invalid' is not one of "
                            r"\['flat', 'vlan'\]\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/dual_subnet"
                            r"/physical_network/type: {} is not of type 'string'"
                            r"\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/dual_subnet"
                            r"/routes/items/additionalProperties: Additional "
                            r"properties are not allowed \('foo' was unexpected\)"
                            r"\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/dual_subnet"
                            r"/routes/items/destination/ip_subnet_version: "
                            r"172.18.0.0/99 does not appear to be an IPv4 subnet"
                            r"\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/dual_subnet"
                            r"/routes/items/nexthop/ip_address_version: "
                            r"172222.18.1.254 does not appear to be an IPv4 "
                            r"address\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/dual_subnet"
                            r"/routes_ipv6/items/additionalProperties: "
                            r"Additional properties are not allowed \("
                            r"'v6_invalid_key' was unexpected\)\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/dual_subnet"
                            r"/routes_ipv6/items/destination/ip_subnet_version: "
                            r"2001:XXX8:X::/64 does not appear to be an IPv6 "
                            r"subnet\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/dual_subnet"
                            r"/vlan/type: 'not_an_int' is not of type 'integer'"
                            r"\n"))
  
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/ipv6_subnet"
                            r"/additionalProperties: Additional properties are "
                            r"not allowed \(.*'routes'.* were unexpected\)\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/ipv6_subnet"
                            r"/additionalProperties: Additional properties are "
                            r"not allowed \(.*'allocation_pools'.* were "
                            r"unexpected\)\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/ipv6_subnet"
                            r"/additionalProperties: Additional properties are "
                            r"not allowed \(.*'ip_subnet'.* were unexpected\)\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/ipv6_subnet"
                            r"/additionalProperties: Additional properties are "
                            r"not allowed \(.*'gateway_ip'.* were unexpected\)"
                            r"\n"))
  
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/ipv4_subnet"
                            r"/additionalProperties: Additional properties are "
                            r"not allowed \(.*'routes_ipv6'.* were unexpected\)"
                            r"\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/ipv4_subnet"
                            r"/additionalProperties: Additional properties are "
                            r"not allowed \(.*'ipv6_allocation_pools'.* were "
                            r"unexpected\)\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/ipv4_subnet"
                            r"/additionalProperties: Additional properties are "
                            r"not allowed \(.*'ipv6_subnet'.* were unexpected\)"
                            r"\n"))
          self.assertRegex(error_messages_dual,
                           (r"- subnets/additionalProperties/oneOf/ipv4_subnet"
                            r"/additionalProperties: Additional properties are "
                            r"not allowed \(.*'gateway_ipv6'.* were unexpected\)"
                            r"\n"))
  
          self.assertRegex(error_messages_ipv4,
                           r"Failed schema validation at subnets/subnet01:\n")
          self.assertRegex(error_messages_ipv4,
                           (r"- subnets/additionalProperties/oneOf/ipv4_subnet"
                            r"/required: 'ip_subnet' is a required property\n"))
          self.assertRegex(error_messages_ipv4,
                           r"Failed schema validation at subnets/subnet02:\n")
          self.assertRegex(error_messages_ipv4,
                           (r"- subnets/additionalProperties/oneOf/ipv4_subnet"
                            r"/allocation_pools/items/additionalProperties: "
                            r"Additional properties are not allowed \('foo' was "
                            r"unexpected\)\n"))
          self.assertRegex(error_messages_ipv4,
                           (r"- subnets/additionalProperties/oneOf/ipv4_subnet"
                            r"/allocation_pools/items/start/ip_address_version: "
                            r"2001:db8:a::1 does not appear to be an IPv4 "
                            r"address\n"))
          self.assertRegex(error_messages_ipv4,
                           (r"- subnets/additionalProperties/oneOf/ipv4_subnet/"
                            r"enable_dhcp/type: \'not_a_bool\' is not of type "
                            r"\'boolean\'\n"))
          self.assertRegex(error_messages_ipv4,
                           (r"- subnets/additionalProperties/oneOf/ipv4_subnet"
                            r"/gateway_ip/ip_address_version: 2001:db8:a::1 does "
                            r"not appear to be an IPv4 address\n"))
          self.assertRegex(error_messages_ipv4,
                           (r"- subnets/additionalProperties/oneOf/ipv4_subnet"
                            r"/ip_subnet/ip_subnet_version: invalid does not "
                            r"appear to be an IPv4 subnet\n"))
          self.assertRegex(error_messages_ipv4,
                           (r"- subnets/additionalProperties/oneOf/ipv4_subnet"
                            r"/network_type/enum: 'invalid' is not one of "
                            r"\['flat', 'vlan'\].*"))
          self.assertRegex(error_messages_ipv4,
                           (r"- subnets/additionalProperties/oneOf/ipv4_subnet"
                            r"/physical_network/type: {} is not of type 'string'"
                            r"\n"))
          self.assertRegex(error_messages_ipv4,
                           (r"- subnets/additionalProperties/oneOf/ipv4_subnet"
                            r"/routes/items/additionalProperties: Additional "
                            r"properties are not allowed \('foo' was "
                            r"unexpected\)\n"))
          self.assertRegex(error_messages_ipv4,
                           (r"- subnets/additionalProperties/oneOf/ipv4_subnet"
                            r"/routes/items/destination/ip_subnet_version: "
                            r"172.18.0.0/99 does not appear to be an IPv4 subnet"
                            r"\n"))
          self.assertRegex(error_messages_ipv4,
                           (r"- subnets/additionalProperties/oneOf/ipv4_subnet"
                            r"/routes/items/nexthop/ip_address_version: "
                            r"172222.18.1.254 does not appear to be an IPv4 "
                            r"address\n"))
          self.assertRegex(error_messages_ipv4,
                           (r"- subnets/additionalProperties/oneOf/ipv4_subnet"
                            r"/vlan/type: 'not_an_int' is not of type 'integer'"
                            r"\n"))
  
          self.assertRegex(error_messages_ipv6,
                           (r"- subnets/additionalProperties/oneOf/ipv6_subnet"
                            r"/enable_dhcp/type: 'not_a_bool' is not of type "
                            r"'boolean'\n"))
          self.assertRegex(error_messages_ipv6,
                           (r"- subnets/additionalProperties/oneOf/ipv6_subnet"
                            r"/gateway_ipv6/ip_address_version: 172.18.1.254 "
                            r"does not appear to be an IPv6 address\n"))
          self.assertRegex(error_messages_ipv6,
                           (r"- subnets/additionalProperties/oneOf/ipv6_subnet"
                            r"/ipv6_address_mode/enum: 'invalid' is not one of "
                            r"\[None, 'dhcpv6-stateful', 'dhcpv6-stateless'\]"
                            r"\n"))
          self.assertRegex(error_messages_ipv6,
                           (r"- subnets/additionalProperties/oneOf/ipv6_subnet"
                            r"/ipv6_allocation_pools/items"
                            r"/additionalProperties: Additional properties are "
                            r"not allowed \('v6_invalid_key' was unexpected\)"
                            r"\n"))
          self.assertRegex(error_messages_ipv6,
                           (r"- subnets/additionalProperties/oneOf/ipv6_subnet"
                            r"/ipv6_allocation_pools/items/end"
                            r"/ip_address_version: 172.18.1.20 does not appear "
                            r"to be an IPv6 address\n"))
          self.assertRegex(error_messages_ipv6,
                           (r"- subnets/additionalProperties/oneOf/ipv6_subnet"
                            r"/ipv6_subnet/ip_subnet_version: invalid does not "
                            r"appear to be an IPv6 subnet\n"))
          self.assertRegex(error_messages_ipv6,
                           (r"- subnets/additionalProperties/oneOf/ipv6_subnet"
                            r"/network_type/enum: 'invalid' is not one of "
                            r"\['flat', 'vlan'\]\n"))
          self.assertRegex(error_messages_ipv6,
                           (r"- subnets/additionalProperties/oneOf/ipv6_subnet"
                            r"/physical_network/type: {} is not of type 'string'"
                            r"\n"))
          self.assertRegex(error_messages_ipv6,
                           (r"- subnets/additionalProperties/oneOf/ipv6_subnet"
                            r"/routes_ipv6/items/additionalProperties: "
                            r"Additional properties are not allowed "
                            r"\('v6_invalid_key' was unexpected\)\n"))
          self.assertRegex(error_messages_ipv6,
                           (r"- subnets/additionalProperties/oneOf/ipv6_subnet"
                            r"/routes_ipv6/items/destination/ip_subnet_version: "
                            r"2001:XXX8:X::/64 does not appear to be an IPv6 "
                            r"subnet\n"))
          self.assertRegex(error_messages_ipv6,
                           (r"- subnets/additionalProperties/oneOf/ipv6_subnet"
                            r"/vlan/type: 'not_an_int' is not of type 'integer'"
                            r"\n"))
  
      def test_tripleo_resource_tags_to_dict(self):
          tags = ['foo=bar', 'baz=qux', 'tripleo_foo=bar', 'tripleo_baz=qux',
                  'tripleo_net_idx=3']
          expected = {'tripleo_foo': 'bar', 'tripleo_baz': 'qux',
                      'tripleo_net_idx': 3}
          result = network_data_v2.tags_to_dict(tags)
          self.assertEqual(expected, result)
  
      @mock.patch.object(openstack.connection, 'Connection', autospec=True)
      def test_create_name_id_maps(self, conn_mock):
          subnet1 = stubs.FakeNeutronSubnet(id='subnet1_id',
                                            name='subnet1',
                                            cidr='192.168.24.0/24')
          subnet2 = stubs.FakeNeutronSubnet(id='subnet2_id',
                                            name='subnet2',
                                            cidr='192.168.25.0/25')
          subnet3 = stubs.FakeNeutronSubnet(id='subnet3_id',
                                            name='subnet3',
                                            cidr='192.168.26.0/26')
          subnet4 = stubs.FakeNeutronSubnet(id='subnet4_id',
                                            name='subnet4',
                                            cidr='192.168.27.0/27')
          network1 = stubs.FakeNeutronNetwork(
              id='network1_id',
              name='network1',
              subnet_ids=['subnet1_id', 'subnet2_id'],
              tags=['tripleo_network_name=Network1']
          )
          network2 = stubs.FakeNeutronNetwork(
              id='network2_id',
              name='network2',
              subnet_ids=['subnet3_id', 'subnet4_id'],
              tags=['tripleo_network_name=Network2']
          )
          conn_mock.network.networks.return_value = self.a2g([network1,
                                                              network2])
          conn_mock.network.subnets.side_effect = [self.a2g([subnet1, subnet2]),
                                                   self.a2g([subnet3, subnet4])]
          net_maps = network_data_v2.create_name_id_maps(conn_mock)
          expected_by_name_map = {
              'network1': {
                  'id': 'network1_id',
                  'name_upper': 'Network1',
                  'subnets': {
                      'subnet1': 'subnet1_id',
                      'subnet2': 'subnet2_id'
                  }
              },
              'network2': {
                  'id': 'network2_id',
                  'name_upper': 'Network2',
                  'subnets': {
                      'subnet3': 'subnet3_id',
                      'subnet4': 'subnet4_id'
                  }
              }
          }
          expected_by_id_map = {
              'network1_id': 'network1',
              'network2_id': 'network2',
          }
          expected_cidr_prefix_map = {
              'subnet1_id': '24',
              'subnet2_id': '25',
              'subnet3_id': '26',
              'subnet4_id': '27',
          }
          self.assertEqual(expected_by_name_map, net_maps['by_name'])
          self.assertEqual(expected_by_id_map, net_maps['by_id'])
          self.assertEqual(expected_cidr_prefix_map, net_maps['cidr_prefix_map'])
  