Merge "Prestaging support for --for-sw-deploy/--for-install"

This commit is contained in:
Zuul
2024-06-21 23:03:00 +00:00
committed by Gerrit Code Review
13 changed files with 286 additions and 53 deletions

View File

@@ -216,3 +216,6 @@ ANSIBLE_SUBCLOUD_ENROLL_PLAYBOOK = \
# Sysinv client default timeout # Sysinv client default timeout
SYSINV_CLIENT_REST_DEFAULT_TIMEOUT = 600 SYSINV_CLIENT_REST_DEFAULT_TIMEOUT = 600
SUBCLOUD_ISO_PATH = '/opt/platform/iso'
SUBCLOUD_FEED_PATH = '/var/www/pages/feed'

View File

@@ -8,6 +8,7 @@ import os
from oslo_log import log as logging from oslo_log import log as logging
import sh import sh
from dccommon import consts
from dcmanager.common import utils from dcmanager.common import utils
# The 'sh' library is magical - it looks up CLI functions dynamically. # The 'sh' library is magical - it looks up CLI functions dynamically.
@@ -53,15 +54,21 @@ def check_stale_bind_mount(mount_path, source_path):
# TODO(kmacleod): utils.synchronized should be moved into dccommon # TODO(kmacleod): utils.synchronized should be moved into dccommon
@utils.synchronized("ostree-mount-subclouds", external=True) @utils.synchronized("ostree-mount-subclouds", external=True)
def validate_ostree_iso_mount(www_iso_root, source_path): def validate_ostree_iso_mount(software_version):
"""Ensure the ostree_repo is properly mounted under the iso path. """Ensure the ostree_repo is properly mounted under the iso path.
Validity check includes if the mount is stale. Validity check includes if the mount is stale.
If stale, the bind mount is recreated. If stale, the bind mount is recreated.
Note that ostree_repo is mounted in a location not specific to a subcloud. Note that ostree_repo is mounted in a location not specific to a subcloud.
""" """
ostree_repo_mount_path = os.path.join(www_iso_root, "ostree_repo") ostree_repo_mount_path = os.path.join(
ostree_repo_source_path = os.path.join(source_path, "ostree_repo") consts.SUBCLOUD_ISO_PATH, software_version, "ostree_repo"
)
ostree_repo_source_path = os.path.join(
consts.SUBCLOUD_FEED_PATH,
"rel-{version}".format(version=software_version),
"ostree_repo",
)
LOG.debug( LOG.debug(
"Checking ostree_repo mount: %s against %s", "Checking ostree_repo mount: %s against %s",
ostree_repo_mount_path, ostree_repo_mount_path,
@@ -78,7 +85,7 @@ def validate_ostree_iso_mount(www_iso_root, source_path):
os.makedirs(ostree_repo_mount_path, mode=0o755) os.makedirs(ostree_repo_mount_path, mode=0o755)
mount_args = ( mount_args = (
"--bind", "--bind",
"%s/ostree_repo" % source_path, ostree_repo_source_path,
ostree_repo_mount_path, ostree_repo_mount_path,
) )
try: try:

View File

@@ -36,6 +36,7 @@ from dccommon.drivers.openstack.sysinv_v1 import SysinvClient
from dccommon import exceptions from dccommon import exceptions
from dccommon import ostree_mount from dccommon import ostree_mount
from dccommon import utils as dccommon_utils from dccommon import utils as dccommon_utils
from dcmanager.common import consts as dcmanager_consts from dcmanager.common import consts as dcmanager_consts
from dcmanager.common import utils from dcmanager.common import utils
@@ -45,9 +46,7 @@ LOG = logging.getLogger(__name__)
CONF = cfg.CONF CONF = cfg.CONF
BOOT_MENU_TIMEOUT = '5' BOOT_MENU_TIMEOUT = '5'
SUBCLOUD_ISO_PATH = '/opt/platform/iso'
SUBCLOUD_ISO_DOWNLOAD_PATH = '/var/www/pages/iso' SUBCLOUD_ISO_DOWNLOAD_PATH = '/var/www/pages/iso'
SUBCLOUD_FEED_PATH = '/var/www/pages/feed'
DCVAULT_BOOTIMAGE_PATH = '/opt/dc-vault/loads/' DCVAULT_BOOTIMAGE_PATH = '/opt/dc-vault/loads/'
PACKAGE_LIST_PATH = '/usr/local/share/pkg-list' PACKAGE_LIST_PATH = '/usr/local/share/pkg-list'
GEN_ISO_COMMAND = '/usr/local/bin/gen-bootloader-iso.sh' GEN_ISO_COMMAND = '/usr/local/bin/gen-bootloader-iso.sh'
@@ -510,16 +509,10 @@ class SubcloudInstall(object):
if not os.path.isdir(override_path): if not os.path.isdir(override_path):
os.mkdir(override_path, 0o755) os.mkdir(override_path, 0o755)
self.www_iso_root = os.path.join(SUBCLOUD_ISO_PATH, software_version) self.www_iso_root = os.path.join(consts.SUBCLOUD_ISO_PATH, software_version)
feed_path_rel_version = os.path.join(SUBCLOUD_FEED_PATH,
"rel-{version}".format(
version=software_version))
if dccommon_utils.is_debian(software_version): if dccommon_utils.is_debian(software_version):
ostree_mount.validate_ostree_iso_mount( ostree_mount.validate_ostree_iso_mount(software_version)
self.www_iso_root, feed_path_rel_version
)
# Clean up iso directory if it already exists # Clean up iso directory if it already exists
# This may happen if a previous installation attempt was abruptly # This may happen if a previous installation attempt was abruptly
@@ -571,7 +564,7 @@ class SubcloudInstall(object):
self.name, self.name,
software_version + "_packages_list.txt") software_version + "_packages_list.txt")
pkg_file_src = os.path.join(SUBCLOUD_FEED_PATH, pkg_file_src = os.path.join(consts.SUBCLOUD_FEED_PATH,
"rel-{version}".format( "rel-{version}".format(
version=software_version), version=software_version),
'package_checksums') 'package_checksums')

