cgcs-patch: update for stx 5.0

* Add dependency on DNF

* Rebased patch:
  - 0001-patch_agent-do-not-do-the-packages_iter-if-pkggrp-is.patch

* Removed the following patches since they are already in upstream:
  - 0001-Remove-use-of-rpmUtils.miscutils-from-cgcs-patch.patch
  - 0003-Cleaning-up-pylint-settings-for-cgcs-patch.patch
  - 0004-Address-python3-pylint-errors-and-warnings.patch
  - 0005-Clean-up-pylint-W1201-logging-not-lazy-in-cgcs-patch.patch
  - 0006-Migrate-patch-agent-to-use-DNF-for-swmgmt.patch

Story: 2008952
Task: 42576

Signed-off-by: Jackie Huang <jackie.huang@windriver.com>
Change-Id: I01b1795e30036d78097915a07870bc950d13c3d0
This commit is contained in:
Jackie Huang 2021-05-21 15:26:51 +08:00
parent 81faeff279
commit e8fe442a6b
7 changed files with 17 additions and 2354 deletions

View File

@ -8,16 +8,13 @@ SUBPATH0 = "cgcs-patch/cgcs-patch"
LICENSE = "Apache-2.0"
LIC_FILES_CHKSUM = "file://LICENSE;md5=3b83ef96387f14655fc854ddc3c6bd57"
SRC_URI += "file://0001-Remove-use-of-rpmUtils.miscutils-from-cgcs-patch.patch;striplevel=3 \
file://0003-Cleaning-up-pylint-settings-for-cgcs-patch.patch;striplevel=3 \
file://0004-Address-python3-pylint-errors-and-warnings.patch;striplevel=3 \
file://0005-Clean-up-pylint-W1201-logging-not-lazy-in-cgcs-patch.patch;striplevel=3 \
file://0006-Migrate-patch-agent-to-use-DNF-for-swmgmt.patch;striplevel=3 \
file://0007-patch_agent-do-not-do-the-packages_iter-if-pkggrp-is.patch;striplevel=3 \
SRC_URI += "\
file://0001-patch_agent-do-not-do-the-packages_iter-if-pkggrp-is.patch;striplevel=3 \
"
RDEPENDS_${PN}_append = " \
bash \
dnf \
"
RDEPENDS_${PN}-agent_append = " \
bash \

View File

