@@ -380,6 +380,8 @@ def inner(self, context, instance, *args, **kwargs):
380
380
class API :
381
381
"""API for interacting with the compute manager."""
382
382
383
+ _sentinel = object ()
384
+
383
385
def __init__ (self , image_api = None , network_api = None , volume_api = None ):
384
386
self .image_api = image_api or glance .API ()
385
387
self .network_api = network_api or neutron .API ()
@@ -4391,31 +4393,45 @@ def shelve_offload(self, context, instance, clean_shutdown=True):
4391
4393
context , instance = instance ,
4392
4394
clean_shutdown = clean_shutdown , accel_uuids = accel_uuids )
4393
4395
4396
+ def _check_offloaded (self , context , instance ):
4397
+ """Check if the status of an instance is SHELVE_OFFLOADED,
4398
+ if not raise an exception.
4399
+ """
4400
+ if instance .vm_state != vm_states .SHELVED_OFFLOADED :
4401
+ # NOTE(brinzhang): If the server status is 'SHELVED', it still
4402
+ # belongs to a host, the availability_zone should not change.
4403
+ # Unshelving a shelved offloaded server will go through the
4404
+ # scheduler to find a new host.
4405
+ raise exception .UnshelveInstanceInvalidState (
4406
+ state = instance .vm_state , instance_uuid = instance .uuid )
4407
+
4408
+ def _ensure_host_in_az (self , context , host , availability_zone ):
4409
+ """Ensure the host provided belongs to the availability zone,
4410
+ if not raise an exception.
4411
+ """
4412
+ if availability_zone is not None :
4413
+ host_az = availability_zones .get_host_availability_zone (
4414
+ context ,
4415
+ host
4416
+ )
4417
+ if host_az != availability_zone :
4418
+ raise exception .UnshelveHostNotInAZ (
4419
+ host = host , availability_zone = availability_zone )
4420
+
4394
4421
def _validate_unshelve_az (self , context , instance , availability_zone ):
4395
4422
"""Verify the specified availability_zone during unshelve.
4396
4423
4397
- Verifies that the server is shelved offloaded, the AZ exists and
4398
- if [cinder]/cross_az_attach=False, that any attached volumes are in
4399
- the same AZ.
4424
+ Verifies the AZ exists and if [cinder]/cross_az_attach=False, that
4425
+ any attached volumes are in the same AZ.
4400
4426
4401
4427
:param context: nova auth RequestContext for the unshelve action
4402
4428
:param instance: Instance object for the server being unshelved
4403
4429
:param availability_zone: The user-requested availability zone in
4404
4430
which to unshelve the server.
4405
- :raises: UnshelveInstanceInvalidState if the server is not shelved
4406
- offloaded
4407
4431
:raises: InvalidRequest if the requested AZ does not exist
4408
4432
:raises: MismatchVolumeAZException if [cinder]/cross_az_attach=False
4409
4433
and any attached volumes are not in the requested AZ
4410
4434
"""
4411
- if instance .vm_state != vm_states .SHELVED_OFFLOADED :
4412
- # NOTE(brinzhang): If the server status is 'SHELVED', it still
4413
- # belongs to a host, the availability_zone has not changed.
4414
- # Unshelving a shelved offloaded server will go through the
4415
- # scheduler to find a new host.
4416
- raise exception .UnshelveInstanceInvalidState (
4417
- state = instance .vm_state , instance_uuid = instance .uuid )
4418
-
4419
4435
available_zones = availability_zones .get_availability_zones (
4420
4436
context , self .host_api , get_only_available = True )
4421
4437
if availability_zone not in available_zones :
@@ -4443,31 +4459,88 @@ def _validate_unshelve_az(self, context, instance, availability_zone):
4443
4459
4444
4460
@block_extended_resource_request
4445
4461
@check_instance_lock
4446
- @check_instance_state (vm_state = [vm_states .SHELVED ,
4447
- vm_states .SHELVED_OFFLOADED ])
4448
- def unshelve (self , context , instance , new_az = None ):
4449
- """Restore a shelved instance."""
4462
+ @check_instance_state (
4463
+ vm_state = [vm_states .SHELVED , vm_states .SHELVED_OFFLOADED ])
4464
+ def unshelve (
4465
+ self , context , instance , new_az = _sentinel , host = None ):
4466
+ """Restore a shelved instance.
4467
+
4468
+ :param context: the nova request context
4469
+ :param instance: nova.objects.instance.Instance object
4470
+ :param new_az: (optional) target AZ.
4471
+ If None is provided then the current AZ restriction
4472
+ will be removed from the instance.
4473
+ If the parameter is not provided then the current
4474
+ AZ restriction will not be changed.
4475
+ :param host: (optional) a host to target
4476
+ """
4477
+ # Unshelving a shelved offloaded server will go through the
4478
+ # scheduler to pick a new host, so we update the
4479
+ # RequestSpec.availability_zone here. Note that if scheduling
4480
+ # fails the RequestSpec will remain updated, which is not great.
4481
+ # Bug open to track this https://bugs.launchpad.net/nova/+bug/1978573
4482
+
4483
+ az_passed = new_az is not self ._sentinel
4484
+
4450
4485
request_spec = objects .RequestSpec .get_by_instance_uuid (
4451
4486
context , instance .uuid )
4452
4487
4453
- if new_az :
4488
+ # We need to check a list of preconditions and validate inputs first
4489
+
4490
+ # Ensure instance is shelve offloaded
4491
+ if az_passed or host :
4492
+ self ._check_offloaded (context , instance )
4493
+
4494
+ if az_passed and new_az :
4495
+ # we have to ensure that new AZ is valid
4454
4496
self ._validate_unshelve_az (context , instance , new_az )
4455
- LOG .debug ("Replace the old AZ %(old_az)s in RequestSpec "
4456
- "with a new AZ %(new_az)s of the instance." ,
4457
- {"old_az" : request_spec .availability_zone ,
4458
- "new_az" : new_az }, instance = instance )
4459
- # Unshelving a shelved offloaded server will go through the
4460
- # scheduler to pick a new host, so we update the
4461
- # RequestSpec.availability_zone here. Note that if scheduling
4462
- # fails the RequestSpec will remain updated, which is not great,
4463
- # but if we want to change that we need to defer updating the
4464
- # RequestSpec until conductor which probably means RPC changes to
4465
- # pass the new_az variable to conductor. This is likely low
4466
- # priority since the RequestSpec.availability_zone on a shelved
4467
- # offloaded server does not mean much anyway and clearly the user
4468
- # is trying to put the server in the target AZ.
4469
- request_spec .availability_zone = new_az
4470
- request_spec .save ()
4497
+ # This will be the AZ of the instance after the unshelve. It can be
4498
+ # None indicating that the instance is not pinned to any AZ after the
4499
+ # unshelve
4500
+ expected_az_after_unshelve = (
4501
+ request_spec .availability_zone
4502
+ if not az_passed else new_az
4503
+ )
4504
+ # host is requested, so we have to see if it exists and does not
4505
+ # contradict with the AZ of the instance
4506
+ if host :
4507
+ # Ensure that the requested host exists otherwise raise
4508
+ # a ComputeHostNotFound exception
4509
+ objects .ComputeNode .get_first_node_by_host_for_old_compat (
4510
+ context , host , use_slave = True )
4511
+ # A specific host is requested so we need to make sure that it is
4512
+ # not contradicts with the AZ of the instance
4513
+ self ._ensure_host_in_az (
4514
+ context , host , expected_az_after_unshelve )
4515
+
4516
+ if new_az is None :
4517
+ LOG .debug (
4518
+ 'Unpin instance from AZ "%(old_az)s".' ,
4519
+ {'old_az' : request_spec .availability_zone },
4520
+ instance = instance
4521
+ )
4522
+
4523
+ LOG .debug (
4524
+ 'Unshelving instance with old availability_zone "%(old_az)s" to '
4525
+ 'new availability_zone "%(new_az)s" and host "%(host)s".' ,
4526
+ {
4527
+ 'old_az' : request_spec .availability_zone ,
4528
+ 'new_az' : '%s' %
4529
+ new_az if az_passed
4530
+ else 'not provided' ,
4531
+ 'host' : host ,
4532
+ },
4533
+ instance = instance ,
4534
+ )
4535
+ # OK every precondition checks out, we just need to tell the scheduler
4536
+ # where to put the instance
4537
+ # We have the expected AZ already calculated. So we just need to
4538
+ # set it in the request_spec to drive the scheduling
4539
+ request_spec .availability_zone = expected_az_after_unshelve
4540
+ # if host is requested we also need to tell the scheduler that
4541
+ if host :
4542
+ request_spec .requested_destination = objects .Destination (host = host )
4543
+ request_spec .save ()
4471
4544
4472
4545
instance .task_state = task_states .UNSHELVING
4473
4546
instance .save (expected_task_state = [None ])
0 commit comments