357
362
exception.InvalidParameterValue,
358
363
exception.UnsupportedDriverExtension,
359
364
exception.MissingParameterValue)
360
def vendor_passthru(self, context, node_id, driver_method, info):
365
def vendor_passthru(self, context, node_id, driver_method,
361
367
"""RPC method to encapsulate vendor action.
363
369
Synchronously validate driver specific info or get driver status,
364
and if successful, start background worker to perform vendor action
370
and if successful invokes the vendor method. If the method mode
371
is 'async' the conductor will start background worker to perform
367
374
:param context: an admin context.
368
375
:param node_id: the id or uuid of a node.
369
376
:param driver_method: the name of the vendor method.
377
:param http_method: the HTTP method used for the request.
370
378
:param info: vendor method args.
371
379
:raises: InvalidParameterValue if supplied info is not valid.
372
380
:raises: MissingParameterValue if missing supplied info
385
398
if not getattr(task.driver, 'vendor', None):
386
399
raise exception.UnsupportedDriverExtension(
387
400
driver=task.node.driver,
388
extension='vendor passthru')
390
task.driver.vendor.validate(task, method=driver_method,
392
task.spawn_after(self._spawn_worker,
393
task.driver.vendor.vendor_passthru, task,
394
method=driver_method, **info)
396
@messaging.expected_exceptions(exception.InvalidParameterValue,
401
extension='vendor interface')
403
vendor_iface = task.driver.vendor
405
# NOTE(lucasagomes): Before the vendor_passthru() method was
406
# a self-contained method and each driver implemented their own
407
# version of it, now we have a common mechanism that drivers
408
# should use to expose their vendor methods. If a driver still
409
# have their own vendor_passthru() method we call it to be
410
# backward compat. This code should be removed once L opens.
411
if hasattr(vendor_iface, 'vendor_passthru'):
412
LOG.warning(_LW("Drivers implementing their own version "
413
"of vendor_passthru() has been deprecated. "
414
"Please update the code to use the "
415
"@passthru decorator."))
416
vendor_iface.validate(task, method=driver_method,
418
task.spawn_after(self._spawn_worker,
419
vendor_iface.vendor_passthru, task,
420
method=driver_method, **info)
421
# NodeVendorPassthru was always async
425
vendor_opts = vendor_iface.vendor_routes[driver_method]
426
vendor_func = vendor_opts['func']
428
raise exception.InvalidParameterValue(
429
_('No handler for method %s') % driver_method)
431
http_method = http_method.upper()
432
if http_method not in vendor_opts['http_methods']:
433
raise exception.InvalidParameterValue(
434
_('The method %(method)s does not support HTTP %(http)s') %
435
{'method': driver_method, 'http': http_method})
437
vendor_iface.validate(task, method=driver_method,
438
http_method=http_method, **info)
440
# Inform the vendor method which HTTP method it was invoked with
441
info['http_method'] = http_method
443
# Invoke the vendor method accordingly with the mode
444
is_async = vendor_opts['async']
447
task.spawn_after(self._spawn_worker, vendor_func, task, **info)
449
ret = vendor_func(task, **info)
451
return (ret, is_async)
453
@messaging.expected_exceptions(exception.NoFreeConductorWorker,
454
exception.InvalidParameterValue,
397
455
exception.MissingParameterValue,
398
456
exception.UnsupportedDriverExtension,
399
457
exception.DriverNotFound)
400
458
def driver_vendor_passthru(self, context, driver_name, driver_method,
402
"""RPC method which synchronously handles driver-level vendor passthru
403
calls. These calls don't require a node UUID and are executed on a
404
random conductor with the specified driver.
460
"""Handle top-level vendor actions.
462
RPC method which handles driver-level vendor passthru calls. These
463
calls don't require a node UUID and are executed on a random
464
conductor with the specified driver. If the method mode is
465
async the conductor will start background worker to perform
406
468
:param context: an admin context.
407
469
:param driver_name: name of the driver on which to call the method.
408
470
:param driver_method: name of the vendor method, for use by the driver.
471
:param http_method: the HTTP method used for the request.
409
472
:param info: user-supplied data to pass through to the driver.
410
473
:raises: MissingParameterValue if missing supplied info
411
474
:raises: InvalidParameterValue if supplied info is not valid.
425
494
driver=driver_name,
426
495
extension='vendor interface')
428
return driver.vendor.driver_vendor_passthru(context,
429
method=driver_method,
497
# NOTE(lucasagomes): Before the driver_vendor_passthru()
498
# method was a self-contained method and each driver implemented
499
# their own version of it, now we have a common mechanism that
500
# drivers should use to expose their vendor methods. If a driver
501
# still have their own driver_vendor_passthru() method we call
502
# it to be backward compat. This code should be removed
504
if hasattr(driver.vendor, 'driver_vendor_passthru'):
505
LOG.warning(_LW("Drivers implementing their own version "
506
"of driver_vendor_passthru() has been "
507
"deprecated. Please update the code to use "
508
"the @driver_passthru decorator."))
510
driver.vendor.driver_validate(method=driver_method, **info)
511
ret = driver.vendor.driver_vendor_passthru(
512
context, method=driver_method, **info)
513
# DriverVendorPassthru was always sync
517
vendor_opts = driver.vendor.driver_routes[driver_method]
518
vendor_func = vendor_opts['func']
520
raise exception.InvalidParameterValue(
521
_('No handler for method %s') % driver_method)
523
http_method = http_method.upper()
524
if http_method not in vendor_opts['http_methods']:
525
raise exception.InvalidParameterValue(
526
_('The method %(method)s does not support HTTP %(http)s') %
527
{'method': driver_method, 'http': http_method})
529
# Inform the vendor method which HTTP method it was invoked with
530
info['http_method'] = http_method
532
# Invoke the vendor method accordingly with the mode
533
is_async = vendor_opts['async']
535
driver.vendor.driver_validate(method=driver_method, **info)
538
self._spawn_worker(vendor_func, context, **info)
540
ret = vendor_func(context, **info)
542
return (ret, is_async)
544
def _get_vendor_passthru_metadata(self, route_dict):
546
for method, metadata in route_dict.iteritems():
547
# 'func' is the vendor method reference, ignore it
548
d[method] = {k: metadata[k] for k in metadata if k != 'func'}
551
@messaging.expected_exceptions(exception.UnsupportedDriverExtension)
552
def get_node_vendor_passthru_methods(self, context, node_id):
553
"""Retrieve information about vendor methods of the given node.
555
:param context: an admin context.
556
:param node_id: the id or uuid of a node.
557
:returns: dictionary of <method name>:<method metadata> entries.
560
LOG.debug("RPC get_node_vendor_passthru_methods called for node %s"
562
with task_manager.acquire(context, node_id, shared=True) as task:
563
if not getattr(task.driver, 'vendor', None):
564
raise exception.UnsupportedDriverExtension(
565
driver=task.node.driver,
566
extension='vendor interface')
568
return self._get_vendor_passthru_metadata(
569
task.driver.vendor.vendor_routes)
571
@messaging.expected_exceptions(exception.UnsupportedDriverExtension,
572
exception.DriverNotFound)
573
def get_driver_vendor_passthru_methods(self, context, driver_name):
574
"""Retrieve information about vendor methods of the given driver.
576
:param context: an admin context.
577
:param driver_name: name of the driver.
578
:returns: dictionary of <method name>:<method metadata> entries.
581
# Any locking in a top-level vendor action will need to be done by the
582
# implementation, as there is little we could reasonably lock on here.
583
LOG.debug("RPC get_driver_vendor_passthru_methods for driver %s"
585
driver = self._get_driver(driver_name)
586
if not getattr(driver, 'vendor', None):
587
raise exception.UnsupportedDriverExtension(
589
extension='vendor interface')
591
return self._get_vendor_passthru_metadata(driver.vendor.driver_routes)
432
593
def _provisioning_error_handler(self, e, node, provision_state,
433
594
target_provision_state):
491
652
# want to add retries or extra synchronization here.
492
653
with task_manager.acquire(context, node_id, shared=False) as task:
494
# Only rebuild a node in ACTIVE, ERROR, or DEPLOYFAIL state
495
rebuild_states = [states.ACTIVE,
498
if rebuild and (node.provision_state not in rebuild_states):
499
valid_states_string = ', '.join(rebuild_states)
500
raise exception.InstanceDeployFailure(_(
501
"RPC do_node_deploy called to rebuild %(node)s, but "
502
"provision state is %(curstate)s. State must be one "
503
"of : %(states)s.") % {'node': node.uuid,
504
'curstate': node.provision_state,
505
'states': valid_states_string})
506
elif node.provision_state != states.NOSTATE and not rebuild:
507
raise exception.InstanceDeployFailure(_(
508
"RPC do_node_deploy called for %(node)s, but provision "
509
"state is already %(state)s.") %
510
{'node': node.uuid, 'state': node.provision_state})
512
655
if node.maintenance:
513
656
raise exception.NodeInMaintenance(op=_('provisioning'),
659
task.driver.power.validate(task)
517
660
task.driver.deploy.validate(task)
518
661
except (exception.InvalidParameterValue,
519
662
exception.MissingParameterValue) as e:
520
663
raise exception.InstanceDeployFailure(_(
521
"RPC do_node_deploy failed to validate deploy info. "
522
"Error: %(msg)s") % {'msg': e})
664
"RPC do_node_deploy failed to validate deploy or "
665
"power info. Error: %(msg)s") % {'msg': e})
524
672
# Save the previous states so we can rollback the node to a
525
673
# consistent state in case there's no free workers to do the
527
675
previous_prov_state = node.provision_state
528
676
previous_tgt_provision_state = node.target_provision_state
530
# Set target state to expose that work is in progress
531
node.provision_state = states.DEPLOYING
532
node.target_provision_state = states.DEPLOYDONE
533
node.last_error = None
536
task.set_spawn_error_hook(self._provisioning_error_handler,
537
node, previous_prov_state,
538
previous_tgt_provision_state)
539
task.spawn_after(self._spawn_worker, self._do_node_deploy, task)
679
task.process_event(event)
680
node.last_error = None
682
except exception.InvalidState:
683
raise exception.InstanceDeployFailure(_(
684
"Request received to %(what)s %(node)s, but "
685
"this is not possible in the current state of "
686
"'%(state)s'. ") % {'what': event,
688
'state': node.provision_state})
690
task.set_spawn_error_hook(self._provisioning_error_handler,
691
node, previous_prov_state,
692
previous_tgt_provision_state)
693
task.spawn_after(self._spawn_worker,
694
self._do_node_deploy, task)
541
696
def _do_node_deploy(self, task):
542
697
"""Prepare the environment and deploy a node."""
549
704
# since there may be local persistent state
550
705
node.conductor_affinity = self.conductor.id
551
706
except Exception as e:
707
# NOTE(deva): there is no need to clear conductor_affinity
552
708
with excutils.save_and_reraise_exception():
709
task.process_event('fail')
553
710
LOG.warning(_LW('Error in deploy of node %(node)s: %(err)s'),
554
711
{'node': task.node.uuid, 'err': e})
555
712
node.last_error = _("Failed to deploy. Error: %s") % e
556
node.provision_state = states.DEPLOYFAIL
557
node.target_provision_state = states.NOSTATE
558
# NOTE(deva): there is no need to clear conductor_affinity
560
714
# NOTE(deva): Some drivers may return states.DEPLOYWAIT
561
715
# eg. if they are waiting for a callback
562
716
if new_state == states.DEPLOYDONE:
563
node.target_provision_state = states.NOSTATE
564
node.provision_state = states.ACTIVE
717
task.process_event('done')
565
718
LOG.info(_LI('Successfully deployed node %(node)s with '
566
719
'instance %(instance)s.'),
567
720
{'node': node.uuid, 'instance': node.instance_uuid})
721
elif new_state == states.DEPLOYWAIT:
722
task.process_event('wait')
569
node.provision_state = new_state
724
LOG.error(_LE('Unexpected state %(state)s returned while '
725
'deploying node %(node)s.'),
726
{'state': new_state, 'node': node.uuid})
593
750
with task_manager.acquire(context, node_id, shared=False) as task:
595
if node.provision_state not in [states.ACTIVE,
599
raise exception.InstanceDeployFailure(_(
600
"RPC do_node_tear_down "
601
"not allowed for node %(node)s in state %(state)s")
602
% {'node': node_id, 'state': node.provision_state})
605
753
# NOTE(ghe): Valid power driver values are needed to perform
606
754
# a tear-down. Deploy info is useful to purge the cache but not
607
755
# required for this method.
609
756
task.driver.power.validate(task)
610
757
except (exception.InvalidParameterValue,
611
758
exception.MissingParameterValue) as e:
619
766
previous_prov_state = node.provision_state
620
767
previous_tgt_provision_state = node.target_provision_state
622
# set target state to expose that work is in progress
623
node.provision_state = states.DELETING
624
node.target_provision_state = states.DELETED
625
node.last_error = None
628
task.set_spawn_error_hook(self._provisioning_error_handler,
629
node, previous_prov_state,
630
previous_tgt_provision_state)
631
task.spawn_after(self._spawn_worker, self._do_node_tear_down, task)
770
task.process_event('delete')
771
node.last_error = None
773
except exception.InvalidState:
774
raise exception.InstanceDeployFailure(_(
775
"RPC do_node_tear_down "
776
"not allowed for node %(node)s in state %(state)s")
777
% {'node': node_id, 'state': node.provision_state})
779
task.set_spawn_error_hook(self._provisioning_error_handler,
780
node, previous_prov_state,
781
previous_tgt_provision_state)
782
task.spawn_after(self._spawn_worker,
783
self._do_node_tear_down, task)
633
785
def _do_node_tear_down(self, task):
634
786
"""Internal RPC method to tear down an existing node deployment."""
637
789
task.driver.deploy.clean_up(task)
638
new_state = task.driver.deploy.tear_down(task)
790
task.driver.deploy.tear_down(task)
639
791
except Exception as e:
640
792
with excutils.save_and_reraise_exception():
641
793
LOG.warning(_LW('Error in tear_down of node %(node)s: '
643
795
{'node': task.node.uuid, 'err': e})
644
796
node.last_error = _("Failed to tear down. Error: %s") % e
645
node.provision_state = states.ERROR
646
node.target_provision_state = states.NOSTATE
797
task.process_event('error')
648
# NOTE(deva): Some drivers may return states.DELETING
649
# eg. if they are waiting for a callback
650
if new_state == states.DELETED:
651
node.target_provision_state = states.NOSTATE
652
node.provision_state = states.NOSTATE
653
LOG.info(_LI('Successfully unprovisioned node %(node)s with '
654
'instance %(instance)s.'),
655
{'node': node.uuid, 'instance': node.instance_uuid})
657
node.provision_state = new_state
799
# NOTE(deva): When tear_down finishes, the deletion is done
800
task.process_event('done')
801
LOG.info(_LI('Successfully unprovisioned node %(node)s with '
802
'instance %(instance)s.'),
803
{'node': node.uuid, 'instance': node.instance_uuid})
804
# NOTE(deva): Currently, NOSTATE is represented as None
805
# However, FSM class treats a target_state of None as
806
# the lack of a target state -- not a target of NOSTATE
807
# Thus, until we migrate to an explicit AVAILABLE state
808
# we need to clear the target_state here manually.
809
node.target_provision_state = None
659
811
# NOTE(deva): there is no need to unset conductor_affinity
660
812
# because it is a reference to the most recent conductor which
991
1144
ret_dict[iface_name]['reason'] = reason
994
@messaging.expected_exceptions(exception.NodeLocked,
995
exception.NodeMaintenanceFailure)
996
def change_node_maintenance_mode(self, context, node_id, mode):
997
"""Set node maintenance mode on or off.
999
:param context: request context.
1000
:param node_id: node id or uuid.
1001
:param mode: True or False.
1002
:raises: NodeMaintenanceFailure
1005
LOG.debug("RPC change_node_maintenance_mode called for node %(node)s"
1006
" with maintenance mode: %(mode)s" % {'node': node_id,
1009
with task_manager.acquire(context, node_id, shared=True) as task:
1011
if mode is not node.maintenance:
1012
node.maintenance = mode
1015
msg = _("The node is already in maintenance mode") if mode \
1016
else _("The node is not in maintenance mode")
1017
raise exception.NodeMaintenanceFailure(node=node_id,
1022
1147
@lockutils.synchronized(WORKER_SPAWN_lOCK, 'ironic-')
1023
1148
def _spawn_worker(self, func, *args, **kwargs):