Define a hypervisor driver method for getting MAC addresses.

This lets hypervisors export the MAC addresses an instance needs to have (or
return the default of None if the hypervisor is not constrained by MAC
addresses).  being provisioned.

Change-Id: I319a8deb2d09fbf8cecb7071256cf931924ef0e9
This commit is contained in:
Robert Collins
2013-01-15 13:00:10 +13:00
parent ce0a770059
commit b6b9ca1136
7 changed files with 103 additions and 7 deletions

View File

@@ -678,9 +678,9 @@ class ComputeManager(manager.SchedulerDependentManager):
try:
limits = filter_properties.get('limits', {})
with rt.instance_claim(context, instance, limits):
macs = self.driver.macs_for_instance(instance)
network_info = self._allocate_network(context, instance,
requested_networks)
requested_networks, macs)
block_device_info = self._prep_block_device(context,
instance, bdms)
instance = self._spawn(context, instance, image_meta,
@@ -911,7 +911,7 @@ class ComputeManager(manager.SchedulerDependentManager):
expected_task_state=(task_states.SCHEDULING,
None))
def _allocate_network(self, context, instance, requested_networks):
def _allocate_network(self, context, instance, requested_networks, macs):
"""Allocate networks for an instance and return the network info."""
instance = self._instance_update(context, instance['uuid'],
vm_state=vm_states.BUILDING,
@@ -922,7 +922,8 @@ class ComputeManager(manager.SchedulerDependentManager):
# allocate and get network info
network_info = self.network_api.allocate_for_instance(
context, instance, vpn=is_vpn,
requested_networks=requested_networks)
requested_networks=requested_networks,
macs=macs)
except Exception:
LOG.exception(_('Instance failed network setup'),
instance=instance)

View File

@@ -184,9 +184,15 @@ class API(base.Base):
@refresh_cache
def allocate_for_instance(self, context, instance, vpn,
requested_networks):
requested_networks, macs=None):
"""Allocates all network structures for an instance.
TODO(someone): document the rest of these parameters.
:param macs: None or a set of MAC addresses that the instance
should use. macs is supplied by the hypervisor driver (contrast
with requested_networks which is user supplied).
NB: macs is ignored by nova-network.
:returns: network info as from get_instance_nw_info() below
"""
args = {}

View File

@@ -104,7 +104,15 @@ class API(base.Base):
return nets
def allocate_for_instance(self, context, instance, **kwargs):
"""Allocate all network resources for the instance."""
"""Allocate all network resources for the instance.
TODO(someone): document the rest of these parameters.
:param macs: None or a set of MAC addresses that the instance
should use. macs is supplied by the hypervisor driver (contrast
with requested_networks which is user supplied).
NB: QuantumV2 does not yet honour mac address limits.
"""
quantum = quantumv2.get_client(context)
LOG.debug(_('allocate_for_instance() for %s'),
instance['display_name'])

View File

@@ -1510,6 +1510,27 @@ class ComputeTestCase(BaseTestCase):
instance=instance)
self.compute.terminate_instance(self.context, instance=instance)
def test_run_instance_queries_macs(self):
# run_instance should ask the driver for node mac addresses and pass
# that to the network_api in use.
fake_network.unset_stub_network_methods(self.stubs)
instance = jsonutils.to_primitive(self._create_fake_instance())
macs = set(['01:23:45:67:89:ab'])
self.mox.StubOutWithMock(self.compute.network_api,
"allocate_for_instance")
self.compute.network_api.allocate_for_instance(
mox.IgnoreArg(),
mox.IgnoreArg(),
requested_networks=None,
vpn=False, macs=macs).AndReturn(
fake_network.fake_get_instance_nw_info(self.stubs, 1, 1,
spectacular=True))
self.mox.StubOutWithMock(self.compute.driver, "macs_for_instance")
self.compute.driver.macs_for_instance(instance).AndReturn(macs)
self.mox.ReplayAll()
self.compute.run_instance(self.context, instance=instance)
def test_instance_set_to_error_on_uncaught_exception(self):
# Test that instance is set to error state when exception is raised.
instance = jsonutils.to_primitive(self._create_fake_instance())
@@ -1520,7 +1541,8 @@ class ComputeTestCase(BaseTestCase):
mox.IgnoreArg(),
mox.IgnoreArg(),
requested_networks=None,
vpn=False).AndRaise(rpc_common.RemoteError())
vpn=False,
macs=None).AndRaise(rpc_common.RemoteError())
fake_network.unset_stub_network_methods(self.stubs)

