Esempio n. 1
0
 def test_with_context(self):
     with ConsulServiceController().start_service() as service:
         consul_client = service.create_consul_client()
         lock_manager = ConsulLockManager(consul_client=consul_client)
         with lock_manager.acquire(KEY_1) as lock_information:
             self.assertEqual(lock_information, lock_manager.find(KEY_1))
         self.assertIsNone(lock_manager.find(KEY_1))
Esempio n. 2
0
 def test_find_regex_when_no_locks(self):
     with ConsulServiceController().start_service() as service:
         lock_manager = ConsulLockManager(
             consul_client=service.create_consul_client())
         found_locks = lock_manager.find_regex(
             f"{KEY_1}{KEY_DIRECTORY_SEPARATOR}[0-9]+")
         self.assertEqual(0, len(found_locks))
Esempio n. 3
0
 def test_with_context(self):
     with ConsulServiceController().start_service() as service:
         consul_client = service.create_consul_client()
         lock_manager = ConsulLockManager(consul_client=consul_client)
         with lock_manager.acquire(KEY_1) as lock_information:
             self.assertEqual(lock_information, lock_manager.find(KEY_1))
         self.assertIsNone(lock_manager.find(KEY_1))
Esempio n. 4
0
 def find_regex(service: ConsulDockerisedService):
     consul_client = service.create_consul_client()
     lock_manager = ConsulLockManager(consul_client=consul_client)
     consul_client.kv.put(KEYS_1[1], "unrelated")
     found_locks = lock_manager.find_regex(KEYS_1_REGEX)
     self.assertEqual(2, len(found_locks))
     self.assertIsInstance(found_locks[KEYS_1[0]], ConsulLockInformation)
     self.assertIsNone(found_locks[KEYS_1[1]])
Esempio n. 5
0
 def find_regex(service: ConsulDockerisedService):
     consul_client = service.create_consul_client()
     lock_manager = ConsulLockManager(consul_client=consul_client)
     consul_client.kv.put(KEYS_1[1], "unrelated")
     found_locks = lock_manager.find_regex(KEYS_1_REGEX)
     self.assertEqual(2, len(found_locks))
     self.assertIsInstance(found_locks[KEYS_1[0]],
                           ConsulLockInformation)
     self.assertIsNone(found_locks[KEYS_1[1]])
Esempio n. 6
0
 def test_unlock_all(self):
     test_keys = [f"{KEY_1}_{i}" for i in range(5)]
     with ConsulServiceController().start_service() as service:
         lock_manager = ConsulLockManager(consul_client=service.create_consul_client())
         for key in test_keys:
             lock = lock_manager.acquire(key)
             assert isinstance(lock, ConsulLockInformation)
         unlock_results = lock_manager.release_all(test_keys)
     for unlock_result in unlock_results:
         self.assertTrue(unlock_result)
Esempio n. 7
0
 def test_unlock_all(self):
     test_keys = [f"{KEY_1}_{i}" for i in range(5)]
     with ConsulServiceController().start_service() as service:
         lock_manager = ConsulLockManager(
             consul_client=service.create_consul_client())
         for key in test_keys:
             lock = lock_manager.acquire(key)
             assert isinstance(lock, ConsulLockInformation)
         unlock_results = lock_manager.release_all(test_keys)
     for unlock_result in unlock_results:
         self.assertTrue(unlock_result)
Esempio n. 8
0
def _release_lock(lock_manager: ConsulLockManager, configuration: CliUnlockConfiguration):
    """
    Unlocks a lock.
    :param lock_manager: the lock manager
    :param configuration: the configuration required to unlock the lock
    """
    if configuration.regex_key_enabled:
        release_information = sorted(list(lock_manager.release_regex(key_regex=configuration.key)))
    else:
        release_information = lock_manager.release(key=configuration.key)
    print(json.dumps(release_information))

    exit(SUCCESS_EXIT_CODE)
