From 1c9f40b96b7ae2722ef39e10e73a22ecab454c6c Mon Sep 17 00:00:00 2001 From: Huifeng Le Date: Wed, 7 Nov 2018 01:14:21 +0800 Subject: [PATCH] Add scenario test for trunk E2E flow Add Scenario test "test_parent_port_connectivity_after_trunk_deleted" to verify the E2E flow of fix proposed for Bug: #1794424 "Enable delete bound trunk for linux bridge agent" Co-Authored-By: Allain Legacy Depends-On: https://review.openstack.org/#/c/605589/ Change-Id: Ic2e02f4b5dc8d7930e251340d8be194733b0a4f7 Related-Bug: #1794424 Story: 2003889 --- .zuul.yaml | 17 +++ neutron_tempest_plugin/api/base.py | 4 +- neutron_tempest_plugin/config.py | 4 + neutron_tempest_plugin/scenario/test_trunk.py | 104 +++++++++++++++++- .../services/network/json/network_client.py | 7 ++ 5 files changed, 131 insertions(+), 5 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index bd8619f1..da636181 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -163,6 +163,7 @@ $TEMPEST_CONFIG: neutron_plugin_options: available_type_drivers: flat,vlan,local,vxlan + q_agent: linuxbridge - job: name: neutron-tempest-plugin-scenario-linuxbridge-queens @@ -172,6 +173,14 @@ branch_override: stable/queens devstack_localrc: NETWORK_API_EXTENSIONS: address-scope,agent,allowed-address-pairs,auto-allocated-topology,availability_zone,binding,default-subnetpools,dhcp_agent_scheduler,dns-integration,ext-gw-mode,external-net,extra_dhcp_opt,extraroute,flavors,ip-substring-filtering,l3-flavors,l3-ha,l3_agent_scheduler,logging,metering,multi-provider,net-mtu,net-mtu-writable,network-ip-availability,network_availability_zone,pagination,port-security,project-id,provider,qos,qos-fip,quotas,quota_details,rbac-policies,router,router_availability_zone,security-group,port-security-groups-filtering,segment,service-type,sorting,standard-attr-description,standard-attr-revisions,standard-attr-timestamp,standard-attr-tag,subnet_allocation,tag,tag-ext,trunk,trunk-details + devstack_local_conf: + test-config: + # NOTE: ignores linux bridge's trunk delete on bound port test + # for queens branch (as https://review.openstack.org/#/c/605589/ + # fix will not apply for queens branch) + $TEMPEST_CONFIG: + neutron_plugin_options: + q_agent: None - job: name: neutron-tempest-plugin-scenario-linuxbridge-rocky @@ -181,6 +190,14 @@ branch_override: stable/rocky devstack_localrc: NETWORK_API_EXTENSIONS: address-scope,agent,allowed-address-pairs,auto-allocated-topology,availability_zone,binding,default-subnetpools,dhcp_agent_scheduler,dns-domain-ports,dns-integration,ext-gw-mode,external-net,extra_dhcp_opt,extraroute,fip-port-details,flavors,ip-substring-filtering,l3-flavors,l3-ha,l3_agent_scheduler,logging,metering,multi-provider,net-mtu,net-mtu-writable,network-ip-availability,network_availability_zone,pagination,port-security,project-id,provider,qos,qos-fip,quotas,quota_details,rbac-policies,router,router_availability_zone,security-group,port-security-groups-filtering,segment,service-type,sorting,standard-attr-description,standard-attr-revisions,standard-attr-timestamp,standard-attr-tag,subnet_allocation,tag,tag-ext,trunk,trunk-details + devstack_local_conf: + test-config: + # NOTE: ignores linux bridge's trunk delete on bound port test + # for rocky branch (as https://review.openstack.org/#/c/605589/ + # fix will not apply for rocky branch) + $TEMPEST_CONFIG: + neutron_plugin_options: + q_agent: None - job: name: neutron-tempest-plugin-dvr-multinode-scenario diff --git a/neutron_tempest_plugin/api/base.py b/neutron_tempest_plugin/api/base.py index 028d9018..e1eafcd7 100644 --- a/neutron_tempest_plugin/api/base.py +++ b/neutron_tempest_plugin/api/base.py @@ -764,7 +764,7 @@ class BaseNetworkTest(test.BaseTestCase): return trunk @classmethod - def delete_trunk(cls, trunk, client=None): + def delete_trunk(cls, trunk, client=None, detach_parent_port=True): """Delete network trunk :param trunk: dictionary containing trunk ID (trunk['id']) @@ -790,7 +790,7 @@ class BaseNetworkTest(test.BaseTestCase): parent_port.update(client.show_port(parent_port['id'])['port']) return not parent_port['device_id'] - if not is_parent_port_detached(): + if detach_parent_port and not is_parent_port_detached(): # this could probably happen when trunk is deleted and parent port # has been assigned to a VM that is still running. Here we are # assuming that device_id points to such VM. diff --git a/neutron_tempest_plugin/config.py b/neutron_tempest_plugin/config.py index 030a126d..1bc96177 100644 --- a/neutron_tempest_plugin/config.py +++ b/neutron_tempest_plugin/config.py @@ -53,6 +53,10 @@ NeutronPluginOptions = [ '"mtu": - integer ' '"cidr" - string ' '"provider:segmentation_id": - integer'), + cfg.StrOpt('q_agent', + default=None, + choices=['None', 'linuxbridge', 'ovs', 'sriov'], + help='Agent used for devstack@q-agt.service'), # Option for feature to connect via SSH to VMs using an intermediate SSH # server diff --git a/neutron_tempest_plugin/scenario/test_trunk.py b/neutron_tempest_plugin/scenario/test_trunk.py index 1903180a..85b16cb9 100644 --- a/neutron_tempest_plugin/scenario/test_trunk.py +++ b/neutron_tempest_plugin/scenario/test_trunk.py @@ -47,8 +47,8 @@ class TrunkTest(base.BaseTempestTestCase): # setup basic topology for servers we can log into cls.network = cls.create_network() cls.subnet = cls.create_subnet(cls.network) - router = cls.create_router_by_client() - cls.create_router_interface(router['id'], cls.subnet['id']) + cls.router = cls.create_router_by_client() + cls.create_router_interface(cls.router['id'], cls.subnet['id']) cls.keypair = cls.create_keypair() cls.secgroup = cls.os_primary.network_client.create_security_group( name=data_utils.rand_name('secgroup')) @@ -95,6 +95,27 @@ class TrunkTest(base.BaseTempestTestCase): t = self.client.show_trunk(trunk_id)['trunk'] return t['status'] == 'ACTIVE' + def _create_server_with_network(self, network, use_advanced_image=False): + port = self.create_port(network, security_groups=[ + self.secgroup['security_group']['id']]) + server, fip = self._create_server_with_fip( + port['id'], use_advanced_image=use_advanced_image) + ssh_user = CONF.validation.image_ssh_user + if use_advanced_image: + ssh_user = CONF.neutron_plugin_options.advanced_image_ssh_user + + server_ssh_client = ssh.Client( + fip['floating_ip_address'], + ssh_user, + pkey=self.keypair['private_key']) + + return { + 'server': server, + 'fip': fip, + 'ssh_client': server_ssh_client, + 'port': port, + } + def _create_server_with_port_and_subport(self, vlan_network, vlan_tag, use_advanced_image=False): parent_port = self.create_port(self.network, security_groups=[ @@ -107,7 +128,7 @@ class TrunkTest(base.BaseTempestTestCase): 'port_id': port_for_subport['id'], 'segmentation_type': 'vlan', 'segmentation_id': vlan_tag} - self.create_trunk(parent_port, [subport]) + trunk = self.create_trunk(parent_port, [subport]) server, fip = self._create_server_with_fip( parent_port['id'], use_advanced_image=use_advanced_image) @@ -126,6 +147,8 @@ class TrunkTest(base.BaseTempestTestCase): 'fip': fip, 'ssh_client': server_ssh_client, 'subport': port_for_subport, + 'parentport': parent_port, + 'trunk': trunk, } def _wait_for_server(self, server, advanced_image=False): @@ -260,3 +283,78 @@ class TrunkTest(base.BaseTempestTestCase): servers[1]['subport']['fixed_ips'][0]['ip_address'], should_succeed=True ) + + @testtools.skipUnless( + CONF.neutron_plugin_options.advanced_image_ref, + "Advanced image is required to run this test.") + @testtools.skipUnless( + CONF.neutron_plugin_options.q_agent == "linuxbridge", + "Linux bridge agent is required to run this test.") + @decorators.idempotent_id('d61cbdf6-1896-491c-b4b4-871caf7fbffe') + def test_parent_port_connectivity_after_trunk_deleted_lb(self): + vlan_tag = 10 + + vlan_network = self.create_network() + vlan_subnet = self.create_subnet(vlan_network) + self.create_router_interface(self.router['id'], vlan_subnet['id']) + + trunk_network_server = self._create_server_with_port_and_subport( + vlan_network, vlan_tag, use_advanced_image=True) + normal_network_server = self._create_server_with_network(self.network) + vlan_network_server = self._create_server_with_network(vlan_network) + + self._wait_for_server(trunk_network_server, advanced_image=True) + # Configure VLAN interfaces on server + command = CONFIGURE_VLAN_INTERFACE_COMMANDS % {'tag': vlan_tag} + trunk_network_server['ssh_client'].exec_command(command) + out = trunk_network_server['ssh_client'].exec_command( + 'PATH=$PATH:/usr/sbin;ip addr list') + LOG.debug("Interfaces on server %s: %s", trunk_network_server, out) + + self._wait_for_server(normal_network_server) + self._wait_for_server(vlan_network_server) + + # allow intra-securitygroup traffic + rule = self.client.create_security_group_rule( + security_group_id=self.secgroup['security_group']['id'], + direction='ingress', ethertype='IPv4', protocol='icmp', + remote_group_id=self.secgroup['security_group']['id']) + self.addCleanup(self.client.delete_security_group_rule, + rule['security_group_rule']['id']) + + # Ping from trunk_network_server to normal_network_server + # via parent port + self.check_remote_connectivity( + trunk_network_server['ssh_client'], + normal_network_server['port']['fixed_ips'][0]['ip_address'], + should_succeed=True + ) + + # Ping from trunk_network_server to vlan_network_server via VLAN + # interface should success + self.check_remote_connectivity( + trunk_network_server['ssh_client'], + vlan_network_server['port']['fixed_ips'][0]['ip_address'], + should_succeed=True + ) + + # Delete the trunk + self.delete_trunk(trunk_network_server['trunk'], + detach_parent_port=False) + LOG.debug("Trunk %s is deleted.", trunk_network_server['trunk']['id']) + + # Ping from trunk_network_server to normal_network_server + # via parent port success after trunk deleted + self.check_remote_connectivity( + trunk_network_server['ssh_client'], + normal_network_server['port']['fixed_ips'][0]['ip_address'], + should_succeed=True + ) + + # Ping from trunk_network_server to vlan_network_server via VLAN + # interface should fail after trunk deleted + self.check_remote_connectivity( + trunk_network_server['ssh_client'], + vlan_network_server['port']['fixed_ips'][0]['ip_address'], + should_succeed=False + ) diff --git a/neutron_tempest_plugin/services/network/json/network_client.py b/neutron_tempest_plugin/services/network/json/network_client.py index 3b07e247..db6c152a 100644 --- a/neutron_tempest_plugin/services/network/json/network_client.py +++ b/neutron_tempest_plugin/services/network/json/network_client.py @@ -871,6 +871,13 @@ class NetworkClientJSON(service_client.RestClient): body = jsonutils.loads(body) return service_client.ResponseBody(resp, body) + def delete_security_group_rule(self, security_group_rule_id): + uri = '%s/security-group-rules/%s' % (self.uri_prefix, + security_group_rule_id) + resp, body = self.delete(uri) + self.expected_success(204, resp.status) + return service_client.ResponseBody(resp, body) + def list_security_groups(self, **kwargs): post_body = {'security_groups': kwargs} body = jsonutils.dumps(post_body)