Package ClusterShell :: Module NodeUtils
[hide private]
[frames] | no frames]

Source Code for Module ClusterShell.NodeUtils

  1  # Copyright CEA/DAM/DIF (2010) 
  2  #  Contributors: 
  3  #   Stephane THIELL <stephane.thiell@cea.fr> 
  4  #   Aurelien DEGREMONT <aurelien.degremont@cea.fr> 
  5  # 
  6  # This file is part of the ClusterShell library. 
  7  # 
  8  # This software is governed by the CeCILL-C license under French law and 
  9  # abiding by the rules of distribution of free software.  You can  use, 
 10  # modify and/ or redistribute the software under the terms of the CeCILL-C 
 11  # license as circulated by CEA, CNRS and INRIA at the following URL 
 12  # "http://www.cecill.info". 
 13  # 
 14  # As a counterpart to the access to the source code and  rights to copy, 
 15  # modify and redistribute granted by the license, users are provided only 
 16  # with a limited warranty  and the software's author,  the holder of the 
 17  # economic rights,  and the successive licensors  have only  limited 
 18  # liability. 
 19  # 
 20  # In this respect, the user's attention is drawn to the risks associated 
 21  # with loading,  using,  modifying and/or developing or reproducing the 
 22  # software by the user in light of its specific status of free software, 
 23  # that may mean  that it is complicated to manipulate,  and  that  also 
 24  # therefore means  that it is reserved for developers  and  experienced 
 25  # professionals having in-depth computer knowledge. Users are therefore 
 26  # encouraged to load and test the software's suitability as regards their 
 27  # requirements in conditions enabling the security of their systems and/or 
 28  # data to be ensured and,  more generally, to use and operate it in the 
 29  # same conditions as regards security. 
 30  # 
 31  # The fact that you are presently reading this means that you have had 
 32  # knowledge of the CeCILL-C license and that you accept its terms. 
 33  # 
 34  # $Id: NodeUtils.py 285 2010-06-28 22:27:41Z st-cea $ 
 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   
53 -class GroupSourceException(Exception):
54 """Base GroupSource exception"""
55 - def __init__(self, message, group_source):
56 Exception.__init__(self, message) 57 self.group_source = group_source
58
59 -class GroupSourceNoUpcall(GroupSourceException):
60 """Raised when upcall is not available"""
61
62 -class GroupSourceQueryFailed(GroupSourceException):
63 """Raised when a query failed (eg. no group found)"""
64
65 -class GroupResolverError(Exception):
66 """Base GroupResolver error"""
67
68 -class GroupResolverSourceError(GroupResolverError):
69 """Raised when upcall is not available"""
70
71 -class GroupResolverConfigError(GroupResolverError):
72 """Raised when a configuration error is encountered"""
73 74
75 -class GroupSource(object):
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 # Cache upcall data 85 self._cache_map = {} 86 self._cache_list = [] 87 self._cache_all = None 88 self._cache_reverse = {} 89 90 # Supported external upcalls 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
96 - def _verbose_print(self, msg):
97 if self.verbosity > 0: 98 print >> sys.stderr, "%s<%s> %s" % \ 99 (self.__class__.__name__, self.name, msg)
100
101 - def _upcall_read(self, cmdtpl, vars=dict()):
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
118 - def resolv_map(self, group):
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
128 - def resolv_list(self):
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
141 - def resolv_all(self):
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
154 - def resolv_reverse(self, node):
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
168 -class GroupResolver(object):
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
183 - def set_verbosity(self, value):
184 """ 185 Set debugging verbosity value. 186 """ 187 for source in self._sources.itervalues(): 188 source.verbosity = value
189
190 - def add_source(self, group_source):
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
199 - def sources(self):
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
215 - def _source(self, namespace):
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
225 - def group_nodes(self, group, namespace=None):
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
232 - def all_nodes(self, namespace=None):
233 """ 234 Find all nodes. You may specify an optional namespace. 235 """ 236 source = self._source(namespace) 237 return self._list(source, 'all')
238
239 - def grouplist(self, namespace=None):
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
247 - def has_node_groups(self, namespace=None):
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
257 - def node_groups(self, node, namespace=None):
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
265 -class GroupResolverConfig(GroupResolver):
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
272 - def __init__(self, configfile):
273 """ 274 """ 275 GroupResolver.__init__(self) 276 277 self.default_sourcename = None 278 279 self.config = ConfigParser() 280 self.config.read(configfile) 281 282 # Get config file sections 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 # When not specified, select a random section. 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
319 - def _source(self, namespace):
320 return GroupResolver._source(self, namespace or self.default_sourcename)
321
322 - def sources(self):
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