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:
Boden R 2014-10-03 13:48:08 -04:00 committed by Gary Kotton
parent e5ed57dc3f
commit 1153a46738
6 changed files with 376 additions and 54 deletions

View File

@ -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

View File

@ -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):

View File

@ -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 '

View File

@ -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)

View File

@ -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,

View File

@ -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):