Update PatchData Object and parse Debian metadata

The PatchData object is updated to reflect the fields that would be
used in the Debian env. Similarly, the code to parse metadata has
been updated to incorporate the newer ostree commits and checksums.

Tests:
sw-patch upload
sw-patch delete

Story: 2009969
Task: 45203
Signed-off-by: Jessica Castelino <jessica.castelino@windriver.com>
Change-Id: I9efca439f212f1ab91655b8024bf4f8937ff882f
changes/70/839670/7
Jessica Castelino 5 months ago
parent 0eb171c11c
commit 1e36da5f09
  1. 39
      sw-patch/bin/setup_patch_repo
  2. 137
      sw-patch/cgcs-patch/cgcs_patch/patch_controller.py
  3. 509
      sw-patch/cgcs-patch/cgcs_patch/patch_functions.py
  4. 15
      sw-patch/cgcs-patch/cgcs_patch/tests/test_patch_utils.py

@ -135,45 +135,6 @@ def main():
# Purge unneeded RPMs
keep = {}
for patch_id in allpatches.metadata.keys():
for rpmname in allpatches.contents[patch_id]:
try:
pkgname, arch, pkgver = pf.parse_rpm_filename(rpmname)
except ValueError as e:
raise e
if pkgname not in keep:
keep[pkgname] = { arch: pkgver }
continue
elif arch not in keep[pkgname]:
keep[pkgname][arch] = pkgver
continue
# Compare versions
keep_pkgver = keep[pkgname][arch]
if pkgver > keep_pkgver:
# Find the rpmname
keep_rpmname = keep_pkgver.generate_rpm_filename(pkgname, arch)
filename = os.path.join(pkgdir, keep_rpmname)
if os.path.exists(filename):
os.remove(filename)
# Keep the new pkgver
keep[pkgname][arch] = pkgver
else:
filename = os.path.join(pkgdir, rpmname)
if os.path.exists(filename):
os.remove(filename)
# Create the repo metadata
if os.path.exists('/usr/bin/createrepo_c'):
createrepo = '/usr/bin/createrepo_c'
else:
createrepo = 'createrepo'
os.chdir(output)
subprocess.check_call([createrepo, '-g', 'comps.xml', '.'])
if __name__ == "__main__":
sys.exit(main())