View File

@@ -17,8 +17,11 @@
"""Tests for network API."""
import itertools
import random
import mox
from nova import context
from nova import exception
from nova import network
@@ -37,6 +40,25 @@ class ApiTestCase(test.TestCase):
self.context = context.RequestContext('fake-user',
'fake-project')
def test_allocate_for_instance_handles_macs_passed(self):
# If a macs argument is supplied to the 'nova-network' API, it is just
# ignored. This test checks that the call down to the rpcapi layer
# doesn't pass macs down: nova-network doesn't support hypervisor
# mac address limits (today anyhow).
macs = set(['ab:cd:ef:01:23:34'])
self.mox.StubOutWithMock(
self.network_api.network_rpcapi, "allocate_for_instance")
kwargs = dict(zip(['host', 'instance_id', 'instance_uuid',
'project_id', 'requested_networks', 'rxtx_factor', 'vpn'],
itertools.repeat(mox.IgnoreArg())))
self.network_api.network_rpcapi.allocate_for_instance(
mox.IgnoreArg(), **kwargs).AndReturn([])
self.mox.ReplayAll()
instance = dict(id='id', uuid='uuid', project_id='project_id',
host='host', instance_type={'rxtx_factor': 0})
self.network_api.allocate_for_instance(
'context', instance, 'vpn', 'requested_networks', macs=macs)
def _do_test_associate_floating_ip(self, orig_instance_uuid):
"""Test post-association logic."""

View File

@@ -420,6 +420,14 @@ class TestQuantumv2(test.TestCase):
# Allocate one port in two networks env.
self._allocate_for_instance(2)
def test_allocate_for_instance_accepts_macs_kwargs_None(self):
# The macs kwarg should be accepted as None.
self._allocate_for_instance(1, macs=None)
def test_allocate_for_instance_accepts_macs_kwargs_set(self):
# The macs kwarg should be accepted, as a set.
self._allocate_for_instance(1, macs=set(['ab:cd:ef:01:23:45']))
def test_allocate_for_instance_with_requested_networks(self):
# specify only first and last network
requested_networks = [

View File

@@ -732,6 +732,35 @@ class ComputeDriver(object):
# related helpers.
raise NotImplementedError(self.legacy_nwinfo)
def macs_for_instance(self, instance):
"""What MAC addresses must this instance have?
Some hypervisors (such as bare metal) cannot do freeform virtualisation
of MAC addresses. This method allows drivers to return a set of MAC
addresses that the instance is to have. allocate_for_instance will take
this into consideration when provisioning networking for the instance.
Mapping of MAC addresses to actual networks (or permitting them to be
freeform) is up to the network implementation layer. For instance,
with openflow switches, fixed MAC addresses can still be virtualised
onto any L2 domain, with arbitrary VLANs etc, but regular switches
require pre-configured MAC->network mappings that will match the
actual configuration.
Most hypervisors can use the default implementation which returns None.
Hypervisors with MAC limits should return a set of MAC addresses, which
will be supplied to the allocate_for_instance call by the compute
manager, and it is up to that call to ensure that all assigned network
details are compatible with the set of MAC addresses.
This is called during spawn_instance by the compute manager.
:return: None, or a set of MAC ids (e.g. set(['12:34:56:78:90:ab'])).
None means 'no constraints', a set means 'these and only these
MAC addresses'.
"""
return None
def manage_image_cache(self, context, all_instances):
"""
Manage the driver's local image cache.