Merge "Prestaging support for --for-sw-deploy/--for-install"
This commit is contained in:
commit
9f90f52cfd
@ -216,3 +216,6 @@ ANSIBLE_SUBCLOUD_ENROLL_PLAYBOOK = \
|
||||
|
||||
# Sysinv client default timeout
|
||||
SYSINV_CLIENT_REST_DEFAULT_TIMEOUT = 600
|
||||
|
||||
SUBCLOUD_ISO_PATH = '/opt/platform/iso'
|
||||
SUBCLOUD_FEED_PATH = '/var/www/pages/feed'
|
||||
|
@ -8,6 +8,7 @@ import os
|
||||
from oslo_log import log as logging
|
||||
import sh
|
||||
|
||||
from dccommon import consts
|
||||
from dcmanager.common import utils
|
||||
|
||||
# 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
|
||||
@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.
|
||||
|
||||
Validity check includes if the mount is stale.
|
||||
If stale, the bind mount is recreated.
|
||||
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_source_path = os.path.join(source_path, "ostree_repo")
|
||||
ostree_repo_mount_path = os.path.join(
|
||||
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(
|
||||
"Checking ostree_repo mount: %s against %s",
|
||||
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)
|
||||
mount_args = (
|
||||
"--bind",
|
||||
"%s/ostree_repo" % source_path,
|
||||
ostree_repo_source_path,
|
||||
ostree_repo_mount_path,
|
||||
)
|
||||
try:
|
||||
|
@ -36,6 +36,7 @@ from dccommon.drivers.openstack.sysinv_v1 import SysinvClient
|
||||
from dccommon import exceptions
|
||||
from dccommon import ostree_mount
|
||||
from dccommon import utils as dccommon_utils
|
||||
|
||||
from dcmanager.common import consts as dcmanager_consts
|
||||
from dcmanager.common import utils
|
||||
|
||||
@ -45,9 +46,7 @@ LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
BOOT_MENU_TIMEOUT = '5'
|
||||
|
||||
SUBCLOUD_ISO_PATH = '/opt/platform/iso'
|
||||
SUBCLOUD_ISO_DOWNLOAD_PATH = '/var/www/pages/iso'
|
||||
SUBCLOUD_FEED_PATH = '/var/www/pages/feed'
|
||||
DCVAULT_BOOTIMAGE_PATH = '/opt/dc-vault/loads/'
|
||||
PACKAGE_LIST_PATH = '/usr/local/share/pkg-list'
|
||||
GEN_ISO_COMMAND = '/usr/local/bin/gen-bootloader-iso.sh'
|
||||
@ -510,16 +509,10 @@ class SubcloudInstall(object):
|
||||
if not os.path.isdir(override_path):
|
||||
os.mkdir(override_path, 0o755)
|
||||
|
||||
self.www_iso_root = os.path.join(SUBCLOUD_ISO_PATH, software_version)
|
||||
|
||||
feed_path_rel_version = os.path.join(SUBCLOUD_FEED_PATH,
|
||||
"rel-{version}".format(
|
||||
version=software_version))
|
||||
self.www_iso_root = os.path.join(consts.SUBCLOUD_ISO_PATH, software_version)
|
||||
|
||||
if dccommon_utils.is_debian(software_version):
|
||||
ostree_mount.validate_ostree_iso_mount(
|
||||
self.www_iso_root, feed_path_rel_version
|
||||
)
|
||||
ostree_mount.validate_ostree_iso_mount(software_version)
|
||||
|
||||
# Clean up iso directory if it already exists
|
||||
# This may happen if a previous installation attempt was abruptly
|
||||
@ -571,7 +564,7 @@ class SubcloudInstall(object):
|
||||
self.name,
|
||||
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(
|
||||
version=software_version),
|
||||
'package_checksums')
|
||||
|
@ -12,6 +12,7 @@ from oslo_messaging import RemoteError
|
||||
import pecan
|
||||
|
||||
from dcmanager.api.controllers import restcomm
|
||||
from dcmanager.api.controllers.v1.subclouds import SubcloudsController
|
||||
from dcmanager.api.policies import phased_subcloud_deploy as \
|
||||
phased_subcloud_deploy_policy
|
||||
from dcmanager.api import policy
|
||||
@ -210,6 +211,8 @@ class PhasedSubcloudDeployController(object):
|
||||
if not payload:
|
||||
pecan.abort(400, _('Body required'))
|
||||
|
||||
SubcloudsController.validate_software_deploy_state()
|
||||
|
||||
if subcloud.deploy_status not in VALID_STATES_FOR_DEPLOY_INSTALL:
|
||||
allowed_states_str = ', '.join(VALID_STATES_FOR_DEPLOY_INSTALL)
|
||||
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
|
||||
# of the pending deploy states
|
||||
if INSTALL in deploy_states_to_run:
|
||||
SubcloudsController.validate_software_deploy_state()
|
||||
unvalidated_sw_version = \
|
||||
payload.get('release', subcloud.software_version)
|
||||
else:
|
||||
|
@ -44,6 +44,7 @@ from dccommon.drivers.openstack.fm import FmClient
|
||||
from dccommon.drivers.openstack.sdk_platform import (
|
||||
OptimizedOpenStackDriver as OpenStackDriver
|
||||
)
|
||||
from dccommon.drivers.openstack import software_v1
|
||||
from dccommon.drivers.openstack.sysinv_v1 import SysinvClient
|
||||
from dccommon.drivers.openstack import vim
|
||||
from dccommon import exceptions as dccommon_exceptions
|
||||
@ -129,36 +130,54 @@ class SubcloudsController(object):
|
||||
|
||||
@staticmethod
|
||||
def _get_prestage_payload(request):
|
||||
fields = ['sysadmin_password', 'force', consts.PRESTAGE_REQUEST_RELEASE]
|
||||
payload = {
|
||||
'force': False
|
||||
}
|
||||
fields = [
|
||||
"sysadmin_password",
|
||||
"force",
|
||||
consts.PRESTAGE_REQUEST_RELEASE,
|
||||
consts.PRESTAGE_FOR_INSTALL,
|
||||
consts.PRESTAGE_FOR_SW_DEPLOY,
|
||||
]
|
||||
payload = {"force": False}
|
||||
try:
|
||||
body = json.loads(request.body)
|
||||
except Exception:
|
||||
pecan.abort(400, _('Request body is malformed.'))
|
||||
pecan.abort(400, _("Request body is malformed."))
|
||||
|
||||
for field in fields:
|
||||
val = body.get(field)
|
||||
if val is None:
|
||||
if field == 'sysadmin_password':
|
||||
if field == "sysadmin_password":
|
||||
pecan.abort(400, _("%s is required." % field))
|
||||
else:
|
||||
if field == 'sysadmin_password':
|
||||
if field == "sysadmin_password":
|
||||
try:
|
||||
base64.b64decode(val).decode('utf-8')
|
||||
payload['sysadmin_password'] = val
|
||||
base64.b64decode(val).decode("utf-8")
|
||||
payload["sysadmin_password"] = val
|
||||
except Exception:
|
||||
pecan.abort(
|
||||
400,
|
||||
_('Failed to decode subcloud sysadmin_password, '
|
||||
'verify the password is base64 encoded'))
|
||||
elif field == 'force':
|
||||
if val.lower() in ('true', 'false', 't', 'f'):
|
||||
payload['force'] = val.lower() in ('true', 't')
|
||||
_(
|
||||
"Failed to decode subcloud sysadmin_password, "
|
||||
"verify the password is base64 encoded"
|
||||
),
|
||||
)
|
||||
elif field == "force":
|
||||
if val.lower() in ("true", "false", "t", "f"):
|
||||
payload["force"] = val.lower() in ("true", "t")
|
||||
else:
|
||||
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:
|
||||
payload[consts.PRESTAGE_REQUEST_RELEASE] = val
|
||||
return payload
|
||||
@ -522,6 +541,53 @@ class SubcloudsController(object):
|
||||
subcloud_dict.update(extra_details)
|
||||
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)
|
||||
@index.when(method='POST', template='json')
|
||||
def post(self):
|
||||
@ -531,6 +597,18 @@ class SubcloudsController(object):
|
||||
restcomm.extract_credentials_for_policy())
|
||||
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)
|
||||
|
||||
payload = psd_common.get_request_data(request, None,
|
||||
@ -1003,6 +1081,7 @@ class SubcloudsController(object):
|
||||
if utils.subcloud_is_secondary_state(subcloud.deploy_status):
|
||||
pecan.abort(500, _("Cannot perform on %s "
|
||||
"state subcloud" % subcloud.deploy_status))
|
||||
self.validate_software_deploy_state()
|
||||
config_file = psd_common.get_config_file_path(subcloud.name,
|
||||
consts.DEPLOY_CONFIG)
|
||||
has_bootstrap_values = consts.BOOTSTRAP_VALUES in request.POST
|
||||
@ -1106,8 +1185,11 @@ class SubcloudsController(object):
|
||||
LOG.exception("validate_prestage failed")
|
||||
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(
|
||||
payload.get(consts.PRESTAGE_REQUEST_RELEASE))
|
||||
payload.get(consts.PRESTAGE_REQUEST_RELEASE), for_install)
|
||||
|
||||
try:
|
||||
self.dcmanager_rpc_client.prestage_subcloud(context, payload)
|
||||
|
@ -399,6 +399,8 @@ EXTRA_ARGS_RELEASE = 'release'
|
||||
# http request/response arguments for prestage
|
||||
PRESTAGE_SOFTWARE_VERSION = 'prestage-software-version'
|
||||
PRESTAGE_REQUEST_RELEASE = 'release'
|
||||
PRESTAGE_FOR_INSTALL = 'for_install'
|
||||
PRESTAGE_FOR_SW_DEPLOY = 'for_sw_deploy'
|
||||
|
||||
# Device Image Bitstream Types
|
||||
BITSTREAM_TYPE_ROOT_KEY = 'root-key'
|
||||
|
@ -36,6 +36,7 @@ from dccommon.drivers.openstack.sdk_platform import (
|
||||
from dccommon.drivers.openstack.sysinv_v1 import SysinvClient
|
||||
from dccommon.exceptions import PlaybookExecutionFailed
|
||||
from dccommon.exceptions import PlaybookExecutionTimeout
|
||||
from dccommon import ostree_mount
|
||||
from dccommon.utils import AnsiblePlaybook
|
||||
|
||||
from dcmanager.common import consts
|
||||
@ -110,7 +111,12 @@ def global_prestage_validate(payload):
|
||||
" 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.
|
||||
|
||||
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
|
||||
# of the subcloud or any active/inactive/imported load on the system controller
|
||||
# (can be checked with "system load-list" command).
|
||||
if software_version and \
|
||||
software_version != subcloud.software_version and \
|
||||
software_version not in installed_loads:
|
||||
if not for_sw_deploy and software_major_release and \
|
||||
software_major_release != subcloud.software_version and \
|
||||
software_major_release not in installed_loads:
|
||||
raise exceptions.PrestagePreCheckFailedException(
|
||||
subcloud=subcloud.name,
|
||||
orch_skip=True,
|
||||
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):
|
||||
@ -185,12 +191,21 @@ def validate_prestage(subcloud, payload):
|
||||
|
||||
installed_loads = []
|
||||
software_version = None
|
||||
software_major_release = None
|
||||
if 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()
|
||||
|
||||
for_sw_deploy = is_prestage_for_sw_deploy(payload)
|
||||
|
||||
# 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 = \
|
||||
_get_prestage_subcloud_info(subcloud)
|
||||
@ -238,6 +253,13 @@ def is_local(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):
|
||||
"""Subcloud prestaging
|
||||
|
||||
@ -253,8 +275,11 @@ def prestage_subcloud(context, payload):
|
||||
- run prestage_images.yml ansible playbook
|
||||
"""
|
||||
subcloud_name = payload['subcloud_name']
|
||||
LOG.info("Prestaging subcloud: %s, force=%s" % (subcloud_name,
|
||||
payload['force']))
|
||||
for_sw_deploy = is_prestage_for_sw_deploy(payload)
|
||||
LOG.info(
|
||||
f"Prestaging subcloud: {subcloud_name}, "
|
||||
f"force={payload['force']}, for_sw_deploy={for_sw_deploy}"
|
||||
)
|
||||
try:
|
||||
subcloud = db_api.subcloud_get_by_name(context, subcloud_name)
|
||||
except exceptions.SubcloudNameNotFound:
|
||||
@ -282,6 +307,7 @@ def prestage_subcloud(context, payload):
|
||||
def _prestage_standalone_thread(context, subcloud, payload):
|
||||
"""Run the prestage operations inside a separate thread"""
|
||||
log_file = utils.get_subcloud_ansible_log_file(subcloud.name)
|
||||
for_sw_deploy = is_prestage_for_sw_deploy(payload)
|
||||
try:
|
||||
prestage_packages(context, subcloud, payload)
|
||||
# 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(
|
||||
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)
|
||||
LOG.info("Prestage complete: %s", subcloud.name)
|
||||
|
||||
except Exception:
|
||||
LOG.exception(
|
||||
f"Subcloud prestaging failed (in standalone thread) {subcloud.name}"
|
||||
)
|
||||
prestage_fail(context, subcloud.id)
|
||||
raise
|
||||
|
||||
@ -382,8 +414,24 @@ def prestage_packages(context, subcloud, payload):
|
||||
ANSIBLE_PRESTAGE_INVENTORY_SUFFIX)
|
||||
|
||||
prestage_software_version = payload.get(
|
||||
consts.PRESTAGE_REQUEST_RELEASE, SW_VERSION)
|
||||
extra_vars_str = "software_version=%s" % prestage_software_version
|
||||
consts.PRESTAGE_REQUEST_RELEASE, SW_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,
|
||||
["ansible-playbook",
|
||||
ANSIBLE_PRESTAGE_SUBCLOUD_PACKAGES_PLAYBOOK,
|
||||
@ -414,10 +462,21 @@ def prestage_images(context, subcloud, payload):
|
||||
"""
|
||||
prestage_software_version = payload.get(
|
||||
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
|
||||
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):
|
||||
image_list_filename = utils.get_filename_by_prefix(deploy_dir,
|
||||
'prestage_images')
|
||||
@ -428,14 +487,14 @@ def prestage_images(context, subcloud, payload):
|
||||
LOG.debug("prestage images list file: %s", image_list_file)
|
||||
else:
|
||||
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
|
||||
# skip the images prestage.
|
||||
LOG.info("Images prestage is skipped for %s as the prestage images "
|
||||
"list for release %s has not been uploaded and the "
|
||||
"subcloud is running a different load than %s."
|
||||
% (subcloud.name, prestage_software_version,
|
||||
prestage_software_version))
|
||||
% (subcloud.name, prestage_major_release,
|
||||
prestage_major_release))
|
||||
return
|
||||
|
||||
# Ansible inventory filename for the specified subcloud
|
||||
|
@ -1406,7 +1406,7 @@ def create_subcloud_rehome_data_template():
|
||||
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.
|
||||
|
||||
Return the sw_version by first validating a set release version.
|
||||
@ -1416,7 +1416,10 @@ def get_sw_version(release=None):
|
||||
|
||||
if release:
|
||||
try:
|
||||
validate_release_version_supported(release)
|
||||
if for_install:
|
||||
validate_major_release_version_supported(release)
|
||||
else:
|
||||
validate_minor_release_version_exists(release)
|
||||
return release
|
||||
except exceptions.ValidateFail as e:
|
||||
pecan.abort(400,
|
||||
@ -1428,7 +1431,7 @@ def get_sw_version(release=None):
|
||||
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.
|
||||
|
||||
:param release_version_to_check: version string to validate
|
||||
@ -1452,6 +1455,37 @@ def validate_release_version_supported(release_version_to_check):
|
||||
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():
|
||||
"""Parse the upgrades metadata file to build a list of supported versions.
|
||||
|
||||
|
@ -340,9 +340,16 @@ class SwUpdateManager(manager.Manager):
|
||||
patch_file = payload.get('patch')
|
||||
installed_loads = []
|
||||
software_version = None
|
||||
software_major_release = None
|
||||
for_sw_deploy = False
|
||||
if 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()
|
||||
# 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?
|
||||
# todo(abailey): refactor this code to use classes
|
||||
@ -426,8 +433,13 @@ class SwUpdateManager(manager.Manager):
|
||||
try:
|
||||
prestage.global_prestage_validate(payload)
|
||||
prestage_global_validated = True
|
||||
installed_loads = utils.get_systemcontroller_installed_loads()
|
||||
prestage.initial_subcloud_validate(
|
||||
subcloud, installed_loads, software_version)
|
||||
subcloud,
|
||||
installed_loads,
|
||||
software_major_release,
|
||||
for_sw_deploy
|
||||
)
|
||||
except exceptions.PrestagePreCheckFailedException as ex:
|
||||
raise exceptions.BadRequest(resource='strategy',
|
||||
msg=str(ex))
|
||||
@ -597,7 +609,11 @@ class SwUpdateManager(manager.Manager):
|
||||
# Do initial validation for subcloud
|
||||
try:
|
||||
prestage.initial_subcloud_validate(
|
||||
subcloud, installed_loads, software_version)
|
||||
subcloud,
|
||||
installed_loads,
|
||||
software_major_release,
|
||||
for_sw_deploy,
|
||||
)
|
||||
except exceptions.PrestagePreCheckFailedException:
|
||||
LOG.warn("Excluding subcloud from prestage strategy: %s",
|
||||
subcloud.name)
|
||||
|
@ -31,6 +31,7 @@ from sqlalchemy.engine import Engine
|
||||
from sqlalchemy import event
|
||||
|
||||
from dccommon.utils import AnsiblePlaybook
|
||||
from dcmanager.api.controllers.v1.subclouds import SubcloudsController
|
||||
from dcmanager.audit import rpcapi
|
||||
from dcmanager.audit import subcloud_audit_manager
|
||||
from dcmanager.common import consts
|
||||
@ -382,3 +383,11 @@ class DCManagerTestCase(base.BaseTestCase):
|
||||
mock_patch_object = mock.patch.object(AnsiblePlaybook, 'run_playbook')
|
||||
self.mock_ansible_run_playbook = mock_patch_object.start()
|
||||
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
|
||||
|
@ -14,6 +14,7 @@ from tsconfig.tsconfig import SW_VERSION
|
||||
|
||||
from dccommon import consts as dccommon_consts
|
||||
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 phased_subcloud_deploy as psd_common
|
||||
from dcmanager.common import utils as dutils
|
||||
@ -66,6 +67,13 @@ class BaseTestPhasedSubcloudDeployController(DCManagerApiTest):
|
||||
self.mock_is_initial_deployment = mock_patch_object.start()
|
||||
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):
|
||||
"""Test class for PhasedSubcloudDeployController"""
|
||||
@ -197,6 +205,7 @@ class BaseTestPhasedSubcloudDeployPatch(BaseTestPhasedSubcloudDeployController):
|
||||
|
||||
self._mock_get_vault_load_files()
|
||||
self._mock_is_initial_deployment()
|
||||
self._mock_is_valid_software_deploy_state()
|
||||
self._mock_get_network_address_pool()
|
||||
|
||||
self.mock_get_vault_load_files.return_value = \
|
||||
|
@ -230,6 +230,7 @@ class BaseTestSubcloudsController(DCManagerApiTest, SubcloudAPIMixin):
|
||||
self._mock_rpc_subcloud_state_client()
|
||||
self._mock_get_ks_client()
|
||||
self._mock_query()
|
||||
self._mock_valid_software_deploy_state()
|
||||
|
||||
def _update_subcloud(self, **kwargs):
|
||||
self.subcloud = sql_api.subcloud_update(self.ctx, self.subcloud.id, **kwargs)
|
||||
|
@ -370,6 +370,7 @@ class TestSwUpdateManager(base.DCManagerTestCase):
|
||||
for index, strategy_step in enumerate(strategy_step_list):
|
||||
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, "global_prestage_validate")
|
||||
@mock.patch.object(sw_update_manager, "PatchOrchThread")
|
||||
@ -378,6 +379,7 @@ class TestSwUpdateManager(base.DCManagerTestCase):
|
||||
mock_patch_orch_thread,
|
||||
mock_global_prestage_validate,
|
||||
mock_initial_subcloud_validate,
|
||||
mock_installed_loads,
|
||||
):
|
||||
# Create fake subclouds and respective status
|
||||
fake_subcloud1 = self.create_subcloud(
|
||||
@ -404,6 +406,7 @@ class TestSwUpdateManager(base.DCManagerTestCase):
|
||||
|
||||
mock_global_prestage_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)
|
||||
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):
|
||||
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, "global_prestage_validate")
|
||||
@mock.patch.object(sw_update_manager, "PatchOrchThread")
|
||||
@ -436,6 +440,7 @@ class TestSwUpdateManager(base.DCManagerTestCase):
|
||||
mock_patch_orch_thread,
|
||||
mock_global_prestage_validate,
|
||||
mock_initial_subcloud_validate,
|
||||
mock_installed_loads,
|
||||
):
|
||||
# Create fake subclouds and respective status
|
||||
# Subcloud1 will be prestaged load in sync
|
||||
@ -481,6 +486,7 @@ class TestSwUpdateManager(base.DCManagerTestCase):
|
||||
|
||||
mock_global_prestage_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)
|
||||
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):
|
||||
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, "_get_system_controller_upgrades")
|
||||
@mock.patch.object(sw_update_manager, "PatchOrchThread")
|
||||
@ -512,6 +519,7 @@ class TestSwUpdateManager(base.DCManagerTestCase):
|
||||
mock_patch_orch_thread,
|
||||
mock_controller_upgrade,
|
||||
mock_initial_subcloud_validate,
|
||||
mock_installed_loads,
|
||||
):
|
||||
# Create fake subclouds and respective status
|
||||
fake_subcloud1 = self.create_subcloud(
|
||||
@ -538,6 +546,7 @@ class TestSwUpdateManager(base.DCManagerTestCase):
|
||||
|
||||
mock_initial_subcloud_validate.return_value = None
|
||||
mock_controller_upgrade.return_value = list()
|
||||
mock_installed_loads.return_value = ['24.09']
|
||||
|
||||
data = copy.copy(FAKE_SW_PRESTAGE_DATA)
|
||||
data["sysadmin_password"] = ""
|
||||
@ -551,12 +560,17 @@ class TestSwUpdateManager(base.DCManagerTestCase):
|
||||
payload=data,
|
||||
)
|
||||
|
||||
@mock.patch.object(cutils, "get_systemcontroller_installed_loads")
|
||||
@mock.patch.object(prestage, "_get_system_controller_upgrades")
|
||||
@mock.patch.object(sw_update_manager, "PatchOrchThread")
|
||||
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_installed_loads.return_value = ['24.09']
|
||||
|
||||
# Create fake subcloud and respective status (managed & online)
|
||||
fake_subcloud1 = self.create_subcloud(
|
||||
|
Loading…
x
Reference in New Issue
Block a user