Backend support for dcmanager prestage orchestration
Add backend support for orchestrated prestaging for one or a group of subclouds serially or in parallel. Similar to other types of orchestration; prestage strategy can be created, applied, aborted and deleted. When a prestage-strategy is in progress, no other strategy can be created. Orchestrated subcloud prestaging will also consist of 4 states: - precheck – prestage conditions verification - preparing packages – prestage packages generation - prestaging packages – upgrade packages prestaging - prestaging images – upgrade container images prestaging The prestaging progress of each subcloud in the batch can be monitored via dcmanager strategy-step list. Unlike other types of orchestration: - sysadmin-password is required as a mandatory input - interaction with vim is not required Test Plan: PASS: - API-level lifecycle commands for dcmanger prestage-strategy - Test via REST API (see curl samples below) - Test via CLI commands - Create a prestage strategy - For a single subcloud - For a subcloud group - basic validation during creation for both cases - unmanaged/offline/invalid deploy_status - Fail during create if subcloud offline - Apply the strategy - Ensure prestage operations proceed in order for each subcloud, as per the unorchestrated case - Test various failure scenarios: - non AIO-SX subcloud - unmanaged/offline/invalid deploy_status - failure of ansible script - Apply with various values for max parallel subclouds - ranging from 1 to 50 - Apply strategy after unmanaging subcloud(s) in the strategy-list - Show the strategy (in various states) - Delete strategy - Abort the strategy while in progress - Result: in-progress operations run to completion, remaining subclouds are aborted - Regression testing of the original, unorchestrated 'dcmanager subcloud prestage' command - Run prestage operations with no uploaded image list file - Issue an individual 'dcmanager subcloud prestage' command while prestage orchestration is in progress. The command is rejected. - Upgrade scenario with included image list file - ensure all ansible scripts are correctly run (success case) - ensure ansible failures are handled correctly - Test failure of prestage prepare. The first failure should cause the next subclouds in the strategy step to fail. We don't want to keep retrying prestage prepare for all subclouds in the strategy list. - Handle case where prestage prepare ansible fails. This should only be attempted once per strategy step, i.e. we don't want to keep retrying the failed script per subcloud. - The issue is that the rescue deletes the '.prestage_preparation_completed' file along with the packages output directory. So although it is synchronized, after getting the lock, each subcloud retries the same script - Perform host-swact after successful upgrade - ensure prestage preparation is replicated across to the newly active controller Sample Curl Commands used in testing: APIURL=$(system oam-show | awk '/oam_floating_ip/ { print "http://["$4"]:8119/v1.0"; }') # Token - is valid for 1 hour TOKEN=$(openstack token issue | awk '/ id / { print $4; }') Create prestage strategy: curl -g -X POST $APIURL/sw-update-strategy \ -H "X-Auth-Token: ${TOKEN}" \ -H 'Content-Type: application/json' \ -d '{"type": "prestage", \ "sysadmin_password": "TGk2OW51eCo=", \ "force": "true", \ "max-parallel-subclouds": "50", \ "group": "Default"}' Delete prestage strategy: curl -g -X DELETE $APIURL/sw-update-strategy?type=prestage \ -H "X-Auth-Token: ${TOKEN}" \ -H 'Content-Type: application/json' Get prestage strategy: curl -g -X GET $APIURL/sw-update-strategy?type=prestage \ -H "X-Auth-Token: ${TOKEN}" \ -H 'Content-Type: application/json' Apply: curl -g -X POST $APIURL/sw-update-strategy/actions?type=prestage \ -H "X-Auth-Token: ${TOKEN}" \ -H 'Content-Type: application/json' -d '{"action": "apply"}' Malformed data: curl -g -X POST $APIURL/sw-update-strategy \ -H "X-Auth-Token: ${TOKEN}" \ -H 'Content-Type: application/json' \ -d 'abc' Missing mandatory parameter: curl -g -X POST $APIURL/sw-update-strategy \ -H "X-Auth-Token: ${TOKEN}" \ -H 'Content-Type: application/json' \ -d '{"type": "prestage"}' Sysadmin password not encoded: curl -g -X POST $APIURL/sw-update-strategy \ -H "X-Auth-Token: ${TOKEN}" \ -H 'Content-Type: application/json' \ -d '{"type": "prestage", "sysadmin_password": "abc"}' Invalid force option value: curl -g -X POST $APIURL/sw-update-strategy \ -H "X-Auth-Token: ${TOKEN}" \ -H 'Content-Type: application/json' \ -d '{"type": "prestage", \ "sysadmin_password": "TGk2OW51eCo=", \ "force": "abc"}' Depends-On: https://review.opendev.org/c/starlingx/ansible-playbooks/+/831208 Story: 2009799 Task: 44592 Change-Id: Ie28970e8fb1b862b5659ff264967e91ce8be2ab0 Signed-off-by: Kyle MacLeod <kyle.macleod@windriver.com>
This commit is contained in:
@@ -221,8 +221,8 @@ class SubcloudsController(object):
|
||||
else:
|
||||
if field == 'sysadmin_password':
|
||||
try:
|
||||
payload['sysadmin_password'] = base64.b64decode(
|
||||
val).decode('utf-8')
|
||||
base64.b64decode(val).decode('utf-8')
|
||||
payload['sysadmin_password'] = val
|
||||
except Exception:
|
||||
pecan.abort(
|
||||
400,
|
||||
@@ -1423,10 +1423,16 @@ class SubcloudsController(object):
|
||||
payload = self._get_prestage_payload(request)
|
||||
payload['subcloud_name'] = subcloud.name
|
||||
try:
|
||||
payload['oam_floating_ip'] = \
|
||||
prestage.validate_prestage_subcloud(subcloud, payload)
|
||||
prestage.global_prestage_validate(payload)
|
||||
except exceptions.PrestagePreCheckFailedException as exc:
|
||||
LOG.exception("validate_prestage_subcloud failed")
|
||||
LOG.exception("global_prestage_validate failed")
|
||||
pecan.abort(400, _(str(exc)))
|
||||
|
||||
try:
|
||||
payload['oam_floating_ip'] = \
|
||||
prestage.validate_prestage(subcloud, payload)
|
||||
except exceptions.PrestagePreCheckFailedException as exc:
|
||||
LOG.exception("validate_prestage failed")
|
||||
pecan.abort(400, _(str(exc)))
|
||||
|
||||
try:
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# Copyright (c) 2017 Ericsson AB.
|
||||
# Copyright (c) 2017-2021 Wind River Systems, Inc.
|
||||
# Copyright (c) 2017-2022 Wind River Systems, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@@ -39,13 +39,15 @@ SUPPORTED_STRATEGY_TYPES = [
|
||||
consts.SW_UPDATE_TYPE_KUBE_ROOTCA_UPDATE,
|
||||
consts.SW_UPDATE_TYPE_KUBERNETES,
|
||||
consts.SW_UPDATE_TYPE_PATCH,
|
||||
consts.SW_UPDATE_TYPE_PRESTAGE,
|
||||
consts.SW_UPDATE_TYPE_UPGRADE
|
||||
]
|
||||
|
||||
# some strategies allow force for all subclouds
|
||||
FORCE_ALL_TYPES = [
|
||||
consts.SW_UPDATE_TYPE_KUBE_ROOTCA_UPDATE,
|
||||
consts.SW_UPDATE_TYPE_KUBERNETES
|
||||
consts.SW_UPDATE_TYPE_KUBERNETES,
|
||||
consts.SW_UPDATE_TYPE_PRESTAGE
|
||||
]
|
||||
|
||||
|
||||
|
@@ -79,6 +79,7 @@ SW_UPDATE_TYPE_FIRMWARE = "firmware"
|
||||
SW_UPDATE_TYPE_KUBE_ROOTCA_UPDATE = "kube-rootca-update"
|
||||
SW_UPDATE_TYPE_KUBERNETES = "kubernetes"
|
||||
SW_UPDATE_TYPE_PATCH = "patch"
|
||||
SW_UPDATE_TYPE_PRESTAGE = "prestage"
|
||||
SW_UPDATE_TYPE_UPGRADE = "upgrade"
|
||||
|
||||
# Software update states
|
||||
@@ -164,6 +165,12 @@ STRATEGY_STATE_CREATING_VIM_KUBE_ROOTCA_UPDATE_STRATEGY = \
|
||||
STRATEGY_STATE_APPLYING_VIM_KUBE_ROOTCA_UPDATE_STRATEGY = \
|
||||
"applying vim kube rootca update strategy"
|
||||
|
||||
# Prestage orchestration states (ordered)
|
||||
STRATEGY_STATE_PRESTAGE_PRE_CHECK = "prestage-precheck"
|
||||
STRATEGY_STATE_PRESTAGE_PREPARE = "prestage-prepare"
|
||||
STRATEGY_STATE_PRESTAGE_PACKAGES = "prestaging-packages"
|
||||
STRATEGY_STATE_PRESTAGE_IMAGES = "prestaging-images"
|
||||
|
||||
# Subcloud deploy status states
|
||||
DEPLOY_STATE_NONE = 'not-deployed'
|
||||
DEPLOY_STATE_PRE_DEPLOY = 'pre-deploy'
|
||||
@@ -201,12 +208,11 @@ UPGRADE_STATE_ACTIVATION_FAILED = 'activation-failed'
|
||||
UPGRADE_STATE_ACTIVATION_COMPLETE = 'activation-complete'
|
||||
|
||||
# Prestage States
|
||||
PRESTAGE_STATE_PREPARE = 'prestage-prepare'
|
||||
PRESTAGE_STATE_PACKAGES = 'prestaging-packages'
|
||||
PRESTAGE_STATE_IMAGES = 'prestaging-images'
|
||||
PRESTAGE_STATE_PREPARE = STRATEGY_STATE_PRESTAGE_PREPARE
|
||||
PRESTAGE_STATE_PACKAGES = STRATEGY_STATE_PRESTAGE_PACKAGES
|
||||
PRESTAGE_STATE_IMAGES = STRATEGY_STATE_PRESTAGE_IMAGES
|
||||
PRESTAGE_STATE_FAILED = 'prestage-failed'
|
||||
PRESTAGE_STATE_COMPLETE = 'prestage-complete'
|
||||
PRESTAGE_FILE_POSTFIX = '_prestage.yml'
|
||||
|
||||
# Alarm aggregation
|
||||
ALARMS_DISABLED = "disabled"
|
||||
@@ -262,6 +268,8 @@ EXTRA_ARGS_TO_VERSION = 'to-version'
|
||||
EXTRA_ARGS_CERT_FILE = 'cert-file'
|
||||
EXTRA_ARGS_EXPIRY_DATE = 'expiry-date'
|
||||
EXTRA_ARGS_SUBJECT = 'subject'
|
||||
EXTRA_ARGS_SYSADMIN_PASSWORD = 'sysadmin_password'
|
||||
EXTRA_ARGS_FORCE = 'force'
|
||||
|
||||
# Device Image Bitstream Types
|
||||
BITSTREAM_TYPE_ROOT_KEY = 'root-key'
|
||||
|
@@ -195,7 +195,25 @@ class PreCheckFailedException(DCManagerException):
|
||||
|
||||
|
||||
class PrestagePreCheckFailedException(DCManagerException):
|
||||
message = _("Subcloud %(subcloud)s prestage precheck failed: %(details)s")
|
||||
"""PrestagePreCheckFailedException
|
||||
|
||||
Extended to include 'orch_skip' property, indicating that
|
||||
the subcloud can be skipped during orchestrated prestage
|
||||
operations.
|
||||
"""
|
||||
def __init__(self, subcloud, details, orch_skip=False):
|
||||
self.orch_skip = orch_skip
|
||||
# Subcloud can be none if we are failing
|
||||
# during global prestage validation
|
||||
if subcloud is None:
|
||||
self.message = _("Prestage failed: %s" % details)
|
||||
elif orch_skip:
|
||||
self.message = _("Prestage skipped '%s': %s"
|
||||
% (subcloud, details))
|
||||
else:
|
||||
self.message = _("Prestage failed '%s': %s"
|
||||
% (subcloud, details))
|
||||
super(PrestagePreCheckFailedException, self).__init__()
|
||||
|
||||
|
||||
class VaultLoadMissingError(DCManagerException):
|
||||
|
@@ -20,8 +20,8 @@ Common prestaging operations.
|
||||
These are shared across dcmanager (SubcloudManager) and orchestration.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import os
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
from oslo_log import log as logging
|
||||
@@ -35,23 +35,25 @@ from dccommon.exceptions import PlaybookExecutionFailed
|
||||
from dccommon.utils import run_playbook
|
||||
|
||||
from dcmanager.common import consts
|
||||
from dcmanager.common.consts import PRESTAGE_FILE_POSTFIX
|
||||
from dcmanager.common import exceptions
|
||||
from dcmanager.common import utils
|
||||
from dcmanager.db import api as db_api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
PREPARE_PRESTAGE_PACKAGES_SCRIPT = \
|
||||
'/usr/local/bin/prepare-prestage-packages.sh'
|
||||
DEPLOY_BASE_DIR = DEPLOY_DIR + '/' + SW_VERSION
|
||||
PREPARE_PRESTAGE_PACKAGES_OUTPUT_PATH = DEPLOY_BASE_DIR + '/prestage/shared'
|
||||
PREPARE_PRESTAGE_PACKAGES_IMAGES_LIST \
|
||||
= DEPLOY_BASE_DIR + '/prestage_images_image_list.txt'
|
||||
PRESTAGE_PREPARATION_COMPLETED_FILE = os.path.join(
|
||||
PREPARE_PRESTAGE_PACKAGES_OUTPUT_PATH, '.prestage_preparation_completed')
|
||||
PRESTAGE_PREPARATION_FAILED_FILE = os.path.join(
|
||||
DEPLOY_BASE_DIR, '.prestage_preparation_failed')
|
||||
ANSIBLE_PREPARE_PRESTAGE_PACKAGES_PLAYBOOK = \
|
||||
"/usr/share/ansible/stx-ansible/playbooks/prepare_prestage_packages.yml"
|
||||
ANSIBLE_PRESTAGE_SUBCLOUD_PACKAGES_PLAYBOOK = \
|
||||
"/usr/share/ansible/stx-ansible/playbooks/prestage_sw_packages.yml"
|
||||
ANSIBLE_PRESTAGE_SUBCLOUD_IMAGES_PLAYBOOK = \
|
||||
"/usr/share/ansible/stx-ansible/playbooks/prestage_images.yml"
|
||||
ANSIBLE_PRESTAGE_INVENTORY_SUFFIX = '_prestage_inventory.yml'
|
||||
|
||||
|
||||
def is_deploy_status_prestage(deploy_status):
|
||||
@@ -85,7 +87,69 @@ def is_system_controller_upgrading():
|
||||
return len(_get_system_controller_upgrades()) != 0
|
||||
|
||||
|
||||
def validate_prestage_subcloud(subcloud, payload):
|
||||
def global_prestage_validate(payload):
|
||||
"""Global prestage validation (not subcloud-specific)"""
|
||||
|
||||
if is_system_controller_upgrading():
|
||||
raise exceptions.PrestagePreCheckFailedException(
|
||||
subcloud=consts.SYSTEM_CONTROLLER_NAME,
|
||||
details='Prestage operations not allowed while system'
|
||||
' controller upgrade is in progress.')
|
||||
|
||||
if ('sysadmin_password' not in payload
|
||||
or payload['sysadmin_password'] is None
|
||||
or payload['sysadmin_password'] == ''):
|
||||
raise exceptions.PrestagePreCheckFailedException(
|
||||
subcloud=None,
|
||||
orch_skip=False,
|
||||
details="Missing required parameter 'sysadmin_password'")
|
||||
|
||||
# Ensure we can decode the sysadmin_password
|
||||
# (we decode again when running ansible)
|
||||
try:
|
||||
base64.b64decode(payload['sysadmin_password']).decode('utf-8')
|
||||
except Exception as ex:
|
||||
raise exceptions.PrestagePreCheckFailedException(
|
||||
subcloud=None,
|
||||
orch_skip=False,
|
||||
details="Failed to decode subcloud sysadmin_password,"
|
||||
" verify the password is base64 encoded."
|
||||
" Details: %s" % ex)
|
||||
|
||||
|
||||
def initial_subcloud_validate(subcloud):
|
||||
"""Basic validation a subcloud prestage operation.
|
||||
|
||||
Raises a PrestageCheckFailedException on failure.
|
||||
"""
|
||||
LOG.debug("Validating subcloud prestage '%s'", subcloud.name)
|
||||
|
||||
if subcloud.availability_status != consts.AVAILABILITY_ONLINE:
|
||||
raise exceptions.PrestagePreCheckFailedException(
|
||||
subcloud=subcloud.name,
|
||||
orch_skip=True,
|
||||
details="Subcloud is offline.")
|
||||
|
||||
if subcloud.management_state != consts.MANAGEMENT_MANAGED:
|
||||
raise exceptions.PrestagePreCheckFailedException(
|
||||
subcloud=subcloud.name,
|
||||
orch_skip=True,
|
||||
details="Subcloud is not managed.")
|
||||
|
||||
allowed_deploy_states = [consts.DEPLOY_STATE_DONE,
|
||||
consts.PRESTAGE_STATE_FAILED,
|
||||
consts.PRESTAGE_STATE_COMPLETE]
|
||||
if subcloud.deploy_status not in allowed_deploy_states:
|
||||
raise exceptions.PrestagePreCheckFailedException(
|
||||
subcloud=subcloud.name,
|
||||
orch_skip=True,
|
||||
details="Prestage operation is only allowed while"
|
||||
" subcloud deploy status is one of: %s."
|
||||
" The current deploy status is %s."
|
||||
% (', '.join(allowed_deploy_states), subcloud.deploy_status))
|
||||
|
||||
|
||||
def validate_prestage(subcloud, payload):
|
||||
"""Validate a subcloud prestage operation.
|
||||
|
||||
Prestage conditions validation
|
||||
@@ -96,60 +160,70 @@ def validate_prestage_subcloud(subcloud, payload):
|
||||
- Subcloud has no management-affecting alarms (unless force=true)
|
||||
|
||||
Raises a PrestageCheckFailedException on failure.
|
||||
|
||||
Returns the oam_floating_ip for subsequent use by ansible.
|
||||
"""
|
||||
force = payload['force']
|
||||
LOG.debug("Validating subcloud prestage '%s', force=%s",
|
||||
subcloud.name, force)
|
||||
LOG.debug("Validating subcloud prestage '%s'", subcloud.name)
|
||||
|
||||
if subcloud.availability_status != consts.AVAILABILITY_ONLINE:
|
||||
raise exceptions.PrestagePreCheckFailedException(
|
||||
subcloud=subcloud.name, details="Subcloud is offline")
|
||||
# re-run the initial validation
|
||||
initial_subcloud_validate(subcloud)
|
||||
|
||||
subcloud_type, system_health, oam_floating_ip = \
|
||||
_get_prestage_subcloud_info(subcloud.name)
|
||||
|
||||
# TODO(kmacleod) for orchestration, make sure we check this
|
||||
# as part of strategy create
|
||||
if is_system_controller_upgrading():
|
||||
if subcloud_type != consts.SYSTEM_MODE_SIMPLEX:
|
||||
raise exceptions.PrestagePreCheckFailedException(
|
||||
subcloud=subcloud.name,
|
||||
details='Prestage operations not allowed while system'
|
||||
' controller upgrade is in progress.')
|
||||
orch_skip=True,
|
||||
details="Prestage operation is only accepted for a simplex"
|
||||
" subcloud.")
|
||||
|
||||
if (subcloud_type != consts.SYSTEM_MODE_SIMPLEX or
|
||||
subcloud.management_state != consts.MANAGEMENT_MANAGED):
|
||||
raise exceptions.PrestagePreCheckFailedException(
|
||||
subcloud=subcloud.name,
|
||||
details='Prestage operation is only accepted for a simplex'
|
||||
' subcloud that is currently online and managed.')
|
||||
|
||||
if (not force
|
||||
if (not payload['force']
|
||||
and not utils.pre_check_management_affected_alarm(system_health)):
|
||||
raise exceptions.PrestagePreCheckFailedException(
|
||||
subcloud=subcloud.name,
|
||||
details='There is management affecting alarm(s) in the'
|
||||
' subcloud. Please resolve the alarm condition(s)'
|
||||
' or use --force option and try again.')
|
||||
|
||||
allowed_deploy_states = [consts.DEPLOY_STATE_DONE,
|
||||
consts.PRESTAGE_STATE_FAILED,
|
||||
consts.PRESTAGE_STATE_COMPLETE]
|
||||
if subcloud.deploy_status not in allowed_deploy_states:
|
||||
raise exceptions.PrestagePreCheckFailedException(
|
||||
subcloud=subcloud.name,
|
||||
details='Prestage operation is only allowed while'
|
||||
' subcloud deploy status is one of: %s.'
|
||||
' The current deploy status is %s.'
|
||||
% (', '.join(allowed_deploy_states), subcloud.deploy_status))
|
||||
orch_skip=False,
|
||||
details="Subcloud has management affecting alarm(s)."
|
||||
" Please resolve the alarm condition(s)"
|
||||
" or use --force option and try again.")
|
||||
|
||||
return oam_floating_ip
|
||||
|
||||
|
||||
@utils.synchronized('prestage-prepare-cleanup', external=True)
|
||||
def cleanup_failed_preparation():
|
||||
"""Remove the preparation failed file if it exists from a previous run"""
|
||||
if os.path.exists(PRESTAGE_PREPARATION_FAILED_FILE):
|
||||
LOG.debug("Cleanup: removing %s", PRESTAGE_PREPARATION_FAILED_FILE)
|
||||
os.remove(PRESTAGE_PREPARATION_FAILED_FILE)
|
||||
|
||||
|
||||
def prestage_start(context, subcloud_id):
|
||||
subcloud = db_api.subcloud_update(
|
||||
context, subcloud_id,
|
||||
deploy_status=consts.PRESTAGE_STATE_PREPARE)
|
||||
return subcloud
|
||||
|
||||
|
||||
def prestage_complete(context, subcloud_id):
|
||||
db_api.subcloud_update(
|
||||
context, subcloud_id,
|
||||
deploy_status=consts.PRESTAGE_STATE_COMPLETE)
|
||||
|
||||
|
||||
def prestage_fail(context, subcloud_id):
|
||||
db_api.subcloud_update(
|
||||
context, subcloud_id,
|
||||
deploy_status=consts.PRESTAGE_STATE_FAILED)
|
||||
|
||||
|
||||
def is_upgrade(subcloud_version):
|
||||
return SW_VERSION != subcloud_version
|
||||
|
||||
|
||||
def prestage_subcloud(context, payload):
|
||||
"""Subcloud prestaging
|
||||
|
||||
This is the standalone (not orchestrated) prestage implementation.
|
||||
|
||||
4 phases:
|
||||
1. Prestage validation (already done by this point)
|
||||
- Subcloud exists, is online, is managed, is AIO-SX
|
||||
@@ -173,14 +247,12 @@ def prestage_subcloud(context, payload):
|
||||
subcloud=subcloud_name,
|
||||
details="Subcloud does not exist")
|
||||
|
||||
# TODO(kmacleod) fail if prestaging orchestration in progress
|
||||
|
||||
subcloud = db_api.subcloud_update(
|
||||
context, subcloud.id,
|
||||
deploy_status=consts.PRESTAGE_STATE_PREPARE)
|
||||
cleanup_failed_preparation()
|
||||
subcloud = prestage_start(context, subcloud.id)
|
||||
try:
|
||||
apply_thread = threading.Thread(
|
||||
target=run_prestage_thread, args=(context, subcloud, payload))
|
||||
target=_prestage_standalone_thread,
|
||||
args=(context, subcloud, payload))
|
||||
|
||||
apply_thread.start()
|
||||
|
||||
@@ -188,50 +260,83 @@ def prestage_subcloud(context, payload):
|
||||
|
||||
except Exception:
|
||||
LOG.exception("Subcloud prestaging failed %s" % subcloud_name)
|
||||
db_api.subcloud_update(
|
||||
context, subcloud_name,
|
||||
deploy_status=consts.PRESTAGE_STATE_FAILED)
|
||||
prestage_fail(context, subcloud.id)
|
||||
|
||||
|
||||
def run_prestage_thread(context, subcloud, payload):
|
||||
def _sync_run_prestage_prepare_packages(context, subcloud, payload):
|
||||
"""Run prepare prestage packages ansible script."""
|
||||
|
||||
if os.path.exists(PRESTAGE_PREPARATION_FAILED_FILE):
|
||||
LOG.warn("Subcloud %s prestage preparation aborted due to "
|
||||
"previous %s failure", subcloud.name,
|
||||
consts.PRESTAGE_STATE_PREPARE)
|
||||
raise Exception("Aborted due to previous %s failure"
|
||||
% consts.PRESTAGE_STATE_PREPARE)
|
||||
|
||||
LOG.info("Running prepare prestage ansible script, version=%s "
|
||||
"(subcloud_id=%s)", SW_VERSION, subcloud.id)
|
||||
db_api.subcloud_update(context,
|
||||
subcloud.id,
|
||||
deploy_status=consts.PRESTAGE_STATE_PREPARE)
|
||||
|
||||
# Ansible inventory filename for the specified subcloud
|
||||
ansible_subcloud_inventory_file = \
|
||||
utils.get_ansible_filename(subcloud.name,
|
||||
ANSIBLE_PRESTAGE_INVENTORY_SUFFIX)
|
||||
|
||||
extra_vars_str = "current_software_version=%s previous_software_version=%s" \
|
||||
% (SW_VERSION, subcloud.software_version)
|
||||
|
||||
try:
|
||||
_run_ansible(context,
|
||||
["ansible-playbook",
|
||||
ANSIBLE_PREPARE_PRESTAGE_PACKAGES_PLAYBOOK,
|
||||
"--inventory", ansible_subcloud_inventory_file,
|
||||
"--extra-vars", extra_vars_str],
|
||||
"prepare",
|
||||
subcloud,
|
||||
consts.PRESTAGE_STATE_PREPARE,
|
||||
payload['sysadmin_password'],
|
||||
payload['oam_floating_ip'],
|
||||
ansible_subcloud_inventory_file)
|
||||
except Exception:
|
||||
# Flag the failure on file system so that other orchestrated
|
||||
# strategy steps in this run fail immediately. This file is
|
||||
# removed at the start of each orchestrated/standalone run.
|
||||
# This creates the file if it doesn't exist:
|
||||
with open(PRESTAGE_PREPARATION_FAILED_FILE, 'a'):
|
||||
pass
|
||||
raise
|
||||
|
||||
LOG.info("Prepare prestage ansible successful")
|
||||
|
||||
|
||||
@utils.synchronized('prestage-prepare-packages', external=True)
|
||||
def prestage_prepare(context, subcloud, payload):
|
||||
"""Run the prepare prestage packages playbook if required."""
|
||||
if is_upgrade(subcloud.software_version):
|
||||
if not os.path.exists(PRESTAGE_PREPARATION_COMPLETED_FILE):
|
||||
_sync_run_prestage_prepare_packages(context, subcloud, payload)
|
||||
else:
|
||||
LOG.info(
|
||||
"Skipping prestage package preparation (not required)")
|
||||
else:
|
||||
LOG.info("Skipping prestage package preparation (reinstall)")
|
||||
|
||||
|
||||
def _prestage_standalone_thread(context, subcloud, payload):
|
||||
"""Run the prestage operations inside a separate thread"""
|
||||
try:
|
||||
is_upgrade = SW_VERSION != subcloud.software_version
|
||||
prestage_prepare(context, subcloud, payload)
|
||||
prestage_packages(context, subcloud, payload)
|
||||
prestage_images(context, subcloud, payload)
|
||||
|
||||
if is_upgrade:
|
||||
# TODO(kmacleod): check for '.prestage_prepation_completed' file instead
|
||||
# of Packages directory (not in place yet)
|
||||
if not os.path.exists(
|
||||
os.path.join(PREPARE_PRESTAGE_PACKAGES_OUTPUT_PATH,
|
||||
'Packages')):
|
||||
if not _run_prestage_prepare_packages(
|
||||
context, subcloud.id):
|
||||
db_api.subcloud_update(
|
||||
context, subcloud.id,
|
||||
deploy_status=consts.PRESTAGE_STATE_FAILED)
|
||||
return
|
||||
else:
|
||||
LOG.info(
|
||||
"Skipping prestage package preparation (not required)")
|
||||
else:
|
||||
LOG.info("Skipping prestage package preparation (reinstall)")
|
||||
|
||||
if _run_prestage_ansible(
|
||||
context, subcloud, payload['oam_floating_ip'],
|
||||
payload['sysadmin_password'], is_upgrade):
|
||||
db_api.subcloud_update(
|
||||
context, subcloud.id,
|
||||
deploy_status=consts.PRESTAGE_STATE_COMPLETE)
|
||||
LOG.info("Prestage complete: %s", subcloud.name)
|
||||
else:
|
||||
db_api.subcloud_update(
|
||||
context, subcloud.id,
|
||||
deploy_status=consts.PRESTAGE_STATE_FAILED)
|
||||
prestage_complete(context, subcloud.id)
|
||||
LOG.info("Prestage complete: %s", subcloud.name)
|
||||
|
||||
except Exception:
|
||||
LOG.exception("Unexpected exception")
|
||||
db_api.subcloud_update(context, subcloud.id,
|
||||
deploy_status=consts.PRESTAGE_STATE_FAILED)
|
||||
prestage_fail(context, subcloud.id)
|
||||
raise
|
||||
|
||||
|
||||
def _get_prestage_subcloud_info(subcloud_name):
|
||||
@@ -257,116 +362,115 @@ def _get_prestage_subcloud_info(subcloud_name):
|
||||
LOG.exception(e)
|
||||
raise exceptions.PrestagePreCheckFailedException(
|
||||
subcloud=subcloud_name,
|
||||
details='Failed to retrieve subcloud system mode and system health.')
|
||||
details="Failed to retrieve subcloud system mode and system health.")
|
||||
|
||||
|
||||
@utils.synchronized('prestage-prepare-packages', external=True)
|
||||
def _run_prestage_prepare_packages(context, subcloud_id):
|
||||
def _run_ansible(context, prestage_command, phase,
|
||||
subcloud, deploy_status,
|
||||
sysadmin_password, oam_floating_ip,
|
||||
ansible_subcloud_inventory_file):
|
||||
|
||||
if deploy_status == consts.PRESTAGE_STATE_PREPARE:
|
||||
LOG.info("Preparing prestage shared packages for subcloud: %s, version: %s",
|
||||
subcloud.name, SW_VERSION)
|
||||
else:
|
||||
LOG.info("Prestaging %s for subcloud: %s, version: %s",
|
||||
phase, subcloud.name, SW_VERSION)
|
||||
|
||||
LOG.info("Running prestage prepare packages script, version=%s "
|
||||
"(subcloud_id=%s)", SW_VERSION, subcloud_id)
|
||||
db_api.subcloud_update(context,
|
||||
subcloud_id,
|
||||
deploy_status=consts.PRESTAGE_STATE_PREPARE)
|
||||
|
||||
if not os.path.exists(PREPARE_PRESTAGE_PACKAGES_SCRIPT):
|
||||
LOG.error("Prepare prestage packages script does not exist: %s",
|
||||
PREPARE_PRESTAGE_PACKAGES_SCRIPT)
|
||||
return False
|
||||
|
||||
LOG.info("Executing script: %s --release-id %s",
|
||||
PREPARE_PRESTAGE_PACKAGES_SCRIPT, SW_VERSION)
|
||||
try:
|
||||
output = subprocess.check_output(
|
||||
[PREPARE_PRESTAGE_PACKAGES_SCRIPT, "--release-id", SW_VERSION],
|
||||
stderr=subprocess.STDOUT)
|
||||
LOG.info("%s output:\n%s", PREPARE_PRESTAGE_PACKAGES_SCRIPT, output)
|
||||
except subprocess.CalledProcessError:
|
||||
LOG.exception("Failed to prepare prestage packages for %s", SW_VERSION)
|
||||
return False
|
||||
|
||||
LOG.info("Prestage prepare packages successful")
|
||||
return True
|
||||
|
||||
|
||||
def _run_prestage_ansible(context, subcloud, oam_floating_ip,
|
||||
sysadmin_password, is_upgrade):
|
||||
|
||||
subcloud.id,
|
||||
deploy_status=deploy_status)
|
||||
log_file = os.path.join(consts.DC_ANSIBLE_LOG_DIR, subcloud.name) + \
|
||||
'_prestage_playbook_output.log'
|
||||
|
||||
# Ansible inventory filename for the specified subcloud
|
||||
ansible_subcloud_inventory_file = \
|
||||
utils.get_ansible_filename(subcloud.name, PRESTAGE_FILE_POSTFIX)
|
||||
'_playbook_output.log'
|
||||
|
||||
# Create the ansible inventory for the new subcloud
|
||||
utils.create_subcloud_inventory_with_admin_creds(
|
||||
subcloud.name,
|
||||
ansible_subcloud_inventory_file,
|
||||
oam_floating_ip,
|
||||
ansible_pass=sysadmin_password)
|
||||
|
||||
def _run_ansible(prestage_command, phase, deploy_status):
|
||||
LOG.info("Prestaging %s for subcloud: %s, version: %s",
|
||||
phase, subcloud.name, SW_VERSION)
|
||||
db_api.subcloud_update(context,
|
||||
subcloud.id,
|
||||
deploy_status=deploy_status)
|
||||
try:
|
||||
run_playbook(log_file, prestage_command)
|
||||
except PlaybookExecutionFailed:
|
||||
LOG.error("Failed to run the prestage %s playbook"
|
||||
" for subcloud %s, check individual log at "
|
||||
"%s for detailed output.",
|
||||
phase, subcloud.name, log_file)
|
||||
return False
|
||||
|
||||
LOG.info("Prestage %s successful for subcloud %s",
|
||||
phase, subcloud.name)
|
||||
return True
|
||||
|
||||
# Always run prestage_packages.yml playbook.
|
||||
#
|
||||
# Pass the image_list_file to the prestage_images.yml playbook if the
|
||||
# prestage images file has been uploaded for the target software
|
||||
# version. If this file does not exist and the prestage is for upgrade,
|
||||
# skip calling prestage_images.yml playbook.
|
||||
#
|
||||
# Ensure the final state is either prestage-failed or prestage-complete
|
||||
# regardless whether prestage_images.yml playbook is skipped or not.
|
||||
#
|
||||
ansible_pass=base64.b64decode(sysadmin_password).decode('utf-8'))
|
||||
try:
|
||||
extra_vars_str = "software_version=%s" % SW_VERSION
|
||||
if not _run_ansible(["ansible-playbook",
|
||||
ANSIBLE_PRESTAGE_SUBCLOUD_PACKAGES_PLAYBOOK,
|
||||
"--inventory", ansible_subcloud_inventory_file,
|
||||
"--extra-vars", extra_vars_str],
|
||||
"packages",
|
||||
consts.PRESTAGE_STATE_PACKAGES):
|
||||
return False
|
||||
|
||||
image_list_exists = \
|
||||
os.path.exists(PREPARE_PRESTAGE_PACKAGES_IMAGES_LIST)
|
||||
LOG.debug("prestage images list: %s, exists: %s",
|
||||
PREPARE_PRESTAGE_PACKAGES_IMAGES_LIST, image_list_exists)
|
||||
if is_upgrade and image_list_exists:
|
||||
extra_vars_str += (" image_list_file=%s" %
|
||||
PREPARE_PRESTAGE_PACKAGES_IMAGES_LIST)
|
||||
|
||||
if not is_upgrade or (is_upgrade and image_list_exists):
|
||||
if not _run_ansible(["ansible-playbook",
|
||||
ANSIBLE_PRESTAGE_SUBCLOUD_IMAGES_PLAYBOOK,
|
||||
"--inventory",
|
||||
ansible_subcloud_inventory_file,
|
||||
"--extra-vars", extra_vars_str],
|
||||
"images",
|
||||
consts.PRESTAGE_STATE_IMAGES):
|
||||
return False
|
||||
else:
|
||||
LOG.info("Skipping ansible prestage images step, upgrade: %s,"
|
||||
" image_list_exists: %s",
|
||||
is_upgrade, image_list_exists)
|
||||
return True
|
||||
|
||||
run_playbook(log_file, prestage_command)
|
||||
except PlaybookExecutionFailed as ex:
|
||||
msg = ("Prestaging %s failed for subcloud %s,"
|
||||
" check individual log at %s for detailed output."
|
||||
% (phase, subcloud.name, log_file))
|
||||
LOG.exception("%s: %s", msg, ex)
|
||||
raise Exception(msg)
|
||||
finally:
|
||||
utils.delete_subcloud_inventory(ansible_subcloud_inventory_file)
|
||||
|
||||
LOG.info("Prestage %s successful for subcloud %s",
|
||||
phase, subcloud.name)
|
||||
|
||||
|
||||
def prestage_packages(context, subcloud, payload):
|
||||
"""Run the prestage packages ansible script."""
|
||||
|
||||
# Ansible inventory filename for the specified subcloud
|
||||
ansible_subcloud_inventory_file = \
|
||||
utils.get_ansible_filename(subcloud.name,
|
||||
ANSIBLE_PRESTAGE_INVENTORY_SUFFIX)
|
||||
|
||||
extra_vars_str = "software_version=%s" % SW_VERSION
|
||||
_run_ansible(context,
|
||||
["ansible-playbook",
|
||||
ANSIBLE_PRESTAGE_SUBCLOUD_PACKAGES_PLAYBOOK,
|
||||
"--inventory", ansible_subcloud_inventory_file,
|
||||
"--extra-vars", extra_vars_str],
|
||||
"packages",
|
||||
subcloud,
|
||||
consts.PRESTAGE_STATE_PACKAGES,
|
||||
payload['sysadmin_password'],
|
||||
payload['oam_floating_ip'],
|
||||
ansible_subcloud_inventory_file)
|
||||
|
||||
|
||||
def prestage_images(context, subcloud, payload):
|
||||
"""Run the prestage images ansible script.
|
||||
|
||||
Approach:
|
||||
|
||||
If the prestage images file has been uploaded for the target software
|
||||
version then pass the image_list_file to the prestage_images.yml playbook
|
||||
If this file does not exist and the prestage is for upgrade,
|
||||
skip calling prestage_images.yml playbook.
|
||||
|
||||
Ensure the final state is either prestage-failed or prestage-complete
|
||||
regardless whether prestage_images.yml playbook is skipped or not.
|
||||
"""
|
||||
image_list_file = os.path.join(DEPLOY_BASE_DIR,
|
||||
utils.get_filename_by_prefix(
|
||||
DEPLOY_BASE_DIR, 'prestage_images'))
|
||||
image_list_exists = \
|
||||
image_list_file is not None and os.path.exists(image_list_file)
|
||||
LOG.debug("prestage images list: %s, exists: %s",
|
||||
image_list_file, image_list_exists)
|
||||
|
||||
upgrade = is_upgrade(subcloud.software_version)
|
||||
|
||||
extra_vars_str = "software_version=%s" % SW_VERSION
|
||||
if upgrade and image_list_exists:
|
||||
extra_vars_str += (" image_list_file=%s" % image_list_file)
|
||||
|
||||
# Ansible inventory filename for the specified subcloud
|
||||
ansible_subcloud_inventory_file = \
|
||||
utils.get_ansible_filename(subcloud.name,
|
||||
ANSIBLE_PRESTAGE_INVENTORY_SUFFIX)
|
||||
|
||||
if not upgrade or (upgrade and image_list_exists):
|
||||
_run_ansible(context,
|
||||
["ansible-playbook",
|
||||
ANSIBLE_PRESTAGE_SUBCLOUD_IMAGES_PLAYBOOK,
|
||||
"--inventory", ansible_subcloud_inventory_file,
|
||||
"--extra-vars", extra_vars_str],
|
||||
"images",
|
||||
subcloud,
|
||||
consts.PRESTAGE_STATE_IMAGES,
|
||||
payload['sysadmin_password'],
|
||||
payload['oam_floating_ip'],
|
||||
ansible_subcloud_inventory_file)
|
||||
else:
|
||||
LOG.info("Skipping ansible prestage images step, upgrade: %s,"
|
||||
" image_list_exists: %s",
|
||||
upgrade, image_list_exists)
|
||||
|
@@ -284,6 +284,10 @@ def synchronized(name, external=True, fair=False):
|
||||
|
||||
|
||||
def get_filename_by_prefix(dir_path, prefix):
|
||||
"""Returns the first filename found matching 'prefix' within 'dir_path'
|
||||
|
||||
Note: returns base filename only - result does not include dir_path
|
||||
"""
|
||||
for filename in os.listdir(dir_path):
|
||||
if filename.startswith(prefix):
|
||||
return filename
|
||||
|
@@ -351,11 +351,13 @@ def sw_update_strategy_get(context, update_type=None):
|
||||
return IMPL.sw_update_strategy_get(context, update_type=update_type)
|
||||
|
||||
|
||||
def sw_update_strategy_update(context, state=None, update_type=None):
|
||||
def sw_update_strategy_update(context, state=None,
|
||||
update_type=None, additional_args=None):
|
||||
"""Update a sw update or raise if it does not exist."""
|
||||
return IMPL.sw_update_strategy_update(context,
|
||||
state,
|
||||
update_type=update_type)
|
||||
update_type=update_type,
|
||||
additional_args=additional_args)
|
||||
|
||||
|
||||
def sw_update_strategy_destroy(context, update_type=None):
|
||||
|
@@ -540,12 +540,20 @@ def sw_update_strategy_create(context, type, subcloud_apply_type,
|
||||
|
||||
|
||||
@require_admin_context
|
||||
def sw_update_strategy_update(context, state=None, update_type=None):
|
||||
def sw_update_strategy_update(context, state=None,
|
||||
update_type=None, additional_args=None):
|
||||
with write_session() as session:
|
||||
sw_update_strategy_ref = \
|
||||
sw_update_strategy_get(context, update_type=update_type)
|
||||
if state is not None:
|
||||
sw_update_strategy_ref.state = state
|
||||
if additional_args is not None:
|
||||
if sw_update_strategy_ref.extra_args is None:
|
||||
sw_update_strategy_ref.extra_args = additional_args
|
||||
else:
|
||||
# extend the existing dictionary
|
||||
sw_update_strategy_ref.extra_args = dict(
|
||||
sw_update_strategy_ref.extra_args, **additional_args)
|
||||
sw_update_strategy_ref.save(session)
|
||||
return sw_update_strategy_ref
|
||||
|
||||
|
@@ -429,6 +429,7 @@ class OrchThread(threading.Thread):
|
||||
LOG.exception("(%s) exception during delete"
|
||||
% self.update_type)
|
||||
raise e
|
||||
LOG.info("(%s) Finished deleting update strategy" % self.update_type)
|
||||
|
||||
def delete_subcloud_strategy(self, strategy_step):
|
||||
"""Delete the update strategy in this subcloud
|
||||
@@ -448,6 +449,10 @@ class OrchThread(threading.Thread):
|
||||
|
||||
def do_delete_subcloud_strategy(self, strategy_step):
|
||||
"""Delete the vim strategy in this subcloud"""
|
||||
|
||||
if self.vim_strategy_name is None:
|
||||
return
|
||||
|
||||
region = self.get_region_name(strategy_step)
|
||||
|
||||
LOG.info("(%s) Deleting vim strategy:(%s) for region:(%s)"
|
||||
|
@@ -0,0 +1,51 @@
|
||||
# Copyright (c) 2022 Wind River Systems, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
from oslo_log import log as logging
|
||||
|
||||
from dcmanager.common import consts
|
||||
from dcmanager.orchestrator.orch_thread import OrchThread
|
||||
from dcmanager.orchestrator.states.prestage import states
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PrestageOrchThread(OrchThread):
|
||||
"""Prestage Orchestration Thread"""
|
||||
|
||||
# Every state in prestage orchestration must have an operator
|
||||
# The states are listed here in their typical execution order
|
||||
STATE_OPERATORS = {
|
||||
consts.STRATEGY_STATE_PRESTAGE_PRE_CHECK:
|
||||
states.PrestagePreCheckState,
|
||||
consts.STRATEGY_STATE_PRESTAGE_PREPARE:
|
||||
states.PrestagePrepareState,
|
||||
consts.STRATEGY_STATE_PRESTAGE_PACKAGES:
|
||||
states.PrestagePackagesState,
|
||||
consts.STRATEGY_STATE_PRESTAGE_IMAGES:
|
||||
states.PrestageImagesState,
|
||||
}
|
||||
|
||||
def __init__(self, strategy_lock, audit_rpc_client):
|
||||
super(PrestageOrchThread, self).__init__(
|
||||
strategy_lock,
|
||||
audit_rpc_client,
|
||||
consts.SW_UPDATE_TYPE_PRESTAGE,
|
||||
None,
|
||||
consts.STRATEGY_STATE_PRESTAGE_PRE_CHECK)
|
||||
|
||||
def trigger_audit(self):
|
||||
"""Trigger an audit"""
|
||||
pass
|
@@ -0,0 +1,174 @@
|
||||
#
|
||||
# Copyright (c) 2022 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
import abc
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from dcmanager.common import consts
|
||||
from dcmanager.common import exceptions
|
||||
from dcmanager.common import prestage
|
||||
from dcmanager.common import utils
|
||||
from dcmanager.db import api as db_api
|
||||
from dcmanager.orchestrator.states.base import BaseState
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PrestageState(BaseState):
|
||||
"""Perform prepare operation"""
|
||||
|
||||
def __init__(self, next_state, region_name):
|
||||
super(PrestageState, self).__init__(
|
||||
next_state=next_state, region_name=region_name)
|
||||
|
||||
@abc.abstractmethod
|
||||
def _do_state_action(self, strategy_step):
|
||||
pass
|
||||
|
||||
def perform_state_action(self, strategy_step):
|
||||
"""Wrapper to ensure proper error handling"""
|
||||
try:
|
||||
self._do_state_action(strategy_step)
|
||||
|
||||
except Exception:
|
||||
prestage.prestage_fail(self.context, strategy_step.subcloud.id)
|
||||
raise
|
||||
|
||||
# state machine can proceed to the next state
|
||||
return self.next_state
|
||||
|
||||
|
||||
class PrestagePreCheckState(PrestageState):
|
||||
"""Perform pre check operations"""
|
||||
|
||||
def __init__(self, region_name):
|
||||
super(PrestagePreCheckState, self).__init__(
|
||||
next_state=consts.STRATEGY_STATE_PRESTAGE_PREPARE,
|
||||
region_name=region_name)
|
||||
|
||||
@utils.synchronized('prestage-update-extra-args', external=True)
|
||||
def _update_oam_floating_ip(self, strategy_step, oam_floating_ip):
|
||||
# refresh the extra_args
|
||||
extra_args = utils.get_sw_update_strategy_extra_args(self.context)
|
||||
if 'oam_floating_ip_dict' in extra_args:
|
||||
LOG.debug("Updating oam_floating_ip_dict: %s: %s",
|
||||
strategy_step.subcloud.name, oam_floating_ip)
|
||||
oam_floating_ip_dict = extra_args['oam_floating_ip_dict']
|
||||
oam_floating_ip_dict[strategy_step.subcloud.name] \
|
||||
= oam_floating_ip
|
||||
else:
|
||||
LOG.debug("Creating oam_floating_ip_dict: %s: %s",
|
||||
strategy_step.subcloud.name, oam_floating_ip)
|
||||
oam_floating_ip_dict = {
|
||||
strategy_step.subcloud.name: oam_floating_ip
|
||||
}
|
||||
db_api.sw_update_strategy_update(
|
||||
self.context, state=None, update_type=None,
|
||||
additional_args={'oam_floating_ip_dict': oam_floating_ip_dict})
|
||||
|
||||
def _do_state_action(self, strategy_step):
|
||||
extra_args = utils.get_sw_update_strategy_extra_args(self.context)
|
||||
if extra_args is None:
|
||||
message = "Prestage pre-check: missing all mandatory arguments"
|
||||
self.error_log(strategy_step, message)
|
||||
raise Exception(message)
|
||||
|
||||
payload = {
|
||||
'sysadmin_password': extra_args['sysadmin_password'],
|
||||
'force': extra_args['force']
|
||||
}
|
||||
try:
|
||||
oam_floating_ip = prestage.validate_prestage(
|
||||
strategy_step.subcloud, payload)
|
||||
|
||||
self._update_oam_floating_ip(strategy_step, oam_floating_ip)
|
||||
if strategy_step.stage == 1:
|
||||
# Note: this cleanup happens for every subcloud, but they are all
|
||||
# processed before moving on to the next strategy step
|
||||
# TODO(kmacleod) although this is a quick check, it is
|
||||
# synchronized, so we may want to figure out a better
|
||||
# way to only run this once
|
||||
prestage.cleanup_failed_preparation()
|
||||
prestage.prestage_start(self.context, strategy_step.subcloud.id)
|
||||
|
||||
except exceptions.PrestagePreCheckFailedException as ex:
|
||||
if ex.orch_skip:
|
||||
self.info_log(strategy_step,
|
||||
"Pre-check: skipping subcloud: %s" % ex)
|
||||
|
||||
# Update the details to show that this subcloud has been skipped
|
||||
db_api.strategy_step_update(self.context,
|
||||
strategy_step.subcloud.id,
|
||||
details=str(ex))
|
||||
|
||||
self.override_next_state(consts.STRATEGY_STATE_COMPLETE)
|
||||
else:
|
||||
self.info_log(strategy_step, "Pre-check failed: %s" % ex)
|
||||
raise
|
||||
else:
|
||||
self.info_log(strategy_step, "Pre-check pass")
|
||||
|
||||
|
||||
class PrestagePrepareState(PrestageState):
|
||||
"""Perform prepare operation"""
|
||||
|
||||
def __init__(self, region_name):
|
||||
super(PrestagePrepareState, self).__init__(
|
||||
next_state=consts.STRATEGY_STATE_PRESTAGE_PACKAGES,
|
||||
region_name=region_name)
|
||||
|
||||
def _do_state_action(self, strategy_step):
|
||||
extra_args = utils.get_sw_update_strategy_extra_args(self.context)
|
||||
payload = {
|
||||
'sysadmin_password': extra_args['sysadmin_password'],
|
||||
'oam_floating_ip':
|
||||
extra_args['oam_floating_ip_dict'][strategy_step.subcloud.name],
|
||||
'force': extra_args['force']
|
||||
}
|
||||
prestage.prestage_prepare(self.context, strategy_step.subcloud, payload)
|
||||
self.info_log(strategy_step, "Prepare finished")
|
||||
|
||||
|
||||
class PrestagePackagesState(PrestageState):
|
||||
"""Perform prestage packages operation"""
|
||||
|
||||
def __init__(self, region_name):
|
||||
super(PrestagePackagesState, self).__init__(
|
||||
next_state=consts.STRATEGY_STATE_PRESTAGE_IMAGES,
|
||||
region_name=region_name)
|
||||
|
||||
def _do_state_action(self, strategy_step):
|
||||
extra_args = utils.get_sw_update_strategy_extra_args(self.context)
|
||||
payload = {
|
||||
'sysadmin_password': extra_args['sysadmin_password'],
|
||||
'oam_floating_ip':
|
||||
extra_args['oam_floating_ip_dict'][strategy_step.subcloud.name],
|
||||
'force': extra_args['force']
|
||||
}
|
||||
prestage.prestage_packages(self.context,
|
||||
strategy_step.subcloud, payload)
|
||||
self.info_log(strategy_step, "Packages finished")
|
||||
|
||||
|
||||
class PrestageImagesState(PrestageState):
|
||||
"""Perform prestage images operation"""
|
||||
|
||||
def __init__(self, region_name):
|
||||
super(PrestageImagesState, self).__init__(
|
||||
next_state=consts.STRATEGY_STATE_COMPLETE,
|
||||
region_name=region_name)
|
||||
|
||||
def _do_state_action(self, strategy_step):
|
||||
extra_args = utils.get_sw_update_strategy_extra_args(self.context)
|
||||
payload = {
|
||||
'sysadmin_password': extra_args['sysadmin_password'],
|
||||
'oam_floating_ip':
|
||||
extra_args['oam_floating_ip_dict'][strategy_step.subcloud.name],
|
||||
'force': extra_args['force']
|
||||
}
|
||||
prestage.prestage_images(self.context, strategy_step.subcloud, payload)
|
||||
self.info_log(strategy_step, "Images finished")
|
||||
prestage.prestage_complete(self.context, strategy_step.subcloud.id)
|
@@ -24,6 +24,7 @@ from dcmanager.audit import rpcapi as dcmanager_audit_rpc_client
|
||||
from dcmanager.common import consts
|
||||
from dcmanager.common import exceptions
|
||||
from dcmanager.common import manager
|
||||
from dcmanager.common import prestage
|
||||
from dcmanager.common import utils
|
||||
from dcmanager.db import api as db_api
|
||||
from dcmanager.orchestrator.fw_update_orch_thread import FwUpdateOrchThread
|
||||
@@ -32,6 +33,7 @@ from dcmanager.orchestrator.kube_rootca_update_orch_thread \
|
||||
from dcmanager.orchestrator.kube_upgrade_orch_thread \
|
||||
import KubeUpgradeOrchThread
|
||||
from dcmanager.orchestrator.patch_orch_thread import PatchOrchThread
|
||||
from dcmanager.orchestrator.prestage_orch_thread import PrestageOrchThread
|
||||
from dcmanager.orchestrator.sw_upgrade_orch_thread import SwUpgradeOrchThread
|
||||
from dcorch.common import consts as dcorch_consts
|
||||
|
||||
@@ -77,6 +79,10 @@ class SwUpdateManager(manager.Manager):
|
||||
self.audit_rpc_client)
|
||||
self.kube_rootca_update_orch_thread.start()
|
||||
|
||||
self.prestage_orch_thread = PrestageOrchThread(self.strategy_lock,
|
||||
self.audit_rpc_client)
|
||||
self.prestage_orch_thread.start()
|
||||
|
||||
def stop(self):
|
||||
# Stop (and join) the worker threads
|
||||
# - patch orchestration thread
|
||||
@@ -88,12 +94,15 @@ class SwUpdateManager(manager.Manager):
|
||||
# - fw update orchestration thread
|
||||
self.fw_update_orch_thread.stop()
|
||||
self.fw_update_orch_thread.join()
|
||||
# - kube upgrade orchestration thread
|
||||
# - kube upgrade orchestration thread
|
||||
self.kube_upgrade_orch_thread.stop()
|
||||
self.kube_upgrade_orch_thread.join()
|
||||
# - kube rootca update orchestration thread
|
||||
self.kube_rootca_update_orch_thread.stop()
|
||||
self.kube_rootca_update_orch_thread.join()
|
||||
# - prestage orchestration thread
|
||||
self.prestage_orch_thread.stop()
|
||||
self.prestage_orch_thread.join()
|
||||
|
||||
def _validate_subcloud_status_sync(self, strategy_type,
|
||||
subcloud_status, force,
|
||||
@@ -148,6 +157,12 @@ class SwUpdateManager(manager.Manager):
|
||||
dcorch_consts.ENDPOINT_TYPE_KUBE_ROOTCA and
|
||||
subcloud_status.sync_status ==
|
||||
consts.SYNC_STATUS_OUT_OF_SYNC)
|
||||
elif strategy_type == consts.SW_UPDATE_TYPE_PRESTAGE:
|
||||
# For prestage we reuse the ENDPOINT_TYPE_LOAD.
|
||||
# We just need to key off a unique endpoint,
|
||||
# so that the strategy is created only once.
|
||||
return (subcloud_status.endpoint_type
|
||||
== dcorch_consts.ENDPOINT_TYPE_LOAD)
|
||||
# Unimplemented strategy_type status check. Log an error
|
||||
LOG.error("_validate_subcloud_status_sync for %s not implemented" %
|
||||
strategy_type)
|
||||
@@ -295,6 +310,7 @@ class SwUpdateManager(manager.Manager):
|
||||
# Has the user specified a specific subcloud?
|
||||
# todo(abailey): refactor this code to use classes
|
||||
cloud_name = payload.get('cloud_name')
|
||||
prestage_global_validated = False
|
||||
if cloud_name and cloud_name != consts.SYSTEM_CONTROLLER_NAME:
|
||||
# Make sure subcloud exists
|
||||
try:
|
||||
@@ -354,6 +370,15 @@ class SwUpdateManager(manager.Manager):
|
||||
raise exceptions.BadRequest(
|
||||
resource='strategy',
|
||||
msg='Subcloud %s does not require patching' % cloud_name)
|
||||
elif strategy_type == consts.SW_UPDATE_TYPE_PRESTAGE:
|
||||
# Do initial validation for subcloud
|
||||
try:
|
||||
prestage.global_prestage_validate(payload)
|
||||
prestage_global_validated = True
|
||||
prestage.initial_subcloud_validate(subcloud)
|
||||
except exceptions.PrestagePreCheckFailedException as ex:
|
||||
raise exceptions.BadRequest(resource='strategy',
|
||||
msg=str(ex))
|
||||
|
||||
extra_args = None
|
||||
# kube rootca update orchestration supports extra creation args
|
||||
@@ -373,6 +398,20 @@ class SwUpdateManager(manager.Manager):
|
||||
consts.EXTRA_ARGS_TO_VERSION:
|
||||
payload.get(consts.EXTRA_ARGS_TO_VERSION),
|
||||
}
|
||||
elif strategy_type == consts.SW_UPDATE_TYPE_PRESTAGE:
|
||||
if not prestage_global_validated:
|
||||
try:
|
||||
prestage.global_prestage_validate(payload)
|
||||
except exceptions.PrestagePreCheckFailedException as ex:
|
||||
raise exceptions.BadRequest(
|
||||
resource='strategy',
|
||||
msg=str(ex))
|
||||
|
||||
extra_args = {
|
||||
consts.EXTRA_ARGS_SYSADMIN_PASSWORD:
|
||||
payload.get(consts.EXTRA_ARGS_SYSADMIN_PASSWORD),
|
||||
consts.EXTRA_ARGS_FORCE: force
|
||||
}
|
||||
|
||||
# Don't create a strategy if any of the subclouds is online and the
|
||||
# relevant sync status is unknown. Offline subcloud is skipped unless
|
||||
@@ -386,6 +425,7 @@ class SwUpdateManager(manager.Manager):
|
||||
else:
|
||||
subclouds = db_api.subcloud_get_all_with_status(context)
|
||||
|
||||
subclouds_processed = list()
|
||||
for subcloud, subcloud_status in subclouds:
|
||||
if (cloud_name and subcloud.name != cloud_name or
|
||||
subcloud.management_state != consts.MANAGEMENT_MANAGED):
|
||||
@@ -448,6 +488,16 @@ class SwUpdateManager(manager.Manager):
|
||||
resource='strategy',
|
||||
msg='Kube rootca update sync status is unknown for '
|
||||
'one or more subclouds')
|
||||
elif strategy_type == consts.SW_UPDATE_TYPE_PRESTAGE:
|
||||
if subcloud.name not in subclouds_processed:
|
||||
# Do initial validation for subcloud
|
||||
try:
|
||||
prestage.initial_subcloud_validate(subcloud)
|
||||
except exceptions.PrestagePreCheckFailedException:
|
||||
LOG.warn("Excluding subcloud from prestage strategy: %s",
|
||||
subcloud.name)
|
||||
continue
|
||||
subclouds_processed.append(subcloud.name)
|
||||
|
||||
# handle extra_args processing such as staging to the vault
|
||||
self._process_extra_args_creation(strategy_type, extra_args)
|
||||
@@ -519,7 +569,10 @@ class SwUpdateManager(manager.Manager):
|
||||
status,
|
||||
force,
|
||||
subcloud.availability_status):
|
||||
LOG.debug("Created for %s" % subcloud.id)
|
||||
LOG.debug("Creating strategy_step for endpoint_type: %s, "
|
||||
"sync_status: %s, subcloud: %s, id: %s",
|
||||
status.endpoint_type, status.sync_status,
|
||||
subcloud.name, subcloud.id)
|
||||
db_api.strategy_step_create(
|
||||
context,
|
||||
subcloud.id,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2017-2021 Wind River Systems, Inc.
|
||||
# Copyright (c) 2017-2022 Wind River Systems, Inc.
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
@@ -164,6 +164,20 @@ class TestSwUpdate(base.DCManagerTestCase):
|
||||
self.fake_kube_rootca_update_orch_thread
|
||||
self.addCleanup(p.stop)
|
||||
|
||||
if strategy_type == consts.SW_UPDATE_TYPE_PRESTAGE:
|
||||
sw_update_manager.PrestageOrchThread.stopped = lambda x: False
|
||||
worker = \
|
||||
sw_update_manager.PrestageOrchThread(mock_strategy_lock,
|
||||
mock_dcmanager_audit_api)
|
||||
else:
|
||||
# mock the prestage orch thread
|
||||
self.fake_prestage_orch_thread = FakeOrchThread()
|
||||
p = mock.patch.object(sw_update_manager, 'PrestageOrchThread')
|
||||
self.mock_prestage_orch_thread = p.start()
|
||||
self.mock_prestage_orch_thread.return_value = \
|
||||
self.fake_prestage_orch_thread
|
||||
self.addCleanup(p.stop)
|
||||
|
||||
return worker
|
||||
|
||||
def setup_subcloud(self):
|
||||
|
@@ -595,6 +595,13 @@ class TestSwUpdateManager(base.DCManagerTestCase):
|
||||
self.fake_kube_rootca_update_orch_thread
|
||||
self.addCleanup(p.stop)
|
||||
|
||||
self.fake_prestage_orch_thread = FakeOrchThread()
|
||||
p = mock.patch.object(sw_update_manager, 'PrestageOrchThread')
|
||||
self.mock_prestage_orch_thread = p.start()
|
||||
self.mock_prestage_orch_thread.return_value = \
|
||||
self.fake_prestage_orch_thread
|
||||
self.addCleanup(p.stop)
|
||||
|
||||
# Mock the dcmanager audit API
|
||||
self.fake_dcmanager_audit_api = FakeDCManagerAuditAPI()
|
||||
p = mock.patch('dcmanager.audit.rpcapi.ManagerAuditClient')
|
||||
|
Reference in New Issue
Block a user