23
23
from oslo_config import cfg
25
from ironic.common import boot_devices
25
26
from ironic.common import dhcp_factory
26
27
from ironic.common import exception
28
from ironic.common.glance_service import service_utils
27
29
from ironic.common.i18n import _
28
30
from ironic.common.i18n import _LE
29
31
from ironic.common.i18n import _LI
37
39
from ironic.conductor import task_manager
38
40
from ironic.conductor import utils as manager_utils
39
41
from ironic.drivers import base
42
from ironic.drivers.modules import agent
43
from ironic.drivers.modules import agent_base_vendor
40
44
from ironic.drivers.modules import deploy_utils
41
45
from ironic.drivers.modules import image_cache
42
46
from ironic.drivers.modules import iscsi_deploy
98
102
REQUIRED_PROPERTIES = {
99
'pxe_deploy_kernel': _("UUID (from Glance) of the deployment kernel. "
103
'deploy_kernel': _("UUID (from Glance) of the deployment kernel. "
105
'deploy_ramdisk': _("UUID (from Glance) of the ramdisk that is "
106
"mounted at boot time. Required."),
107
'pxe_deploy_kernel': _("DEPRECATED: Use deploy_kernel instead. UUID "
108
"(from Glance) of the deployment kernel. "
101
'pxe_deploy_ramdisk': _("UUID (from Glance) of the ramdisk that is "
110
'pxe_deploy_ramdisk': _("DEPRECATED: Use deploy_ramdisk instead. UUID "
111
"(from Glance) of the ramdisk that is "
102
112
"mounted at boot time. Required."),
104
114
COMMON_PROPERTIES = REQUIRED_PROPERTIES
118
128
info = node.driver_info
120
d_info['deploy_kernel'] = info.get('pxe_deploy_kernel')
121
d_info['deploy_ramdisk'] = info.get('pxe_deploy_ramdisk')
131
# NOTE(lucasagomes): For backwards compatibility let's keep accepting
132
# pxe_deploy_{kernel, ramdisk}, should be removed in Liberty.
133
deprecated_msg = _LW('The "%(old_param)s" parameter is deprecated. '
134
'Please update the node %(node)s to use '
135
'"%(new_param)s" instead.')
137
for parameter in ('deploy_kernel', 'deploy_ramdisk'):
138
value = info.get(parameter)
140
old_parameter = 'pxe_' + parameter
141
value = info.get(old_parameter)
143
LOG.warning(deprecated_msg, {'old_param': old_parameter,
144
'new_param': parameter,
146
d_info[parameter] = value
123
148
error_msg = _("Cannot validate PXE bootloader. Some parameters were"
124
149
" missing in node's driver_info")
125
deploy_utils.check_for_missing_params(d_info, error_msg, 'pxe_')
150
deploy_utils.check_for_missing_params(d_info, error_msg)
160
185
:returns: A dictionary of pxe options to be used in the pxe bootfile
188
is_whole_disk_image = node.driver_internal_info.get('is_whole_disk_image')
163
190
if CONF.pxe.ipxe_enabled:
164
191
deploy_kernel = '/'.join([CONF.pxe.http_url, node.uuid,
165
192
'deploy_kernel'])
166
193
deploy_ramdisk = '/'.join([CONF.pxe.http_url, node.uuid,
167
194
'deploy_ramdisk'])
168
kernel = '/'.join([CONF.pxe.http_url, node.uuid, 'kernel'])
169
ramdisk = '/'.join([CONF.pxe.http_url, node.uuid, 'ramdisk'])
195
if not is_whole_disk_image:
196
kernel = '/'.join([CONF.pxe.http_url, node.uuid, 'kernel'])
197
ramdisk = '/'.join([CONF.pxe.http_url, node.uuid, 'ramdisk'])
171
199
deploy_kernel = pxe_info['deploy_kernel'][1]
172
200
deploy_ramdisk = pxe_info['deploy_ramdisk'][1]
173
kernel = pxe_info['kernel'][1]
174
ramdisk = pxe_info['ramdisk'][1]
201
if not is_whole_disk_image:
202
kernel = pxe_info['kernel'][1]
203
ramdisk = pxe_info['ramdisk'][1]
177
206
'deployment_aki_path': deploy_kernel,
178
207
'deployment_ari_path': deploy_ramdisk,
181
208
'pxe_append_params': CONF.pxe.pxe_append_params,
182
209
'tftp_server': CONF.pxe.tftp_server
212
if not is_whole_disk_image:
213
pxe_options.update({'aki_path': kernel,
214
'ari_path': ramdisk})
185
216
deploy_ramdisk_options = iscsi_deploy.build_deploy_ramdisk_options(node)
186
217
pxe_options.update(deploy_ramdisk_options)
219
# NOTE(lucasagomes): We are going to extend the normal PXE config
220
# to also contain the agent options so it could be used for both the
221
# DIB ramdisk and the IPA ramdisk
222
agent_opts = agent.build_agent_options(node)
223
pxe_options.update(agent_opts)
187
225
return pxe_options
208
246
"""Fetch the necessary kernels and ramdisks for the instance."""
209
247
fileutils.ensure_tree(
210
248
os.path.join(pxe_utils.get_root_dir(), node.uuid))
211
LOG.debug("Fetching kernel and ramdisk for node %s",
249
LOG.debug("Fetching necessary kernel and ramdisk for node %s",
213
251
deploy_utils.fetch_images(ctx, TFTPImageCache(), pxe_info.values(),
214
252
CONF.force_raw_images)
218
256
"""Generate the paths for tftp files for this instance
220
258
Raises IronicException if
221
- instance does not contain kernel_id or ramdisk_id
222
- deploy_kernel_id or deploy_ramdisk_id can not be read from
259
- instance does not contain kernel or ramdisk
260
- deploy_kernel or deploy_ramdisk can not be read from
223
261
driver_info and defaults are not set
230
268
image_info.update(pxe_utils.get_deploy_kr_info(node.uuid, d_info))
270
if node.driver_internal_info.get('is_whole_disk_image'):
232
273
i_info = node.instance_info
233
274
labels = ('kernel', 'ramdisk')
234
275
if not (i_info.get('kernel') and i_info.get('ramdisk')):
235
glance_service = service.Service(version=1, context=ctx)
276
glance_service = service.GlanceImageService(version=1, context=ctx)
236
277
iproperties = glance_service.show(d_info['image_source'])['properties']
237
278
for label in labels:
238
279
i_info[label] = str(iproperties[label + '_id'])
280
321
:raises: InvalidParameterValue.
281
322
:raises: MissingParameterValue
284
# Check the boot_mode capability parameter value.
285
driver_utils.validate_boot_mode_capability(task.node)
326
# Check the boot_mode and boot_option capabilities values.
327
driver_utils.validate_boot_mode_capability(node)
328
driver_utils.validate_boot_option_capability(node)
330
boot_mode = driver_utils.get_node_capability(node, 'boot_mode')
287
332
if CONF.pxe.ipxe_enabled:
288
333
if not CONF.pxe.http_url or not CONF.pxe.http_root:
290
335
"iPXE boot is enabled but no HTTP URL or HTTP "
291
336
"root was specified."))
292
337
# iPXE and UEFI should not be configured together.
293
if driver_utils.get_node_capability(task.node,
294
'boot_mode') == 'uefi':
338
if boot_mode == 'uefi':
295
339
LOG.error(_LE("UEFI boot mode is not supported with "
296
340
"iPXE boot enabled."))
297
341
raise exception.InvalidParameterValue(_(
298
342
"Conflict: iPXE is enabled, but cannot be used with node"
299
343
"%(node_uuid)s configured to use UEFI boot") %
300
{'node_uuid': task.node.uuid})
344
{'node_uuid': node.uuid})
302
d_info = _parse_deploy_info(task.node)
346
d_info = _parse_deploy_info(node)
304
348
iscsi_deploy.validate(task)
306
props = ['kernel_id', 'ramdisk_id']
307
iscsi_deploy.validate_glance_image_properties(task.context, d_info,
350
if node.driver_internal_info.get('is_whole_disk_image'):
352
elif service_utils.is_glance_image(d_info['image_source']):
353
props = ['kernel_id', 'ramdisk_id']
355
props = ['kernel', 'ramdisk']
357
iscsi_deploy.validate_image_properties(task.context, d_info, props)
310
359
@task_manager.require_exclusive_lock
311
360
def deploy(self, task):
316
365
request to the power driver.
317
366
This causes the node to boot into the deployment ramdisk and triggers
318
367
the next phase of PXE-based deployment via
319
VendorPassthru._continue_deploy().
368
VendorPassthru.pass_deploy_info().
321
370
:param task: a TaskManager instance containing the node to act on.
322
371
:returns: deploy state DEPLOYWAIT.
331
380
provider = dhcp_factory.DHCPFactory()
332
381
provider.update_dhcp(task, dhcp_opts)
334
# NOTE(faizan): Under UEFI boot mode, setting of boot device may differ
335
# between different machines. IPMI does not work for setting boot
336
# devices in UEFI mode for certain machines.
337
# Expected IPMI failure for uefi boot mode. Logging a message to
338
# set the boot device manually and continue with deploy.
340
manager_utils.node_set_boot_device(task, 'pxe', persistent=True)
341
except exception.IPMIFailure:
342
if driver_utils.get_node_capability(task.node,
343
'boot_mode') == 'uefi':
344
LOG.warning(_LW("ipmitool is unable to set boot device while "
345
"the node is in UEFI boot mode."
346
"Please set the boot device manually."))
383
deploy_utils.try_set_boot_device(task, boot_devices.PXE)
350
384
manager_utils.node_power_action(task, states.REBOOT)
352
386
return states.DEPLOYWAIT
391
425
pxe_utils.create_pxe_config(task, pxe_options,
392
426
pxe_config_template)
428
# FIXME(lucasagomes): If it's local boot we should not cache
429
# the image kernel and ramdisk (Or even require it).
393
430
_cache_ramdisk_kernel(task.context, task.node, pxe_info)
432
iwdi = task.node.driver_internal_info.get('is_whole_disk_image')
433
# NOTE(deva): prepare may be called from conductor._do_takeover
434
# in which case, it may need to regenerate the PXE config file for an
435
# already-active deployment.
436
if task.node.provision_state == states.ACTIVE:
437
# this should have been stashed when the deploy was done
438
# but let's guard, just in case it's missing
440
root_uuid_or_disk_id = task.node.driver_internal_info[
441
'root_uuid_or_disk_id']
444
LOG.warn(_LW("The UUID for the root partition can't be "
445
"found, unable to switch the pxe config from "
446
"deployment mode to service (boot) mode for node "
447
"%(node)s"), {"node": task.node.uuid})
449
LOG.warn(_LW("The disk id for the whole disk image can't "
450
"be found, unable to switch the pxe config from "
451
"deployment mode to service (boot) mode for "
452
"node %(node)s"), {"node": task.node.uuid})
454
pxe_config_path = pxe_utils.get_pxe_config_file_path(
456
deploy_utils.switch_pxe_config(
457
pxe_config_path, root_uuid_or_disk_id,
458
driver_utils.get_node_capability(task.node, 'boot_mode'),
395
461
def clean_up(self, task):
396
462
"""Clean up the deployment environment for the task's node.
421
487
_destroy_token_file(node)
423
489
def take_over(self, task):
424
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
425
provider = dhcp_factory.DHCPFactory()
426
provider.update_dhcp(task, dhcp_opts)
429
class VendorPassthru(base.VendorInterface):
490
if not iscsi_deploy.get_boot_option(task.node) == "local":
491
# If it's going to PXE boot we need to update the DHCP server
492
dhcp_opts = pxe_utils.dhcp_options_for_instance(task)
493
provider = dhcp_factory.DHCPFactory()
494
provider.update_dhcp(task, dhcp_opts)
496
# If it's going to boot from the local disk, we don't need
497
# PXE config files. They still need to be generated as part
498
# of the prepare() because the deployment does PXE boot the
500
pxe_utils.clean_up_pxe_config(task)
503
class VendorPassthru(agent_base_vendor.BaseAgentVendor):
430
504
"""Interface to mix IPMI and PXE vendor-specific interfaces."""
432
506
def get_properties(self):
435
509
def validate(self, task, method, **kwargs):
436
510
"""Validates the inputs for a vendor passthru.
438
This method checks whether the vendor passthru method is a valid one,
439
and then validates whether the required information for executing the
440
vendor passthru has been provided or not.
512
If invalid, raises an exception; otherwise returns None.
442
517
:param task: a TaskManager instance containing the node to act on.
443
518
:param method: method to be validated.
444
519
:param kwargs: kwargs containins the method's parameters.
445
520
:raises: InvalidParameterValue if any parameters is invalid.
447
iscsi_deploy.get_deploy_info(task.node, **kwargs)
522
if method == 'pass_deploy_info':
523
driver_utils.validate_boot_option_capability(task.node)
524
iscsi_deploy.get_deploy_info(task.node, **kwargs)
449
@base.passthru(['POST'], method='pass_deploy_info')
526
@base.passthru(['POST'])
450
527
@task_manager.require_exclusive_lock
451
def _continue_deploy(self, task, **kwargs):
528
def pass_deploy_info(self, task, **kwargs):
452
529
"""Continues the deployment of baremetal node over iSCSI.
454
531
This method continues the deployment of the baremetal node over iSCSI
462
539
task.process_event('resume')
464
541
_destroy_token_file(node)
466
root_uuid = iscsi_deploy.continue_deploy(task, **kwargs)
542
is_whole_disk_image = node.driver_internal_info['is_whole_disk_image']
543
uuid_dict_returned = iscsi_deploy.continue_deploy(task, **kwargs)
544
root_uuid_or_disk_id = uuid_dict_returned.get(
545
'root uuid', uuid_dict_returned.get('disk identifier'))
547
# TODO(rameshg87): It's not correct to return here as it will leave
548
# the node in DEPLOYING state. This will be fixed in bug 1405519.
549
if not root_uuid_or_disk_id:
552
# save the node's root disk UUID so that another conductor could
553
# rebuild the PXE config file. Due to a shortcoming in Nova objects,
554
# we have to assign to node.driver_internal_info so the node knows it
556
driver_internal_info = node.driver_internal_info
557
driver_internal_info['root_uuid_or_disk_id'] = root_uuid_or_disk_id
558
node.driver_internal_info = driver_internal_info
472
pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid)
473
deploy_utils.switch_pxe_config(pxe_config_path, root_uuid,
474
driver_utils.get_node_capability(node, 'boot_mode'))
562
if iscsi_deploy.get_boot_option(node) == "local":
563
deploy_utils.try_set_boot_device(task, boot_devices.DISK)
564
# If it's going to boot from the local disk, get rid of
565
# the PXE configuration files used for the deployment
566
pxe_utils.clean_up_pxe_config(task)
568
pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid)
569
node_cap = driver_utils.get_node_capability(node, 'boot_mode')
570
deploy_utils.switch_pxe_config(pxe_config_path,
571
root_uuid_or_disk_id,
572
node_cap, is_whole_disk_image)
476
574
deploy_utils.notify_deploy_complete(kwargs['address'])
478
575
LOG.info(_LI('Deployment to node %s done'), node.uuid)
479
576
task.process_event('done')
480
577
except Exception as e:
483
580
{'instance': node.instance_uuid, 'error': e})
484
581
msg = _('Failed to continue iSCSI deployment.')
485
582
deploy_utils.set_failed_state(task, msg)
584
@task_manager.require_exclusive_lock
585
def continue_deploy(self, task, **kwargs):
586
"""Method invoked when deployed with the IPA ramdisk.
588
This method is invoked during a heartbeat from an agent when
589
the node is in wait-call-back state. This deploys the image on
590
the node and then configures the node to boot according to the
591
desired boot option (netboot or localboot).
593
:param task: a TaskManager object containing the node.
594
:param kwargs: the kwargs passed from the heartbeat method.
595
:raises: InstanceDeployFailure, if it encounters some error during
598
task.process_event('resume')
600
LOG.debug('Continuing the deployment on node %s', node.uuid)
602
# NOTE(lucasagomes): We don't use the token file with the agent,
603
# but as it's created as part of deploy() we are going to remove
605
_destroy_token_file(node)
607
uuid_dict_returned = iscsi_deploy.do_agent_iscsi_deploy(task,
610
is_whole_disk_image = node.driver_internal_info['is_whole_disk_image']
611
if iscsi_deploy.get_boot_option(node) == "local":
612
# Install the boot loader
613
root_uuid = uuid_dict_returned.get('root uuid')
614
efi_sys_uuid = uuid_dict_returned.get('efi system partition uuid')
615
self.configure_local_boot(
616
task, root_uuid=root_uuid,
617
efi_system_part_uuid=efi_sys_uuid)
619
# If it's going to boot from the local disk, get rid of
620
# the PXE configuration files used for the deployment
621
pxe_utils.clean_up_pxe_config(task)
623
root_uuid_or_disk_id = uuid_dict_returned.get(
624
'root uuid', uuid_dict_returned.get('disk identifier'))
625
pxe_config_path = pxe_utils.get_pxe_config_file_path(node.uuid)
626
boot_mode = driver_utils.get_node_capability(node, 'boot_mode')
627
deploy_utils.switch_pxe_config(pxe_config_path,
628
root_uuid_or_disk_id,
629
boot_mode, is_whole_disk_image)
631
self.reboot_and_finish_deploy(task)