Skip to content

Commit c49bd42

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "api: Block unsupported actions with vDPA"
2 parents 9feafc3 + 45798ad commit c49bd42

18 files changed

+168
-44
lines changed

nova/api/openstack/compute/attach_interfaces.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,12 @@ def create(self, req, server_id, body):
178178
exception.InterfaceAttachPciClaimFailed,
179179
exception.InterfaceAttachResourceAllocationFailed) as e:
180180
raise exc.HTTPBadRequest(explanation=e.format_message())
181-
except (exception.InstanceIsLocked,
182-
exception.FixedIpAlreadyInUse,
183-
exception.PortInUse) as e:
181+
except (
182+
exception.OperationNotSupportedForVDPAInterface,
183+
exception.InstanceIsLocked,
184+
exception.FixedIpAlreadyInUse,
185+
exception.PortInUse,
186+
) as e:
184187
raise exc.HTTPConflict(explanation=e.format_message())
185188
except (exception.PortNotFound,
186189
exception.NetworkNotFound) as e:
@@ -214,7 +217,10 @@ def delete(self, req, server_id, id):
214217
instance, port_id=port_id)
215218
except exception.PortNotFound as e:
216219
raise exc.HTTPNotFound(explanation=e.format_message())
217-
except exception.InstanceIsLocked as e:
220+
except (
221+
exception.OperationNotSupportedForVDPAInterface,
222+
exception.InstanceIsLocked,
223+
) as e:
218224
raise exc.HTTPConflict(explanation=e.format_message())
219225
except NotImplementedError:
220226
common.raise_feature_not_supported()

nova/api/openstack/compute/evacuate.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,10 @@ def _evacuate(self, req, id, body):
130130
raise exc.HTTPBadRequest(explanation=e.format_message())
131131
except exception.ForbiddenWithAccelerators as e:
132132
raise exc.HTTPForbidden(explanation=e.format_message())
133-
except exception.OperationNotSupportedForVTPM as e:
133+
except (
134+
exception.OperationNotSupportedForVTPM,
135+
exception.OperationNotSupportedForVDPAInterface,
136+
) as e:
134137
raise exc.HTTPConflict(explanation=e.format_message())
135138

136139
if (not api_version_request.is_supported(req, min_version='2.14') and

nova/api/openstack/compute/migrate_server.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def _migrate(self, req, id, body):
6666
exception.InstanceIsLocked,
6767
exception.InstanceNotReady,
6868
exception.ServiceUnavailable,
69+
exception.OperationNotSupportedForVDPAInterface,
6970
) as e:
7071
raise exc.HTTPConflict(explanation=e.format_message())
7172
except exception.InstanceInvalidState as state_error:
@@ -142,6 +143,7 @@ def _migrate_live(self, req, id, body):
142143
except (
143144
exception.OperationNotSupportedForSEV,
144145
exception.OperationNotSupportedForVTPM,
146+
exception.OperationNotSupportedForVDPAInterface,
145147
) as e:
146148
raise exc.HTTPConflict(explanation=e.format_message())
147149
except exception.InstanceIsLocked as e:

nova/api/openstack/compute/rescue.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def _rescue(self, req, id, body):
6666
except (
6767
exception.InstanceIsLocked,
6868
exception.OperationNotSupportedForVTPM,
69+
exception.OperationNotSupportedForVDPAInterface,
6970
exception.InvalidVolume,
7071
) as e:
7172
raise exc.HTTPConflict(explanation=e.format_message())

