Merge "Cap the number of workers always when multiplier is not set"

This commit is contained in:
Zuul 2021-03-29 14:23:02 +00:00 committed by Gerrit Code Review
commit f28ae8a3e1
10 changed files with 274 additions and 50 deletions

View File

@ -290,8 +290,8 @@ options:
The CPU core multiplier to use when configuring worker processes for The CPU core multiplier to use when configuring worker processes for
this services e.g. metadata-api. By default, the number of workers for this services e.g. metadata-api. By default, the number of workers for
each daemon is set to twice the number of CPU cores a service unit has. each daemon is set to twice the number of CPU cores a service unit has.
When deployed in a LXD container, this default value will be capped to 4 This default value will be capped to 4 workers unless this
workers unless this configuration option is set. configuration option is set.
# Required if using FlatManager (nova-network) # Required if using FlatManager (nova-network)
bridge-interface: bridge-interface:
type: string type: string

View File

@ -337,10 +337,8 @@ class NRPE(object):
"command": nrpecheck.command, "command": nrpecheck.command,
} }
# If we were passed max_check_attempts, add that to the relation data # If we were passed max_check_attempts, add that to the relation data
try: if nrpecheck.max_check_attempts is not None:
nrpe_monitors[nrpecheck.shortname]['max_check_attempts'] = nrpecheck.max_check_attempts nrpe_monitors[nrpecheck.shortname]['max_check_attempts'] = nrpecheck.max_check_attempts
except AttributeError:
pass
# update-status hooks are configured to firing every 5 minutes by # update-status hooks are configured to firing every 5 minutes by
# default. When nagios-nrpe-server is restarted, the nagios server # default. When nagios-nrpe-server is restarted, the nagios server

View File

