~larry-e-works/uci-engine/write-exitcode-to-file

« back to all changes in this revision

Viewing changes to charms/precise/webui/hooks/charmhelpers/core/hookenv.py

  • Committer: Joe Talbott
  • Date: 2014-01-27 14:54:08 UTC
  • mfrom: (126.3.8 webui)
  • mto: This revision was merged to the branch mainline in revision 161.
  • Revision ID: joe.talbott@canonical.com-20140127145408-zpubebx02y6oumxq
merge doanac's cleanup branch

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
"Interactions with the Juju environment"
2
 
# Copyright 2013 Canonical Ltd.
3
 
#
4
 
# Authors:
5
 
#  Charm Helpers Developers <juju@lists.ubuntu.com>
6
 
 
7
 
import os
8
 
import json
9
 
import yaml
10
 
import subprocess
11
 
import UserDict
12
 
from subprocess import CalledProcessError
13
 
 
14
 
CRITICAL = "CRITICAL"
15
 
ERROR = "ERROR"
16
 
WARNING = "WARNING"
17
 
INFO = "INFO"
18
 
DEBUG = "DEBUG"
19
 
MARKER = object()
20
 
 
21
 
cache = {}
22
 
 
23
 
 
24
 
def cached(func):
25
 
    """Cache return values for multiple executions of func + args
26
 
 
27
 
    For example:
28
 
 
29
 
        @cached
30
 
        def unit_get(attribute):
31
 
            pass
32
 
 
33
 
        unit_get('test')
34
 
 
35
 
    will cache the result of unit_get + 'test' for future calls.
36
 
    """
37
 
    def wrapper(*args, **kwargs):
38
 
        global cache
39
 
        key = str((func, args, kwargs))
40
 
        try:
41
 
            return cache[key]
42
 
        except KeyError:
43
 
            res = func(*args, **kwargs)
44
 
            cache[key] = res
45
 
            return res
46
 
    return wrapper
47
 
 
48
 
 
49
 
def flush(key):
50
 
    """Flushes any entries from function cache where the
51
 
    key is found in the function+args """
52
 
    flush_list = []
53
 
    for item in cache:
54
 
        if key in item:
55
 
            flush_list.append(item)
56
 
    for item in flush_list:
57
 
        del cache[item]
58
 
 
59
 
 
60
 
def log(message, level=None):
61
 
    """Write a message to the juju log"""
62
 
    command = ['juju-log']
63
 
    if level:
64
 
        command += ['-l', level]
65
 
    command += [message]
66
 
    subprocess.call(command)
67
 
 
68
 
 
69
 
class Serializable(UserDict.IterableUserDict):
70
 
    """Wrapper, an object that can be serialized to yaml or json"""
71
 
 
72
 
    def __init__(self, obj):
73
 
        # wrap the object
74
 
        UserDict.IterableUserDict.__init__(self)
75
 
        self.data = obj
76
 
 
77
 
    def __getattr__(self, attr):
78
 
        # See if this object has attribute.
79
 
        if attr in ("json", "yaml", "data"):
80
 
            return self.__dict__[attr]
81
 
        # Check for attribute in wrapped object.
82
 
        got = getattr(self.data, attr, MARKER)
83
 
        if got is not MARKER:
84
 
            return got
85
 
        # Proxy to the wrapped object via dict interface.
86
 
        try:
87
 
            return self.data[attr]
88
 
        except KeyError:
89
 
            raise AttributeError(attr)
90
 
 
91
 
    def __getstate__(self):
92
 
        # Pickle as a standard dictionary.
93
 
        return self.data
94
 
 
95
 
    def __setstate__(self, state):
96
 
        # Unpickle into our wrapper.
97
 
        self.data = state
98
 
 
99
 
    def json(self):
100
 
        """Serialize the object to json"""
101
 
        return json.dumps(self.data)
102
 
 
103
 
    def yaml(self):
104
 
        """Serialize the object to yaml"""
105
 
        return yaml.dump(self.data)
106
 
 
107
 
 
108
 
def execution_environment():
109
 
    """A convenient bundling of the current execution context"""
110
 
    context = {}
111
 
    context['conf'] = config()
112
 
    if relation_id():
113
 
        context['reltype'] = relation_type()
114
 
        context['relid'] = relation_id()
115
 
        context['rel'] = relation_get()
116
 
    context['unit'] = local_unit()
117
 
    context['rels'] = relations()
118
 
    context['env'] = os.environ
119
 
    return context
120
 
 
121
 
 
122
 
def in_relation_hook():
123
 
    """Determine whether we're running in a relation hook"""
124
 
    return 'JUJU_RELATION' in os.environ
125
 
 
126
 
 
127
 
def relation_type():
128
 
    """The scope for the current relation hook"""
129
 
    return os.environ.get('JUJU_RELATION', None)
130
 
 
131
 
 
132
 
