Merge "USM State Machine"

This commit is contained in:
Zuul 2023-06-30 14:44:58 +00:00 committed by Gerrit Code Review
commit 33b379914e
9 changed files with 645 additions and 733 deletions

View File

@ -38,43 +38,31 @@ class SoftwareAPIController(object):
@expose('json')
@expose('query.xml', content_type='application/xml')
def deploy_create(self, *args, **kwargs):
if sc.any_patch_host_installing():
return dict(error="Rejected: One or more nodes are installing releases.")
try:
result = sc.software_deploy_create_api(list(args), **kwargs)
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
sc.software_sync()
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def deploy_delete(self, *args, **kwargs):
def deploy_activate(self, *args):
if sc.any_patch_host_installing():
return dict(error="Rejected: One or more nodes are installing a release.")
try:
result = sc.software_deploy_delete_api(list(args), **kwargs)
result = sc.software_deploy_activate_api(list(args)[0])
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
sc.software_sync()
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def deploy_start(self, *args):
def deploy_complete(self, *args):
if sc.any_patch_host_installing():
return dict(error="Rejected: One or more nodes are installing a release.")
sc.send_latest_feed_commit_to_agent()
try:
result = sc.software_deploy_complete_api(list(args)[0])
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
sc.software_sync()
return dict(info="%s is ready to be deployed on all hosts" % list(args)[0])
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
@ -92,23 +80,29 @@ class SoftwareAPIController(object):
return result
@expose('json')
def is_applied(self, *args):
return sc.is_applied(list(args))
@expose('json')
def is_available(self, *args):
return sc.is_available(list(args))
@expose('json')
@expose('query.xml', content_type='application/xml')
def query(self, **kwargs):
def deploy_start(self, *args, **kwargs):
if sc.any_patch_host_installing():
return dict(error="Rejected: One or more nodes are installing releases.")
try:
sd = sc.software_release_query_cached(**kwargs)
result = sc.software_deploy_start_api(list(args)[0], **kwargs)
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
return dict(sd=sd)
sc.send_latest_feed_commit_to_agent()
sc.software_sync()
return result
@expose('json')
def is_completed(self, *args):
return sc.is_completed(list(args))
@expose('json')
def is_uploaded(self, *args):
return sc.is_uploaded(list(args))
@expose('json')
@expose('show.xml', content_type='application/xml')
@ -142,11 +136,6 @@ class SoftwareAPIController(object):
sc.software_sync()
return result
@expose('json')
@expose('query_hosts.xml', content_type='application/xml')
def query_hosts(self, *args): # pylint: disable=unused-argument
return dict(data=sc.query_host_cache())
@expose('json')
def upload_dir(self, **kwargs):
# todo(abailey): extensions should be configurable or
@ -172,6 +161,21 @@ class SoftwareAPIController(object):
sc.software_sync()
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def query(self, **kwargs):
try:
sd = sc.software_release_query_cached(**kwargs)
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
return dict(sd=sd)
@expose('json')
@expose('query_hosts.xml', content_type='application/xml')
def query_hosts(self, *args): # pylint: disable=unused-argument
return dict(data=sc.query_host_cache())
class RootController:
"""pecan REST API root"""

View File

