Extract _update_ports_for_instance

To make allocate_for_instance easier to read, extract port update into
its own method called _update_ports_for_instance.

blueprint prep-for-network-aware-scheduling

Change-Id: I3ba46a831e1954de12a6c70a44113d2b75b874a2
This commit is contained in:
John Garbutt 2016-06-15 10:43:11 +01:00
parent 9c45d6d033
commit 9480ffc974
2 changed files with 153 additions and 26 deletions

View File

@ -690,6 +690,9 @@ class API(base_api.NetworkAPI):
# We do not want to create a new neutron session for each call
neutron = get_client(context)
#
# Validate ports and networks with neutron
#
requested_networks = kwargs.get('requested_networks')
ports, ordered_networks = self._validate_requested_port_ids(
context, instance, neutron, requested_networks)
@ -716,13 +719,59 @@ class API(base_api.NetworkAPI):
#
# Update existing and newly created ports
#
dhcp_opts = kwargs.get('dhcp_options', None)
dhcp_opts = kwargs.get('dhcp_options')
bind_host_id = kwargs.get('bind_host_id')
hypervisor_macs = kwargs.get('macs', None)
available_macs = _filter_hypervisor_macs(instance, ports,
hypervisor_macs)
# We always need admin_client to build nw_info,
# we sometimes need it when updating ports
admin_client = get_client(context, admin=True)
ordered_nets, ordered_ports, preexisting_port_ids, \
created_port_ids = self._update_ports_for_instance(
context, instance,
neutron, admin_client, requests_and_created_ports, nets,
bind_host_id, dhcp_opts, available_macs)
#
# Perform a full update of the network_info_cache,
# including re-fetching lots of the required data from neutron
#
nw_info = self.get_instance_nw_info(
context, instance, networks=ordered_nets,
port_ids=ordered_ports,
admin_client=admin_client,
preexisting_port_ids=preexisting_port_ids,
update_cells=True)
# 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,
# this only affects the attach case, not the original use for this
# method.
return network_model.NetworkInfo([vif for vif in nw_info
if vif['id'] in created_port_ids +
preexisting_port_ids])
def _update_ports_for_instance(self, context, instance, neutron,
admin_client, requests_and_created_ports, nets,
bind_host_id, dhcp_opts, available_macs):
"""Create port for network_requests that don't have a port_id
:param context: The request context.
:param instance: nova.objects.instance.Instance object.
:param neutron: client using user context
:param admin_client: client using admin context
:param requests_and_created_ports: [(NetworkRequest, created_port_id)]
:param nets: a dict of network_id to networks returned from neutron
:param bind_host_id: a string for port['binding:host_id']
:param dhcp_opts: a list dicts that contain dhcp option name and value
e.g. [{'opt_name': 'tftp-server', 'opt_value': '1.2.3.4'}]
:param available_macs: a list of available mac addresses
"""
# The neutron client and port_client (either the admin context or
# tenant context) are read here. The reason for this is that there are
# a number of different calls for the instance allocation.
@ -730,9 +779,7 @@ class API(base_api.NetworkAPI):
port_client = (neutron if not
self._has_port_binding_extension(context,
refresh_cache=True, neutron=neutron) else
get_client(context, admin=True))
# Store the admin client - this is used later
admin_client = port_client if neutron != port_client else None
admin_client)
preexisting_port_ids = []
created_port_ids = []
@ -803,20 +850,9 @@ class API(base_api.NetworkAPI):
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,
preexisting_port_ids=preexisting_port_ids,
update_cells=True)
# 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,
# this only affects the attach case, not the original use for this
# method.
return network_model.NetworkInfo([vif for vif in nw_info
if vif['id'] in created_port_ids +
preexisting_port_ids])
return (nets_in_requested_order, ports_in_requested_order,
preexisting_port_ids, created_port_ids)
def _refresh_neutron_extensions_cache(self, context, neutron=None):
"""Refresh the neutron extensions cache when necessary."""

View File