def relation_id():
133
 
    """The relation ID for the current relation hook"""
134
 
    return os.environ.get('JUJU_RELATION_ID', None)
135
 
 
136
 
 
137
 
def local_unit():
138
 
    """Local unit ID"""
139
 
    return os.environ['JUJU_UNIT_NAME']
140
 
 
141
 
 
142
 
def remote_unit():
143
 
    """The remote unit for the current relation hook"""
144
 
    return os.environ['JUJU_REMOTE_UNIT']
145
 
 
146
 
 
147
 
def service_name():
148
 
    """The name service group this unit belongs to"""
149
 
    return local_unit().split('/')[0]
150
 
 
151
 
 
152
 
@cached
153
 
def config(scope=None):
154
 
    """Juju charm configuration"""
155
 
    config_cmd_line = ['config-get']
156
 
    if scope is not None:
157
 
        config_cmd_line.append(scope)
158
 
    config_cmd_line.append('--format=json')
159
 
    try:
160
 
        return json.loads(subprocess.check_output(config_cmd_line))
161
 
    except ValueError:
162
 
        return None
163
 
 
164
 
 
165
 
@cached
166
 
def relation_get(attribute=None, unit=None, rid=None):
167
 
    """Get relation information"""
168
 
    _args = ['relation-get', '--format=json']
169
 
    if rid:
170
 
        _args.append('-r')
171
 
        _args.append(rid)
172
 
    _args.append(attribute or '-')
173
 
    if unit:
174
 
        _args.append(unit)
175
 
    try:
176
 
        return json.loads(subprocess.check_output(_args))
177
 
    except ValueError:
178
 
        return None
179
 
    except CalledProcessError, e:
180
 
        if e.returncode == 2:
181
 
            return None
182
 
        raise
183
 
 
184
 
 
185
 
def relation_set(relation_id=None, relation_settings={}, **kwargs):
186
 
    """Set relation information for the current unit"""
187
 
    relation_cmd_line = ['relation-set']
188
 
    if relation_id is not None:
189
 
        relation_cmd_line.extend(('-r', relation_id))
190
 
    for k, v in (relation_settings.items() + kwargs.items()):
191
 
        if v is None:
192
 
            relation_cmd_line.append('{}='.format(k))
193
 
        else:
194
 
            relation_cmd_line.append('{}={}'.format(k, v))
195
 
    subprocess.check_call(relation_cmd_line)
196
 
    # Flush cache of any relation-gets for local unit
197
 
    flush(local_unit())
198
 
 
199
 
 
200
 
@cached
201
 
def relation_ids(reltype=None):
202
 
    """A list of relation_ids"""
203
 
    reltype = reltype or relation_type()
204
 
    relid_cmd_line = ['relation-ids', '--format=json']
205
 
    if reltype is not None:
206
 
        relid_cmd_line.append(reltype)
207
 
        return json.loads(subprocess.check_output(relid_cmd_line)) or []
208
 
    return []
209
 
 
210
 
 
211
 
@cached
212
 
def related_units(relid=None):
213
 
    """A list of related units"""
214
 
    relid = relid or relation_id()
215
 
    units_cmd_line = ['relation-list', '--format=json']
216
 
    if relid is not None:
217
 
        units_cmd_line.extend(('-r', relid))
218
 
    return json.loads(subprocess.check_output(units_cmd_line)) or []
219
 
 
220
 
 
221
 
@cached
222
 
def relation_for_unit(unit=None, rid=None):
223
 
    """Get the json represenation of a unit's relation"""
224
 
    unit = unit or remote_unit()
225
 
    relation = relation_get(unit=unit, rid=rid)
226
 
    for key in relation:
227
 
        if key.endswith('-list'):
228
 
            relation[key] = relation[key].split()
229
 
    relation['__unit__'] = unit
230
 
    return relation
231
 
 
232
 
 
233
 
@cached
234
 
def relations_for_id(relid=None):
235
 
    """Get relations of a specific relation ID"""
236
 
    relation_data = []
237
 
    relid = relid or relation_ids()
238
 
    for unit in related_units(relid):
239
 
        unit_data = relation_for_unit(unit, relid)
240
 
        unit_data['__relid__'] = relid
241
 
        relation_data.append(unit_data)
242
 
    return relation_data
243
 
 
244
 
 
245
 
@cached
246
 
def relations_of_type(reltype=None):
247
 
    """Get relations of a specific type"""
248
 
    relation_data = []
249
 
    reltype = reltype or relation_type()
250
 
    for relid in relation_ids(reltype):
251
 
        for relation in relations_for_id(relid):
252
 
            relation['__relid__'] = relid
253
 
            relation_data.append(relation)
254
 
    return relation_data
255
 
 
256
 
 
257
 
@cached
258
 
def relation_types():
259
 
    """Get a list of relation types supported by this charm"""
260
 
    charmdir = os.environ.get('CHARM_DIR', '')