@ -24,16 +24,21 @@ ADDRESS_VERSION_IPV4 = 4
ADDRESS_VERSION_IPV6 = 6
CONTROLLER_FLOATING_HOSTNAME = "controller"
AVAILABLE = 'Available'
APPLIED = 'Applied'
PARTIAL_APPLY = 'Partial-Apply'
PARTIAL_REMOVE = 'Partial-Remove'
COMMITTED = 'Committed'
ABORTING = 'aborting'
AVAILABLE = 'available'
COMMITTED = 'committed'
DEPLOYED = 'deployed'
DEPLOYING_ACTIVATE = 'deploying-activate'
DEPLOYING_COMPLETE = 'deploying-complete'
DEPLOYING_HOST = 'deploying-host'
DEPLOYING_START = 'deploying-start'
REMOVING = 'removing'
UNAVAILABLE = 'unavailable'
UNKNOWN = 'n/a'
STATUS_DEVELOPEMENT = 'DEV'
STATUS_OBSOLETE = 'OBS'
STATUS_RELEASED = 'REL'
STATUS_DEVELOPEMENT = 'DEV'
PATCH_AGENT_STATE_IDLE = "idle"
PATCH_AGENT_STATE_INSTALLING = "installing"
@ -42,12 +47,12 @@ PATCH_AGENT_STATE_INSTALL_REJECTED = "install-rejected"
SOFTWARE_STORAGE_DIR = "/opt/software"
FEED_OSTREE_BASE_DIR = "/var/www/pages/feed"
OSTREE_BASE_DEPLOYMENT_DIR = "/ostree/deploy/debian/deploy/"
OSTREE_REF = "starlingx"
OSTREE_REMOTE = "debian"
FEED_OSTREE_BASE_DIR = "/var/www/pages/feed"
SYSROOT_OSTREE = "/sysroot/ostree/repo"
OSTREE_BASE_DEPLOYMENT_DIR = "/ostree/deploy/debian/deploy/"
PATCH_SCRIPTS_STAGING_DIR = "/var/www/pages/updates/software-scripts"
SYSROOT_OSTREE = "/sysroot/ostree/repo"
LOOPBACK_INTERFACE_NAME = "lo"
@ -57,7 +62,7 @@ SEMANTIC_ACTIONS = [SEMANTIC_PREAPPLY, SEMANTIC_PREREMOVE]
CHECKOUT_FOLDER = "checked_out_commit"
DEPLOYMENT_STATE_INACTIVE = "Inactive"
DEPLOYMENT_STATE_ACTIVE = "Active"
DEPLOYMENT_STATE_INACTIVE = "Inactive"
DEPLOYMENT_STATE_PRESTAGING = "Prestaging"
DEPLOYMENT_STATE_PRESTAGED = "Prestaged"

View File

