~juju/ubuntu/precise/juju/0.5-packaging

« back to all changes in this revision

Viewing changes to juju/state/hook.py

  • Committer: Package Import Robot
  • Author(s): Clint Byrum
  • Date: 2012-04-18 15:45:34 UTC
  • mfrom: (1.1.12)
  • Revision ID: package-import@ubuntu.com-20120418154534-tkoahx8nmnrb9ph7
Tags: 0.5+bzr531-0ubuntu1
* New upstream snapshot (LP: #985249)
* d/p/fix-tests-without-aws-key.patch: Dropped as it has been
  superseded by a better upstream fix (LP: #819329)
* d/p/no-write-sample-on-help.patch: Dropped, Applied upstream.
* d/p/disable-failing-zookeeper-test.patch refreshed.
* d/control: new code requires latest txzookeeper upstream.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from collections import namedtuple
 
2
 
1
3
from twisted.internet.defer import inlineCallbacks, returnValue, succeed, fail
2
4
 
3
5
from juju.state.base import StateBase
4
6
from juju.state.errors import (
5
 
    UnitRelationStateNotFound, StateNotFound, RelationBrokenContextError)
 
7
    UnitRelationStateNotFound, StateNotFound, RelationBrokenContextError,
 
8
    RelationStateNotFound, InvalidRelationIdentity, StateChanged)
 
9
from juju.state.relation import (
 
10
    RelationStateManager, ServiceRelationState, UnitRelationState)
6
11
from juju.state.service import ServiceStateManager, parse_service_name
7
12
from juju.state.utils import YAMLState
8
13
 
9
14
 
10
 
class RelationChange(object):
11
 
    """Encapsulation of relation change variables passed to hook.
12
 
    """
 
15
class RelationChange(
 
16
    namedtuple(
 
17
        "RelationChange",
 
18
        "relation_ident change_type unit_name")):
13
19
 
14
 
    def __init__(self, relation_name, change_type, unit_name):
15
 
        self._relation_name = relation_name
16
 
        self._change_type = change_type
17
 
        self._unit_name = unit_name
 
20
    __slots__ = ()
18
21
 
19
22
    @property
20
23
    def relation_name(self):
21
 
        return self._relation_name
22
 
 
23
 
    @property
24
 
    def change_type(self):
25
 
        return self._change_type
26
 
 
27
 
    @property
28
 
    def unit_name(self):
29
 
        return self._unit_name
 
24
        return self.relation_ident.split(":")[0]
30
25
 
31
26
 
32
27
class HookContext(StateBase):
33
28
    """Context for hooks which don't depend on relation state. """
34
29
 
35
 
    def __init__(self, client, unit_name):
 
30
    def __init__(self, client, unit_name, topology=None):
36
31
        super(HookContext, self).__init__(client)
37
32
 
38
33
        self._unit_name = unit_name
47
42
        # Service options
48
43
        self._config_options = None
49
44
 
50
 
        # Topology for resolving name<->id of units.
51
 
        self._topology = None
 
45
        # Cached topology
 
46
        self._topology = topology
52
47
 
53
48
    @inlineCallbacks
54
49
    def _resolve_id(self, unit_id):
102
97
        returnValue(self._config_options)
103
98
 
104
99
    @inlineCallbacks
 
100
    def get_relations(self):
 
101
        """Get the relations associated to the local service."""
 
102
        relations = []
 
103
        if self._topology is None:
 
104
            self._topology = yield self._read_topology()
 
105
        service = yield self.get_local_service()
 
106
        internal_service_id = service.internal_id
 
107
        for info in self._topology.get_relations_for_service(
 
108
                internal_service_id):
 
109
            service_info = info["service"]
 
110
            relations.append(
 
111
                ServiceRelationState(
 
112
                    self._client,
 
113
                    internal_service_id,
 
114
                    info["relation_id"],
 
115
                    info["scope"],
 
116
                    **service_info))
 
117
        returnValue(relations)
 
118
 
 
119
    @inlineCallbacks
 
120
    def get_relation_idents(self, relation_name):
 
121
        """Return the relation idents for `relation_name`"""
 
122
        relations = yield self.get_relations()
 
123
        returnValue(sorted([r.relation_ident for r in relations if
 
124
                            not relation_name or
 
125
                            r.relation_name == relation_name]))
 
126
 
 
127
    @inlineCallbacks
 
128
    def get_relation_id_and_scope(self, relation_ident):
 
129
        """Return the (internal) relation id for `relation_ident`."""
 
130
        parts = relation_ident.split(":")
 
131
        if len(parts) != 2 or not parts[1].isdigit():
 
132
            raise InvalidRelationIdentity(relation_ident)
 
133
        relation_name, normalized_id = parts
 
134
        relation_id = "%s-%s" % ("relation", normalized_id.zfill(10))
 
135
        # Double check the internal relation id by looking it up
 
136
        relations = yield self.get_relations()
 
137
        for r in relations:
 
138
            if (r.relation_name == relation_name and \
 
139
                r.internal_relation_id == relation_id):
 
140
                returnValue((relation_id, r.relation_scope))
 
141
        else:
 
142
            raise RelationStateNotFound()
 
143
 
 
144
    @inlineCallbacks
 
145
    def get_relation_hook_context(self, relation_ident):
 
146
        """Return a child hook context for `relation_ident`"""
 
147
        service = yield self.get_local_service()
 
148
        unit = yield self.get_local_unit_state()
 
149
        relation_id, relation_scope = yield self.get_relation_id_and_scope(
 
150
            relation_ident)
 
151
        unit_relation = UnitRelationState(
 
152
            self._client, service.internal_id, unit.internal_id,
 
153
            relation_id, relation_scope)
 
154
        # Ensure that the topology is shared so there's a consistent view
 
155
        # between the parent and any children.
 
156
        returnValue(RelationHookContext(
 
157
                self._client, unit_relation,
 
158
                relation_ident,
 
159
                unit_name=self._unit_name,
 
160
                topology=self._topology))
 
161
 
 
162
    @inlineCallbacks
105
163
    def flush(self):
106
164
        """Flush pending state."""
107
165
        config = yield self.get_config()
115
173
    hook. Also buffers all writes till the flush method is invoked.
116
174
    """
117
175
 
118
 
    def __init__(self, client, unit_relation, change, members=None,
119
 
                 unit_name=None):
 
176
    def __init__(self, client, unit_relation, relation_ident,
 
177
                 members=None, unit_name=None, topology=None):
120
178
        """
121
179
        @param unit_relation: The unit relation state associated to the hook.
122
180
        @param change: A C{RelationChange} instance.
123
181
        """
124
182
        # Zookeeper client.
125
 
        super(RelationHookContext, self).__init__(client, unit_name=unit_name)
 
183
        super(RelationHookContext, self).__init__(
 
184
            client, unit_name=unit_name, topology=topology)
126
185
        self._unit_relation = unit_relation
127
 
        # The current change we're being executed against.
128
 
        self._change = change
129
186
 
130
187
        # A cache of related units in the relation.
131
188
        self._members = members
132
189
 
133
 
    def _settings_path(self, unit_id):
134
 
        return "/relations/%s/settings/%s" % (
135
 
            self._unit_relation.internal_relation_id, unit_id)
 
190
        # The relation ident of this context
 
191
        self._relation_ident = relation_ident
 
192
 
 
193
        # Whether settings have been modified (set/delete) for this context
 
194
        self._needs_flushing = False
 
195
 
 
196
        # A cache of the relation scope path
 
197
        self._settings_scope_path = None
 
198
 
 
199
    def get_settings_path(self, unit_id):
 
200
        if self._unit_relation.relation_scope == "global":
 
201
            return "/relations/%s/settings/%s" % (
 
202
                self._unit_relation.internal_relation_id, unit_id)
 
203
 
 
204
        if self._settings_scope_path:
 
205
            return "%s/%s" % (self._settings_scope_path, unit_id)
 
206
 
 
207
        def process(unit_settings_path):
 
208
            self._settings_scope_path = "/".join(
 
209
                unit_settings_path.split("/")[:-1])
 
210
            return "%s/%s" % (self._settings_scope_path, unit_id)
 
211
 
 
212
        d = self._unit_relation.get_settings_path()
 
213
        return d.addCallback(process)
 
214
 
 
215
    @property
 
216
    def relation_ident(self):
 
217
        """Returns the relation ident corresponding to this context."""
 
218
        return self._relation_ident
 
219
 
 
220
    @property
 
221
    def relation_name(self):
 
222
        """Returns the relation name corresponding to this context."""
 
223
        return self._relation_ident.split(":")[0]
136
224
 
137
225
    @inlineCallbacks
138
226
    def get_members(self):
140
228
        if self._members is not None:
141
229
            returnValue(self._members)
142
230
 
143
 
        container = yield self._unit_relation.get_related_unit_container()
 
231
        try:
 
232
            container = yield self._unit_relation.get_related_unit_container()
 
233
        except StateChanged:
 
234
            # The unit relation has vanished, so there are no members.
 
235
            returnValue([])
144
236
        unit_ids = yield self._client.get_children(container)
145
237
        if self._unit_relation.internal_unit_id in unit_ids:
146
238
            unit_ids.remove(self._unit_relation.internal_unit_id)
164
256
            returnValue(self._node_cache[unit_name])
165
257
 
166
258
        unit_id = yield self._resolve_name(unit_name)
167
 
        path = self._settings_path(unit_id)
 
259
        path = yield self.get_settings_path(unit_id)
168
260
 
169
261
        # verify the unit relation path exists
170
262
        relation_data = YAMLState(self._client, path)
173
265
        except StateNotFound:
174
266
            raise UnitRelationStateNotFound(
175
267
                self._unit_relation.internal_relation_id,
176
 
                self._change.relation_name,
 
268
                self.relation_name,
177
269
                unit_name)
178
270
 
179
271
        # cache the value
209
301
        if not isinstance(data, dict):
210
302
            raise TypeError("A dictionary is required.")
211
303
 
 
304
        self._needs_flushing = True
212
305
        state = yield self._setup_relation_state()
213
306
        state.update(data)
214
307
 
215
308
    @inlineCallbacks
216
309
    def set_value(self, key, value):
217
310
        """Set a relation value for a unit."""
 
311
        self._needs_flushing = True
218
312
        state = yield self._setup_relation_state()
219
313
        state[key] = value
220
314
 
221
315
    @inlineCallbacks
222
316
    def delete_value(self, key):
223
317
        """Delete a relation value for a unit."""
 
318
        self._needs_flushing = True
224
319
        state = yield self._setup_relation_state()
225
320
        try:
226
321
            del state[key]
246
341
        could also be done with config settings, but given their
247
342
        usage model, doesn't seem to be worth logging).
248
343
        """
249
 
        rel_state = yield self._setup_relation_state()
250
 
        relation_setting_changes = yield rel_state.write()
 
344
        relation_setting_changes = []
 
345
        if self._needs_flushing:
 
346
            rel_state = yield self._setup_relation_state()
 
347
            relation_setting_changes = yield rel_state.write()
251
348
        yield super(RelationHookContext, self).flush()
252
349
        returnValue(relation_setting_changes)
253
350
 
266
363
        super(DepartedRelationHookContext, self).__init__(client, unit_name)
267
364
        self._relation_name = relation_name
268
365
        self._relation_id = relation_id
269
 
        self._settings_path = "/relations/%s/settings/%s" % (
270
 
            relation_id, unit_id)
271
 
 
 
366
        self._settings_path = None
272
367
        # Cache of relation settings for the local unit
273
368
        self._relation_cache = None
274
369
 
275
370
    def get_members(self):
276
371
        return succeed([])
277
372
 
 
373
    @property
 
374
    def relation_ident(self):
 
375
        """Returns the external relation id corresponding to this context."""
 
376
        return ServiceRelationState.get_relation_ident(
 
377
            self._relation_name, self._relation_id)
 
378
 
 
379
    @inlineCallbacks
 
380
    def get_settings_path(self):
 
381
        if self._settings_path:
 
382
            returnValue(self._settings_path)
 
383
 
 
384
        unit_id = yield self._resolve_name(self._unit_name)
 
385
        topology = yield self._read_topology()
 
386
        container = topology.get_service_unit_container(unit_id)
 
387
        container_info = ""
 
388
        if container:
 
389
            container_info = "%s/" % container[-1]
 
390
 
 
391
        self._settings_path = "/relations/%s/%ssettings/%s" % (
 
392
            self._relation_id, container_info, unit_id)
 
393
        returnValue(self._settings_path)
 
394
 
278
395
    @inlineCallbacks
279
396
    def get(self, unit_name):
280
397
        # Only this unit's settings should be accessible.
282
399
            raise RelationBrokenContextError(
283
400
                "Cannot access other units in broken relation")
284
401
 
 
402
        settings_path = yield self.get_settings_path()
 
403
 
285
404
        if self._relation_cache is None:
286
 
            relation_data = YAMLState(self._client, self._settings_path)
 
405
            relation_data = YAMLState(self._client, settings_path)
287
406
            try:
288
407
                yield relation_data.read(required=True)
289
408
                self._relation_cache = dict(relation_data)