1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 """
37 Cluster nodes utility module
38
39 The NodeUtils module is a ClusterShell helper module that provides
40 supplementary services to manage nodes in a cluster. It is primarily
41 designed to enhance the NodeSet module providing some binding support
42 to external node groups sources in separate namespaces (example of
43 group sources are: files, jobs scheduler, custom scripts, etc.).
44 """
45
46 import sys
47
48 from ConfigParser import ConfigParser, NoOptionError, NoSectionError
49 from string import Template
50 from subprocess import Popen, PIPE
51
52
54 """Base GroupSource exception"""
55 - def __init__(self, message, group_source):
58
60 """Raised when upcall is not available"""
61
63 """Raised when a query failed (eg. no group found)"""
64
66 """Base GroupResolver error"""
67
69 """Raised when upcall is not available"""
70
72 """Raised when a configuration error is encountered"""
73
74
76 """
77 GroupSource class managing external calls for nodegroup support.
78 """
79 - def __init__(self, name, map_upcall, all_upcall=None,
80 list_upcall=None, reverse_upcall=None):
81 self.name = name
82 self.verbosity = 0
83
84
85 self._cache_map = {}
86 self._cache_list = []
87 self._cache_all = None
88 self._cache_reverse = {}
89
90
91 self.map_upcall = map_upcall
92 self.all_upcall = all_upcall
93 self.list_upcall = list_upcall
94 self.reverse_upcall = reverse_upcall
95
97 if self.verbosity > 0:
98 print >> sys.stderr, "%s<%s> %s" % \
99 (self.__class__.__name__, self.name, msg)
100
102 """
103 Invoke the specified upcall command, raise an Exception if
104 something goes wrong and return the command output otherwise.
105 """
106 cmdline = Template(getattr(self, "%s_upcall" % \
107 cmdtpl)).safe_substitute(vars)
108 self._verbose_print("EXEC '%s'" % cmdline)
109 proc = Popen(cmdline, stdout=PIPE, shell=True)
110 output = proc.communicate()[0].strip()
111 self._verbose_print("READ '%s'" % output)
112 if proc.returncode != 0:
113 self._verbose_print("ERROR '%s' returned %d" % (cmdline, \
114 proc.returncode))
115 raise GroupSourceQueryFailed(cmdline, self)
116 return output
117
119 """
120 Get nodes from group 'group', using the cached value if
121 available.
122 """
123 if group not in self._cache_map:
124 self._cache_map[group] = self._upcall_read('map', dict(GROUP=group))
125
126 return self._cache_map[group]
127
129 """
130 Return a list of all group names for this group source, using
131 the cached value if available.
132 """
133 if not self.list_upcall:
134 raise GroupSourceNoUpcall("list", self)
135
136 if not self._cache_list:
137 self._cache_list = self._upcall_read('list')
138
139 return self._cache_list
140
142 """
143 Return the content of special group ALL, using the cached value
144 if available.
145 """
146 if not self.all_upcall:
147 raise GroupSourceNoUpcall("all", self)
148
149 if not self._cache_all:
150 self._cache_all = self._upcall_read('all')
151
152 return self._cache_all
153
155 """
156 Return the group name matching the provided node, using the
157 cached value if available.
158 """
159 if not self.reverse_upcall:
160 raise GroupSourceNoUpcall("reverse", self)
161
162 if node not in self._cache_reverse:
163 self._cache_reverse[node] = self._upcall_read('reverse', \
164 dict(NODE=node))
165 return self._cache_reverse[node]
166
167
169 """
170 Base class GroupResolver that aims to provide node/group resolution
171 from multiple GroupSource's.
172 """
173
174 - def __init__(self, default_source=None):
175 """
176 Initialize GroupResolver object.
177 """
178 self._sources = {}
179 self._default_source = default_source
180 if default_source:
181 self._sources[default_source.name] = default_source
182
184 """
185 Set debugging verbosity value.
186 """
187 for source in self._sources.itervalues():
188 source.verbosity = value
189
191 """
192 Add a GroupSource to this resolver.
193 """
194 if group_source.name in self._sources:
195 raise ValueError("GroupSource '%s': name collision" % \
196 group_source.name)
197 self._sources[group_source.name] = group_source
198
200 """
201 Get the list of all resolver source names.
202 """
203 return self._sources.keys()
204
205 - def _list(self, source, what, *args):
206 """Helper method that returns a list of result when the source
207 is defined."""
208 result = []
209 assert source
210 raw = getattr(source, 'resolv_%s' % what)(*args)
211 for line in raw.splitlines():
212 map(result.append, line.strip().split())
213 return result
214
216 """Helper method that returns the source by namespace name."""
217 if not namespace:
218 source = self._default_source
219 else:
220 source = self._sources.get(namespace)
221 if not source:
222 raise GroupResolverSourceError(namespace or "<default>")
223 return source
224
226 """
227 Find nodes for specified group name and optional namespace.
228 """
229 source = self._source(namespace)
230 return self._list(source, 'map', group)
231
233 """
234 Find all nodes. You may specify an optional namespace.
235 """
236 source = self._source(namespace)
237 return self._list(source, 'all')
238
240 """
241 Get full group list. You may specify an optional
242 namespace.
243 """
244 source = self._source(namespace)
245 return self._list(source, 'list')
246
248 """
249 Return whether finding group list for a specified node is
250 supported by the resolver (in optional namespace).
251 """
252 try:
253 return bool(self._source(namespace).reverse_upcall)
254 except GroupResolverSourceError:
255 return False
256
258 """
259 Find group list for specified node and optional namespace.
260 """
261 source = self._source(namespace)
262 return self._list(source, 'reverse', node)
263
264
266 """
267 GroupResolver class that is able to automatically setup its
268 GroupSource's from a configuration file. This is the default
269 resolver for NodeSet.
270 """
271
273 """
274 """
275 GroupResolver.__init__(self)
276
277 self.default_sourcename = None
278
279 self.config = ConfigParser()
280 self.config.read(configfile)
281
282
283 group_sections = self.config.sections()
284 if 'Main' in group_sections:
285 group_sections.remove('Main')
286
287 if not group_sections:
288 return
289
290 try:
291 self.default_sourcename = self.config.get('Main', 'default')
292 if self.default_sourcename and self.default_sourcename \
293 not in group_sections:
294 raise GroupResolverConfigError("Default group source not found: "
295 "\"%s\"" % self.default_sourcename)
296 except (NoSectionError, NoOptionError):
297 pass
298
299
300 if not self.default_sourcename:
301 self.default_sourcename = group_sections[0]
302
303 try:
304 for section in group_sections:
305 map_upcall = self.config.get(section, 'map', True)
306 all_upcall = list_upcall = reverse_upcall = None
307 if self.config.has_option(section, 'all'):
308 all_upcall = self.config.get(section, 'all', True)
309 if self.config.has_option(section, 'list'):
310 list_upcall = self.config.get(section, 'list', True)
311 if self.config.has_option(section, 'reverse'):
312 reverse_upcall = self.config.get(section, 'reverse', True)
313
314 self.add_source(GroupSource(section, map_upcall, all_upcall,
315 list_upcall, reverse_upcall))
316 except (NoSectionError, NoOptionError), e:
317 raise GroupResolverConfigError(str(e))
318
321
323 """
324 Get the list of all resolver source names (default source is always
325 first).
326 """
327 srcs = GroupResolver.sources(self)
328 if srcs:
329 srcs.remove(self.default_sourcename)
330 srcs.insert(0, self.default_sourcename)
331 return srcs
332