@ -25,7 +25,7 @@ from charmhelpers.contrib.network.ovs import ovsdb as ch_ovsdb
from charmhelpers.fetch import apt_install from charmhelpers.fetch import apt_install
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
log, WARNING, INFO, DEBUG log, WARNING, INFO, DEBUG, charm_name
) )
from charmhelpers.core.host import ( from charmhelpers.core.host import (
CompareHostReleases, CompareHostReleases,
@ -666,3 +666,28 @@ def patch_ports_on_bridge(bridge):
# reference to PEP479 just doing a return will provide a emtpy iterator # reference to PEP479 just doing a return will provide a emtpy iterator
# and not None. # and not None.
return return
def generate_external_ids(external_id_value=None):
"""Generate external-ids dictionary that can be used to mark OVS bridges
and ports as managed by the charm.
:param external_id_value: Value of the external-ids entry.
Note: 'managed' will be used if not specified.
:type external_id_value: Optional[str]
:returns: Dict with a single external-ids entry.
{
'external-ids': {
charm-``charm_name``: ``external_id_value``
}
}
:rtype: Dict[str, Dict[str]]
"""
external_id_key = "charm-{}".format(charm_name())
external_id_value = ('managed' if external_id_value is None
else external_id_value)
return {
'external-ids': {
external_id_key: external_id_value
}
}

View File

@ -42,6 +42,7 @@ import pika
import swiftclient import swiftclient
from charmhelpers.core.decorators import retry_on_exception from charmhelpers.core.decorators import retry_on_exception
from charmhelpers.contrib.amulet.utils import ( from charmhelpers.contrib.amulet.utils import (
AmuletUtils AmuletUtils
) )

View File

@ -47,7 +47,7 @@ from charmhelpers.contrib.network.ip import (
) )
from charmhelpers.core.host import ( from charmhelpers.core.host import (
CA_CERT_DIR, ca_cert_absolute_path,
install_ca_cert, install_ca_cert,
mkdir, mkdir,
write_file, write_file,
@ -307,6 +307,26 @@ def install_certs(ssl_dir, certs, chain=None, user='root', group='root'):
content=bundle['key'], perms=0o640) content=bundle['key'], perms=0o640)
def get_cert_relation_ca_name(cert_relation_id=None):
"""Determine CA certificate name as provided by relation.
The filename on disk depends on the name chosen for the application on the
providing end of the certificates relation.
:param cert_relation_id: (Optional) Relation id providing the certs
:type cert_relation_id: str
:returns: CA certificate filename without path nor extension
:rtype: str
"""
if cert_relation_id is None:
try:
cert_relation_id = relation_ids('certificates')[0]
except IndexError:
return ''
return '{}_juju_ca_cert'.format(
remote_service_name(relid=cert_relation_id))
def _manage_ca_certs(ca, cert_relation_id): def _manage_ca_certs(ca, cert_relation_id):
"""Manage CA certs. """Manage CA certs.
@ -316,7 +336,7 @@ def _manage_ca_certs(ca, cert_relation_id):
:type cert_relation_id: str :type cert_relation_id: str
""" """
config_ssl_ca = config('ssl_ca') config_ssl_ca = config('ssl_ca')
config_cert_file = '{}/{}.crt'.format(CA_CERT_DIR, CONFIG_CA_CERT_FILE) config_cert_file = ca_cert_absolute_path(CONFIG_CA_CERT_FILE)
if config_ssl_ca: if config_ssl_ca:
log("Installing CA certificate from charm ssl_ca config to {}".format( log("Installing CA certificate from charm ssl_ca config to {}".format(
config_cert_file), INFO) config_cert_file), INFO)
@ -329,8 +349,7 @@ def _manage_ca_certs(ca, cert_relation_id):
log("Installing CA certificate from certificate relation", INFO) log("Installing CA certificate from certificate relation", INFO)
install_ca_cert( install_ca_cert(
ca.encode(), ca.encode(),
name='{}_juju_ca_cert'.format( name=get_cert_relation_ca_name(cert_relation_id))
remote_service_name(relid=cert_relation_id)))
def process_certificates(service_name, relation_id, unit, def process_certificates(service_name, relation_id, unit,

View File

@ -74,7 +74,6 @@ from charmhelpers.core.host import (
pwgen, pwgen,
lsb_release, lsb_release,
CompareHostReleases, CompareHostReleases,
is_container,
) )
from charmhelpers.contrib.hahelpers.cluster import ( from charmhelpers.contrib.hahelpers.cluster import (
determine_apache_port, determine_apache_port,
@ -1596,16 +1595,21 @@ def _calculate_workers():
@returns int: number of worker processes to use @returns int: number of worker processes to use
''' '''
multiplier = config('worker-multiplier') or DEFAULT_MULTIPLIER multiplier = config('worker-multiplier')
# distinguish an empty config and an explicit config as 0.0
if multiplier is None:
multiplier = DEFAULT_MULTIPLIER
count = int(_num_cpus() * multiplier) count = int(_num_cpus() * multiplier)
if multiplier > 0 and count == 0: if count <= 0:
# assign at least one worker
count = 1 count = 1
if config('worker-multiplier') is None and is_container(): if config('worker-multiplier') is None:
# NOTE(jamespage): Limit unconfigured worker-multiplier # NOTE(jamespage): Limit unconfigured worker-multiplier
# to MAX_DEFAULT_WORKERS to avoid insane # to MAX_DEFAULT_WORKERS to avoid insane
# worker configuration in LXD containers # worker configuration on large servers
# on large servers
# Reference: https://pad.lv/1665270 # Reference: https://pad.lv/1665270
count = min(count, MAX_DEFAULT_WORKERS) count = min(count, MAX_DEFAULT_WORKERS)

View File

@ -483,9 +483,26 @@ def get_swift_codename(version):
return None return None
@deprecate("moved to charmhelpers.contrib.openstack.utils.get_installed_os_version()", "2021-01", log=juju_log)
def get_os_codename_package(package, fatal=True): def get_os_codename_package(package, fatal=True):
'''Derive OpenStack release codename from an installed package.''' """Derive OpenStack release codename from an installed package.
Initially, see if the openstack-release pkg is available (by trying to
install it) and use it instead.
If it isn't then it falls back to the existing method of checking the
version of the package passed and then resolving the version from that
using lookup tables.
Note: if possible, charms should use get_installed_os_version() to
determine the version of the "openstack-release" pkg.
:param package: the package to test for version information.
:type package: str
:param fatal: If True (default), then die via error_out()
:type fatal: bool
:returns: the OpenStack release codename (e.g. ussuri)
:rtype: str
"""
codename = get_installed_os_version() codename = get_installed_os_version()
if codename: if codename:
@ -579,8 +596,22 @@ def get_os_version_package(pkg, fatal=True):
def get_installed_os_version(): def get_installed_os_version():
apt_install(filter_installed_packages(['openstack-release']), fatal=False) """Determine the OpenStack release code name from openstack-release pkg.
print("OpenStack Release: {}".format(openstack_release()))
This uses the "openstack-release" pkg (if it exists) to return the
OpenStack release codename (e.g. usurri, mitaka, ocata, etc.)
Note, it caches the result so that it is only done once per hook.
:returns: the OpenStack release codename, if available
:rtype: Optional[str]
"""
@cached
def _do_install():
apt_install(filter_installed_packages(['openstack-release']),
fatal=False, quiet=True)
_do_install()
return openstack_release().get('OPENSTACK_CODENAME') return openstack_release().get('OPENSTACK_CODENAME')
@ -1717,7 +1748,10 @@ def make_assess_status_func(*args, **kwargs):
def pausable_restart_on_change(restart_map, stopstart=False, def pausable_restart_on_change(restart_map, stopstart=False,
restart_functions=None): restart_functions=None,
can_restart_now_f=None,
post_svc_restart_f=None,
pre_restarts_wait_f=None):
"""A restart_on_change decorator that checks to see if the unit is """A restart_on_change decorator that checks to see if the unit is
paused. If it is paused then the decorated function doesn't fire. paused. If it is paused then the decorated function doesn't fire.
@ -1743,11 +1777,28 @@ def pausable_restart_on_change(restart_map, stopstart=False,
function won't be called if the decorated function is never called. Note, function won't be called if the decorated function is never called. Note,
retains backwards compatibility for passing a non-callable dictionary. retains backwards compatibility for passing a non-callable dictionary.
@param f: the function to decorate :param f: function to decorate.
@param restart_map: (optionally callable, which then returns the :type f: Callable
restart_map) the restart map {conf_file: [services]} :param restart_map: Optionally callable, which then returns the restart_map or
@param stopstart: DEFAULT false; whether to stop, start or just restart the restart map {conf_file: [services]}
@returns decorator to use a restart_on_change with pausability :type restart_map: Union[Callable[[],], Dict[str, List[str,]]
:param stopstart: whether to stop, start or restart a service
:type stopstart: booleean
:param restart_functions: nonstandard functions to use to restart services
{svc: func, ...}
:type restart_functions: Dict[str, Callable[[str], None]]
:param can_restart_now_f: A function used to check if the restart is
permitted.
:type can_restart_now_f: Callable[[str, List[str]], boolean]
:param post_svc_restart_f: A function run after a service has
restarted.
:type post_svc_restart_f: Callable[[str], None]
:param pre_restarts_wait_f: A function callled before any restarts.
:type pre_restarts_wait_f: Callable[None, None]
:returns: decorator to use a restart_on_change with pausability
:rtype: decorator
""" """
def wrap(f): def wrap(f):
# py27 compatible nonlocal variable. When py3 only, replace with # py27 compatible nonlocal variable. When py3 only, replace with
@ -1763,8 +1814,13 @@ def pausable_restart_on_change(restart_map, stopstart=False,
if callable(restart_map) else restart_map if callable(restart_map) else restart_map
# otherwise, normal restart_on_change functionality # otherwise, normal restart_on_change functionality
return restart_on_change_helper( return restart_on_change_helper(
(lambda: f(*args, **kwargs)), __restart_map_cache['cache'], (lambda: f(*args, **kwargs)),
stopstart, restart_functions) __restart_map_cache['cache'],
stopstart,
restart_functions,
can_restart_now_f,
post_svc_restart_f,
pre_restarts_wait_f)
return wrapped_f return wrapped_f
return wrap return wrap
@ -2418,3 +2474,26 @@ def get_api_application_status():
msg = 'Some units are not ready' msg = 'Some units are not ready'
juju_log(msg, 'DEBUG') juju_log(msg, 'DEBUG')
return app_state, msg return app_state, msg
def sequence_status_check_functions(*functions):
"""Sequence the functions passed so that they all get a chance to run as
the charm status check functions.
:param *functions: a list of functions that return (state, message)
:type *functions: List[Callable[[OSConfigRender], (str, str)]]
:returns: the Callable that takes configs and returns (state, message)
:rtype: Callable[[OSConfigRender], (str, str)]
"""
def _inner_sequenced_functions(configs):
state, message = 'unknown', ''
for f in functions:
new_state, new_message = f(configs)
state = workload_state_compare(state, new_state)
if message:
message = "{}, {}".format(message, new_message)
else:
message = new_message
return state, message
return _inner_sequenced_functions

View File

@ -226,6 +226,17 @@ def relation_id(relation_name=None, service_or_unit=None):
raise ValueError('Must specify neither or both of relation_name and service_or_unit') raise ValueError('Must specify neither or both of relation_name and service_or_unit')
def departing_unit():
"""The departing unit for the current relation hook.
Available since juju 2.8.
:returns: the departing unit, or None if the information isn't available.
:rtype: Optional[str]
"""
return os.environ.get('JUJU_DEPARTING_UNIT', None)
def local_unit(): def local_unit():
"""Local unit ID""" """Local unit ID"""
return os.environ['JUJU_UNIT_NAME'] return os.environ['JUJU_UNIT_NAME']

View File

@ -34,7 +34,7 @@ import itertools
import six import six
from contextlib import contextmanager from contextlib import contextmanager
from collections import OrderedDict from collections import OrderedDict, defaultdict
from .hookenv import log, INFO, DEBUG, local_unit, charm_name from .hookenv import log, INFO, DEBUG, local_unit, charm_name
from .fstab import Fstab from .fstab import Fstab
from charmhelpers.osplatform import get_platform from charmhelpers.osplatform import get_platform
@ -730,37 +730,84 @@ def restart_on_change(restart_map, stopstart=False, restart_functions=None):
def restart_on_change_helper(lambda_f, restart_map, stopstart=False, def restart_on_change_helper(lambda_f, restart_map, stopstart=False,
restart_functions=None): restart_functions=None,
can_restart_now_f=None,
post_svc_restart_f=None,
pre_restarts_wait_f=None):
"""Helper function to perform the restart_on_change function. """Helper function to perform the restart_on_change function.
This is provided for decorators to restart services if files described This is provided for decorators to restart services if files described
in the restart_map have changed after an invocation of lambda_f(). in the restart_map have changed after an invocation of lambda_f().
@param lambda_f: function to call. This functions allows for a number of helper functions to be passed.
@param restart_map: {file: [service, ...]}
@param stopstart: whether to stop, start or restart a service `restart_functions` is a map with a service as the key and the
@param restart_functions: nonstandard functions to use to restart services corresponding value being the function to call to restart the service. For
example if `restart_functions={'some-service': my_restart_func}` then
`my_restart_func` should a function which takes one argument which is the
service name to be retstarted.
`can_restart_now_f` is a function which checks that a restart is permitted. It
should returna bool which indicates if a restart is allowed and should
take a service name (str) and a list of changed files (List[str]) as
arguments.
`post_svc_restart_f` is a function which runs after a service has been
restarted. It takes the service name that was restarted as an argument.
`pre_restarts_wait_f` is a function which is called before any restarts
occur. The use case for this is an application which wants to try and
stagger restarts between units.
:param lambda_f: function to call.
:type lambda_f: Callable[[], ANY]
:param restart_map: {file: [service, ...]}
:type restart_map: Dict[str, List[str,]]
:param stopstart: whether to stop, start or restart a service
:type stopstart: booleean
:param restart_functions: nonstandard functions to use to restart services
{svc: func, ...} {svc: func, ...}
@returns result of lambda_f() :type restart_functions: Dict[str, Callable[[str], None]]
:param can_restart_now_f: A function used to check if the restart is
permitted.
:type can_restart_now_f: Callable[[str, List[str]], boolean]
:param post_svc_restart_f: A function run after a service has
restarted.
:type post_svc_restart_f: Callable[[str], None]
:param pre_restarts_wait_f: A function callled before any restarts.
:type pre_restarts_wait_f: Callable[None, None]
:returns: result of lambda_f()
:rtype: ANY
""" """
if restart_functions is None: if restart_functions is None:
restart_functions = {} restart_functions = {}
checksums = {path: path_hash(path) for path in restart_map} checksums = {path: path_hash(path) for path in restart_map}
r = lambda_f() r = lambda_f()
changed_files = defaultdict(list)
restarts = []
# create a list of lists of the services to restart # create a list of lists of the services to restart
restarts = [restart_map[path] for path, services in restart_map.items():
for path in restart_map if path_hash(path) != checksums[path]:
if path_hash(path) != checksums[path]] restarts.append(services)
for svc in services:
changed_files[svc].append(path)
# create a flat list of ordered services without duplicates from lists # create a flat list of ordered services without duplicates from lists
services_list = list(OrderedDict.fromkeys(itertools.chain(*restarts))) services_list = list(OrderedDict.fromkeys(itertools.chain(*restarts)))
if services_list: if services_list:
if pre_restarts_wait_f:
pre_restarts_wait_f()
actions = ('stop', 'start') if stopstart else ('restart',) actions = ('stop', 'start') if stopstart else ('restart',)
for service_name in services_list: for service_name in services_list:
if can_restart_now_f:
if not can_restart_now_f(service_name, changed_files[service_name]):
continue
if service_name in restart_functions: if service_name in restart_functions:
restart_functions[service_name](service_name) restart_functions[service_name](service_name)
else: else:
for action in actions: for action in actions:
service(action, service_name) service(action, service_name)
if post_svc_restart_f:
post_svc_restart_f(service_name)
return r return r
@ -1068,6 +1115,17 @@ def modulo_distribution(modulo=3, wait=30, non_zero_wait=False):
return calculated_wait_time return calculated_wait_time
def ca_cert_absolute_path(basename_without_extension):
"""Returns absolute path to CA certificate.
:param basename_without_extension: Filename without extension
:type basename_without_extension: str
:returns: Absolute full path
:rtype: str
"""
return '{}/{}.crt'.format(CA_CERT_DIR, basename_without_extension)
def install_ca_cert(ca_cert, name=None): def install_ca_cert(ca_cert, name=None):
""" """
Install the given cert as a trusted CA. Install the given cert as a trusted CA.
@ -1083,7 +1141,7 @@ def install_ca_cert(ca_cert, name=None):
ca_cert = ca_cert.encode('utf8') ca_cert = ca_cert.encode('utf8')
if not name: if not name:
name = 'juju-{}'.format(charm_name()) name = 'juju-{}'.format(charm_name())
cert_file = '{}/{}.crt'.format(CA_CERT_DIR, name) cert_file = ca_cert_absolute_path(name)
new_hash = hashlib.md5(ca_cert).hexdigest() new_hash = hashlib.md5(ca_cert).hexdigest()
if file_hash(cert_file) == new_hash: if file_hash(cert_file) == new_hash:
return return

View File

@ -13,6 +13,7 @@
# limitations under the License. # limitations under the License.
from collections import OrderedDict from collections import OrderedDict
import os
import platform import platform
import re import re
import six import six
@ -20,6 +21,7 @@ import subprocess
import sys import sys
import time import time
from charmhelpers import deprecate
from charmhelpers.core.host import get_distrib_codename, get_system_env from charmhelpers.core.host import get_distrib_codename, get_system_env
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
@ -251,13 +253,19 @@ def apt_cache(*_, **__):
# Detect this situation, log a warning and make the call to # Detect this situation, log a warning and make the call to
# ``apt_pkg.init()`` to avoid the consumer Python interpreter from # ``apt_pkg.init()`` to avoid the consumer Python interpreter from
# crashing with a segmentation fault. # crashing with a segmentation fault.
log('Support for use of upstream ``apt_pkg`` module in conjunction' @deprecate(
'with charm-helpers is deprecated since 2019-06-25', level=WARNING) 'Support for use of upstream ``apt_pkg`` module in conjunction'
'with charm-helpers is deprecated since 2019-06-25',
date=None, log=lambda x: log(x, level=WARNING))
def one_shot_log():
pass
one_shot_log()
sys.modules['apt_pkg'].init() sys.modules['apt_pkg'].init()
return ubuntu_apt_pkg.Cache() return ubuntu_apt_pkg.Cache()
def apt_install(packages, options=None, fatal=False): def apt_install(packages, options=None, fatal=False, quiet=False):
"""Install one or more packages. """Install one or more packages.
:param packages: Package(s) to install :param packages: Package(s) to install
@ -267,6 +275,8 @@ def apt_install(packages, options=None, fatal=False):
:param fatal: Whether the command's output should be checked and :param fatal: Whether the command's output should be checked and
retried. retried.
:type fatal: bool :type fatal: bool
:param quiet: if True (default), supress log message to stdout/stderr
:type quiet: bool
:raises: subprocess.CalledProcessError :raises: subprocess.CalledProcessError
""" """
if options is None: if options is None:
@ -279,9 +289,10 @@ def apt_install(packages, options=None, fatal=False):
cmd.append(packages) cmd.append(packages)
else: else:
cmd.extend(packages) cmd.extend(packages)
log("Installing {} with options: {}".format(packages, if not quiet:
options)) log("Installing {} with options: {}"
_run_apt_command(cmd, fatal) .format(packages, options))
_run_apt_command(cmd, fatal, quiet=quiet)
def apt_upgrade(options=None, fatal=False, dist=False): def apt_upgrade(options=None, fatal=False, dist=False):
@ -723,7 +734,7 @@ def _verify_is_ubuntu_rel(release, os_release):
def _run_with_retries(cmd, max_retries=CMD_RETRY_COUNT, retry_exitcodes=(1,), def _run_with_retries(cmd, max_retries=CMD_RETRY_COUNT, retry_exitcodes=(1,),
retry_message="", cmd_env=None): retry_message="", cmd_env=None, quiet=False):
"""Run a command and retry until success or max_retries is reached. """Run a command and retry until success or max_retries is reached.
:param cmd: The apt command to run. :param cmd: The apt command to run.
@ -738,11 +749,20 @@ def _run_with_retries(cmd, max_retries=CMD_RETRY_COUNT, retry_exitcodes=(1,),
:type retry_message: str :type retry_message: str
:param: cmd_env: Environment variables to add to the command run. :param: cmd_env: Environment variables to add to the command run.
:type cmd_env: Option[None, Dict[str, str]] :type cmd_env: Option[None, Dict[str, str]]
:param quiet: if True, silence the output of the command from stdout and
stderr
:type quiet: bool
""" """
env = get_apt_dpkg_env() env = get_apt_dpkg_env()
if cmd_env: if cmd_env:
env.update(cmd_env) env.update(cmd_env)
kwargs = {}
if quiet:
devnull = os.devnull if six.PY2 else subprocess.DEVNULL
kwargs['stdout'] = devnull
kwargs['stderr'] = devnull
if not retry_message: if not retry_message:
retry_message = "Failed executing '{}'".format(" ".join(cmd)) retry_message = "Failed executing '{}'".format(" ".join(cmd))
retry_message += ". Will retry in {} seconds".format(CMD_RETRY_DELAY) retry_message += ". Will retry in {} seconds".format(CMD_RETRY_DELAY)
@ -753,7 +773,7 @@ def _run_with_retries(cmd, max_retries=CMD_RETRY_COUNT, retry_exitcodes=(1,),
retry_results = (None,) + retry_exitcodes retry_results = (None,) + retry_exitcodes
while result in retry_results: while result in retry_results:
try: try:
result = subprocess.check_call(cmd, env=env) result = subprocess.check_call(cmd, env=env, **kwargs)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
retry_count = retry_count + 1 retry_count = retry_count + 1
if retry_count > max_retries: if retry_count > max_retries:
@ -763,7 +783,7 @@ def _run_with_retries(cmd, max_retries=CMD_RETRY_COUNT, retry_exitcodes=(1,),
time.sleep(CMD_RETRY_DELAY) time.sleep(CMD_RETRY_DELAY)
def _run_apt_command(cmd, fatal=False): def _run_apt_command(cmd, fatal=False, quiet=False):
"""Run an apt command with optional retries. """Run an apt command with optional retries.
:param cmd: The apt command to run. :param cmd: The apt command to run.
@ -771,13 +791,22 @@ def _run_apt_command(cmd, fatal=False):
:param fatal: Whether the command's output should be checked and :param fatal: Whether the command's output should be checked and
retried. retried.
:type fatal: bool :type fatal: bool
:param quiet: if True, silence the output of the command from stdout and
stderr
:type quiet: bool
""" """
if fatal: if fatal:
_run_with_retries( _run_with_retries(
cmd, retry_exitcodes=(1, APT_NO_LOCK,), cmd, retry_exitcodes=(1, APT_NO_LOCK,),
retry_message="Couldn't acquire DPKG lock") retry_message="Couldn't acquire DPKG lock",
quiet=quiet)
else: else:
subprocess.call(cmd, env=get_apt_dpkg_env()) kwargs = {}
if quiet:
devnull = os.devnull if six.PY2 else subprocess.DEVNULL
kwargs['stdout'] = devnull
kwargs['stderr'] = devnull
subprocess.call(cmd, env=get_apt_dpkg_env(), **kwargs)
def get_upstream_version(package): def get_upstream_version(package):