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

Source Code for Module ClusterShell.NodeSet

   1  # 
   2  # Copyright CEA/DAM/DIF (2007, 2008, 2009, 2010) 
   3  #  Contributor: Stephane THIELL <stephane.thiell@cea.fr> 
   4  # 
   5  # This file is part of the ClusterShell library. 
   6  # 
   7  # This software is governed by the CeCILL-C license under French law and 
   8  # abiding by the rules of distribution of free software.  You can  use, 
   9  # modify and/ or redistribute the software under the terms of the CeCILL-C 
  10  # license as circulated by CEA, CNRS and INRIA at the following URL 
  11  # "http://www.cecill.info". 
  12  # 
  13  # As a counterpart to the access to the source code and  rights to copy, 
  14  # modify and redistribute granted by the license, users are provided only 
  15  # with a limited warranty  and the software's author,  the holder of the 
  16  # economic rights,  and the successive licensors  have only  limited 
  17  # liability. 
  18  # 
  19  # In this respect, the user's attention is drawn to the risks associated 
  20  # with loading,  using,  modifying and/or developing or reproducing the 
  21  # software by the user in light of its specific status of free software, 
  22  # that may mean  that it is complicated to manipulate,  and  that  also 
  23  # therefore means  that it is reserved for developers  and  experienced 
  24  # professionals having in-depth computer knowledge. Users are therefore 
  25  # encouraged to load and test the software's suitability as regards their 
  26  # requirements in conditions enabling the security of their systems and/or 
  27  # data to be ensured and,  more generally, to use and operate it in the 
  28  # same conditions as regards security. 
  29  # 
  30  # The fact that you are presently reading this means that you have had 
  31  # knowledge of the CeCILL-C license and that you accept its terms. 
  32  # 
  33  # $Id: NodeSet.py 297 2010-07-21 20:57:35Z st-cea $ 
  34   
  35  """ 
  36  Cluster node set module. 
  37   
  38  A module to deal efficiently with 1D rangesets and nodesets (pdsh-like). 
  39  Instances of RangeSet and NodeSet both provide similar operations than 
  40  the builtin set() type and Set object. 
  41  See http://www.python.org/doc/lib/set-objects.html 
  42   
  43  Usage example 
  44  ============= 
  45    >>> # Import NodeSet class 
  46    ... from ClusterShell.NodeSet import NodeSet 
  47    >>> 
  48    >>> # Create a new nodeset from string 
  49    ... nodeset = NodeSet("cluster[1-30]") 
  50    >>> # Add cluster32 to nodeset 
  51    ... nodeset.update("cluster32") 
  52    >>> # Remove from nodeset 
  53    ... nodeset.difference_update("cluster[2-5]") 
  54    >>> # Print nodeset as a pdsh-like pattern 
  55    ... print nodeset 
  56    cluster[1,6-30,32] 
  57    >>> # Iterate over node names in nodeset 
  58    ... for node in nodeset: 
  59    ...     print node 
  60    [...] 
  61  """ 
  62   
  63  import copy 
  64  import re 
  65   
  66  import ClusterShell.NodeUtils as NodeUtils 
  67   
  68   
  69  # Define default GroupResolver object used by NodeSet 
  70  DEF_GROUPS_CONFIG = "/etc/clustershell/groups.conf" 
  71  DEF_STD_GROUP_RESOLVER = NodeUtils.GroupResolverConfig(DEF_GROUPS_CONFIG) 
  72  STD_GROUP_RESOLVER = DEF_STD_GROUP_RESOLVER 
