Update enroll-init logic to allow retry

This update enables enroll-init to re-run even after a prior success,
providing support for oam/passowrd reconfiguration following failures
in later enrollment stages. Changes include:

- Cloud-init cleanup is now deferred until the end of enrollment,
  enabling flexibility in the enroll-init step.
- A new RVMC mode, 'eject_image_only', has been added to strictly eject
  an inserted image. This is invoked as part of the enroll-init playbook
  to eject the seed ISO, preventing cloud-config from being reapplied
  during reboots. This is necessary since cloud-init remains enabled
  until the enrollment concludes.

Additionally, these changes have been made:
- Replace incorrect admin_password with 'sysadmin_password' from the
  payload to reconfigure the sysadmin password.
- Utilize the enroll-init playbook, separated from the install playbook
  (see dependent changes).

Background:
Cloud-init services were originally cleaned up immediately after a
successful enroll-init (OAM and password reconfiguration) to:
- Align with default cloud-init behavior: configuration applied once
  per instance.
- Prevent unintended conflicts during the enrollment process with
  'run-always' modules, such as an inserted seed ISO retriggering
  reconfiguration during later reboots.

However, this caused a limitation where, if enrollment failed after a
successful enroll-init, retries would skip enroll-init, necessitating
manual intervention for oam/password if needed.

Test Plan:
1. PASS: Verify enrollment without the admin field in the bootstrap
         values. Ensure semantic checks, and confirm
         'sysadmin_password' is used for password reconfiguration.

2. PASS: Mount and validate the contents of the generated seed ISO.
         The cloud-config should no longer specify a cleanup script.

3. PASS: Run a remote install:
         Validate RVMC script output. The script should execute as
         before, the new code path should not be reached.

4. PASS: Validate enroll-init retries:
         - Induce failure in the enroll-init step.
         - Simulate a later stage enrollment failure after
           a successful enroll-init.

         Ensure OAM and password can be reconfigured during retries
         in all scenarios.

5. PASS: Verify that cloud-init remains enabled until the end of
         enrollment.

Story: 2011100
Task: 51363

Depends-On: https://review.opendev.org/c/starlingx/ansible-playbooks/+/935519

Change-Id: I65bf2a28638c75930ba3f71f33267e42fb449b6c
Signed-off-by: Salman Rana <salman.rana@windriver.com>
This commit is contained in:
Salman Rana 2024-11-18 08:48:43 -05:00
parent bdb6275ee2
commit ba5f718276
6 changed files with 49 additions and 16 deletions

View File

@ -266,7 +266,7 @@ MANDATORY_ENROLL_INIT_VALUES = [
"external_oam_subnet",
"external_oam_gateway_address",
"external_oam_floating_address",
"admin_password",
"sysadmin_password",
"system_mode",
"software_version",
] + BMC_INSTALL_VALUES
@ -309,6 +309,10 @@ ANSIBLE_SUBCLOUD_INSTALL_PLAYBOOK = (
ENROLL_INIT_SEED_ISO_NAME = "seed.iso"
ANSIBLE_SUBCLOUD_ENROLL_INIT_PLAYBOOK = (
"/usr/share/ansible/stx-ansible/playbooks/enroll_init.yml"
)
ANSIBLE_SUBCLOUD_ENROLL_PLAYBOOK = (
"/usr/share/ansible/stx-ansible/playbooks/enroll_subcloud.yml"
)

View File

@ -1747,6 +1747,24 @@ class VmcObject(object):
self.redfish_obj.logout()
self.logging_util.dlog1("Session : Closed")
def eject_image_only(self):
"""Eject image only without any other iso related operations."""
self._redfish_client_connect()
self._redfish_root_query()
self._redfish_create_session()
self._redfish_get_managers()
self._redfish_get_systems_members()
self._redfish_get_vm_url()
self._redfish_load_vm_actions()
self._redfish_eject_image()
self.logging_util.ilog("Done")
if self.redfish_obj is not None and self.session is True:
self.redfish_obj.logout()
self.logging_util.dlog1("Session : Closed")
##############################################################################
# Methods to be called from rvmc module

View File

@ -153,12 +153,11 @@ class SubcloudEnrollmentInit(object):
raise exceptions.EnrollInitExecutionFailed(reason=msg)
hashed_password = crypt.crypt(
iso_values["admin_password"], crypt.mksalt(crypt.METHOD_SHA512)
iso_values["sysadmin_password"], crypt.mksalt(crypt.METHOD_SHA512)
)
enroll_utils = "/usr/local/bin/"
reconfig_script = os.path.join(enroll_utils, "enroll-init-reconfigure")
cleanup_script = os.path.join(enroll_utils, "enroll-init-cleanup")
extern_oam_gw_ip = iso_values["external_oam_gateway_address"].split(",")[0]
reconfig_command = (
@ -177,10 +176,7 @@ class SubcloudEnrollmentInit(object):
f"{iso_values['external_oam_node_1_address'].split(',')[0]}"
)
runcmd = [
reconfig_command,
cleanup_script,
]
runcmd = [reconfig_command]
user_data_file = os.path.join(path, "user-data")
with open(user_data_file, "w") as f_out_user_data_file:

View File

@ -351,7 +351,7 @@ class SubcloudManager(manager.Manager):
if state == "init":
enroll_command = [
"ansible-playbook",
dccommon_consts.ANSIBLE_SUBCLOUD_INSTALL_PLAYBOOK,
dccommon_consts.ANSIBLE_SUBCLOUD_ENROLL_INIT_PLAYBOOK,
"-i",
ansible_subcloud_inventory_file,
"--limit",

View File

@ -4924,7 +4924,7 @@ class TestSubcloudEnrollment(BaseTestSubcloudManager):
self.iso_values = {
"software_version": self.rel_version,
"admin_password": "St8rlingX*",
"sysadmin_password": "St8rlingX*",
"bootstrap_interface": "enp2s1",
"external_oam_floating_address": "10.10.10.2",
"network_mask": "255.255.255.0",
@ -4986,7 +4986,7 @@ class TestSubcloudEnrollment(BaseTestSubcloudManager):
# Test with incomplete iso_values, expect KeyError
copied_dict = self.iso_values.copy()
copied_dict.pop("admin_password")
copied_dict.pop("sysadmin_password")
self.assertRaises(
KeyError,

View File

@ -170,6 +170,12 @@ def parse_arguments():
"--config_file", type=str, required=True, help="RVMC config file"
)
parser.add_argument(
"--eject_image_only",
action="store_true",
help="Optional execution mode to strictly eject an inserted image",
)
return parser.parse_args()
@ -305,13 +311,22 @@ if __name__ == "__main__":
logging_util.ilog("BMC IP Addr : %s" % target_object.ip)
logging_util.ilog("Host Image : %s" % target_object.img)
excluded_operations = []
if os.path.basename(target_object.img) == consts.ENROLL_INIT_SEED_ISO_NAME:
# If the host image is a seed ISO,
# the boot order should not be changed.
excluded_operations = ["set_boot_override"]
if args.eject_image_only:
target_object.eject_image_only()
else:
excluded_operations = []
# TODO(srana): Decouple this from ENROLL_INIT_SEED_ISO_NAME.
# We should generalize the approach and pass the excluded operations
# to the script for enrollment.
if (
os.path.basename(target_object.img)
== consts.ENROLL_INIT_SEED_ISO_NAME
):
# If the host image is a seed ISO,
# the boot order should not be changed.
excluded_operations = ["set_boot_override"]
target_object.execute(excluded_operations)
target_object.execute(excluded_operations)
except eventlet.timeout.Timeout as e:
if e is not script_timeout:
raise