@ -540,7 +540,7 @@ class TestNeutronv2Base(test.TestCase):
self.instance,
networks=nets_in_requested_net_order,
port_ids=ports_in_requested_net_order,
admin_client=None,
admin_client=self.moxed_client,
preexisting_port_ids=preexisting_port_ids,
update_cells=True
).AndReturn(self._returned_nw_info)
@ -549,16 +549,17 @@ class TestNeutronv2Base(test.TestCase):
def _stub_allocate_for_instance_port_binding(self, api, portbinding,
has_dns_extension):
if portbinding:
neutronapi.get_client(mox.IgnoreArg()).AndReturn(
self.moxed_client)
neutronapi.get_client(
mox.IgnoreArg(), admin=True).AndReturn(
self.moxed_client)
has_portbinding = False
if portbinding:
has_portbinding = True
api.extensions[constants.PORTBINDING_EXT] = 1
self.mox.StubOutWithMock(api, '_refresh_neutron_extensions_cache')
neutronapi.get_client(mox.IgnoreArg()).AndReturn(
self.moxed_client)
neutronapi.get_client(
mox.IgnoreArg(), admin=True).AndReturn(
self.moxed_client)
api._refresh_neutron_extensions_cache(mox.IgnoreArg(),
neutron=self.moxed_client)
self.mox.StubOutWithMock(api, '_has_port_binding_extension')
@ -1264,6 +1265,9 @@ class TestNeutronv2(TestNeutronv2Base):
(request, ('portid_' + request.network_id))
for request in requested_networks
]
neutronapi.get_client(
mox.IgnoreArg(), admin=True).AndReturn(
self.moxed_client)
index = 0
for network in self.nets2:
binding_port_req_body = {
@ -4285,13 +4289,55 @@ class TestNeutronv2ExtraDhcpOpts(TestNeutronv2Base):
dhcp_options=dhcp_opts)
class TestAllocateForInstanceHelpers(test.NoDBTestCase):
class TestAllocateForInstance(test.NoDBTestCase):
def setUp(self):
super(TestAllocateForInstanceHelpers, self).setUp()
super(TestAllocateForInstance, self).setUp()
self.context = context.RequestContext('userid', uuids.my_tenant)
self.instance = objects.Instance(uuid=uuids.instance,
project_id=uuids.tenant_id, hostname="host")
def test_allocate_for_instance_raises_invalid_input(self):
api = neutronapi.API()
self.instance.project_id = ""
self.assertRaises(exception.InvalidInput,
api.allocate_for_instance, self.context, self.instance)
@mock.patch.object(neutronapi.API, 'get_instance_nw_info')
@mock.patch.object(neutronapi.API, '_update_ports_for_instance')
@mock.patch.object(neutronapi, '_filter_hypervisor_macs')
@mock.patch.object(neutronapi.API, '_create_ports_for_instance')
@mock.patch.object(neutronapi.API, '_process_security_groups')
@mock.patch.object(neutronapi.API, '_clean_security_groups')
@mock.patch.object(neutronapi.API, '_validate_requested_network_ids')
@mock.patch.object(neutronapi.API, '_validate_requested_port_ids')
@mock.patch.object(neutronapi, 'get_client')
def test_allocate_for_instance_minimal_args(self, mock_get_client,
mock_validate_ports, mock_validate_nets, mock_clean_sg, mock_sg,
mock_create_ports, mock_filter_macs, mock_update_ports, mock_gni):
api = neutronapi.API()
mock_get_client.side_effect = ["user", "admin"]
mock_validate_ports.return_value = ("ports", "ordered_nets")
mock_validate_nets.return_value = "nets"
mock_clean_sg.return_value = "security_groups"
mock_sg.return_value = "security_group_ids"
mock_create_ports.return_value = "requests_and_created_ports"
mock_filter_macs.return_value = "available_macs"
mock_update_ports.return_value = (
"nets", "ports", [uuids.preexist], [uuids.created])
mock_gni.return_value = [
{"id": uuids.created}, {"id": uuids.preexist}, {"id": "foo"}
]
result = api.allocate_for_instance(self.context, self.instance)
# TODO(johngarbutt) we need to replace the old mox coverage
# with new tests that can build on this very poor test
self.assertEqual(len(result), 2)
self.assertEqual(result[0], {"id": uuids.created})
self.assertEqual(result[1], {"id": uuids.preexist})
def test_populate_mac_address_skip_if_none(self):
api = neutronapi.API()
port_req_body = {}
@ -4613,6 +4659,51 @@ class TestAllocateForInstanceHelpers(test.NoDBTestCase):
self.assertFalse(mock_client.create_port.called)
@mock.patch.object(objects.VirtualInterface, "create")
def test_update_ports_for_instance_with_portbinding(self, mock_create):
api = neutronapi.API()
self.instance.availability_zone = "test_az"
mock_neutron = mock.Mock()
mock_admin = mock.Mock()
requests_and_created_ports = [
(objects.NetworkRequest(
network_id=uuids.net1), uuids.port1),
(objects.NetworkRequest(
network_id=uuids.net2, port_id=uuids.port2), None)]
net1 = {"id": uuids.net1}
net2 = {"id": uuids.net2}
nets = {uuids.net1: net1, uuids.net2: net2}
bind_host_id = "bind_host_id"
dhcp_opts = [{'opt_name': 'tftp-server', 'opt_value': '1.2.3.4'}]
available_macs = ["mac1", "mac2"]
mock_neutron.list_extensions.return_value = {"extensions": [
{"name": "asdf"}, {"name": constants.PORTBINDING_EXT}]}
port1 = {"port": {"id": uuids.port1, "mac_address": "mac1r"}}
port2 = {"port": {"id": uuids.port2, "mac_address": "mac2r"}}
mock_admin.update_port.side_effect = [port1, port2]
ordered_nets, ordered_ports, preexisting_port_ids, \
created_port_ids = api._update_ports_for_instance(
self.context, self.instance,
mock_neutron, mock_admin, requests_and_created_ports, nets,
bind_host_id, dhcp_opts, available_macs)
# TODO(johngarbutt) need to build on this test so we can replace
# all the mox based tests
self.assertEqual([net1, net2], ordered_nets, "ordered_nets")
self.assertEqual([uuids.port1, uuids.port2], ordered_ports,
"ordered_ports")
self.assertEqual([uuids.port2], preexisting_port_ids, "preexisting")
self.assertEqual([uuids.port1], created_port_ids, "created")
mock_admin.update_port.assert_called_with(uuids.port2,
{'port': {
'device_owner': 'compute:test_az',
'mac_address': 'mac1',
'binding:host_id': bind_host_id,
'extra_dhcp_opts': dhcp_opts,
'device_id': self.instance.uuid}})
class TestNeutronv2NeutronHostnameDNS(TestNeutronv2Base):
def setUp(self):