@ -1,5 +1,5 @@
"""
Copyright (c) 2014-2019 Wind River Systems, Inc.
Copyright (c) 2014-2022 Wind River Systems, Inc.
SPDX-License-Identifier: Apache-2.0
@ -27,10 +27,7 @@ from cgcs_patch.patch_functions import BasePackageData
from cgcs_patch.patch_functions import avail_dir
from cgcs_patch.patch_functions import applied_dir
from cgcs_patch.patch_functions import committed_dir
from cgcs_patch.patch_functions import contentCompare
from cgcs_patch.patch_functions import PatchFile
from cgcs_patch.patch_functions import parse_rpm_filename
from cgcs_patch.patch_functions import parse_pkgver
from cgcs_patch.patch_functions import package_dir
from cgcs_patch.patch_functions import repo_dir
from cgcs_patch.patch_functions import semantics_dir
@ -767,9 +764,7 @@ class PatchController(PatchService):
continue
for pkg in list(self.hosts[ip].installed):
for patch_id in list(self.patch_data.content_versions):
if pkg not in self.patch_data.content_versions[patch_id]:
continue
for patch_id in list(self.patch_data.contents):
if patch_id not in self.patch_data.metadata:
LOG.error("Patch data missing for %s", patch_id)
@ -786,19 +781,11 @@ class PatchController(PatchService):
# Ignore epoch
installed_ver = installed_ver.split(':')[1]
patch_ver = self.patch_data.content_versions[patch_id][pkg]
if ":" in patch_ver:
# Ignore epoch
patch_ver = patch_ver.split(':')[1]
rc = contentCompare(parse_pkgver(installed_ver),
parse_pkgver(patch_ver))
if self.patch_data.metadata[patch_id]["repostate"] == constants.AVAILABLE:
# The RPM is not expected to be installed.
# If the installed version is the same or higher,
# this patch is in a Partial-Remove state
if rc >= 0 or patch_id in self.interim_state:
if patch_id in self.interim_state:
self.patch_data.metadata[patch_id]["patchstate"] = constants.PARTIAL_REMOVE
if self.patch_data.metadata[patch_id].get("reboot_required") != "N":
self.allow_insvc_patching = False
@ -807,7 +794,7 @@ class PatchController(PatchService):
# The RPM is expected to be installed.
# If the installed version is the lower,
# this patch is in a Partial-Apply state
if rc == -1 or patch_id in self.interim_state:
if patch_id in self.interim_state:
self.patch_data.metadata[patch_id]["patchstate"] = constants.PARTIAL_APPLY
if self.patch_data.metadata[patch_id].get("reboot_required") != "N":
self.allow_insvc_patching = False
@ -821,8 +808,8 @@ class PatchController(PatchService):
# Check the to_remove list
for pkg in self.hosts[ip].to_remove:
for patch_id in list(self.patch_data.content_versions):
if pkg not in self.patch_data.content_versions[patch_id]:
for patch_id in list(self.patch_data.contents):
if pkg not in self.patch_data.contents[patch_id]:
continue
if patch_id not in self.patch_data.metadata:
@ -845,8 +832,8 @@ class PatchController(PatchService):
# Check the missing_pkgs list
for pkg in self.hosts[ip].missing_pkgs:
for patch_id in list(self.patch_data.content_versions):
if pkg not in self.patch_data.content_versions[patch_id]:
for patch_id in list(self.patch_data.contents):
if pkg not in self.patch_data.contents[patch_id]:
continue
if patch_id not in self.patch_data.metadata:
@ -972,7 +959,6 @@ class PatchController(PatchService):
metadata_dir=mdir,
metadata_only=True,
existing_content=self.patch_data.contents[patch_id],
allpatches=self.patch_data,
base_pkgdata=self.base_pkgdata)
self.patch_data.update_patch(thispatch)
msg = "%s is already imported. Updated metadata only" % patch_id
@ -1007,11 +993,10 @@ class PatchController(PatchService):
try:
thispatch = PatchFile.extract_patch(patch,
metadata_dir=avail_dir,
allpatches=self.patch_data,
base_pkgdata=self.base_pkgdata)
msg_info += "%s is now available\n" % patch_id
self.patch_data.add_patch(patch_id, thispatch)
self.patch_data.add_patch(thispatch)
self.patch_data.metadata[patch_id]["repostate"] = constants.AVAILABLE
if len(self.hosts) > 0:
@ -1131,51 +1116,6 @@ class PatchController(PatchService):
msg_info += msg + "\n"
continue
# To allow for easy cleanup, we're going to first iterate
# through the content list to determine where to copy the file.
# As a second step, we'll go through the list and copy each file.
# If there are problems querying the content, none will be copied.
content_dict = {}
patch_sw_version = self.patch_data.metadata[patch_id]["sw_version"]
for contentname in self.patch_data.contents[patch_id]:
# the log can be debug or removed altogether once code is working
msg = "OSTREE applying %s " % contentname
LOG.info(msg)
contentfile = self.get_store_filename(patch_sw_version, contentname)
if not os.path.isfile(contentfile):
msg = "Could not find content file: %s" % contentfile
LOG.error(msg)
raise ContentFail(msg)
# todo: should 'repo' be replaced with something ostree related
repo_filename = self.get_repo_filename(patch_sw_version, contentname)
if repo_filename is None:
msg = "Failed to determine repo path for %s" % contentfile
LOG.exception(msg)
raise ContentFail(msg)
repo_pkg_dir = os.path.dirname(repo_filename)
if not os.path.exists(repo_pkg_dir):
os.makedirs(repo_pkg_dir)
content_dict[contentfile] = repo_filename
# Copy the Content. If a failure occurs, clean up copied files.
copied = []
for contentfile, repo_filename in content_dict.items():
LOG.info("Copy %s to %s", contentfile, repo_filename)
try:
shutil.copy(contentfile, repo_filename)
copied.append(repo_filename)
except IOError:
msg = "Failed to copy %s" % contentfile
LOG.exception(msg)
# Clean up files
for filename in copied:
LOG.info("Cleaning up %s", filename)
os.remove(filename)
raise ContentFail(msg)
try:
# Move the patching metadata from avail to applied dir
shutil.move("%s/%s-metadata.xml" % (avail_dir, patch_id),
@ -1516,9 +1456,6 @@ class PatchController(PatchService):
LOG.info(msg)
return dict(info=msg_info, warning=msg_warning, error=msg_error)
# Generate the groups xml
self.patch_data.gen_release_groups_xml(release)
# Create the repo
try:
# todo(jcasteli) determine if ostree change needs a createrepo equivalent
@ -1933,60 +1870,6 @@ class PatchController(PatchService):
if patch_sw_version not in cleanup:
cleanup[patch_sw_version] = {}
for rpmname in self.patch_data.contents[patch_id]:
try:
pkgname, arch, pkgver = parse_rpm_filename(rpmname)
except ValueError as e:
self.patch_data_lock.release()
raise e
if pkgname not in keep[patch_sw_version]:
keep[patch_sw_version][pkgname] = {arch: pkgver}
continue
elif arch not in keep[patch_sw_version][pkgname]:
keep[patch_sw_version][pkgname][arch] = pkgver
continue
# Compare versions
keep_pkgver = keep[patch_sw_version][pkgname][arch]
if pkgver > keep_pkgver:
if pkgname not in cleanup[patch_sw_version]:
cleanup[patch_sw_version][pkgname] = {arch: [keep_pkgver]}
elif arch not in cleanup[patch_sw_version][pkgname]:
cleanup[patch_sw_version][pkgname][arch] = [keep_pkgver]
else:
cleanup[patch_sw_version][pkgname][arch].append(keep_pkgver)
# Find the rpmname
keep_rpmname = keep_pkgver.generate_rpm_filename(pkgname, arch)
store_filename = self.get_store_filename(patch_sw_version, keep_rpmname)
if store_filename is not None and os.path.exists(store_filename):
cleanup_files.add(store_filename)
repo_filename = self.get_repo_filename(patch_sw_version, keep_rpmname)
if repo_filename is not None and os.path.exists(repo_filename):
cleanup_files.add(repo_filename)
# Keep the new pkgver
keep[patch_sw_version][pkgname][arch] = pkgver
else:
# Put this pkg in the cleanup list
if pkgname not in cleanup[patch_sw_version]:
cleanup[patch_sw_version][pkgname] = {arch: [pkgver]}
elif arch not in cleanup[patch_sw_version][pkgname]:
cleanup[patch_sw_version][pkgname][arch] = [pkgver]
else:
cleanup[patch_sw_version][pkgname][arch].append(pkgver)
store_filename = self.get_store_filename(patch_sw_version, rpmname)
if store_filename is not None and os.path.exists(store_filename):
cleanup_files.add(store_filename)
repo_filename = self.get_repo_filename(patch_sw_version, rpmname)
if repo_filename is not None and os.path.exists(repo_filename):
cleanup_files.add(repo_filename)
self.patch_data_lock.release()
# Calculate disk space
@ -2024,8 +1907,6 @@ class PatchController(PatchService):
raise MetadataFail(msg)
# OSTREE: this repo update code needs to be re-examined
# Update the repo
self.patch_data.gen_groups_xml()
for ver, rdir in repo_dir.items():
try:
# todo(jcasteli) determine if ostree change needs additional actions

@ -1,5 +1,5 @@
"""
Copyright (c) 2014-2019 Wind River Systems, Inc.
Copyright (c) 2014-2022 Wind River Systems, Inc.
SPDX-License-Identifier: Apache-2.0
@ -157,55 +157,6 @@ def write_xml_file(top,
outfile.write(minidom.parseString(rough_xml).toprettyxml(indent=" "))
def parse_rpm_filename(filename):
# Drop the extension
basename = os.path.splitext(os.path.basename(filename))[0]
# RPM name format is:
# [<epoch>:]<pkgname>-<version>-<release>.<arch>
#
pattern = re.compile(r'((([^:]):)?)(.*)-([^-]+)-(.*)\.([^\.]*)$')
m = pattern.match(basename)
if m is None:
raise ValueError("Filename does not match expected RPM format: %s" % basename)
epoch = m.group(3)
pkgname = m.group(4)
version = m.group(5)
release = m.group(6)
arch = m.group(7)
return (pkgname, arch, PackageVersion(epoch, version, release))
def parse_pkgver(pkgver):
# Version format is:
# [<epoch>:]<version>-<release>
#
pattern = re.compile(r'((([^:]):)?)([^-]+)((-(.*))?)$')
m = pattern.match(pkgver)
if m is None:
raise ValueError("Package version does not match expected format: %s" % pkgver)
epoch = m.group(3)
version = m.group(4)
release = m.group(7)
return (epoch, version, release)
# OSTREE: this method may be removed or revisited
# based on comparing SHAs and dependencies
def contentCompare(_list1, _list2):
LOG.info("OSTREE: DEV MODE. contentCompare always returns zero")
return 0
def get_release_from_patch(patchfile):
rel = ""
try:
@ -224,87 +175,6 @@ def get_release_from_patch(patchfile):
return rel
class PackageVersion(object):
"""
The PackageVersion class provides a structure for RPM version information,
along with suport for comparison operators.
"""
def __init__(self, epoch, version, release):
self.epoch = epoch
self.version = version
self.release = release
def __le__(self, other):
"""
This function is called by comparison operators to compare
two versions. The contentCompare() function takes two versions,
specified in a list structure, and returns -1, 0, or 1.
"""
out = contentCompare((self.epoch, self.version, self.release),
(other.epoch, other.version, other.release))
if out == 1:
return False
return True
def __eq__(self, other):
out = contentCompare((self.epoch, self.version, self.release),
(other.epoch, other.version, other.release))
if out == 0:
return True
return False
def __ne__(self, other):
out = contentCompare((self.epoch, self.version, self.release),
(other.epoch, other.version, other.release))
if out == 0:
return False
return True
def __gt__(self, other):
out = contentCompare((self.epoch, self.version, self.release),
(other.epoch, other.version, other.release))
if out == 1:
return True
return False
def __lt__(self, other):
out = contentCompare((self.epoch, self.version, self.release),
(other.epoch, other.version, other.release))
if out == -1:
return True
return False
def __ge__(self, other):
out = contentCompare((self.epoch, self.version, self.release),
(other.epoch, other.version, other.release))
if out == -1:
return False
return True
def __str__(self):
"""
This function is called by str() and print to compute the
informal string representation of a PackageVersion object.
"""
prefix = ""
if self.epoch is not None and self.epoch != '':
# Prefix the version with epoch, if specified
prefix = "%s:" % self.epoch
return "%s%s-%s" % (prefix, self.version, self.release)
def __hash__(self):
return hash(self.__str__())
def generate_rpm_filename(self, pkgname, arch):
prefix = ""
if self.epoch is not None and self.epoch != '':
# Prefix the version with epoch, if specified
prefix = "%s:" % self.epoch
return "%s%s-%s-%s.%s.rpm" % (prefix, pkgname, self.version, self.release, arch)
class BasePackageData(object):
"""
Information about the base package data provided by the load
@ -331,17 +201,6 @@ class BasePackageData(object):
continue
self.pkgs[sw_rel] = {}
for _root, _dirs, files in os.walk("%s/Packages" % reldir): # pylint: disable=unused-variable
for name in files:
if name.endswith(".rpm"):
try:
pkgname, arch, pkgver = parse_rpm_filename(name)
except ValueError as e:
raise e
if pkgname not in self.pkgs[sw_rel]:
self.pkgs[sw_rel][pkgname] = {}
self.pkgs[sw_rel][pkgname][arch] = pkgver
# Clean up deleted data
for sw_rel in self.pkgs:
@ -365,19 +224,6 @@ class PatchData(object):
Aggregated patch data
"""
def __init__(self):
#
# The groups dict provides information about targetted (new) packages,
# identifying the software group in which to include the package.
# This allows the patch agent to identify new packages to install
# (or erase) as appropriate.
# This dict is nested as follows:
# [ patch_sw_version ] - Release associated with the patch
# [ group/ptype ] - Group (personality) in which the pkg belongs
# [ patch_id ]
# [ package ]
#
self.groups = {}
#
# The metadata dict stores all metadata associated with a patch.
# This dict is keyed on patch_id, with metadata for each patch stored
@ -391,61 +237,10 @@ class PatchData(object):
#
self.contents = {}
#
# The content_versions dict provides a simple list of packages and their
# versions for each patch, used by the patch controller in determining
# patch states.
# content_versions[patch_id][pkgname] = "%s-%s" % (pkgver.version, pkgver.release)
#
self.content_versions = {}
#
# The package_versions dict provides a mapping of packages to the patch_id,
# including the package arch.
# [ patch_sw_version ]
# [ pkgname ]
# [ arch ]
# [ pkgver ]
# -> patch_id
self.package_versions = {}
#
# The semantics dict stores the lists of semantic actions provided by each patch,
# indexed by patch_id.
#
self.semantics = {}
def add_patch(self, patch_id, new_patch):
def add_patch(self, new_patch):
# We can just use "update" on these dicts because they are indexed by patch_id
self.metadata.update(new_patch.metadata)
self.contents.update(new_patch.contents)
self.content_versions.update(new_patch.content_versions)
self.semantics.update(new_patch.semantics)
# Need to recursively update package_version and keys dicts
for patch_sw_version in list(new_patch.package_versions):
if patch_sw_version not in self.package_versions:
self.package_versions[patch_sw_version] = {}
for pkgname in list(new_patch.package_versions[patch_sw_version]):
if pkgname not in self.package_versions[patch_sw_version]:
self.package_versions[patch_sw_version][pkgname] = {}
for arch in list(new_patch.package_versions[patch_sw_version][pkgname]):
if arch not in self.package_versions[patch_sw_version][pkgname]:
self.package_versions[patch_sw_version][pkgname][arch] = {}
for pkgver in list(new_patch.package_versions[patch_sw_version][pkgname][arch]):
self.package_versions[patch_sw_version][pkgname][arch][pkgver] = patch_id
for patch_sw_version in list(new_patch.groups):
if patch_sw_version not in self.groups:
self.groups[patch_sw_version] = {}
for ptype in list(new_patch.groups[patch_sw_version]):
if ptype not in self.groups[patch_sw_version]:
self.groups[patch_sw_version][ptype] = {}
for patch_id in list(new_patch.groups[patch_sw_version][ptype]):
if patch_id not in self.groups[patch_sw_version][ptype]:
self.groups[patch_sw_version][ptype][patch_id] = {}
self.groups[patch_sw_version][ptype][patch_id].update(
new_patch.groups[patch_sw_version][ptype][patch_id])
def update_patch(self, updated_patch):
for patch_id in list(updated_patch.metadata):
@ -455,27 +250,7 @@ class PatchData(object):
self.metadata[patch_id]['repostate'] = cur_repostate
def delete_patch(self, patch_id):
for patch_sw_version in list(self.package_versions):
for pkgname in list(self.package_versions[patch_sw_version]):
for arch in list(self.package_versions[patch_sw_version][pkgname]):
for pkgver in list(self.package_versions[patch_sw_version][pkgname][arch]):
if self.package_versions[patch_sw_version][pkgname][arch][pkgver] == patch_id:
del self.package_versions[patch_sw_version][pkgname][arch][pkgver]
if len(self.package_versions[patch_sw_version][pkgname][arch]) == 0:
del self.package_versions[patch_sw_version][pkgname][arch]
if len(self.package_versions[patch_sw_version][pkgname]) == 0:
del self.package_versions[patch_sw_version][pkgname]
if len(self.package_versions[patch_sw_version]) == 0:
del self.package_versions[patch_sw_version]
for patch_sw_version in list(self.groups):
for ptype in list(self.groups[patch_sw_version]):
if patch_id in self.groups[patch_sw_version][ptype]:
del self.groups[patch_sw_version][ptype][patch_id]
del self.content_versions[patch_id]
del self.contents[patch_id]
del self.semantics[patch_id]
del self.metadata[patch_id]
@staticmethod
@ -541,13 +316,6 @@ class PatchData(object):
# <status>Dev</status>
# <unremovable/>
# <reboot_required/>
# <personality type="worker">
# <package>pkgA</package>
# <package>pkgB</package>
# </personality>
# <personality type="controller">
# <package>pkgB</package>
# </personality>
# </patch>
#
@ -590,67 +358,27 @@ class PatchData(object):
package_dir[patch_sw_version] = "%s/%s" % (root_package_dir, patch_sw_version)
repo_dir[patch_sw_version] = "%s/rel-%s" % (repo_root_dir, patch_sw_version)
# Specifying personality for given packages is optional,
# intended to allow a patch to include a new package.
# For each configured personality type, create a software group.
for personality in root.findall("personality"):
ptype = personality.attrib["type"]
tag = "personality-%s" % ptype
self.metadata[patch_id][tag] = list()
for pkg in personality.findall("package"):
self.metadata[patch_id][tag].append(pkg.text)
if patch_sw_version not in self.groups:
self.groups[patch_sw_version] = {}
if ptype not in self.groups[patch_sw_version]:
self.groups[patch_sw_version][ptype] = {}
if patch_id not in self.groups[patch_sw_version][ptype]:
self.groups[patch_sw_version][ptype][patch_id] = {}
self.groups[patch_sw_version][ptype][patch_id][pkg.text] = True
self.metadata[patch_id]["requires"] = []
for req in root.findall("requires"):
for req_patch in req.findall("req_patch_id"):
self.metadata[patch_id]["requires"].append(req_patch.text)
self.contents[patch_id] = list()
self.content_versions[patch_id] = {}
for content in root.findall("contents"):
for rpmname in content.findall("rpm"):
try:
pkgname, arch, pkgver = parse_rpm_filename(rpmname.text)
except ValueError as e:
LOG.exception(e)
return None
self.contents[patch_id].append(rpmname.text)
self.content_versions[patch_id][pkgname] = "%s-%s" % (pkgver.version, pkgver.release)
self.contents[patch_id] = {}
if patch_sw_version not in self.package_versions:
self.package_versions[patch_sw_version] = {}
if pkgname not in self.package_versions[patch_sw_version]:
self.package_versions[patch_sw_version][pkgname] = {}
if arch not in self.package_versions[patch_sw_version][pkgname]:
self.package_versions[patch_sw_version][pkgname][arch] = {}
self.package_versions[patch_sw_version][pkgname][arch][pkgver] = patch_id
self.semantics[patch_id] = list()
for semantics in root.findall("semantics"):
for action in semantics.findall("action"):
self.semantics[patch_id].append(action.text)
for content in root.findall("contents/ostree"):
self.contents[patch_id]["number_of_commits"] = content.findall("number_of_commits")[0].text
self.contents[patch_id]["base"] = {}
self.contents[patch_id]["base"]["commit"] = content.findall("base/commit")[0].text
self.contents[patch_id]["base"]["checksum"] = content.findall("base/checksum")[0].text
for i in range(int(self.contents[patch_id]["number_of_commits"])):
self.contents[patch_id]["commit%s" % (i + 1)] = {}
self.contents[patch_id]["commit%s" % (i + 1)]["commit"] = \
content.findall("commit%s/commit" % (i + 1))[0].text
self.contents[patch_id]["commit%s" % (i + 1)]["checksum"] = \
content.findall("commit%s/checksum" % (i + 1))[0].text
return patch_id
def find_patch_with_pkgver(self, sw_ver, pkgname, arch, pkgver):
if sw_ver not in self.package_versions or \
pkgname not in self.package_versions[sw_ver] or \
arch not in self.package_versions[sw_ver][pkgname] or \
pkgver not in self.package_versions[sw_ver][pkgname][arch]:
return None
return self.package_versions[sw_ver][pkgname][arch][pkgver]
def load_all_metadata(self,
loaddir,
repostate=None):
@ -668,54 +396,6 @@ class PatchData(object):
self.load_all_metadata(avail_dir, repostate=constants.AVAILABLE)
self.load_all_metadata(committed_dir, repostate=constants.COMMITTED)
def gen_release_groups_xml(self, sw_version, output_dir=None):
"""
Generate the groups configuration file for the patching repo
"""
if output_dir is None:
output_dir = repo_dir[sw_version]
if not os.path.exists(output_dir):
os.makedirs(output_dir)
fname = "%s/comps.xml" % output_dir
top = ElementTree.Element('comps')
if sw_version in self.groups:
for groupname in sorted(list(self.groups[sw_version])):
if self.groups[sw_version][groupname]:
group = ElementTree.SubElement(top, 'group')
add_text_tag_to_xml(group, 'id',
"updates-%s" % groupname)
add_text_tag_to_xml(group, 'default',
"false")
add_text_tag_to_xml(group, 'uservisible',
"true")
add_text_tag_to_xml(group, 'display_order',
"1024")
add_text_tag_to_xml(group, 'name',
"updates-%s" % groupname)
add_text_tag_to_xml(group, 'description',
"Patches for %s" % groupname)
package_element = ElementTree.SubElement(group,
'packagelist')
for patch_id in sorted(self.groups[sw_version][groupname]):
if self.metadata[patch_id]["repostate"] == constants.APPLIED \
or self.metadata[patch_id]["repostate"] == constants.COMMITTED:
for pkg in sorted(self.groups[sw_version][groupname][patch_id]):
tag = ElementTree.SubElement(package_element,
'packagereq',
type="mandatory")
tag.text = pkg
write_xml_file(top, fname)
def gen_groups_xml(self):
for ver in repo_dir:
self.gen_release_groups_xml(ver)
def query_line(self,
patch_id,
index):
@ -748,23 +428,7 @@ class PatchMetadata(object):
self.reboot_required = None
self.apply_active_release_only = None
self.requires = []
self.groups = {}
self.contents = {}
self.semantics = []
def add_package(self,
groupname,
pkg):
"""
Add a package to a particular group
:param groupname: Yum software group, eg. "controller"
:param pkg: Name of the package
:return:
"""
if groupname not in self.groups:
self.groups[groupname] = {}
self.groups[groupname][pkg] = True
def add_rpm(self,
fname):
@ -776,15 +440,6 @@ class PatchMetadata(object):
rpmname = os.path.basename(fname)
self.contents[rpmname] = True
def add_semantic(self,
action):
"""
Add a semantic check to the patch
:param action: semantic action
:return:
"""
self.semantics.append(action)
def gen_xml(self,
fname="metadata.xml"):
"""
@ -815,15 +470,6 @@ class PatchMetadata(object):
add_text_tag_to_xml(top, 'apply_active_release_only',
self.apply_active_release_only)
for groupname in sorted(list(self.groups)):
if self.groups[groupname]:
group = ElementTree.SubElement(top,
'personality',
type=groupname)
for pkg in sorted(self.groups[groupname]):
add_text_tag_to_xml(group, 'package', pkg)
content = ElementTree.SubElement(top, 'contents')
for rpmname in sorted(list(self.contents)):
add_text_tag_to_xml(content, 'rpm', rpmname)
@ -832,10 +478,6 @@ class PatchMetadata(object):
for req_patch in sorted(self.requires):
add_text_tag_to_xml(req, 'req_patch_id', req_patch)
semantics = ElementTree.SubElement(top, 'semantics')
for action in sorted(self.semantics):
add_text_tag_to_xml(semantics, 'action', action)
write_xml_file(top, fname)
@ -846,11 +488,9 @@ class PatchFile(object):
def __init__(self):
self.meta = PatchMetadata()
self.rpmlist = {}
self.semantics = {}
def add_rpm(self,
fname,
personality=None):
fname):
"""
Add an RPM to the patch
:param fname: Path to RPM
@ -865,44 +505,12 @@ class PatchFile(object):
# Add the RPM to the patch
self.rpmlist[os.path.abspath(fname)] = True
if personality is not None:
# Get the package name from the RPM itself,
# and add it to the appropriate group(s)
pkgname = subprocess.check_output(["rpm",
"-qp",
"--queryformat",
"%{NAME}",
"--nosignature",
fname])
if isinstance(personality, list):
for p in personality:
self.meta.add_package(p, pkgname)
elif isinstance(personality, str):
self.meta.add_package(personality, pkgname)
def add_semantic(self,
action,
fname):
"""
Add a semantic check to the patch
:param action: Semantic check type
:param fname: Path to semantic check
:return:
"""
# Add the semantic to the metadata
self.meta.add_semantic(action)
self.semantics[action] = os.path.abspath(fname)
def gen_patch(self, outdir):
"""
Generate the patch file, named PATCHID.patch
:param outdir: Output directory for the patch
:return:
"""
if self.meta.sw_version is None or self.meta.sw_version == '':
raise MetadataFail("The release version must be specified in the sw_version field")
if not self.rpmlist:
raise MetadataFail("Cannot generate empty patch")
@ -936,16 +544,6 @@ class PatchFile(object):
tar.add(os.path.basename(rpmfile))
tar.close()
# Copy semantics to tmpdir, if any
if len(self.semantics) > 0:
tar = tarfile.open("semantics.tar", "w")
for action in list(self.semantics):
os.mkdir(action, 0o755)
sname = os.path.join(action, self.meta.id)
shutil.copy(self.semantics[action], sname)
tar.add(sname)
tar.close()
# Generate the metadata xml file
self.meta.gen_xml("metadata.xml")
@ -1014,7 +612,7 @@ class PatchFile(object):
raise SystemExit(e.returncode)
@staticmethod
def read_patch(path, metadata_only=False, cert_type=None):
def read_patch(path, cert_type=None):
# We want to enable signature checking by default
# Note: cert_type=None is required if we are to enforce 'no dev patches on a formal load' rule.
@ -1073,14 +671,6 @@ class PatchFile(object):
tar = tarfile.open("metadata.tar")
tar.extractall()
if not metadata_only:
tar = tarfile.open("software.tar")
tar.extractall()
if os.path.exists("semantics.tar"):
tar = tarfile.open("semantics.tar")
tar.extractall()
@staticmethod
def query_patch(patch, field=None):
@ -1102,7 +692,7 @@ class PatchFile(object):
# Need to determine the cert_type
for cert_type_str in cert_type_all:
try:
PatchFile.read_patch(abs_patch, metadata_only=True, cert_type=[cert_type_str])
PatchFile.read_patch(abs_patch, cert_type=[cert_type_str])
except PatchValidationFailure:
pass
else:
@ -1116,7 +706,7 @@ class PatchFile(object):
# We can't omit cert_type, or pass None, because that will trigger the code
# path used by installed product, in which dev keys are not accepted unless
# a magic file exists.
PatchFile.read_patch(abs_patch, metadata_only=True, cert_type=cert_type_all)
PatchFile.read_patch(abs_patch, cert_type=cert_type_all)
thispatch = PatchData()
patch_id = thispatch.parse_metadata("metadata.xml")
@ -1174,7 +764,7 @@ class PatchFile(object):
meta_data = PatchFile.query_patch(abs_patch)
if 'cert' in meta_data:
cert_type = meta_data['cert']
PatchFile.read_patch(abs_patch, metadata_only=True, cert_type=cert_type)
PatchFile.read_patch(abs_patch, cert_type=cert_type)
PatchData.modify_metadata_text("metadata.xml", key, value)
PatchFile.write_patch(new_abs_patch, cert_type=cert_type)
os.rename(new_abs_patch, abs_patch)
@ -1204,7 +794,6 @@ class PatchFile(object):
metadata_dir=avail_dir,
metadata_only=False,
existing_content=None,
allpatches=None,
base_pkgdata=None):
"""
Extract the metadata and patch contents
@ -1228,7 +817,7 @@ class PatchFile(object):
try:
# Open the patch file and extract the contents to the tmpdir
PatchFile.read_patch(abs_patch, metadata_only)
PatchFile.read_patch(abs_patch)
thispatch = PatchData()
patch_id = thispatch.parse_metadata("metadata.xml")
@ -1248,38 +837,13 @@ class PatchFile(object):
LOG.exception(msg)
raise PatchValidationFailure(msg)
for rpmname in thispatch.contents[patch_id]:
pkgname, arch, pkgver = parse_rpm_filename(rpmname)
base_pkgver = base_pkgdata.find_version(patch_sw_version, pkgname, arch)
if base_pkgver is not None:
# Compare the patch RPM's version against the base
if pkgver <= base_pkgver:
msg = "RPM %s in patch %s must be higher version than original (%s)" % \
(rpmname, patch_id, base_pkgver)
LOG.exception(msg)
raise PatchValidationFailure(msg)
if allpatches is not None:
# Compare the patch RPM's version against other patches
other = allpatches.find_patch_with_pkgver(patch_sw_version, pkgname, arch, pkgver)
if other is not None:
msg = "Patch %s contains rpm %s, which is already provided by patch %s" % \
(patch_id, rpmname, other)
LOG.exception(msg)
raise PatchValidationFailure(msg)
if metadata_only:
# This is a re-import. Ensure the content lines up
if existing_content is None \
or len(existing_content) != len(thispatch.contents[patch_id]):
or existing_content != thispatch.contents[patch_id]:
msg = "Contents of re-imported patch do not match"
LOG.exception(msg)
raise PatchMismatchFailure(msg)
for rpmname in existing_content:
if rpmname not in thispatch.contents[patch_id]:
msg = "Contents of re-imported patch do not match"
LOG.exception(msg)
raise PatchMismatchFailure(msg)
shutil.move("metadata.xml",
"%s/%s-metadata.xml" % (abs_metadata_dir, patch_id))
@ -1292,18 +856,6 @@ class PatchFile(object):
os.makedirs(rpm_dir)
shutil.move(rpmname, "%s/" % rpm_dir)
for action in constants.SEMANTIC_ACTIONS:
action_file = os.path.join(action, patch_id)
if not os.path.exists(action_file):
continue
action_dir = os.path.join(semantics_dir, action)
if not os.path.exists(action_dir):
os.makedirs(action_dir)
os.chmod(action_file, 0o544)
shutil.move(action_file, action_dir)
except PatchValidationFailure as e:
raise e
except PatchMismatchFailure as e:
@ -1415,25 +967,6 @@ def patch_build():
pf.meta.install_instructions = arg
elif opt == "--req":
pf.meta.requires.append(arg)
elif opt == "--all-nodes":
for p in ("controller",
"worker",
"worker-lowlatency",
"storage",
"controller-worker",
"controller-worker-lowlatency"):
pf.add_rpm(arg, personality=p)
elif opt in ("--controller",
"--worker",
"--worker-lowlatency",
"--storage",
"--controller-worker",
"--controller-worker-lowlatency"):
pf.add_rpm(arg, personality=opt[2:])
elif opt == "--pre-apply":
pf.add_semantic(constants.SEMANTIC_PREAPPLY, arg)
elif opt == "--pre-remove":
pf.add_semantic(constants.SEMANTIC_PREREMOVE, arg)
elif opt == "--apply-active-release-only":
pf.meta.apply_active_release_only = "Y"

@ -1,7 +1,7 @@
#
# SPDX-License-Identifier: Apache-2.0
#
# Copyright (c) 2019 Wind River Systems, Inc.
# Copyright (c) 2019-2022 Wind River Systems, Inc.
#
import mock
@ -131,16 +131,3 @@ class CgcsPatchUtilsTestCase(testtools.TestCase):
result = cgcs_patch.utils.ip_to_versioned_localhost(ip)
self.assertEqual(expected_result, result)
def test_parse_pkgver(self):
versions = {
'0:1.2.3-r4': ('0', '1.2.3', 'r4'),
'4.3.2-1': (None, '4.3.2', '1'),
'8.1.4': (None, '8.1.4', None),
'5:7.5.3': ('5', '7.5.3', None),
'This is a weird version string': (None, 'This is a weird version string', None),
}
for ver, expected in versions.items():
result = cgcs_patch.patch_functions.parse_pkgver(ver)
self.assertEqual(result, expected)

Loading…
Cancel
Save