|
| 1 | +import six |
1 | 2 | import json |
| 3 | +import random |
| 4 | +import time |
| 5 | + |
2 | 6 | from st2common import log as logging |
3 | 7 | from st2common.runners.base_action import Action |
4 | 8 |
|
5 | 9 | # http://python-consul.readthedocs.org/en/latest/# |
6 | 10 | import consul |
7 | 11 |
|
| 12 | +LOG = logging.getLogger(__name__) |
| 13 | + |
8 | 14 |
|
9 | 15 | class ConsulBaseAction(Action): |
10 | 16 |
|
11 | 17 | def __init__(self, config): |
12 | 18 | super(ConsulBaseAction, self).__init__(config) |
13 | 19 | self.consul = self._get_client() |
14 | | - self.logger = logging.getLogger(__name__) |
15 | 20 |
|
16 | 21 | def _get_client(self): |
17 | 22 | dc = self.config.get('dc') |
@@ -41,3 +46,116 @@ def from_json(self, value): |
41 | 46 | except ValueError: |
42 | 47 | pass |
43 | 48 | return value |
| 49 | + |
| 50 | + |
| 51 | +class LockManager(object): |
| 52 | + semaphore = { |
| 53 | + "Limit": 0, |
| 54 | + "Holders": {} |
| 55 | + } |
| 56 | + |
| 57 | + def __init__(self, client, key_prefix, name, node, token, dc): |
| 58 | + """ |
| 59 | + Initialise object with common variables used to create or destroy a lock. |
| 60 | + """ |
| 61 | + self.client = client |
| 62 | + self.key_prefix = key_prefix |
| 63 | + self.name = name |
| 64 | + self.node = node |
| 65 | + self.token = token |
| 66 | + self.dc = dc |
| 67 | + |
| 68 | + def lock(self, max_locks, acquire_timeout, checks, behavior, ttl): |
| 69 | + """ |
| 70 | + Method called by the Lock action. |
| 71 | + """ |
| 72 | + self.max_locks = max_locks |
| 73 | + self.wait_timeout = acquire_timeout |
| 74 | + self.checks = checks |
| 75 | + self.behavior = behavior |
| 76 | + self.ttl = ttl |
| 77 | + |
| 78 | + if self.max_locks > 1: |
| 79 | + result = self.create_semaphore() |
| 80 | + else: |
| 81 | + result = self.create_lock("/".join([self.key_prefix, self.name, '.lock'])) |
| 82 | + return result |
| 83 | + |
| 84 | + def unlock(self, session_id): |
| 85 | + """ |
| 86 | + Method called by the Unlock action. |
| 87 | + """ |
| 88 | + key = "/".join([self.key_prefix, self.name, '.lock']) |
| 89 | + return self.release_lock(key, session_id) |
| 90 | + |
| 91 | + def create_semaphore(self): |
| 92 | + raise NotImplementedError |
| 93 | + |
| 94 | + def release_lock(self, key, session_id): |
| 95 | + """ |
| 96 | + Release the lock on the key and destroy the session. |
| 97 | + """ |
| 98 | + result = (False, "Failed to release lock.") |
| 99 | + if self.client.kv.put( |
| 100 | + key=key, |
| 101 | + value=None, |
| 102 | + release=session_id, |
| 103 | + token=self.token, |
| 104 | + dc=self.dc |
| 105 | + ) is True: |
| 106 | + if self.destroy_session(session_id) is True: |
| 107 | + result = (True, "Lock released and session destroyed.") |
| 108 | + else: |
| 109 | + result = (True, "Lock released but session could not be destroyed.") |
| 110 | + return result |
| 111 | + |
| 112 | + def destroy_session(self, session_id): |
| 113 | + self.client.session.destroy(session_id, self.dc) |
| 114 | + |
| 115 | + def create_lock(self, key_name): |
| 116 | + result = (False, "") |
| 117 | + session_id = self.create_session() |
| 118 | + if isinstance(session_id, six.string_types) and len(session_id) > 0: |
| 119 | + if self.acquire_lock(session_id, key_name, value=session_id): |
| 120 | + result = (True, session_id) |
| 121 | + else: |
| 122 | + result = (False, "Failed to acquire lock on key.") |
| 123 | + self.destroy_session(session_id) |
| 124 | + else: |
| 125 | + result = (False, "Failed to create a session") |
| 126 | + return result |
| 127 | + |
| 128 | + def create_session(self): |
| 129 | + """ |
| 130 | + https://www.consul.io/docs/internals/sessions.html |
| 131 | + """ |
| 132 | + return self.client.session.create( |
| 133 | + name=self.name, |
| 134 | + node=self.node, |
| 135 | + checks=self.checks, |
| 136 | + lock_delay=5, |
| 137 | + behavior=self.behavior, |
| 138 | + ttl=self.ttl, |
| 139 | + dc=self.dc |
| 140 | + ) |
| 141 | + |
| 142 | + def acquire_lock(self, session_id, key, cas=None, value=""): |
| 143 | + """ |
| 144 | + wait_timeout is used to determine when to abandon the lock acquisition. |
| 145 | + """ |
| 146 | + result = False |
| 147 | + timeout = time.time() + self.wait_timeout |
| 148 | + LOG.info("Acquire lock timeout: {}".format(timeout)) |
| 149 | + while time.time() < timeout: |
| 150 | + result = self.client.kv.put( |
| 151 | + key=key, |
| 152 | + value=value, |
| 153 | + cas=cas, |
| 154 | + acquire=session_id, |
| 155 | + token=self.token, |
| 156 | + dc=self.dc |
| 157 | + ) |
| 158 | + if result is True: |
| 159 | + break |
| 160 | + time.sleep(random.random()) |
| 161 | + return result |
0 commit comments