diff --git a/vmware_nsx_tempest/config.py b/vmware_nsx_tempest/config.py index f2671e7..1f3a443 100644 --- a/vmware_nsx_tempest/config.py +++ b/vmware_nsx_tempest/config.py @@ -201,3 +201,12 @@ NSXv3Group = [ help="enable ens based changes like port-security-disabled" " no security-group"), ] + +dns_group = cfg.OptGroup(name='dns', + title="DNS Configuration Options") + +DNSGroup = [ + cfg.StrOpt('nameservers', + default='', + help="DNS Nameserver IP address"), +] diff --git a/vmware_nsx_tempest/lib/appliance_manager.py b/vmware_nsx_tempest/lib/appliance_manager.py index 2a71460..82f0b3f 100644 --- a/vmware_nsx_tempest/lib/appliance_manager.py +++ b/vmware_nsx_tempest/lib/appliance_manager.py @@ -333,6 +333,12 @@ class ApplianceManager(manager.NetworkScenarioTest): floating_ip['id']) return floating_ip + def delete_floatingip(self, floating_ip, client=None): + """Delete floating IP associated to a resource/port on Neutron""" + if not client: + client = self.os_admin.floating_ips_client + client.delete_floatingip(floating_ip['id']) + def create_topology_instance( self, server_name, networks, security_groups=None, config_drive=None, keypair=None, image_id=None, @@ -397,6 +403,11 @@ class ApplianceManager(manager.NetworkScenarioTest): self.topology_servers[server_name] = server return server + def delete_topology_instance(self, server, servers_client=None): + if not servers_client: + servers_client = self.os_admin.servers_client + servers_client.delete_server(server['id']) + def _list_ports(self, *args, **kwargs): """List ports using admin creds """ ports_list = self.os_admin.ports_client.list_ports( @@ -451,3 +462,11 @@ class ApplianceManager(manager.NetworkScenarioTest): user_id = self.security_groups_client.user_id tenant_id = self.security_groups_client.tenant_id return user_id, tenant_id + + def create_topology_port(self, network, + ports_client=None, **args): + if not ports_client: + ports_client = self.ports_client + port = ports_client.create_port(network_id=network['id'], **args) + self.addCleanup(ports_client.delete_port, port['port']['id']) + return port diff --git a/vmware_nsx_tempest/lib/feature_manager.py b/vmware_nsx_tempest/lib/feature_manager.py index 766a3a2..b5d64ce 100644 --- a/vmware_nsx_tempest/lib/feature_manager.py +++ b/vmware_nsx_tempest/lib/feature_manager.py @@ -25,7 +25,6 @@ from tempest.lib import exceptions as lib_exc from vmware_nsx_tempest._i18n import _ from vmware_nsx_tempest.common import constants from vmware_nsx_tempest.lib import traffic_manager -from vmware_nsx_tempest.services import designate_base from vmware_nsx_tempest.services.lbaas import health_monitors_client from vmware_nsx_tempest.services.lbaas import listeners_client from vmware_nsx_tempest.services.lbaas import load_balancers_client @@ -44,8 +43,7 @@ RULE_TYPE_DSCP_MARK = "dscp_marking" # It includes feature related function such CRUD Mdproxy, L2GW or QoS -class FeatureManager(traffic_manager.IperfManager, - designate_base.DnsClientBase): +class FeatureManager(traffic_manager.IperfManager): @classmethod def setup_clients(cls): """Create various client connections. Such as NSXv3 and L2 Gateway. @@ -109,10 +107,9 @@ class FeatureManager(traffic_manager.IperfManager, net_client.region, net_client.endpoint_type, **_params) - net_client.service = 'dns' cls.zones_v2_client = openstack_network_clients.ZonesV2Client( net_client.auth_provider, - net_client.service, + 'dns', net_client.region, net_client.endpoint_type, **_params) diff --git a/vmware_nsx_tempest/plugin.py b/vmware_nsx_tempest/plugin.py index fd8b295..1d5efdf 100644 --- a/vmware_nsx_tempest/plugin.py +++ b/vmware_nsx_tempest/plugin.py @@ -27,7 +27,8 @@ _opts = [ (config_nsx.network_group, config_nsx.NetworkGroup), (config_nsx.nsxv_group, config_nsx.NSXvGroup), (config_nsx.l2gw_group, config_nsx.L2gwGroup), - (config_nsx.nsxv3_group, config_nsx.NSXv3Group) + (config_nsx.nsxv3_group, config_nsx.NSXv3Group), + (config_nsx.dns_group, config_nsx.DNSGroup) ] diff --git a/vmware_nsx_tempest/services/openstack_network_clients.py b/vmware_nsx_tempest/services/openstack_network_clients.py index 28929cb..9f2dd36 100644 --- a/vmware_nsx_tempest/services/openstack_network_clients.py +++ b/vmware_nsx_tempest/services/openstack_network_clients.py @@ -311,8 +311,6 @@ class ZonesV2Client(designate_base.DnsClientBase): zonesv2 show zone zonesv2 list zones """ - resource = 'zone' - resource_plural = 'policies' path = 'zones' resource_base_path = '/v2/%s' % path @@ -343,3 +341,8 @@ class ZonesV2Client(designate_base.DnsClientBase): def list_zones(self): return self._list_request(self.resource_base_path) + + def list_recordset_zone(self, zone_id): + request = self.resource_base_path + '/' + zone_id + '/recordsets' + resp, body = self._list_request(request) + return resp, body diff --git a/vmware_nsx_tempest/tests/api/test_v2_designate.py b/vmware_nsx_tempest/tests/api/test_v2_designate.py index 2f22edc..1d78a56 100644 --- a/vmware_nsx_tempest/tests/api/test_v2_designate.py +++ b/vmware_nsx_tempest/tests/api/test_v2_designate.py @@ -17,7 +17,6 @@ from oslo_log import log as logging from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import decorators -from tempest import test from vmware_nsx_tempest.lib import feature_manager @@ -32,9 +31,6 @@ class TestZonesV2Ops(feature_manager.FeatureManager): @classmethod def skip_checks(cls): super(TestZonesV2Ops, cls).skip_checks() - if not test.is_extension_enabled('designate', 'network'): - msg = "Extension designate is not enabled." - raise cls.skipException(msg) @classmethod def setup_credentials(cls): diff --git a/vmware_nsx_tempest/tests/scenario/test_designate.py b/vmware_nsx_tempest/tests/scenario/test_designate.py index ffd56a3..0380a13 100644 --- a/vmware_nsx_tempest/tests/scenario/test_designate.py +++ b/vmware_nsx_tempest/tests/scenario/test_designate.py @@ -1,4 +1,4 @@ -# Copyright 2017 VMware, Inc. +# Copyright 2018 VMware, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -12,11 +12,15 @@ # 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 dns.resolver + +import time + from oslo_log import log as logging from tempest import config from tempest.lib import decorators -from tempest import test +from tempest.lib import exceptions as lib_exc from vmware_nsx_tempest.lib import feature_manager @@ -31,15 +35,6 @@ class TestZonesV2Ops(feature_manager.FeatureManager): @classmethod def skip_checks(cls): super(TestZonesV2Ops, cls).skip_checks() - if not test.is_extension_enabled('designate', 'network'): - msg = "Extension designate is not enabled." - raise cls.skipException(msg) - - @classmethod - def setup_credentials(cls): - cls.set_network_resources() - cls.admin_mgr = cls.get_client_manager('admin') - super(TestZonesV2Ops, cls).setup_credentials() @classmethod def setup_clients(cls): @@ -47,55 +42,287 @@ class TestZonesV2Ops(feature_manager.FeatureManager): Create various client connections. Such as NSX. """ super(TestZonesV2Ops, cls).setup_clients() + cls.cmgr_adm = cls.get_client_manager('admin') - def define_security_groups(self): - self.zone_sg = self.create_topology_empty_security_group( - namestart="zone_sg_") - # Common rules to allow the following traffic - # 1. Egress ICMP IPv4 any any - # 2. Egress ICMP IPv6 any any - # 3. Ingress ICMP IPv4 from public network - # 4. Ingress TCP 22 (SSH) from public network - common_ruleset = [dict(direction='egress', protocol='icmp'), - dict(direction='egress', protocol='icmp', - ethertype='IPv6'), - dict(direction='egress', protocol='tcp', - port_range_min=22, port_range_max=22), - dict(direction='egress', protocol='udp'), + def define_security_groups(self, tenant_id): + sec_rule_client = self.os_admin.security_group_rules_client + sec_client = self.os_admin.security_groups_client + kwargs = dict(tenant_id=tenant_id, + security_group_rules_client=sec_rule_client, + security_groups_client=sec_client) + self.designate_sg = self.create_topology_security_group( + **kwargs) + common_ruleset = [dict(direction='egress', protocol='tcp', + port_range_min=53, port_range_max=53, ), dict(direction='ingress', protocol='tcp', - port_range_min=22, port_range_max=22), - dict(direction='ingress', protocol='udp'), - dict(direction='ingress', protocol='icmp')] + port_range_min=53, port_range_max=53, )] for rule in common_ruleset: - self.add_security_group_rule(self.qos_sg, rule) + self.add_security_group_rule(self.designate_sg, rule, + ruleclient=sec_rule_client, secclient=sec_client, + tenant_id=tenant_id) - -class TestZonesScenario(TestZonesV2Ops): - - @decorators.idempotent_id('e26cf8c6-164d-4097-b066-4e2100382d53') - def test_network_zone_update(self): - """ - Test - Create a zone, check zone exits, create a network - update network with the zone - """ + def create_designate_zone(self): LOG.info('Create a zone') zone = self.create_zone(wait_until=True) LOG.info('Ensure we respond with CREATE+PENDING') self.assertEqual('CREATE', zone['action']) self.assertEqual('PENDING', zone['status']) + return zone + + def create_zone_topology(self, zone_name): + networks_client = self.os_admin.networks_client network_designate = self.create_topology_network( - "network_designate", dns_domain=zone['name']) - self.create_topology_subnet("subnet_designate", network_designate) - self.assertEqual(network_designate['dns_domain'], zone['name']) - LOG.info('Show recordset of the zone') - recordset = self.list_record_set_zone(zone['id']) - self.assertEqual(recordset['metadata']['total_count'], 2) - if any(record['type'] == 'NS' for record in recordset['recordsets']): + "network_designate", networks_client=networks_client, + dns_domain=zone_name) + tenant_id = network_designate['tenant_id'] + self.define_security_groups(tenant_id) + subnet_client = self.os_adm.subnets_client + routers_client = self.os_adm.routers_client + router_designate = self.create_topology_router("router_designate", + routers_client=routers_client) + self.create_topology_subnet("subnet_designate", + network_designate, subnets_client=subnet_client, + routers_client=routers_client, router_id=router_designate['id']) + return network_designate + + def verify_recordset(self, record_set, count): + self.assertEqual(record_set[1]['metadata']['total_count'], count) + if any(record['type'] == 'NS' + for record in record_set[1]['recordsets']): LOG.info('NS record is present') else: LOG.error('NS record is missing') - if any(record['type'] == 'SOA' for record in recordset['recordsets']): + raise Exception('ERROR: NS record is absent') + if any(record['type'] == 'SOA' + for record in record_set[1]['recordsets']): LOG.info('SOA record if present') else: - LOG.info('NS record is missing') + LOG.error('SOA record is missing') + raise Exception('ERROR: SOA record is absent') + if count == 3: + if any(record['type'] == 'A' + for record in record_set[1]['recordsets']): + LOG.info('A record if present') + else: + LOG.error('A record is missing') + raise Exception('ERROR: A record is absent') + + def verify_recordset_floatingip(self, record_set, fip): + for record in record_set[1]['recordsets']: + if record['type'] == 'A': + if record['records'][0] == fip: + LOG.info('Record contains fip of the vm') + return record + return None + + +class TestZonesScenario(TestZonesV2Ops): + + @decorators.idempotent_id('17ba050e-8256-4ff5-bc9e-8da7628c433c') + def test_zone_list_without_fip_instance(self): + """ + Create a zone, check zone exits + Create a network and subnet + Update network with the zone + Boot a VM + Verify recordset only has SOA and NS record types + """ + image_id = self.get_glance_image_id(['cirros', 'esx']) + zone = self.create_designate_zone() + network_designate = self.create_zone_topology(zone['name']) + self.assertEqual(network_designate['dns_domain'], zone['name']) + LOG.info('Show recordset of the zone') + recordset = self.list_record_set_zone(zone['id']) + self.verify_recordset(recordset, 2) + self.create_topology_instance( + "dns_vm", [network_designate], + security_groups=[{'name': self.designate_sg['name']}], + clients=self.os_adm, + create_floating_ip=False, image_id=image_id) + LOG.info('Show recordset of the zone') + recordset = self.list_record_set_zone(zone['id']) + self.verify_recordset(recordset, 2) + + @decorators.idempotent_id('a4de3cca-54e1-4e8b-8b52-2148e55eed84') + def test_zone_list_with_fip_instance(self): + """ + Create a zone, check zone exits + Create a network and subnet + Update network with the zone + Boot a VM and associate fip + Verify recordset contains entry for fip + """ + image_id = self.get_glance_image_id(['cirros', 'esx']) + zone = self.create_zone() + network_designate = self.create_zone_topology(zone['name']) + self.assertEqual(network_designate['dns_domain'], zone['name']) + LOG.info('Show recordset of the zone') + recordset = self.list_record_set_zone(zone['id']) + self.verify_recordset(recordset, 2) + dns_vm = self.create_topology_instance( + "dns_vm", [network_designate], + security_groups=[{'name': self.designate_sg['name']}], + clients=self.os_adm, + create_floating_ip=True, image_id=image_id) + fip = dns_vm['floating_ips'][0]['floating_ip_address'] + LOG.info('Show recordset of the zone') + recordset = self.list_record_set_zone(zone['id']) + self.verify_recordset(recordset, 3) + record = self.verify_recordset_floatingip(recordset, fip) + if record is None: + raise Exception('fip is missing in the recordset') + + @decorators.idempotent_id('c7a169ce-365d-40ac-8690-003bf6c623fd') + def test_zone_list_with_fip_deletion_instance(self): + """ + Create a zone, check zone exits + Create a network and subnet + Update network with the zone + Boot a VM and assign fip + Verify recordset contains the fip + Delete VM + Verify recordset does not have entry for fip + """ + image_id = self.get_glance_image_id(['cirros', 'esx']) + zone = self.create_zone() + network_designate = self.create_zone_topology(zone['name']) + self.assertEqual(network_designate['dns_domain'], zone['name']) + LOG.info('Show recordset of the zone') + recordset = self.list_record_set_zone(zone['id']) + self.verify_recordset(recordset, 2) + dns_vm = self.create_topology_instance( + "dns_vm", [network_designate], + security_groups=[{'name': self.designate_sg['name']}], + clients=self.os_adm, + create_floating_ip=True, image_id=image_id) + LOG.info('Show recordset of the zone') + recordset = self.list_record_set_zone(zone['id']) + fip = dns_vm['floating_ips'][0]['floating_ip_address'] + self.verify_recordset(recordset, 3) + self.verify_recordset_floatingip(recordset, fip) + self.delete_floatingip(dns_vm['floating_ips'][0]) + self.delete_topology_instance(dns_vm) + LOG.info('Show recordset of the zone') + recordset = self.list_record_set_zone(zone['id']) + self.verify_recordset(recordset, 2) + + @decorators.idempotent_id('4375b8fb-54c0-403d-a65b-ae6744dcad86') + def test_zone_list_without_fip_port(self): + """ + Create a zone, check zone exits + Create a network and subnet + Update network with the zone + Create a port + Verify zone record set has SOA and NS record typres + """ + zone = self.create_designate_zone() + network_designate = self.create_zone_topology(zone['name']) + self.assertEqual(network_designate['dns_domain'], zone['name']) + LOG.info('Show recordset of the zone') + recordset = self.list_record_set_zone(zone['id']) + self.verify_recordset(recordset, 2) + ports_client = self.os_admin.ports_client + self.create_topology_port(network_designate, ports_client) + LOG.info('Show recordset of the zone') + recordset = self.list_record_set_zone(zone['id']) + self.verify_recordset(recordset, 2) + + @decorators.idempotent_id('f7df72d8-ee96-4a7a-b03d-ca6d04b9f589') + def test_zone_list_with_fip_port(self): + """ + Create a zone, check zone exits + Create a network and subnet + Update network with the zone + Create a port and assign fip + Verify record set for the zone contains fip + """ + zone = self.create_zone() + network_designate = self.create_zone_topology(zone['name']) + self.assertEqual(network_designate['dns_domain'], zone['name']) + LOG.info('Show recordset of the zone') + recordset = self.list_record_set_zone(zone['id']) + self.verify_recordset(recordset, 2) + ports_client = self.os_admin.ports_client + post_body = {"dns_name": "tempest-port"} + port = self.create_topology_port(network_designate, ports_client, + **post_body) + fip = self.create_floatingip(port['port'], port['port']['id'], + client=self.os_admin.floating_ips_client) + time.sleep(120) + LOG.info('Show recordset of the zone') + recordset = self.list_record_set_zone(zone['id']) + self.verify_recordset(recordset, 3) + record = self.verify_recordset_floatingip(recordset, fip) + if record is None: + raise Exception('fip is missing in the recordset') + + @decorators.idempotent_id('863ebce1-9a4c-43c3-95d8-ad0b4c3f4b36') + def test_zone_nslookup_from_extvm(self): + """ + Create a zone + Update network with zone + Boot an instance and associate fip + Perform nslookup for the dns name from ext vm + """ + image_id = self.get_glance_image_id(['cirros', 'esx']) + zone = self.create_zone() + network_designate = self.create_zone_topology(zone['name']) + self.assertEqual(network_designate['dns_domain'], zone['name']) + dns_vm = self.create_topology_instance( + "dns_vm", [network_designate], + security_groups=[{'name': self.designate_sg['name']}], + clients=self.os_adm, + create_floating_ip=True, image_id=image_id) + fip = dns_vm['floating_ips'][0]['floating_ip_address'] + LOG.info('Show recordset of the zone') + recordset = self.list_record_set_zone(zone['id']) + self.verify_recordset(recordset, 3) + record = self.verify_recordset_floatingip(recordset, fip) + if record is None: + raise Exception('fip is missing in the recordset') + my_resolver = dns.resolver.Resolver() + nameserver = CONF.dns.nameservers[:-3] + my_resolver.nameservers = [nameserver] + try: + answer = my_resolver.query(record['name']) + except Exception: + LOG.error('ns lookup failed on ext-vm') + if (record['name'] not in answer.response.to_text() + or fip not in answer.response.to_text()): + LOG.error('failed to resolve dns for the instance') + raise Exception('DNS response does not have entry ' + 'for the instance') + + @decorators.idempotent_id('6286cbd5-b0e4-4daa-9d8f-f27802c95925') + def test_zone_deletion_post_fip_association(self): + """ + Create a zone + Update network with zone + Boot an instance and associate fip + Delete zone successfully + """ + image_id = self.get_glance_image_id(['cirros', 'esx']) + zone = self.create_zone() + network_designate = self.create_zone_topology(zone['name']) + self.assertEqual(network_designate['dns_domain'], zone['name']) + dns_vm = self.create_topology_instance( + "dns_vm", [network_designate], + security_groups=[{'name': self.designate_sg['name']}], + clients=self.os_adm, + create_floating_ip=True, image_id=image_id) + fip = dns_vm['floating_ips'][0]['floating_ip_address'] + LOG.info('Show recordset of the zone') + recordset = self.list_record_set_zone(zone['id']) + self.verify_recordset(recordset, 3) + record = self.verify_recordset_floatingip(recordset, fip) + if record is None: + raise Exception('fip is missing in the recordset') + LOG.info('Delete the zone') + body = self.delete_zone(zone['id']) + LOG.info('Ensure we respond with DELETE+PENDING') + self.assertEqual('DELETE', body['action']) + self.assertEqual('PENDING', body['status']) + # sleep for delete zone to change from PENDING to SUCCESS + time.sleep(100) + self.assertRaises(lib_exc.NotFound, self.delete_zone, + zone['id'])