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.
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']),
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.
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.
303
env = os.environ.copy()
307
if not retry_message:
308
retry_message = "Failed executing '{}'".format(" ".join(cmd))
309
retry_message += ". Will retry in {} seconds".format(CMD_RETRY_DELAY)
314
retry_results = (None,) + retry_exitcodes
315
while result in retry_results:
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:
322
result = e.returncode
324
time.sleep(CMD_RETRY_DELAY)
289
327
def _run_apt_command(cmd, fatal=False):
290
"""Run an APT command.
292
Checks the output and retries if the fatal flag is set
295
:param: cmd: str: The apt command to run.
328
"""Run an apt command with optional retries.
296
330
:param: fatal: bool: Whether the command's output should be checked and
299
env = os.environ.copy()
301
if 'DEBIAN_FRONTEND' not in env:
302
env['DEBIAN_FRONTEND'] = 'noninteractive'
333
# Provide DEBIAN_FRONTEND=noninteractive if not present in the environment.
335
'DEBIAN_FRONTEND': os.environ.get('DEBIAN_FRONTEND', 'noninteractive')}
308
# If the command is considered "fatal", we need to retry if the apt
309
# lock was not acquired.
311
while result is None or result == APT_NO_LOCK:
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:
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)
339
cmd, cmd_env=cmd_env, retry_exitcodes=(1, APT_NO_LOCK,),
340
retry_message="Couldn't acquire DPKG lock")
342
env = os.environ.copy()
324
344
subprocess.call(cmd, env=env)