Add a global lock file to Certbot (#4369)

* add fasteners as a dependency

* add LOCK_FILE constant

* Add lock file to Certbot

* Move code to _run_subcommand

* move lock file path into CLI_CONSTANTS

* add --lock-path flag

* move locking code to separate function

* Add TestAcquireFileLock

* assert we log

* test lock contention

* add fasteners to certbot-auto

* Use a different lock file for each test in MainTest
This commit is contained in:
Brad Warren
2017-03-20 15:48:39 -07:00
committed by Peter Eckersley
parent 8011fb2879
commit 32122cfa21
8 changed files with 123 additions and 2 deletions
+52 -1
View File
@@ -8,6 +8,7 @@ import sys
import time
import traceback
import fasteners
import zope.component
from acme import jose
@@ -866,6 +867,56 @@ def _post_logging_setup(config, plugins, cli_args):
logger.debug("Discovered plugins: %r", plugins)
def acquire_file_lock(lock_path):
"""Obtain a lock on the file at the specified path.
:param str lock_path: path to the file to be locked
:returns: lock file object representing the acquired lock
:rtype: fasteners.InterProcessLock
:raises .Error: if the lock is held by another process
"""
lock = fasteners.InterProcessLock(lock_path)
logger.debug("Attempting to acquire lock file %s", lock_path)
try:
lock.acquire(blocking=False)
except IOError as err:
logger.debug(err)
logger.warning(
"Unable to access lock file %s. You should set --lock-file "
"to a writeable path to ensure multiple instances of "
"Certbot don't attempt modify your configuration "
"simultaneously.", lock_path)
else:
if not lock.acquired:
raise errors.Error(
"Another instance of Certbot is already running.")
return lock
def _run_subcommand(config, plugins):
"""Executes the Certbot subcommand specified in the configuration.
:param .IConfig config: parsed configuration object
:param .PluginsRegistry plugins: available plugins
:returns: return value from the specified subcommand
:rtype: str or int
"""
lock = acquire_file_lock(config.lock_path)
try:
return config.func(config, plugins)
finally:
if lock.acquired:
lock.release()
def main(cli_args=sys.argv[1:]):
"""Command line argument parsing and main script execution."""
sys.excepthook = functools.partial(_handle_exception, config=None)
@@ -893,7 +944,7 @@ def main(cli_args=sys.argv[1:]):
zope.component.provideUtility(report)
atexit.register(report.atexit_print_messages)
return config.func(config, plugins)
return _run_subcommand(config, plugins)
if __name__ == "__main__":