234 lines
7.2 KiB
Python
234 lines
7.2 KiB
Python
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
# Copyright (c) 2024 Wind River Systems, Inc.
|
|
#
|
|
|
|
import os
|
|
from packaging import version
|
|
import shutil
|
|
from software import constants
|
|
from software.exceptions import FileSystemError
|
|
from software.exceptions import InternalError
|
|
from software.software_functions import LOG
|
|
from software import utils
|
|
|
|
|
|
class SWRelease(object):
|
|
'''wrapper class to group matching metadata and contents'''
|
|
|
|
def __init__(self, rel_id, metadata, contents):
|
|
self._id = rel_id
|
|
self._metadata = metadata
|
|
self._contents = contents
|
|
self._sw_version = None
|
|
|
|
@property
|
|
def metadata(self):
|
|
return self._metadata
|
|
|
|
@property
|
|
def contents(self):
|
|
return self._contents
|
|
|
|
@property
|
|
def id(self):
|
|
return self._id
|
|
|
|
@property
|
|
def state(self):
|
|
return self.metadata['state']
|
|
|
|
@staticmethod
|
|
def is_valid_state_transition(from_state, to_state):
|
|
if to_state not in constants.VALID_RELEASE_STATES:
|
|
msg = "Invalid state %s." % to_state
|
|
LOG.error(msg)
|
|
# this is a bug
|
|
raise InternalError(msg)
|
|
|
|
if from_state in constants.RELEASE_STATE_VALID_TRANSITION:
|
|
if to_state in constants.RELEASE_STATE_VALID_TRANSITION[from_state]:
|
|
return True
|
|
return False
|
|
|
|
@staticmethod
|
|
def ensure_state_transition(to_state):
|
|
to_dir = constants.RELEASE_STATE_TO_DIR_MAP[to_state]
|
|
if not os.path.isdir(to_dir):
|
|
try:
|
|
os.makedirs(to_dir, mode=0o755, exist_ok=True)
|
|
except FileExistsError:
|
|
error = "Cannot create directory %s" % to_dir
|
|
raise FileSystemError(error)
|
|
|
|
def update_state(self, state):
|
|
if SWRelease.is_valid_state_transition(self.state, state):
|
|
LOG.info("%s state from %s to %s" % (self.id, self.state, state))
|
|
SWRelease.ensure_state_transition(state)
|
|
|
|
to_dir = constants.RELEASE_STATE_TO_DIR_MAP[state]
|
|
from_dir = constants.RELEASE_STATE_TO_DIR_MAP[self.state]
|
|
try:
|
|
shutil.move("%s/%s-metadata.xml" % (from_dir, self.id),
|
|
"%s/%s-metadata.xml" % (to_dir, self.id))
|
|
except shutil.Error:
|
|
msg = "Failed to move the metadata for %s" % self.id
|
|
LOG.exception(msg)
|
|
raise FileSystemError(msg)
|
|
|
|
self.metadata['state'] = state
|
|
else:
|
|
# this is a bug
|
|
error = "Invalid state transition %s, current is %s, target state is %s" % \
|
|
(self.id, self.state, state)
|
|
LOG.info(error)
|
|
raise InternalError(error)
|
|
|
|
@property
|
|
def sw_release(self):
|
|
'''3 sections MM.mm.pp release version'''
|
|
return self.metadata['sw_version']
|
|
|
|
@property
|
|
def sw_version(self):
|
|
'''2 sections MM.mm software version'''
|
|
if self._sw_version is None:
|
|
self._sw_version = utils.get_major_release_version(self.sw_release)
|
|
return self._sw_version
|
|
|
|
def _get_latest_commit(self):
|
|
num_commits = self.contents['number_of_commits']
|
|
if int(num_commits) > 0:
|
|
commit_tag = "commit%s" % num_commits
|
|
return self.contents[commit_tag]
|
|
else:
|
|
# may consider raise InvalidRelease exception in this case after
|
|
# iso metadata comes with commit id
|
|
LOG.warning("Commit data not found in metadata. Release %s" %
|
|
self.id)
|
|
return None
|
|
|
|
@property
|
|
def commit_id(self):
|
|
commit = self._get_latest_commit()
|
|
if commit is not None:
|
|
return commit['commit']
|
|
else:
|
|
# may consider raise InvalidRelease exception when iso comes with
|
|
# latest commit
|
|
return None
|
|
|
|
def _get_by_key(self, key, default=None):
|
|
if key in self._metadata:
|
|
return self._metadata[key]
|
|
else:
|
|
return default
|
|
|
|
@property
|
|
def summary(self):
|
|
return self._get_by_key('summary')
|
|
|
|
@property
|
|
def description(self):
|
|
return self._get_by_key('description')
|
|
|
|
@property
|
|
def install_instructions(self):
|
|
return self._get_by_key('install_instructions')
|
|
|
|
@property
|
|
def warnings(self):
|
|
return self._get_by_key('warnings')
|
|
|
|
@property
|
|
def status(self):
|
|
return self._get_by_key('status')
|
|
|
|
@property
|
|
def unremovable(self):
|
|
return self._get_by_key('unremovable')
|
|
|
|
@property
|
|
def reboot_required(self):
|
|
return self._get_by_key('reboot_required')
|
|
|
|
@property
|
|
def restart_script(self):
|
|
return self._get_by_key('restart_script')
|
|
|
|
@property
|
|
def commit_checksum(self):
|
|
commit = self._get_latest_commit()
|
|
if commit is not None:
|
|
return commit['checksum']
|
|
else:
|
|
# may consider raise InvalidRelease exception when iso comes with
|
|
# latest commit
|
|
return None
|
|
|
|
@property
|
|
def is_ga_release(self):
|
|
ver = version.parse(self.sw_release)
|
|
_, _, pp = ver.release
|
|
return pp == 0
|
|
|
|
@property
|
|
def is_deletable(self):
|
|
return self.state in constants.DELETABLE_STATE
|
|
|
|
|
|
class SWReleaseCollection(object):
|
|
'''SWReleaseCollection encapsulates aggregated software release collection
|
|
managed by USM.
|
|
'''
|
|
|
|
def __init__(self, release_data):
|
|
self._sw_releases = {}
|
|
for rel_id in release_data.metadata:
|
|
rel_data = release_data.metadata[rel_id]
|
|
contents = release_data.contents[rel_id]
|
|
sw_release = SWRelease(rel_id, rel_data, contents)
|
|
self._sw_releases[rel_id] = sw_release
|
|
|
|
def get_release_by_id(self, rel_id):
|
|
if rel_id in self._sw_releases:
|
|
return self._sw_releases[rel_id]
|
|
return None
|
|
|
|
def get_release_by_commit_id(self, commit_id):
|
|
for _, sw_release in self._sw_releases:
|
|
if sw_release.commit_id == commit_id:
|
|
return sw_release
|
|
return None
|
|
|
|
def iterate_releases_by_state(self, state):
|
|
'''return iteration of releases matching specified state.
|
|
sorted by id in ascending order
|
|
'''
|
|
sorted_list = sorted(self._sw_releases)
|
|
for rel_id in sorted_list:
|
|
rel_data = self._sw_releases[rel_id]
|
|
if rel_data.metadata['state'] == state:
|
|
yield rel_data
|
|
|
|
def iterate_releases(self):
|
|
'''return iteration of all releases sorted by id in ascending order'''
|
|
sorted_list = sorted(self._sw_releases)
|
|
for rel_id in sorted_list:
|
|
yield self._sw_releases[rel_id]
|
|
|
|
def update_state(self, list_of_releases, state):
|
|
for release_id in list_of_releases:
|
|
release = self.get_release_by_id(release_id)
|
|
if release is not None:
|
|
if SWRelease.is_valid_state_transition(release.state, state):
|
|
SWRelease.ensure_state_transition(state)
|
|
else:
|
|
LOG.error("release %s not found" % release_id)
|
|
|
|
for release_id in list_of_releases:
|
|
release = self.get_release_by_id(release_id)
|
|
if release is not None:
|
|
release.update_state(state)
|