deploy activate for major release

This change implements major release deploy activate operation.

Execute the deploy activate scripts after verify all required state for
major release deploy activate in separate sub-process. Deploy state is
updated to "activate-done" when activate operation completes successfully
and "activate-failed" otherwise.

TCs:
    Passed: USM major release deploy activate completed successfully
    Passed: USM major release deploy activate failed
    Passed: USM major release deploy activate after activate failed

Story: 2010676
Task: 50082

Change-Id: I8810674cdc3c67700ff7c419528704c4b8905f51
Signed-off-by: Bin Qian <bin.qian@windriver.com>
This commit is contained in:
Bin Qian 2024-05-10 04:20:09 +00:00
parent 6decc21117
commit 61709d3d64
5 changed files with 139 additions and 17 deletions

View File

@ -37,6 +37,7 @@ console_scripts =
software-agent = software.software_agent:main
software-migrate = software.utilities.migrate:migrate
software-deploy-update = software.utilities.update_deploy_state:update_state
software-deploy-activate = software.utilities.activate:activate
[wheel]

View File

@ -191,8 +191,9 @@ def require_deploy_state(require_states, prompt):
return res
else:
msg = ""
require_states_text = ", ".join([state.value for state in require_states])
if prompt:
msg = prompt.format(state=state, require_states=require_states)
msg = prompt.format(state=state, require_states=require_states_text)
raise InvalidOperation(msg)
return exec_op
return wrap

View File