261
 
    mdf = open(os.path.join(charmdir, 'metadata.yaml'))
262
 
    md = yaml.safe_load(mdf)
263
 
    rel_types = []
264
 
    for key in ('provides', 'requires', 'peers'):
265
 
        section = md.get(key)
266
 
        if section:
267
 
            rel_types.extend(section.keys())
268
 
    mdf.close()
269
 
    return rel_types
270
 
 
271
 
 
272
 
@cached
273
 
def relations():
274
 
    """Get a nested dictionary of relation data for all related units"""
275
 
    rels = {}
276
 
    for reltype in relation_types():
277
 
        relids = {}
278
 
        for relid in relation_ids(reltype):
279
 
            units = {local_unit(): relation_get(unit=local_unit(), rid=relid)}
280
 
            for unit in related_units(relid):
281
 
                reldata = relation_get(unit=unit, rid=relid)
282
 
                units[unit] = reldata
283
 
            relids[relid] = units
284
 
        rels[reltype] = relids
285
 
    return rels
286
 
 
287
 
 
288
 
@cached
289
 
def is_relation_made(relation, keys='private-address'):
290
 
    '''
291
 
    Determine whether a relation is established by checking for
292
 
    presence of key(s).  If a list of keys is provided, they
293
 
    must all be present for the relation to be identified as made
294
 
    '''
295
 
    if isinstance(keys, str):
296
 
        keys = [keys]
297
 
    for r_id in relation_ids(relation):
298
 
        for unit in related_units(r_id):
299
 
            context = {}
300
 
            for k in keys:
301
 
                context[k] = relation_get(k, rid=r_id,
302
 
                                          unit=unit)
303
 
            if None not in context.values():
304
 
                return True
305
 
    return False
306
 
 
307
 
 
308
 
def open_port(port, protocol="TCP"):
309
 
    """Open a service network port"""
310
 
    _args = ['open-port']
311
 
    _args.append('{}/{}'.format(port, protocol))
312
 
    subprocess.check_call(_args)
313
 
 
314
 
 
315
 
def close_port(port, protocol="TCP"):
316
 
    """Close a service network port"""
317
 
    _args = ['close-port']
318
 
    _args.append('{}/{}'.format(port, protocol))
319
 
    subprocess.check_call(_args)
320
 
 
321
 
 
322
 
@cached
323
 
def unit_get(attribute):
324
 
    """Get the unit ID for the remote unit"""
325
 
    _args = ['unit-get', '--format=json', attribute]
326
 
    try:
327
 
        return json.loads(subprocess.check_output(_args))
328
 
    except ValueError:
329
 
        return None
330
 
 
331
 
 
332
 
def unit_private_ip():
333
 
    """Get this unit's private IP address"""
334
 
    return unit_get('private-address')
335
 
 
336
 
 
337
 
class UnregisteredHookError(Exception):
338
 
    """Raised when an undefined hook is called"""
339
 
    pass
340
 
 
341
 
 
342
 
class Hooks(object):
343
 
    """A convenient handler for hook functions.
344
 
 
345
 
    Example:
346
 
        hooks = Hooks()
347
 
 
348
 
        # register a hook, taking its name from the function name
349
 
        @hooks.hook()
350
 
        def install():
351
 
            ...
352
 
 
353
 
        # register a hook, providing a custom hook name
354
 
        @hooks.hook("config-changed")
355
 
        def config_changed():
356
 
            ...
357
 
 
358
 
        if __name__ == "__main__":
359
 
            # execute a hook based on the name the program is called by
360
 
            hooks.execute(sys.argv)
361
 
    """
362
 
 
363
 
    def __init__(self):
364
 
        super(Hooks, self).__init__()
365
 
        self._hooks = {}
366
 
 
367
 
    def register(self, name, function):
368
 
        """Register a hook"""
369
 
        self._hooks[name] = function
370
 
 
371
 
    def execute(self, args):
372
 
        """Execute a registered hook based on args[0]"""
373
 
        hook_name = os.path.basename(args[0])
374
 
        if hook_name in self._hooks:
375
 
            self._hooks[hook_name]()
376
 
        else:
377
 
            raise UnregisteredHookError(hook_name)
378
 
 
379
 
    def hook(self, *hook_names):
380
 
        """Decorator, registering them as hooks"""
381
 
        def wrapper(decorated):
382
 
            for hook_name in hook_names:
383
 
                self.register(hook_name, decorated)
384
 
            else:
385
 
                self.register(decorated.__name__, decorated)
386
 
                if '_' in decorated.__name__:
387
 
                    self.register(
388
 
                        decorated.__name__.replace('_', '-'), decorated)
389
 
            return decorated
390
 
        return wrapper
391
 
 
392
 
 
393
 
def charm_dir():
394
 
    """Return the root directory of the current charm"""
395
 
    return os.environ.get('CHARM_DIR')