From: Shashank Hegde Date: Fri, 3 Jul 2015 01:16:56 +0000 (-0700) Subject: In Arista ML2 driver Reconfigure VLAN on VM migration X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=e0abd3f1039f57cc825a844a33e8341534377d31;p=openstack-build%2Fneutron-build.git In Arista ML2 driver Reconfigure VLAN on VM migration Whenever a VM moves from one compute node to the other, the VLAN on the old switch interface was not removed and the VLAN was not being provisioned on the new switch interface. With this patch, the VLANs are provisioned correctly. Closes-Bug: #1471050 Change-Id: I658ef87ffd82119f41beda9ba019e29a62dc96ff --- diff --git a/neutron/plugins/ml2/drivers/arista/mechanism_arista.py b/neutron/plugins/ml2/drivers/arista/mechanism_arista.py index b82bf1af2..9d746bfb6 100644 --- a/neutron/plugins/ml2/drivers/arista/mechanism_arista.py +++ b/neutron/plugins/ml2/drivers/arista/mechanism_arista.py @@ -267,6 +267,22 @@ class AristaDriver(driver_api.MechanismDriver): orig_port = context.original if new_port['name'] != orig_port['name']: LOG.info(_LI('Port name changed to %s'), new_port['name']) + new_port = context.current + device_id = new_port['device_id'] + device_owner = new_port['device_owner'] + host = context.host + + # device_id and device_owner are set on VM boot + is_vm_boot = device_id and device_owner + if host and host != orig_port['binding:host_id'] and is_vm_boot: + port_id = new_port['id'] + network_id = new_port['network_id'] + tenant_id = new_port['tenant_id'] + if not tenant_id: + tenant_id = context._plugin_context.tenant_id + with self.eos_sync_lock: + db_lib.update_vm_host(device_id, host, port_id, + network_id, tenant_id) def update_port_postcommit(self, context): """Update the name of a given port in EOS. @@ -276,9 +292,6 @@ class AristaDriver(driver_api.MechanismDriver): """ port = context.current orig_port = context.original - if port['name'] == orig_port['name']: - # nothing to do - return device_id = port['device_id'] device_owner = port['device_owner'] @@ -310,6 +323,12 @@ class AristaDriver(driver_api.MechanismDriver): ) if vm_provisioned and net_provisioned: try: + orig_host = orig_port['binding:host_id'] + if host != orig_host: + # The port moved to a different host. So delete the + # old port on the old host before creating a new + # port on the new host. + self._delete_port(port, orig_host, tenant_id) self.rpc.plug_port_into_network(device_id, hostname, port_id, @@ -348,30 +367,42 @@ class AristaDriver(driver_api.MechanismDriver): from appropriate network. """ port = context.current - device_id = port['device_id'] host = context.host - port_id = port['id'] - network_id = port['network_id'] tenant_id = port['tenant_id'] if not tenant_id: tenant_id = context._plugin_context.tenant_id + + with self.eos_sync_lock: + self._delete_port(port, host, tenant_id) + + def _delete_port(self, port, host, tenant_id): + """Deletes the port from EOS. + + param port: Port which is to be deleted + param host: The host on which the port existed + param tenant_id: The tenant to which the port belongs to. Some times + the tenant id in the port dict is not present (as in + the case of HA router). + """ + device_id = port['device_id'] + port_id = port['id'] + network_id = port['network_id'] device_owner = port['device_owner'] try: - with self.eos_sync_lock: - hostname = self._host_name(host) - if device_owner == n_const.DEVICE_OWNER_DHCP: - self.rpc.unplug_dhcp_port_from_network(device_id, - hostname, - port_id, - network_id, - tenant_id) - else: - self.rpc.unplug_host_from_network(device_id, - hostname, - port_id, - network_id, - tenant_id) + hostname = self._host_name(host) + if device_owner == n_const.DEVICE_OWNER_DHCP: + self.rpc.unplug_dhcp_port_from_network(device_id, + hostname, + port_id, + network_id, + tenant_id) + else: + self.rpc.unplug_host_from_network(device_id, + hostname, + port_id, + network_id, + tenant_id) # if necessary, delete tenant as well. self.delete_tenant(tenant_id) except arista_exc.AristaRpcError: diff --git a/neutron/tests/unit/plugins/ml2/drivers/arista/test_mechanism_arista.py b/neutron/tests/unit/plugins/ml2/drivers/arista/test_mechanism_arista.py index d743d6aa4..57d507121 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/arista/test_mechanism_arista.py +++ b/neutron/tests/unit/plugins/ml2/drivers/arista/test_mechanism_arista.py @@ -254,6 +254,84 @@ class AristaDriverTestCase(testlib_api.SqlTestCase): mechanism_arista.db_lib.assert_has_calls(expected_calls) + def test_update_port_precommit(self): + tenant_id = 'ten-1' + network_id = 'net1-id' + segmentation_id = 1001 + vm_id = 'vm1' + + network_context = self._get_network_context(tenant_id, + network_id, + segmentation_id, + False) + + port_context = self._get_port_context(tenant_id, + network_id, + vm_id, + network_context) + host_id = port_context.current['binding:host_id'] + port_context.original['binding:host_id'] = 'ubuntu0' + port_id = port_context.current['id'] + self.drv.update_port_precommit(port_context) + + expected_calls = [ + mock.call.update_vm_host(vm_id, host_id, port_id, + network_id, tenant_id) + ] + + mechanism_arista.db_lib.assert_has_calls(expected_calls) + + def test_update_port_postcommit(self): + tenant_id = 'ten-1' + network_id = 'net1-id' + segmentation_id = 1001 + vm_id = 'vm1' + + network_context = self._get_network_context(tenant_id, + network_id, + segmentation_id, + False) + port_context = self._get_port_context(tenant_id, + network_id, + vm_id, + network_context) + + mechanism_arista.db_lib.is_vm_provisioned.return_value = True + mechanism_arista.db_lib.is_network_provisioned.return_value = True + mechanism_arista.db_lib.get_shared_network_owner_id.return_value = 1 + mechanism_arista.db_lib.get_segmentation_id.return_value = 1001 + mechanism_arista.db_lib.num_nets_provisioned.return_value = 1 + mechanism_arista.db_lib.num_vms_provisioned.return_value = 1 + + port = port_context.current + device_id = port['device_id'] + device_owner = port['device_owner'] + host_id = port['binding:host_id'] + orig_host_id = 'ubuntu0' + port_context.original['binding:host_id'] = orig_host_id + port_id = port['id'] + port_name = port['name'] + + self.drv.update_port_postcommit(port_context) + + expected_calls = [ + mock.call.NeutronNets(), + mock.call.get_segmentation_id(tenant_id, network_id), + mock.call.is_vm_provisioned(device_id, host_id, port_id, + network_id, tenant_id), + mock.call.is_network_provisioned(tenant_id, network_id, + segmentation_id), + mock.call.unplug_host_from_network(device_id, orig_host_id, + port_id, network_id, tenant_id), + mock.call.num_nets_provisioned(tenant_id), + mock.call.num_vms_provisioned(tenant_id), + mock.call.plug_port_into_network(device_id, host_id, port_id, + network_id, tenant_id, + port_name, device_owner) + ] + + mechanism_arista.db_lib.assert_has_calls(expected_calls) + def _get_network_context(self, tenant_id, net_id, seg_id, shared): network = {'id': net_id, 'tenant_id': tenant_id, @@ -271,7 +349,7 @@ class AristaDriverTestCase(testlib_api.SqlTestCase): 'id': 101, 'network_id': net_id } - return FakePortContext(port, port, network) + return FakePortContext(port, dict(port), network) class fake_keystone_info_class(object):