113
114
return pxe_utils.get_deploy_kr_info(node.uuid, node.driver_info)
116
def _set_failed_state(task, msg):
117
"""Set a node's error state and provision state to signal Nova.
119
When deploy steps aren't called by explicitly the conductor, but are
120
the result of callbacks, we need to set the node's state explicitly.
121
This tells Nova to change the instance's status so the user can see
122
their deploy/tear down had an issue and makes debugging/deleting Nova
126
node.provision_state = states.DEPLOYFAIL
127
node.target_provision_state = states.NOSTATE
130
manager_utils.node_power_action(task, states.POWER_OFF)
132
msg = (_('Node %s failed to power off while handling deploy '
133
'failure. This may be a serious condition. Node '
134
'should be removed from Ironic or put in maintenance '
135
'mode until the problem is resolved.') % node.uuid)
138
# NOTE(deva): node_power_action() erases node.last_error
139
# so we need to set it again here.
140
node.last_error = msg
144
117
@image_cache.cleanup(priority=25)
145
118
class AgentTFTPImageCache(image_cache.ImageCache):
146
119
def __init__(self, image_service=None):
153
126
image_service=image_service)
156
# copied from pxe driver - should be refactored per LP1350594
157
def _fetch_images(ctx, cache, images_info):
158
"""Check for available disk space and fetch images using ImageCache.
161
:param cache: ImageCache instance to use for fetching
162
:param images_info: list of tuples (image uuid, destination path)
163
:raises: InstanceDeployFailure if unable to find enough disk space
167
image_cache.clean_up_caches(ctx, cache.master_dir, images_info)
168
except exception.InsufficientDiskSpace as e:
169
raise exception.InstanceDeployFailure(reason=e)
171
# NOTE(dtantsur): This code can suffer from race condition,
172
# if disk space is used between the check and actual download.
173
# This is probably unavoidable, as we can't control other
174
# (probably unrelated) processes
175
for uuid, path in images_info:
176
cache.fetch_image(uuid, path, ctx=ctx)
179
# copied from pxe driver - should be refactored per LP1350594
180
129
def _cache_tftp_images(ctx, node, pxe_info):
181
130
"""Fetch the necessary kernels and ramdisks for the instance."""
182
131
fileutils.ensure_tree(
183
132
os.path.join(CONF.pxe.tftp_root, node.uuid))
184
133
LOG.debug("Fetching kernel and ramdisk for node %s",
186
_fetch_images(ctx, AgentTFTPImageCache(), pxe_info.values())
135
deploy_utils.fetch_images(ctx, AgentTFTPImageCache(), pxe_info.values())
189
138
def build_instance_info_for_deploy(task):
220
171
def validate(self, task):
221
172
"""Validate the driver-specific Node deployment info.
223
This method validates whether the 'instance_info' property of the
224
supplied node contains the required information for this driver to
225
deploy images to the node.
174
This method validates whether the properties of the supplied node
175
contain the required information for this driver to deploy images to
227
178
:param task: a TaskManager instance
228
:raises: InvalidParameterValue
179
:raises: MissingParameterValue
231
_get_tftp_image_info(task.node)
233
raise exception.InvalidParameterValue(_(
234
'Node %s failed to validate deploy image info') %
183
params['driver_info.deploy_kernel'] = node.driver_info.get(
185
params['driver_info.deploy_ramdisk'] = node.driver_info.get(
187
params['instance_info.image_source'] = node.instance_info.get(
189
error_msg = _('Node %s failed to validate deploy image info. Some '
190
'parameters were missing') % node.uuid
191
deploy_utils.check_for_missing_params(params, error_msg)
237
193
@task_manager.require_exclusive_lock
238
194
def deploy(self, task):
357
def driver_vendor_passthru(self, task, method, **kwargs):
358
"""A node that does not know its UUID should POST to this method.
359
Given method, route the command to the appropriate private function.
361
if method not in self.driver_routes:
362
raise exception.InvalidParameterValue(_('No handler for method %s')
364
func = self.driver_routes[method]
365
return func(task, **kwargs)
367
def vendor_passthru(self, task, **kwargs):
368
"""A node that knows its UUID should heartbeat to this passthru.
370
It will get its node object back, with what Ironic thinks its provision
371
state is and the target provision state is.
373
method = kwargs['method'] # Existence checked in mixin
374
if method not in self.vendor_routes:
375
raise exception.InvalidParameterValue(_('No handler for method '
377
func = self.vendor_routes[method]
379
return func(task, **kwargs)
380
except exception.IronicException as e:
381
with excutils.save_and_reraise_exception():
382
# log this because even though the exception is being
383
# reraised, it won't be handled if it is an async. call.
384
LOG.exception(_LE('vendor_passthru failed with method %s'),
386
except Exception as e:
387
# catch-all in case something bubbles up here
388
# log this because even though the exception is being
389
# reraised, it won't be handled if it is an async. call.
390
LOG.exception(_LE('vendor_passthru failed with method %s'), method)
391
raise exception.VendorPassthruException(message=e)
393
def _heartbeat(self, task, **kwargs):
308
def driver_validate(self, method, **kwargs):
309
"""Validate the driver deployment info.
311
:param method: method to be validated.
313
version = kwargs.get('version')
316
raise exception.MissingParameterValue(_('Missing parameter '
318
if version not in self.supported_payload_versions:
319
raise exception.InvalidParameterValue(_('Unknown lookup '
320
'payload version: %s')
323
@base.passthru(['POST'])
324
def heartbeat(self, task, **kwargs):
394
325
"""Method for agent to periodically check in.
396
327
The agent should be sending its agent_url (so Ironic can talk back)
399
kwargs should have the following format:
401
'agent_url': 'http://AGENT_HOST:AGENT_PORT'
403
AGENT_PORT defaults to 9999.
328
as a kwarg. kwargs should have the following format::
331
'agent_url': 'http://AGENT_HOST:AGENT_PORT'
334
AGENT_PORT defaults to 9999.
406
337
driver_info = node.driver_info
409
340
{'node': node.uuid,
410
341
'heartbeat': driver_info.get('agent_last_heartbeat')})
411
342
driver_info['agent_last_heartbeat'] = int(_time())
412
# FIXME(rloo): This could raise KeyError exception if 'agent_url'
413
# wasn't specified. Instead, raise MissingParameterValue.
414
driver_info['agent_url'] = kwargs['agent_url']
344
driver_info['agent_url'] = kwargs['agent_url']
346
raise exception.MissingParameterValue(_('For heartbeat operation, '
347
'"agent_url" must be '
416
350
node.driver_info = driver_info
419
353
# Async call backs don't set error state on their own
420
354
# TODO(jimrollenhagen) improve error messages here
355
msg = _('Failed checking if deploy is done.')
422
357
if node.provision_state == states.DEPLOYWAIT:
423
358
msg = _('Node failed to get image for deploy.')
442
377
LOG.debug('Continuing deploy for %s', node.uuid)
380
'id': image_source.split('/')[-1],
446
381
'urls': [node.instance_info['image_url']],
447
382
'checksum': node.instance_info['image_checksum'],
383
# NOTE(comstud): Older versions of ironic do not set
384
# 'disk_format' nor 'container_format', so we use .get()
385
# to maintain backwards compatibility in case code was
386
# upgraded in the middle of a build request.
387
'disk_format': node.instance_info.get('image_disk_format'),
388
'container_format': node.instance_info.get(
389
'image_container_format')
450
392
# Tell the client to download and write the image with the given args
485
427
node.target_provision_state = states.NOSTATE
488
def _lookup(self, context, **kwargs):
489
"""Method to be called the first time a ramdisk agent checks in. This
430
@base.driver_passthru(['POST'], async=False)
431
def lookup(self, context, **kwargs):
432
"""Find a matching node for the agent.
434
Method to be called the first time a ramdisk agent checks in. This
490
435
can be because this is a node just entering decom or a node that
491
436
rebooted for some reason. We will use the mac addresses listed in the
492
437
kwargs to find the matching node, then return the node object to the
493
agent. The agent can that use that UUID to use the normal vendor
438
agent. The agent can that use that UUID to use the node vendor
496
441
Currently, we don't handle the instance where the agent doesn't have
497
442
a matching node (i.e. a brand new, never been in Ironic node).
499
kwargs should have the following format:
506
"mac_address": "00:11:22:33:44:55",
507
"switch_port_descr": "port24"
508
"switch_chassis_descr": "tor1"
444
kwargs should have the following format::
452
"mac_address": "00:11:22:33:44:55",
453
"switch_port_descr": "port24"
454
"switch_chassis_descr": "tor1"
515
460
The interfaces list should include a list of the non-IPMI MAC addresses
516
461
in the form aa:bb:cc:dd:ee:ff.
523
468
:raises: NotFound if no matching node is found.
524
469
:raises: InvalidParameterValue with unknown payload version
526
version = kwargs.get('version')
528
if version not in self.supported_payload_versions:
529
raise exception.InvalidParameterValue(_('Unknown lookup payload'
530
'version: %s') % version)
531
interfaces = self._get_interfaces(version, kwargs)
471
inventory = kwargs.get('inventory')
472
interfaces = self._get_interfaces(inventory)
532
473
mac_addresses = self._get_mac_addresses(interfaces)
534
475
node = self._find_node_by_macs(context, mac_addresses)
556
def _get_interfaces(self, version, inventory):
497
def _get_interfaces(self, inventory):
559
interfaces = inventory['inventory']['interfaces']
500
interfaces = inventory['interfaces']
560
501
except (KeyError, TypeError):
561
502
raise exception.InvalidParameterValue(_(
562
503
'Malformed network interfaces lookup: %s') % inventory)
578
518
return mac_addresses
580
520
def _find_node_by_macs(self, context, mac_addresses):
581
"""Given a list of MAC addresses, find the ports that match the MACs
521
"""Get nodes for a given list of MAC addresses.
523
Given a list of MAC addresses, find the ports that match the MACs
582
524
and return the node they are all connected to.
584
526
:raises: NodeNotFound if the ports point to multiple nodes or no
602
544
def _find_ports_by_macs(self, context, mac_addresses):
603
"""Given a list of MAC addresses, find the ports that match the MACs
545
"""Get ports for a given list of MAC addresses.
547
Given a list of MAC addresses, find the ports that match the MACs
604
548
and return them as a list of Port objects, or an empty list if there
619
563
def _get_node_id(self, ports):
620
"""Given a list of ports, either return the node_id they all share or
564
"""Get a node ID for a list of ports.
566
Given a list of ports, either return the node_id they all share or
621
567
raise a NotFound if there are multiple node_ids, which indicates some
622
568
ports are connected to one node and the remaining port(s) are connected
623
569
to one or more other nodes.