1 | """ |
---|
2 | Python bindings for the Moira library |
---|
3 | |
---|
4 | Moira is the Athena Service Management system. It serves as the |
---|
5 | central repository for information about users, groups hosts, print |
---|
6 | queues, and several other aspects of the Athena environment. |
---|
7 | """ |
---|
8 | |
---|
9 | import os |
---|
10 | import re |
---|
11 | |
---|
12 | import _moira |
---|
13 | from _moira import (auth, host, motd, noop, proxy, MoiraException) |
---|
14 | |
---|
15 | |
---|
16 | help_re = re.compile('([a-z0-9_, ]*) \(([a-z0-9_, ]*)\)(?: => ([a-z0-9_, ]*))?', |
---|
17 | re.I) |
---|
18 | et_re = re.compile(r'^\s*#\s*define\s+([A-Za-z0-9_]+)\s+.*?([0-9]+)') |
---|
19 | |
---|
20 | |
---|
21 | _arg_cache = {} |
---|
22 | _return_cache = {} |
---|
23 | _et_cache = {} |
---|
24 | |
---|
25 | |
---|
26 | def _clear_caches(): |
---|
27 | """Clear query caches. |
---|
28 | |
---|
29 | Clears all caches that may only be accurate for a particular Moira |
---|
30 | server or query version. |
---|
31 | """ |
---|
32 | _arg_cache.clear() |
---|
33 | _return_cache.clear() |
---|
34 | |
---|
35 | |
---|
36 | def connect(server=''): |
---|
37 | _moira.connect(server) |
---|
38 | version(-1) |
---|
39 | connect.__doc__ = _moira.connect.__doc__ |
---|
40 | |
---|
41 | |
---|
42 | def disconnect(): |
---|
43 | """Disconnect from the active Moira server""" |
---|
44 | _moira.disconnect() |
---|
45 | _clear_caches() |
---|
46 | |
---|
47 | |
---|
48 | def _load_help(handle): |
---|
49 | """Fetch info about the arguments and return values for a query. |
---|
50 | |
---|
51 | This uses the "_help" Moira query to retrieve names for the |
---|
52 | arguments and return values to and from a particular Moira |
---|
53 | query. These values are cached and used for translating arguments |
---|
54 | and return values into and out of dictionaries and into and out of |
---|
55 | tuples. |
---|
56 | """ |
---|
57 | help_string = ', '.join(query('_help', handle)[0]).strip() |
---|
58 | |
---|
59 | handle_str, arg_str, return_str = help_re.match(help_string).groups('') |
---|
60 | |
---|
61 | handles = handle_str.split(', ') |
---|
62 | args = arg_str.split(', ') |
---|
63 | returns = return_str.split(', ') |
---|
64 | |
---|
65 | for h in handles: |
---|
66 | _arg_cache[h] = args |
---|
67 | _return_cache[h] = returns |
---|
68 | |
---|
69 | |
---|
70 | def _list_query(handle, *args): |
---|
71 | """ |
---|
72 | Execute a Moira query and return the result as a list of tuples. |
---|
73 | |
---|
74 | This bypasses the tuple -> dict conversion done in moira.query() |
---|
75 | """ |
---|
76 | results = [] |
---|
77 | _moira._query(handle, results.append, *args) |
---|
78 | return results |
---|
79 | |
---|
80 | |
---|
81 | def _parse_args(handle, args, kwargs): |
---|
82 | """ |
---|
83 | Convert a set of arguments into the canonical Moira list form. |
---|
84 | |
---|
85 | Both query and access accept either positional arguments or |
---|
86 | keyword arguments, cross-referenced against the argument names |
---|
87 | given by the "_help" query. |
---|
88 | |
---|
89 | This function takes the args and kwargs as they're provided to |
---|
90 | either of those functions and returns a list of purely positional |
---|
91 | arguments that can be passed to the low-level Moira query |
---|
92 | function. |
---|
93 | """ |
---|
94 | if (handle not in _return_cache or |
---|
95 | not _return_cache[handle]): |
---|
96 | _load_help(handle) |
---|
97 | |
---|
98 | if kwargs: |
---|
99 | return tuple(kwargs.get(i, '*') |
---|
100 | for i in _arg_cache[handle]) |
---|
101 | else: |
---|
102 | return args |
---|
103 | |
---|
104 | |
---|
105 | def query(handle, *args, **kwargs): |
---|
106 | """ |
---|
107 | Execute a Moira query and return the result as a list of dicts. |
---|
108 | |
---|
109 | Arguments can be specified either as positional or keyword |
---|
110 | arguments. If specified by keyword, they are cross-referenced with |
---|
111 | the argument name given by the query "_help handle". |
---|
112 | |
---|
113 | All of the real work of Moira is done in queries. There are over |
---|
114 | 100 queries, each of which requires different arguments. The |
---|
115 | arguments to the queries should be passed as separate arguments to |
---|
116 | the function. |
---|
117 | """ |
---|
118 | if handle.startswith('_'): |
---|
119 | return _list_query(handle, *args) |
---|
120 | else: |
---|
121 | fmt = kwargs.pop('fmt', dict) |
---|
122 | |
---|
123 | args = _parse_args(handle, args, kwargs) |
---|
124 | |
---|
125 | plain_results = _list_query(handle, *args) |
---|
126 | results = [] |
---|
127 | |
---|
128 | for r in plain_results: |
---|
129 | results.append(fmt(zip(_return_cache[handle], r))) |
---|
130 | |
---|
131 | return results |
---|
132 | |
---|
133 | |
---|
134 | def access(handle, *args, **kwargs): |
---|
135 | """ |
---|
136 | Determine if the user has the necessary access to perform a query. |
---|
137 | |
---|
138 | As with moira.query, arguments can be specified either as |
---|
139 | positional or keyword arguments. If specified as keywords, they |
---|
140 | are cross-referenced with the argument names given by the "_help" |
---|
141 | query. |
---|
142 | |
---|
143 | This function returns True if the user, as currently |
---|
144 | authenticated, would be allowed to perform the query with the |
---|
145 | given arguments, and False otherwise. |
---|
146 | """ |
---|
147 | args = _parse_args(handle, args, kwargs) |
---|
148 | |
---|
149 | try: |
---|
150 | _moira._access(handle, *args) |
---|
151 | return True |
---|
152 | except MoiraException, e: |
---|
153 | if e.code != errors()['MR_PERM']: |
---|
154 | raise |
---|
155 | return False |
---|
156 | |
---|
157 | |
---|
158 | def version(ver): |
---|
159 | # Changing the Moira version can change a query's arguments and |
---|
160 | # return values |
---|
161 | _clear_caches() |
---|
162 | return _moira.version(ver) |
---|
163 | version.__doc__ = _moira.version.__doc__ |
---|
164 | |
---|
165 | |
---|
166 | def errors(): |
---|
167 | """ |
---|
168 | Return a dict of Moira error codes. |
---|
169 | |
---|
170 | This function parses error codes out of the Moira header files and |
---|
171 | returns a dictionary of those error codes. |
---|
172 | |
---|
173 | The value that's returned should be treated as immutable. It's a |
---|
174 | bug that it isn't. |
---|
175 | """ |
---|
176 | if not _et_cache: |
---|
177 | for prefix in ('/usr/include', |
---|
178 | '/sw/include'): |
---|
179 | header = os.path.join(prefix, 'moira/mr_et.h') |
---|
180 | if os.path.exists(header): |
---|
181 | for line in open(header): |
---|
182 | m = et_re.search(line) |
---|
183 | if m: |
---|
184 | errname, errcode = m.groups() |
---|
185 | _et_cache[errname] = int(errcode) |
---|
186 | |
---|
187 | return _et_cache |
---|
188 | |
---|
189 | |
---|
190 | __all__ = ['connect', 'disconnect', 'auth', 'host', 'motd', 'noop', 'query', |
---|
191 | 'proxy', 'version', 'access', 'errors', '_list_query', |
---|
192 | 'MoiraException'] |
---|