View File

@@ -12,6 +12,7 @@ from oslo_messaging import RemoteError
import pecan import pecan
from dcmanager.api.controllers import restcomm from dcmanager.api.controllers import restcomm
from dcmanager.api.controllers.v1.subclouds import SubcloudsController
from dcmanager.api.policies import phased_subcloud_deploy as \ from dcmanager.api.policies import phased_subcloud_deploy as \
phased_subcloud_deploy_policy phased_subcloud_deploy_policy
from dcmanager.api import policy from dcmanager.api import policy
@@ -210,6 +211,8 @@ class PhasedSubcloudDeployController(object):
if not payload: if not payload:
pecan.abort(400, _('Body required')) pecan.abort(400, _('Body required'))
SubcloudsController.validate_software_deploy_state()
if subcloud.deploy_status not in VALID_STATES_FOR_DEPLOY_INSTALL: if subcloud.deploy_status not in VALID_STATES_FOR_DEPLOY_INSTALL:
allowed_states_str = ', '.join(VALID_STATES_FOR_DEPLOY_INSTALL) allowed_states_str = ', '.join(VALID_STATES_FOR_DEPLOY_INSTALL)
pecan.abort(400, _('Subcloud deploy status must be either: %s') pecan.abort(400, _('Subcloud deploy status must be either: %s')
@@ -471,6 +474,7 @@ class PhasedSubcloudDeployController(object):
# Consider the incoming release parameter only if install is one # Consider the incoming release parameter only if install is one
# of the pending deploy states # of the pending deploy states
if INSTALL in deploy_states_to_run: if INSTALL in deploy_states_to_run:
SubcloudsController.validate_software_deploy_state()
unvalidated_sw_version = \ unvalidated_sw_version = \
payload.get('release', subcloud.software_version) payload.get('release', subcloud.software_version)
else: else:

View File

@@ -44,6 +44,7 @@ from dccommon.drivers.openstack.fm import FmClient
from dccommon.drivers.openstack.sdk_platform import ( from dccommon.drivers.openstack.sdk_platform import (
OptimizedOpenStackDriver as OpenStackDriver OptimizedOpenStackDriver as OpenStackDriver
) )
from dccommon.drivers.openstack import software_v1
from dccommon.drivers.openstack.sysinv_v1 import SysinvClient from dccommon.drivers.openstack.sysinv_v1 import SysinvClient
from dccommon.drivers.openstack import vim from dccommon.drivers.openstack import vim
from dccommon import exceptions as dccommon_exceptions from dccommon import exceptions as dccommon_exceptions
@@ -129,36 +130,54 @@ class SubcloudsController(object):
@staticmethod @staticmethod
def _get_prestage_payload(request): def _get_prestage_payload(request):
fields = ['sysadmin_password', 'force', consts.PRESTAGE_REQUEST_RELEASE] fields = [
payload = { "sysadmin_password",
'force': False "force",
} consts.PRESTAGE_REQUEST_RELEASE,
consts.PRESTAGE_FOR_INSTALL,
consts.PRESTAGE_FOR_SW_DEPLOY,
]
payload = {"force": False}
try: try:
body = json.loads(request.body) body = json.loads(request.body)
except Exception: except Exception:
pecan.abort(400, _('Request body is malformed.')) pecan.abort(400, _("Request body is malformed."))
for field in fields: for field in fields:
val = body.get(field) val = body.get(field)
if val is None: if val is None:
if field == 'sysadmin_password': if field == "sysadmin_password":
pecan.abort(400, _("%s is required." % field)) pecan.abort(400, _("%s is required." % field))
else: else:
if field == 'sysadmin_password': if field == "sysadmin_password":
try: try:
base64.b64decode(val).decode('utf-8') base64.b64decode(val).decode("utf-8")
payload['sysadmin_password'] = val payload["sysadmin_password"] = val
except Exception: except Exception:
pecan.abort( pecan.abort(
400, 400,
_('Failed to decode subcloud sysadmin_password, ' _(
'verify the password is base64 encoded')) "Failed to decode subcloud sysadmin_password, "
elif field == 'force': "verify the password is base64 encoded"
if val.lower() in ('true', 'false', 't', 'f'): ),
payload['force'] = val.lower() in ('true', 't') )
elif field == "force":
if val.lower() in ("true", "false", "t", "f"):
payload["force"] = val.lower() in ("true", "t")
else: else:
pecan.abort( pecan.abort(
400, _('Invalid value for force option: %s' % val)) 400, _("Invalid value for force option: %s" % val)
)
elif field in (
consts.PRESTAGE_FOR_INSTALL,
consts.PRESTAGE_FOR_SW_DEPLOY
):
if val.lower() in ("true", "false", "t", "f"):
payload[field] = val.lower() in ("true", "t")
else:
errmsg = f"Invalid value for {field} option: {val}"
pecan.abort(400, _(errmsg))
elif field == consts.PRESTAGE_REQUEST_RELEASE: elif field == consts.PRESTAGE_REQUEST_RELEASE:
payload[consts.PRESTAGE_REQUEST_RELEASE] = val payload[consts.PRESTAGE_REQUEST_RELEASE] = val
return payload return payload
@@ -522,6 +541,53 @@ class SubcloudsController(object):
subcloud_dict.update(extra_details) subcloud_dict.update(extra_details)
return subcloud_dict return subcloud_dict
@staticmethod
def is_valid_software_deploy_state():
try:
m_os_ks_client = OpenStackDriver(
region_name=dccommon_consts.DEFAULT_REGION_NAME, region_clients=None
).keystone_client
software_endpoint = m_os_ks_client.endpoint_cache.get_endpoint(
dccommon_consts.ENDPOINT_TYPE_SOFTWARE
)
software_client = software_v1.SoftwareClient(
dccommon_consts.DEFAULT_REGION_NAME,
m_os_ks_client.session,
endpoint=software_endpoint,
)
software_list = software_client.list()
for release in software_list:
if release["state"] not in (
software_v1.AVAILABLE,
software_v1.COMMITTED,
software_v1.DEPLOYED,
software_v1.UNAVAILABLE,
):
LOG.info(
"is_valid_software_deploy_state, not valid for: %s",
software_list
)
return False
LOG.debug("is_valid_software_deploy_state, valid: %s", software_list)
except Exception:
LOG.exception("Failure initializing OS Client, disallowing.")
return False
return True
@staticmethod
def validate_software_deploy_state():
if not SubcloudsController.is_valid_software_deploy_state():
pecan.abort(
400,
_(
"A local software deployment operation is in progress. "
"Please finish the software deployment operation before "
"(re)installing/updating the subcloud."
),
)
@utils.synchronized(LOCK_NAME) @utils.synchronized(LOCK_NAME)
@index.when(method='POST', template='json') @index.when(method='POST', template='json')
def post(self): def post(self):
@@ -531,6 +597,18 @@ class SubcloudsController(object):
restcomm.extract_credentials_for_policy()) restcomm.extract_credentials_for_policy())
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
self.validate_software_deploy_state()
if not SubcloudsController.is_valid_software_deploy_state():
pecan.abort(
400,
_(
"A local software deployment operation is in progress. "
"Please finish the software deployment opearation before "
"(re)installing/updating the subcloud."
),
)
bootstrap_sc_name = psd_common.get_bootstrap_subcloud_name(request) bootstrap_sc_name = psd_common.get_bootstrap_subcloud_name(request)
payload = psd_common.get_request_data(request, None, payload = psd_common.get_request_data(request, None,
@@ -1003,6 +1081,7 @@ class SubcloudsController(object):
if utils.subcloud_is_secondary_state(subcloud.deploy_status): if utils.subcloud_is_secondary_state(subcloud.deploy_status):
pecan.abort(500, _("Cannot perform on %s " pecan.abort(500, _("Cannot perform on %s "
"state subcloud" % subcloud.deploy_status)) "state subcloud" % subcloud.deploy_status))
self.validate_software_deploy_state()
config_file = psd_common.get_config_file_path(subcloud.name, config_file = psd_common.get_config_file_path(subcloud.name,
consts.DEPLOY_CONFIG) consts.DEPLOY_CONFIG)
has_bootstrap_values = consts.BOOTSTRAP_VALUES in request.POST has_bootstrap_values = consts.BOOTSTRAP_VALUES in request.POST
@@ -1106,8 +1185,11 @@ class SubcloudsController(object):
LOG.exception("validate_prestage failed") LOG.exception("validate_prestage failed")
pecan.abort(400, _(str(exc))) pecan.abort(400, _(str(exc)))
for_install = True
if consts.PRESTAGE_FOR_SW_DEPLOY in payload:
for_install = False
prestage_software_version = utils.get_sw_version( prestage_software_version = utils.get_sw_version(
payload.get(consts.PRESTAGE_REQUEST_RELEASE)) payload.get(consts.PRESTAGE_REQUEST_RELEASE), for_install)
try: try:
self.dcmanager_rpc_client.prestage_subcloud(context, payload) self.dcmanager_rpc_client.prestage_subcloud(context, payload)

View File

@@ -399,6 +399,8 @@ EXTRA_ARGS_RELEASE = 'release'
# http request/response arguments for prestage # http request/response arguments for prestage
PRESTAGE_SOFTWARE_VERSION = 'prestage-software-version' PRESTAGE_SOFTWARE_VERSION = 'prestage-software-version'
PRESTAGE_REQUEST_RELEASE = 'release' PRESTAGE_REQUEST_RELEASE = 'release'
PRESTAGE_FOR_INSTALL = 'for_install'
PRESTAGE_FOR_SW_DEPLOY = 'for_sw_deploy'
# Device Image Bitstream Types # Device Image Bitstream Types
BITSTREAM_TYPE_ROOT_KEY = 'root-key' BITSTREAM_TYPE_ROOT_KEY = 'root-key'

View File

@@ -36,6 +36,7 @@ from dccommon.drivers.openstack.sdk_platform import (
from dccommon.drivers.openstack.sysinv_v1 import SysinvClient from dccommon.drivers.openstack.sysinv_v1 import SysinvClient
from dccommon.exceptions import PlaybookExecutionFailed from dccommon.exceptions import PlaybookExecutionFailed
from dccommon.exceptions import PlaybookExecutionTimeout from dccommon.exceptions import PlaybookExecutionTimeout
from dccommon import ostree_mount
from dccommon.utils import AnsiblePlaybook from dccommon.utils import AnsiblePlaybook
from dcmanager.common import consts from dcmanager.common import consts
@@ -110,7 +111,12 @@ def global_prestage_validate(payload):
" Details: %s" % ex) " Details: %s" % ex)
def initial_subcloud_validate(subcloud, installed_loads, software_version): def initial_subcloud_validate(
subcloud,
installed_loads,
software_major_release, # format: YY.MM
for_sw_deploy,
):
"""Basic validation a subcloud prestage operation. """Basic validation a subcloud prestage operation.
Raises a PrestageCheckFailedException on failure. Raises a PrestageCheckFailedException on failure.
@@ -158,14 +164,14 @@ def initial_subcloud_validate(subcloud, installed_loads, software_version):
# The request software version must be either the same as the software version # The request software version must be either the same as the software version
# of the subcloud or any active/inactive/imported load on the system controller # of the subcloud or any active/inactive/imported load on the system controller
# (can be checked with "system load-list" command). # (can be checked with "system load-list" command).
if software_version and \ if not for_sw_deploy and software_major_release and \
software_version != subcloud.software_version and \ software_major_release != subcloud.software_version and \
software_version not in installed_loads: software_major_release not in installed_loads:
raise exceptions.PrestagePreCheckFailedException( raise exceptions.PrestagePreCheckFailedException(
subcloud=subcloud.name, subcloud=subcloud.name,
orch_skip=True, orch_skip=True,
details="Specified release is not supported. " details="Specified release is not supported. "
"%s version must first be imported" % software_version) f"{software_major_release} version must first be imported")
def validate_prestage(subcloud, payload): def validate_prestage(subcloud, payload):
@@ -185,12 +191,21 @@ def validate_prestage(subcloud, payload):
installed_loads = [] installed_loads = []
software_version = None software_version = None
software_major_release = None
if payload.get(consts.PRESTAGE_REQUEST_RELEASE): if payload.get(consts.PRESTAGE_REQUEST_RELEASE):
software_version = payload.get(consts.PRESTAGE_REQUEST_RELEASE) software_version = payload.get(consts.PRESTAGE_REQUEST_RELEASE)
software_major_release = utils.get_major_release(software_version)
installed_loads = utils.get_systemcontroller_installed_loads() installed_loads = utils.get_systemcontroller_installed_loads()
for_sw_deploy = is_prestage_for_sw_deploy(payload)
# re-run the initial validation # re-run the initial validation
initial_subcloud_validate(subcloud, installed_loads, software_version) initial_subcloud_validate(
subcloud,
installed_loads,
software_major_release,
for_sw_deploy,
)
subcloud_type, system_health, oam_floating_ip = \ subcloud_type, system_health, oam_floating_ip = \
_get_prestage_subcloud_info(subcloud) _get_prestage_subcloud_info(subcloud)
@@ -238,6 +253,13 @@ def is_local(subcloud_version, specified_version):
return subcloud_version == specified_version return subcloud_version == specified_version
def is_prestage_for_sw_deploy(payload):
# The default is for_install, so we can simply check if the payload is
# tagged with for_sw_deploy
for_sw_deploy = payload.get(consts.PRESTAGE_FOR_SW_DEPLOY, False)
return for_sw_deploy
def prestage_subcloud(context, payload): def prestage_subcloud(context, payload):
"""Subcloud prestaging """Subcloud prestaging
@@ -253,8 +275,11 @@ def prestage_subcloud(context, payload):
- run prestage_images.yml ansible playbook - run prestage_images.yml ansible playbook
""" """
subcloud_name = payload['subcloud_name'] subcloud_name = payload['subcloud_name']
LOG.info("Prestaging subcloud: %s, force=%s" % (subcloud_name, for_sw_deploy = is_prestage_for_sw_deploy(payload)
payload['force'])) LOG.info(
f"Prestaging subcloud: {subcloud_name}, "
f"force={payload['force']}, for_sw_deploy={for_sw_deploy}"
)
try: try:
subcloud = db_api.subcloud_get_by_name(context, subcloud_name) subcloud = db_api.subcloud_get_by_name(context, subcloud_name)
except exceptions.SubcloudNameNotFound: except exceptions.SubcloudNameNotFound:
@@ -282,6 +307,7 @@ def prestage_subcloud(context, payload):
def _prestage_standalone_thread(context, subcloud, payload): def _prestage_standalone_thread(context, subcloud, payload):
"""Run the prestage operations inside a separate thread""" """Run the prestage operations inside a separate thread"""
log_file = utils.get_subcloud_ansible_log_file(subcloud.name) log_file = utils.get_subcloud_ansible_log_file(subcloud.name)
for_sw_deploy = is_prestage_for_sw_deploy(payload)
try: try:
prestage_packages(context, subcloud, payload) prestage_packages(context, subcloud, payload)
# Get the prestage versions from the logs generated by # Get the prestage versions from the logs generated by
@@ -289,11 +315,17 @@ def _prestage_standalone_thread(context, subcloud, payload):
prestage_versions = utils.get_msg_output_info( prestage_versions = utils.get_msg_output_info(
log_file, PRINT_PRESTAGE_VERSIONS_TASK, PRESTAGE_VERSIONS_KEY_STR) log_file, PRINT_PRESTAGE_VERSIONS_TASK, PRESTAGE_VERSIONS_KEY_STR)
prestage_images(context, subcloud, payload) # TODO(kmacleod) need to invoke this for retagging images
if not for_sw_deploy:
prestage_images(context, subcloud, payload)
prestage_complete(context, subcloud.id, prestage_versions) prestage_complete(context, subcloud.id, prestage_versions)
LOG.info("Prestage complete: %s", subcloud.name) LOG.info("Prestage complete: %s", subcloud.name)
except Exception: except Exception:
LOG.exception(
f"Subcloud prestaging failed (in standalone thread) {subcloud.name}"
)
prestage_fail(context, subcloud.id) prestage_fail(context, subcloud.id)
raise raise
@@ -382,8 +414,24 @@ def prestage_packages(context, subcloud, payload):
ANSIBLE_PRESTAGE_INVENTORY_SUFFIX) ANSIBLE_PRESTAGE_INVENTORY_SUFFIX)
prestage_software_version = payload.get( prestage_software_version = payload.get(
consts.PRESTAGE_REQUEST_RELEASE, SW_VERSION) consts.PRESTAGE_REQUEST_RELEASE, SW_VERSION
extra_vars_str = "software_version=%s" % prestage_software_version )
prestage_major_release = utils.get_major_release(prestage_software_version)
extra_vars_str = f"software_version={prestage_software_version} "
extra_vars_str += f"software_major_release={prestage_major_release}"
if is_prestage_for_sw_deploy(payload):
extra_vars_str += (
f" prestage_install={consts.PRESTAGE_FOR_SW_DEPLOY}"
)
else:
# default
extra_vars_str += (
f" prestage_install={consts.PRESTAGE_FOR_INSTALL}"
)
ostree_mount.validate_ostree_iso_mount(prestage_major_release)
_run_ansible(context, _run_ansible(context,
["ansible-playbook", ["ansible-playbook",
ANSIBLE_PRESTAGE_SUBCLOUD_PACKAGES_PLAYBOOK, ANSIBLE_PRESTAGE_SUBCLOUD_PACKAGES_PLAYBOOK,
@@ -414,10 +462,21 @@ def prestage_images(context, subcloud, payload):
""" """
prestage_software_version = payload.get( prestage_software_version = payload.get(
consts.PRESTAGE_REQUEST_RELEASE, SW_VERSION) consts.PRESTAGE_REQUEST_RELEASE, SW_VERSION)
extra_vars_str = "software_version=%s" % prestage_software_version prestage_major_release = utils.get_major_release(
prestage_software_version
)
extra_vars_str = f"software_version={prestage_software_version} "
extra_vars_str += f"software_major_release={prestage_major_release}"
# TODO(kmacleod) we may not need these if images are not prestaged
# if for_sw_deploy
if consts.PRESTAGE_FOR_INSTALL in payload:
extra_vars_str += f" for_install={payload[consts.PRESTAGE_FOR_INSTALL]}"
elif consts.PRESTAGE_FOR_SW_DEPLOY in payload:
extra_vars_str += f" for_sw_deploy={payload[consts.PRESTAGE_FOR_SW_DEPLOY]}"
image_list_filename = None image_list_filename = None
deploy_dir = os.path.join(DEPLOY_BASE_DIR, prestage_software_version) deploy_dir = os.path.join(DEPLOY_BASE_DIR, prestage_major_release)
if os.path.isdir(deploy_dir): if os.path.isdir(deploy_dir):
image_list_filename = utils.get_filename_by_prefix(deploy_dir, image_list_filename = utils.get_filename_by_prefix(deploy_dir,
'prestage_images') 'prestage_images')
@@ -428,14 +487,14 @@ def prestage_images(context, subcloud, payload):
LOG.debug("prestage images list file: %s", image_list_file) LOG.debug("prestage images list file: %s", image_list_file)
else: else:
LOG.debug("prestage images list file does not exist") LOG.debug("prestage images list file does not exist")
if prestage_software_version != subcloud.software_version: if prestage_major_release != subcloud.software_version:
# Prestage source is remote but there is no images list file so # Prestage source is remote but there is no images list file so
# skip the images prestage. # skip the images prestage.
LOG.info("Images prestage is skipped for %s as the prestage images " LOG.info("Images prestage is skipped for %s as the prestage images "
"list for release %s has not been uploaded and the " "list for release %s has not been uploaded and the "
"subcloud is running a different load than %s." "subcloud is running a different load than %s."
% (subcloud.name, prestage_software_version, % (subcloud.name, prestage_major_release,
prestage_software_version)) prestage_major_release))
return return
# Ansible inventory filename for the specified subcloud # Ansible inventory filename for the specified subcloud

View File

@@ -1406,7 +1406,7 @@ def create_subcloud_rehome_data_template():
return {'saved_payload': {}} return {'saved_payload': {}}
def get_sw_version(release=None): def get_sw_version(release=None, for_install=True):
"""Get the sw_version to be used. """Get the sw_version to be used.
Return the sw_version by first validating a set release version. Return the sw_version by first validating a set release version.
@@ -1416,7 +1416,10 @@ def get_sw_version(release=None):
if release: if release:
try: try:
validate_release_version_supported(release) if for_install:
validate_major_release_version_supported(release)
else:
validate_minor_release_version_exists(release)
return release return release
except exceptions.ValidateFail as e: except exceptions.ValidateFail as e:
pecan.abort(400, pecan.abort(400,
@@ -1428,7 +1431,7 @@ def get_sw_version(release=None):
return tsc.SW_VERSION return tsc.SW_VERSION
def validate_release_version_supported(release_version_to_check): def validate_major_release_version_supported(release_version_to_check):
"""Check if a release version is supported by the current active version. """Check if a release version is supported by the current active version.
:param release_version_to_check: version string to validate :param release_version_to_check: version string to validate
@@ -1452,6 +1455,37 @@ def validate_release_version_supported(release_version_to_check):
return True return True
def is_major_release(version):
return not is_minor_release(version)
def is_minor_release(version):
split_version = version.split('.')
if len(split_version) == 2:
return False
if len(split_version) == 3:
if split_version[2] == '0':
return False
return True
LOG.error(f"Unexpected release version found: {version}, assuming major release")
return False
def get_major_release(version):
"""Returns the YY.MM portion of the given version string"""
split_version = version.split('.')
return '.'.join(split_version[0:2])
def validate_minor_release_version_exists(release_version_to_check):
# TODO(kmacleod): For minor releases (for_sw_deploy) we need to
# validate the given minor release
# This should lookup all the minor releases and validate the input version
# exists
LOG.warn("TODO: validate_minor_release_version_exists")
def get_current_supported_upgrade_versions(): def get_current_supported_upgrade_versions():
"""Parse the upgrades metadata file to build a list of supported versions. """Parse the upgrades metadata file to build a list of supported versions.

View File

@@ -340,9 +340,16 @@ class SwUpdateManager(manager.Manager):
patch_file = payload.get('patch') patch_file = payload.get('patch')
installed_loads = [] installed_loads = []
software_version = None software_version = None
software_major_release = None
for_sw_deploy = False
if payload.get(consts.PRESTAGE_REQUEST_RELEASE): if payload.get(consts.PRESTAGE_REQUEST_RELEASE):
software_version = payload.get(consts.PRESTAGE_REQUEST_RELEASE) software_version = payload.get(consts.PRESTAGE_REQUEST_RELEASE)
software_major_release = utils.get_major_release(software_version)
installed_loads = utils.get_systemcontroller_installed_loads() installed_loads = utils.get_systemcontroller_installed_loads()
# TODO(kmacleod): Hugo: we need to say whether this is a
# for-install or for-fw-deploy prestaging operation Setting this to
# a for-install operation for now (since that is the default)
for_sw_deploy = False
# Has the user specified a specific subcloud? # Has the user specified a specific subcloud?
# todo(abailey): refactor this code to use classes # todo(abailey): refactor this code to use classes
@@ -426,8 +433,13 @@ class SwUpdateManager(manager.Manager):
try: try:
prestage.global_prestage_validate(payload) prestage.global_prestage_validate(payload)
prestage_global_validated = True prestage_global_validated = True
installed_loads = utils.get_systemcontroller_installed_loads()
prestage.initial_subcloud_validate( prestage.initial_subcloud_validate(
subcloud, installed_loads, software_version) subcloud,
installed_loads,
software_major_release,
for_sw_deploy
)
except exceptions.PrestagePreCheckFailedException as ex: except exceptions.PrestagePreCheckFailedException as ex:
raise exceptions.BadRequest(resource='strategy', raise exceptions.BadRequest(resource='strategy',
msg=str(ex)) msg=str(ex))
@@ -597,7 +609,11 @@ class SwUpdateManager(manager.Manager):
# Do initial validation for subcloud # Do initial validation for subcloud
try: try:
prestage.initial_subcloud_validate( prestage.initial_subcloud_validate(
subcloud, installed_loads, software_version) subcloud,
installed_loads,
software_major_release,
for_sw_deploy,
)
except exceptions.PrestagePreCheckFailedException: except exceptions.PrestagePreCheckFailedException:
LOG.warn("Excluding subcloud from prestage strategy: %s", LOG.warn("Excluding subcloud from prestage strategy: %s",
subcloud.name) subcloud.name)

View File

@@ -31,6 +31,7 @@ from sqlalchemy.engine import Engine
from sqlalchemy import event from sqlalchemy import event
from dccommon.utils import AnsiblePlaybook from dccommon.utils import AnsiblePlaybook
from dcmanager.api.controllers.v1.subclouds import SubcloudsController
from dcmanager.audit import rpcapi from dcmanager.audit import rpcapi
from dcmanager.audit import subcloud_audit_manager from dcmanager.audit import subcloud_audit_manager
from dcmanager.common import consts from dcmanager.common import consts
@@ -382,3 +383,11 @@ class DCManagerTestCase(base.BaseTestCase):
mock_patch_object = mock.patch.object(AnsiblePlaybook, 'run_playbook') mock_patch_object = mock.patch.object(AnsiblePlaybook, 'run_playbook')
self.mock_ansible_run_playbook = mock_patch_object.start() self.mock_ansible_run_playbook = mock_patch_object.start()
self.addCleanup(mock_patch_object.stop) self.addCleanup(mock_patch_object.stop)
def _mock_valid_software_deploy_state(self, return_value=True):
mock_patch_object = mock.patch.object(
SubcloudsController, "is_valid_software_deploy_state"
)
self.mock_valid_software_deploy_state = mock_patch_object.start()
self.addCleanup(mock_patch_object.stop)
self.mock_valid_software_deploy_state.return_value = return_value

View File

@@ -14,6 +14,7 @@ from tsconfig.tsconfig import SW_VERSION
from dccommon import consts as dccommon_consts from dccommon import consts as dccommon_consts
from dcmanager.api.controllers.v1 import phased_subcloud_deploy as psd_api from dcmanager.api.controllers.v1 import phased_subcloud_deploy as psd_api
from dcmanager.api.controllers.v1.subclouds import SubcloudsController
from dcmanager.common import consts from dcmanager.common import consts
from dcmanager.common import phased_subcloud_deploy as psd_common from dcmanager.common import phased_subcloud_deploy as psd_common
from dcmanager.common import utils as dutils from dcmanager.common import utils as dutils
@@ -66,6 +67,13 @@ class BaseTestPhasedSubcloudDeployController(DCManagerApiTest):
self.mock_is_initial_deployment = mock_patch_object.start() self.mock_is_initial_deployment = mock_patch_object.start()
self.addCleanup(mock_patch_object.stop) self.addCleanup(mock_patch_object.stop)
def _mock_is_valid_software_deploy_state(self):
mock_patch_object = mock.patch.object(
SubcloudsController, "is_valid_software_deploy_state"
)
self.mock_is_valid_software_deploy_state = mock_patch_object.start()
self.addCleanup(mock_patch_object.stop)
class TestPhasedSubcloudDeployController(BaseTestPhasedSubcloudDeployController): class TestPhasedSubcloudDeployController(BaseTestPhasedSubcloudDeployController):
"""Test class for PhasedSubcloudDeployController""" """Test class for PhasedSubcloudDeployController"""
@@ -197,6 +205,7 @@ class BaseTestPhasedSubcloudDeployPatch(BaseTestPhasedSubcloudDeployController):
self._mock_get_vault_load_files() self._mock_get_vault_load_files()
self._mock_is_initial_deployment() self._mock_is_initial_deployment()
self._mock_is_valid_software_deploy_state()
self._mock_get_network_address_pool() self._mock_get_network_address_pool()
self.mock_get_vault_load_files.return_value = \ self.mock_get_vault_load_files.return_value = \

View File

@@ -230,6 +230,7 @@ class BaseTestSubcloudsController(DCManagerApiTest, SubcloudAPIMixin):
self._mock_rpc_subcloud_state_client() self._mock_rpc_subcloud_state_client()
self._mock_get_ks_client() self._mock_get_ks_client()
self._mock_query() self._mock_query()
self._mock_valid_software_deploy_state()
def _update_subcloud(self, **kwargs): def _update_subcloud(self, **kwargs):
self.subcloud = sql_api.subcloud_update(self.ctx, self.subcloud.id, **kwargs) self.subcloud = sql_api.subcloud_update(self.ctx, self.subcloud.id, **kwargs)

View File

@@ -370,6 +370,7 @@ class TestSwUpdateManager(base.DCManagerTestCase):
for index, strategy_step in enumerate(strategy_step_list): for index, strategy_step in enumerate(strategy_step_list):
self.assertEqual(subcloud_ids[index], strategy_step.subcloud_id) self.assertEqual(subcloud_ids[index], strategy_step.subcloud_id)
@mock.patch.object(cutils, "get_systemcontroller_installed_loads")
@mock.patch.object(prestage, "initial_subcloud_validate") @mock.patch.object(prestage, "initial_subcloud_validate")
@mock.patch.object(prestage, "global_prestage_validate") @mock.patch.object(prestage, "global_prestage_validate")
@mock.patch.object(sw_update_manager, "PatchOrchThread") @mock.patch.object(sw_update_manager, "PatchOrchThread")
@@ -378,6 +379,7 @@ class TestSwUpdateManager(base.DCManagerTestCase):
mock_patch_orch_thread, mock_patch_orch_thread,
mock_global_prestage_validate, mock_global_prestage_validate,
mock_initial_subcloud_validate, mock_initial_subcloud_validate,
mock_installed_loads,
): ):
# Create fake subclouds and respective status # Create fake subclouds and respective status
fake_subcloud1 = self.create_subcloud( fake_subcloud1 = self.create_subcloud(
@@ -404,6 +406,7 @@ class TestSwUpdateManager(base.DCManagerTestCase):
mock_global_prestage_validate.return_value = None mock_global_prestage_validate.return_value = None
mock_initial_subcloud_validate.return_value = None mock_initial_subcloud_validate.return_value = None
mock_installed_loads.return_value = ['24.09']
data = copy.copy(FAKE_SW_PRESTAGE_DATA) data = copy.copy(FAKE_SW_PRESTAGE_DATA)
fake_password = (base64.b64encode("testpass".encode("utf-8"))).decode( fake_password = (base64.b64encode("testpass".encode("utf-8"))).decode(
@@ -428,6 +431,7 @@ class TestSwUpdateManager(base.DCManagerTestCase):
for index, strategy_step in enumerate(strategy_step_list): for index, strategy_step in enumerate(strategy_step_list):
self.assertEqual(subcloud_ids[index], strategy_step.subcloud_id) self.assertEqual(subcloud_ids[index], strategy_step.subcloud_id)
@mock.patch.object(cutils, "get_systemcontroller_installed_loads")
@mock.patch.object(prestage, "initial_subcloud_validate") @mock.patch.object(prestage, "initial_subcloud_validate")
@mock.patch.object(prestage, "global_prestage_validate") @mock.patch.object(prestage, "global_prestage_validate")
@mock.patch.object(sw_update_manager, "PatchOrchThread") @mock.patch.object(sw_update_manager, "PatchOrchThread")
@@ -436,6 +440,7 @@ class TestSwUpdateManager(base.DCManagerTestCase):
mock_patch_orch_thread, mock_patch_orch_thread,
mock_global_prestage_validate, mock_global_prestage_validate,
mock_initial_subcloud_validate, mock_initial_subcloud_validate,
mock_installed_loads,
): ):
# Create fake subclouds and respective status # Create fake subclouds and respective status
# Subcloud1 will be prestaged load in sync # Subcloud1 will be prestaged load in sync
@@ -481,6 +486,7 @@ class TestSwUpdateManager(base.DCManagerTestCase):
mock_global_prestage_validate.return_value = None mock_global_prestage_validate.return_value = None
mock_initial_subcloud_validate.return_value = None mock_initial_subcloud_validate.return_value = None
mock_installed_loads.return_value = ['24.09']
data = copy.copy(FAKE_SW_PRESTAGE_DATA) data = copy.copy(FAKE_SW_PRESTAGE_DATA)
fake_password = (base64.b64encode("testpass".encode("utf-8"))).decode( fake_password = (base64.b64encode("testpass".encode("utf-8"))).decode(
@@ -504,6 +510,7 @@ class TestSwUpdateManager(base.DCManagerTestCase):
for index, strategy_step in enumerate(strategy_step_list): for index, strategy_step in enumerate(strategy_step_list):
self.assertEqual(subcloud_ids[index], strategy_step.subcloud_id) self.assertEqual(subcloud_ids[index], strategy_step.subcloud_id)
@mock.patch.object(cutils, "get_systemcontroller_installed_loads")
@mock.patch.object(prestage, "initial_subcloud_validate") @mock.patch.object(prestage, "initial_subcloud_validate")
@mock.patch.object(prestage, "_get_system_controller_upgrades") @mock.patch.object(prestage, "_get_system_controller_upgrades")
@mock.patch.object(sw_update_manager, "PatchOrchThread") @mock.patch.object(sw_update_manager, "PatchOrchThread")
@@ -512,6 +519,7 @@ class TestSwUpdateManager(base.DCManagerTestCase):
mock_patch_orch_thread, mock_patch_orch_thread,
mock_controller_upgrade, mock_controller_upgrade,
mock_initial_subcloud_validate, mock_initial_subcloud_validate,
mock_installed_loads,
): ):
# Create fake subclouds and respective status # Create fake subclouds and respective status
fake_subcloud1 = self.create_subcloud( fake_subcloud1 = self.create_subcloud(
@@ -538,6 +546,7 @@ class TestSwUpdateManager(base.DCManagerTestCase):
mock_initial_subcloud_validate.return_value = None mock_initial_subcloud_validate.return_value = None
mock_controller_upgrade.return_value = list() mock_controller_upgrade.return_value = list()
mock_installed_loads.return_value = ['24.09']
data = copy.copy(FAKE_SW_PRESTAGE_DATA) data = copy.copy(FAKE_SW_PRESTAGE_DATA)
data["sysadmin_password"] = "" data["sysadmin_password"] = ""
@@ -551,12 +560,17 @@ class TestSwUpdateManager(base.DCManagerTestCase):
payload=data, payload=data,
) )
@mock.patch.object(cutils, "get_systemcontroller_installed_loads")
@mock.patch.object(prestage, "_get_system_controller_upgrades") @mock.patch.object(prestage, "_get_system_controller_upgrades")
@mock.patch.object(sw_update_manager, "PatchOrchThread") @mock.patch.object(sw_update_manager, "PatchOrchThread")
def test_create_sw_prestage_strategy_backup_in_progress( def test_create_sw_prestage_strategy_backup_in_progress(
self, mock_patch_orch_thread, mock_controller_upgrade self,
mock_patch_orch_thread,
mock_controller_upgrade,
mock_installed_loads,
): ):
mock_controller_upgrade.return_value = list() mock_controller_upgrade.return_value = list()
mock_installed_loads.return_value = ['24.09']
# Create fake subcloud and respective status (managed & online) # Create fake subcloud and respective status (managed & online)
fake_subcloud1 = self.create_subcloud( fake_subcloud1 = self.create_subcloud(