Skip to content

Commit

Permalink
Merge "neutron: handle 'auto' network request in allocate_for_instance"
Browse files Browse the repository at this point in the history
  • Loading branch information
Jenkins authored and openstack-gerrit committed Jun 24, 2016
2 parents 84ab4d7 + d7320be commit 9fdd680
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 11 deletions.
78 changes: 68 additions & 10 deletions nova/network/neutronv2/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ def setup_networks_on_host(self, context, instance, host=None,
"""Setup or teardown the network structures."""

def _get_available_networks(self, context, project_id,
net_ids=None, neutron=None):
net_ids=None, neutron=None,
auto_allocate=False):
"""Return a network list available for the tenant.
The list contains networks owned by the tenant and public networks.
If net_ids specified, it searches networks with requested IDs only.
Expand All @@ -153,6 +154,14 @@ def _get_available_networks(self, context, project_id,
else:
# (1) Retrieve non-public network list owned by the tenant.
search_opts = {'tenant_id': project_id, 'shared': False}
if auto_allocate:
# The auto-allocated-topology extension may create complex
# network topologies and it does so in a non-transactional
# fashion. Therefore API users may be exposed to resources that
# are transient or partially built. A client should use
# resources that are meant to be ready and this can be done by
# checking their admin_state_up flag.
search_opts['admin_state_up'] = True
nets = neutron.list_networks(**search_opts).get('networks', [])
# (2) Retrieve public network list.
search_opts = {'shared': True}
Expand Down Expand Up @@ -362,7 +371,10 @@ def _process_requested_networks(self, context, instance, neutron,
ports = {}
net_ids = []
ordered_networks = []
if requested_networks:
# If we're asked to auto-allocate the network then there won't be any
# ports or real neutron networks to lookup, so just return empty
# results.
if requested_networks and not requested_networks.auto_allocate:
for request in requested_networks:

# Process a request to use a pre-existing neutron port.
Expand Down Expand Up @@ -545,16 +557,28 @@ def allocate_for_instance(self, context, instance, **kwargs):
self._process_requested_networks(context,
instance, neutron, requested_networks, hypervisor_macs))

auto_allocate = requested_networks and requested_networks.auto_allocate
nets = self._get_available_networks(context, instance.project_id,
net_ids, neutron=neutron)
net_ids, neutron=neutron,
auto_allocate=auto_allocate)
if not nets:
# NOTE(chaochin): If user specifies a network id and the network
# can not be found, raise NetworkNotFound error.

if requested_networks:
for request in requested_networks:
if not request.port_id and request.network_id:
raise exception.NetworkNotFound(
network_id=request.network_id)
# There are no networks available for the project to use and
# none specifically requested, so check to see if we're asked
# to auto-allocate the network.
if auto_allocate:
# During validate_networks we checked to see if
# auto-allocation is available so we don't need to do that
# again here.
nets = [self._auto_allocate_network(instance, neutron)]
else:
# NOTE(chaochin): If user specifies a network id and the
# network can not be found, raise NetworkNotFound error.
for request in requested_networks:
if not request.port_id and request.network_id:
raise exception.NetworkNotFound(
network_id=request.network_id)
else:
LOG.debug("No network configured", instance=instance)
return network_model.NetworkInfo([])
Expand All @@ -564,7 +588,8 @@ def allocate_for_instance(self, context, instance, **kwargs):
# with None params=(network_id=None, requested_ip=None, port_id=None,
# pci_request_id=None):
if (not requested_networks
or requested_networks.is_single_unspecified):
or requested_networks.is_single_unspecified
or requested_networks.auto_allocate):
# If no networks were requested and none are available, consider
# it a bad request.
if not nets:
Expand Down Expand Up @@ -1140,6 +1165,39 @@ def _can_auto_allocate_network(self, context, neutron):
'auto-allocated-topology extension is not available.')
return False

def _auto_allocate_network(self, instance, neutron):
"""Automatically allocates a network for the given project.
:param instance: create the network for the project that owns this
instance
:param neutron: neutron client
:returns: Details of the network that was created.
:raises: nova.exception.UnableToAutoAllocateNetwork
:raises: nova.exception.NetworkNotFound
"""
project_id = instance.project_id
LOG.debug('Automatically allocating a network for project %s.',
project_id, instance=instance)
try:
topology = neutron.get_auto_allocated_topology(
project_id)['auto_allocated_topology']
except neutron_client_exc.Conflict:
raise exception.UnableToAutoAllocateNetwork(project_id=project_id)

try:
network = neutron.show_network(topology['id'])['network']
except neutron_client_exc.NetworkNotFoundClient:
# This shouldn't happen since we just created the network, but
# handle it anyway.
LOG.error(_LE('Automatically allocated network %(network_id)s '
'was not found.'), {'network_id': topology['id']},
instance=instance)
raise exception.UnableToAutoAllocateNetwork(project_id=project_id)

LOG.debug('Automatically allocated network: %s', network,
instance=instance)
return network

def _ports_needed_per_instance(self, context, neutron, requested_networks):

# TODO(danms): Remove me when all callers pass an object
Expand Down
129 changes: 128 additions & 1 deletion nova/tests/unit/network/test_neutronv2.py
Original file line number Diff line number Diff line change
Expand Up @@ -1280,7 +1280,8 @@ class BailOutEarly(Exception):
# of the function
api._get_available_networks(self.context, self.instance.project_id,
[],
neutron=self.moxed_client).\
neutron=self.moxed_client,
auto_allocate=False).\
AndRaise(BailOutEarly)
self.mox.ReplayAll()
requested_networks = objects.NetworkRequestList(
Expand Down Expand Up @@ -4795,3 +4796,129 @@ def test__ports_needed_per_instance_auto_reqs_no_nets_ok(self):
requested_networks))
can_alloc.assert_called_once_with(
self.context, mock.sentinel.neutron)

def test__process_requested_networks_auto_allocate(self):
# Tests that _process_requested_networks doesn't really do anything
# if there is an auto-allocate network request.
net_req = objects.NetworkRequest(
network_id=net_req_obj.NETWORK_ID_AUTO)
requested_networks = objects.NetworkRequestList(objects=[net_req])
self.assertEqual(({}, [], [], None),
self.api._process_requested_networks(
self.context, mock.sentinel.instance,
mock.sentinel.neutron_client, requested_networks))

def test__auto_allocate_network_conflict(self):
# Tests that we handle a 409 from Neutron when auto-allocating topology
instance = mock.Mock(project_id=self.context.project_id)
ntrn = mock.Mock()
ntrn.get_auto_allocated_topology = mock.Mock(
side_effect=exceptions.Conflict)
self.assertRaises(exception.UnableToAutoAllocateNetwork,
self.api._auto_allocate_network, instance, ntrn)
ntrn.get_auto_allocated_topology.assert_called_once_with(
instance.project_id)

def test__auto_allocate_network_network_not_found(self):
# Tests that we handle a 404 from Neutron when auto-allocating topology
instance = mock.Mock(project_id=self.context.project_id)
ntrn = mock.Mock()
ntrn.get_auto_allocated_topology.return_value = {
'auto_allocated_topology': {
'id': uuids.network_id
}
}
ntrn.show_network = mock.Mock(
side_effect=exceptions.NetworkNotFoundClient)
self.assertRaises(exception.UnableToAutoAllocateNetwork,
self.api._auto_allocate_network, instance, ntrn)
ntrn.show_network.assert_called_once_with(uuids.network_id)

def test__auto_allocate_network(self):
# Tests the happy path.
instance = mock.Mock(project_id=self.context.project_id)
ntrn = mock.Mock()
ntrn.get_auto_allocated_topology.return_value = {
'auto_allocated_topology': {
'id': uuids.network_id
}
}
ntrn.show_network.return_value = {'network': mock.sentinel.network}
self.assertEqual(mock.sentinel.network,
self.api._auto_allocate_network(instance, ntrn))

def test_allocate_for_instance_auto_allocate(self):
# Tests the happy path.
ntrn = mock.Mock()
# mock neutron.list_networks which is called from
# _get_available_networks when net_ids is empty, which it will be
# because _process_requested_networks will return an empty list since
# we requested 'auto' allocation.
ntrn.list_networks.return_value = {}

fake_network = {
'id': uuids.network_id,
'subnets': [
uuids.subnet_id,
]
}

def fake_get_instance_nw_info(context, instance, **kwargs):
# assert the network and port are what was used in the test
self.assertIn('networks', kwargs)
self.assertEqual(1, len(kwargs['networks']))
self.assertEqual(uuids.network_id,
kwargs['networks'][0]['id'])
self.assertIn('port_ids', kwargs)
self.assertEqual(1, len(kwargs['port_ids']))
self.assertEqual(uuids.port_id, kwargs['port_ids'][0])
# return a fake vif
return [model.VIF(id=uuids.port_id)]

@mock.patch('nova.network.neutronv2.api.get_client', return_value=ntrn)
@mock.patch.object(self.api, '_has_port_binding_extension',
return_value=True)
@mock.patch.object(self.api, '_auto_allocate_network',
return_value=fake_network)
@mock.patch.object(self.api, '_check_external_network_attach')
@mock.patch.object(self.api, '_populate_neutron_extension_values')
@mock.patch.object(self.api, '_populate_mac_address')
@mock.patch.object(self.api, '_create_port', spec=True,
return_value=uuids.port_id)
@mock.patch.object(self.api, '_update_port_dns_name')
@mock.patch.object(self.api, 'get_instance_nw_info',
fake_get_instance_nw_info)
def do_test(self,
update_port_dsn_name_mock,
create_port_mock,
populate_mac_addr_mock,
populate_ext_values_mock,
check_external_net_attach_mock,
auto_allocate_mock,
has_port_binding_mock,
get_client_mock):
instance = fake_instance.fake_instance_obj(self.context)
net_req = objects.NetworkRequest(
network_id=net_req_obj.NETWORK_ID_AUTO)
requested_networks = objects.NetworkRequestList(objects=[net_req])

nw_info = self.api.allocate_for_instance(
self.context, instance, requested_networks=requested_networks)
self.assertEqual(1, len(nw_info))
self.assertEqual(uuids.port_id, nw_info[0]['id'])
# assert that we filtered available networks on admin_state_up=True
ntrn.list_networks.assert_has_calls([
mock.call(tenant_id=instance.project_id, shared=False,
admin_state_up=True),
mock.call(shared=True)])

# assert the calls to create the port are using the network that
# was auto-allocated
port_req_body = mock.ANY
create_port_mock.assert_called_once_with(
ntrn, instance, uuids.network_id, port_req_body,
None, # request.address (fixed IP)
[], # security_group_ids - we didn't request any
)

do_test(self)

0 comments on commit 9fdd680

Please sign in to comment.