OLD | NEW |
1 """ Hook Execution. | 1 """ Hook Execution. |
2 """ | 2 """ |
3 import os | 3 import os |
4 import fnmatch | 4 import fnmatch |
5 import logging | 5 import logging |
6 import tempfile | 6 import tempfile |
7 | 7 |
8 from twisted.internet.defer import ( | 8 from twisted.internet.defer import ( |
9 inlineCallbacks, DeferredQueue, Deferred, DeferredLock, returnValue) | 9 inlineCallbacks, DeferredQueue, Deferred, DeferredLock, returnValue, |
| 10 DeferredFilesystemLock) |
| 11 |
10 from twisted.internet.error import ProcessExitedAlready | 12 from twisted.internet.error import ProcessExitedAlready |
11 | 13 |
12 DEBUG_HOOK_TEMPLATE = r"""#!/bin/bash | 14 DEBUG_HOOK_TEMPLATE = r"""#!/bin/bash |
13 set -e | 15 set -e |
14 export JUJU_DEBUG=$(mktemp -d) | 16 export JUJU_DEBUG=$(mktemp -d) |
15 exec > $JUJU_DEBUG/debug.log >&1 | 17 exec > $JUJU_DEBUG/debug.log >&1 |
16 | 18 |
17 # Save environment variables and export them for sourcing. | 19 # Save environment variables and export them for sourcing. |
18 FILTER='^\(LS_COLORS\|LESSOPEN\|LESSCLOSE\|PWD\)=' | 20 FILTER='^\(LS_COLORS\|LESSOPEN\|LESSCLOSE\|PWD\)=' |
19 env | grep -v $FILTER > $JUJU_DEBUG/env.sh | 21 env | grep -v $FILTER > $JUJU_DEBUG/env.sh |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
59 | 61 |
60 A typical unit agent is subscribed to multiple event streams | 62 A typical unit agent is subscribed to multiple event streams |
61 across unit and relation lifecycles. All of which will attempt to | 63 across unit and relation lifecycles. All of which will attempt to |
62 execute hooks in response to events. In order to serialize hook | 64 execute hooks in response to events. In order to serialize hook |
63 execution and bring observability, a hook executor is utilized | 65 execution and bring observability, a hook executor is utilized |
64 across the different components that want to execute hooks. | 66 across the different components that want to execute hooks. |
65 """ | 67 """ |
66 | 68 |
67 STOP = object() | 69 STOP = object() |
68 | 70 |
| 71 LOCK_PATH = "/var/lib/juju/hook.lock" |
| 72 |
69 def __init__(self): | 73 def __init__(self): |
70 self._running = False | 74 self._running = False |
71 self._executions = DeferredQueue() | 75 self._executions = DeferredQueue() |
72 self._observer = None | 76 self._observer = None |
73 self._log = logging.getLogger("hook.executor") | 77 self._log = logging.getLogger("hook.executor") |
74 self._run_lock = DeferredLock() | 78 self._run_lock = DeferredLock() |
75 | 79 |
| 80 # Serialized container hook execution |
| 81 self._fs_lock = DeferredFilesystemLock(self.LOCK_PATH) |
| 82 |
76 # The currently executing hook invoker. None if no hook is executing. | 83 # The currently executing hook invoker. None if no hook is executing. |
77 self._invoker = None | 84 self._invoker = None |
78 # The currently executing hook's context. None if no hook is executing. | 85 # The currently executing hook's context. None if no hook is executing. |
79 self._hook_context = None | 86 self._hook_context = None |
80 # The current names of hooks that should be debugged. | 87 # The current names of hooks that should be debugged. |
81 self._debug_hook_names = None | 88 self._debug_hook_names = None |
82 # The path to the last utilized tempfile debug hook. | 89 # The path to the last utilized tempfile debug hook. |
83 self._debug_hook_file_path = None | 90 self._debug_hook_file_path = None |
84 | 91 |
85 @property | 92 @property |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
117 | 124 |
118 if next is self.STOP: | 125 if next is self.STOP: |
119 continue | 126 continue |
120 | 127 |
121 yield self._run_lock.acquire() | 128 yield self._run_lock.acquire() |
122 | 129 |
123 if not self._running: | 130 if not self._running: |
124 self._run_lock.release() | 131 self._run_lock.release() |
125 continue | 132 continue |
126 | 133 |
127 yield self._run_one(*next) | 134 yield self._fs_lock.deferUntilLocked() |
128 self._run_lock.release() | 135 |
| 136 try: |
| 137 yield self._run_one(*next) |
| 138 finally: |
| 139 try: |
| 140 self._fs_lock.unlock() |
| 141 except ValueError: |
| 142 # Defensive.. If on unlock we're not the owner the impl |
| 143 # will raise an error, we don't care as long the sys |
| 144 # is not blocked by us, lock will sanitize |
| 145 pass |
| 146 self._run_lock.release() |
129 | 147 |
130 @inlineCallbacks | 148 @inlineCallbacks |
131 def _run_one(self, invoker, path, exec_deferred): | 149 def _run_one(self, invoker, path, exec_deferred): |
132 """Run a hook. | 150 """Run a hook. |
133 """ | 151 """ |
134 hook_path = self.get_hook_path(path) | 152 hook_path = self.get_hook_path(path) |
135 | 153 |
136 if not os.path.exists(hook_path): | 154 if not os.path.exists(hook_path): |
137 self._log.info( | 155 self._log.info( |
138 "Hook does not exist, skipping %s", hook_path) | 156 "Hook does not exist, skipping %s", hook_path) |
(...skipping 143 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
282 | 300 |
283 def __call__(self, invoker, hook_path): | 301 def __call__(self, invoker, hook_path): |
284 """Schedule a hook for execution. | 302 """Schedule a hook for execution. |
285 | 303 |
286 Returns a deferred that fires when the hook has been executed. | 304 Returns a deferred that fires when the hook has been executed. |
287 """ | 305 """ |
288 exec_deferred = Deferred() | 306 exec_deferred = Deferred() |
289 self._executions.put( | 307 self._executions.put( |
290 (invoker, hook_path, exec_deferred)) | 308 (invoker, hook_path, exec_deferred)) |
291 return exec_deferred | 309 return exec_deferred |
OLD | NEW |