Esempio n. 9
0
def _acquire_lock(lock_manager: ConsulLockManager, configuration: CliLockConfiguration) \
        -> Optional[ConnectedConsulLockInformation]:
    """
    TODO
    :param lock_manager:
    :param configuration:
    :return:
    """
    event_listeners: LockEventListener = {}
    if configuration.on_before_locked_executables is not None:
        event_listeners["on_before_lock"] = _generate_event_listener_caller(
            configuration.on_before_locked_executables)
    if configuration.on_lock_already_locked_executables is not None:
        event_listeners["on_lock_already_locked"] = _generate_event_listener_caller(
            configuration.on_lock_already_locked_executables)

    try:
        return lock_manager.acquire(
            key=configuration.key, blocking=not configuration.non_blocking,
            timeout=configuration.timeout, metadata=configuration.metadata, **event_listeners,
            lock_poll_interval_generator=lambda i: configuration.lock_poll_interval)
    except LockAcquireTimeoutError as e:
        logger.debug(e)
        logger.error(f"Timed out whilst waiting to acquire lock: {configuration.key}")
        print(json.dumps(None))
        exit(LOCK_ACQUIRE_TIMEOUT_EXIT_CODE)
def main(lock_key: str):
    lock_manager = ConsulLockManager()
    lock = lock_manager.find(lock_key)
    logger.debug(f"Lock with key \"{lock_key}\": {lock}")

    if lock is not None and lock.metadata is not None and JOB_ID_LOCK_METADATA_KEY in lock.metadata:
        job_id = lock.metadata[JOB_ID_LOCK_METADATA_KEY]
        logger.info(f"Lock currently held by CI job with ID: {job_id}")

        job_running = is_ci_job_running(job_id)
        logger.info(f"CI job with ID {job_id} {'is' if job_running else 'is not'} running")
        if not job_running:
            logger.info(f"Releasing lock for {lock.key} held by non-running job {job_id}")
            released = lock_manager.release(lock.key)
            logger.info("Released lock!" if released is not None else "Did not manage to release lock (someone else "
                                                                      "probably else released it before me)")
Esempio n. 11
0
def main(cli_arguments: List[str]):
    """
    Entrypoint.
    :param cli_arguments: arguments passed in via the CLI
    :raises SystemExit: always raised
    """
    cli_configuration: CliConfiguration
    try:
        cli_configuration = parse_cli_configuration(cli_arguments)
    except InvalidCliArgumentError as e:
        logger.error(e)
        exit(INVALID_CLI_ARGUMENT_EXIT_CODE)
    except SystemExit as e:
        exit(e.code)

    if cli_configuration.log_verbosity:
        logging.getLogger(PACKAGE_NAME).setLevel(cli_configuration.log_verbosity)

    consul_configuration: ConsulConfiguration
    try:
        consul_configuration = get_consul_configuration_from_environment()
    except KeyError as e:
        logger.error(f"Cannot connect to Consul - the environment variable {e.args[0]} must be set")
        exit(MISSING_REQUIRED_ENVIRONMENT_VARIABLE_EXIT_CODE)
    except InvalidEnvironmentVariableError as e:
        logger.error(e)
        exit(INVALID_ENVIRONMENT_VARIABLE_EXIT_CODE)

    lock_manager: ConsulLockManager
    try:
        lock_manager = ConsulLockManager(
            consul_configuration=consul_configuration, session_ttl_in_seconds=cli_configuration.session_ttl)
    except InvalidSessionTtlValueError as e:
        logger.error(e)
        exit(INVALID_SESSION_TTL_EXIT_CODE)

    try:
        {
            CliLockConfiguration: _acquire_lock_and_exit,
            CliLockAndExecuteConfiguration: _acquire_lock_and_execute,
            CliUnlockConfiguration: _release_lock
        }[type(cli_configuration)](lock_manager, cli_configuration)
    except PermissionDeniedConsulError as e:
        error_message = f"Invalid credentials - are you sure you have set {CONSUL_TOKEN_ENVIRONMENT_VARIABLE} " \
                        f"correctly?"
        logger.debug(e)
        logger.error(error_message)
        exit(PERMISSION_DENIED_EXIT_CODE)
    except DoubleSlashKeyError as e:
        logger.debug(e)
        logger.error(f"Double slashes \"//\" in keys get converted into single slashes \"/\" - please use a "
                     f"single slash if this is intended: {cli_configuration.key}")
        exit(INVALID_KEY_EXIT_CODE)
    except NonNormalisedKeyError as e:
        logger.debug(e)
        logger.error(f"Key paths must be normalised - use \"{normpath(e.key)}\" if this key was intended: "
                     f"{cli_configuration.key}")
        exit(INVALID_KEY_EXIT_CODE)
