diff --git a/nova/api/openstack/compute/attach_interfaces.py b/nova/api/openstack/compute/attach_interfaces.py index cbd5c6be5825..ec27922b1a29 100644 --- a/nova/api/openstack/compute/attach_interfaces.py +++ b/nova/api/openstack/compute/attach_interfaces.py @@ -119,7 +119,8 @@ class InterfaceAttachmentController(wsgi.Controller): exception.NetworkDuplicated, exception.NetworkAmbiguous, exception.NoMoreFixedIps, - exception.PortNotUsable) as e: + exception.PortNotUsable, + exception.AttachInterfaceNotSupported) as e: raise exc.HTTPBadRequest(explanation=e.format_message()) except (exception.InstanceIsLocked, exception.FixedIpAlreadyInUse, @@ -128,8 +129,6 @@ class InterfaceAttachmentController(wsgi.Controller): except (exception.PortNotFound, exception.NetworkNotFound) as e: raise exc.HTTPNotFound(explanation=e.format_message()) - except NotImplementedError: - common.raise_feature_not_supported() except exception.InterfaceAttachFailed as e: raise webob.exc.HTTPInternalServerError( explanation=e.format_message()) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 9e726efd8c39..b233440535fb 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -5023,6 +5023,9 @@ class ComputeManager(manager.Manager): def attach_interface(self, context, instance, network_id, port_id, requested_ip): """Use hotplug to add an network adapter to an instance.""" + if not self.driver.capabilities['supports_attach_interface']: + raise exception.AttachInterfaceNotSupported( + instance_id=instance.uuid) 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, diff --git a/nova/exception.py b/nova/exception.py index 3caaef7d0efe..0f39b925e75f 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -2119,3 +2119,8 @@ class OsInfoNotFound(NotFound): class BuildRequestNotFound(NotFound): msg_fmt = _("BuildRequest not found for instance %(uuid)s") + + +class AttachInterfaceNotSupported(Invalid): + msg_fmt = _("Attaching interfaces is not supported for " + "instance %(instance)s.") diff --git a/nova/tests/unit/api/openstack/compute/test_attach_interfaces.py b/nova/tests/unit/api/openstack/compute/test_attach_interfaces.py index 89933050465e..9df2604bf8aa 100644 --- a/nova/tests/unit/api/openstack/compute/test_attach_interfaces.py +++ b/nova/tests/unit/api/openstack/compute/test_attach_interfaces.py @@ -303,14 +303,6 @@ class InterfaceAttachTestsV21(test.NoDBTestCase): self.attachments.create, self.req, FAKE_UUID1, body=body) - @mock.patch.object(compute_api.API, 'attach_interface', - side_effect=NotImplementedError()) - def test_attach_interface_with_not_implemented(self, _mock): - body = {'interfaceAttachment': {'net_id': FAKE_NET_ID1}} - self.assertRaises(exc.HTTPNotImplemented, - self.attachments.create, self.req, FAKE_UUID1, - body=body) - def test_detach_interface_with_invalid_state(self): def fake_detach_interface_invalid_state(*args, **kwargs): raise exception.InstanceInvalidState( diff --git a/nova/tests/unit/compute/test_compute.py b/nova/tests/unit/compute/test_compute.py index ab7cbe3ac851..cbfc136be97e 100644 --- a/nova/tests/unit/compute/test_compute.py +++ b/nova/tests/unit/compute/test_compute.py @@ -9528,11 +9528,13 @@ class ComputeAPITestCase(BaseTestCase): bind_host_id='fake-host' ).AndReturn(nwinfo) self.mox.ReplayAll() - vif = self.compute.attach_interface(self.context, - instance, - network_id, - port_id, - req_ip) + with mock.patch.dict(self.compute.driver.capabilities, + supports_attach_interface=True): + vif = self.compute.attach_interface(self.context, + instance, + network_id, + port_id, + req_ip) self.assertEqual(vif['id'], network_id) return nwinfo, port_id @@ -9555,8 +9557,10 @@ class ComputeAPITestCase(BaseTestCase): mock.patch.object(self.compute.network_api, 'allocate_port_for_instance'), mock.patch.object(self.compute.network_api, - 'deallocate_port_for_instance')) as ( - mock_attach, mock_allocate, mock_deallocate): + 'deallocate_port_for_instance'), + mock.patch.dict(self.compute.driver.capabilities, + supports_attach_interface=True)) as ( + mock_attach, mock_allocate, mock_deallocate, mock_dict): mock_allocate.return_value = nwinfo mock_attach.side_effect = exception.NovaException("attach_failed") diff --git a/nova/tests/unit/compute/test_compute_mgr.py b/nova/tests/unit/compute/test_compute_mgr.py index 2af816093be9..b89c5f21f10a 100644 --- a/nova/tests/unit/compute/test_compute_mgr.py +++ b/nova/tests/unit/compute/test_compute_mgr.py @@ -1663,7 +1663,9 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase): mock.call(self.context, f_instance, e, mock.ANY)]) - do_test() + with mock.patch.dict(self.compute.driver.capabilities, + supports_attach_interface=True): + do_test() def test_detach_interface_failure(self): # Test that the fault methods are invoked when a detach fails diff --git a/nova/tests/unit/virt/ironic/test_driver.py b/nova/tests/unit/virt/ironic/test_driver.py index e2a6902bb7d7..6adcf8cfcb74 100644 --- a/nova/tests/unit/virt/ironic/test_driver.py +++ b/nova/tests/unit/virt/ironic/test_driver.py @@ -121,6 +121,15 @@ class IronicDriverTestCase(test.NoDBTestCase): self.assertFalse(self.driver.capabilities['supports_recreate'], 'Driver capabilities for \'supports_recreate\'' 'is invalid') + self.assertFalse(self.driver.capabilities[ + 'supports_migrate_to_same_host'], + 'Driver capabilities for ' + '\'supports_migrate_to_same_host\' is invalid') + self.assertFalse(self.driver.capabilities[ + 'supports_attach_interface'], + 'Driver capabilities for ' + '\'supports_attach_interface\' ' + 'is invalid') def test__get_hypervisor_type(self): self.assertEqual('ironic', self.driver._get_hypervisor_type()) diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index 89ee71a381c3..248529ff67b0 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -663,6 +663,10 @@ class LibvirtConnTestCase(test.NoDBTestCase): self.assertFalse(drvr.capabilities['supports_migrate_to_same_host'], 'Driver capabilities for ' '\'supports_migrate_to_same_host\' is invalid') + self.assertTrue(drvr.capabilities['supports_attach_interface'], + 'Driver capabilities for ' + '\'supports_attach_interface\' ' + 'is invalid') def create_fake_libvirt_mock(self, **kwargs): """Defining mocks for LibvirtDriver(libvirt is not used).""" diff --git a/nova/virt/driver.py b/nova/virt/driver.py index ef8272712476..bf980b8c882f 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -135,7 +135,8 @@ class ComputeDriver(object): capabilities = { "has_imagecache": False, "supports_recreate": False, - "supports_migrate_to_same_host": False + "supports_migrate_to_same_host": False, + "supports_attach_interface": False } def __init__(self, virtapi): diff --git a/nova/virt/hyperv/driver.py b/nova/virt/hyperv/driver.py index 091c0fb086c3..29e4ac3379eb 100644 --- a/nova/virt/hyperv/driver.py +++ b/nova/virt/hyperv/driver.py @@ -94,7 +94,8 @@ class HyperVDriver(driver.ComputeDriver): capabilities = { "has_imagecache": False, "supports_recreate": False, - "supports_migrate_to_same_host": True + "supports_migrate_to_same_host": True, + "supports_attach_interface": True } def __init__(self, virtapi): diff --git a/nova/virt/ironic/driver.py b/nova/virt/ironic/driver.py index 9cbf4d706459..e3a5d3b271d3 100644 --- a/nova/virt/ironic/driver.py +++ b/nova/virt/ironic/driver.py @@ -124,7 +124,9 @@ class IronicDriver(virt_driver.ComputeDriver): capabilities = {"has_imagecache": False, "supports_recreate": False, - "supports_migrate_to_same_host": False} + "supports_migrate_to_same_host": False, + "supports_attach_interface": False + } def __init__(self, virtapi, read_only=False): super(IronicDriver, self).__init__(virtapi) diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 69afba4eb018..2f92691635bb 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -504,7 +504,8 @@ class LibvirtDriver(driver.ComputeDriver): capabilities = { "has_imagecache": True, "supports_recreate": True, - "supports_migrate_to_same_host": False + "supports_migrate_to_same_host": False, + "supports_attach_interface": True } def __init__(self, virtapi, read_only=False): diff --git a/nova/virt/vmwareapi/driver.py b/nova/virt/vmwareapi/driver.py index 8273b4275728..d3916ef8b1cb 100644 --- a/nova/virt/vmwareapi/driver.py +++ b/nova/virt/vmwareapi/driver.py @@ -56,7 +56,8 @@ class VMwareVCDriver(driver.ComputeDriver): capabilities = { "has_imagecache": True, "supports_recreate": False, - "supports_migrate_to_same_host": True + "supports_migrate_to_same_host": True, + "supports_attach_interface": True } # Legacy nodename is of the form: ()