nova/api/openstack/compute/servers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,7 @@ def _resize(self, req, instance_id, flavor_id, auto_disk_config=None):
951951
raise exc.HTTPForbidden(
952952
explanation=error.format_message())
953953
except (
954+
exception.OperationNotSupportedForVDPAInterface,
954955
exception.InstanceIsLocked,
955956
exception.InstanceNotReady,
956957
exception.MixedInstanceNotSupportByComputeService,
@@ -1106,6 +1107,7 @@ def _action_rebuild(self, req, id, body):
11061107
except (
11071108
exception.InstanceIsLocked,
11081109
exception.OperationNotSupportedForVTPM,
1110+
exception.OperationNotSupportedForVDPAInterface,
11091111
) as e:
11101112
raise exc.HTTPConflict(explanation=e.format_message())
11111113
except exception.InstanceInvalidState as state_error:

nova/api/openstack/compute/shelve.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ def _shelve(self, req, id, body):
5252
except (
5353
exception.InstanceIsLocked,
5454
exception.OperationNotSupportedForVTPM,
55+
exception.OperationNotSupportedForVDPAInterface,
5556
exception.UnexpectedTaskStateError,
5657
) as e:
5758
raise exc.HTTPConflict(explanation=e.format_message())

nova/api/openstack/compute/suspend_server.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ def _suspend(self, req, id, body):
3838
target={'user_id': server.user_id,
3939
'project_id': server.project_id})
4040
self.compute_api.suspend(context, server)
41-
except (exception.OperationNotSupportedForSEV,
42-
exception.InstanceIsLocked) as e:
41+
except (
42+
exception.OperationNotSupportedForSEV,
43+
exception.OperationNotSupportedForVDPAInterface,
44+
exception.InstanceIsLocked,
45+
) as e:
4346
raise exc.HTTPConflict(explanation=e.format_message())
4447
except exception.InstanceInvalidState as state_error:
4548
common.raise_http_conflict_for_instance_invalid_state(state_error,

nova/compute/api.py

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,27 @@ def inner(self, context, instance, *args, **kw):
265265
return outer
266266

267267

268+
def reject_vdpa_instances(operation):
269+
"""Reject requests to decorated function if instance has vDPA interfaces.
270+
271+
Raise OperationNotSupportedForVDPAInterfaces if operations involves one or
272+
more vDPA interfaces.
273+
"""
274+
275+
def outer(f):
276+
@functools.wraps(f)
277+
def inner(self, context, instance, *args, **kw):
278+
if any(
279+
vif['vnic_type'] == network_model.VNIC_TYPE_VDPA
280+
for vif in instance.get_network_info()
281+
):
282+
raise exception.OperationNotSupportedForVDPAInterface(
283+
instance_uuid=instance.uuid, operation=operation)
284+
return f(self, context, instance, *args, **kw)
285+
return inner
286+
return outer
287+
288+
268289
def load_cells():
269290
global CELLS
270291
if not CELLS:
@@ -3948,6 +3969,9 @@ def _validate_host_for_cold_migrate(
39483969

39493970
# TODO(stephenfin): This logic would be so much easier to grok if we
39503971
# finally split resize and cold migration into separate code paths
3972+
# FIXME(sean-k-mooney): Cold migrate and resize to different hosts
3973+
# probably works but they have not been tested so block them for now
3974+
@reject_vdpa_instances(instance_actions.RESIZE)
39513975
@block_accelerators()
39523976
@check_instance_lock
39533977
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED])
@@ -3962,6 +3986,7 @@ def resize(self, context, instance, flavor_id=None, clean_shutdown=True,
39623986
host_name is always None in the resize case.
39633987
host_name can be set in the cold migration case only.
39643988
"""
3989+
39653990
allow_cross_cell_resize = self._allow_cross_cell_resize(
39663991
context, instance)
39673992

@@ -4165,6 +4190,9 @@ def _allow_resize_to_same_host(self, cold_migrate, instance):
41654190
allow_same_host = CONF.allow_resize_to_same_host
41664191
return allow_same_host
41674192

4193+
# FIXME(sean-k-mooney): Shelve works but unshelve does not due to bug
4194+
# #1851545, so block it for now
4195+
@reject_vdpa_instances(instance_actions.SHELVE)
41684196
@reject_vtpm_instances(instance_actions.SHELVE)
41694197
@block_accelerators(until_service=54)
41704198
@check_instance_lock
@@ -4184,7 +4212,6 @@ def shelve(self, context, instance, clean_shutdown=True):
41844212
instance.system_metadata.update(
41854213
{'image_base_image_ref': instance.image_ref}
41864214
)
4187-
41884215
instance.save(expected_task_state=[None])
41894216

41904217
self._record_action_start(context, instance, instance_actions.SHELVE)
@@ -4352,6 +4379,10 @@ def get_instance_diagnostics(self, context, instance):
43524379
return self.compute_rpcapi.get_instance_diagnostics(context,
43534380
instance=instance)
43544381

4382+
# FIXME(sean-k-mooney): Suspend does not work because we do not unplug
4383+
# the vDPA devices before calling managed save as we do with SR-IOV
4384+
# devices
4385+
@reject_vdpa_instances(instance_actions.SUSPEND)
43554386
@block_accelerators()
43564387
@reject_sev_instances(instance_actions.SUSPEND)
43574388
@check_instance_lock
@@ -5015,19 +5046,27 @@ def attach_interface(self, context, instance, network_id, port_id,
50155046
self._record_action_start(
50165047
context, instance, instance_actions.ATTACH_INTERFACE)
50175048

5018-
# NOTE(gibi): Checking if the requested port has resource request as
5019-
# such ports are only supported if the compute service version is >= 55
5020-
# TODO(gibi): Remove this check in X as there we can be sure that all
5021-
# computes are new enough
50225049
if port_id:
5023-
port = self.network_api.show_port(context, port_id)
5024-
if port['port'].get(constants.RESOURCE_REQUEST):
5050+
port = self.network_api.show_port(context, port_id)['port']
5051+
# NOTE(gibi): Checking if the requested port has resource request
5052+
# as such ports are only supported if the compute service version
5053+
# is >= 55.
5054+
# TODO(gibi): Remove this check in X as there we can be sure
5055+
# that all computes are new enough.
5056+
if port.get(constants.RESOURCE_REQUEST):
50255057
svc = objects.Service.get_by_host_and_binary(
50265058
context, instance.host, 'nova-compute')
50275059
if svc.version < 55:
50285060
raise exception.AttachInterfaceWithQoSPolicyNotSupported(
50295061
instance_uuid=instance.uuid)
50305062

5063+
if port.get('binding:vnic_type', "normal") == "vdpa":
5064+
# FIXME(sean-k-mooney): Attach works but detach results in a
5065+
# QEMU error; blocked until this is resolved
5066+
raise exception.OperationNotSupportedForVDPAInterface(
5067+
instance_uuid=instance.uuid,
5068+
operation=instance_actions.ATTACH_INTERFACE)
5069+
50315070
return self.compute_rpcapi.attach_interface(context,
50325071
instance=instance, network_id=network_id, port_id=port_id,
50335072
requested_ip=requested_ip, tag=tag)
@@ -5038,6 +5077,29 @@ def attach_interface(self, context, instance, network_id, port_id,
50385077
task_state=[None])
50395078
def detach_interface(self, context, instance, port_id):
50405079
"""Detach an network adapter from an instance."""
5080+
5081+
# FIXME(sean-k-mooney): Detach currently results in a failure to remove
5082+
# the interface from the live libvirt domain, so while the networking
5083+
# is torn down on the host the vDPA device is still attached to the VM.
5084+
# This is likely a libvirt/qemu bug so block detach until that is
5085+
# resolved.
5086+
for vif in instance.get_network_info():
5087+
if vif['id'] == port_id:
5088+
if vif['vnic_type'] == 'vdpa':
5089+
raise exception.OperationNotSupportedForVDPAInterface(
5090+
instance_uuid=instance.uuid,
5091+
operation=instance_actions.DETACH_INTERFACE)
5092+
break
5093+
else:
5094+
# NOTE(sean-k-mooney) This should never happen but just in case the
5095+
# info cache does not have the port we are detaching we can fall
5096+
# back to neutron.
5097+
port = self.network_api.show_port(context, port_id)['port']
5098+
if port.get('binding:vnic_type', 'normal') == 'vdpa':
5099+
raise exception.OperationNotSupportedForVDPAInterface(
5100+
instance_uuid=instance.uuid,
5101+
operation=instance_actions.DETACH_INTERFACE)
5102+
50415103
self._record_action_start(
50425104
context, instance, instance_actions.DETACH_INTERFACE)
50435105
self.compute_rpcapi.detach_interface(context, instance=instance,
@@ -5079,6 +5141,7 @@ def update_instance_metadata(self, context, instance,
50795141

50805142
return _metadata
50815143

5144+
@reject_vdpa_instances(instance_actions.LIVE_MIGRATION)
50825145
@block_accelerators()
50835146
@reject_vtpm_instances(instance_actions.LIVE_MIGRATION)
50845147
@reject_sev_instances(instance_actions.LIVE_MIGRATION)
@@ -5210,6 +5273,8 @@ def live_migrate_abort(self, context, instance, migration_id,
52105273
self.compute_rpcapi.live_migration_abort(context,
52115274
instance, migration.id)
52125275

5276+
# FIXME(sean-k-mooney): rebuild works but we have not tested evacuate yet
5277+
@reject_vdpa_instances(instance_actions.EVACUATE)
52135278
@reject_vtpm_instances(instance_actions.EVACUATE)
52145279
@block_accelerators(until_service=SUPPORT_ACCELERATOR_SERVICE_FOR_REBUILD)
52155280
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED,

nova/exception.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,14 @@ class OperationNotSupportedForVTPM(NovaException):
537537
code = 409
538538

539539

540+
class OperationNotSupportedForVDPAInterface(NovaException):
541+
msg_fmt = _(
542+
"Operation '%(operation)s' not supported for instance with "
543+
"vDPA ports ((instance_uuid)s)."
544+
)
545+
code = 409
546+
547+
540548
class InvalidHypervisorType(Invalid):
541549
msg_fmt = _("The supplied hypervisor type of is invalid.")
542550

nova/tests/unit/api/openstack/compute/test_evacuate.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,15 @@ def test_evacuate__with_vtpm(self, mock_evacuate):
119119
webob.exc.HTTPConflict,
120120
{'host': 'foo', 'onSharedStorage': 'False', 'adminPass': 'bar'})
121121

122+
@mock.patch('nova.compute.api.API.evacuate')
123+
def test_evacuate__with_vdpa_interface(self, mock_evacuate):
124+
mock_evacuate.side_effect = \
125+
exception.OperationNotSupportedForVDPAInterface(
126+
instance_uuid=uuids.instance, operation='foo')
127+
self._check_evacuate_failure(
128+
webob.exc.HTTPConflict,
129+
{'host': 'foo', 'onSharedStorage': 'False', 'adminPass': 'bar'})
130+
122131
def test_evacuate_with_active_service(self):
123132
def fake_evacuate(*args, **kwargs):
124133
raise exception.ComputeServiceInUse("Service still in use")

0 commit comments

Comments
 (0)