Esempio n. 12
0
    def __init__(self,
                 data_key: str,
                 lock_key: str,
                 url: str = None,
                 token: str = None,
                 consul_client=None,
                 configuration_checksum_mappings: Mapping[str, str] = None):
        Consul = ConsulChecksumStorage._load_consul_class()
        ConsulLockManager = ConsulChecksumStorage._load_consul_lock_manager()

        if url is not None and consul_client is not None:
            raise ValueError("Cannot use both `url` and `consul_client`")

        self.data_key = data_key
        self.lock_key = lock_key

        consul_client_kwargs: Dict = {}
        if url is not None:
            parsed_url = urlparse(url)
            consul_client_kwargs["host"] = parsed_url.hostname
            consul_client_kwargs["port"] = parsed_url.port
            consul_client_kwargs["scheme"] = parsed_url.scheme if len(
                parsed_url.scheme) > 0 else "http"
        self._consul_client = consul_client if consul_client is not None else Consul(
            **consul_client_kwargs)

        if token is None:
            token = os.environ.get(
                ConsulChecksumStorage.CONSUL_HTTP_TOKEN_ENVIRONMENT_VARIABLE,
                None)
        if token is not None:
            # Work around for https://github.com/cablehead/python-consul/issues/170
            self._consul_client.token = token
            self._consul_client.http.session.headers.update(
                {"X-Consul-Token": token})

        self._lock_manager = ConsulLockManager(
            consul_client=self._consul_client,
            session_ttl_in_seconds=ConsulChecksumStorage.
            CONSUL_SESSION_LOCK_DEFAULT_TIMEOUT)

        super().__init__(configuration_checksum_mappings)
Esempio n. 13
0
def _acquire_lock_and_execute(lock_manager: ConsulLockManager, configuration: CliLockAndExecuteConfiguration):
    """
    Executes whilst holding a lock, exiting after the execute returns with the executables return code.
    :param lock_manager: the lock manager
    :param configuration: the configuration
    """
    lock = _acquire_lock(lock_manager, configuration)
    if lock is None:
        exit(UNABLE_TO_ACQUIRE_LOCK_EXIT_CODE)
    return_code, _, _ = lock_manager.execute_with_lock(configuration.executable, lock)
    exit(return_code)
Esempio n. 14
0
 def action_executor(key: str,
                     service: ConsulDockerisedService) -> CaptureResult:
     nonlocal init_args_generator, init_kwargs_generator, action_args, action_kwargs, action_property
     lock_manager = ConsulLockManager(
         *init_args_generator(key, service),
         **init_kwargs_generator(key, service))
     action_args = action_args if action_args is not None else []
     action_kwargs = action_kwargs if action_kwargs is not None else {}
     return TestConsulLockManager._CAPTURE_WRAP_BUILDER.build(
         getattr(lock_manager, action_property))(key, *action_args,
                                                 **action_kwargs)
Esempio n. 15
0
def main(lock_key: str):
    lock_manager = ConsulLockManager()
    lock = lock_manager.find(lock_key)
    logger.debug(f"Lock with key \"{lock_key}\": {lock}")

    if lock is not None and lock.metadata is not None and JOB_ID_LOCK_METADATA_KEY in lock.metadata:
        job_id = lock.metadata[JOB_ID_LOCK_METADATA_KEY]
        logger.info(f"Lock currently held by CI job with ID: {job_id}")

        job_running = is_ci_job_running(job_id)
        logger.info(
            f"CI job with ID {job_id} {'is' if job_running else 'is not'} running"
        )
        if not job_running:
            logger.info(
                f"Releasing lock for {lock.key} held by non-running job {job_id}"
            )
            released = lock_manager.release(lock.key)
            logger.info("Released lock!" if released is not None else
                        "Did not manage to release lock (someone else "
                        "probably else released it before me)")
Esempio n. 16
0
 def test_find_when_no_locks(self):
     with ConsulServiceController().start_service() as service:
         lock_manager = ConsulLockManager(
             consul_client=service.create_consul_client())
         self.assertIsNone(lock_manager.find(KEY_1))
Esempio n. 17
0
 def test_can_get_configuration_from_environment(self):
     with ConsulServiceController().start_service() as service:
         set_consul_env(service)
         lock_manager = ConsulLockManager()
         self.assertIsNone(lock_manager.release(KEY_1))
