20
20
from oslo_config import cfg
21
from oslo_utils import excutils
22
23
from ironic.common import boot_devices
24
from ironic.common import dhcp_factory
23
25
from ironic.common import exception
26
from ironic.common.glance_service import service_utils
24
27
from ironic.common.i18n import _
25
28
from ironic.common.i18n import _LE
26
29
from ironic.common.i18n import _LI
30
from ironic.common import image_service
27
31
from ironic.common import images
32
from ironic.common import keystone
28
33
from ironic.common import states
29
34
from ironic.common import swift
30
35
from ironic.conductor import task_manager
31
36
from ironic.conductor import utils as manager_utils
32
37
from ironic.drivers import base
33
38
from ironic.drivers.modules import agent
39
from ironic.drivers.modules import agent_base_vendor
34
40
from ironic.drivers.modules import deploy_utils
35
41
from ironic.drivers.modules.ilo import common as ilo_common
36
42
from ironic.drivers.modules import ipmitool
66
80
def _get_boot_iso(task, root_uuid):
67
81
"""This method returns a boot ISO to boot the node.
69
It chooses one of the two options in the order as below:
70
1. Image deployed has a meta-property 'boot_iso' in Glance. This should
83
It chooses one of the three options in the order as below:
84
1. Does nothing if 'ilo_boot_iso' is present in node's instance_info.
85
2. Image deployed has a meta-property 'boot_iso' in Glance. This should
71
86
refer to the UUID of the boot_iso which exists in Glance.
72
2. Generates a boot ISO on the fly using kernel and ramdisk mentioned in
87
3. Generates a boot ISO on the fly using kernel and ramdisk mentioned in
73
88
the image deployed. It uploads the generated boot ISO to Swift.
75
90
:param task: a TaskManager instance containing the node to act on.
76
91
:param root_uuid: the uuid of the root partition.
77
:returns: the information about the boot ISO. Returns the information in
78
the format 'glance:<glance-boot-iso-uuid>' or
79
'swift:<swift-boot_iso-object-name>'. In case of Swift, it is assumed
80
that the object exists in CONF.ilo.swift_ilo_container.
92
:returns: boot ISO URL. Should be either of below:
93
* A Swift object - It should be of format 'swift:<object-name>'. It is
94
assumed that the image object is present in
95
CONF.ilo.swift_ilo_container;
96
* A Glance image - It should be format 'glance://<glance-image-uuid>'
97
or just <glance-image-uuid>;
81
99
On error finding the boot iso, it returns None.
82
100
:raises: MissingParameterValue, if any of the required parameters are
83
101
missing in the node's driver_info or instance_info.
85
103
value in the node's driver_info or instance_info.
86
104
:raises: SwiftOperationError, if operation with Swift fails.
87
105
:raises: ImageCreationFailed, if creation of boot ISO failed.
106
:raises: exception.ImageRefValidationFailed if ilo_boot_iso is not
89
# Option 1 - Check if user has provided a boot_iso in Glance.
90
109
LOG.debug("Trying to get a boot ISO to boot the baremetal node")
111
# Option 1 - Check if user has provided ilo_boot_iso in node's
113
if task.node.instance_info.get('ilo_boot_iso'):
114
LOG.debug("Using ilo_boot_iso provided in node's instance_info")
115
boot_iso = task.node.instance_info['ilo_boot_iso']
116
if not service_utils.is_glance_image(boot_iso):
118
image_service.HttpImageService().validate_href(boot_iso)
119
except exception.ImageRefValidationFailed:
120
with excutils.save_and_reraise_exception():
121
LOG.error(_LE("Virtual media deploy accepts only Glance "
122
"images or HTTP(S) URLs as "
123
"instance_info['ilo_boot_iso']. Either %s "
124
"is not a valid HTTP(S) URL or is "
125
"not reachable."), boot_iso)
126
return task.node.instance_info['ilo_boot_iso']
128
# Option 2 - Check if user has provided a boot_iso in Glance. If boot_iso
129
# is a supported non-glance href execution will proceed to option 3.
91
130
deploy_info = _parse_deploy_info(task.node)
93
132
image_href = deploy_info['image_source']
95
images.get_glance_image_properties(task.context,
134
images.get_image_properties(task.context,
96
135
image_href, ['boot_iso', 'kernel_id', 'ramdisk_id']))
98
boot_iso_uuid = glance_properties.get('boot_iso')
99
kernel_uuid = glance_properties.get('kernel_id')
100
ramdisk_uuid = glance_properties.get('ramdisk_id')
137
boot_iso_uuid = image_properties.get('boot_iso')
138
kernel_href = (task.node.instance_info.get('kernel') or
139
image_properties.get('kernel_id'))
140
ramdisk_href = (task.node.instance_info.get('ramdisk') or
141
image_properties.get('ramdisk_id'))
102
143
if boot_iso_uuid:
103
144
LOG.debug("Found boot_iso %s in Glance", boot_iso_uuid)
104
return 'glance:%s' % boot_iso_uuid
106
# NOTE(faizan) For uefi boot_mode, operator should provide efi capable
108
if driver_utils.get_node_capability(task.node, 'boot_mode') == 'uefi':
109
LOG.error(_LE("Unable to find boot_iso in Glance, required to deploy "
110
"node %(node)s in UEFI boot mode."),
111
{'node': task.node.uuid})
114
if not kernel_uuid or not ramdisk_uuid:
115
LOG.error(_LE("Unable to find 'kernel_id' and 'ramdisk_id' in Glance "
116
"image %(image)s for generating boot ISO for %(node)s"),
147
if not kernel_href or not ramdisk_href:
148
LOG.error(_LE("Unable to find kernel or ramdisk for "
149
"image %(image)s to generate boot ISO for %(node)s"),
117
150
{'image': image_href, 'node': task.node.uuid})
123
156
# will require synchronisation across conductor nodes for the shared boot
124
157
# ISO. Such a synchronisation mechanism doesn't exist in ironic as of now.
126
# Option 2 - Create boot_iso from kernel/ramdisk, upload to Swift
159
# Option 3 - Create boot_iso from kernel/ramdisk, upload to Swift
127
160
# and provide its name.
161
deploy_iso_uuid = deploy_info['ilo_deploy_iso']
162
boot_mode = driver_utils.get_node_capability(task.node, 'boot_mode')
128
163
boot_iso_object_name = _get_boot_iso_object_name(task.node)
129
164
kernel_params = CONF.pxe.pxe_append_params
130
165
container = CONF.ilo.swift_ilo_container
132
167
with tempfile.NamedTemporaryFile() as fileobj:
133
168
boot_iso_tmp_file = fileobj.name
134
169
images.create_boot_iso(task.context, boot_iso_tmp_file,
135
kernel_uuid, ramdisk_uuid, root_uuid, kernel_params)
170
kernel_href, ramdisk_href,
171
deploy_iso_uuid, root_uuid,
172
kernel_params, boot_mode)
136
173
swift_api = swift.SwiftAPI()
137
174
swift_api.create_object(container, boot_iso_object_name,
140
177
LOG.debug("Created boot_iso %s in Swift", boot_iso_object_name)
145
182
def _clean_up_boot_iso_for_instance(node):
146
"""Deletes the boot ISO created in Swift for the instance.
183
"""Deletes the boot ISO if it was created in Swift for the instance.
148
185
:param node: an ironic node object.
187
ilo_boot_iso = node.instance_info.get('ilo_boot_iso')
188
if not (ilo_boot_iso and ilo_boot_iso.startswith('swift')):
150
190
swift_api = swift.SwiftAPI()
151
191
container = CONF.ilo.swift_ilo_container
152
192
boot_iso_object_name = _get_boot_iso_object_name(node)
208
248
arguments for ramdisk in virtual media floppy, and then reboots the node.
210
250
:param task: a TaskManager instance containing the node to act on.
211
:param iso: a bootable ISO image to attach to. The boot iso
212
should be present in either Glance or in Swift. If present in
213
Glance, it should be of format 'glance:<glance-image-uuid>'.
214
If present in Swift, it should be of format 'swift:<object-name>'.
215
It is assumed that object is present in CONF.ilo.swift_ilo_container.
251
:param iso: a bootable ISO image href to attach to. Should be either
253
* A Swift object - It should be of format 'swift:<object-name>'.
254
It is assumed that the image object is present in
255
CONF.ilo.swift_ilo_container;
256
* A Glance image - It should be format 'glance://<glance-image-uuid>'
257
or just <glance-image-uuid>;
216
259
:param ramdisk_options: the options to be passed to the ramdisk in virtual
218
261
:raises: ImageCreationFailed, if it failed while creating the floppy image.
223
266
manager_utils.node_power_action(task, states.REBOOT)
269
def _prepare_agent_vmedia_boot(task):
270
"""prepare for vmedia boot."""
272
deploy_ramdisk_opts = agent.build_agent_options(task.node)
273
deploy_iso = task.node.driver_info['ilo_deploy_iso']
274
_reboot_into(task, deploy_iso, deploy_ramdisk_opts)
226
277
class IloVirtualMediaIscsiDeploy(base.DeployInterface):
228
279
def get_properties(self):
234
285
:param task: a TaskManager instance containing the node to act on.
235
286
:raises: InvalidParameterValue, if some information is invalid.
236
287
:raises: MissingParameterValue if 'kernel_id' and 'ramdisk_id' are
237
missing in the Glance image.
288
missing in the Glance image or 'kernel' and 'ramdisk' not provided
289
in instance_info for non-Glance image.
239
291
iscsi_deploy.validate(task)
241
props = ['kernel_id', 'ramdisk_id']
242
293
d_info = _parse_deploy_info(task.node)
243
iscsi_deploy.validate_glance_image_properties(task.context, d_info,
295
if task.node.driver_internal_info.get('is_whole_disk_image'):
297
elif service_utils.is_glance_image(d_info['image_source']):
298
props = ['kernel_id', 'ramdisk_id']
300
props = ['kernel', 'ramdisk']
301
iscsi_deploy.validate_image_properties(task.context, d_info, props)
245
302
driver_utils.validate_boot_mode_capability(task.node)
303
driver_utils.validate_boot_option_capability(task.node)
247
305
@task_manager.require_exclusive_lock
248
306
def deploy(self, task):
267
325
iscsi_deploy.check_image_size(task)
269
327
deploy_ramdisk_opts = iscsi_deploy.build_deploy_ramdisk_options(node)
328
agent_opts = agent.build_agent_options(node)
329
deploy_ramdisk_opts.update(agent_opts)
270
330
deploy_nic_mac = deploy_utils.get_single_nic_with_vif_port_id(task)
271
331
deploy_ramdisk_opts['BOOTIF'] = deploy_nic_mac
272
deploy_iso_uuid = node.driver_info['ilo_deploy_iso']
273
deploy_iso = 'glance:' + deploy_iso_uuid
332
deploy_iso = node.driver_info['ilo_deploy_iso']
275
334
_reboot_into(task, deploy_iso, deploy_ramdisk_opts)
295
354
:param task: a TaskManager instance containing the node to act on.
296
355
:raises: IloOperationError, if some operation on iLO failed.
298
boot_mode = driver_utils.get_node_capability(task.node, 'boot_mode')
299
if boot_mode is not None:
300
ilo_common.set_boot_mode(task.node, boot_mode)
302
ilo_common.update_boot_mode_capability(task)
357
ilo_common.update_boot_mode(task)
304
359
def clean_up(self, task):
305
360
"""Clean up the deployment environment for the task's node.
445
def get_clean_steps(self, task):
446
"""Get the list of clean steps from the agent.
448
:param task: a TaskManager object containing the node
449
:returns: A list of clean step dictionaries
451
steps = deploy_utils.agent_get_clean_steps(task)
452
if CONF.ilo.clean_priority_erase_devices:
454
if (step.get('step') == 'erase_devices' and
455
step.get('interface') == 'deploy'):
456
# Override with operator set priority
457
step['priority'] = CONF.ilo.clean_priority_erase_devices
461
def execute_clean_step(self, task, step):
462
"""Execute a clean step asynchronously on the agent.
464
:param task: a TaskManager object containing the node
465
:param step: a clean step dictionary to execute
466
:returns: states.CLEANING to signify the step will be completed async
468
return deploy_utils.agent_execute_clean_step(task, step)
470
def prepare_cleaning(self, task):
471
"""Boot into the agent to prepare for cleaning."""
472
# Create cleaning ports if necessary
473
provider = dhcp_factory.DHCPFactory().provider
475
# If we have left over ports from a previous cleaning, remove them
476
if getattr(provider, 'delete_cleaning_ports', None):
477
provider.delete_cleaning_ports(task)
479
if getattr(provider, 'create_cleaning_ports', None):
480
provider.create_cleaning_ports(task)
482
_prepare_agent_vmedia_boot(task)
483
# Tell the conductor we are waiting for the agent to boot.
484
return states.CLEANING
486
def tear_down_cleaning(self, task):
487
"""Clean up the PXE and DHCP files after cleaning."""
488
manager_utils.node_power_action(task, states.POWER_OFF)
489
# If we created cleaning ports, delete them
490
provider = dhcp_factory.DHCPFactory().provider
491
if getattr(provider, 'delete_cleaning_ports', None):
492
provider.delete_cleaning_ports(task)
393
495
class IloPXEDeploy(pxe.PXEDeploy):
453
551
class IloPXEVendorPassthru(pxe.VendorPassthru):
455
@base.passthru(['POST'], method='pass_deploy_info')
456
def _continue_deploy(self, task, **kwargs):
553
@base.passthru(['POST'])
554
def pass_deploy_info(self, task, **kwargs):
457
555
manager_utils.node_set_boot_device(task, boot_devices.PXE, True)
458
super(IloPXEVendorPassthru, self)._continue_deploy(task, **kwargs)
461
class VendorPassthru(base.VendorInterface):
556
super(IloPXEVendorPassthru, self).pass_deploy_info(task, **kwargs)
559
class VendorPassthru(agent_base_vendor.BaseAgentVendor):
462
560
"""Vendor-specific interfaces for iLO deploy drivers."""
464
562
def get_properties(self):
479
577
:raises: InvalidParameterValue, if any of the parameters have invalid
482
iscsi_deploy.get_deploy_info(task.node, **kwargs)
484
@base.passthru(['POST'], method='pass_deploy_info')
580
if method == 'pass_deploy_info':
581
iscsi_deploy.get_deploy_info(task.node, **kwargs)
583
def _configure_vmedia_boot(self, task, root_uuid):
584
"""Configure vmedia boot for the node."""
586
boot_iso = _get_boot_iso(task, root_uuid)
588
LOG.error(_LE("Cannot get boot ISO for node %s"), node.uuid)
591
ilo_common.setup_vmedia_for_boot(task, boot_iso)
592
manager_utils.node_set_boot_device(task, boot_devices.CDROM)
594
i_info = node.instance_info
595
if not i_info.get('ilo_boot_iso'):
596
i_info['ilo_boot_iso'] = boot_iso
597
node.instance_info = i_info
599
@base.passthru(['POST'])
485
600
@task_manager.require_exclusive_lock
486
def _continue_deploy(self, task, **kwargs):
601
def pass_deploy_info(self, task, **kwargs):
487
602
"""Continues the iSCSI deployment from where ramdisk left off.
489
Continues the iSCSI deployment from the conductor node, finds the
490
boot ISO to boot the node, and sets the node to boot from boot ISO.
604
This method continues the iSCSI deployment from the conductor node
605
and writes the deploy image to the bare metal's disk. After that,
606
it does the following depending on boot_option for deploy:
607
- If the boot_option requested for this deploy is 'local', then it
608
sets the node to boot from disk (ramdisk installs the boot loader
609
present within the image to the bare metal's disk).
610
- If the boot_option requested is 'netboot' or no boot_option is
611
requested, it finds/creates the boot ISO to boot the instance
612
image, attaches the boot ISO to the bare metal and then sets
613
the node to boot from CDROM.
492
615
:param task: a TaskManager instance containing the node to act on.
493
616
:param kwargs: kwargs containing parameters for iSCSI deployment.
497
620
task.process_event('resume')
622
iwdi = node.driver_internal_info.get('is_whole_disk_image')
499
623
ilo_common.cleanup_vmedia_boot(task)
500
root_uuid = iscsi_deploy.continue_deploy(task, **kwargs)
624
uuid_dict_returned = iscsi_deploy.continue_deploy(task, **kwargs)
625
root_uuid_or_disk_id = uuid_dict_returned.get(
626
'root uuid', uuid_dict_returned.get('disk identifier'))
628
# TODO(rameshg87): It's not correct to return here as it will leave
629
# the node in DEPLOYING state. This will be fixed in bug 1405519.
630
if not root_uuid_or_disk_id:
506
boot_iso = _get_boot_iso(task, root_uuid)
509
LOG.error(_LE("Cannot get boot ISO for node %s"), node.uuid)
512
ilo_common.setup_vmedia_for_boot(task, boot_iso)
513
manager_utils.node_set_boot_device(task, boot_devices.CDROM)
515
address = kwargs.get('address')
516
deploy_utils.notify_deploy_complete(address)
634
# For iscsi_ilo driver, we boot from disk everytime if the image
635
# deployed is a whole disk image.
636
if iscsi_deploy.get_boot_option(node) == "local" or iwdi:
637
manager_utils.node_set_boot_device(task, boot_devices.DISK,
640
self._configure_vmedia_boot(task, root_uuid_or_disk_id)
642
deploy_utils.notify_deploy_complete(kwargs.get('address'))
518
644
LOG.info(_LI('Deployment to node %s done'), node.uuid)
520
i_info = node.instance_info
521
i_info['ilo_boot_iso'] = boot_iso
522
node.instance_info = i_info
523
645
task.process_event('done')
524
646
except Exception as e:
525
647
LOG.error(_LE('Deploy failed for instance %(instance)s. '
527
649
{'instance': node.instance_uuid, 'error': e})
528
650
msg = _('Failed to continue iSCSI deployment.')
529
651
deploy_utils.set_failed_state(task, msg)
653
@task_manager.require_exclusive_lock
654
def continue_deploy(self, task, **kwargs):
655
"""Method invoked when deployed with the IPA ramdisk.
657
This method is invoked during a heartbeat from an agent when
658
the node is in wait-call-back state. This deploys the image on
659
the node and then configures the node to boot according to the
660
desired boot option (netboot or localboot).
662
:param task: a TaskManager object containing the node.
663
:param kwargs: the kwargs passed from the heartbeat method.
664
:raises: InstanceDeployFailure, if it encounters some error during
667
task.process_event('resume')
669
LOG.debug('Continuing the deployment on node %s', node.uuid)
671
ilo_common.cleanup_vmedia_boot(task)
673
uuid_dict_returned = iscsi_deploy.do_agent_iscsi_deploy(task,
675
root_uuid = uuid_dict_returned.get('root uuid')
677
if iscsi_deploy.get_boot_option(node) == "local":
678
efi_system_part_uuid = uuid_dict_returned.get(
679
'efi system partition uuid')
680
self.configure_local_boot(
681
task, root_uuid=root_uuid,
682
efi_system_part_uuid=efi_system_part_uuid)
684
# Agent vendorpassthru are made without auth token.
685
# We require auth_token to talk to glance while building boot iso.
686
task.context.auth_token = keystone.get_admin_auth_token()
687
self._configure_vmedia_boot(task, root_uuid)
689
self.reboot_and_finish_deploy(task)