@ -1,171 +0,0 @@
From 80ee2e342d1854f439a1ec25c2f6a3a3625a0720 Mon Sep 17 00:00:00 2001
From: Don Penney <don.penney@windriver.com>
Date: Sun, 22 Dec 2019 22:45:18 -0500
Subject: [PATCH] Remove use of rpmUtils.miscutils from cgcs-patch
The rpmUtils.miscutils.stringToVersion function will not be available
in CentOS8, as it is not currently provided for python3. A similar
function exists in cgcs_patch.patch_functions, using regex to parse
the version from an RPM filename. This update adds a new function,
expand_pkgver, implemented in a similar fashion using regex, providing
the same capability as rpmUtils.miscutils.stringToVersion.
Change-Id: I2a04f3dbf85d62c87ca1afcf988b672aafceb642
Story: 2006228
Task: 37871
Signed-off-by: Don Penney <don.penney@windriver.com>
---
cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py | 11 +++++------
cgcs-patch/cgcs-patch/cgcs_patch/patch_controller.py | 6 +++---
cgcs-patch/cgcs-patch/cgcs_patch/patch_functions.py | 18 ++++++++++++++++++
.../cgcs-patch/cgcs_patch/tests/test_patch_agent.py | 2 --
.../cgcs_patch/tests/test_patch_controller.py | 2 --
.../cgcs-patch/cgcs_patch/tests/test_patch_utils.py | 14 ++++++++++++++
6 files changed, 40 insertions(+), 13 deletions(-)
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py b/cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py
index b95b79d..77930d7 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py
@@ -19,9 +19,8 @@ import sys
import yaml
import shutil
-from rpmUtils.miscutils import stringToVersion # pylint: disable=import-error
-
from cgcs_patch.patch_functions import configure_logging
+from cgcs_patch.patch_functions import parse_pkgver
from cgcs_patch.patch_functions import LOG
import cgcs_patch.config as cfg
from cgcs_patch.base import PatchService
@@ -519,8 +518,8 @@ class PatchAgent(PatchService):
# 1, if first arg is higher version
# 0, if versions are same
# -1, if first arg is lower version
- rc = rpm.labelCompare(stringToVersion(version),
- stringToVersion(stored_ver))
+ rc = rpm.labelCompare(parse_pkgver(version),
+ parse_pkgver(stored_ver))
if rc > 0:
# Update version
@@ -709,8 +708,8 @@ class PatchAgent(PatchService):
compare_version = base_version
# Compare the installed version to what's in the repo
- rc = rpm.labelCompare(stringToVersion(installed_version),
- stringToVersion(compare_version))
+ rc = rpm.labelCompare(parse_pkgver(installed_version),
+ parse_pkgver(compare_version))
if rc == 0:
# Versions match, nothing to do.
continue
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/patch_controller.py b/cgcs-patch/cgcs-patch/cgcs_patch/patch_controller.py
index 1ba8f5e..4b94a5f 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/patch_controller.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/patch_controller.py
@@ -17,7 +17,7 @@ import rpm
import os
import gc
-from rpmUtils.miscutils import stringToVersion # pylint: disable=import-error
+from cgcs_patch.patch_functions import parse_pkgver
from wsgiref import simple_server
from cgcs_patch.api import app
@@ -776,8 +776,8 @@ class PatchController(PatchService):
# Ignore epoch
patch_ver = patch_ver.split(':')[1]
- rc = rpm.labelCompare(stringToVersion(installed_ver),
- stringToVersion(patch_ver))
+ rc = rpm.labelCompare(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.
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/patch_functions.py b/cgcs-patch/cgcs-patch/cgcs_patch/patch_functions.py
index 832e4e9..281a286 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/patch_functions.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/patch_functions.py
@@ -176,6 +176,24 @@ def parse_rpm_filename(filename):
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)
+
+
class PackageVersion(object):
"""
The PackageVersion class provides a structure for RPM version information,
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/tests/test_patch_agent.py b/cgcs-patch/cgcs-patch/cgcs_patch/tests/test_patch_agent.py
index c953e4f..bd1eef9 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/tests/test_patch_agent.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/tests/test_patch_agent.py
@@ -10,8 +10,6 @@ import sys
import testtools
sys.modules['rpm'] = mock.Mock()
-sys.modules['rpmUtils'] = mock.Mock()
-sys.modules['rpmUtils.miscutils'] = mock.Mock()
import cgcs_patch.patch_agent # noqa: E402
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/tests/test_patch_controller.py b/cgcs-patch/cgcs-patch/cgcs_patch/tests/test_patch_controller.py
index d11623f..e2b02c0 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/tests/test_patch_controller.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/tests/test_patch_controller.py
@@ -10,8 +10,6 @@ import sys
import testtools
sys.modules['rpm'] = mock.Mock()
-sys.modules['rpmUtils'] = mock.Mock()
-sys.modules['rpmUtils.miscutils'] = mock.Mock()
import cgcs_patch.patch_controller # noqa: E402
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/tests/test_patch_utils.py b/cgcs-patch/cgcs-patch/cgcs_patch/tests/test_patch_utils.py
index a5eb8d4..653c65a 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/tests/test_patch_utils.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/tests/test_patch_utils.py
@@ -9,6 +9,7 @@ import socket
import testtools
import cgcs_patch.constants
+import cgcs_patch.patch_functions
import cgcs_patch.utils
@@ -130,3 +131,16 @@ 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)

View File

@ -1,21 +1,25 @@
From 059984de897fe2c8c48811ceb76a0331f94b3557 Mon Sep 17 00:00:00 2001
From b97b3a2f35f8cf0a9eae975ece510c2bb27ceef9 Mon Sep 17 00:00:00 2001
From: Jackie Huang <jackie.huang@windriver.com>
Date: Wed, 13 May 2020 22:10:01 +0800
Date: Fri, 21 May 2021 15:24:13 +0800
Subject: [PATCH] patch_agent: do not do the packages_iter if pkggrp is None
Addn the handling of packages_iter to the else block to avoid:
Add the handling of packages_iter to the else block to avoid:
AttributeError: 'NoneType' object has no attribute 'packages_iter'
Upstream-Status: Inappropriate [poky-stx specific]
Rebased for stx 5.0
Signed-off-by: Jackie Huang <jackie.huang@windriver.com>
---
cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py | 44 ++++++++++++-------------
1 file changed, 22 insertions(+), 22 deletions(-)
cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py | 45 +++++++++++++------------
1 file changed, 23 insertions(+), 22 deletions(-)
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py b/cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py
index d8bc375..489d484 100644
index e895e00..8de3c0a 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py
@@ -490,29 +490,29 @@ class PatchAgent(PatchService):
@@ -536,29 +536,30 @@ class PatchAgent(PatchService):
break
if pkggrp is None:
@ -64,8 +68,9 @@ index d8bc375..489d484 100644
+ LOG.info("To install: %s", self.to_install)
+ LOG.info("To remove: %s", self.to_remove)
+ LOG.info("Missing: %s", self.missing_pkgs)
return True
+
if len(self.duplicated_pkgs) > 0:
LOG.info("Duplicated: %s", self.duplicated_pkgs)
--
2.7.4

View File

@ -1,441 +0,0 @@
From de774c85653692b2a901123b5653d0e2101c5353 Mon Sep 17 00:00:00 2001
From: Al Bailey <Al.Bailey@windriver.com>
Date: Fri, 4 Oct 2019 12:29:03 -0500
Subject: [PATCH] Cleaning up pylint settings for cgcs patch
This also adds cgcs_make_patch folder for pylint
pylint is invoked with two different pylint.rc files
so that different codes can be suppressed for the
two different code structures.
Change-Id: I0d7a87ed435ed716a3c1ea98f5d7badfd2adac7d
Story: 2004515
Task: 37701
Signed-off-by: Al Bailey <Al.Bailey@windriver.com>
---
cgcs-patch/cgcs-patch/pylint.rc | 14 +-
cgcs-patch/cgcs-patch/pylint_make_patch.rc | 352 +++++++++++++++++++++++++++++
cgcs-patch/cgcs-patch/tox.ini | 6 +-
3 files changed, 365 insertions(+), 7 deletions(-)
create mode 100644 cgcs-patch/cgcs-patch/pylint_make_patch.rc
diff --git a/cgcs-patch/cgcs-patch/pylint.rc b/cgcs-patch/cgcs-patch/pylint.rc
index dc20bb0..812b6b5 100644
--- a/cgcs-patch/cgcs-patch/pylint.rc
+++ b/cgcs-patch/cgcs-patch/pylint.rc
@@ -44,8 +44,16 @@ symbols=no
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
-#disable=
-disable=C, R, W0603, W0613, W0702, W0703, W1201
+# E1111 assignment-from-no-return
+# W0107 unnecessary-pass
+# W0603 global-statement
+# W0612 unused-variable
+# W0613 unused-argument
+# W0703 broad-except
+# W0705 duplicate-except
+# W1201 logging-not-lazy
+# W1505, deprecated-method
+disable=C, R, E1111, W0107, W0603, W0612, W0613, W0703, W0705, W1201, W1505
[REPORTS]
@@ -61,7 +69,7 @@ output-format=text
files-output=no
# Tells whether to display a full report or only the messages
-reports=yes
+reports=no
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
diff --git a/cgcs-patch/cgcs-patch/pylint_make_patch.rc b/cgcs-patch/cgcs-patch/pylint_make_patch.rc
new file mode 100644
index 0000000..ef4e838
--- /dev/null
+++ b/cgcs-patch/cgcs-patch/pylint_make_patch.rc
@@ -0,0 +1,352 @@
+[MASTER]
+
+# Specify a configuration file.
+#rcfile=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Profiled execution.
+profile=no
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=CVS
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+# DEPRECATED
+include-ids=no
+
+# DEPRECATED
+symbols=no
+
+
+[MESSAGES CONTROL]
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time. See also the "--disable" option for examples.
+#enable=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifiers separated by comma (,) or put this
+# option multiple times (only on the command line, not in the configuration
+# file where it should appear only once).You can also use "--disable=all" to
+# disable everything first and then reenable specific checks. For example, if
+# you want to run only the similarities checker, you can use "--disable=all
+# --enable=similarities". If you want to run only the classes checker, but have
+# no Warning level messages displayed, use"--disable=all --enable=classes
+# --disable=W"
+# The following are suppressed due to pylint warnings in cgcs_make_patch
+# fixme Use of fixme, todo, etc..
+# E1101 no-member
+# W0101 unreachable
+# W0104 pointless-statement
+# W0107 unnecessary-pass
+# W0212 protected-access
+# W0231 super-init-not-called
+# W0603 global-statement
+# W0612 unused-variable
+# W0613 unused-argument
+# W0622 redefined-builtin
+# W0703 broad-except
+# W1401 anomalous-backslash-in-string
+# W1505, deprecated-method
+disable=C, R, fixme, E1101,
+ W0101, W0104, W0107, W0212, W0231, W0603, W0612, W0613, W0622, W0703,
+ W1401, W1505
+
+[REPORTS]
+
+# Set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html. You can also give a reporter class, eg
+# mypackage.mymodule.MyReporterClass.
+output-format=text
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]".
+files-output=no
+
+# Tells whether to display a full report or only the messages
+reports=no
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Add a comment according to your evaluation note. This is used by the global
+# evaluation report (RP0004).
+comment=no
+
+# Template used to display messages. This is a python new-style format string
+# used to format the message information. See doc for all details
+#msg-template=
+
+
+[BASIC]
+
+# Required attributes for module, separated by a comma
+required-attributes=
+
+# List of builtins function names that should not be used, separated by a comma
+bad-functions=map,filter,apply,input,file
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,k,ex,Run,_
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,bar,baz,toto,tutu,tata
+
+# Colon-delimited sets of names that determine each other's naming style when
+# the name regexes allow several styles.
+name-group=
+
+# Include a hint for the correct naming format with invalid-name
+include-naming-hint=no
+
+# Regular expression matching correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for function names
+function-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct variable names
+variable-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for variable names
+variable-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct constant names
+const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Naming hint for constant names
+const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Regular expression matching correct attribute names
+attr-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for attribute names
+attr-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct argument names
+argument-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for argument names
+argument-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression matching correct class attribute names
+class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+
+# Naming hint for class attribute names
+class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
+
+# Regular expression matching correct inline iteration names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Naming hint for inline iteration names
+inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
+
+# Regular expression matching correct class names
+class-rgx=[A-Z_][a-zA-Z0-9]+$
+
+# Naming hint for class names
+class-name-hint=[A-Z_][a-zA-Z0-9]+$
+
+# Regular expression matching correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Naming hint for module names
+module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression matching correct method names
+method-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Naming hint for method names
+method-name-hint=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match function or class names that do
+# not require a docstring.
+no-docstring-rgx=__.*__
+
+# Minimum line length for functions/classes that require docstrings, shorter
+# ones are exempt.
+docstring-min-length=-1
+
+
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=80
+
+# Regexp for a line that is allowed to be longer than the limit.
+ignore-long-lines=^\s*(# )?<?https?://\S+>?$
+
+# Allow the body of an if to be on the same line as the test if there is no
+# else.
+single-line-if-stmt=no
+
+# List of optional constructs for which whitespace checking is disabled
+no-space-check=trailing-comma,dict-separator
+
+# Maximum number of lines in a module
+max-module-lines=1000
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string=' '
+
+# Number of spaces of indent required inside a hanging or continued line.
+indent-after-paren=4
+
+
+[LOGGING]
+
+# Logging modules to check that the string format arguments are in logging
+# function parameter format
+logging-modules=logging
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,XXX,TODO
+
+
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+# Ignore imports when computing similarities.
+ignore-imports=no
+
+
+[TYPECHECK]
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of module names for which member attributes should not be checked
+# (useful for modules/projects where namespaces are manipulated during runtime
+# and thus existing member attributes cannot be deduced by static analysis
+ignored-modules=
+
+# List of classes names for which member attributes should not be checked
+# (useful for classes with attributes dynamically set).
+ignored-classes=rpm,PKCS1_PSS
+
+# When zope mode is activated, add a predefined set of Zope acquired attributes
+# to generated-members.
+zope=no
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E0201 when accessed. Python regular
+# expressions are accepted.
+generated-members=REQUEST,acl_users,aq_parent
+
+
+[VARIABLES]
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching the name of dummy variables (i.e. expectedly
+# not used).
+dummy-variables-rgx=_$|dummy
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+
+[CLASSES]
+
+# List of interface methods to ignore, separated by a comma. This is used for
+# instance to not check methods defines in Zope's Interface base class.
+ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+# List of valid names for the first argument in a metaclass class method.
+valid-metaclass-classmethod-first-arg=mcs
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=5
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore
+ignored-argument-names=_.*
+
+# Maximum number of locals for function / method body
+max-locals=15
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of branch for function / method body
+max-branches=12
+
+# Maximum number of statements in function / method body
+max-statements=50
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=regsub,TERMIOS,Bastion,rexec
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled)
+int-import-graph=
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "Exception"
+overgeneral-exceptions=Exception
diff --git a/cgcs-patch/cgcs-patch/tox.ini b/cgcs-patch/cgcs-patch/tox.ini
index ba9c568..88e5723 100644
--- a/cgcs-patch/cgcs-patch/tox.ini
+++ b/cgcs-patch/cgcs-patch/tox.ini
@@ -76,7 +76,6 @@ exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,release-tag-*
enable-extensions = H106 H203 H904
max-line-length = 120
-
[testenv:flake8]
basepython = python3
usedevelop = False
@@ -85,13 +84,12 @@ commands =
flake8 {posargs} .
[testenv:pylint]
+basepython = python3
deps = {[testenv]deps}
pylint
-
-basepython = python2.7
sitepackages = False
-
commands = pylint cgcs_patch --rcfile=./pylint.rc
+ pylint cgcs_make_patch --rcfile=./pylint_make_patch.rc
[testenv:cover]
setenv =

View File

@ -1,213 +0,0 @@
From d6675196199ddcefccba0d5d745ac4e93aaecd0f Mon Sep 17 00:00:00 2001
From: Don Penney <don.penney@windriver.com>
Date: Wed, 4 Dec 2019 22:26:52 -0500
Subject: [PATCH] Address python3 pylint errors and warnings
This commit addresses issues detected by the updated python3 pylint:
- Added a return code to the report_app_dependencies function to
satisfy the E1111 error reported.
- Added line-specific pylint disable for unused-argument for cases
where the inclusion of such arguments in the function signature was
intentional.
- Added line-specific pylint disable for the duplicate-except case
found, as python3 has merged IOError into OSError, while these are
separate exceptions in python2. Once we're running solely on python3,
this duplicate exception handling can be dropped.
Change-Id: I96a521288e71948f06ad0c88a12c8f475ed8bc99
Closes-Bug: 1855180
Signed-off-by: Don Penney <don.penney@windriver.com>
---
cgcs-patch/cgcs-patch/cgcs_patch/api/controllers/root.py | 4 ++--
cgcs-patch/cgcs-patch/cgcs_patch/messages.py | 2 +-
cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py | 6 +++---
cgcs-patch/cgcs-patch/cgcs_patch/patch_client.py | 6 +++---
cgcs-patch/cgcs-patch/cgcs_patch/patch_controller.py | 8 +++++---
cgcs-patch/cgcs-patch/cgcs_patch/patch_functions.py | 2 +-
cgcs-patch/cgcs-patch/cgcs_patch/tests/test_patch_controller.py | 2 +-
cgcs-patch/cgcs-patch/pylint.rc | 6 +-----
8 files changed, 17 insertions(+), 19 deletions(-)
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/api/controllers/root.py b/cgcs-patch/cgcs-patch/cgcs_patch/api/controllers/root.py
index f1e0262..4c7bd7f 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/api/controllers/root.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/api/controllers/root.py
@@ -182,7 +182,7 @@ class PatchAPIController(object):
@expose('json')
@expose('query_hosts.xml', content_type='application/xml')
- def query_hosts(self, *args):
+ def query_hosts(self, *args): # pylint: disable=unused-argument
return dict(data=pc.query_host_cache())
@expose('json')
@@ -197,7 +197,7 @@ class PatchAPIController(object):
@expose('json')
@expose('query.xml', content_type='application/xml')
- def host_install(self, *args):
+ def host_install(self, *args): # pylint: disable=unused-argument
return dict(error="Deprecated: Use host_install_async")
@expose('json')
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/messages.py b/cgcs-patch/cgcs-patch/cgcs_patch/messages.py
index a57ea28..6abc29d 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/messages.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/messages.py
@@ -60,5 +60,5 @@ class PatchMessage(object):
return PATCHMSG_STR[self.msgtype]
return "invalid-type"
- def handle(self, sock, addr):
+ def handle(self, sock, addr): # pylint: disable=unused-argument
LOG.info("Unhandled message type: %s" % self.msgtype)
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py b/cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py
index 77930d7..547db52 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py
@@ -150,7 +150,7 @@ class PatchMessageHelloAgent(messages.PatchMessage):
resp = PatchMessageHelloAgentAck()
resp.send(sock)
- def send(self, sock):
+ def send(self, sock): # pylint: disable=unused-argument
LOG.error("Should not get here")
@@ -196,7 +196,7 @@ class PatchMessageQueryDetailed(messages.PatchMessage):
resp = PatchMessageQueryDetailedResp()
resp.send(sock)
- def send(self, sock):
+ def send(self, sock): # pylint: disable=unused-argument
LOG.error("Should not get here")
@@ -258,7 +258,7 @@ class PatchMessageAgentInstallReq(messages.PatchMessage):
resp.status = pa.handle_install()
resp.send(sock, addr)
- def send(self, sock):
+ def send(self, sock): # pylint: disable=unused-argument
LOG.error("Should not get here")
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/patch_client.py b/cgcs-patch/cgcs-patch/cgcs_patch/patch_client.py
index 705590c..af189fc 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/patch_client.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/patch_client.py
@@ -960,7 +960,7 @@ def wait_for_install_complete(agent_ip):
return rc
-def host_install(debug, args):
+def host_install(debug, args): # pylint: disable=unused-argument
force = False
rc = 0
@@ -1072,7 +1072,7 @@ def patch_upload_dir_req(debug, args):
return check_rc(req)
-def patch_install_local(debug, args):
+def patch_install_local(debug, args): # pylint: disable=unused-argument
""" This function is used to trigger patch installation prior to configuration """
# Check to see if initial configuration has completed
if os.path.isfile(INITIAL_CONTROLLER_CONFIG_COMPLETE):
@@ -1214,7 +1214,7 @@ def patch_is_available_req(args):
return rc
-def patch_report_app_dependencies_req(debug, args):
+def patch_report_app_dependencies_req(debug, args): # pylint: disable=unused-argument
if len(args) < 2:
print_help()
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/patch_controller.py b/cgcs-patch/cgcs-patch/cgcs_patch/patch_controller.py
index 4b94a5f..79a6401 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/patch_controller.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/patch_controller.py
@@ -392,7 +392,7 @@ class PatchMessageHelloAgentAck(messages.PatchMessage):
self.agent_state)
pc.hosts_lock.release()
- def send(self, sock):
+ def send(self, sock): # pylint: disable=unused-argument
LOG.error("Should not get here")
@@ -469,7 +469,7 @@ class PatchMessageQueryDetailedResp(messages.PatchMessage):
else:
pc.hosts_lock.release()
- def send(self, sock):
+ def send(self, sock): # pylint: disable=unused-argument
LOG.error("Should not get here")
@@ -525,7 +525,7 @@ class PatchMessageAgentInstallResp(messages.PatchMessage):
pc.hosts[addr[0]].install_reject_reason = self.reject_reason
pc.hosts_lock.release()
- def send(self, sock):
+ def send(self, sock): # pylint: disable=unused-argument
LOG.error("Should not get here")
@@ -2298,6 +2298,8 @@ class PatchController(PatchService):
finally:
self.patch_data_lock.release()
+ return True
+
def query_app_dependencies(self):
"""
Query application dependencies
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/patch_functions.py b/cgcs-patch/cgcs-patch/cgcs_patch/patch_functions.py
index 281a286..e9017f2 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/patch_functions.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/patch_functions.py
@@ -1253,7 +1253,7 @@ class PatchFile(object):
msg = "Failed during patch extraction"
LOG.exception(msg)
raise PatchFail(msg)
- except IOError:
+ except IOError: # pylint: disable=duplicate-except
msg = "Failed during patch extraction"
LOG.exception(msg)
raise PatchFail(msg)
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/tests/test_patch_controller.py b/cgcs-patch/cgcs-patch/cgcs_patch/tests/test_patch_controller.py
index e2b02c0..1db4b68 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/tests/test_patch_controller.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/tests/test_patch_controller.py
@@ -17,6 +17,6 @@ import cgcs_patch.patch_controller # noqa: E402
class CgcsPatchControllerTestCase(testtools.TestCase):
@mock.patch('six.moves.builtins.open')
- def test_cgcs_patch_controller_instantiate(self, mock_open):
+ def test_cgcs_patch_controller_instantiate(self, mock_open): # pylint: disable=unused-argument
# pylint: disable=unused-variable
pc = cgcs_patch.patch_controller.PatchController() # noqa: F841
diff --git a/cgcs-patch/cgcs-patch/pylint.rc b/cgcs-patch/cgcs-patch/pylint.rc
index 812b6b5..a2d888b 100644
--- a/cgcs-patch/cgcs-patch/pylint.rc
+++ b/cgcs-patch/cgcs-patch/pylint.rc
@@ -44,16 +44,12 @@ symbols=no
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
-# E1111 assignment-from-no-return
# W0107 unnecessary-pass
# W0603 global-statement
-# W0612 unused-variable
-# W0613 unused-argument
# W0703 broad-except
-# W0705 duplicate-except
# W1201 logging-not-lazy
# W1505, deprecated-method
-disable=C, R, E1111, W0107, W0603, W0612, W0613, W0703, W0705, W1201, W1505
+disable=C, R, W0107, W0603, W0703, W1201, W1505
[REPORTS]

