~ubuntu-branches/ubuntu/vivid/ironic/vivid-updates

« back to all changes in this revision

Viewing changes to ironic/drivers/modules/pxe.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2015-03-30 11:14:57 UTC
  • mfrom: (1.2.6)
  • Revision ID: package-import@ubuntu.com-20150330111457-kr4ju3guf22m4vbz
Tags: 2015.1~b3-0ubuntu1
* New upstream release.
  + d/control: 
    - Align with upstream dependencies.
    - Add dh-python to build-dependencies.
    - Add psmisc as a dependency. (LP: #1358820)
  + d/p/fix-requirements.patch: Rediffed.
  + d/ironic-conductor.init.in: Fixed typos in LSB headers,
    thanks to JJ Asghar. (LP: #1429962)

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
 
23
23
from oslo_config import cfg
24
24
 
 
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
96
100
 
97
101
 
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. "
 
104
                       "Required."),
 
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. "
100
109
                           "Required."),
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."),
103
113
}
104
114
COMMON_PROPERTIES = REQUIRED_PROPERTIES
117
127
    """
118
128
    info = node.driver_info
119
129
    d_info = {}
120
 
    d_info['deploy_kernel'] = info.get('pxe_deploy_kernel')
121
 
    d_info['deploy_ramdisk'] = info.get('pxe_deploy_ramdisk')
 
130
 
 
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.')
 
136
 
 
137
    for parameter in ('deploy_kernel', 'deploy_ramdisk'):
 
138
        value = info.get(parameter)
 
139
        if not value:
 
140
            old_parameter = 'pxe_' + parameter
 
141
            value = info.get(old_parameter)
 
142
            if value:
 
143
                LOG.warning(deprecated_msg, {'old_param': old_parameter,
 
144
                                             'new_param': parameter,
 
145
                                             'node': node.uuid})
 
146
        d_info[parameter] = value
122
147
 
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)
126
151
 
127
152
    return d_info
128
153
 
160
185
    :returns: A dictionary of pxe options to be used in the pxe bootfile
161
186
        template.
162
187
    """
 
188
    is_whole_disk_image = node.driver_internal_info.get('is_whole_disk_image')
 
189
 
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'])
170
198
    else:
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]
175
204
 
176
205
    pxe_options = {
177
206
        'deployment_aki_path': deploy_kernel,
178
207
        'deployment_ari_path': deploy_ramdisk,
179
 
        'aki_path': kernel,
180
 
        'ari_path': ramdisk,
181
208
        'pxe_append_params': CONF.pxe.pxe_append_params,
182
209
        'tftp_server': CONF.pxe.tftp_server
183
210
    }
184
211
 
 
212
    if not is_whole_disk_image:
 
213
        pxe_options.update({'aki_path': kernel,
 
214
                            'ari_path': ramdisk})
 
215
 
185
216
    deploy_ramdisk_options = iscsi_deploy.build_deploy_ramdisk_options(node)
186
217
    pxe_options.update(deploy_ramdisk_options)
 
218
 
 
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)
 
224
 
187
225
    return pxe_options
188
226
 
189
227
 
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",
212
250
              node.uuid)
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
219
257
 
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
224
262
 
225
263
    """
229
267
 
230
268
    image_info.update(pxe_utils.get_deploy_kr_info(node.uuid, d_info))
231
269
 
 
270
    if node.driver_internal_info.get('is_whole_disk_image'):
 
271
        return image_info
 
272
 
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
282
323
        """
283
 
 
284
 
        # Check the boot_mode capability parameter value.
285
 
        driver_utils.validate_boot_mode_capability(task.node)
 
324
        node = task.node
 
325
 
 
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)
 
329
 
 
330
        boot_mode = driver_utils.get_node_capability(node, 'boot_mode')
286
331
 
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})
301
345
 
302
 
        d_info = _parse_deploy_info(task.node)
 
346
        d_info = _parse_deploy_info(node)
303
347
 
304
348
        iscsi_deploy.validate(task)
305
349
 
306
 
        props = ['kernel_id', 'ramdisk_id']
307
 
        iscsi_deploy.validate_glance_image_properties(task.context, d_info,
308
 
                                                      props)
 
350
        if node.driver_internal_info.get('is_whole_disk_image'):
 
351
            props = []
 
352
        elif service_utils.is_glance_image(d_info['image_source']):
 
353
            props = ['kernel_id', 'ramdisk_id']
 
354
        else:
 
355
            props = ['kernel', 'ramdisk']
 
356
 
 
357
        iscsi_deploy.validate_image_properties(task.context, d_info, props)
309
358
 
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().
320
369
 
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)
333
382
 
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.
339
 
        try:
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."))
347
 
            else:
348
 
                raise
349
 
 
 
383
        deploy_utils.try_set_boot_device(task, boot_devices.PXE)
350
384
        manager_utils.node_power_action(task, states.REBOOT)
351
385
 
352
386
        return states.DEPLOYWAIT
390
424
 
391
425
        pxe_utils.create_pxe_config(task, pxe_options,
392
426
                                    pxe_config_template)
 
427
 
 
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)
394
431
 
 
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
 
439
            try:
 
440
                root_uuid_or_disk_id = task.node.driver_internal_info[
 
441
                                                'root_uuid_or_disk_id']
 
442
            except KeyError:
 
443
                if not iwdi:
 
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})
 
448
                else:
 
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})
 
453
            else:
 
454
                pxe_config_path = pxe_utils.get_pxe_config_file_path(
 
455
                    task.node.uuid)
 
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'),
 
459
                    iwdi)
 
460
 
395
461
    def clean_up(self, task):
396
462
        """Clean up the deployment environment for the task's node.
397
463
 
421
487
        _destroy_token_file(node)
422
488
 
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)
427
 
 
428
 
 
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)
 
495
        else:
 
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
 
499
            # deploy ramdisk
 
500
            pxe_utils.clean_up_pxe_config(task)
 
501
 
 
502
 
 
503
class VendorPassthru(agent_base_vendor.BaseAgentVendor):
430
504
    """Interface to mix IPMI and PXE vendor-specific interfaces."""
431
505
 
432
506
    def get_properties(self):
435
509
    def validate(self, task, method, **kwargs):
436
510
        """Validates the inputs for a vendor passthru.
437
511
 
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.
 
513
 
 
514
        Valid methods:
 
515
        * pass_deploy_info
441
516
 
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.
446
521
        """
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)
448
525
 
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.
453
530
 
454
531
        This method continues the deployment of the baremetal node over iSCSI
462
539
        task.process_event('resume')
463
540
 
464
541
        _destroy_token_file(node)
465
 
 
466
 
        root_uuid = iscsi_deploy.continue_deploy(task, **kwargs)
467
 
 
468
 
        if not root_uuid:
 
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'))
 
546
 
 
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:
469
550
            return
470
551
 
 
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
 
555
        # has changed.
 
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
 
559
        node.save()
 
560
 
471
561
        try:
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)
 
567
            else:
 
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)
475
573
 
476
574
            deploy_utils.notify_deploy_complete(kwargs['address'])
477
 
 
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)
 
583
 
 
584
    @task_manager.require_exclusive_lock
 
585
    def continue_deploy(self, task, **kwargs):
 
586
        """Method invoked when deployed with the IPA ramdisk.
 
587
 
 
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).
 
592
 
 
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
 
596
            the deploy.
 
597
        """
 
598
        task.process_event('resume')
 
599
        node = task.node
 
600
        LOG.debug('Continuing the deployment on node %s', node.uuid)
 
601
 
 
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
 
604
        # it here.
 
605
        _destroy_token_file(node)
 
606
 
 
607
        uuid_dict_returned = iscsi_deploy.do_agent_iscsi_deploy(task,
 
608
                                                                self._client)
 
609
 
 
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)
 
618
 
 
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)
 
622
        else:
 
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)
 
630
 
 
631
        self.reboot_and_finish_deploy(task)