Preserve preexisting ports on server delete
When using the '--nic port-id' option for nova boot to bind a server to an existing port, nova deletes the preexisting port on server terminate (delete). This behavior is not correct since the user has preallocated the port object and will expect it to only be unbound from the server on delete. This patch allow the existing port to be unbound from the instance on server delete whereupon the port will still exist but not be associated with the instance in neutron (note the '--nic port-id' opt is only supported with neutron). The patch also adds unit tests to verify the behavior + changes. This patch also moves instance.info_cache.delete() to be called after _shutdown in the compute manager as shutdown calls deallocate_for_instance so the info_cache is still needed at this point. Based on original work from Aaron Rosen <aaronorosen@gmail.com> for which you can see a stale patch review here: https://review.openstack.org/#/c/77043/ UpgradeImpact: Existing applications that created neutron ports and assigned them to a running instance or to a instance that is created will need to be aware that they are now respponsible for the deletion of the ports and that these ports will not be deleted by nova when the instance is destroyed. Co-authored-by: Aaron Rosen <aaronorosen@gmail.com> Co-authored-by: Davanum Srinivas <davanum@gmail.com> Change-Id: Ia5367cf064d40690670ffeac3c1f16998464c234 Closes-bug: 1158684
This commit is contained in:
parent
e5ed57dc3f
commit
1153a46738
|
@ -2542,10 +2542,15 @@ class ComputeManager(manager.Manager):
|
|||
LOG.debug('Events pending at deletion: %(events)s',
|
||||
{'events': ','.join(events.keys())},
|
||||
instance=instance)
|
||||
instance.info_cache.delete()
|
||||
self._notify_about_instance_usage(context, instance,
|
||||
"delete.start")
|
||||
self._shutdown_instance(context, instance, bdms)
|
||||
# NOTE(dims): instance.info_cache.delete() should be called after
|
||||
# _shutdown_instance in the compute manager as shutdown calls
|
||||
# deallocate_for_instance so the info_cache is still needed
|
||||
# at this point.
|
||||
instance.info_cache.delete()
|
||||
|
||||
# NOTE(vish): We have already deleted the instance, so we have
|
||||
# to ignore problems cleaning up the volumes. It
|
||||
# would be nice to let the user know somehow that
|
||||
|
|
|
@ -299,7 +299,7 @@ class VIF(Model):
|
|||
details=None, devname=None, ovs_interfaceid=None,
|
||||
qbh_params=None, qbg_params=None, active=False,
|
||||
vnic_type=VNIC_TYPE_NORMAL, profile=None,
|
||||
**kwargs):
|
||||
preserve_on_delete=False, **kwargs):
|
||||
super(VIF, self).__init__()
|
||||
|
||||
self['id'] = id
|
||||
|
@ -315,6 +315,7 @@ class VIF(Model):
|
|||
self['active'] = active
|
||||
self['vnic_type'] = vnic_type
|
||||
self['profile'] = profile
|
||||
self['preserve_on_delete'] = preserve_on_delete
|
||||
|
||||
self._set_meta(kwargs)
|
||||
|
||||
|
@ -322,7 +323,7 @@ class VIF(Model):
|
|||
keys = ['id', 'address', 'network', 'vnic_type',
|
||||
'type', 'profile', 'details', 'devname',
|
||||
'ovs_interfaceid', 'qbh_params', 'qbg_params',
|
||||
'active']
|
||||
'active', 'preserve_on_delete']
|
||||
return all(self[k] == other[k] for k in keys)
|
||||
|
||||
def __ne__(self, other):
|
||||
|
|
|
@ -312,8 +312,10 @@ class API(base_api.NetworkAPI):
|
|||
raise exception.ExternalNetworkAttachForbidden(
|
||||
network_uuid=net['id'])
|
||||
|
||||
def _unbind_ports(self, context, ports, neutron, port_client=None):
|
||||
"""Unbind the specified ports.
|
||||
def _unbind_ports(self, context, ports,
|
||||
neutron, port_client=None):
|
||||
"""Unbind the given ports by clearing their device_id and
|
||||
device_owner.
|
||||
|
||||
:param context: The request context.
|
||||
:param ports: list of port IDs.
|
||||
|
@ -321,22 +323,21 @@ class API(base_api.NetworkAPI):
|
|||
:param port_client: The client with appropriate karma for
|
||||
updating the ports.
|
||||
"""
|
||||
port_binding = self._has_port_binding_extension(context,
|
||||
refresh_cache=True, neutron=neutron)
|
||||
if port_client is None:
|
||||
# Requires admin creds to set port bindings
|
||||
port_client = (neutron if not port_binding else
|
||||
get_client(context, admin=True))
|
||||
for port_id in ports:
|
||||
port_req_body = {'port': {'device_id': '', 'device_owner': ''}}
|
||||
if port_binding:
|
||||
port_req_body['port']['binding:host_id'] = None
|
||||
try:
|
||||
port_req_body = {'port': {'device_id': ''}}
|
||||
if port_client is None:
|
||||
# Reset device_id and device_owner for the ports
|
||||
port_req_body.update({'device_owner': ''})
|
||||
neutron.update_port(port_id, port_req_body)
|
||||
else:
|
||||
# Requires admin creds to set port bindings
|
||||
if self._has_port_binding_extension(context,
|
||||
neutron=neutron):
|
||||
port_req_body['port']['binding:host_id'] = None
|
||||
port_client.update_port(port_id, port_req_body)
|
||||
port_client.update_port(port_id, port_req_body)
|
||||
except Exception:
|
||||
LOG.exception(_LE("Unable to reset device ID for port %s"),
|
||||
port_id)
|
||||
LOG.exception(_LE("Unable to clear device ID "
|
||||
"for port '%s'"), port_id)
|
||||
|
||||
def allocate_for_instance(self, context, instance, **kwargs):
|
||||
"""Allocate network resources for the instance.
|
||||
|
@ -486,7 +487,7 @@ class API(base_api.NetworkAPI):
|
|||
elif uuid_match:
|
||||
security_group_ids.append(uuid_match)
|
||||
|
||||
touched_port_ids = []
|
||||
preexisting_port_ids = []
|
||||
created_port_ids = []
|
||||
ports_in_requested_order = []
|
||||
nets_in_requested_order = []
|
||||
|
@ -527,7 +528,7 @@ class API(base_api.NetworkAPI):
|
|||
if request.port_id:
|
||||
port = ports[request.port_id]
|
||||
port_client.update_port(port['id'], port_req_body)
|
||||
touched_port_ids.append(port['id'])
|
||||
preexisting_port_ids.append(port['id'])
|
||||
ports_in_requested_order.append(port['id'])
|
||||
else:
|
||||
created_port = self._create_port(
|
||||
|
@ -538,14 +539,15 @@ class API(base_api.NetworkAPI):
|
|||
ports_in_requested_order.append(created_port)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
self._unbind_ports(context, touched_port_ids, neutron,
|
||||
port_client)
|
||||
self._unbind_ports(context,
|
||||
preexisting_port_ids,
|
||||
neutron, port_client)
|
||||
self._delete_ports(neutron, instance, created_port_ids)
|
||||
|
||||
nw_info = self.get_instance_nw_info(context, instance,
|
||||
networks=nets_in_requested_order,
|
||||
port_ids=ports_in_requested_order,
|
||||
admin_client=admin_client)
|
||||
nw_info = self.get_instance_nw_info(
|
||||
context, instance, networks=nets_in_requested_order,
|
||||
port_ids=ports_in_requested_order,
|
||||
admin_client=admin_client,
|
||||
preexisting_port_ids=preexisting_port_ids)
|
||||
# NOTE(danms): Only return info about ports we created in this run.
|
||||
# In the initial allocation case, this will be everything we created,
|
||||
# and in later runs will only be what was created that time. Thus,
|
||||
|
@ -553,7 +555,7 @@ class API(base_api.NetworkAPI):
|
|||
# method.
|
||||
return network_model.NetworkInfo([vif for vif in nw_info
|
||||
if vif['id'] in created_port_ids +
|
||||
touched_port_ids])
|
||||
preexisting_port_ids])
|
||||
|
||||
def _refresh_neutron_extensions_cache(self, context, neutron=None):
|
||||
"""Refresh the neutron extensions cache when necessary."""
|
||||
|
@ -638,10 +640,18 @@ class API(base_api.NetworkAPI):
|
|||
# NOTE(danms): Temporary and transitional
|
||||
if isinstance(requested_networks, objects.NetworkRequestList):
|
||||
requested_networks = requested_networks.as_tuples()
|
||||
ports_to_skip = [port_id for nets, fips, port_id, pci_request_id
|
||||
in requested_networks]
|
||||
ports = set(ports) - set(ports_to_skip)
|
||||
ports_to_skip = set([port_id for nets, fips, port_id, pci_request_id
|
||||
in requested_networks])
|
||||
# NOTE(boden): requested_networks only passed in when deallocating
|
||||
# from a failed build / spawn call. Therefore we need to include
|
||||
# preexisting ports when deallocating from a standard delete op
|
||||
# in which case requested_networks is not provided.
|
||||
ports_to_skip |= set(self._get_preexisting_port_ids(instance))
|
||||
ports = set(ports) - ports_to_skip
|
||||
|
||||
# Reset device_id and device_owner for the ports that are skipped
|
||||
self._unbind_ports(context, ports_to_skip, neutron)
|
||||
# Delete the rest of the ports
|
||||
self._delete_ports(neutron, instance, ports, raise_if_fail=True)
|
||||
|
||||
# NOTE(arosen): This clears out the network_cache only if the instance
|
||||
|
@ -668,7 +678,12 @@ class API(base_api.NetworkAPI):
|
|||
Return network information for the instance
|
||||
"""
|
||||
neutron = get_client(context)
|
||||
self._delete_ports(neutron, instance, [port_id], raise_if_fail=True)
|
||||
preexisting_ports = self._get_preexisting_port_ids(instance)
|
||||
if port_id in preexisting_ports:
|
||||
self._unbind_ports(context, [port_id], neutron)
|
||||
else:
|
||||
self._delete_ports(neutron, instance, [port_id],
|
||||
raise_if_fail=True)
|
||||
return self.get_instance_nw_info(context, instance)
|
||||
|
||||
def list_ports(self, context, **search_opts):
|
||||
|
@ -686,7 +701,8 @@ class API(base_api.NetworkAPI):
|
|||
|
||||
def get_instance_nw_info(self, context, instance, networks=None,
|
||||
port_ids=None, use_slave=False,
|
||||
admin_client=None):
|
||||
admin_client=None,
|
||||
preexisting_port_ids=None):
|
||||
"""Return network information for specified instance
|
||||
and update cache.
|
||||
"""
|
||||
|
@ -695,7 +711,8 @@ class API(base_api.NetworkAPI):
|
|||
# the master. For now we just ignore this arg.
|
||||
with lockutils.lock('refresh_cache-%s' % instance.uuid):
|
||||
result = self._get_instance_nw_info(context, instance, networks,
|
||||
port_ids, admin_client)
|
||||
port_ids, admin_client,
|
||||
preexisting_port_ids)
|
||||
base_api.update_instance_cache_with_nw_info(self, context,
|
||||
instance,
|
||||
nw_info=result,
|
||||
|
@ -703,13 +720,15 @@ class API(base_api.NetworkAPI):
|
|||
return result
|
||||
|
||||
def _get_instance_nw_info(self, context, instance, networks=None,
|
||||
port_ids=None, admin_client=None):
|
||||
port_ids=None, admin_client=None,
|
||||
preexisting_port_ids=None):
|
||||
# NOTE(danms): This is an inner method intended to be called
|
||||
# by other code that updates instance nwinfo. It *must* be
|
||||
# called with the refresh_cache-%(instance_uuid) lock held!
|
||||
LOG.debug('_get_instance_nw_info()', instance=instance)
|
||||
nw_info = self._build_network_info_model(context, instance, networks,
|
||||
port_ids, admin_client)
|
||||
port_ids, admin_client,
|
||||
preexisting_port_ids)
|
||||
return network_model.NetworkInfo.hydrate(nw_info)
|
||||
|
||||
def _gather_port_ids_and_networks(self, context, instance, networks=None,
|
||||
|
@ -1422,8 +1441,21 @@ class API(base_api.NetworkAPI):
|
|||
network['should_create_bridge'] = should_create_bridge
|
||||
return network, ovs_interfaceid
|
||||
|
||||
def _get_preexisting_port_ids(self, instance):
|
||||
"""Retrieve the preexisting ports associated with the given instance.
|
||||
These ports were not created by nova and hence should not be
|
||||
deallocated upon instance deletion.
|
||||
"""
|
||||
net_info = compute_utils.get_nw_info_for_instance(instance)
|
||||
if not net_info:
|
||||
LOG.debug('Instance cache missing network info.',
|
||||
instance=instance)
|
||||
return [vif['id'] for vif in net_info
|
||||
if vif.get('preserve_on_delete')]
|
||||
|
||||
def _build_network_info_model(self, context, instance, networks=None,
|
||||
port_ids=None, admin_client=None):
|
||||
port_ids=None, admin_client=None,
|
||||
preexisting_port_ids=None):
|
||||
"""Return list of ordered VIFs attached to instance.
|
||||
|
||||
:param context - request context.
|
||||
|
@ -1436,6 +1468,11 @@ class API(base_api.NetworkAPI):
|
|||
this value will be populated from the existing
|
||||
cached value.
|
||||
:param admin_client - a neutron client for the admin context.
|
||||
:param preexisting_port_ids - List of port_ids that nova didn't
|
||||
allocate and therefore shouldn't be
|
||||
deleted when an instance is deallocated.
|
||||
If value is None or empty the value will
|
||||
be populated from existing cached value.
|
||||
"""
|
||||
|
||||
search_opts = {'tenant_id': instance.project_id,
|
||||
|
@ -1453,6 +1490,9 @@ class API(base_api.NetworkAPI):
|
|||
context, instance, networks, port_ids)
|
||||
nw_info = network_model.NetworkInfo()
|
||||
|
||||
if not preexisting_port_ids:
|
||||
preexisting_port_ids = self._get_preexisting_port_ids(instance)
|
||||
|
||||
current_neutron_port_map = {}
|
||||
for current_neutron_port in current_neutron_ports:
|
||||
current_neutron_port_map[current_neutron_port['id']] = (
|
||||
|
@ -1478,6 +1518,8 @@ class API(base_api.NetworkAPI):
|
|||
network, ovs_interfaceid = (
|
||||
self._nw_info_build_network(current_neutron_port,
|
||||
networks, subnets))
|
||||
preserve_on_delete = (current_neutron_port['id'] in
|
||||
preexisting_port_ids)
|
||||
|
||||
nw_info.append(network_model.VIF(
|
||||
id=current_neutron_port['id'],
|
||||
|
@ -1490,7 +1532,8 @@ class API(base_api.NetworkAPI):
|
|||
details=current_neutron_port.get('binding:vif_details'),
|
||||
ovs_interfaceid=ovs_interfaceid,
|
||||
devname=devname,
|
||||
active=vif_active))
|
||||
active=vif_active,
|
||||
preserve_on_delete=preserve_on_delete))
|
||||
|
||||
elif nw_info_refresh:
|
||||
LOG.info(_LI('Port %s from network info_cache is no '
|
||||
|
|
|
@ -82,6 +82,45 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
|
|||
mock_sync.assert_called_with(mock.ANY, mock_get.return_value,
|
||||
pwr_state)
|
||||
|
||||
def test_delete_instance_info_cache_delete_ordering(self):
|
||||
call_tracker = mock.Mock()
|
||||
call_tracker.clear_events_for_instance.return_value = None
|
||||
mgr_class = self.compute.__class__
|
||||
orig_delete = mgr_class._delete_instance
|
||||
specd_compute = mock.create_autospec(mgr_class)
|
||||
# spec out everything except for the method we really want
|
||||
# to test, then use call_tracker to verify call sequence
|
||||
specd_compute._delete_instance = orig_delete
|
||||
|
||||
mock_inst = mock.Mock()
|
||||
mock_inst.uuid = 'inst-1'
|
||||
mock_inst.save = mock.Mock()
|
||||
mock_inst.destroy = mock.Mock()
|
||||
mock_inst.system_metadata = mock.Mock()
|
||||
|
||||
def _mark_notify(*args, **kwargs):
|
||||
call_tracker._notify_about_instance_usage(*args, **kwargs)
|
||||
|
||||
def _mark_shutdown(*args, **kwargs):
|
||||
call_tracker._shutdown_instance(*args, **kwargs)
|
||||
|
||||
specd_compute.instance_events = call_tracker
|
||||
specd_compute._notify_about_instance_usage = _mark_notify
|
||||
specd_compute._shutdown_instance = _mark_shutdown
|
||||
mock_inst.info_cache = call_tracker
|
||||
|
||||
specd_compute._delete_instance(specd_compute,
|
||||
self.context,
|
||||
mock_inst,
|
||||
mock.Mock(),
|
||||
mock.Mock())
|
||||
|
||||
methods_called = [n for n, a, k in call_tracker.mock_calls]
|
||||
self.assertEqual(['clear_events_for_instance',
|
||||
'_notify_about_instance_usage',
|
||||
'_shutdown_instance', 'delete'],
|
||||
methods_called)
|
||||
|
||||
def test_allocate_network_succeeds_after_retries(self):
|
||||
self.flags(network_allocate_retries=8)
|
||||
|
||||
|
|
|
@ -390,6 +390,10 @@ class VIFTests(test.NoDBTestCase):
|
|||
vif2 = model.VIF(profile={'pci_slot': '0000:0a:00.2'})
|
||||
self.assertNotEqual(vif1, vif2)
|
||||
|
||||
vif1 = model.VIF(preserve_on_delete=True)
|
||||
vif2 = model.VIF(preserve_on_delete=False)
|
||||
self.assertNotEqual(vif1, vif2)
|
||||
|
||||
def test_create_vif_with_type(self):
|
||||
vif_dict = dict(
|
||||
id=1,
|
||||
|
|
|
@ -206,11 +206,13 @@ class TestNeutronv2Base(test.TestCase):
|
|||
'display_name': 'test_instance',
|
||||
'availability_zone': 'nova',
|
||||
'host': 'some_host',
|
||||
'info_cache': {'network_info': []},
|
||||
'security_groups': []}
|
||||
self.instance2 = {'project_id': '9d049e4b60b64716978ab415e6fbd5c0',
|
||||
'uuid': str(uuid.uuid4()),
|
||||
'display_name': 'test_instance2',
|
||||
'availability_zone': 'nova',
|
||||
'info_cache': {'network_info': []},
|
||||
'security_groups': []}
|
||||
self.nets1 = [{'id': 'my_netid1',
|
||||
'name': 'my_netname1',
|
||||
|
@ -488,6 +490,7 @@ class TestNeutronv2Base(test.TestCase):
|
|||
self.mox.ReplayAll()
|
||||
return api
|
||||
|
||||
preexisting_port_ids = []
|
||||
ports_in_requested_net_order = []
|
||||
nets_in_requested_net_order = []
|
||||
for request in ordered_networks:
|
||||
|
@ -527,6 +530,7 @@ class TestNeutronv2Base(test.TestCase):
|
|||
).AndReturn(
|
||||
{'port': port})
|
||||
ports_in_requested_net_order.append(request.port_id)
|
||||
preexisting_port_ids.append(request.port_id)
|
||||
else:
|
||||
request.address = fixed_ips.get(request.network_id)
|
||||
if request.address:
|
||||
|
@ -557,7 +561,8 @@ class TestNeutronv2Base(test.TestCase):
|
|||
self.instance,
|
||||
networks=nets_in_requested_net_order,
|
||||
port_ids=ports_in_requested_net_order,
|
||||
admin_client=None
|
||||
admin_client=None,
|
||||
preexisting_port_ids=preexisting_port_ids
|
||||
).AndReturn(self._returned_nw_info)
|
||||
self.mox.ReplayAll()
|
||||
return api
|
||||
|
@ -912,7 +917,9 @@ class TestNeutronv2(TestNeutronv2Base):
|
|||
objects=[objects.NetworkRequest(port_id='my_portid1')]))
|
||||
self.assertEqual(self.port_data1, result)
|
||||
|
||||
def test_allocate_for_instance_not_enough_macs_via_ports(self):
|
||||
@mock.patch('nova.network.neutronv2.api.API._unbind_ports')
|
||||
def test_allocate_for_instance_not_enough_macs_via_ports(self,
|
||||
mock_unbind):
|
||||
# using a hypervisor MAC via a pre-created port will stop it being
|
||||
# used to dynamically create a port on a network. We put the network
|
||||
# first in requested_networks so that if the code were to not pre-check
|
||||
|
@ -929,8 +936,11 @@ class TestNeutronv2(TestNeutronv2Base):
|
|||
api.allocate_for_instance, self.context,
|
||||
self.instance, requested_networks=requested_networks,
|
||||
macs=set(['my_mac1']))
|
||||
mock_unbind.assert_called_once_with(self.context, [],
|
||||
self.moxed_client, mock.ANY)
|
||||
|
||||
def test_allocate_for_instance_not_enough_macs(self):
|
||||
@mock.patch('nova.network.neutronv2.api.API._unbind_ports')
|
||||
def test_allocate_for_instance_not_enough_macs(self, mock_unbind):
|
||||
# If not enough MAC addresses are available to allocate to networks, an
|
||||
# error should be raised.
|
||||
# We could pass in macs=set(), but that wouldn't tell us that
|
||||
|
@ -949,6 +959,8 @@ class TestNeutronv2(TestNeutronv2Base):
|
|||
self.instance,
|
||||
requested_networks=requested_networks,
|
||||
macs=set(['my_mac2']))
|
||||
mock_unbind.assert_called_once_with(self.context, [],
|
||||
self.moxed_client, mock.ANY)
|
||||
|
||||
def test_allocate_for_instance_two_macs_two_networks(self):
|
||||
# If two MACs are available and two networks requested, two new ports
|
||||
|
@ -1039,7 +1051,11 @@ class TestNeutronv2(TestNeutronv2Base):
|
|||
nwinfo = api.allocate_for_instance(self.context, self.instance)
|
||||
self.assertEqual(len(nwinfo), 0)
|
||||
|
||||
def test_allocate_for_instance_ex1(self):
|
||||
@mock.patch('nova.network.neutronv2.api.API._get_preexisting_port_ids')
|
||||
@mock.patch('nova.network.neutronv2.api.API._unbind_ports')
|
||||
def test_allocate_for_instance_ex1(self,
|
||||
mock_unbind,
|
||||
mock_preexisting):
|
||||
"""verify we will delete created ports
|
||||
if we fail to allocate all net resources.
|
||||
|
||||
|
@ -1048,6 +1064,7 @@ class TestNeutronv2(TestNeutronv2Base):
|
|||
"""
|
||||
self.instance = fake_instance.fake_instance_obj(self.context,
|
||||
**self.instance)
|
||||
mock_preexisting.return_value = []
|
||||
api = neutronapi.API()
|
||||
self.mox.StubOutWithMock(api, '_populate_neutron_extension_values')
|
||||
self.mox.StubOutWithMock(api, '_has_port_binding_extension')
|
||||
|
@ -1094,6 +1111,8 @@ class TestNeutronv2(TestNeutronv2Base):
|
|||
api.allocate_for_instance,
|
||||
self.context, self.instance,
|
||||
requested_networks=requested_networks)
|
||||
mock_unbind.assert_called_once_with(self.context, [],
|
||||
self.moxed_client, mock.ANY)
|
||||
|
||||
def test_allocate_for_instance_ex2(self):
|
||||
"""verify we have no port to delete
|
||||
|
@ -1276,6 +1295,7 @@ class TestNeutronv2(TestNeutronv2Base):
|
|||
self.moxed_client.list_ports(
|
||||
device_id=self.instance.uuid).AndReturn(
|
||||
{'ports': ret_data})
|
||||
self.moxed_client.list_extensions().AndReturn({'extensions': []})
|
||||
if requested_networks:
|
||||
for net, fip, port, request_id in requested_networks:
|
||||
self.moxed_client.update_port(port)
|
||||
|
@ -1292,7 +1312,9 @@ class TestNeutronv2(TestNeutronv2Base):
|
|||
api.deallocate_for_instance(self.context, self.instance,
|
||||
requested_networks=requested_networks)
|
||||
|
||||
def test_deallocate_for_instance_1_with_requested(self):
|
||||
@mock.patch('nova.network.neutronv2.api.API._get_preexisting_port_ids')
|
||||
def test_deallocate_for_instance_1_with_requested(self, mock_preexisting):
|
||||
mock_preexisting.return_value = []
|
||||
requested = objects.NetworkRequestList(
|
||||
objects=[objects.NetworkRequest(network_id='fake-net',
|
||||
address='1.2.3.4',
|
||||
|
@ -1300,7 +1322,9 @@ class TestNeutronv2(TestNeutronv2Base):
|
|||
# Test to deallocate in one port env.
|
||||
self._deallocate_for_instance(1, requested_networks=requested)
|
||||
|
||||
def test_deallocate_for_instance_2_with_requested(self):
|
||||
@mock.patch('nova.network.neutronv2.api.API._get_preexisting_port_ids')
|
||||
def test_deallocate_for_instance_2_with_requested(self, mock_preexisting):
|
||||
mock_preexisting.return_value = []
|
||||
requested = objects.NetworkRequestList(
|
||||
objects=[objects.NetworkRequest(network_id='fake-net',
|
||||
address='1.2.3.4',
|
||||
|
@ -1308,24 +1332,32 @@ class TestNeutronv2(TestNeutronv2Base):
|
|||
# Test to deallocate in one port env.
|
||||
self._deallocate_for_instance(2, requested_networks=requested)
|
||||
|
||||
def test_deallocate_for_instance_1(self):
|
||||
@mock.patch('nova.network.neutronv2.api.API._get_preexisting_port_ids')
|
||||
def test_deallocate_for_instance_1(self, mock_preexisting):
|
||||
mock_preexisting.return_value = []
|
||||
# Test to deallocate in one port env.
|
||||
self._deallocate_for_instance(1)
|
||||
|
||||
def test_deallocate_for_instance_2(self):
|
||||
@mock.patch('nova.network.neutronv2.api.API._get_preexisting_port_ids')
|
||||
def test_deallocate_for_instance_2(self, mock_preexisting):
|
||||
mock_preexisting.return_value = []
|
||||
# Test to deallocate in two ports env.
|
||||
self._deallocate_for_instance(2)
|
||||
|
||||
def test_deallocate_for_instance_port_not_found(self):
|
||||
@mock.patch('nova.network.neutronv2.api.API._get_preexisting_port_ids')
|
||||
def test_deallocate_for_instance_port_not_found(self,
|
||||
mock_preexisting):
|
||||
# TODO(mriedem): Remove this conversion when all neutronv2 APIs are
|
||||
# converted to handling instance objects.
|
||||
self.instance = fake_instance.fake_instance_obj(self.context,
|
||||
**self.instance)
|
||||
mock_preexisting.return_value = []
|
||||
port_data = self.port_data1
|
||||
self.moxed_client.list_ports(
|
||||
device_id=self.instance.uuid).AndReturn(
|
||||
{'ports': port_data})
|
||||
|
||||
self.moxed_client.list_extensions().AndReturn({'extensions': []})
|
||||
NeutronNotFound = exceptions.NeutronClientException(status_code=404)
|
||||
for port in reversed(port_data):
|
||||
self.moxed_client.delete_port(port['id']).AndRaise(
|
||||
|
@ -2466,7 +2498,12 @@ class TestNeutronv2(TestNeutronv2Base):
|
|||
|
||||
def test_build_network_info_model(self):
|
||||
api = neutronapi.API()
|
||||
fake_inst = {'project_id': 'fake', 'uuid': 'uuid'}
|
||||
|
||||
fake_inst = objects.Instance()
|
||||
fake_inst.project_id = 'fake'
|
||||
fake_inst.uuid = 'uuid'
|
||||
fake_inst.info_cache = objects.InstanceInfoCache()
|
||||
fake_inst.info_cache.network_info = model.NetworkInfo()
|
||||
fake_ports = [
|
||||
# admin_state_up=True and status='ACTIVE' thus vif.active=True
|
||||
{'id': 'port1',
|
||||
|
@ -2577,11 +2614,10 @@ class TestNeutronv2(TestNeutronv2Base):
|
|||
|
||||
self.mox.ReplayAll()
|
||||
neutronapi.get_client('fake')
|
||||
instance = self._fake_instance_object(fake_inst)
|
||||
instance.info_cache = objects.InstanceInfoCache.new(
|
||||
fake_inst.info_cache = objects.InstanceInfoCache.new(
|
||||
self.context, 'fake-uuid')
|
||||
instance.info_cache.network_info = model.NetworkInfo.hydrate([])
|
||||
nw_infos = api._build_network_info_model(self.context, instance,
|
||||
fake_inst.info_cache.network_info = model.NetworkInfo.hydrate([])
|
||||
nw_infos = api._build_network_info_model(self.context, fake_inst,
|
||||
fake_nets,
|
||||
[fake_ports[2]['id'],
|
||||
fake_ports[0]['id'],
|
||||
|
@ -2696,6 +2732,7 @@ class TestNeutronv2(TestNeutronv2Base):
|
|||
if binding_vnic_type:
|
||||
test_port['port']['binding:vnic_type'] = binding_vnic_type
|
||||
|
||||
mock_get_client.reset_mock()
|
||||
mock_client = mock_get_client()
|
||||
mock_client.show_port.return_value = test_port
|
||||
vnic_type, phynet_name = api._get_port_vnic_info(
|
||||
|
@ -2991,9 +3028,15 @@ class TestNeutronv2WithMock(test.TestCase):
|
|||
network_uuid)
|
||||
fake_show_network.assert_called_once_with(network_uuid)
|
||||
|
||||
def test_deallocate_for_instance_uses_delete_helper(self):
|
||||
@mock.patch('nova.network.neutronv2.api.API._get_preexisting_port_ids')
|
||||
@mock.patch('nova.network.neutronv2.api.API.'
|
||||
'_refresh_neutron_extensions_cache')
|
||||
def test_deallocate_for_instance_uses_delete_helper(self,
|
||||
mock_refresh,
|
||||
mock_preexisting):
|
||||
# setup fake data
|
||||
instance = fake_instance.fake_instance_obj(self.context)
|
||||
mock_preexisting.return_value = []
|
||||
port_data = {'ports': [{'id': str(uuid.uuid4())}]}
|
||||
ports = set([port['id'] for port in port_data.get('ports')])
|
||||
api = neutronapi.API()
|
||||
|
@ -3039,7 +3082,9 @@ class TestNeutronv2WithMock(test.TestCase):
|
|||
raise_if_fail=True)
|
||||
mock_client.delete_port.assert_called_once_with('port1')
|
||||
|
||||
def test_deallocate_port_for_instance_fails(self):
|
||||
@mock.patch('nova.network.neutronv2.api.API._get_preexisting_port_ids')
|
||||
def test_deallocate_port_for_instance_fails(self, mock_preexisting):
|
||||
mock_preexisting.return_value = []
|
||||
mock_client = mock.Mock()
|
||||
api = neutronapi.API()
|
||||
with contextlib.nested(
|
||||
|
@ -3124,6 +3169,191 @@ class TestNeutronv2WithMock(test.TestCase):
|
|||
update_port_mock.assert_called_once_with(
|
||||
'fake-port-2', {'port': {'binding:host_id': instance.host}})
|
||||
|
||||
@mock.patch('nova.network.neutronv2.api.compute_utils')
|
||||
def test_get_preexisting_port_ids(self, mocked_comp_utils):
|
||||
mocked_comp_utils.get_nw_info_for_instance.return_value = [model.VIF(
|
||||
id='1', preserve_on_delete=False), model.VIF(
|
||||
id='2', preserve_on_delete=True), model.VIF(
|
||||
id='3', preserve_on_delete=True)]
|
||||
result = self.api._get_preexisting_port_ids(None)
|
||||
self.assertEqual(['2', '3'], result, "Invalid preexisting ports")
|
||||
|
||||
def _test_unbind_ports_get_client(self, mock_neutron,
|
||||
mock_has_ext, has_ext=False):
|
||||
mock_ctx = mock.Mock(is_admin=False)
|
||||
mock_has_ext.return_value = has_ext
|
||||
ports = ["1", "2", "3"]
|
||||
|
||||
self.api._unbind_ports(mock_ctx, ports, mock_neutron)
|
||||
|
||||
get_client_calls = []
|
||||
get_client_calls.append(mock.call(mock_ctx)
|
||||
if not has_ext else
|
||||
mock.call(mock_ctx, admin=True))
|
||||
|
||||
if has_ext:
|
||||
self.assertEqual(1, mock_neutron.call_count)
|
||||
mock_neutron.assert_has_calls(get_client_calls, True)
|
||||
else:
|
||||
self.assertEqual(0, mock_neutron.call_count)
|
||||
|
||||
@mock.patch('nova.network.neutronv2.api.API._has_port_binding_extension')
|
||||
@mock.patch('nova.network.neutronv2.api.get_client')
|
||||
def test_unbind_ports_get_client_binding_extension(self,
|
||||
mock_neutron,
|
||||
mock_has_ext):
|
||||
self._test_unbind_ports_get_client(mock_neutron, mock_has_ext, True)
|
||||
|
||||
@mock.patch('nova.network.neutronv2.api.API._has_port_binding_extension')
|
||||
@mock.patch('nova.network.neutronv2.api.get_client')
|
||||
def test_unbind_ports_get_client(self, mock_neutron, mock_has_ext):
|
||||
self._test_unbind_ports_get_client(mock_neutron, mock_has_ext)
|
||||
|
||||
def _test_unbind_ports(self, mock_neutron, mock_has_ext, has_ext=False):
|
||||
mock_client = mock.Mock()
|
||||
mock_update_port = mock.Mock()
|
||||
mock_client.update_port = mock_update_port
|
||||
mock_ctx = mock.Mock(is_admin=False)
|
||||
mock_has_ext.return_value = has_ext
|
||||
mock_neutron.return_value = mock_client
|
||||
ports = ["1", "2", "3"]
|
||||
|
||||
api = neutronapi.API()
|
||||
api._unbind_ports(mock_ctx, ports, mock_client)
|
||||
|
||||
body = {'port': {'device_id': '', 'device_owner': ''}}
|
||||
if has_ext:
|
||||
body['port']['binding:host_id'] = None
|
||||
update_port_calls = []
|
||||
for p in ports:
|
||||
update_port_calls.append(mock.call(p, body))
|
||||
|
||||
self.assertEqual(3, mock_update_port.call_count)
|
||||
mock_update_port.assert_has_calls(update_port_calls)
|
||||
|
||||
@mock.patch('nova.network.neutronv2.api.API._has_port_binding_extension')
|
||||
@mock.patch('nova.network.neutronv2.api.get_client')
|
||||
def test_unbind_ports_binding_ext(self, mock_neutron, mock_has_ext):
|
||||
self._test_unbind_ports(mock_neutron, mock_has_ext, True)
|
||||
|
||||
@mock.patch('nova.network.neutronv2.api.API._has_port_binding_extension')
|
||||
@mock.patch('nova.network.neutronv2.api.get_client')
|
||||
def test_unbind_ports(self, mock_neutron, mock_has_ext):
|
||||
self._test_unbind_ports(mock_neutron, mock_has_ext, False)
|
||||
|
||||
@mock.patch('nova.network.neutronv2.api.API.get_instance_nw_info')
|
||||
@mock.patch('nova.network.neutronv2.api.excutils')
|
||||
@mock.patch('nova.network.neutronv2.api.API._delete_ports')
|
||||
@mock.patch('nova.network.neutronv2.api.API.'
|
||||
'_check_external_network_attach')
|
||||
@mock.patch('nova.network.neutronv2.api.LOG')
|
||||
@mock.patch('nova.network.neutronv2.api.API._unbind_ports')
|
||||
@mock.patch('nova.network.neutronv2.api.API._has_port_binding_extension')
|
||||
@mock.patch('nova.network.neutronv2.api.API.'
|
||||
'_populate_neutron_extension_values')
|
||||
@mock.patch('nova.network.neutronv2.api.API._get_available_networks')
|
||||
@mock.patch('nova.network.neutronv2.api.get_client')
|
||||
def test_allocate_for_instance_unbind(self, mock_ntrn,
|
||||
mock_avail_nets,
|
||||
mock_ext_vals,
|
||||
mock_has_pbe,
|
||||
mock_unbind,
|
||||
mock_log,
|
||||
mock_cena,
|
||||
mock_del_ports,
|
||||
mock_exeu,
|
||||
mock_giwn):
|
||||
mock_nc = mock.Mock()
|
||||
|
||||
def show_port(port_id):
|
||||
return {'port': {'network_id': 'net-1', 'id': port_id}}
|
||||
mock_nc.show_port = show_port
|
||||
|
||||
mock_ntrn.return_value = mock_nc
|
||||
mock_nc.update_port.side_effect = [True, True, Exception]
|
||||
mock_inst = mock.Mock(project_id="proj-1",
|
||||
availability_zone='zone-1',
|
||||
uuid='inst-1')
|
||||
mock_has_pbe.return_value = False
|
||||
nw_req = objects.NetworkRequestList(
|
||||
objects = [objects.NetworkRequest(port_id='fake-port1'),
|
||||
objects.NetworkRequest(port_id='fake-port2'),
|
||||
objects.NetworkRequest(port_id='fail-port')])
|
||||
mock_avail_nets.return_value = [{'id': 'net-1'}]
|
||||
|
||||
self.api.allocate_for_instance(mock.sentinel.ctx,
|
||||
mock_inst,
|
||||
requested_networks=nw_req)
|
||||
|
||||
mock_unbind.assert_called_once_with(mock.sentinel.ctx,
|
||||
['fake-port1', 'fake-port2'],
|
||||
mock.ANY,
|
||||
mock.ANY)
|
||||
|
||||
@mock.patch('nova.objects.network_request.utils')
|
||||
@mock.patch('nova.network.neutronv2.api.LOG')
|
||||
@mock.patch('nova.network.neutronv2.api.base_api')
|
||||
@mock.patch('nova.network.neutronv2.api.API._delete_ports')
|
||||
@mock.patch('nova.network.neutronv2.api.API._unbind_ports')
|
||||
@mock.patch('nova.network.neutronv2.api.API._get_preexisting_port_ids')
|
||||
@mock.patch('nova.network.neutronv2.api.get_client')
|
||||
def test_preexisting_deallocate_for_instance(self, mock_ntrn,
|
||||
mock_gppids,
|
||||
mock_unbind,
|
||||
mock_deletep,
|
||||
mock_baseapi,
|
||||
mock_log,
|
||||
req_utils):
|
||||
req_utils.is_neutron.return_value = True
|
||||
mock_inst = mock.Mock(project_id="proj-1",
|
||||
availability_zone='zone-1',
|
||||
uuid='inst-1')
|
||||
mock_nc = mock.Mock()
|
||||
mock_ntrn.return_value = mock_nc
|
||||
mock_nc.list_ports.return_value = {'ports': [
|
||||
{'id': 'port-1'}, {'id': 'port-2'}, {'id': 'port-3'}
|
||||
]}
|
||||
nw_req = objects.NetworkRequestList(
|
||||
objects = [objects.NetworkRequest(network_id='net-1',
|
||||
address='192.168.0.3',
|
||||
port_id='port-1',
|
||||
pci_request_id='pci-1')])
|
||||
mock_gppids.return_value = ['port-3']
|
||||
|
||||
self.api.deallocate_for_instance(mock.sentinel.ctx, mock_inst,
|
||||
requested_networks=nw_req)
|
||||
|
||||
mock_unbind.assert_called_once_with(mock.sentinel.ctx,
|
||||
set(['port-1', 'port-3']),
|
||||
mock.ANY)
|
||||
mock_deletep.assert_called_once_with(mock_nc,
|
||||
mock_inst,
|
||||
set(['port-2']),
|
||||
raise_if_fail=True)
|
||||
|
||||
@mock.patch('nova.network.neutronv2.api.API.get_instance_nw_info')
|
||||
@mock.patch('nova.network.neutronv2.api.API._unbind_ports')
|
||||
@mock.patch('nova.network.neutronv2.api.compute_utils')
|
||||
@mock.patch('nova.network.neutronv2.api.get_client')
|
||||
def test_preexisting_deallocate_port_for_instance(self,
|
||||
mock_ntrn,
|
||||
mock_comp_utils,
|
||||
mock_unbind,
|
||||
mock_netinfo):
|
||||
mock_comp_utils.get_nw_info_for_instance.return_value = [model.VIF(
|
||||
id='1', preserve_on_delete=False), model.VIF(
|
||||
id='2', preserve_on_delete=True), model.VIF(
|
||||
id='3', preserve_on_delete=True)]
|
||||
mock_inst = mock.Mock(project_id="proj-1",
|
||||
availability_zone='zone-1',
|
||||
uuid='inst-1')
|
||||
mock_client = mock.Mock()
|
||||
mock_ntrn.return_value = mock_client
|
||||
self.api.deallocate_port_for_instance(mock.sentinel.ctx,
|
||||
mock_inst, '2')
|
||||
mock_unbind.assert_called_once_with(mock.sentinel.ctx, ['2'],
|
||||
mock_client)
|
||||
|
||||
|
||||
class TestNeutronv2ModuleMethods(test.TestCase):
|
||||
|
||||
|
|
Loading…
Reference in New Issue