~joeborg/charm-helpers/charm-helpers

« back to all changes in this revision

Viewing changes to charmhelpers/fetch/ubuntu.py

  • Committer: David Britton
  • Date: 2017-03-03 20:46:09 UTC
  • mfrom: (703.1.8 retry-add-apt-repostory)
  • Revision ID: david.britton@canonical.com-20170303204609-p1cjwofk3h41yhzs
Merge in retry-add-apt-repository [a=chad.smith] [r=ericsnow,dpb] [f=1370933]

Show diffs side-by-side

added added

removed removed

Lines of Context:
116
116
}
117
117
 
118
118
APT_NO_LOCK = 100  # The return code for "couldn't acquire lock" in APT.
119
 
APT_NO_LOCK_RETRY_DELAY = 10  # Wait 10 seconds between apt lock checks.
120
 
APT_NO_LOCK_RETRY_COUNT = 30  # Retry to acquire the lock X times.
 
119
CMD_RETRY_DELAY = 10  # Wait 10 seconds between command retries.
 
120
CMD_RETRY_COUNT = 30  # Retry a failing fatal command X times.
121
121
 
122
122
 
123
123
def filter_installed_packages(packages):
249
249
        source.startswith('http') or
250
250
        source.startswith('deb ') or
251
251
            source.startswith('cloud-archive:')):
252
 
        subprocess.check_call(['add-apt-repository', '--yes', source])
 
252
        cmd = ['add-apt-repository', '--yes', source]
 
253
        _run_with_retries(cmd)
253
254
    elif source.startswith('cloud:'):
254
255
        install(filter_installed_packages(['ubuntu-cloud-keyring']),
255
256
                fatal=True)
286
287
                                   key])
287
288
 
288
289
 
 
290
def _run_with_retries(cmd, max_retries=CMD_RETRY_COUNT, retry_exitcodes=(1,),
 
291
                      retry_message="", cmd_env=None):
 
292
    """Run a command and retry until success or max_retries is reached.
 
293
 
 
294
    :param: cmd: str: The apt command to run.
 
295
    :param: max_retries: int: The number of retries to attempt on a fatal
 
296
        command. Defaults to CMD_RETRY_COUNT.
 
297
    :param: retry_exitcodes: tuple: Optional additional exit codes to retry.
 
298
        Defaults to retry on exit code 1.
 
299
    :param: retry_message: str: Optional log prefix emitted during retries.
 
300
    :param: cmd_env: dict: Environment variables to add to the command run.
 
301
    """
 
302
 
 
303
    env = os.environ.copy()
 
304
    if cmd_env:
 
305
        env.update(cmd_env)
 
306
 
 
307
    if not retry_message:
 
308
        retry_message = "Failed executing '{}'".format(" ".join(cmd))
 
309
    retry_message += ". Will retry in {} seconds".format(CMD_RETRY_DELAY)
 
310
 
 
311
    retry_count = 0
 
312
    result = None
 
313
 
 
314
    retry_results = (None,) + retry_exitcodes
 
315
    while result in retry_results:
 
316
        try:
 
317
            result = subprocess.check_call(cmd, env=env)
 
318
        except subprocess.CalledProcessError as e:
 
319
            retry_count = retry_count + 1
 
320
            if retry_count > max_retries:
 
321
                raise
 
322
            result = e.returncode
 
323
            log(retry_message)
 
324
            time.sleep(CMD_RETRY_DELAY)
 
325
 
 
326
 
289
327
def _run_apt_command(cmd, fatal=False):
290
 
    """Run an APT command.
291
 
 
292
 
    Checks the output and retries if the fatal flag is set
293
 
    to True.
294
 
 
295
 
    :param: cmd: str: The apt command to run.
 
328
    """Run an apt command with optional retries.
 
329
 
296
330
    :param: fatal: bool: Whether the command's output should be checked and
297
331
        retried.
298
332
    """
299
 
    env = os.environ.copy()
300
 
 
301
 
    if 'DEBIAN_FRONTEND' not in env:
302
 
        env['DEBIAN_FRONTEND'] = 'noninteractive'
 
333
    # Provide DEBIAN_FRONTEND=noninteractive if not present in the environment.
 
334
    cmd_env = {
 
335
        'DEBIAN_FRONTEND': os.environ.get('DEBIAN_FRONTEND', 'noninteractive')}
303
336
 
304
337
    if fatal:
305
 
        retry_count = 0
306
 
        result = None
307
 
 
308
 
        # If the command is considered "fatal", we need to retry if the apt
309
 
        # lock was not acquired.
310
 
 
311
 
        while result is None or result == APT_NO_LOCK:
312
 
            try:
313
 
                result = subprocess.check_call(cmd, env=env)
314
 
            except subprocess.CalledProcessError as e:
315
 
                retry_count = retry_count + 1
316
 
                if retry_count > APT_NO_LOCK_RETRY_COUNT:
317
 
                    raise
318
 
                result = e.returncode
319
 
                log("Couldn't acquire DPKG lock. Will retry in {} seconds."
320
 
                    "".format(APT_NO_LOCK_RETRY_DELAY))
321
 
                time.sleep(APT_NO_LOCK_RETRY_DELAY)
322
 
 
 
338
        _run_with_retries(
 
339
            cmd, cmd_env=cmd_env, retry_exitcodes=(1, APT_NO_LOCK,),
 
340
            retry_message="Couldn't acquire DPKG lock")
323
341
    else:
 
342
        env = os.environ.copy()
 
343
        env.update(cmd_env)
324
344
        subprocess.call(cmd, env=env)
325
345
 
326
346