Plumbing for tagged nic attachment
This patch adds support to the compute manager and Neutron API for tagged attach for network interfaces. Change-Id: I3ca96e6d791d609db8bf4368a3c71f880de8e7ad Implements: blueprint virt-device-tagged-attach-detach
This commit is contained in:
parent
68c95c9290
commit
0f3e85e136
|
@ -13,7 +13,7 @@
|
|||
"disabled_reason": null,
|
||||
"report_count": 1,
|
||||
"forced_down": false,
|
||||
"version": 19,
|
||||
"version": 20,
|
||||
"availability_zone": null,
|
||||
"uuid": "fa69c544-906b-4a6a-a9c6-c1f7a8078c73"
|
||||
}
|
||||
|
|
|
@ -134,7 +134,8 @@ class InterfaceAttachmentController(wsgi.Controller):
|
|||
exception.NoMoreFixedIps,
|
||||
exception.PortNotUsable,
|
||||
exception.AttachInterfaceNotSupported,
|
||||
exception.SecurityGroupCannotBeApplied) as e:
|
||||
exception.SecurityGroupCannotBeApplied,
|
||||
exception.TaggedAttachmentNotSupported) as e:
|
||||
raise exc.HTTPBadRequest(explanation=e.format_message())
|
||||
except (exception.InstanceIsLocked,
|
||||
exception.FixedIpAlreadyInUse,
|
||||
|
|
|
@ -3922,11 +3922,11 @@ class API(base.Base):
|
|||
vm_states.STOPPED],
|
||||
task_state=[None])
|
||||
def attach_interface(self, context, instance, network_id, port_id,
|
||||
requested_ip):
|
||||
requested_ip, tag=None):
|
||||
"""Use hotplug to add an network adapter to an instance."""
|
||||
return self.compute_rpcapi.attach_interface(context,
|
||||
instance=instance, network_id=network_id, port_id=port_id,
|
||||
requested_ip=requested_ip)
|
||||
requested_ip=requested_ip, tag=tag)
|
||||
|
||||
@check_instance_lock
|
||||
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.PAUSED,
|
||||
|
|
|
@ -481,7 +481,7 @@ class ComputeVirtAPI(virtapi.VirtAPI):
|
|||
class ComputeManager(manager.Manager):
|
||||
"""Manages the running instances from creation to destruction."""
|
||||
|
||||
target = messaging.Target(version='4.15')
|
||||
target = messaging.Target(version='4.16')
|
||||
|
||||
# How long to wait in seconds before re-issuing a shutdown
|
||||
# signal to an instance during power off. The overall
|
||||
|
@ -5151,15 +5151,19 @@ class ComputeManager(manager.Manager):
|
|||
@wrap_exception()
|
||||
@wrap_instance_fault
|
||||
def attach_interface(self, context, instance, network_id, port_id,
|
||||
requested_ip):
|
||||
requested_ip, tag=None):
|
||||
"""Use hotplug to add an network adapter to an instance."""
|
||||
if not self.driver.capabilities['supports_attach_interface']:
|
||||
raise exception.AttachInterfaceNotSupported(
|
||||
instance_uuid=instance.uuid)
|
||||
if (tag and not
|
||||
self.driver.capabilities.get('supports_tagged_attach_interface',
|
||||
False)):
|
||||
raise exception.NetworkInterfaceTaggedAttachNotSupported()
|
||||
bind_host_id = self.driver.network_binding_host_id(context, instance)
|
||||
network_info = self.network_api.allocate_port_for_instance(
|
||||
context, instance, port_id, network_id, requested_ip,
|
||||
bind_host_id=bind_host_id)
|
||||
bind_host_id=bind_host_id, tag=tag)
|
||||
if len(network_info) != 1:
|
||||
LOG.error('allocate_port_for_instance returned %(ports)s '
|
||||
'ports', {'ports': len(network_info)})
|
||||
|
|
|
@ -325,6 +325,7 @@ class ComputeAPI(object):
|
|||
the version because this method was unused before. The version
|
||||
was bumped to signal the availability of the corrected RPC API
|
||||
* 4.15 - Add tag argument to reserve_block_device_name()
|
||||
* 4.16 - Add tag argument to attach_interface()
|
||||
'''
|
||||
|
||||
VERSION_ALIASES = {
|
||||
|
@ -420,13 +421,26 @@ class ComputeAPI(object):
|
|||
instance=instance, network_id=network_id)
|
||||
|
||||
def attach_interface(self, ctxt, instance, network_id, port_id,
|
||||
requested_ip):
|
||||
version = '4.0'
|
||||
cctxt = self.router.client(ctxt).prepare(
|
||||
server=_compute_host(None, instance), version=version)
|
||||
return cctxt.call(ctxt, 'attach_interface',
|
||||
instance=instance, network_id=network_id,
|
||||
port_id=port_id, requested_ip=requested_ip)
|
||||
requested_ip, tag=None):
|
||||
kw = {'instance': instance, 'network_id': network_id,
|
||||
'port_id': port_id, 'requested_ip': requested_ip,
|
||||
'tag': tag}
|
||||
version = '4.16'
|
||||
|
||||
client = self.router.client(ctxt)
|
||||
if not client.can_send_version(version):
|
||||
if tag:
|
||||
# NOTE(artom) Attach attempted with a device role tag, but
|
||||
# we're pinned to less than 4.16 - ie, not all nodes have
|
||||
# received the Pike code yet.
|
||||
raise exception.TaggedAttachmentNotSupported()
|
||||
else:
|
||||
version = '4.0'
|
||||
kw.pop('tag')
|
||||
|
||||
cctxt = client.prepare(server=_compute_host(None, instance),
|
||||
version=version)
|
||||
return cctxt.call(ctxt, 'attach_interface', **kw)
|
||||
|
||||
def attach_volume(self, ctxt, instance, bdm):
|
||||
version = '4.0'
|
||||
|
|
|
@ -283,6 +283,11 @@ class VolumeTaggedAttachToShelvedNotSupported(TaggedAttachmentNotSupported):
|
|||
"shelved-offloaded instances.")
|
||||
|
||||
|
||||
class NetworkInterfaceTaggedAttachNotSupported(TaggedAttachmentNotSupported):
|
||||
msg_fmt = _("Tagged network interface attachment is not supported for "
|
||||
"this server instance.")
|
||||
|
||||
|
||||
class InvalidKeypair(Invalid):
|
||||
msg_fmt = _("Keypair data is invalid: %(reason)s")
|
||||
|
||||
|
|
|
@ -301,7 +301,7 @@ class API(base_api.NetworkAPI):
|
|||
# NOTE(danms): Here for neutron compatibility
|
||||
def allocate_port_for_instance(self, context, instance, port_id,
|
||||
network_id=None, requested_ip=None,
|
||||
bind_host_id=None):
|
||||
bind_host_id=None, tag=None):
|
||||
raise NotImplementedError()
|
||||
|
||||
# NOTE(danms): Here for neutron compatibility
|
||||
|
|
|
@ -210,7 +210,7 @@ class NetworkAPI(base.Base):
|
|||
|
||||
def allocate_port_for_instance(self, context, instance, port_id,
|
||||
network_id=None, requested_ip=None,
|
||||
bind_host_id=None):
|
||||
bind_host_id=None, tag=None):
|
||||
"""Allocate port for instance."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
|
@ -1196,13 +1196,14 @@ class API(base_api.NetworkAPI):
|
|||
|
||||
def allocate_port_for_instance(self, context, instance, port_id,
|
||||
network_id=None, requested_ip=None,
|
||||
bind_host_id=None):
|
||||
bind_host_id=None, tag=None):
|
||||
"""Allocate a port for the instance."""
|
||||
requested_networks = objects.NetworkRequestList(
|
||||
objects=[objects.NetworkRequest(network_id=network_id,
|
||||
address=requested_ip,
|
||||
port_id=port_id,
|
||||
pci_request_id=None)])
|
||||
pci_request_id=None,
|
||||
tag=tag)])
|
||||
return self.allocate_for_instance(context, instance, vpn=False,
|
||||
requested_networks=requested_networks,
|
||||
bind_host_id=bind_host_id)
|
||||
|
|
|
@ -32,7 +32,7 @@ LOG = logging.getLogger(__name__)
|
|||
|
||||
|
||||
# NOTE(danms): This is the global service version counter
|
||||
SERVICE_VERSION = 19
|
||||
SERVICE_VERSION = 20
|
||||
|
||||
|
||||
# NOTE(danms): This is our SERVICE_VERSION history. The idea is that any
|
||||
|
@ -106,6 +106,8 @@ SERVICE_VERSION_HISTORY = (
|
|||
{'compute_rpc': '4.14'},
|
||||
# Version 19: Compute RPC version 4.15
|
||||
{'compute_rpc': '4.15'},
|
||||
# Version 20: Compute RPC version 4.16
|
||||
{'compute_rpc': '4.16'},
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -231,6 +231,14 @@ class InterfaceAttachTestsV21(test.NoDBTestCase):
|
|||
self.assertEqual(result['interfaceAttachment']['net_id'],
|
||||
FAKE_NET_ID1)
|
||||
|
||||
@mock.patch.object(
|
||||
compute_api.API, 'attach_interface',
|
||||
side_effect=exception.NetworkInterfaceTaggedAttachNotSupported())
|
||||
def test_interface_tagged_attach_not_supported(self, mock_attach):
|
||||
body = {'interfaceAttachment': {'net_id': FAKE_NET_ID2}}
|
||||
self.assertRaises(exc.HTTPBadRequest, self.attachments.create,
|
||||
self.req, FAKE_UUID1, body=body)
|
||||
|
||||
def test_attach_interface_with_network_id(self):
|
||||
self.stub_out('nova.compute.api.API.attach_interface',
|
||||
fake_attach_interface)
|
||||
|
|
|
@ -9959,9 +9959,46 @@ class ComputeAPITestCase(BaseTestCase):
|
|||
self.assertEqual(vif['id'], network_id)
|
||||
mock_allocate.assert_called_once_with(
|
||||
self.context, instance, port_id, network_id, req_ip,
|
||||
bind_host_id='fake-host')
|
||||
bind_host_id='fake-host', tag=None)
|
||||
return nwinfo, port_id
|
||||
|
||||
def test_interface_tagged_attach(self):
|
||||
new_type = flavors.get_flavor_by_flavor_id('4')
|
||||
instance = objects.Instance(image_ref=uuids.image_instance,
|
||||
system_metadata={},
|
||||
flavor=new_type,
|
||||
host='fake-host')
|
||||
nwinfo = [fake_network_cache_model.new_vif()]
|
||||
network_id = nwinfo[0]['network']['id']
|
||||
port_id = nwinfo[0]['id']
|
||||
req_ip = '1.2.3.4'
|
||||
mock_allocate = mock.Mock(return_value=nwinfo)
|
||||
self.compute.network_api.allocate_port_for_instance = mock_allocate
|
||||
|
||||
with mock.patch.dict(self.compute.driver.capabilities,
|
||||
supports_attach_interface=True,
|
||||
supports_tagged_attach_interface=True):
|
||||
vif = self.compute.attach_interface(self.context,
|
||||
instance,
|
||||
network_id,
|
||||
port_id,
|
||||
req_ip, tag='foo')
|
||||
self.assertEqual(vif['id'], network_id)
|
||||
mock_allocate.assert_called_once_with(
|
||||
self.context, instance, port_id, network_id, req_ip,
|
||||
bind_host_id='fake-host', tag='foo')
|
||||
return nwinfo, port_id
|
||||
|
||||
def test_tagged_attach_interface_raises(self):
|
||||
instance = self._create_fake_instance_obj()
|
||||
with mock.patch.dict(self.compute.driver.capabilities,
|
||||
supports_attach_interface=True,
|
||||
supports_tagged_attach_interface=False):
|
||||
self.assertRaises(
|
||||
exception.NetworkInterfaceTaggedAttachNotSupported,
|
||||
self.compute.attach_interface, self.context, instance,
|
||||
'fake-network-id', 'fake-port-id', 'fake-req-ip', tag='foo')
|
||||
|
||||
def test_attach_interface_failed(self):
|
||||
new_type = flavors.get_flavor_by_flavor_id('4')
|
||||
instance = objects.Instance(
|
||||
|
@ -9993,7 +10030,8 @@ class ComputeAPITestCase(BaseTestCase):
|
|||
instance, network_id, port_id, req_ip)
|
||||
mock_allocate.assert_called_once_with(self.context, instance,
|
||||
network_id, port_id, req_ip,
|
||||
bind_host_id='fake-host')
|
||||
bind_host_id='fake-host',
|
||||
tag=None)
|
||||
mock_deallocate.assert_called_once_with(self.context, instance,
|
||||
port_id)
|
||||
|
||||
|
|
|
@ -5229,6 +5229,15 @@ class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase):
|
|||
self.assertItemsEqual(['default', uuids.secgroup_uuid],
|
||||
security_groups)
|
||||
|
||||
@mock.patch.object(compute_rpcapi.ComputeAPI, 'attach_interface')
|
||||
def test_tagged_interface_attach(self, mock_attach):
|
||||
instance = self._create_instance_obj()
|
||||
self.compute_api.attach_interface(self.context, instance, None, None,
|
||||
None, tag='foo')
|
||||
mock_attach.assert_called_with(self.context, instance=instance,
|
||||
network_id=None, port_id=None,
|
||||
requested_ip=None, tag='foo')
|
||||
|
||||
|
||||
class ComputeAPIAPICellUnitTestCase(_ComputeAPIUnitTestMixIn,
|
||||
test.NoDBTestCase):
|
||||
|
|
|
@ -183,7 +183,59 @@ class ComputeRpcAPITestCase(test.NoDBTestCase):
|
|||
def test_attach_interface(self):
|
||||
self._test_compute_api('attach_interface', 'call',
|
||||
instance=self.fake_instance_obj, network_id='id',
|
||||
port_id='id2', version='4.0', requested_ip='192.168.1.50')
|
||||
port_id='id2', version='4.16', requested_ip='192.168.1.50',
|
||||
tag='foo')
|
||||
|
||||
def test_attach_interface_raises(self):
|
||||
ctxt = context.RequestContext('fake_user', 'fake_project')
|
||||
instance = self.fake_instance_obj
|
||||
rpcapi = compute_rpcapi.ComputeAPI()
|
||||
cctxt_mock = mock.Mock()
|
||||
mock_client = mock.Mock()
|
||||
rpcapi.router.client = mock.Mock()
|
||||
rpcapi.router.client.return_value = mock_client
|
||||
with test.nested(
|
||||
mock.patch.object(mock_client, 'can_send_version',
|
||||
return_value=False),
|
||||
mock.patch.object(mock_client, 'prepare',
|
||||
return_value=cctxt_mock)
|
||||
) as (
|
||||
can_send_mock, prepare_mock
|
||||
):
|
||||
self.assertRaises(exception.TaggedAttachmentNotSupported,
|
||||
rpcapi.attach_interface, ctxt, instance,
|
||||
'fake_network', 'fake_port', 'fake_requested_ip',
|
||||
tag='foo')
|
||||
can_send_mock.assert_called_once_with('4.16')
|
||||
|
||||
def test_attach_interface_downgrades_version(self):
|
||||
ctxt = context.RequestContext('fake_user', 'fake_project')
|
||||
instance = self.fake_instance_obj
|
||||
rpcapi = compute_rpcapi.ComputeAPI()
|
||||
call_mock = mock.Mock()
|
||||
cctxt_mock = mock.Mock(call=call_mock)
|
||||
mock_client = mock.Mock()
|
||||
rpcapi.router.client = mock.Mock()
|
||||
rpcapi.router.client.return_value = mock_client
|
||||
with test.nested(
|
||||
mock.patch.object(mock_client, 'can_send_version',
|
||||
return_value=False),
|
||||
mock.patch.object(mock_client, 'prepare',
|
||||
return_value=cctxt_mock)
|
||||
) as (
|
||||
can_send_mock, prepare_mock
|
||||
):
|
||||
rpcapi.attach_interface(ctxt, instance, 'fake_network',
|
||||
'fake_port', 'fake_requested_ip')
|
||||
|
||||
can_send_mock.assert_called_once_with('4.16')
|
||||
prepare_mock.assert_called_once_with(server=instance['host'],
|
||||
version='4.0')
|
||||
call_mock.assert_called_once_with(ctxt, 'attach_interface',
|
||||
instance=instance,
|
||||
network_id='fake_network',
|
||||
port_id='fake_port',
|
||||
requested_ip='fake_requested_ip')
|
||||
|
||||
def test_attach_volume(self):
|
||||
self._test_compute_api('attach_volume', 'cast',
|
||||
|
|
|
@ -4445,6 +4445,16 @@ class TestNeutronv2WithMock(test.TestCase):
|
|||
"No specific network was requested and none are available for "
|
||||
"project 'fake-project'.", six.text_type(ex))
|
||||
|
||||
@mock.patch.object(neutronapi.API, 'allocate_for_instance')
|
||||
def test_allocate_port_for_instance_with_tag(self, mock_allocate):
|
||||
instance = fake_instance.fake_instance_obj(self.context)
|
||||
api = neutronapi.API()
|
||||
api.allocate_port_for_instance(self.context, instance, None,
|
||||
network_id=None, requested_ip=None,
|
||||
bind_host_id=None, tag='foo')
|
||||
req_nets_in_call = mock_allocate.call_args[1]['requested_networks']
|
||||
self.assertEqual('foo', req_nets_in_call.objects[0].tag)
|
||||
|
||||
@mock.patch('nova.objects.network_request.utils')
|
||||
@mock.patch('nova.network.neutronv2.api.LOG')
|
||||
@mock.patch('nova.network.neutronv2.api.base_api')
|
||||
|
|
Loading…
Reference in New Issue