Esempio n. 18
0
 def find(service: ConsulDockerisedService):
     lock_manager = ConsulLockManager(
         consul_client=service.create_consul_client())
     found_lock = lock_manager.find(KEYS_1[0])
     self.assertEqual(KEYS_1[0], found_lock.key)
Esempio n. 19
0
 def find_regex(service: ConsulDockerisedService):
     lock_manager = ConsulLockManager(consul_client=service.create_consul_client())
     found_locks = lock_manager.find_regex(KEYS_1_REGEX)
     self.assertCountEqual(KEYS_1, [lock.key for lock in found_locks.values()])
Esempio n. 20
0
 def test_lock_with_non_normalised_path(self):
     lock_manager = ConsulLockManager(
         consul_configuration=_DUMMY_CONSUL_CONFIGURATION)
     self.assertRaises(NonNormalisedKeyError, lock_manager.acquire,
                       NON_NORMALISED_KEY)
Esempio n. 21
0
 def test_cannot_use_after_teardown(self):
     lock_manager = ConsulLockManager(consul_configuration=_DUMMY_CONSUL_CONFIGURATION)
     lock_manager.teardown()
     self.assertRaises(UnusableStateError, lock_manager.acquire)
Esempio n. 22
0
 def find_regex(service: ConsulDockerisedService):
     lock_manager = ConsulLockManager(
         consul_client=service.create_consul_client())
     found_locks = lock_manager.find_regex(KEYS_1_REGEX)
     self.assertCountEqual(KEYS_1,
                           [lock.key for lock in found_locks.values()])
Esempio n. 23
0
 def release(service: ConsulDockerisedService):
     lock_manager = ConsulLockManager(consul_client=service.create_consul_client())
     released_locks = lock_manager.release_regex(KEYS_1_REGEX)
     self.assertCountEqual(KEYS_1, released_locks)
Esempio n. 24
0
 def test_lock_with_double_slash_path(self):
     lock_manager = ConsulLockManager(
         consul_configuration=_DUMMY_CONSUL_CONFIGURATION)
     self.assertRaises(DoubleSlashKeyError, lock_manager.acquire,
                       DOUBLE_SLASH_KEY)
Esempio n. 25
0
class ConsulChecksumStorage(ChecksumStorage):
    """
    Consul storage for configuration -> checksum mappings.
    """
    CONSUL_HTTP_TOKEN_ENVIRONMENT_VARIABLE = "CONSUL_HTTP_TOKEN"
    CONSUL_SESSION_LOCK_DEFAULT_TIMEOUT = 120
    TEXT_ENCODING = "utf-8"
    _IMPORT_MISSING_ERROR_MESSAGE = "To use Consul storage, please install the requirements in " \
                                    "`consul_requirements.txt`"

    @staticmethod
    def _load_consul_class() -> Type:
        """
        Loads the Consul class at run time (optional requirement).
        :return: the Consul class
        :raises MissingOptionalDependencyError: if a required dependency is not installed
        """
        try:
            from consul import Consul
        except ImportError as e:
            raise MissingOptionalDependencyError(
                ConsulChecksumStorage._IMPORT_MISSING_ERROR_MESSAGE) from e
        return Consul

    @staticmethod
    def _load_consul_lock_manager() -> Type:
        """
        Loads the ConsulLockManager class at run time (optional requirement).
        :return: the Consul class
        :raises MissingOptionalDependencyError: if a required dependency is not installed
        """
        try:
            from consullock.managers import ConsulLockManager
        except ImportError as e:
            raise MissingOptionalDependencyError(
                ConsulChecksumStorage._IMPORT_MISSING_ERROR_MESSAGE) from e
        return ConsulLockManager

    @property
    def url(self) -> str:
        return self._consul_client.http.base_uri

    @property
    def token(self) -> str:
        return self._consul_client.token

    def __init__(self,
                 data_key: str,
                 lock_key: str,
                 url: str = None,
                 token: str = None,
                 consul_client=None,
                 configuration_checksum_mappings: Mapping[str, str] = None):
        Consul = ConsulChecksumStorage._load_consul_class()
        ConsulLockManager = ConsulChecksumStorage._load_consul_lock_manager()

        if url is not None and consul_client is not None:
            raise ValueError("Cannot use both `url` and `consul_client`")

        self.data_key = data_key
        self.lock_key = lock_key

        consul_client_kwargs: Dict = {}
        if url is not None:
            parsed_url = urlparse(url)
            consul_client_kwargs["host"] = parsed_url.hostname
            consul_client_kwargs["port"] = parsed_url.port
            consul_client_kwargs["scheme"] = parsed_url.scheme if len(
                parsed_url.scheme) > 0 else "http"
        self._consul_client = consul_client if consul_client is not None else Consul(
            **consul_client_kwargs)

        if token is None:
            token = os.environ.get(
                ConsulChecksumStorage.CONSUL_HTTP_TOKEN_ENVIRONMENT_VARIABLE,
                None)
        if token is not None:
            # Work around for https://github.com/cablehead/python-consul/issues/170
            self._consul_client.token = token
            self._consul_client.http.session.headers.update(
                {"X-Consul-Token": token})

        self._lock_manager = ConsulLockManager(
            consul_client=self._consul_client,
            session_ttl_in_seconds=ConsulChecksumStorage.
            CONSUL_SESSION_LOCK_DEFAULT_TIMEOUT)

        super().__init__(configuration_checksum_mappings)

    def get_checksum(self, configuration_id: str) -> Optional[str]:
        return self.get_all_checksums().get(configuration_id)

    def get_all_checksums(self) -> Dict[str, str]:
        value = self._consul_client.kv.get(self.data_key)[1]
        if value is None:
            return {}
        value = value["Value"].decode(ConsulChecksumStorage.TEXT_ENCODING)
        return json.loads(value)

    def set_checksum(self, configuration_id: str, checksum: str):
        with self._lock_manager.acquire(self.lock_key):
            value = self.get_all_checksums()
            value[configuration_id] = checksum
            self._consul_client.kv.put(self.data_key,
                                       json.dumps(value, sort_keys=True))

    def set_all_checksums(self, configuration_checksum_mappings: Mapping[str,
                                                                         str]):
        with self._lock_manager.acquire(self.lock_key):
            value = self.get_all_checksums()
            value.update(configuration_checksum_mappings)
            self._consul_client.kv.put(self.data_key,
                                       json.dumps(value, sort_keys=True))
