summaryrefslogtreecommitdiff
path: root/ironic
diff options
context:
space:
mode:
authorRuby Loo <rloo@verizonmedia.com>2022-02-04 21:26:26 +0000
committerRuby Loo <opensrloo@gmail.com>2022-03-04 17:38:06 +0000
commitceb2a5235a4a90c3ed0948dcde46370b9d7cbd00 (patch)
tree9fb91037986133662f76462fd032be9b68ca65dc /ironic
parent7d7147b4b151a8fa5528c329fc452572e6d446a5 (diff)
downloadironic-ceb2a5235a4a90c3ed0948dcde46370b9d7cbd00.tar.gz
More fixes for anaconda deploy interface
The anaconda deploy interface has a few issues that are addressed here: - fixes logic in get_instance_image_info() for anaconda. If the ironic node's instance_info doesn't have both 'stage2' and 'ks_template' specified, we weren't using any values from the instance_info. This has been fixed to use values from instance_info if specified. Otherwise, they are set as follows: The 'stage2' value is taken from the image properties. We use the value for 'ks_template' if it is specified in the image properties. If not (since it is optional), we use the config option's '[anaconda]default_ks_template' value. setting. - For anaconda's stage2 directory, we were incorrectly creating a directory using the full path of the stage2 file. It now correctly creates the right directory. - The anaconda deploy interface expects the node's instance_info to be populated with the 'image_url'; added code to do that in PXEAnacondaDeploy's prepare() method. - When the deploy is finished and the bm node is being rebooted, we incorrectly set the node's provision state to 'active' instead of doing it via the provisioning state machine mechanism. - The code that was doing the validation of the kickstart file was incorrect and resulted in errors; this has been addressed. - The '%traceback' section in the packaged 'ks.cfg.template' file is deprecated and fails validation, so it has been removed. Conflict: ironic/common/pxe_utils.py, cache_ramdisk_kernel(), due to code changes in master that aren't in xena. Manually fixed. Change-Id: I953e948bcfa108d4c8e7b145da2f52b652e52a10 (cherry picked from commit 06cc5d47dcdc49193e5e4e748471eb0b9ae97bbb)
Diffstat (limited to 'ironic')
-rw-r--r--ironic/common/pxe_utils.py82
-rw-r--r--ironic/drivers/modules/ks.cfg.template6
-rw-r--r--ironic/drivers/modules/pxe.py7
-rw-r--r--ironic/tests/unit/drivers/modules/test_pxe.py10
4 files changed, 60 insertions, 45 deletions
diff --git a/ironic/common/pxe_utils.py b/ironic/common/pxe_utils.py
index 08d6d3f98..13c27d718 100644
--- a/ironic/common/pxe_utils.py
+++ b/ironic/common/pxe_utils.py
@@ -694,42 +694,52 @@ def get_instance_image_info(task, ipxe_enabled=False):
return image_info
labels = ('kernel', 'ramdisk')
+ image_properties = None
d_info = deploy_utils.get_image_instance_info(node)
if not (i_info.get('kernel') and i_info.get('ramdisk')):
+ # NOTE(rloo): If both are not specified in instance_info
+ # we won't use any of them. We'll use the values specified
+ # with the image, which we assume have been set.
glance_service = service.GlanceImageService(context=ctx)
- iproperties = glance_service.show(d_info['image_source'])['properties']
+ image_properties = glance_service.show(
+ d_info['image_source'])['properties']
for label in labels:
- i_info[label] = str(iproperties[label + '_id'])
+ i_info[label] = str(image_properties[label + '_id'])
node.instance_info = i_info
node.save()
anaconda_labels = ()
if deploy_utils.get_boot_option(node) == 'kickstart':
- # stage2 - Installer stage2 squashfs image
- # ks_template - Anaconda kickstart template
+ # stage2: installer stage2 squashfs image
+ # ks_template: anaconda kickstart template
# ks_cfg - rendered ks_template
anaconda_labels = ('stage2', 'ks_template', 'ks_cfg')
- if not (i_info.get('stage2') and i_info.get('ks_template')):
- iproperties = glance_service.show(
- d_info['image_source']
- )['properties']
- for label in anaconda_labels:
+ if not i_info.get('stage2') or not i_info.get('ks_template'):
+ if not image_properties:
+ glance_service = service.GlanceImageService(context=ctx)
+ image_properties = glance_service.show(
+ d_info['image_source'])['properties']
+ if not i_info.get('ks_template'):
# ks_template is an optional property on the image
- if (label == 'ks_template'
- and not iproperties.get('ks_template')):
- i_info[label] = CONF.anaconda.default_ks_template
- elif label == 'ks_cfg':
- i_info[label] = ''
- elif label == 'stage2' and 'stage2_id' not in iproperties:
- msg = ("stage2_id property missing on the image. "
- "The anaconda deploy interface requires stage2_id "
- "property to be associated with the os image. ")
+ if 'ks_template' not in image_properties:
+ i_info['ks_template'] = CONF.anaconda.default_ks_template
+ else:
+ i_info['ks_template'] = str(
+ image_properties['ks_template'])
+ if not i_info.get('stage2'):
+ if 'stage2_id' not in image_properties:
+ msg = ("'stage2_id' property is missing from the OS image "
+ "%s. The anaconda deploy interface requires this "
+ "to be set with the OS image or in instance_info. "
+ % d_info['image_source'])
raise exception.ImageUnacceptable(msg)
else:
- i_info[label] = str(iproperties['stage2_id'])
+ i_info['stage2'] = str(image_properties['stage2_id'])
+ # NOTE(rloo): This is internally generated; cannot be specified.
+ i_info['ks_cfg'] = ''
- node.instance_info = i_info
- node.save()
+ node.instance_info = i_info
+ node.save()
for label in labels + anaconda_labels:
image_info[label] = (
@@ -1119,16 +1129,17 @@ def validate_kickstart_file(ks_cfg):
return
with tempfile.NamedTemporaryFile(
- dir=CONF.tempdir, suffix='.cfg') as ks_file:
- ks_file.writelines(ks_cfg)
+ dir=CONF.tempdir, suffix='.cfg', mode='wt') as ks_file:
+ ks_file.write(ks_cfg)
+ ks_file.flush()
try:
- result = utils.execute(
+ utils.execute(
'ksvalidator', ks_file.name, check_on_exit=[0], attempts=1
)
- except processutils.ProcessExecutionError:
+ except processutils.ProcessExecutionError as e:
msg = _(("The kickstart file generated does not pass validation. "
- "The ksvalidator tool returned following error(s): %s") %
- (result))
+ "The ksvalidator tool returned the following error: %s") %
+ (e))
raise exception.InvalidKickstartFile(msg)
@@ -1225,17 +1236,14 @@ def cache_ramdisk_kernel(task, pxe_info, ipxe_enabled=False):
else:
path = os.path.join(get_root_dir(), node.uuid)
fileutils.ensure_tree(path)
- # anconda deploy will have 'stage2' as one of the labels in pxe_info dict
+ # anaconda deploy will have 'stage2' as one of the labels in pxe_info dict
if 'stage2' in pxe_info.keys():
- # stage2 will be stored in ipxe http directory. So make sure they
- # exist.
- fileutils.ensure_tree(
- get_file_path_from_label(
- node.uuid,
- get_ipxe_root_dir(),
- 'stage2'
- )
- )
+ # stage2 will be stored in ipxe http directory so make sure the
+ # directory exists.
+ file_path = get_file_path_from_label(node.uuid,
+ get_ipxe_root_dir(),
+ 'stage2')
+ fileutils.ensure_tree(os.path.dirname(file_path))
# ks_cfg is rendered later by the driver using ks_template. It cannot
# be fetched and cached.
t_pxe_info.pop('ks_cfg')
diff --git a/ironic/drivers/modules/ks.cfg.template b/ironic/drivers/modules/ks.cfg.template
index 40552377b..f7ef75e1c 100644
--- a/ironic/drivers/modules/ks.cfg.template
+++ b/ironic/drivers/modules/ks.cfg.template
@@ -18,7 +18,7 @@ autopart
# Downloading and installing OS image using liveimg section is mandatory
liveimg --url {{ ks_options.liveimg_url }}
-# Following %pre, %onerror and %trackback sections are mandatory
+# Following %pre and %onerror sections are mandatory
%pre
/usr/bin/curl -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'X-OpenStack-Ironic-API-Version: 1.72' -d '{"callback_url": "", "agent_token": "{{ ks_options.agent_token }}", "agent_status": "start", "agent_status_message": "Deployment starting. Running pre-installation scripts."}' {{ ks_options.heartbeat_url }}
%end
@@ -27,10 +27,6 @@ liveimg --url {{ ks_options.liveimg_url }}
/usr/bin/curl -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'X-OpenStack-Ironic-API-Version: 1.72' -d '{"callback_url": "", "agent_token": "{{ ks_options.agent_token }}", "agent_status": "error", "agent_status_message": "Error: Deploying using anaconda. Check console for more information."}' {{ ks_options.heartbeat_url }}
%end
-%traceback
-/usr/bin/curl -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'X-OpenStack-Ironic-API-Version: 1.72' -d '{"callback_url": "", "agent_token": "{{ ks_options.agent_token }}", "agent_status": "error", "agent_status_message": "Error: Installer crashed unexpectedly."}' {{ ks_options.heartbeat_url }}
-%end
-
# Sending callback after the installation is mandatory
%post
/usr/bin/curl -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'X-OpenStack-Ironic-API-Version: 1.72' -d '{"callback_url": "", "agent_token": "{{ ks_options.agent_token }}", "agent_status": "end", "agent_status_message": "Deployment completed successfully."}' {{ ks_options.heartbeat_url }}
diff --git a/ironic/drivers/modules/pxe.py b/ironic/drivers/modules/pxe.py
index f3172b150..9df7354a5 100644
--- a/ironic/drivers/modules/pxe.py
+++ b/ironic/drivers/modules/pxe.py
@@ -78,6 +78,9 @@ class PXEAnacondaDeploy(agent_base.AgentBaseMixin, agent_base.HeartbeatMixin,
# NOTE(TheJulia): If this was any other interface, we would
# unconfigure tenant networks, add provisioning networks, etc.
task.driver.storage.attach_volumes(task)
+ node.instance_info = deploy_utils.build_instance_info_for_deploy(
+ task)
+ node.save()
if node.provision_state in (states.ACTIVE, states.UNRESCUING):
# In the event of takeover or unrescue.
task.driver.boot.prepare_instance(task)
@@ -116,13 +119,13 @@ class PXEAnacondaDeploy(agent_base.AgentBaseMixin, agent_base.HeartbeatMixin,
agent_base.log_and_raise_deployment_error(task, msg)
try:
+ task.process_event('resume')
self.clean_up(task)
manager_utils.node_power_action(task, states.POWER_OFF)
task.driver.network.remove_provisioning_network(task)
task.driver.network.configure_tenant_networks(task)
manager_utils.node_power_action(task, states.POWER_ON)
- node.provision_state = states.ACTIVE
- node.save()
+ task.process_event('done')
except Exception as e:
msg = (_('Error rebooting node %(node)s after deploy. '
'Error: %(error)s') %
diff --git a/ironic/tests/unit/drivers/modules/test_pxe.py b/ironic/tests/unit/drivers/modules/test_pxe.py
index f57a0105b..fecc0b374 100644
--- a/ironic/tests/unit/drivers/modules/test_pxe.py
+++ b/ironic/tests/unit/drivers/modules/test_pxe.py
@@ -895,15 +895,23 @@ class PXEAnacondaDeployTestCase(db_base.DbTestCase):
mock_prepare_ks_config.assert_called_once_with(task, image_info,
anaconda_boot=True)
+ @mock.patch.object(deploy_utils, 'build_instance_info_for_deploy',
+ autospec=True)
@mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True)
- def test_prepare(self, mock_prepare_instance):
+ def test_prepare(self, mock_prepare_instance, mock_build_instance):
+
node = self.node
node.provision_state = states.DEPLOYING
node.instance_info = {}
node.save()
+ updated_instance_info = {'image_url': 'foo'}
+ mock_build_instance.return_value = updated_instance_info
with task_manager.acquire(self.context, node.uuid) as task:
task.driver.deploy.prepare(task)
self.assertFalse(mock_prepare_instance.called)
+ mock_build_instance.assert_called_once_with(task)
+ node.refresh()
+ self.assertEqual(updated_instance_info, node.instance_info)
@mock.patch.object(pxe.PXEBoot, 'prepare_instance', autospec=True)
def test_prepare_active(self, mock_prepare_instance):