73 74 75 -class RangeSetException(Exception):
76 """Base RangeSet exception class."""
77
78 -class RangeSetParseError(RangeSetException):
79 """Raised when RangeSet parsing cannot be done properly."""
80 - def __init__(self, part, msg):
81 if part: 82 msg = "%s : \"%s\"" % (msg, part) 83 RangeSetException.__init__(self, msg) 84 # faulty subrange; this allows you to target the error 85 self.part = part
86
87 -class RangeSetPaddingError(RangeSetParseError):
88 """Raised when a fatal padding incoherency occurs"""
89 - def __init__(self, part, msg):
90 RangeSetParseError.__init__(self, part, "padding mismatch (%s)" % msg)
91
92 93 -class NodeSetException(Exception):
94 """Base NodeSet exception class."""
95
96 -class NodeSetParseError(NodeSetException):
97 """Raised when NodeSet parsing cannot be done properly."""
98 - def __init__(self, part, msg):
99 if part: 100 msg = "%s : \"%s\"" % (msg, part) 101 NodeSetException.__init__(self, msg) 102 # faulty part; this allows you to target the error 103 self.part = part
104
105 -class NodeSetParseRangeError(NodeSetParseError):
106 """Raised when bad range is encountered during NodeSet parsing."""
107 - def __init__(self, rset_exc):
108 NodeSetParseError.__init__(self, str(rset_exc), "bad range")
109
110 -class NodeSetExternalError(NodeSetException):
111 """Raised when an external error is encountered."""
112
113 114 -class RangeSet:
115 """ 116 Advanced range sets. 117 118 RangeSet creation examples: 119 >>> rset = RangeSet() # empty RangeSet 120 >>> rset = RangeSet("5,10-42") # contains 5, 10 to 42 121 >>> rset = RangeSet("0-10/2") # contains 0, 2, 4, 6, 8, 10 122 123 Also, RangeSet provides methods like update(), intersection_update() 124 or difference_update(), which conform to the Python Set API. 125 """
126 - def __init__(self, pattern=None, autostep=None):
127 """ 128 Initialize RangeSet with optional pdsh-like string pattern and 129 autostep threshold. 130 """ 131 if autostep is None: 132 # disabled by default for pdsh compat (+inf is 1E400, but a bug in 133 # python 2.4 makes it impossible to be pickled, so we use less). 134 self._autostep = 1E100 135 else: 136 # - 1 because user means node count, but we means 137 # real steps. 138 self._autostep = int(autostep) - 1 139 140 self._length = 0 141 self._ranges = [] 142 143 if pattern is not None: 144 145 # Comma separated ranges 146 if pattern.find(',') < 0: 147 subranges = [pattern] 148 else: 149 subranges = pattern.split(',') 150 151 for subrange in subranges: 152 if subrange.find('/') < 0: 153 step = 1 154 baserange = subrange 155 else: 156 baserange, step = subrange.split('/', 1) 157 158 try: 159 step = int(step) 160 except ValueError: 161 raise RangeSetParseError(subrange, 162 "cannot convert string to integer") 163 164 if baserange.find('-') < 0: 165 if step != 1: 166 raise RangeSetParseError(subrange, 167 "invalid step usage") 168 begin = end = baserange 169 else: 170 begin, end = baserange.split('-', 1) 171 172 # compute padding and return node range info tuple 173 try: 174 pad = 0 175 if int(begin) != 0: 176 begins = begin.lstrip("0") 177 if len(begin) - len(begins) > 0: 178 pad = len(begin) 179 start = int(begins) 180 else: 181 if len(begin) > 1: 182 pad = len(begin) 183 start = 0 184 if int(end) != 0: 185 ends = end.lstrip("0") 186 else: 187 ends = end 188 stop = int(ends) 189 except ValueError: 190 raise RangeSetParseError(subrange, 191 "cannot convert string to integer") 192 193 # check preconditions 194 if start > stop or step < 1: 195 raise RangeSetParseError(subrange, 196 "invalid values in range") 197 198 self.add_range(start, stop, step, pad)
199 200 @classmethod
201 - def fromlist(cls, rglist, autostep=None):
202 """ 203 Class method that returns a new RangeSet with ranges from 204 provided list. 205 """ 206 inst = RangeSet(autostep=autostep) 207 for rg in rglist: 208 if isinstance(rg, RangeSet): 209 inst.update(rg) 210 else: 211 inst.update(RangeSet(rg)) 212 return inst
213 214 @classmethod
215 - def fromone(cls, index, pad=0, autostep=None):
216 """ 217 Class method that returns a new RangeSet of one single item. 218 """ 219 inst = RangeSet(autostep=autostep) 220 inst.add(index, pad) 221 return inst
222
223 - def __iter__(self):
224 """ 225 Iterate over each item in RangeSet. 226 """ 227 for start, stop, step, pad in self._ranges: 228 for i in range(start, stop + 1, step): 229 yield "%*d" % (pad, i)
230
231 - def __len__(self):
232 """ 233 Get the number of items in RangeSet. 234 """ 235 return self._length
236
237 - def __str__(self):
238 """ 239 Get range-based string. 240 """ 241 cnt = 0 242 s = "" 243 for start, stop, step, pad in self._ranges: 244 assert pad != None 245 if cnt > 0: 246 s += "," 247 if start == stop: 248 s += "%0*d" % (pad, start) 249 else: 250 assert step >= 0, "Internal error: step < 0" 251 if step == 1: 252 s += "%0*d-%0*d" % (pad, start, pad, stop) 253 else: 254 s += "%0*d-%0*d/%d" % (pad, start, pad, stop, step) 255 cnt += stop - start + 1 256 return s
257 258 # __repr__ is the same as __str__ as it is a valid expression that 259 # could be used to recreate a RangeSet with the same value 260 __repr__ = __str__ 261
262 - def __contains__(self, elem):
263 """ 264 Is element contained in RangeSet? Element can be either a 265 string with optional padding (eg. "002") or an integer 266 (obviously, no padding check is performed for integer). 267 """ 268 # support str type with padding support, eg. `"003" in rangeset' 269 if type(elem) is str: 270 pad = 0 271 if int(elem) != 0: 272 selem = elem.lstrip("0") 273 if len(elem) - len(selem) > 0: 274 pad = len(elem) 275 ielem = int(selem) 276 else: 277 if len(elem) > 1: 278 pad = len(elem) 279 ielem = 0 280 return self._contains_with_padding(ielem, pad) 281 282 # the following cast raises TypeError if elem is not an integer 283 return self._contains(int(elem))
284
285 - def _contains(self, ielem):
286 """ 287 Contains subroutine that takes an integer. 288 """ 289 for rgstart, rgstop, rgstep, rgpad in self._ranges: 290 if ielem >= rgstart and ielem <= rgstop and \ 291 (ielem - rgstart) % rgstep == 0: 292 return True 293 return False
294
295 - def _contains_with_padding(self, ielem, pad):
296 """ 297 Contains subroutine that takes an integer and a padding value. 298 """ 299 for rgstart, rgstop, rgstep, rgpad in self._ranges: 300 # for each ranges, check for inclusion + padding matching 301 # + step matching 302 if ielem >= rgstart and ielem <= rgstop and \ 303 (pad == rgpad or (pad == 0 and len(str(ielem)) >= rgpad)) and \ 304 (ielem - rgstart) % rgstep == 0: 305 return True 306 return False
307
308 - def _binary_sanity_check(self, other):
309 # check that the other argument to a binary operation is also 310 # a RangeSet, raising a TypeError otherwise. 311 if not isinstance(other, RangeSet): 312 raise TypeError, "Binary operation only permitted between RangeSets"
313
314 - def issubset(self, rangeset):
315 """ 316 Report whether another rangeset contains this rangeset. 317 """ 318 self._binary_sanity_check(rangeset) 319 for start, stop, step, pad in self._ranges: 320 for i in range(start, stop + 1, step): 321 if not rangeset._contains_with_padding(i, pad): 322 return False 323 return True
324
325 - def issuperset(self, rangeset):
326 """ 327 Report whether this rangeset contains another rangeset. 328 """ 329 self._binary_sanity_check(rangeset) 330 return rangeset.issubset(self)
331
332 - def __eq__(self, other):
333 """ 334 RangeSet equality comparison. 335 """ 336 # Return NotImplemented instead of raising TypeError, to 337 # indicate that the comparison is not implemented with respect 338 # to the other type (the other comparand then gets a change to 339 # determine the result, then it falls back to object address 340 # comparison). 341 if not isinstance(other, RangeSet): 342 return NotImplemented 343 return len(self) == len(other) and self.issubset(other)
344 345 # inequality comparisons using the is-subset relation 346 __le__ = issubset 347 __ge__ = issuperset 348
349 - def __lt__(self, other):
350 """ 351 x.__lt__(y) <==> x<y 352 """ 353 self._binary_sanity_check(other) 354 return len(self) < len(other) and self.issubset(other)
355
356 - def __gt__(self, other):
357 """ 358 x.__gt__(y) <==> x>y 359 """ 360 self._binary_sanity_check(other) 361 return len(self) > len(other) and self.issuperset(other)
362
363 - def __getitem__(self, i):
364 """ 365 Return the element at index i. 366 """ 367 length = 0 368 for start, stop, step, pad in self._ranges: 369 cnt = (stop - start) / step + 1 370 if i < length + cnt: 371 return start + (i - length) * step 372 length += cnt
373
374 - def __getslice__(self, i, j):
375 """ 376 Return the slice from index i to index j-1. For convenience 377 only, not optimized as of version 1.0. 378 """ 379 return RangeSet.fromlist(list(self)[i:j])
380
381 - def split(self, nbr):
382 """ 383 Split the rangeset into nbr sub-rangeset. Each sub-rangeset will have 384 the same number of element more or less 1. Current rangeset remains 385 unmodified. Returns an iterator. 386 387 >>> RangeSet("1-5").split(3) 388 RangeSet("1-2") 389 RangeSet("3-4") 390 RangeSet("foo5") 391 """ 392 # XXX: This uses the non-optimized __getslice__ method. 393 assert(nbr > 0) 394 395 # We put the same number of element in each sub-nodeset. 396 slice_size = len(self) / nbr 397 left = len(self) % nbr 398 399 begin = 0 400 for i in range(0, nbr): 401 length = slice_size + int(i < left) 402 yield self[begin:begin + length] 403 begin += length
404 405
406 - def _expand(self):
407 """ 408 Expand all items. Internal use. 409 """ 410 items = [] 411 pad = 0 412 for rgstart, rgstop, rgstep, rgpad in self._ranges: 413 items += range(rgstart, rgstop + 1, rgstep) 414 pad = pad or rgpad 415 return items, pad
416
417 - def _fold(self, items, pad):
418 """ 419 Fold items as ranges and group them by step. 420 Return: (ranges, total_length) 421 """ 422 cnt, k, m, istart, rg = 0, -1, 0, None, [] 423 424 # iterate over items and regroup them using steps 425 for i in items: 426 if i > k: 427 cnt += 1 428 if istart is None: 429 istart = k = i 430 elif m > 0: # check step length (m) 431 if m != i - k: 432 if m == 1 or k - istart >= self._autostep * m: 433 # add one range with possible autostep 434 rg.append((istart, k, m, pad)) 435 istart = k = i 436 elif k - istart > m: 437 # stepped without autostep 438 # be careful to let the last one "pending" 439 for j in range(istart, k, m): 440 rg.append((j, j, 1, pad)) 441 istart = k 442 else: 443 rg.append((istart, istart, 1, pad)) 444 istart = k 445 m = i - k 446 k = i 447 448 # finishing 449 if istart is not None: # istart might be 0 450 if m > 0: 451 if m == 1 or k - istart >= self._autostep * m: 452 # add one range with possible autostep 453 rg.append((istart, k, m, pad)) 454 elif k - istart > m: 455 # stepped without autostep 456 for j in range(istart, k + m, m): 457 rg.append((j, j, 1, pad)) 458 else: 459 rg.append((istart, istart, 1, pad)) 460 rg.append((k, k, 1, pad)) 461 else: 462 rg.append((istart, istart, 1, pad)) 463 464 return rg, cnt
465
466 - def add_range(self, start, stop, step=1, pad=0):
467 """ 468 Add a range (start, stop, step and padding length) to RangeSet. 469 """ 470 assert start <= stop, "please provide ordered node index ranges" 471 assert step != None 472 assert step > 0 473 assert pad != None 474 assert pad >= 0 475 assert stop - start < 1e9, "range too large" 476 477 if self._length == 0: 478 # first add optimization 479 stop_adjust = stop - (stop - start) % step 480 if step == 1 or stop_adjust - start >= self._autostep * step: 481 self._ranges = [ (start, stop_adjust, step, pad) ] 482 else: 483 for j in range(start, stop_adjust + step, step): 484 self._ranges.append((j, j, step, pad)) 485 self._length = (stop_adjust - start) / step + 1 486 else: 487 self._ranges, self._length = self._add_range_exfold(start, stop, \ 488 step, pad)
489
490 - def _add_range_exfold(self, start, stop, step, pad):
491 """ 492 Add range expanding then folding all items. 493 """ 494 assert start <= stop, "please provide ordered node index ranges" 495 assert step > 0 496 assert pad != None 497 assert pad >= 0 498 499 items, rgpad = self._expand() 500 items += range(start, stop + 1, step) 501 items.sort() 502 503 return self._fold(items, pad or rgpad)
504
505 - def union(self, other):
506 """ 507 s.union(t) returns a new rangeset with elements from both s and t. 508 """ 509 self_copy = copy.deepcopy(self) 510 self_copy.update(other) 511 return self_copy
512
513 - def __or__(self, other):
514 """ 515 Implements the | operator. So s | t returns a new rangeset with 516 elements from both s and t. 517 """ 518 if not isinstance(other, RangeSet): 519 return NotImplemented 520 return self.union(other)
521
522 - def add(self, elem, pad=0):
523 """ 524 Add element to RangeSet. 525 """ 526 self.add_range(elem, elem, step=1, pad=pad)
527
528 - def update(self, rangeset):
529 """ 530 Update a rangeset with the union of itself and another. 531 """ 532 for start, stop, step, pad in rangeset._ranges: 533 self.add_range(start, stop, step, pad)
534
535 - def clear(self):
536 """ 537 Remove all ranges from this rangeset. 538 """ 539 self._ranges = [] 540 self._length = 0
541
542 - def __ior__(self, other):
543 """ 544 Implements the |= operator. So s |= t returns rangeset s with 545 elements added from t. (Python version 2.5+ required) 546 """ 547 self._binary_sanity_check(other) 548 return self.update(other)
549
550 - def intersection(self, rangeset):
551 """ 552 s.intersection(t) returns a new rangeset with elements common 553 to s and t. 554 """ 555 self_copy = copy.deepcopy(self) 556 self_copy.intersection_update(rangeset) 557 return self_copy
558
559 - def __and__(self, other):
560 """ 561 Implements the & operator. So s & t returns a new rangeset with 562 elements common to s and t. 563 """ 564 if not isinstance(other, RangeSet): 565 return NotImplemented 566 return self.intersection(other)
567
568 - def intersection_update(self, rangeset):
569 """ 570 Intersection with provided RangeSet. 571 """ 572 self._ranges, self._length = self._intersect_exfold(rangeset)
573
574 - def __iand__(self, other):
575 """ 576 Implements the &= operator. So s &= t returns rangeset s keeping 577 only elements also found in t. (Python version 2.5+ required) 578 """ 579 self._binary_sanity_check(other) 580 return self.intersection_update(other)
581
582 - def _intersect_exfold(self, rangeset):
583 """ 584 Calc intersection with the expand/fold method. 585 """ 586 # expand both rangesets 587 items1, pad1 = self._expand() 588 items2, pad2 = rangeset._expand() 589 590 # create a temporary dict with keys from items2 591 iset = dict.fromkeys(items2) 592 593 # fold items that are in both sets 594 return self._fold([e for e in items1 if e in iset], pad1 or pad2)
595
596 - def difference(self, rangeset):
597 """ 598 s.difference(t) returns a new rangeset with elements in s but 599 not in t. 600 in t. 601 """ 602 self_copy = copy.deepcopy(self) 603 self_copy.difference_update(rangeset) 604 return self_copy
605
606 - def __sub__(self, other):
607 """ 608 Implement the - operator. So s - t returns a new rangeset with 609 elements in s but not in t. 610 """ 611 if not isinstance(other, RangeSet): 612 return NotImplemented 613 return self.difference(other)
614
615 - def difference_update(self, rangeset, strict=False):
616 """ 617 s.difference_update(t) returns rangeset s after removing 618 elements found in t. If strict is True, raise KeyError 619 if an element cannot be removed. 620 """ 621 self._ranges, self._length = self._sub_exfold(rangeset, strict)
622
623 - def __isub__(self, other):
624 """ 625 Implement the -= operator. So s -= t returns rangeset s after 626 removing elements found in t. (Python version 2.5+ required) 627 """ 628 self._binary_sanity_check(other) 629 return self.difference_update(other)
630
631 - def remove(self, elem):
632 """ 633 Remove element elem from the RangeSet. Raise KeyError if elem 634 is not contained in the RangeSet. 635 """ 636 items1, pad1 = self._expand() 637 638 try: 639 items1.remove(elem) 640 except ValueError, e: 641 raise KeyError, elem 642 643 self._ranges, self._length = self._fold(items1, pad1)
644
645 - def _sub_exfold(self, rangeset, strict):
646 """ 647 Calc sub/exclusion with the expand/fold method. If strict is 648 True, raise KeyError if the rangeset is not included. 649 """ 650 # expand both rangesets 651 items1, pad1 = self._expand() 652 items2, pad2 = rangeset._expand() 653 654 # create a temporary dict with keys from items2 655 iset = dict.fromkeys(items2) 656 657 if strict: 658 # create a list of remaining items (lst) and update iset 659 lst = [] 660 for e in items1: 661 if e not in iset: 662 lst.append(e) 663 else: 664 del iset[e] 665 666 # if iset is not empty, some elements were not removed 667 if len(iset) > 0: 668 # give the user an indication of the range that cannot 669 # be removed 670 missing = RangeSet() 671 missing._ranges, missing._length = self._fold(iset.keys(), pad2) 672 # repr(missing) is implicit here 673 raise KeyError, missing 674 675 return self._fold(lst, pad1 or pad2) 676 else: 677 # fold items that are in set 1 and not in set 2 678 return self._fold([e for e in items1 if e not in iset], 679 pad1 or pad2)
680
681 - def symmetric_difference(self, other):
682 """ 683 s.symmetric_difference(t) returns the symmetric difference of 684 two rangesets as a new RangeSet. 685 686 (ie. all elements that are in exactly one of the rangesets.) 687 """ 688 self_copy = copy.deepcopy(self) 689 self_copy.symmetric_difference_update(other) 690 return self_copy
691
692 - def __xor__(self, other):
693 """ 694 Implement the ^ operator. So s ^ t returns a new rangeset with 695 elements that are in exactly one of the rangesets. 696 """ 697 if not isinstance(other, RangeSet): 698 return NotImplemented 699 return self.symmetric_difference(other)
700
701 - def symmetric_difference_update(self, rangeset):
702 """ 703 s.symmetric_difference_update(t) returns rangeset s keeping all 704 elements that are in exactly one of the rangesets. 705 """ 706 self._ranges, self._length = self._xor_exfold(rangeset)
707
708 - def __ixor__(self, other):
709 """ 710 Implement the ^= operator. So s ^= t returns rangeset s after 711 keeping all elements that are in exactly one of the rangesets. 712 (Python version 2.5+ required) 713 """ 714 self._binary_sanity_check(other) 715 return self.symmetric_difference_update(other)
716
717 - def _xor_exfold(self, rangeset):
718 """ 719 Calc symmetric difference (xor). 720 """ 721 # expand both rangesets 722 items1, pad1 = self._expand() 723 items2, pad2 = rangeset._expand() 724 725 if pad1 != pad2: 726 raise RangeSetPaddingError('', "%s != %s" % (pad1, pad2)) 727 # same padding, we're clean... 728 729 # create a temporary dicts 730 iset1 = dict.fromkeys(items1) 731 iset2 = dict.fromkeys(items2) 732 733 # keep items that are in one list only 734 allitems = items1 + items2 735 lst = [e for e in allitems if not e in iset1 or not e in iset2] 736 lst.sort() 737 738 return self._fold(lst, pad1)
739
740 741 -class NodeSetBase(object):
742 """ 743 Base class for NodeSet. 744 """
745 - def __init__(self, pattern=None, rangeset=None):
746 """ 747 Initialize an empty NodeSetBase. 748 """ 749 self._length = 0 750 self._patterns = {} 751 if pattern: 752 self._add(pattern, rangeset) 753 elif rangeset: 754 raise ValueError("missing pattern")
755
756 - def _iter(self):
757 """ 758 Iterator on internal item tuples (pattern, index, padding). 759 """ 760 for pat, rangeset in sorted(self._patterns.iteritems()): 761 if rangeset: 762 for start, stop, step, pad in rangeset._ranges: 763 while start <= stop: 764 yield pat, start, pad 765 start += step 766 else: 767 yield pat, None, None
768
769 - def _iterbase(self):
770 """ 771 Iterator on single, one-item NodeSetBase objects. 772 """ 773 for pat, start, pad in self._iter(): 774 if start is not None: 775 yield NodeSetBase(pat, RangeSet.fromone(start, pad)) 776 else: 777 yield NodeSetBase(pat) # no node index
778
779 - def __iter__(self):
780 """ 781 Iterator on single nodes as string. 782 """ 783 # Does not call self._iterbase() + str() for better performance. 784 for pat, start, pad in self._iter(): 785 if start is not None: 786 yield pat % ("%0*d" % (pad, start)) 787 else: 788 yield pat
789
790 - def __len__(self):
791 """ 792 Get the number of nodes in NodeSet. 793 """ 794 cnt = 0 795 for rangeset in self._patterns.itervalues(): 796 if rangeset: 797 cnt += len(rangeset) 798 else: 799 cnt += 1 800 return cnt
801
802 - def __str__(self):
803 """ 804 Get ranges-based pattern of node list. 805 """ 806 result = "" 807 for pat, rangeset in sorted(self._patterns.iteritems()): 808 if rangeset: 809 s = str(rangeset) 810 cnt = len(rangeset) 811 if cnt > 1: 812 s = "[" + s + "]" 813 result += pat % s 814 else: 815 result += pat 816 result += "," 817 return result[:-1]
818
819 - def __contains__(self, other):
820 """ 821 Is node contained in NodeSet ? 822 """ 823 return self.issuperset(other)
824
825 - def _binary_sanity_check(self, other):
826 # check that the other argument to a binary operation is also 827 # a NodeSet, raising a TypeError otherwise. 828 if not isinstance(other, NodeSetBase): 829 raise TypeError, "Binary operation only permitted between NodeSetBase"
830
831 - def issubset(self, other):
832 """ 833 Report whether another nodeset contains this nodeset. 834 """ 835 self._binary_sanity_check(other) 836 return other.issuperset(self)
837
838 - def issuperset(self, other):
839 """ 840 Report whether this nodeset contains another nodeset. 841 """ 842 self._binary_sanity_check(other) 843 status = True 844 for pat, erangeset in other._patterns.iteritems(): 845 rangeset = self._patterns.get(pat) 846 if rangeset: 847 status = rangeset.issuperset(erangeset) 848 else: 849 # might be an unnumbered node (key in dict but no value) 850 status = self._patterns.has_key(pat) 851 if not status: 852 break 853 return status
854
855 - def __eq__(self, other):
856 """ 857 NodeSet equality comparison. 858 """ 859 # See comment for for RangeSet.__eq__() 860 if not isinstance(other, NodeSetBase): 861 return NotImplemented 862 return len(self) == len(other) and self.issuperset(other)
863 864 # inequality comparisons using the is-subset relation 865 __le__ = issubset 866 __ge__ = issuperset 867
868 - def __lt__(self, other):
869 """ 870 x.__lt__(y) <==> x<y 871 """ 872 self._binary_sanity_check(other) 873 return len(self) < len(other) and self.issubset(other)
874
875 - def __gt__(self, other):
876 """ 877 x.__gt__(y) <==> x>y 878 """ 879 self._binary_sanity_check(other) 880 return len(self) > len(other) and self.issuperset(other)
881
882 - def __getitem__(self, i):
883 """ 884 Return the node at index i. For convenience only, not 885 optimized as of version 1.0. 886 """ 887 return list(self)[i]
888
889 - def _add(self, pat, rangeset):
890 """ 891 Add nodes from a (pat, rangeset) tuple. `pat' may be an existing 892 pattern and `rangeset' may be None. 893 """ 894 # get patterns dict entry 895 pat_e = self._patterns.get(pat) 896 897 if pat_e: 898 # don't play with prefix: if there is a value, there is a 899 # rangeset. 900 assert rangeset != None 901 902 # add rangeset in corresponding pattern rangeset 903 pat_e.update(rangeset) 904 else: 905 # create new pattern (with possibly rangeset=None) 906 self._patterns[pat] = copy.copy(rangeset)
907
908 - def union(self, other):
909 """ 910 s.union(t) returns a new set with elements from both s and t. 911 """ 912 self_copy = copy.deepcopy(self) 913 self_copy.update(other) 914 return self_copy
915
916 - def __or__(self, other):
917 """ 918 Implements the | operator. So s | t returns a new nodeset with 919 elements from both s and t. 920 """ 921 if not isinstance(other, NodeSetBase): 922 return NotImplemented 923 return self.union(other)
924
925 - def add(self, other):
926 """ 927 Add node to NodeSet. 928 """ 929 self.update(other)
930
931 - def update(self, other):
932 """ 933 s.update(t) returns nodeset s with elements added from t. 934 """ 935 self._binary_sanity_check(other) 936 937 for pat, rangeset in other._patterns.iteritems(): 938 self._add(pat, rangeset)
939
940 - def clear(self):
941 """ 942 Remove all nodes from this nodeset. 943 """ 944 self._patterns.clear() 945 self._length = 0
946
947 - def __ior__(self, other):
948 """ 949 Implements the |= operator. So s |= t returns nodeset s with 950 elements added from t. (Python version 2.5+ required) 951 """ 952 self._binary_sanity_check(other) 953 return self.update(other)
954
955 - def intersection(self, other):
956 """ 957 s.intersection(t) returns a new set with elements common to s 958 and t. 959 """ 960 self_copy = copy.deepcopy(self) 961 self_copy.intersection_update(other) 962 return self_copy
963
964 - def __and__(self, other):
965 """ 966 Implements the & operator. So s & t returns a new nodeset with 967 elements common to s and t. 968 """ 969 if not isinstance(other, NodeSet): 970 return NotImplemented 971 return self.intersection(other)
972
973 - def intersection_update(self, other):
974 """ 975 s.intersection_update(t) returns nodeset s keeping only 976 elements also found in t. 977 """ 978 self._binary_sanity_check(other) 979 980 if other is self: 981 return 982 983 tmp_ns = NodeSetBase() 984 985 for pat, irangeset in other._patterns.iteritems(): 986 rangeset = self._patterns.get(pat) 987 if rangeset: 988 rs = copy.copy(rangeset) 989 rs.intersection_update(irangeset) 990 # ignore pattern if empty rangeset 991 if len(rs) > 0: 992 tmp_ns._add(pat, rs) 993 elif not irangeset and pat in self._patterns: 994 # intersect two nodes with no rangeset 995 tmp_ns._add(pat, None) 996 elif not irangeset and pat in self._patterns: 997 # intersect two nodes with no rangeset 998 tmp_ns._add(pat, None) 999 1000 # Substitute 1001 self._patterns = tmp_ns._patterns
1002
1003 - def __iand__(self, other):
1004 """ 1005 Implements the &= operator. So s &= t returns nodeset s keeping 1006 only elements also found in t. (Python version 2.5+ required) 1007 """ 1008 self._binary_sanity_check(other) 1009 return self.intersection_update(other)
1010
1011 - def difference(self, other):
1012 """ 1013 s.difference(t) returns a new NodeSet with elements in s but not 1014 in t. 1015 """ 1016 self_copy = copy.deepcopy(self) 1017 self_copy.difference_update(other) 1018 return self_copy
1019
1020 - def __sub__(self, other):
1021 """ 1022 Implement the - operator. So s - t returns a new nodeset with 1023 elements in s but not in t. 1024 """ 1025 if not isinstance(other, NodeSetBase): 1026 return NotImplemented 1027 return self.difference(other)
1028
1029 - def difference_update(self, other, strict=False):
1030 """ 1031 s.difference_update(t) returns nodeset s after removing 1032 elements found in t. If strict is True, raise KeyError 1033 if an element cannot be removed. 1034 """ 1035 self._binary_sanity_check(other) 1036 # the purge of each empty pattern is done afterward to allow self = ns 1037 purge_patterns = [] 1038 1039 # iterate first over exclude nodeset rangesets which is usually smaller 1040 for pat, erangeset in other._patterns.iteritems(): 1041 # if pattern is found, deal with it 1042 rangeset = self._patterns.get(pat) 1043 if rangeset: 1044 # sub rangeset, raise KeyError if not found 1045 rangeset.difference_update(erangeset, strict) 1046 1047 # check if no range left and add pattern to purge list 1048 if len(rangeset) == 0: 1049 purge_patterns.append(pat) 1050 else: 1051 # unnumbered node exclusion 1052 if self._patterns.has_key(pat): 1053 purge_patterns.append(pat) 1054 elif strict: 1055 raise KeyError, pat 1056 1057 for pat in purge_patterns: 1058 del self._patterns[pat]
1059
1060 - def __isub__(self, other):
1061 """ 1062 Implement the -= operator. So s -= t returns nodeset s after 1063 removing elements found in t. (Python version 2.5+ required) 1064 """ 1065 self._binary_sanity_check(other) 1066 return self.difference_update(other)
1067
1068 - def remove(self, elem):
1069 """ 1070 Remove element elem from the nodeset. Raise KeyError if elem 1071 is not contained in the nodeset. 1072 """ 1073 self.difference_update(elem, True)
1074
1075 - def symmetric_difference(self, other):
1076 """ 1077 s.symmetric_difference(t) returns the symmetric difference of 1078 two nodesets as a new NodeSet. 1079 1080 (ie. all nodes that are in exactly one of the nodesets.) 1081 """ 1082 self_copy = copy.deepcopy(self) 1083 self_copy.symmetric_difference_update(other) 1084 return self_copy
1085
1086 - def __xor__(self, other):
1087 """ 1088 Implement the ^ operator. So s ^ t returns a new NodeSet with 1089 nodes that are in exactly one of the nodesets. 1090 """ 1091 if not isinstance(other, NodeSet): 1092 return NotImplemented 1093 return self.symmetric_difference(other)
1094
1095 - def symmetric_difference_update(self, other):
1096 """ 1097 s.symmetric_difference_update(t) returns nodeset s keeping all 1098 nodes that are in exactly one of the nodesets. 1099 """ 1100 self._binary_sanity_check(other) 1101 purge_patterns = [] 1102 1103 # iterate over our rangesets 1104 for pat, rangeset in self._patterns.iteritems(): 1105 brangeset = other._patterns.get(pat) 1106 if brangeset: 1107 rangeset.symmetric_difference_update(brangeset) 1108 else: 1109 if other._patterns.has_key(pat): 1110 purge_patterns.append(pat) 1111 1112 # iterate over other's rangesets 1113 for pat, brangeset in other._patterns.iteritems(): 1114 rangeset = self._patterns.get(pat) 1115 if not rangeset and not self._patterns.has_key(pat): 1116 self._add(pat, brangeset) 1117 1118 # check for patterns cleanup 1119 for pat, rangeset in self._patterns.iteritems(): 1120 if rangeset is not None and len(rangeset) == 0: 1121 purge_patterns.append(pat) 1122 1123 # cleanup 1124 for pat in purge_patterns: 1125 del self._patterns[pat]
1126
1127 - def __ixor__(self, other):
1128 """ 1129 Implement the ^= operator. So s ^= t returns nodeset s after 1130 keeping all nodes that are in exactly one of the nodesets. 1131 (Python version 2.5+ required) 1132 """ 1133 self._binary_sanity_check(other) 1134 return self.symmetric_difference_update(other)
1135
1136 1137 -class NodeGroupBase(NodeSetBase):
1138 """ 1139 """
1140 - def _add(self, pat, rangeset):
1141 """ 1142 Add groups from a (pat, rangeset) tuple. `pat' may be an existing 1143 pattern and `rangeset' may be None. 1144 """ 1145 if pat and pat[0] != '@': 1146 raise ValueError("NodeGroup name must begin with character '@'") 1147 NodeSetBase._add(self, pat, rangeset)
1148
1149 1150 -class ParsingEngine(object):
1151 """ 1152 Class that is able to transform a source into a NodeSetBase. 1153 """ 1154 OP_CODES = { 'update': ',', 1155 'difference_update': '!', 1156 'intersection_update': '&', 1157 'symmetric_difference_update': '^' } 1158
1159 - def __init__(self, group_resolver):
1160 """ 1161 Initialize Parsing Engine. 1162 """ 1163 self.group_resolver = group_resolver
1164
1165 - def parse(self, nsobj, autostep):
1166 """ 1167 Parse provided object if possible and return a NodeSetBase object. 1168 """ 1169 # passing None is supported 1170 if nsobj is None: 1171 return NodeSetBase() 1172 1173 # is nsobj a NodeSetBase instance? 1174 if isinstance(nsobj, NodeSetBase): 1175 return nsobj 1176 1177 # or is nsobj a string? 1178 if type(nsobj) is str: 1179 try: 1180 return self.parse_string(str(nsobj), autostep) 1181 except NodeUtils.GroupSourceQueryFailed, exc: 1182 raise NodeSetParseError(nsobj, str(exc)) 1183 1184 raise TypeError("Unsupported NodeSet input %s" % type(nsobj))
1185
1186 - def parse_string(self, nsstr, autostep):
1187 """ 1188 Parse provided string and return a NodeSetBase object. 1189 """ 1190 ns = NodeSetBase() 1191 1192 for opc, pat, rangeset in self._scan_string(nsstr, autostep): 1193 # Parser main debugging: 1194 #print "OPC %s PAT %s RANGESET %s" % (opc, pat, rangeset) 1195 if self.group_resolver and pat[0] == '@': 1196 ns_group = NodeSetBase() 1197 for nodegroup in NodeGroupBase(pat, rangeset): 1198 # parse/expand nodes group 1199 ns_string_ext = self.parse_group_string(nodegroup) 1200 if ns_string_ext: 1201 # convert result and apply operation 1202 ns_group.update(self.parse(ns_string_ext, autostep)) 1203 # perform operation 1204 getattr(ns, opc)(ns_group) 1205 else: 1206 getattr(ns, opc)(NodeSetBase(pat, rangeset)) 1207 1208 return ns
1209
1210 - def parse_group(self, group, namespace=None, autostep=None):
1211 """Parse provided single group name (without @ prefix).""" 1212 assert self.group_resolver is not None 1213 nodestr = self.group_resolver.group_nodes(group, namespace) 1214 return self.parse(",".join(nodestr), autostep)
1215
1216 - def parse_group_string(self, nodegroup):
1217 """Parse provided group string and return a string.""" 1218 assert nodegroup[0] == '@' 1219 assert self.group_resolver is not None 1220 grpstr = nodegroup[1:] 1221 if grpstr.find(':') < 0: 1222 # default namespace 1223 return ",".join(self.group_resolver.group_nodes(grpstr)) 1224 else: 1225 # specified namespace 1226 namespace, group = grpstr.split(':', 1) 1227 return ",".join(self.group_resolver.group_nodes(group, namespace))
1228
1229 - def _next_op(self, pat):
1230 """Opcode parsing subroutine.""" 1231 op_idx = -1 1232 next_op_code = None 1233 for opc, idx in [(k, pat.find(v)) \ 1234 for k, v in ParsingEngine.OP_CODES.iteritems()]: 1235 if idx >= 0 and (op_idx < 0 or idx <= op_idx): 1236 next_op_code = opc 1237 op_idx = idx 1238 return op_idx, next_op_code
1239
1240 - def _scan_string(self, nsstr, autostep):
1241 """ 1242 Parsing engine's string scanner method. 1243 """ 1244 single_node_re = None 1245 pat = nsstr.strip() 1246 # avoid misformatting 1247 if pat.find('%') >= 0: 1248 pat = pat.replace('%', '%%') 1249 next_op_code = 'update' 1250 while pat is not None: 1251 # Ignore whitespace(s) for convenience 1252 pat = pat.lstrip() 1253 1254 op_code, next_op_code = next_op_code, None 1255 op_idx = -1 1256 op_idx, next_op_code = self._next_op(pat) 1257 bracket_idx = pat.find('[') 1258 1259 # Check if the operator is after the bracket, or if there 1260 # is no operator at all but some brackets. 1261 if bracket_idx >= 0 and (op_idx > bracket_idx or op_idx < 0): 1262 # In this case, we have a pattern of potentially several 1263 # nodes. 1264 # Fill prefix, range and suffix from pattern 1265 # eg. "forbin[3,4-10]-ilo" -> "forbin", "3,4-10", "-ilo" 1266 pfx, sfx = pat.split('[', 1) 1267 try: 1268 rg, sfx = sfx.split(']', 1) 1269 except ValueError: 1270 raise NodeSetParseError(pat, "missing bracket") 1271 1272 # Check if we have a next op-separated node or pattern 1273 op_idx, next_op_code = self._next_op(sfx) 1274 if op_idx < 0: 1275 pat = None 1276 else: 1277 sfx, pat = sfx.split(self.OP_CODES[next_op_code], 1) 1278 1279 # Ignore whitespace(s) 1280 sfx = sfx.rstrip() 1281 1282 # pfx + sfx cannot be empty 1283 if len(pfx) + len(sfx) == 0: 1284 raise NodeSetParseError(pat, "empty node name") 1285 1286 # Process comma-separated ranges 1287 try: 1288 rset = RangeSet(rg, autostep) 1289 except RangeSetParseError, e: 1290 raise NodeSetParseRangeError(e) 1291 1292 yield op_code, "%s%%s%s" % (pfx, sfx), rset 1293 else: 1294 # In this case, either there is no comma and no bracket, 1295 # or the bracket is after the comma, then just return 1296 # the node. 1297 if op_idx < 0: 1298 node = pat 1299 pat = None # break next time 1300 else: 1301 node, pat = pat.split(self.OP_CODES[next_op_code], 1) 1302 # Ignore whitespace(s) 1303 node = node.strip() 1304 1305 if len(node) == 0: 1306 raise NodeSetParseError(pat, "empty node name") 1307 1308 # single node parsing 1309 if single_node_re is None: 1310 single_node_re = re.compile("(\D*)(\d*)(.*)") 1311 1312 mo = single_node_re.match(node) 1313 if not mo: 1314 raise NodeSetParseError(pat, "parse error") 1315 pfx, idx, sfx = mo.groups() 1316 pfx, sfx = pfx or "", sfx or "" 1317 1318 # pfx+sfx cannot be empty 1319 if len(pfx) + len(sfx) == 0: 1320 raise NodeSetParseError(pat, "empty node name") 1321 1322 if idx: 1323 try: 1324 rset = RangeSet(idx, autostep) 1325 except RangeSetParseError, e: 1326 raise NodeSetParseRangeError(e) 1327 p = "%s%%s%s" % (pfx, sfx) 1328 yield op_code, p, rset 1329 else: 1330 # undefined pad means no node index 1331 yield op_code, pfx, None
1332 1333 1334 # Special constant for NodeSet's resolver parameter to avoid any group 1335 # resolution at all. 1336 NOGROUP_RESOLVER=-1
1337 1338 1339 -class NodeSet(NodeSetBase):
1340 """ 1341 Iterable class of nodes with node ranges support. 1342 1343 NodeSet creation examples: 1344 >>> nodeset = NodeSet() # empty NodeSet 1345 >>> nodeset = NodeSet("cluster3") # contains only cluster3 1346 >>> nodeset = NodeSet("cluster[5,10-42]") 1347 >>> nodeset = NodeSet("cluster[0-10/2]") 1348 >>> nodeset = NodeSet("cluster[0-10/2],othername[7-9,120-300]") 1349 1350 NodeSet provides methods like update(), intersection_update() or 1351 difference_update() methods, which conform to the Python Set API. 1352 However, unlike RangeSet or standard Set, NodeSet is somewhat not 1353 so strict for convenience, and understands NodeSet instance or 1354 NodeSet string as argument. Also, there is no strict definition of 1355 one element, for example, it IS allowed to do: 1356 >>> nodeset.remove("blue[36-40]"). 1357 """
1358 - def __init__(self, nodes=None, autostep=None, resolver=None):
1359 """ 1360 Initialize a NodeSet. 1361 The `nodes' argument may be a valid nodeset string or a NodeSet 1362 object. If no nodes are specified, an empty NodeSet is created. 1363 """ 1364 NodeSetBase.__init__(self) 1365 1366 self._autostep = autostep 1367 1368 # Set group resolver. 1369 self._resolver = None 1370 if resolver != NOGROUP_RESOLVER: 1371 self._resolver = resolver or STD_GROUP_RESOLVER 1372 1373 # Initialize default parser. 1374 self._parser = ParsingEngine(self._resolver) 1375 1376 self.update(nodes)
1377 1378 @classmethod
1379 - def fromlist(cls, nodelist, autostep=None, resolver=None):
1380 """ 1381 Class method that returns a new NodeSet with nodes from 1382 provided list. 1383 """ 1384 inst = NodeSet(autostep=autostep, resolver=resolver) 1385 for node in nodelist: 1386 inst.update(node) 1387 return inst
1388 1389 @classmethod
1390 - def fromall(cls, groupsource=None, autostep=None, resolver=None):
1391 """ 1392 Class method that returns a new NodeSet with all nodes from 1393 optional groupsource. 1394 """ 1395 inst = NodeSet(autostep=autostep, resolver=resolver) 1396 if not inst._resolver: 1397 raise NodeSetExternalError("No node group resolver") 1398 try: 1399 # Ask resolver to provide all nodes. 1400 for nodes in inst._resolver.all_nodes(groupsource): 1401 inst.update(nodes) 1402 except NodeUtils.GroupSourceNoUpcall: 1403 # As the resolver is not able to provide all nodes directly, 1404 # failback to list + map(s) method: 1405 try: 1406 # Like in regroup(), we get a NodeSet of all groups in 1407 # specified group source. 1408 allgrpns = NodeSet.fromlist( \ 1409 inst._resolver.grouplist(groupsource), 1410 resolver=NOGROUP_RESOLVER) 1411 # For each individual group, resolve it to node and accumulate. 1412 for grp in allgrpns: 1413 inst.update(NodeSet.fromlist( \ 1414 inst._resolver.group_nodes(grp, groupsource))) 1415 except NodeUtils.GroupSourceNoUpcall: 1416 # We are not able to find "all" nodes, definitely. 1417 raise NodeSetExternalError("Not enough working external " \ 1418 "calls (all, or map + list) defined to get all nodes") 1419 except NodeUtils.GroupSourceQueryFailed, exc: 1420 raise NodeSetExternalError("Unable to get all nodes due to the " \ 1421 "following external failure:\n\t%s" % exc) 1422 return inst
1423
1424 - def __getstate__(self):
1425 """Called when pickling: remove references to group resolver.""" 1426 odict = self.__dict__.copy() 1427 del odict['_resolver'] 1428 del odict['_parser'] 1429 return odict
1430
1431 - def __setstate__(self, dict):
1432 """Called when unpickling: restore parser using non group 1433 resolver.""" 1434 self.__dict__.update(dict) 1435 self._resolver = None 1436 self._parser = ParsingEngine(None)
1437
1438 - def _find_groups(self, node, namespace, allgroups):
1439 """Find groups of node by namespace.""" 1440 if allgroups: 1441 # find node groups using in-memory allgroups 1442 for grp, ns in allgroups.iteritems(): 1443 if node in ns: 1444 yield grp 1445 else: 1446 # find node groups using resolver 1447 for group in self._resolver.node_groups(node, namespace): 1448 yield group
1449
1450 - def regroup(self, groupsource=None, autostep=None, overlap=False, 1451 noprefix=False):
1452 """ 1453 Regroup nodeset using groups. 1454 """ 1455 groups = {} 1456 rest = NodeSet(self, resolver=NOGROUP_RESOLVER) 1457 1458 try: 1459 # Get a NodeSet of all groups in specified group source. 1460 allgrpns = NodeSet.fromlist(self._resolver.grouplist(groupsource), 1461 resolver=NOGROUP_RESOLVER) 1462 except NodeUtils.GroupSourceException: 1463 # If list query failed, we still might be able to regroup 1464 # using reverse. 1465 allgrpns = None 1466 1467 allgroups = {} 1468 1469 # Check for external reverse presence, and also use the 1470 # following heuristic: external reverse is used only when number 1471 # of groups is greater than the NodeSet size. 1472 if self._resolver.has_node_groups(groupsource) and \ 1473 (not allgrpns or len(allgrpns) >= len(self)): 1474 # use external reverse 1475 pass 1476 else: 1477 if not allgrpns: # list query failed and no way to reverse! 1478 return str(rest) 1479 try: 1480 # use internal reverse: populate allgroups 1481 for grp in allgrpns: 1482 nodelist = self._resolver.group_nodes(grp, groupsource) 1483 allgroups[grp] = NodeSet(",".join(nodelist)) 1484 except NodeUtils.GroupSourceQueryFailed, exc: 1485 # External result inconsistency 1486 raise NodeSetExternalError("Unable to map a group " \ 1487 "previously listed\n\tFailed command: %s" % exc) 1488 1489 # For each NodeSetBase in self, finds its groups. 1490 for node in self._iterbase(): 1491 for grp in self._find_groups(node, groupsource, allgroups): 1492 if grp not in groups: 1493 ns = self._parser.parse_group(grp, groupsource, autostep) 1494 groups[grp] = (0, ns) 1495 i, m = groups[grp] 1496 groups[grp] = (i + 1, m) 1497 1498 # Keep only groups that are full. 1499 fulls = [] 1500 for k, (i, m) in groups.iteritems(): 1501 assert i <= len(m) 1502 if i == len(m): 1503 fulls.append((i, k)) 1504 1505 regrouped = NodeSet(resolver=NOGROUP_RESOLVER) 1506 1507 bigalpha = lambda x,y: cmp(y[0],x[0]) or cmp(x[1],y[1]) 1508 1509 # Build regrouped NodeSet by selecting largest groups first. 1510 for num, grp in sorted(fulls, cmp=bigalpha): 1511 if not overlap and groups[grp][1] not in rest: 1512 continue 1513 if groupsource and not noprefix: 1514 regrouped.update("@%s:%s" % (groupsource, grp)) 1515 else: 1516 regrouped.update("@" + grp) 1517 rest.difference_update(groups[grp][1]) 1518 if not rest: 1519 return str(regrouped) 1520 1521 if regrouped: 1522 return "%s,%s" % (regrouped, rest) 1523 1524 return str(rest)
1525
1526 - def issubset(self, other):
1527 """ 1528 Report whether another nodeset contains this nodeset. 1529 """ 1530 ns = self._parser.parse(other, self._autostep) 1531 return NodeSetBase.issuperset(ns, self)
1532
1533 - def issuperset(self, other):
1534 """ 1535 Report whether this nodeset contains another nodeset. 1536 """ 1537 ns = self._parser.parse(other, self._autostep) 1538 return NodeSetBase.issuperset(self, ns)
1539
1540 - def __getslice__(self, i, j):
1541 """ 1542 Return the slice from index i to index j-1. For convenience 1543 only, not optimized as of version 1.0. 1544 """ 1545 return NodeSet.fromlist(list(self)[i:j])
1546
1547 - def split(self, nbr):
1548 """ 1549 Split the nodeset into nbr sub-nodeset. Each sub-nodeset will have the 1550 same number of element more or less 1. Current nodeset remains 1551 unmodified. 1552 1553 >>> NodeSet("foo[1-5]").split(3) 1554 NodeSet("foo[1-2]") 1555 NodeSet("foo[3-4]") 1556 NodeSet("foo5") 1557 """ 1558 # XXX: This uses the non-optimized __getslice__ method. 1559 assert(nbr > 0) 1560 1561 # We put the same number of element in each sub-nodeset. 1562 slice_size = len(self) / nbr 1563 left = len(self) % nbr 1564 1565 begin = 0 1566 for i in range(0, nbr): 1567 length = slice_size + int(i < left) 1568 yield self[begin:begin + length] 1569 begin += length
1570
1571 - def update(self, other):
1572 """ 1573 s.update(t) returns nodeset s with elements added from t. 1574 """ 1575 ns = self._parser.parse(other, self._autostep) 1576 NodeSetBase.update(self, ns)
1577
1578 - def intersection_update(self, other):
1579 """ 1580 s.intersection_update(t) returns nodeset s keeping only 1581 elements also found in t. 1582 """ 1583 ns = self._parser.parse(other, self._autostep) 1584 NodeSetBase.intersection_update(self, ns)
1585
1586 - def difference_update(self, other, strict=False):
1587 """ 1588 s.difference_update(t) returns nodeset s after removing 1589 elements found in t. If strict is True, raise KeyError 1590 if an element cannot be removed. 1591 """ 1592 ns = self._parser.parse(other, self._autostep) 1593 NodeSetBase.difference_update(self, ns, strict)
1594
1595 - def symmetric_difference_update(self, other):
1596 """ 1597 s.symmetric_difference_update(t) returns nodeset s keeping all 1598 nodes that are in exactly one of the nodesets. 1599 """ 1600 ns = self._parser.parse(other, self._autostep) 1601 NodeSetBase.symmetric_difference_update(self, ns)
1602
1603 1604 -def expand(pat):
1605 """ 1606 Commodity function that expands a pdsh-like pattern into a list of 1607 nodes. 1608 """ 1609 return list(NodeSet(pat))
1610
1611 -def fold(pat):
1612 """ 1613 Commodity function that clean dups and fold provided pattern with 1614 ranges and "/step" support. 1615 """ 1616 return str(NodeSet(pat))
1617
1618 -def grouplist(namespace=None):
1619 """ 1620 Commodity function that retrieves the list of groups for a specified 1621 group namespace (or use default namespace). 1622 """ 1623 return STD_GROUP_RESOLVER.grouplist(namespace)
1624
1625 1626 # doctest 1627 1628 -def _test():
1629 import doctest 1630 doctest.testmod()
1631 1632 if __name__ == '__main__': 1633 _test() 1634