Esempio n. 26
0
 def first_locker(key: str,
                  service: ConsulDockerisedService) -> CaptureResult:
     lock_manager = ConsulLockManager(
         consul_client=service.create_consul_client())
     lock_information = lock_manager.acquire(KEY_1)
     return CaptureResult(return_value=lock_information)
Esempio n. 27
0
 def test_find_when_no_locks(self):
     with ConsulServiceController().start_service() as service:
         lock_manager = ConsulLockManager(consul_client=service.create_consul_client())
         self.assertIsNone(lock_manager.find(KEY_1))
Esempio n. 28
0
 def test_cannot_use_after_teardown(self):
     lock_manager = ConsulLockManager(
         consul_configuration=_DUMMY_CONSUL_CONFIGURATION)
     lock_manager.teardown()
     self.assertRaises(UnusableStateError, lock_manager.acquire)
Esempio n. 29
0
 def find(service: ConsulDockerisedService):
     lock_manager = ConsulLockManager(consul_client=service.create_consul_client())
     found_lock = lock_manager.find(KEYS_1[0])
     self.assertEqual(KEYS_1[0], found_lock.key)
Esempio n. 30
0
 def release(service: ConsulDockerisedService):
     lock_manager = ConsulLockManager(
         consul_client=service.create_consul_client())
     released_locks = lock_manager.release_regex(KEYS_1_REGEX)
     self.assertCountEqual(KEYS_1, released_locks)
Esempio n. 31
0
 def test_find_regex_when_no_locks(self):
     with ConsulServiceController().start_service() as service:
         lock_manager = ConsulLockManager(consul_client=service.create_consul_client())
         found_locks = lock_manager.find_regex(f"{KEY_1}{KEY_DIRECTORY_SEPARATOR}[0-9]+")
         self.assertEqual(0, len(found_locks))
Esempio n. 32
0
 def first_locker(key: str, service: ConsulDockerisedService) -> CaptureResult:
     lock_manager = ConsulLockManager(consul_client=service.create_consul_client())
     lock_information = lock_manager.acquire(KEY_1)
     return CaptureResult(return_value=lock_information)
Esempio n. 33
0
 def test_can_get_configuration_from_environment(self):
     with ConsulServiceController().start_service() as service:
         set_consul_env(service)
         lock_manager = ConsulLockManager()
         self.assertIsNone(lock_manager.release(KEY_1))