From 5fdf0290d00a80f3d8a4f6d1375684ccf0046e7d Mon Sep 17 00:00:00 2001 From: Adam Miller Date: Wed, 19 Sep 2018 15:14:25 -0500 Subject: [PATCH] handle yum and dnf lockfiles - fixes #44120 (#45359) * handle yum and dnf lockfiles - fixes #44120 Signed-off-by: Adam Miller * fix logic problem to properly check for dnf lockfile glob Signed-off-by: Adam Miller --- lib/ansible/module_utils/yumdnf.py | 20 ++++++++++++++++++++ lib/ansible/modules/packaging/os/dnf.py | 18 ++++++++++++++++++ lib/ansible/modules/packaging/os/yum.py | 21 +++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/lib/ansible/module_utils/yumdnf.py b/lib/ansible/module_utils/yumdnf.py index aa3e0138a67..eed3ebf21ba 100644 --- a/lib/ansible/module_utils/yumdnf.py +++ b/lib/ansible/module_utils/yumdnf.py @@ -10,6 +10,8 @@ # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) import os +import time +import glob import tempfile from abc import ABCMeta, abstractmethod @@ -43,6 +45,8 @@ yumdnf_argument_spec = dict( update_only=dict(required=False, default="no", type='bool'), validate_certs=dict(type='bool', default=True), # this should not be needed, but exists as a failsafe + lock_poll=dict(type='int', default=-1), + lock_timeout=dict(type='int', default=10), ), required_one_of=[['name', 'list']], mutually_exclusive=[['name', 'list']], @@ -84,6 +88,8 @@ class YumDnf(with_metaclass(ABCMeta, object)): self.update_only = self.module.params['update_only'] self.update_cache = self.module.params['update_cache'] self.validate_certs = self.module.params['validate_certs'] + self.lock_poll = self.module.params['lock_poll'] + self.lock_timeout = self.module.params['lock_timeout'] # It's possible someone passed a comma separated string since it used # to be a string type, so we should handle that @@ -92,6 +98,20 @@ class YumDnf(with_metaclass(ABCMeta, object)): self.enablerepo = self.listify_comma_sep_strings_in_list(self.enablerepo) self.exclude = self.listify_comma_sep_strings_in_list(self.exclude) + # This should really be redefined by both the yum and dnf module but a + # default isn't a bad idea + self.lockfile = '/var/run/yum.pid' + + def wait_for_lock(self): + '''Poll until the lock is removed if interval is a positive number''' + if (os.path.isfile(self.lockfile) or glob.glob(self.lockfile)) and self.lock_timeout > 0: + for iteration in range(0, self.lock_timeout): + time.sleep(self.lock_poll) + if not os.path.isfile(self.lockfile) or not glob.glob(self.lockfile): + break + if os.path.isfile(self.lockfile) or glob.glob(self.lockfile): + self.module.fail_json(msg='{0} lockfile was not released'.format(self.pkg_mgr_name)) + def listify_comma_sep_strings_in_list(self, some_list): """ method to accept a list of strings as the parameter, find any strings diff --git a/lib/ansible/modules/packaging/os/dnf.py b/lib/ansible/modules/packaging/os/dnf.py index d6d6f14344b..f5144b55c94 100644 --- a/lib/ansible/modules/packaging/os/dnf.py +++ b/lib/ansible/modules/packaging/os/dnf.py @@ -176,6 +176,22 @@ options: default: "no" type: bool version_added: "2.7" + lock_poll: + description: + - Poll interval to wait for the dnf lockfile to be freed. + - "By default this is set to -1, if you set it to a positive integer it will enable to polling" + required: false + default: -1 + type: int + version_added: "2.8" + lock_timeout: + description: + - Amount of time to wait for the dnf lockfile to be freed + - This should be set along with C(lock_poll) to enable the lockfile polling. + required: false + default: 10 + type: int + version_added: "2.8" notes: - When used with a `loop:` each package will be processed individually, it is much more efficient to pass the list directly to the `name` option. - Group removal doesn't work if the group was installed with Ansible because @@ -285,6 +301,8 @@ class DnfModule(YumDnf): super(DnfModule, self).__init__(module) self._ensure_dnf() + self.lockfile = "/var/cache/dnf/*_lock.pid" + self.pkg_mgr_name = "dnf" def _sanitize_dnf_error_msg(self, spec, error): """ diff --git a/lib/ansible/modules/packaging/os/yum.py b/lib/ansible/modules/packaging/os/yum.py index 87d06352412..7cb31baedfb 100644 --- a/lib/ansible/modules/packaging/os/yum.py +++ b/lib/ansible/modules/packaging/os/yum.py @@ -183,6 +183,22 @@ options: default: "no" type: bool version_added: "2.7" + lock_poll: + description: + - Poll interval to wait for the yum lockfile to be freed. + - "By default this is set to -1, if you set it to a positive integer it will enable to polling" + required: false + default: -1 + type: int + version_added: "2.8" + lock_timeout: + description: + - Amount of time to wait for the yum lockfile to be freed + - This should be set along with C(lock_poll) to enable the lockfile polling. + required: false + default: 10 + type: int + version_added: "2.8" notes: - When used with a `loop:` each package will be processed individually, it is much more efficient to pass the list directly to the `name` option. @@ -365,6 +381,9 @@ class YumModule(YumDnf): # This populates instance vars for all argument spec params super(YumModule, self).__init__(module) + self.pkg_mgr_name = "yum" + self.lockfile = '/var/run/yum.pid' + def fetch_rpm_from_url(self, spec): # FIXME: Remove this once this PR is merged: # https://github.com/ansible/ansible/pull/19172 @@ -1434,6 +1453,8 @@ class YumModule(YumDnf): if not HAS_YUM_PYTHON: error_msgs.append('The Python 2 yum module is needed for this module. If you require Python 3 support use the `dnf` Ansible module instead.') + self.wait_for_lock() + if self.disable_excludes and yum.__version_info__ < (3, 4): self.module.fail_json(msg="'disable_includes' is available in yum version 3.4 and onwards.")