@ -51,6 +51,7 @@ from software.exceptions import ReleaseInvalidRequest
from software.exceptions import ReleaseValidationFailure
from software.exceptions import ReleaseIsoDeleteFailure
from software.exceptions import SoftwareServiceError
from software.exceptions import InvalidOperation
from software.release_data import reload_release_data
from software.release_data import get_SWReleaseCollection
from software.software_functions import collect_current_load_for_hosts
@ -708,7 +709,7 @@ class SWMessageDeployStateChanged(messages.PatchMessage):
self.valid = True
self.agent = None
valid_agents = ['deploy-start']
valid_agents = ['deploy-start', 'deploy-activate']
if 'agent' in data:
self.agent = data['agent']
else:
@ -722,7 +723,9 @@ class SWMessageDeployStateChanged(messages.PatchMessage):
valid_state = {
DEPLOY_STATES.START_DONE.value: DEPLOY_STATES.START_DONE,
DEPLOY_STATES.START_FAILED.value: DEPLOY_STATES.START_FAILED
DEPLOY_STATES.START_FAILED.value: DEPLOY_STATES.START_FAILED,
DEPLOY_STATES.ACTIVATE_FAILED.value: DEPLOY_STATES.ACTIVATE_FAILED,
DEPLOY_STATES.ACTIVATE_DONE.value: DEPLOY_STATES.ACTIVATE_DONE
}
if 'deploy-state' in data and data['deploy-state']:
deploy_state = data['deploy-state']
@ -2290,7 +2293,9 @@ class PatchController(PatchService):
deploy_state = DeployState.get_instance()
state_event = {
DEPLOY_STATES.START_DONE: deploy_state.start_done,
DEPLOY_STATES.START_FAILED: deploy_state.start_failed
DEPLOY_STATES.START_FAILED: deploy_state.start_failed,
DEPLOY_STATES.ACTIVATE_DONE: deploy_state.activate_completed,
DEPLOY_STATES.ACTIVATE_FAILED: deploy_state.activate_failed
}
if new_state in state_event:
state_event[new_state]()
@ -2680,11 +2685,66 @@ class PatchController(PatchService):
return dict(info=msg_info, warning=msg_warning, error=msg_error)
def _activate(self):
# TODO(bqian) activate the deployment
deploy = self.db_api_instance.get_deploy_all()
if deploy:
deploy = deploy[0]
else:
msg = "Deployment is missing unexpectedly"
raise InvalidOperation(msg)
deploying = ReleaseState(release_state=states.DEPLOYING)
if deploying.is_major_release_deployment():
return self._activate_major_release(deploy)
else:
return self.activate_patching_release()
def activate_patching_release(self):
deploy_state = DeployState.get_instance()
deploy_state.activate()
# patching release activate operations go here
deploy_state.activate_completed()
return True
def _activate_major_release(self, deploy):
cmd_path = "/usr/bin/software-deploy-activate"
from_release = utils.get_major_release_version(deploy.get("from_release"))
to_release = utils.get_major_release_version(deploy.get("to_release"))
upgrade_activate_cmd = [cmd_path, from_release, to_release]
try:
LOG.info("starting subprocess %s" % ' '.join(upgrade_activate_cmd))
subprocess.Popen(' '.join(upgrade_activate_cmd), start_new_session=True, shell=True)
LOG.info("subprocess started")
except subprocess.SubprocessError as e:
LOG.error("Failed to start command: %s. Error %s" % (' '.join(upgrade_activate_cmd), e))
return False
return True
def _check_pre_activate(self):
# check current deployment, deploy to all hosts have completed,
# the deploy state is host-done, or
# activate-failed' as reattempt from a previous failed activate
deploy_state = DeployState.get_deploy_state()
if deploy_state not in [DEPLOY_STATES.HOST_DONE, DEPLOY_STATES.ACTIVATE_FAILED]:
msg = "Must complete deploying all hosts before activating the deployment"
raise InvalidOperation(msg)
deploy_hosts = self.db_api_instance.get_deploy_host()
invalid_hosts = []
for deploy_host in deploy_hosts:
if deploy_host['state'] not in [states.DEPLOYED]:
invalid_hosts.append(deploy_host)
if len(invalid_hosts) > 0:
msg = "All hosts must have completed deployment before activating the deployment"
for invalid_host in invalid_hosts:
msg += "%s: %s\n" % (invalid_host["hostname"], invalid_host["state"])
raise InvalidOperation(msg)
@require_deploy_state([DEPLOY_STATES.HOST_DONE, DEPLOY_STATES.ACTIVATE_FAILED],
"Must complete deploying all hosts before activating the deployment")
"Activate deployment only when current deployment state is {require_states}")
def software_deploy_activate_api(self) -> dict:
"""
Activates the deployment associated with the release
@ -2694,15 +2754,17 @@ class PatchController(PatchService):
msg_warning = ""
msg_error = ""
self._check_pre_activate()
deploy_state = DeployState.get_instance()
deploy_state.activate()
if self._activate():
deploy_state.activate_completed()
msg_info += "Deployment has been activated.\n"
else:
try:
self._activate()
msg_info = "Deploy activate has started"
except Exception:
deploy_state.activate_failed()
msg_error += "Deployment activation has failed.\n"
raise
return dict(info=msg_info, warning=msg_warning, error=msg_error)

View File

@ -0,0 +1,58 @@
#
# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import argparse
from oslo_log import log
from software.states import DEPLOY_STATES
from software.utilities.update_deploy_state import update_deploy_state
from software.utilities.utils import execute_migration_scripts
from software.utilities.utils import ACTION_ACTIVATE
LOG = log.getLogger(__name__)
def do_activate(from_release, to_release):
agent = 'deploy-activate'
res = True
state = DEPLOY_STATES.ACTIVATE_DONE.value
try:
execute_migration_scripts(from_release, to_release, ACTION_ACTIVATE)
except Exception:
state = DEPLOY_STATES.ACTIVATE_FAILED.value
res = False
finally:
try:
update_deploy_state(agent, deploy_state=state)
if res:
LOG.info("Deploy activate completed successfully")
else:
LOG.error("Deploy activate failed")
except Exception:
LOG.error("Update deploy state failed")
res = False
return res
def activate():
# this is the entry point to start data migration
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument("from_release",
default=False,
help="From release")
parser.add_argument("to_release",
default=False,
help="To release")
args = parser.parse_args()
if do_activate(args.from_release, args.to_release):
exit(0)
else:
exit(1)

View File

@ -32,7 +32,7 @@ def get_udp_socket(server_addr, server_port):
return sock
def update_deploy_state(server_addr, server_port, agent, deploy_state=None, host=None, host_state=None, timeout=1):
def update_deploy_state(agent, deploy_state=None, host=None, host_state=None, timeout=1):
"""
Send MessageDeployStateChanged message to software-controller via
upd packet, wait for ack or raise exception.
@ -47,6 +47,10 @@ def update_deploy_state(server_addr, server_port, agent, deploy_state=None, host
}
"""
server_addr = "controller"
cfg.read_config()
server_port = cfg.controller_port
msg = {
"msgtype": PATCHMSG_DEPLOY_STATE_CHANGED,
"msgversion": 1,
@ -104,9 +108,5 @@ def update_state():
args = parser.parse_args()
server = "controller"
cfg.read_config()
server_port = cfg.controller_port
update_deploy_state(server, int(server_port), args.agent,
deploy_state=args.state,
update_deploy_state(args.agent, deploy_state=args.state,
host=args.host, host_state=args.host_state)