@ -95,32 +95,29 @@ def print_software_op_result(req):
hdr_version = "Version"
hdr_rr = "RR"
hdr_state = "State"
hdr_deploy_state = "Deploy State"
width_release = len(hdr_release)
width_version = len(hdr_version)
width_rr = len(hdr_rr)
width_state = len(hdr_state)
width_deploy_state = len(hdr_deploy_state)
show_all = False
for release_id in list(sd):
width_release = max(len(release_id), width_release)
width_deploy_state = max(len(sd[release_id]["deploy_state"]), width_deploy_state)
width_state = max(len(sd[release_id]["state"]), width_state)
if "sw_version" in sd[release_id]:
show_all = True
width_version = max(len(sd[release_id]["sw_version"]), width_version)
if show_all:
print("{0:^{width_release}} {1:^{width_rr}} {2:^{width_version}} {3:^{width_state}} {4:^{width_deploy_state}}".format(
hdr_release, hdr_rr, hdr_version, hdr_state, hdr_deploy_state,
print("{0:^{width_release}} {1:^{width_rr}} {2:^{width_version}} {3:^{width_state}}".format(
hdr_release, hdr_rr, hdr_version, hdr_state,
width_release=width_release, width_rr=width_rr,
width_version=width_version, width_state=width_state, width_deploy_state=width_deploy_state))
width_version=width_version, width_state=width_state))
print("{0} {1} {2} {3} {4}".format(
'=' * width_release, '=' * width_rr, '=' * width_version, '=' * width_state, '=' * width_deploy_state))
print("{0} {1} {2} {3}".format(
'=' * width_release, '=' * width_rr, '=' * width_version, '=' * width_state))
for release_id in sorted(list(sd)):
if "reboot_required" in sd[release_id]:
@ -128,23 +125,20 @@ def print_software_op_result(req):
else:
rr = "Y"
print("{0:<{width_release}} {1:^{width_rr}} {2:^{width_version}} {3:^{width_state}} {4:^{width_deploy_state}}".format(
print("{0:<{width_release}} {1:^{width_rr}} {2:^{width_version}} {3:^{width_state}}".format(
release_id,
rr,
sd[release_id]["sw_version"],
sd[release_id]["deploy_state"],
sd[release_id]["state"],
width_release=width_release, width_rr=width_rr,
width_version=width_version, width_state=width_state,
width_deploy_state=width_deploy_state))
width_version=width_version, width_state=width_state))
else:
print("{0:^{width_release}} {1:^{width_state}} {2:^{width_deploy_state}}".format(
hdr_release, hdr_state, hdr_deploy_state,
width_release=width_release, width_state=width_state,
width_deploy_state=width_deploy_state))
print("{0:^{width_release}} {1:^{width_state}}".format(
hdr_release, hdr_state,
width_release=width_release, width_state=width_state))
print("{0} {1} {2}".format(
'=' * width_release, '=' * width_state, '=' * width_deploy_state))
print("{0} {1}".format(
'=' * width_release, '=' * width_state))
for release_id in sorted(list(sd)):
if "reboot_required" in sd[release_id]:
@ -152,13 +146,12 @@ def print_software_op_result(req):
else:
rr = "Y"
print("{0:<{width_release}} {1:^{width_rr}} {2:^{width_state}} {3:^{width_deploy_state}}".format(
print("{0:<{width_release}} {1:^{width_rr}} {2:^{width_state}}".format(
release_id,
rr,
sd[release_id]["state"],
sd[release_id]["deploy_state"],
width_release=width_release, width_rr=width_rr,
width_state=width_state, width_deploy_state=width_deploy_state))
width_state=width_state))
print("")
@ -197,11 +190,6 @@ def print_release_show_result(req):
print(textwrap.fill(" {0:<15} ".format("State:") + sd[release_id]["state"],
width=TERM_WIDTH, subsequent_indent=' ' * 20))
if sd[release_id]["state"] == "n/a":
if "deploy_state" in sd[release_id] and sd[release_id]["deploy_state"] != "":
print(textwrap.fill(" {0:<15} ".format("Deploy State:") + sd[release_id]["deploy_state"],
width=TERM_WIDTH, subsequent_indent=' ' * 20))
if "status" in sd[release_id] and sd[release_id]["status"] != "":
print(textwrap.fill(" {0:<15} ".format("Status:") + sd[release_id]["status"],
width=TERM_WIDTH, subsequent_indent=' ' * 20))
@ -752,60 +740,13 @@ def release_upload_dir_req(args):
return check_rc(req)
def deploy_create_req(args):
# args.deployment is a list
deployment = args.deployment
# Ignore interrupts during this function
signal.signal(signal.SIGINT, signal.SIG_IGN)
# Issue deploy_create request
deployments = "/".join(deployment)
url = "http://%s/software/deploy_create/%s" % (api_addr, deployments)
headers = {}
append_auth_token_if_required(headers)
req = requests.post(url, headers=headers)
if args.debug:
print_result_debug(req)
else:
print_software_op_result(req)
return check_rc(req)
def deploy_delete_req(args):
# args.deployment is a list
deployment = args.deployment
# Ignore interrupts during this function
signal.signal(signal.SIGINT, signal.SIG_IGN)
# Issue deploy_delete request
deployments = "/".join(deployment)
url = "http://%s/software/deploy_delete/%s" % (api_addr, deployments)
headers = {}
append_auth_token_if_required(headers)
req = requests.post(url, headers=headers)
if args.debug:
print_result_debug(req)
else:
print_software_op_result(req)
return check_rc(req)
def deploy_precheck_req(args):
print(args.deployment)
return 1
def deploy_start_req(args):
# args.deployment is a list
# args.deployment is a string
deployment = args.deployment
# Ignore interrupts during this function
@ -827,13 +768,47 @@ def deploy_start_req(args):
def deploy_activate_req(args):
print(args.deployment)
return 1
# args.deployment is a string
deployment = args.deployment
# Ignore interrupts during this function
signal.signal(signal.SIGINT, signal.SIG_IGN)
# Issue deploy_start request
url = "http://%s/software/deploy_activate/%s" % (api_addr, deployment)
headers = {}
append_auth_token_if_required(headers)
req = requests.post(url, headers=headers)
if args.debug:
print_result_debug(req)
else:
print_software_op_result(req)
return check_rc(req)
def deploy_complete_req(args):
print(args.deployment)
return 1
# args.deployment is a string
deployment = args.deployment
# Ignore interrupts during this function
signal.signal(signal.SIGINT, signal.SIG_IGN)
# Issue deploy_complete request
url = "http://%s/software/deploy_complete/%s" % (api_addr, deployment)
headers = {}
append_auth_token_if_required(headers)
req = requests.post(url, headers=headers)
if args.debug:
print_result_debug(req)
else:
print_software_op_result(req)
return check_rc(req)
def deploy_list_req(args):
@ -1119,8 +1094,6 @@ def check_for_os_region_name(args):
def register_deploy_commands(commands):
"""deploy commands
- create
- delete
- precheck
- start
- host
@ -1151,28 +1124,6 @@ def register_deploy_commands(commands):
)
sub_cmds.required = True
# --- software deploy create -----------------------
cmd = sub_cmds.add_parser(
'create',
help='Create and prestage a software deployment'
)
cmd.set_defaults(cmd='create')
cmd.set_defaults(func=deploy_create_req)
cmd.add_argument('deployment',
nargs="+", # accepts a list
help='Deployment ID to create')
# --- software deploy delete -----------------------
cmd = sub_cmds.add_parser(
'delete',
help='Delete the software deployment'
)
cmd.set_defaults(cmd='delete')
cmd.set_defaults(func=deploy_delete_req)
cmd.add_argument('deployment',
nargs="+",
help='Deployment ID to delete')
# --- software deploy precheck -----------------------
cmd = sub_cmds.add_parser(
'precheck',

File diff suppressed because it is too large Load Diff

View File

@ -38,8 +38,15 @@ except Exception:
# Constants
patch_dir = constants.SOFTWARE_STORAGE_DIR
avail_dir = "%s/metadata/available" % patch_dir
applied_dir = "%s/metadata/applied" % patch_dir
available_dir = "%s/metadata/available" % patch_dir
unavailable_dir = "%s/metadata/unavailable" % patch_dir
deploying_start_dir = "%s/metadata/deploying_start" % patch_dir
deploying_host_dir = "%s/metadata/deploying_host" % patch_dir
deploying_activate_dir = "%s/metadata/deploying_activate" % patch_dir
deploying_complete_dir = "%s/metadata/deploying_complete" % patch_dir
deployed_dir = "%s/metadata/deployed" % patch_dir
removing_dir = "%s/metadata/removing" % patch_dir
aborting_dir = "%s/metadata/aborting" % patch_dir
committed_dir = "%s/metadata/committed" % patch_dir
semantics_dir = "%s/semantics" % patch_dir
@ -241,10 +248,10 @@ class ReleaseData(object):
def update_release(self, updated_release):
for release_id in list(updated_release.metadata):
# Update all fields except deploy_state
cur_deploy_state = self.metadata[release_id]['deploy_state']
# Update all fields except state
cur_state = self.metadata[release_id]['state']
self.metadata[release_id].update(updated_release.metadata[release_id])
self.metadata[release_id]['deploy_state'] = cur_deploy_state
self.metadata[release_id]['state'] = cur_state
def delete_release(self, release_id):
del self.contents[release_id]
@ -286,11 +293,11 @@ class ReleaseData(object):
def parse_metadata(self,
filename,
deploy_state=None):
state=None):
"""
Parse an individual release metadata XML file
:param filename: XML file
:param deploy_state: Indicates Applied, Available, or Committed
:param state: Indicates Applied, Available, or Committed
:return: Release ID
"""
tree = ElementTree.parse(filename)
@ -316,10 +323,7 @@ class ReleaseData(object):
self.metadata[release_id] = {}
self.metadata[release_id]["deploy_state"] = deploy_state
# Release state is unknown at this point
self.metadata[release_id]["state"] = "n/a"
self.metadata[release_id]["state"] = state
self.metadata[release_id]["sw_version"] = "unknown"
@ -372,20 +376,27 @@ class ReleaseData(object):
def load_all_metadata(self,
loaddir,
deploy_state=None):
state=None):
"""
Parse all metadata files in the specified dir
:return:
"""
for fname in glob.glob("%s/*.xml" % loaddir):
self.parse_metadata(fname, deploy_state)
self.parse_metadata(fname, state)
def load_all(self):
# Reset the data
self.__init__()
self.load_all_metadata(applied_dir, deploy_state=constants.APPLIED)
self.load_all_metadata(avail_dir, deploy_state=constants.AVAILABLE)
self.load_all_metadata(committed_dir, deploy_state=constants.COMMITTED)
self.load_all_metadata(available_dir, state=constants.AVAILABLE)
self.load_all_metadata(unavailable_dir, state=constants.UNAVAILABLE)
self.load_all_metadata(deploying_start_dir, state=constants.DEPLOYING_START)
self.load_all_metadata(deploying_host_dir, state=constants.DEPLOYING_HOST)
self.load_all_metadata(deploying_activate_dir, state=constants.DEPLOYING_ACTIVATE)
self.load_all_metadata(deploying_complete_dir, state=constants.DEPLOYING_COMPLETE)
self.load_all_metadata(deployed_dir, state=constants.DEPLOYED)
self.load_all_metadata(removing_dir, state=constants.REMOVING)
self.load_all_metadata(aborting_dir, state=constants.ABORTING)
self.load_all_metadata(committed_dir, state=constants.COMMITTED)
def query_line(self,
release_id,
@ -785,7 +796,7 @@ class PatchFile(object):
@staticmethod
def extract_patch(patch,
metadata_dir=avail_dir,
metadata_dir=available_dir,
metadata_only=False,
existing_content=None,
base_pkgdata=None):

View File

@ -40,11 +40,6 @@ ${error}
${s["sw_version"]}
% endif
</sw_version>
<deploy_state>
% if s["deploy_state"] != "":
${s["deploy_state"]}
% endif
</deploy_state>
<state>
% if s["state"] != "":
${s["state"]}

View File

@ -47,11 +47,6 @@ ${showpatch(release_id)}
${r["sw_version"]}
% endif
</sw_version>
<deploy_state>
% if r["deploy_state"] != "":
${r["deploy_state"]}
% endif
</deploy_state>
<state>
% if r["state"] != "":
${r["state"]}

View File

@ -29,9 +29,8 @@ FAKE_PATCH_1_META = {
"apply_active_release_only": "",
"description": "Patch 1 description",
"install_instructions": "Patch 1 instructions",
"state": STATE_NA,
"reboot_required": PATCH_FLAG_YES,
"deploy_state": STATE_APPLIED,
"state": STATE_APPLIED,
"requires": [],
"status": STATUS_DEV,
"summary": "Patch 1 summary",
@ -45,9 +44,8 @@ FAKE_PATCH_2_META = {
"apply_active_release_only": "",
"description": "Patch 2 description",
"install_instructions": "Patch 2 instructions",
"state": STATE_AVAILABLE,
"reboot_required": PATCH_FLAG_NO,
"deploy_state": STATE_AVAILABLE,
"state": STATE_AVAILABLE,
"requires": [FAKE_PATCH_ID_1],
"status": STATUS_DEV,
"summary": "Patch 2 summary",

View File

@ -37,6 +37,25 @@ def get_major_release_version(sw_release_version):
return None
def compare_release_version(sw_release_version_1, sw_release_version_2):
"""Compares release versions and returns True if first is higher than second """
if not sw_release_version_1 or not sw_release_version_2:
return None
else:
try:
separator = '.'
separated_string_1 = sw_release_version_1.split(separator)
separated_string_2 = sw_release_version_2.split(separator)
if len(separated_string_1) != len(separated_string_2):
return None
for index, val in enumerate(separated_string_1):
if int(val) > int(separated_string_2[index]):
return True
return False
except Exception:
return None
def gethostbyname(hostname):
"""gethostbyname with IPv6 support """
try: