From 21551737776ed5f7f80c2f661114655baa359166 Mon Sep 17 00:00:00 2001 From: "Andrei V. Ostapenko" Date: Thu, 19 Jan 2017 21:09:31 +0200 Subject: [PATCH] Adopt code to ironic changes (since kilo) Ironic code now provide PXE configuration tools. So we can drop our own PXE related code. Ironic images manipulation code have been improved. This allow to drop some more our code. Change-Id: Iaeda025456dbde9163c792e6de6d165c6f03f197 --- bareon_ironic/bareon.py | 5 + bareon_ironic/modules/bareon_base.py | 311 ++++++++------------------ bareon_ironic/modules/bareon_rsync.py | 15 -- etc/ironic/ironic.conf.bareon_sample | 26 --- 4 files changed, 104 insertions(+), 253 deletions(-) diff --git a/bareon_ironic/bareon.py b/bareon_ironic/bareon.py index aeb25a1..cca731c 100644 --- a/bareon_ironic/bareon.py +++ b/bareon_ironic/bareon.py @@ -16,6 +16,7 @@ from ironic.drivers import base from ironic.drivers.modules import inspector from ironic.drivers.modules import ipmitool +from ironic.drivers.modules import pxe from ironic.drivers.modules import ssh from bareon_ironic.modules import bareon_rsync @@ -37,6 +38,7 @@ class BareonSwiftAndIPMIToolDriver(base.BaseDriver): def __init__(self): self.power = ipmitool.IPMIPower() self.deploy = bareon_swift.BareonSwiftDeploy() + self.boot = pxe.PXEBoot() self.management = ipmitool.IPMIManagement() self.vendor = bareon_swift.BareonSwiftVendor() self.inspect = inspector.Inspector.create_if_enabled( @@ -59,6 +61,7 @@ class BareonSwiftAndSSHDriver(base.BaseDriver): def __init__(self): self.power = ssh.SSHPower() self.deploy = bareon_swift.BareonSwiftDeploy() + self.boot = pxe.PXEBoot() self.management = ssh.SSHManagement() self.vendor = bareon_swift.BareonSwiftVendor() self.inspect = inspector.Inspector.create_if_enabled( @@ -80,6 +83,7 @@ class BareonRsyncAndIPMIToolDriver(base.BaseDriver): def __init__(self): self.power = ipmitool.IPMIPower() self.deploy = bareon_rsync.BareonRsyncDeploy() + self.boot = pxe.PXEBoot() self.management = ipmitool.IPMIManagement() self.vendor = bareon_rsync.BareonRsyncVendor() self.inspect = inspector.Inspector.create_if_enabled( @@ -102,6 +106,7 @@ class BareonRsyncAndSSHDriver(base.BaseDriver): def __init__(self): self.power = ssh.SSHPower() self.deploy = bareon_rsync.BareonRsyncDeploy() + self.boot = pxe.PXEBoot() self.management = ssh.SSHManagement() self.vendor = bareon_rsync.BareonRsyncVendor() self.inspect = inspector.Inspector.create_if_enabled( diff --git a/bareon_ironic/modules/bareon_base.py b/bareon_ironic/modules/bareon_base.py index e4ff029..4393bdb 100644 --- a/bareon_ironic/modules/bareon_base.py +++ b/bareon_ironic/modules/bareon_base.py @@ -28,21 +28,12 @@ import stevedore import six from oslo_concurrency import processutils from oslo_config import cfg -from oslo_utils import excutils -from oslo_utils import fileutils from oslo_log import log from oslo_service import loopingcall -from ironic_lib import utils as ironic_utils - from ironic.common import boot_devices -from ironic.common import dhcp_factory from ironic.common import exception -from ironic.common import keystone -from ironic.common import pxe_utils from ironic.common import states -from ironic.common import utils -from ironic.common.glance_service import service_utils from ironic.common.i18n import _ from ironic.common.i18n import _LE from ironic.common.i18n import _LI @@ -50,7 +41,6 @@ from ironic.conductor import task_manager from ironic.conductor import utils as manager_utils from ironic.drivers import base from ironic.drivers.modules import deploy_utils -from ironic.drivers.modules import image_cache from ironic.objects import node as db_node from bareon_ironic.modules import bareon_exception @@ -58,18 +48,8 @@ from bareon_ironic.modules import bareon_utils from bareon_ironic.modules.resources import actions from bareon_ironic.modules.resources import image_service from bareon_ironic.modules.resources import resources -from bareon_ironic.modules.resources import rsync agent_opts = [ - cfg.StrOpt('pxe_config_template', - default=os.path.join(os.path.dirname(__file__), - 'bareon_config.template'), - help='Template file for two-disk boot PXE configuration.'), - cfg.StrOpt('pxe_config_template_live', - default=os.path.join(os.path.dirname(__file__), - 'bareon_config_live.template'), - help='Template file for three-disk (live boot) PXE ' - 'configuration.'), cfg.StrOpt('bareon_pxe_append_params', default='nofb nomodeset vga=normal', help='Additional append parameters for baremetal PXE boot.'), @@ -77,8 +57,6 @@ agent_opts = [ help='UUID (from Glance) of the default deployment kernel.'), cfg.StrOpt('deploy_ramdisk', help='UUID (from Glance) of the default deployment ramdisk.'), - cfg.StrOpt('deploy_squashfs', - help='UUID (from Glance) of the default deployment root FS.'), cfg.StrOpt('deploy_config_priority', default='instance:node:image:conf', help='Priority for deploy config'), @@ -110,14 +88,12 @@ REQUIRED_PROPERTIES = {} OTHER_PROPERTIES = { 'deploy_kernel': _('UUID (from Glance) of the deployment kernel.'), 'deploy_ramdisk': _('UUID (from Glance) of the deployment ramdisk.'), - 'deploy_squashfs': _('UUID (from Glance) of the deployment root FS image ' - 'mounted at boot time.'), 'bareon_username': _('SSH username; default is "root" Optional.'), 'bareon_key_filename': _('Name of SSH private key file; default is ' '"/etc/ironic/bareon_key". Optional.'), 'bareon_ssh_port': _('SSH port; default is 22. Optional.'), 'bareon_deploy_script': _('path to bareon executable entry point; ' - 'default is "provision_ironic" Optional.'), + 'default is "bareon-provision" Optional.'), 'deploy_config': _('Deploy config Glance image id/name'), } COMMON_PROPERTIES = OTHER_PROPERTIES @@ -127,40 +103,6 @@ REQUIRED_BAREON_VERSION = "0.0." TERMINATE_FLAG = 'terminate_deployment' -@image_cache.cleanup(priority=25) -class AgentTFTPImageCache(image_cache.ImageCache): - def __init__(self, image_service=None): - super(AgentTFTPImageCache, self).__init__( - CONF.pxe.tftp_master_path, - # MiB -> B - CONF.pxe.image_cache_size * 1024 * 1024, - # min -> sec - CONF.pxe.image_cache_ttl * 60, - image_service=image_service) - - -def _create_rootfs_link(task): - """Create Swift temp url for deployment root FS.""" - rootfs = task.node.driver_info['deploy_squashfs'] - if service_utils.is_glance_image(rootfs): - glance = image_service.GlanceImageService(version=2, - context=task.context) - image_info = glance.show(rootfs) - temp_url = glance.swift_temp_url(image_info) - temp_url += '&filename=/root.squashfs' - return temp_url - - try: - image_service.HttpImageService().validate_href(rootfs) - except exception.ImageRefValidationFailed: - with excutils.save_and_reraise_exception(): - LOG.error(_LE("Agent deploy supports only HTTP URLs as " - "driver_info['deploy_squashfs']. Either %s " - "is not a valid HTTP URL or " - "is not reachable."), rootfs) - return rootfs - - def _clean_up_images(task): node = task.node if node.instance_info.get('images_cleaned_up', False): @@ -203,13 +145,9 @@ class BareonDeploy(base.DeployInterface): :param task: a TaskManager instance :raises: MissingParameterValue """ - node = task.node - params = self._get_boot_files(node) - error_msg = _('Node %s failed to validate deploy image info. Some ' - 'parameters were missing') % node.uuid - deploy_utils.check_for_missing_params(params, error_msg) - self._parse_driver_info(node) + _NodeDriverInfoAdapter(task.node) + self._validate_deployment_config(task) @task_manager.require_exclusive_lock def deploy(self, task): @@ -223,7 +161,7 @@ class BareonDeploy(base.DeployInterface): :param task: a TaskManager instance. :returns: status of the deploy. One of ironic.common.states. """ - self._do_pxe_boot(task) + manager_utils.node_power_action(task, states.REBOOT) return states.DEPLOYWAIT @task_manager.require_exclusive_lock @@ -241,9 +179,21 @@ class BareonDeploy(base.DeployInterface): :param task: a TaskManager instance. """ + self._fetch_resources(task) - self._validate_deployment_config(task) - self._prepare_pxe_boot(task) + + # Temporary set possible missing driver_info fields. This changes will + # not become persistent until someone do + # node.driver_info = updated_driver_info + # node.save() + driver_info = task.node.driver_info + for field, value in ( + ('deploy_kernel', CONF.bareon.deploy_kernel), + ('deploy_ramdisk', CONF.bareon.deploy_ramdisk)): + driver_info.setdefault(field, value) + + task.driver.boot.prepare_ramdisk(task, + self._build_pxe_config_options(task)) def clean_up(self, task): """Clean up the deployment environment for this node. @@ -261,28 +211,13 @@ class BareonDeploy(base.DeployInterface): :param task: a TaskManager instance. """ - # NOTE(lobur): most of the cleanup is done immediately after - # deployment (see end of pass_deploy_info). - # - PXE resources are left till the node is unprovisioned because we - # plan to add support of tenant PXE boot. - # - Resources such as provision.json are left till the node is - # unprovisioned to simplify debugging. - self._clean_up_pxe(task) + + task.driver.boot.clean_up_ramdisk(task) _clean_up_images(task) - self._clean_up_resource_dirs(task) def take_over(self, task): pass - def _clean_up_pxe(self, task): - """Clean up left over PXE and DHCP files.""" - pxe_info = self._get_tftp_image_info(task.node) - for label in pxe_info: - path = pxe_info[label][1] - ironic_utils.unlink_without_raise(path) - AgentTFTPImageCache().clean_up() - pxe_utils.clean_up_pxe_config(task) - def _fetch_resources(self, task): self._fetch_provision_json(task) self._fetch_actions(task) @@ -387,102 +322,24 @@ class BareonDeploy(base.DeployInterface): with open(filename, 'w') as f: f.write(json.dumps(actions_data)) - def _clean_up_resource_dirs(self, task): - utils.rmtree_without_raise( - resources.get_abs_node_workdir_path(task.node)) - utils.rmtree_without_raise( - rsync.get_abs_node_workdir_path(task.node)) - - def _build_instance_info_for_deploy(self, task): - raise NotImplementedError - - def _do_pxe_boot(self, task, ports=None): - """Reboot the node into the PXE ramdisk. - - :param task: a TaskManager instance - :param ports: a list of Neutron port dicts to update DHCP options on. - If None, will get the list of ports from the Ironic port objects. - """ - dhcp_opts = pxe_utils.dhcp_options_for_instance(task) - provider = dhcp_factory.DHCPFactory() - provider.update_dhcp(task, dhcp_opts, ports) - manager_utils.node_set_boot_device(task, boot_devices.PXE, - persistent=True) - manager_utils.node_power_action(task, states.REBOOT) - - def _cache_tftp_images(self, ctx, node, pxe_info): - """Fetch the necessary kernels and ramdisks for the instance.""" - fileutils.ensure_tree( - os.path.join(CONF.pxe.tftp_root, node.uuid)) - LOG.debug("Fetching kernel and ramdisk for node %s", - node.uuid) - deploy_utils.fetch_images(ctx, AgentTFTPImageCache(), - pxe_info.values()) - - def _prepare_pxe_boot(self, task): - """Prepare the files required for PXE booting the agent.""" - pxe_info = self._get_tftp_image_info(task.node) - - # Do live boot if squashfs is specified in either way. - is_live_boot = (task.node.driver_info.get('deploy_squashfs') or - CONF.bareon.deploy_squashfs) - pxe_options = self._build_pxe_config_options(task, pxe_info, - is_live_boot) - template = (CONF.bareon.pxe_config_template_live if is_live_boot - else CONF.bareon.pxe_config_template) - pxe_utils.create_pxe_config(task, - pxe_options, - template) - - self._cache_tftp_images(task.context, task.node, pxe_info) - - def _get_tftp_image_info(self, node): - params = self._get_boot_files(node) - return pxe_utils.get_deploy_kr_info(node.uuid, params) - - def _build_pxe_config_options(self, task, pxe_info, live_boot): + def _build_pxe_config_options(self, task): """Builds the pxe config options for booting agent. This method builds the config options to be replaced on the agent pxe config template. :param task: a TaskManager instance - :param pxe_info: A dict containing the 'deploy_kernel' and - 'deploy_ramdisk' for the agent pxe config template. :returns: a dict containing the options to be applied on the agent pxe config template. """ - ironic_api = (CONF.conductor.api_url or - keystone.get_service_url()).rstrip('/') agent_config_opts = { - 'deployment_aki_path': pxe_info['deploy_kernel'][1], - 'deployment_ari_path': pxe_info['deploy_ramdisk'][1], - 'bareon_pxe_append_params': CONF.bareon.bareon_pxe_append_params, 'deployment_id': task.node.uuid, - 'api-url': ironic_api, + 'ironic_api_url': deploy_utils.get_ironic_api_url(), } - if live_boot: - agent_config_opts['rootfs-url'] = _create_rootfs_link(task) - return agent_config_opts - def _get_boot_files(self, node): - d_info = node.driver_info - params = { - 'deploy_kernel': d_info.get('deploy_kernel', - CONF.bareon.deploy_kernel), - 'deploy_ramdisk': d_info.get('deploy_ramdisk', - CONF.bareon.deploy_ramdisk), - } - # Present only when live boot is used - squashfs = d_info.get('deploy_squashfs', CONF.bareon.deploy_squashfs) - if squashfs: - params['deploy_squashfs'] = squashfs - - return params - def _get_image_resource_mode(self): raise NotImplementedError @@ -571,43 +428,6 @@ class BareonDeploy(base.DeployInterface): return images.resources - @staticmethod - def _parse_driver_info(node): - """Gets the information needed for accessing the node. - - :param node: the Node object. - :returns: dictionary of information. - :raises: InvalidParameterValue if any required parameters are - incorrect. - :raises: MissingParameterValue if any required parameters are missing. - - """ - info = node.driver_info - d_info = {} - error_msgs = [] - - d_info['username'] = info.get('bareon_username', 'root') - d_info['key_filename'] = info.get('bareon_key_filename', - '/etc/ironic/bareon_key') - - if not os.path.isfile(d_info['key_filename']): - error_msgs.append(_("SSH key file %s not found.") % - d_info['key_filename']) - - try: - d_info['port'] = int(info.get('bareon_ssh_port', 22)) - except ValueError: - error_msgs.append(_("'bareon_ssh_port' must be an integer.")) - - if error_msgs: - msg = (_('The following errors were encountered while parsing ' - 'driver_info:\n%s') % '\n'.join(error_msgs)) - raise exception.InvalidParameterValue(msg) - - d_info['script'] = info.get('bareon_deploy_script', 'bareon-provision') - - return d_info - def terminate_deployment(self, task): node = task.node if TERMINATE_FLAG not in node.instance_info: @@ -670,7 +490,7 @@ class BareonVendor(base.VendorInterface): """ return COMMON_PROPERTIES - def validate(self, task, method, **kwargs): + def validate(self, task, method=None, **kwargs): """Validate the driver-specific Node deployment info. :param task: a TaskManager instance @@ -689,7 +509,7 @@ class BareonVendor(base.VendorInterface): if not kwargs.get('address'): raise exception.MissingParameterValue(_('Bareon must pass ' 'address of a node.')) - BareonDeploy._parse_driver_info(task.node) + _NodeDriverInfoAdapter(task.node) def validate_switch_boot(self, task, **kwargs): if not kwargs.get('image'): @@ -717,18 +537,24 @@ class BareonVendor(base.VendorInterface): deploy_utils.set_failed_state(task, err_msg) return - params = BareonDeploy._parse_driver_info(node) - params['host'] = kwargs.get('address') + driver_info = _NodeDriverInfoAdapter(task.node) cmd = '{} --data_driver "{}" --deploy_driver "{}"'.format( - params.pop('script'), bareon_utils.node_data_driver(node), + driver_info.entry_point, bareon_utils.node_data_driver(node), node.instance_info['deploy_driver']) if CONF.debug: cmd += ' --debug' instance_info = node.instance_info + connect_args = { + 'username': driver_info.ssh_login, + 'key_filename': driver_info.ssh_key, + 'host': kwargs['address']} + if driver_info.ssh_port: + connect_args['port'] = driver_info.ssh_port + try: - ssh = bareon_utils.get_ssh_connection(task, **params) + ssh = bareon_utils.get_ssh_connection(task, **connect_args) sftp = ssh.open_sftp() self._check_bareon_version(ssh, node.uuid) @@ -744,7 +570,7 @@ class BareonVendor(base.VendorInterface): bareon_utils.sftp_write_to(sftp, configdrive, '/tmp/config-drive.img') - out, err = self._deploy(task, ssh, cmd, **params) + out, err = self._deploy(task, ssh, cmd, **connect_args) LOG.info(_LI('[%(node)s] Bareon pass on node %(node)s'), {'node': node.uuid}) LOG.debug('[%s] Bareon stdout is: "%s"', node.uuid, out) @@ -752,7 +578,7 @@ class BareonVendor(base.VendorInterface): self._get_boot_info(task, ssh) - self._run_actions(task, ssh, sftp, params) + self._run_actions(task, ssh, sftp, connect_args) manager_utils.node_power_action(task, states.POWER_OFF) manager_utils.node_set_boot_device(task, boot_devices.DISK, @@ -762,8 +588,8 @@ class BareonVendor(base.VendorInterface): except exception.SSHConnectFailed as e: msg = ( _('[%(node)s] SSH connect to node %(host)s failed. ' - 'Error: %(error)s') % {'host': params['host'], 'error': e, - 'node': node.uuid}) + 'Error: %(error)s') % {'host': connect_args['host'], + 'error': e, 'node': node.uuid}) self._deploy_failed(task, msg) except exception.ConfigInvalid as e: @@ -1144,3 +970,64 @@ def get_on_fail_script_path(node): def get_tenant_images_json_path(node): return os.path.join(resources.get_node_resources_dir(node), "tenant_images.json") + + +# TODO(dbogun): handle all driver_info keys +class _NodeDriverInfoAdapter(object): + ssh_port = None + # TODO(dbogun): check API way to defined access defaults + ssh_login = 'root' + ssh_key = '/etc/ironic/bareon_key' + entry_point = 'bareon-provision' + + def __init__(self, node): + self.node = node + self._raw = self.node.driver_info + self._errors = [] + + self._extract_ssh_port() + self._extract_optional_parameters() + self._validate() + + if self._errors: + raise exception.InvalidParameterValue(_( + 'The following errors were encountered while parsing ' + 'driver_info:\n {}').format(' \n'.join(self._errors))) + + def _extract_ssh_port(self): + key = 'bareon_ssh_port' + try: + port = self._raw[key] + port = int(port) + if not 0 < port < 65536: + raise ValueError( + 'Port number {} is outside of allowed range'.format(port)) + except KeyError: + port = None + except ValueError as e: + self._errors.append('{}: {}'.format(key, str(e))) + return + + self.ssh_port = port + + def _extract_optional_parameters(self): + for attr, name in ( + ('ssh_key', 'bareon_key_filename'), + ('ssh_login', 'bareon_username'), + ('entry_point', 'bareon_deploy_script')): + try: + value = self._raw[name] + except KeyError: + continue + setattr(self, attr, value) + + def _validate(self): + self._validate_ssh_key() + + def _validate_ssh_key(self): + try: + open(self.ssh_key).close() + except IOError as e: + self._errors.append( + 'Unable to use "{key}" as ssh private key: ' + '{e.strerror} (errno={e.errno})'.format(key=self.ssh_key, e=e)) diff --git a/bareon_ironic/modules/bareon_rsync.py b/bareon_ironic/modules/bareon_rsync.py index d3d12df..6b0ceb9 100644 --- a/bareon_ironic/modules/bareon_rsync.py +++ b/bareon_ironic/modules/bareon_rsync.py @@ -25,22 +25,7 @@ from bareon_ironic.modules import bareon_base from bareon_ironic.modules.resources import resources from bareon_ironic.modules.resources import rsync -rsync_opts = [ - cfg.StrOpt('rsync_master_path', - default='/rsync/master_images', - help='Directory where master rsync images are stored on disk.'), - cfg.IntOpt('image_cache_size', - default=20480, - help='Maximum size (in MiB) of cache for master images, ' - 'including those in use.'), - cfg.IntOpt('image_cache_ttl', - default=10080, - help='Maximum TTL (in minutes) for old master images in ' - 'cache.'), -] - CONF = cfg.CONF -CONF.register_opts(rsync_opts, group='rsync') class BareonRsyncDeploy(bareon_base.BareonDeploy): diff --git a/etc/ironic/ironic.conf.bareon_sample b/etc/ironic/ironic.conf.bareon_sample index 406ac61..2dc4b3b 100644 --- a/etc/ironic/ironic.conf.bareon_sample +++ b/etc/ironic/ironic.conf.bareon_sample @@ -1,13 +1,4 @@ [bareon] - -# Template file for two-disk boot PXE configuration. (string -# value) -#pxe_config_template=$pybasedir/modules/bareon_config.template - -# Template file for three-disk (live boot) PXE configuration. -# (string value) -#pxe_config_template_live=$pybasedir/modules/bareon_config_live.template - # Additional append parameters for baremetal PXE boot. (string # value) #bareon_pxe_append_params=nofb nomodeset vga=normal @@ -20,10 +11,6 @@ # (string value) #deploy_ramdisk= -# UUID (from Glance) of the default deployment root FS. -# (string value) -#deploy_squashfs= - # Priority for deploy config (string value) #deploy_config_priority=instance:node:image:conf @@ -70,19 +57,6 @@ #resource_cache_ttl=1440 [rsync] - -# Directory where master rsync images are stored on disk. -# (string value) -#rsync_master_path=/rsync/master_images - -# Maximum size (in MiB) of cache for master images, including -# those in use. (integer value) -#image_cache_size=20480 - -# Maximum TTL (in minutes) for old master images in cache. -# (integer value) -#image_cache_ttl=10080 - # IP address of Ironic compute node's rsync server. (string # value) #rsync_server=$my_ip