View File

@ -1,673 +0,0 @@
From b206b6574a75dfc3793886529064e3d938759be8 Mon Sep 17 00:00:00 2001
From: Don Penney <don.penney@windriver.com>
Date: Mon, 23 Dec 2019 14:36:08 -0500
Subject: [PATCH] Clean up pylint W1201 logging-not-lazy in cgcs-patch
Change-Id: Ib461890ddf7635645d42660dc07a153e2449b09e
Story: 2007050
Task: 37874
Signed-off-by: Don Penney <don.penney@windriver.com>
---
.../cgcs-patch/cgcs_patch/api/controllers/root.py | 2 +-
cgcs-patch/cgcs-patch/cgcs_patch/base.py | 4 +-
cgcs-patch/cgcs-patch/cgcs_patch/messages.py | 2 +-
cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py | 76 +++++++++----------
.../cgcs-patch/cgcs_patch/patch_controller.py | 86 +++++++++++-----------
cgcs-patch/cgcs-patch/pylint.rc | 3 +-
6 files changed, 86 insertions(+), 87 deletions(-)
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/api/controllers/root.py b/cgcs-patch/cgcs-patch/cgcs_patch/api/controllers/root.py
index 4c7bd7f..883b58d 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/api/controllers/root.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/api/controllers/root.py
@@ -135,7 +135,7 @@ class PatchAPIController(object):
def upload_dir(self, **kwargs):
files = []
for path in kwargs.values():
- LOG.info("upload-dir: Retrieving patches from %s" % path)
+ LOG.info("upload-dir: Retrieving patches from %s", path)
for f in glob.glob(path + '/*.patch'):
if os.path.isfile(f):
files.append(f)
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/base.py b/cgcs-patch/cgcs-patch/cgcs_patch/base.py
index 8e47905..e12e26c 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/base.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/base.py
@@ -160,11 +160,11 @@ class PatchService(object):
if result == self.mcast_addr:
return
except subprocess.CalledProcessError as e:
- LOG.error("Command output: %s" % e.output)
+ LOG.error("Command output: %s", e.output)
return
# Close the socket and set it up again
- LOG.info("Detected missing multicast addr (%s). Reconfiguring" % self.mcast_addr)
+ LOG.info("Detected missing multicast addr (%s). Reconfiguring", self.mcast_addr)
while self.setup_socket() is None:
LOG.info("Unable to setup sockets. Waiting to retry")
time.sleep(5)
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/messages.py b/cgcs-patch/cgcs-patch/cgcs_patch/messages.py
index 6abc29d..86ff99f 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/messages.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/messages.py
@@ -61,4 +61,4 @@ class PatchMessage(object):
return "invalid-type"
def handle(self, sock, addr): # pylint: disable=unused-argument
- LOG.info("Unhandled message type: %s" % self.msgtype)
+ LOG.info("Unhandled message type: %s", self.msgtype)
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py b/cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py
index 547db52..3abd891 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py
@@ -70,7 +70,7 @@ def setflag(fname):
with open(fname, "w") as f:
f.write("%d\n" % os.getpid())
except Exception:
- LOG.exception("Failed to update %s flag" % fname)
+ LOG.exception("Failed to update %s flag", fname)
def clearflag(fname):
@@ -78,7 +78,7 @@ def clearflag(fname):
try:
os.remove(fname)
except Exception:
- LOG.exception("Failed to clear %s flag" % fname)
+ LOG.exception("Failed to clear %s flag", fname)
def check_install_uuid():
@@ -101,7 +101,7 @@ def check_install_uuid():
controller_install_uuid = str(req.text).rstrip()
if install_uuid != controller_install_uuid:
- LOG.error("Local install_uuid=%s doesn't match controller=%s" % (install_uuid, controller_install_uuid))
+ LOG.error("Local install_uuid=%s doesn't match controller=%s", install_uuid, controller_install_uuid)
return False
return True
@@ -239,7 +239,7 @@ class PatchMessageAgentInstallReq(messages.PatchMessage):
messages.PatchMessage.encode(self)
def handle(self, sock, addr):
- LOG.info("Handling host install request, force=%s" % self.force)
+ LOG.info("Handling host install request, force=%s", self.force)
global pa
resp = PatchMessageAgentInstallResp()
@@ -354,7 +354,7 @@ class PatchAgent(PatchService):
config = yaml.load(output)
except subprocess.CalledProcessError as e:
LOG.exception("Failed to query channels")
- LOG.error("Command output: %s" % e.output)
+ LOG.error("Command output: %s", e.output)
return False
except Exception:
LOG.exception("Failed to query channels")
@@ -390,23 +390,23 @@ class PatchAgent(PatchService):
config[channel].get('baseurl') != ch_baseurl):
# Config is invalid
add_channel = True
- LOG.warning("Invalid smart config found for %s" % channel)
+ LOG.warning("Invalid smart config found for %s", channel)
try:
output = subprocess.check_output(smart_cmd +
["channel", "--yes",
"--remove", channel],
stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
- LOG.exception("Failed to configure %s channel" % channel)
- LOG.error("Command output: %s" % e.output)
+ LOG.exception("Failed to configure %s channel", channel)
+ LOG.error("Command output: %s", e.output)
return False
else:
# Channel is missing
add_channel = True
- LOG.warning("Channel %s is missing from config" % channel)
+ LOG.warning("Channel %s is missing from config", channel)
if add_channel:
- LOG.info("Adding channel %s" % channel)
+ LOG.info("Adding channel %s", channel)
cmd_args = ["channel", "--yes", "--add", channel,
"type=%s" % ch_type,
"name=%s" % ch_name]
@@ -417,8 +417,8 @@ class PatchAgent(PatchService):
output = subprocess.check_output(smart_cmd + cmd_args,
stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
- LOG.exception("Failed to configure %s channel" % channel)
- LOG.error("Command output: %s" % e.output)
+ LOG.exception("Failed to configure %s channel", channel)
+ LOG.error("Command output: %s", e.output)
return False
updated = True
@@ -431,7 +431,7 @@ class PatchAgent(PatchService):
config = yaml.load(output)
except subprocess.CalledProcessError as e:
LOG.exception("Failed to query smart config")
- LOG.error("Command output: %s" % e.output)
+ LOG.error("Command output: %s", e.output)
return False
except Exception:
LOG.exception("Failed to query smart config")
@@ -441,15 +441,15 @@ class PatchAgent(PatchService):
nolinktos = 'rpm-nolinktos'
if config.get(nolinktos) is not True:
# Set the flag
- LOG.warning("Setting %s option" % nolinktos)
+ LOG.warning("Setting %s option", nolinktos)
try:
output = subprocess.check_output(smart_cmd +
["config", "--set",
"%s=true" % nolinktos],
stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
- LOG.exception("Failed to configure %s option" % nolinktos)
- LOG.error("Command output: %s" % e.output)
+ LOG.exception("Failed to configure %s option", nolinktos)
+ LOG.error("Command output: %s", e.output)
return False
updated = True
@@ -458,15 +458,15 @@ class PatchAgent(PatchService):
nosignature = 'rpm-check-signatures'
if config.get(nosignature) is not False:
# Set the flag
- LOG.warning("Setting %s option" % nosignature)
+ LOG.warning("Setting %s option", nosignature)
try:
output = subprocess.check_output(smart_cmd +
["config", "--set",
"%s=false" % nosignature],
stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
- LOG.exception("Failed to configure %s option" % nosignature)
- LOG.error("Command output: %s" % e.output)
+ LOG.exception("Failed to configure %s option", nosignature)
+ LOG.error("Command output: %s", e.output)
return False
updated = True
@@ -476,7 +476,7 @@ class PatchAgent(PatchService):
subprocess.check_output(smart_update, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
LOG.exception("Failed to update smartpm")
- LOG.error("Command output: %s" % e.output)
+ LOG.error("Command output: %s", e.output)
return False
# Reset the patch op counter to force a detailed query
@@ -584,7 +584,7 @@ class PatchAgent(PatchService):
self.installed[pkgname] = version.split('@')[0]
break
except subprocess.CalledProcessError:
- LOG.error("Failed to query installed version of %s" % pkgname)
+ LOG.error("Failed to query installed version of %s", pkgname)
self.changes = True
@@ -641,7 +641,7 @@ class PatchAgent(PatchService):
subprocess.check_output(smart_update, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
LOG.error("Failed to update smartpm")
- LOG.error("Command output: %s" % e.output)
+ LOG.error("Command output: %s", e.output)
# Set a state to "unknown"?
return False
@@ -663,7 +663,7 @@ class PatchAgent(PatchService):
output = subprocess.check_output(smart_query_installed)
pkgs_installed = self.parse_smart_pkglist(output)
except subprocess.CalledProcessError as e:
- LOG.error("Failed to query installed pkgs: %s" % e.output)
+ LOG.error("Failed to query installed pkgs: %s", e.output)
# Set a state to "unknown"?
return False
@@ -671,7 +671,7 @@ class PatchAgent(PatchService):
output = subprocess.check_output(smart_query_base)
pkgs_base = self.parse_smart_pkglist(output)
except subprocess.CalledProcessError as e:
- LOG.error("Failed to query base pkgs: %s" % e.output)
+ LOG.error("Failed to query base pkgs: %s", e.output)
# Set a state to "unknown"?
return False
@@ -679,7 +679,7 @@ class PatchAgent(PatchService):
output = subprocess.check_output(smart_query_updates)
pkgs_updates = self.parse_smart_pkglist(output)
except subprocess.CalledProcessError as e:
- LOG.error("Failed to query patched pkgs: %s" % e.output)
+ LOG.error("Failed to query patched pkgs: %s", e.output)
# Set a state to "unknown"?
return False
@@ -722,11 +722,11 @@ class PatchAgent(PatchService):
# Look for new packages
self.check_groups()
- LOG.info("Patch state query returns %s" % self.changes)
- LOG.info("Installed: %s" % self.installed)
- LOG.info("To install: %s" % self.to_install)
- LOG.info("To remove: %s" % self.to_remove)
- LOG.info("Missing: %s" % self.missing_pkgs)
+ LOG.info("Patch state query returns %s", self.changes)
+ LOG.info("Installed: %s", self.installed)
+ LOG.info("To install: %s", self.to_install)
+ LOG.info("To remove: %s", self.to_remove)
+ LOG.info("Missing: %s", self.missing_pkgs)
return True
@@ -794,16 +794,16 @@ class PatchAgent(PatchService):
try:
if verbose_to_stdout:
print("Installing software updates...")
- LOG.info("Installing: %s" % ", ".join(install_set))
+ LOG.info("Installing: %s", ", ".join(install_set))
output = subprocess.check_output(smart_install_cmd + install_set, stderr=subprocess.STDOUT)
changed = True
for line in output.split('\n'):
- LOG.info("INSTALL: %s" % line)
+ LOG.info("INSTALL: %s", line)
if verbose_to_stdout:
print("Software updated.")
except subprocess.CalledProcessError as e:
LOG.exception("Failed to install RPMs")
- LOG.error("Command output: %s" % e.output)
+ LOG.error("Command output: %s", e.output)
rc = False
if verbose_to_stdout:
print("WARNING: Software update failed.")
@@ -820,16 +820,16 @@ class PatchAgent(PatchService):
try:
if verbose_to_stdout:
print("Handling patch removal...")
- LOG.info("Removing: %s" % ", ".join(remove_set))
+ LOG.info("Removing: %s", ", ".join(remove_set))
output = subprocess.check_output(smart_remove_cmd + remove_set, stderr=subprocess.STDOUT)
changed = True
for line in output.split('\n'):
- LOG.info("REMOVE: %s" % line)
+ LOG.info("REMOVE: %s", line)
if verbose_to_stdout:
print("Patch removal complete.")
except subprocess.CalledProcessError as e:
LOG.exception("Failed to remove RPMs")
- LOG.error("Command output: %s" % e.output)
+ LOG.error("Command output: %s", e.output)
rc = False
if verbose_to_stdout:
print("WARNING: Patch removal failed.")
@@ -862,7 +862,7 @@ class PatchAgent(PatchService):
self.node_is_patched = False
except subprocess.CalledProcessError as e:
LOG.exception("In-Service patch scripts failed")
- LOG.error("Command output: %s" % e.output)
+ LOG.error("Command output: %s", e.output)
# Fail the patching operation
rc = False
@@ -1071,7 +1071,7 @@ def main():
# In certain cases, the lighttpd server could still be running using
# its default port 80, as opposed to the port configured in platform.conf
global http_port_real
- LOG.info("Failed install_uuid check via http_port=%s. Trying with default port 80" % http_port_real)
+ LOG.info("Failed install_uuid check via http_port=%s. Trying with default port 80", http_port_real)
http_port_real = 80
pa.handle_install(verbose_to_stdout=True, disallow_insvc_patch=True)
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/patch_controller.py b/cgcs-patch/cgcs-patch/cgcs_patch/patch_controller.py
index 79a6401..f2b24c8 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/patch_controller.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/patch_controller.py
@@ -137,11 +137,11 @@ class AgentNeighbour(object):
if out_of_date != self.out_of_date or requires_reboot != self.requires_reboot:
self.out_of_date = out_of_date
self.requires_reboot = requires_reboot
- LOG.info("Agent %s (%s) reporting out_of_date=%s, requires_reboot=%s" % (
- self.hostname,
- self.ip,
- self.out_of_date,
- self.requires_reboot))
+ LOG.info("Agent %s (%s) reporting out_of_date=%s, requires_reboot=%s",
+ self.hostname,
+ self.ip,
+ self.out_of_date,
+ self.requires_reboot)
if self.last_query_id != query_id:
self.last_query_id = query_id
@@ -488,7 +488,7 @@ class PatchMessageAgentInstallReq(messages.PatchMessage):
LOG.error("Should not get here")
def send(self, sock):
- LOG.info("sending install request to node: %s" % self.ip)
+ LOG.info("sending install request to node: %s", self.ip)
self.encode()
message = json.dumps(self.message)
sock.sendto(message, (self.ip, cfg.agent_port))
@@ -512,7 +512,7 @@ class PatchMessageAgentInstallResp(messages.PatchMessage):
messages.PatchMessage.encode(self)
def handle(self, sock, addr):
- LOG.info("Handling install resp from %s" % addr[0])
+ LOG.info("Handling install resp from %s", addr[0])
global pc
# LOG.info("Handling hello ack")
@@ -551,7 +551,7 @@ class PatchMessageDropHostReq(messages.PatchMessage):
return
if self.ip is None:
- LOG.error("Received PATCHMSG_DROP_HOST_REQ with no ip: %s" % json.dumps(self.data))
+ LOG.error("Received PATCHMSG_DROP_HOST_REQ with no ip: %s", json.dumps(self.data))
return
pc.drop_host(self.ip, sync_nbr=False)
@@ -602,7 +602,7 @@ class PatchController(PatchService):
with open(app_dependency_filename, 'r') as f:
self.app_dependencies = json.loads(f.read())
except Exception:
- LOG.exception("Failed to read app dependencies: %s" % app_dependency_filename)
+ LOG.exception("Failed to read app dependencies: %s", app_dependency_filename)
else:
self.app_dependencies = {}
@@ -658,7 +658,7 @@ class PatchController(PatchService):
counter = config.getint('runtime', 'patch_op_counter')
self.patch_op_counter = counter
- LOG.info("patch_op_counter is: %d" % self.patch_op_counter)
+ LOG.info("patch_op_counter is: %d", self.patch_op_counter)
except configparser.Error:
LOG.exception("Failed to read state info")
@@ -679,9 +679,9 @@ class PatchController(PatchService):
"rsync://%s/patching/" % host_url,
"%s/" % patch_dir],
stderr=subprocess.STDOUT)
- LOG.info("Synced to mate patching via rsync: %s" % output)
+ LOG.info("Synced to mate patching via rsync: %s", output)
except subprocess.CalledProcessError as e:
- LOG.error("Failed to rsync: %s" % e.output)
+ LOG.error("Failed to rsync: %s", e.output)
return False
try:
@@ -691,9 +691,9 @@ class PatchController(PatchService):
"rsync://%s/repo/" % host_url,
"%s/" % repo_root_dir],
stderr=subprocess.STDOUT)
- LOG.info("Synced to mate repo via rsync: %s" % output)
+ LOG.info("Synced to mate repo via rsync: %s", output)
except subprocess.CalledProcessError:
- LOG.error("Failed to rsync: %s" % output)
+ LOG.error("Failed to rsync: %s", output)
return False
self.read_state_file()
@@ -710,7 +710,7 @@ class PatchController(PatchService):
with open(app_dependency_filename, 'r') as f:
self.app_dependencies = json.loads(f.read())
except Exception:
- LOG.exception("Failed to read app dependencies: %s" % app_dependency_filename)
+ LOG.exception("Failed to read app dependencies: %s", app_dependency_filename)
else:
self.app_dependencies = {}
@@ -757,7 +757,7 @@ class PatchController(PatchService):
continue
if patch_id not in self.patch_data.metadata:
- LOG.error("Patch data missing for %s" % patch_id)
+ LOG.error("Patch data missing for %s", patch_id)
continue
# If the patch is on a different release than the host, skip it.
@@ -811,7 +811,7 @@ class PatchController(PatchService):
continue
if patch_id not in self.patch_data.metadata:
- LOG.error("Patch data missing for %s" % patch_id)
+ LOG.error("Patch data missing for %s", patch_id)
continue
if personality not in self.patch_data.metadata[patch_id]:
@@ -835,7 +835,7 @@ class PatchController(PatchService):
continue
if patch_id not in self.patch_data.metadata:
- LOG.error("Patch data missing for %s" % patch_id)
+ LOG.error("Patch data missing for %s", patch_id)
continue
if personality not in self.patch_data.metadata[patch_id]:
@@ -902,10 +902,10 @@ class PatchController(PatchService):
if os.path.exists(semchk):
try:
- LOG.info("Running semantic check: %s" % semchk)
+ LOG.info("Running semantic check: %s", semchk)
subprocess.check_output([semchk] + patch_state_args,
stderr=subprocess.STDOUT)
- LOG.info("Semantic check %s passed" % semchk)
+ LOG.info("Semantic check %s passed", semchk)
except subprocess.CalledProcessError as e:
msg = "Semantic check failed for %s:\n%s" % (patch_id, e.output)
LOG.exception(msg)
@@ -1158,7 +1158,7 @@ class PatchController(PatchService):
# Copy the RPMs. If a failure occurs, clean up copied files.
copied = []
for rpmfile in rpmlist:
- LOG.info("Copy %s to %s" % (rpmfile, rpmlist[rpmfile]))
+ LOG.info("Copy %s to %s", rpmfile, rpmlist[rpmfile])
try:
shutil.copy(rpmfile, rpmlist[rpmfile])
copied.append(rpmlist[rpmfile])
@@ -1167,7 +1167,7 @@ class PatchController(PatchService):
LOG.exception(msg)
# Clean up files
for filename in copied:
- LOG.info("Cleaning up %s" % filename)
+ LOG.info("Cleaning up %s", filename)
os.remove(filename)
raise RpmFail(msg)
@@ -1206,7 +1206,7 @@ class PatchController(PatchService):
"comps.xml",
rdir],
stderr=subprocess.STDOUT)
- LOG.info("Repo[%s] updated:\n%s" % (ver, output))
+ LOG.info("Repo[%s] updated:\n%s", ver, output)
except subprocess.CalledProcessError:
msg = "Failed to update the repo for %s" % ver
LOG.exception(msg)
@@ -1387,7 +1387,7 @@ class PatchController(PatchService):
"comps.xml",
rdir],
stderr=subprocess.STDOUT)
- LOG.info("Repo[%s] updated:\n%s" % (ver, output))
+ LOG.info("Repo[%s] updated:\n%s", ver, output)
except subprocess.CalledProcessError:
msg = "Failed to update the repo for %s" % ver
LOG.exception(msg)
@@ -1529,7 +1529,7 @@ class PatchController(PatchService):
"comps.xml",
repo_dir[release]],
stderr=subprocess.STDOUT)
- LOG.info("Repo[%s] updated:\n%s" % (release, output))
+ LOG.info("Repo[%s] updated:\n%s", release, output)
except subprocess.CalledProcessError:
msg = "Failed to update the repo for %s" % release
LOG.exception(msg)
@@ -1844,7 +1844,7 @@ class PatchController(PatchService):
for patch_id in sorted(patch_ids):
if patch_id not in self.patch_data.metadata.keys():
errormsg = "%s is unrecognized\n" % patch_id
- LOG.info("patch_query_dependencies: %s" % errormsg)
+ LOG.info("patch_query_dependencies: %s", errormsg)
results["error"] += errormsg
failure = True
self.patch_data_lock.release()
@@ -1892,7 +1892,7 @@ class PatchController(PatchService):
errormsg = "A commit cannot be performed with non-REL status patches in the system:\n"
for patch_id in non_rel_list:
errormsg += " %s\n" % patch_id
- LOG.info("patch_commit rejected: %s" % errormsg)
+ LOG.info("patch_commit rejected: %s", errormsg)
results["error"] += errormsg
return results
@@ -1901,7 +1901,7 @@ class PatchController(PatchService):
for patch_id in sorted(patch_ids):
if patch_id not in self.patch_data.metadata.keys():
errormsg = "%s is unrecognized\n" % patch_id
- LOG.info("patch_commit: %s" % errormsg)
+ LOG.info("patch_commit: %s", errormsg)
results["error"] += errormsg
failure = True
self.patch_data_lock.release()
@@ -1925,7 +1925,7 @@ class PatchController(PatchService):
errormsg = "The following patches are not applied and cannot be committed:\n"
for patch_id in avail_list:
errormsg += " %s\n" % patch_id
- LOG.info("patch_commit rejected: %s" % errormsg)
+ LOG.info("patch_commit rejected: %s", errormsg)
results["error"] += errormsg
return results
@@ -2039,7 +2039,7 @@ class PatchController(PatchService):
"comps.xml",
rdir],
stderr=subprocess.STDOUT)
- LOG.info("Repo[%s] updated:\n%s" % (ver, output))
+ LOG.info("Repo[%s] updated:\n%s", ver, output)
except subprocess.CalledProcessError:
msg = "Failed to update the repo for %s" % ver
LOG.exception(msg)
@@ -2100,7 +2100,7 @@ class PatchController(PatchService):
self.hosts_lock.release()
msg = "Unknown host specified: %s" % host_ip
msg_error += msg + "\n"
- LOG.error("Error in host-install: " + msg)
+ LOG.error("Error in host-install: %s", msg)
return dict(info=msg_info, warning=msg_warning, error=msg_error)
msg = "Running host-install for %s (%s), force=%s, async_req=%s" % (host_ip, ip, force, async_req)
@@ -2128,7 +2128,7 @@ class PatchController(PatchService):
# async_req install requested, so return now
msg = "Patch installation request sent to %s." % self.hosts[ip].hostname
msg_info += msg + "\n"
- LOG.info("host-install async_req: " + msg)
+ LOG.info("host-install async_req: %s", msg)
return dict(info=msg_info, warning=msg_warning, error=msg_error)
# Now we wait, up to ten mins... TODO: Wait on a condition
@@ -2141,7 +2141,7 @@ class PatchController(PatchService):
self.hosts_lock.release()
msg = "Agent expired while waiting: %s" % ip
msg_error += msg + "\n"
- LOG.error("Error in host-install: " + msg)
+ LOG.error("Error in host-install: %s", msg)
break
if not self.hosts[ip].install_pending:
@@ -2150,17 +2150,17 @@ class PatchController(PatchService):
if self.hosts[ip].install_status:
msg = "Patch installation was successful on %s." % self.hosts[ip].hostname
msg_info += msg + "\n"
- LOG.info("host-install: " + msg)
+ LOG.info("host-install: %s", msg)
elif self.hosts[ip].install_reject_reason:
msg = "Patch installation rejected by %s. %s" % (
self.hosts[ip].hostname,
self.hosts[ip].install_reject_reason)
msg_error += msg + "\n"
- LOG.error("Error in host-install: " + msg)
+ LOG.error("Error in host-install: %s", msg)
else:
msg = "Patch installation failed on %s." % self.hosts[ip].hostname
msg_error += msg + "\n"
- LOG.error("Error in host-install: " + msg)
+ LOG.error("Error in host-install: %s", msg)
self.hosts_lock.release()
break
@@ -2172,7 +2172,7 @@ class PatchController(PatchService):
if not resp_rx:
msg = "Timeout occurred while waiting response from %s." % ip
msg_error += msg + "\n"
- LOG.error("Error in host-install: " + msg)
+ LOG.error("Error in host-install: %s", msg)
return dict(info=msg_info, warning=msg_warning, error=msg_error)
@@ -2203,7 +2203,7 @@ class PatchController(PatchService):
self.hosts_lock.release()
msg = "Unknown host specified: %s" % host_ip
msg_error += msg + "\n"
- LOG.error("Error in drop-host: " + msg)
+ LOG.error("Error in drop-host: %s", msg)
return dict(info=msg_info, warning=msg_warning, error=msg_error)
msg = "Running drop-host for %s (%s)" % (host_ip, ip)
@@ -2272,8 +2272,8 @@ class PatchController(PatchService):
appname = kwargs.get("app")
- LOG.info("Handling app dependencies report: app=%s, patch_ids=%s" %
- (appname, ','.join(patch_ids)))
+ LOG.info("Handling app dependencies report: app=%s, patch_ids=%s",
+ appname, ','.join(patch_ids))
self.patch_data_lock.acquire()
@@ -2516,7 +2516,7 @@ class PatchControllerMainThread(threading.Thread):
inputs = [pc.sock_in] + agent_query_conns
outputs = []
- # LOG.info("Running select, remaining=%d" % remaining)
+ # LOG.info("Running select, remaining=%d", remaining)
rlist, wlist, xlist = select.select(inputs, outputs, inputs, remaining)
if (len(rlist) == 0 and
@@ -2641,7 +2641,7 @@ class PatchControllerMainThread(threading.Thread):
for n in nbrs:
# Age out controllers after 2 minutes
if pc.controller_neighbours[n].get_age() >= 120:
- LOG.info("Aging out controller %s from table" % n)
+ LOG.info("Aging out controller %s from table", n)
del pc.controller_neighbours[n]
pc.controller_neighbours_lock.release()
@@ -2650,7 +2650,7 @@ class PatchControllerMainThread(threading.Thread):
for n in nbrs:
# Age out hosts after 1 hour
if pc.hosts[n].get_age() >= 3600:
- LOG.info("Aging out host %s from table" % n)
+ LOG.info("Aging out host %s from table", n)
del pc.hosts[n]
for patch_id in pc.interim_state.keys():
if n in pc.interim_state[patch_id]:
diff --git a/cgcs-patch/cgcs-patch/pylint.rc b/cgcs-patch/cgcs-patch/pylint.rc
index a2d888b..57a9829 100644
--- a/cgcs-patch/cgcs-patch/pylint.rc
+++ b/cgcs-patch/cgcs-patch/pylint.rc
@@ -47,9 +47,8 @@ symbols=no
# W0107 unnecessary-pass
# W0603 global-statement
# W0703 broad-except
-# W1201 logging-not-lazy
# W1505, deprecated-method
-disable=C, R, W0107, W0603, W0703, W1201, W1505
+disable=C, R, W0107, W0603, W0703, W1505
[REPORTS]

View File

@ -1,841 +0,0 @@
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py b/cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py
index 3abd891..d8bc375 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/patch_agent.py
@@ -5,22 +5,26 @@ SPDX-License-Identifier: Apache-2.0
"""
-import os
-import time
-import socket
+import dnf
+import dnf.callback
+import dnf.comps
+import dnf.exceptions
+import dnf.rpm
+import dnf.sack
+import dnf.transaction
import json
-import select
-import subprocess
+import libdnf.transaction
+import os
import random
import requests
-import xml.etree.ElementTree as ElementTree
-import rpm
-import sys
-import yaml
+import select
import shutil
+import socket
+import subprocess
+import sys
+import time
from cgcs_patch.patch_functions import configure_logging
-from cgcs_patch.patch_functions import parse_pkgver
from cgcs_patch.patch_functions import LOG
import cgcs_patch.config as cfg
from cgcs_patch.base import PatchService
@@ -50,19 +54,13 @@ pa = None
http_port_real = http_port
-# Smart commands
-smart_cmd = ["/usr/bin/smart"]
-smart_quiet = smart_cmd + ["--quiet"]
-smart_update = smart_quiet + ["update"]
-smart_newer = smart_quiet + ["newer"]
-smart_orphans = smart_quiet + ["query", "--orphans", "--show-format", "$name\n"]
-smart_query = smart_quiet + ["query"]
-smart_query_repos = smart_quiet + ["query", "--channel=base", "--channel=updates"]
-smart_install_cmd = smart_cmd + ["install", "--yes", "--explain"]
-smart_remove_cmd = smart_cmd + ["remove", "--yes", "--explain"]
-smart_query_installed = smart_quiet + ["query", "--installed", "--show-format", "$name $version\n"]
-smart_query_base = smart_quiet + ["query", "--channel=base", "--show-format", "$name $version\n"]
-smart_query_updates = smart_quiet + ["query", "--channel=updates", "--show-format", "$name $version\n"]
+# DNF commands
+dnf_cmd = ['/usr/bin/dnf']
+dnf_quiet = dnf_cmd + ['--quiet']
+dnf_makecache = dnf_quiet + ['makecache',
+ '--disablerepo="*"',
+ '--enablerepo', 'platform-base',
+ '--enablerepo', 'platform-updates']
def setflag(fname):
@@ -123,10 +121,6 @@ class PatchMessageHelloAgent(messages.PatchMessage):
def handle(self, sock, addr):
# Send response
- # Run the smart config audit
- global pa
- pa.timed_audit_smart_config()
-
#
# If a user tries to do a host-install on an unlocked node,
# without bypassing the lock check (either via in-service
@@ -289,6 +283,46 @@ class PatchMessageAgentInstallResp(messages.PatchMessage):
resp.send(sock)
+class PatchAgentDnfTransLogCB(dnf.callback.TransactionProgress):
+ def __init__(self):
+ dnf.callback.TransactionProgress.__init__(self)
+
+ self.log_prefix = 'dnf trans'
+
+ def progress(self, package, action, ti_done, ti_total, ts_done, ts_total):
+ if action in dnf.transaction.ACTIONS:
+ action_str = dnf.transaction.ACTIONS[action]
+ elif action == dnf.transaction.TRANS_POST:
+ action_str = 'Post transaction'
+ else:
+ action_str = 'unknown(%d)' % action
+
+ if ti_done is not None:
+ # To reduce the volume of logs, only log 0% and 100%
+ if ti_done == 0 or ti_done == ti_total:
+ LOG.info('%s PROGRESS %s: %s %0.1f%% [%s/%s]',
+ self.log_prefix, action_str, package,
+ (ti_done * 100 / ti_total),
+ ts_done, ts_total)
+ else:
+ LOG.info('%s PROGRESS %s: %s [%s/%s]',
+ self.log_prefix, action_str, package, ts_done, ts_total)
+
+ def filelog(self, package, action):
+ if action in dnf.transaction.FILE_ACTIONS:
+ msg = '%s: %s' % (dnf.transaction.FILE_ACTIONS[action], package)
+ else:
+ msg = '%s: %s' % (package, action)
+ LOG.info('%s FILELOG %s', self.log_prefix, msg)
+
+ def scriptout(self, msgs):
+ if msgs:
+ LOG.info("%s SCRIPTOUT :\n%s", self.log_prefix, msgs)
+
+ def error(self, message):
+ LOG.error("%s ERROR: %s", self.log_prefix, message)
+
+
class PatchAgent(PatchService):
def __init__(self):
PatchService.__init__(self)
@@ -298,9 +332,14 @@ class PatchAgent(PatchService):
self.listener = None
self.changes = False
self.installed = {}
+ self.installed_dnf = []
self.to_install = {}
+ self.to_install_dnf = []
+ self.to_downgrade_dnf = []
self.to_remove = []
+ self.to_remove_dnf = []
self.missing_pkgs = []
+ self.missing_pkgs_dnf = []
self.patch_op_counter = 0
self.node_is_patched = os.path.exists(node_is_patched_file)
self.node_is_patched_timestamp = 0
@@ -308,6 +347,7 @@ class PatchAgent(PatchService):
self.state = constants.PATCH_AGENT_STATE_IDLE
self.last_config_audit = 0
self.rejection_timestamp = 0
+ self.dnfb = None
# Check state flags
if os.path.exists(patch_installing_file):
@@ -343,289 +383,40 @@ class PatchAgent(PatchService):
self.listener.bind(('', self.port))
self.listener.listen(2) # Allow two connections, for two controllers
- def audit_smart_config(self):
- LOG.info("Auditing smart configuration")
-
- # Get the current channel config
- try:
- output = subprocess.check_output(smart_cmd +
- ["channel", "--yaml"],
- stderr=subprocess.STDOUT)
- config = yaml.load(output)
- except subprocess.CalledProcessError as e:
- LOG.exception("Failed to query channels")
- LOG.error("Command output: %s", e.output)
- return False
- except Exception:
- LOG.exception("Failed to query channels")
- return False
-
- expected = [{'channel': 'rpmdb',
- 'type': 'rpm-sys',
- 'name': 'RPM Database',
- 'baseurl': None},
- {'channel': 'base',
- 'type': 'rpm-md',
- 'name': 'Base',
- 'baseurl': "http://controller:%s/feed/rel-%s" % (http_port_real, SW_VERSION)},
- {'channel': 'updates',
- 'type': 'rpm-md',
- 'name': 'Patches',
- 'baseurl': "http://controller:%s/updates/rel-%s" % (http_port_real, SW_VERSION)}]
-
- updated = False
-
- for item in expected:
- channel = item['channel']
- ch_type = item['type']
- ch_name = item['name']
- ch_baseurl = item['baseurl']
-
- add_channel = False
-
- if channel in config:
- # Verify existing channel config
- if (config[channel].get('type') != ch_type or
- config[channel].get('name') != ch_name or
- config[channel].get('baseurl') != ch_baseurl):
- # Config is invalid
- add_channel = True
- LOG.warning("Invalid smart config found for %s", channel)
- try:
- output = subprocess.check_output(smart_cmd +
- ["channel", "--yes",
- "--remove", channel],
- stderr=subprocess.STDOUT)
- except subprocess.CalledProcessError as e:
- LOG.exception("Failed to configure %s channel", channel)
- LOG.error("Command output: %s", e.output)
- return False
- else:
- # Channel is missing
- add_channel = True
- LOG.warning("Channel %s is missing from config", channel)
-
- if add_channel:
- LOG.info("Adding channel %s", channel)
- cmd_args = ["channel", "--yes", "--add", channel,
- "type=%s" % ch_type,
- "name=%s" % ch_name]
- if ch_baseurl is not None:
- cmd_args += ["baseurl=%s" % ch_baseurl]
-
- try:
- output = subprocess.check_output(smart_cmd + cmd_args,
- stderr=subprocess.STDOUT)
- except subprocess.CalledProcessError as e:
- LOG.exception("Failed to configure %s channel", channel)
- LOG.error("Command output: %s", e.output)
- return False
-
- updated = True
-
- # Validate the smart config
- try:
- output = subprocess.check_output(smart_cmd +
- ["config", "--yaml"],
- stderr=subprocess.STDOUT)
- config = yaml.load(output)
- except subprocess.CalledProcessError as e:
- LOG.exception("Failed to query smart config")
- LOG.error("Command output: %s", e.output)
- return False
- except Exception:
- LOG.exception("Failed to query smart config")
- return False
-
- # Check for the rpm-nolinktos flag
- nolinktos = 'rpm-nolinktos'
- if config.get(nolinktos) is not True:
- # Set the flag
- LOG.warning("Setting %s option", nolinktos)
- try:
- output = subprocess.check_output(smart_cmd +
- ["config", "--set",
- "%s=true" % nolinktos],
- stderr=subprocess.STDOUT)
- except subprocess.CalledProcessError as e:
- LOG.exception("Failed to configure %s option", nolinktos)
- LOG.error("Command output: %s", e.output)
- return False
-
- updated = True
-
- # Check for the rpm-check-signatures flag
- nosignature = 'rpm-check-signatures'
- if config.get(nosignature) is not False:
- # Set the flag
- LOG.warning("Setting %s option", nosignature)
- try:
- output = subprocess.check_output(smart_cmd +
- ["config", "--set",
- "%s=false" % nosignature],
- stderr=subprocess.STDOUT)
- except subprocess.CalledProcessError as e:
- LOG.exception("Failed to configure %s option", nosignature)
- LOG.error("Command output: %s", e.output)
- return False
-
- updated = True
-
- if updated:
- try:
- subprocess.check_output(smart_update, stderr=subprocess.STDOUT)
- except subprocess.CalledProcessError as e:
- LOG.exception("Failed to update smartpm")
- LOG.error("Command output: %s", e.output)
- return False
-
- # Reset the patch op counter to force a detailed query
- self.patch_op_counter = 0
-
- self.last_config_audit = time.time()
- return True
-
- def timed_audit_smart_config(self):
- rc = True
- if (time.time() - self.last_config_audit) > 1800:
- # It's been 30 minutes since the last completed audit
- LOG.info("Kicking timed audit")
- rc = self.audit_smart_config()
-
- return rc
-
@staticmethod
- def parse_smart_pkglist(output):
- pkglist = {}
- for line in output.splitlines():
- if line == '':
- continue
-
- fields = line.split()
- pkgname = fields[0]
- (version, arch) = fields[1].split('@')
-
- if pkgname not in pkglist:
- pkglist[pkgname] = {}
- pkglist[pkgname][arch] = version
- elif arch not in pkglist[pkgname]:
- pkglist[pkgname][arch] = version
+ def pkgobjs_to_list(pkgobjs):
+ # Transform pkgobj list to format used by patch-controller
+ output = {}
+ for pkg in pkgobjs:
+ if pkg.epoch != 0:
+ output[pkg.name] = "%s:%s-%s@%s" % (pkg.epoch, pkg.version, pkg.release, pkg.arch)
else:
- stored_ver = pkglist[pkgname][arch]
-
- # The rpm.labelCompare takes version broken into 3 components
- # It returns:
- # 1, if first arg is higher version
- # 0, if versions are same
- # -1, if first arg is lower version
- rc = rpm.labelCompare(parse_pkgver(version),
- parse_pkgver(stored_ver))
+ output[pkg.name] = "%s-%s@%s" % (pkg.version, pkg.release, pkg.arch)
- if rc > 0:
- # Update version
- pkglist[pkgname][arch] = version
+ return output
- return pkglist
+ def dnf_reset_client(self):
+ if self.dnfb is not None:
+ self.dnfb.close()
+ self.dnfb = None
- @staticmethod
- def get_pkg_version(pkglist, pkg, arch):
- if pkg not in pkglist:
- return None
- if arch not in pkglist[pkg]:
- return None
- return pkglist[pkg][arch]
-
- def parse_smart_newer(self, output):
- # Skip the first two lines, which are headers
- for line in output.splitlines()[2:]:
- if line == '':
- continue
-
- fields = line.split()
- pkgname = fields[0]
- installedver = fields[2]
- newver = fields[5]
+ self.dnfb = dnf.Base()
+ self.dnfb.conf.substitutions['infra'] = 'stock'
- self.installed[pkgname] = installedver
- self.to_install[pkgname] = newver
-
- def parse_smart_orphans(self, output):
- for pkgname in output.splitlines():
- if pkgname == '':
- continue
+ # Reset default installonlypkgs list
+ self.dnfb.conf.installonlypkgs = []
- highest_version = None
+ self.dnfb.read_all_repos()
- try:
- query = subprocess.check_output(smart_query_repos + ["--show-format", '$version\n', pkgname])
- # The last non-blank version is the highest
- for version in query.splitlines():
- if version == '':
- continue
- highest_version = version.split('@')[0]
-
- except subprocess.CalledProcessError:
- # Package is not in the repo
- highest_version = None
-
- if highest_version is None:
- # Package is to be removed
- self.to_remove.append(pkgname)
+ # Ensure only platform repos are enabled for transaction
+ for repo in self.dnfb.repos.all():
+ if repo.id == 'platform-base' or repo.id == 'platform-updates':
+ repo.enable()
else:
- # Rollback to the highest version
- self.to_install[pkgname] = highest_version
+ repo.disable()
- # Get the installed version
- try:
- query = subprocess.check_output(smart_query + ["--installed", "--show-format", '$version\n', pkgname])
- for version in query.splitlines():
- if version == '':
- continue
- self.installed[pkgname] = version.split('@')[0]
- break
- except subprocess.CalledProcessError:
- LOG.error("Failed to query installed version of %s", pkgname)
-
- self.changes = True
-
- def check_groups(self):
- # Get the groups file
- mygroup = "updates-%s" % "-".join(subfunctions)
- self.missing_pkgs = []
- installed_pkgs = []
-
- groups_url = "http://controller:%s/updates/rel-%s/comps.xml" % (http_port_real, SW_VERSION)
- try:
- req = requests.get(groups_url)
- if req.status_code != 200:
- LOG.error("Failed to get groups list from server")
- return False
- except requests.ConnectionError:
- LOG.error("Failed to connect to server")
- return False
-
- # Get list of installed packages
- try:
- query = subprocess.check_output(["rpm", "-qa", "--queryformat", "%{NAME}\n"])
- installed_pkgs = query.split()
- except subprocess.CalledProcessError:
- LOG.exception("Failed to query RPMs")
- return False
-
- root = ElementTree.fromstring(req.text)
- for child in root:
- group_id = child.find('id')
- if group_id is None or group_id.text != mygroup:
- continue
-
- pkglist = child.find('packagelist')
- if pkglist is None:
- continue
-
- for pkg in pkglist.findall('packagereq'):
- if pkg.text not in installed_pkgs and pkg.text not in self.missing_pkgs:
- self.missing_pkgs.append(pkg.text)
- self.changes = True
+ # Read repo info
+ self.dnfb.fill_sack()
def query(self):
""" Check current patch state """
@@ -633,14 +424,15 @@ class PatchAgent(PatchService):
LOG.info("Failed install_uuid check. Skipping query")
return False
- if not self.audit_smart_config():
- # Set a state to "unknown"?
- return False
+ if self.dnfb is not None:
+ self.dnfb.close()
+ self.dnfb = None
+ # TODO(dpenney): Use python APIs for makecache
try:
- subprocess.check_output(smart_update, stderr=subprocess.STDOUT)
+ subprocess.check_output(dnf_makecache, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
- LOG.error("Failed to update smartpm")
+ LOG.error("Failed to run dnf makecache")
LOG.error("Command output: %s", e.output)
# Set a state to "unknown"?
return False
@@ -649,78 +441,72 @@ class PatchAgent(PatchService):
self.query_id = random.random()
self.changes = False
+ self.installed_dnf = []
self.installed = {}
- self.to_install = {}
+ self.to_install_dnf = []
+ self.to_downgrade_dnf = []
self.to_remove = []
+ self.to_remove_dnf = []
self.missing_pkgs = []
+ self.missing_pkgs_dnf = []
- # Get the repo data
- pkgs_installed = {}
- pkgs_base = {}
- pkgs_updates = {}
-
- try:
- output = subprocess.check_output(smart_query_installed)
- pkgs_installed = self.parse_smart_pkglist(output)
- except subprocess.CalledProcessError as e:
- LOG.error("Failed to query installed pkgs: %s", e.output)
- # Set a state to "unknown"?
- return False
-
- try:
- output = subprocess.check_output(smart_query_base)
- pkgs_base = self.parse_smart_pkglist(output)
- except subprocess.CalledProcessError as e:
- LOG.error("Failed to query base pkgs: %s", e.output)
- # Set a state to "unknown"?
- return False
+ self.dnf_reset_client()
- try:
- output = subprocess.check_output(smart_query_updates)
- pkgs_updates = self.parse_smart_pkglist(output)
- except subprocess.CalledProcessError as e:
- LOG.error("Failed to query patched pkgs: %s", e.output)
- # Set a state to "unknown"?
- return False
+ # Get the repo data
+ pkgs_installed = dnf.sack._rpmdb_sack(self.dnfb).query().installed() # pylint: disable=protected-access
+ avail = self.dnfb.sack.query().available().latest()
- # There are four possible actions:
- # 1. If installed pkg is not in base or updates, remove it.
- # 2. If installed pkg version is higher than highest in base
- # or updates, downgrade it.
- # 3. If installed pkg version is lower than highest in updates,
- # upgrade it.
- # 4. If pkg in grouplist is not in installed, install it.
+ # There are three possible actions:
+ # 1. If installed pkg is not in a repo, remove it.
+ # 2. If installed pkg version does not match newest repo version, update it.
+ # 3. If a package in the grouplist is not installed, install it.
for pkg in pkgs_installed:
- for arch in pkgs_installed[pkg]:
- installed_version = pkgs_installed[pkg][arch]
- updates_version = self.get_pkg_version(pkgs_updates, pkg, arch)
- base_version = self.get_pkg_version(pkgs_base, pkg, arch)
-
- if updates_version is None and base_version is None:
- # Remove it
- self.to_remove.append(pkg)
- self.changes = True
- continue
+ highest = avail.filter(name=pkg.name, arch=pkg.arch)
+ if highest:
+ highest_pkg = highest[0]
- compare_version = updates_version
- if compare_version is None:
- compare_version = base_version
-
- # Compare the installed version to what's in the repo
- rc = rpm.labelCompare(parse_pkgver(installed_version),
- parse_pkgver(compare_version))
- if rc == 0:
- # Versions match, nothing to do.
+ if pkg.evr_eq(highest_pkg):
continue
+
+ if pkg.evr_gt(highest_pkg):
+ self.to_downgrade_dnf.append(highest_pkg)
else:
- # Install the version from the repo
- self.to_install[pkg] = "@".join([compare_version, arch])
- self.installed[pkg] = "@".join([installed_version, arch])
- self.changes = True
+ self.to_install_dnf.append(highest_pkg)
+ else:
+ self.to_remove_dnf.append(pkg)
+ self.to_remove.append(pkg.name)
+
+ self.installed_dnf.append(pkg)
+ self.changes = True
# Look for new packages
- self.check_groups()
+ self.dnfb.read_comps()
+ grp_id = 'updates-%s' % '-'.join(subfunctions)
+ pkggrp = None
+ for grp in self.dnfb.comps.groups_iter():
+ if grp.id == grp_id:
+ pkggrp = grp
+ break
+
+ if pkggrp is None:
+ LOG.error("Could not find software group: %s", grp_id)
+
+ for pkg in pkggrp.packages_iter():
+ try:
+ res = pkgs_installed.filter(name=pkg.name)
+ if len(res) == 0:
+ found_pkg = avail.filter(name=pkg.name)
+ self.missing_pkgs_dnf.append(found_pkg[0])
+ self.missing_pkgs.append(found_pkg[0].name)
+ self.changes = True
+ except dnf.exceptions.PackageNotFoundError:
+ self.missing_pkgs_dnf.append(pkg)
+ self.missing_pkgs.append(pkg.name)
+ self.changes = True
+
+ self.installed = self.pkgobjs_to_list(self.installed_dnf)
+ self.to_install = self.pkgobjs_to_list(self.to_install_dnf + self.to_downgrade_dnf)
LOG.info("Patch state query returns %s", self.changes)
LOG.info("Installed: %s", self.installed)
@@ -730,6 +516,35 @@ class PatchAgent(PatchService):
return True
+ def resolve_dnf_transaction(self, undo_failure=True):
+ LOG.info("Starting to process transaction: undo_failure=%s", undo_failure)
+ self.dnfb.resolve()
+ self.dnfb.download_packages(self.dnfb.transaction.install_set)
+
+ tid = self.dnfb.do_transaction(display=PatchAgentDnfTransLogCB())
+
+ transaction_rc = True
+ for t in self.dnfb.transaction:
+ if t.state != libdnf.transaction.TransactionItemState_DONE:
+ transaction_rc = False
+ break
+
+ self.dnf_reset_client()
+
+ if not transaction_rc:
+ if undo_failure:
+ LOG.error("Failure occurred... Undoing last transaction (%s)", tid)
+ old = self.dnfb.history.old((tid,))[0]
+ mobj = dnf.db.history.MergedTransactionWrapper(old)
+
+ self.dnfb._history_undo_operations(mobj, old.tid, True) # pylint: disable=protected-access
+
+ if not self.resolve_dnf_transaction(undo_failure=False):
+ LOG.error("Failed to undo transaction")
+
+ LOG.info("Transaction complete: undo_failure=%s, success=%s", undo_failure, transaction_rc)
+ return transaction_rc
+
def handle_install(self, verbose_to_stdout=False, disallow_insvc_patch=False):
#
# The disallow_insvc_patch parameter is set when we're installing
@@ -781,64 +596,54 @@ class PatchAgent(PatchService):
if verbose_to_stdout:
print("Checking for software updates...")
self.query()
- install_set = []
- for pkg, version in self.to_install.items():
- install_set.append("%s-%s" % (pkg, version))
-
- install_set += self.missing_pkgs
changed = False
rc = True
- if len(install_set) > 0:
+ if len(self.to_install_dnf) > 0 or len(self.to_downgrade_dnf) > 0:
+ LOG.info("Adding pkgs to installation set: %s", self.to_install)
+ for pkg in self.to_install_dnf:
+ self.dnfb.package_install(pkg)
+
+ for pkg in self.to_downgrade_dnf:
+ self.dnfb.package_downgrade(pkg)
+
+ changed = True
+
+ if len(self.missing_pkgs_dnf) > 0:
+ LOG.info("Adding missing pkgs to installation set: %s", self.missing_pkgs)
+ for pkg in self.missing_pkgs_dnf:
+ self.dnfb.package_install(pkg)
+ changed = True
+
+ if len(self.to_remove_dnf) > 0:
+ LOG.info("Adding pkgs to be removed: %s", self.to_remove)
+ for pkg in self.to_remove_dnf:
+ self.dnfb.package_remove(pkg)
+ changed = True
+
+ if changed:
+ # Run the transaction set
+ transaction_rc = False
try:
- if verbose_to_stdout:
- print("Installing software updates...")
- LOG.info("Installing: %s", ", ".join(install_set))
- output = subprocess.check_output(smart_install_cmd + install_set, stderr=subprocess.STDOUT)
- changed = True
- for line in output.split('\n'):
- LOG.info("INSTALL: %s", line)
- if verbose_to_stdout:
- print("Software updated.")
- except subprocess.CalledProcessError as e:
- LOG.exception("Failed to install RPMs")
- LOG.error("Command output: %s", e.output)
+ transaction_rc = self.resolve_dnf_transaction()
+ except dnf.exceptions.DepsolveError:
+ LOG.error("Failures resolving dependencies in transaction")
+ except dnf.exceptions.DownloadError:
+ LOG.error("Failures downloading in transaction")
+
+ if not transaction_rc:
+ LOG.error("Failures occurred during transaction")
rc = False
if verbose_to_stdout:
print("WARNING: Software update failed.")
+
else:
if verbose_to_stdout:
print("Nothing to install.")
LOG.info("Nothing to install")
- if rc:
- self.query()
- remove_set = self.to_remove
-
- if len(remove_set) > 0:
- try:
- if verbose_to_stdout:
- print("Handling patch removal...")
- LOG.info("Removing: %s", ", ".join(remove_set))
- output = subprocess.check_output(smart_remove_cmd + remove_set, stderr=subprocess.STDOUT)
- changed = True
- for line in output.split('\n'):
- LOG.info("REMOVE: %s", line)
- if verbose_to_stdout:
- print("Patch removal complete.")
- except subprocess.CalledProcessError as e:
- LOG.exception("Failed to remove RPMs")
- LOG.error("Command output: %s", e.output)
- rc = False
- if verbose_to_stdout:
- print("WARNING: Patch removal failed.")
- else:
- if verbose_to_stdout:
- print("Nothing to remove.")
- LOG.info("Nothing to remove")
-
- if changed:
+ if changed and rc:
# Update the node_is_patched flag
setflag(node_is_patched_file)
@@ -1057,7 +862,7 @@ class PatchAgent(PatchService):
def main():
global pa
- configure_logging()
+ configure_logging(dnf_log=True)
cfg.read_config()
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/patch_functions.py b/cgcs-patch/cgcs-patch/cgcs_patch/patch_functions.py
index e9017f2..2ee9fce 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/patch_functions.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/patch_functions.py
@@ -69,7 +69,7 @@ def handle_exception(exc_type, exc_value, exc_traceback):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
-def configure_logging(logtofile=True, level=logging.INFO):
+def configure_logging(logtofile=True, level=logging.INFO, dnf_log=False):
if logtofile:
my_exec = os.path.basename(sys.argv[0])
@@ -84,6 +84,11 @@ def configure_logging(logtofile=True, level=logging.INFO):
main_log_handler = logging.FileHandler(logfile)
main_log_handler.setFormatter(formatter)
LOG.addHandler(main_log_handler)
+
+ if dnf_log:
+ dnf_logger = logging.getLogger('dnf')
+ dnf_logger.addHandler(main_log_handler)
+
try:
os.chmod(logfile, 0o640)
except Exception:
diff --git a/cgcs-patch/cgcs-patch/cgcs_patch/tests/test_patch_agent.py b/cgcs-patch/cgcs-patch/cgcs_patch/tests/test_patch_agent.py
index bd1eef9..7e30fc5 100644
--- a/cgcs-patch/cgcs-patch/cgcs_patch/tests/test_patch_agent.py
+++ b/cgcs-patch/cgcs-patch/cgcs_patch/tests/test_patch_agent.py
@@ -10,6 +10,15 @@ import sys
import testtools
sys.modules['rpm'] = mock.Mock()
+sys.modules['dnf'] = mock.Mock()
+sys.modules['dnf.callback'] = mock.Mock()
+sys.modules['dnf.comps'] = mock.Mock()
+sys.modules['dnf.exceptions'] = mock.Mock()
+sys.modules['dnf.rpm'] = mock.Mock()
+sys.modules['dnf.sack'] = mock.Mock()
+sys.modules['dnf.transaction'] = mock.Mock()
+sys.modules['libdnf'] = mock.Mock()
+sys.modules['libdnf.transaction'] = mock.Mock()
import cgcs_patch.patch_agent # noqa: E402
diff --git a/cgcs-patch/cgcs-patch/pylint.rc b/cgcs-patch/cgcs-patch/pylint.rc
index 57a9829..f511718 100644
--- a/cgcs-patch/cgcs-patch/pylint.rc
+++ b/cgcs-patch/cgcs-patch/pylint.rc
@@ -45,10 +45,11 @@ symbols=no
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
# W0107 unnecessary-pass
+# W0511 fixme
# W0603 global-statement
# W0703 broad-except
# W1505, deprecated-method
-disable=C, R, W0107, W0603, W0703, W1505
+disable=C, R, W0107, W0511, W0603, W0703, W1505
[REPORTS]
@@ -235,7 +236,7 @@ ignore-mixin-members=yes
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis
-ignored-modules=
+ignored-modules=dnf,libdnf
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set).
diff --git a/cgcs-patch/cgcs-patch/test-requirements.txt b/cgcs-patch/cgcs-patch/test-requirements.txt
index 3f4e581..56e4806 100644
--- a/cgcs-patch/cgcs-patch/test-requirements.txt
+++ b/cgcs-patch/cgcs-patch/test-requirements.txt
@@ -8,4 +8,3 @@ coverage!=4.4,>=4.0 # Apache-2.0
mock>=2.0.0 # BSD
stestr>=1.0.0 # Apache-2.0
testtools>=2.2.0 # MIT
-