Package ganeti :: Package utils
[hide private]
[frames] | no frames]

Source Code for Package ganeti.utils

  1  # 
  2  # 
  3   
  4  # Copyright (C) 2006, 2007, 2010, 2011 Google Inc. 
  5  # 
  6  # This program is free software; you can redistribute it and/or modify 
  7  # it under the terms of the GNU General Public License as published by 
  8  # the Free Software Foundation; either version 2 of the License, or 
  9  # (at your option) any later version. 
 10  # 
 11  # This program is distributed in the hope that it will be useful, but 
 12  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 14  # General Public License for more details. 
 15  # 
 16  # You should have received a copy of the GNU General Public License 
 17  # along with this program; if not, write to the Free Software 
 18  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
 19  # 02110-1301, USA. 
 20   
 21   
 22  """Ganeti utility module. 
 23   
 24  This module holds functions that can be used in both daemons (all) and 
 25  the command line scripts. 
 26   
 27  """ 
 28   
 29  # Allow wildcard import in pylint: disable=W0401 
 30   
 31  import os 
 32  import re 
 33  import errno 
 34  import pwd 
 35  import time 
 36  import itertools 
 37  import select 
 38  import logging 
 39  import signal 
 40   
 41  from ganeti import errors 
 42  from ganeti import constants 
 43  from ganeti import compat 
 44  from ganeti import pathutils 
 45   
 46  from ganeti.utils.algo import * 
 47  from ganeti.utils.filelock import * 
 48  from ganeti.utils.hash import * 
 49  from ganeti.utils.io import * 
 50  from ganeti.utils.log import * 
 51  from ganeti.utils.lvm import * 
 52  from ganeti.utils.mlock import * 
 53  from ganeti.utils.nodesetup import * 
 54  from ganeti.utils.process import * 
 55  from ganeti.utils.retry import * 
 56  from ganeti.utils.text import * 
 57  from ganeti.utils.wrapper import * 
 58  from ganeti.utils.x509 import * 
 59   
 60   
 61  _VALID_SERVICE_NAME_RE = re.compile("^[-_.a-zA-Z0-9]{1,128}$") 
 62   
 63  UUID_RE = re.compile(constants.UUID_REGEX) 
 64   
 65   
66 -def ForceDictType(target, key_types, allowed_values=None):
67 """Force the values of a dict to have certain types. 68 69 @type target: dict 70 @param target: the dict to update 71 @type key_types: dict 72 @param key_types: dict mapping target dict keys to types 73 in constants.ENFORCEABLE_TYPES 74 @type allowed_values: list 75 @keyword allowed_values: list of specially allowed values 76 77 """ 78 if allowed_values is None: 79 allowed_values = [] 80 81 if not isinstance(target, dict): 82 msg = "Expected dictionary, got '%s'" % target 83 raise errors.TypeEnforcementError(msg) 84 85 for key in target: 86 if key not in key_types: 87 msg = "Unknown parameter '%s'" % key 88 raise errors.TypeEnforcementError(msg) 89 90 if target[key] in allowed_values: 91 continue 92 93 ktype = key_types[key] 94 if ktype not in constants.ENFORCEABLE_TYPES: 95 msg = "'%s' has non-enforceable type %s" % (key, ktype) 96 raise errors.ProgrammerError(msg) 97 98 if ktype in (constants.VTYPE_STRING, constants.VTYPE_MAYBE_STRING): 99 if target[key] is None and ktype == constants.VTYPE_MAYBE_STRING: 100 pass 101 elif not isinstance(target[key], basestring): 102 if isinstance(target[key], bool) and not target[key]: 103 target[key] = "" 104 else: 105 msg = "'%s' (value %s) is not a valid string" % (key, target[key]) 106 raise errors.TypeEnforcementError(msg) 107 elif ktype == constants.VTYPE_BOOL: 108 if isinstance(target[key], basestring) and target[key]: 109 if target[key].lower() == constants.VALUE_FALSE: 110 target[key] = False 111 elif target[key].lower() == constants.VALUE_TRUE: 112 target[key] = True 113 else: 114 msg = "'%s' (value %s) is not a valid boolean" % (key, target[key]) 115 raise errors.TypeEnforcementError(msg) 116 elif target[key]: 117 target[key] = True 118 else: 119 target[key] = False 120 elif ktype == constants.VTYPE_SIZE: 121 try: 122 target[key] = ParseUnit(target[key]) 123 except errors.UnitParseError, err: 124 msg = "'%s' (value %s) is not a valid size. error: %s" % \ 125 (key, target[key], err) 126 raise errors.TypeEnforcementError(msg) 127 elif ktype == constants.VTYPE_INT: 128 try: 129 target[key] = int(target[key]) 130 except (ValueError, TypeError): 131 msg = "'%s' (value %s) is not a valid integer" % (key, target[key]) 132 raise errors.TypeEnforcementError(msg)
133 134
135 -def ValidateServiceName(name):
136 """Validate the given service name. 137 138 @type name: number or string 139 @param name: Service name or port specification 140 141 """ 142 try: 143 numport = int(name) 144 except (ValueError, TypeError): 145 # Non-numeric service name 146 valid = _VALID_SERVICE_NAME_RE.match(name) 147 else: 148 # Numeric port (protocols other than TCP or UDP might need adjustments 149 # here) 150 valid = (numport >= 0 and numport < (1 << 16)) 151 152 if not valid: 153 raise errors.OpPrereqError("Invalid service name '%s'" % name, 154 errors.ECODE_INVAL) 155 156 return name
157 158
159 -def _ComputeMissingKeys(key_path, options, defaults):
160 """Helper functions to compute which keys a invalid. 161 162 @param key_path: The current key path (if any) 163 @param options: The user provided options 164 @param defaults: The default dictionary 165 @return: A list of invalid keys 166 167 """ 168 defaults_keys = frozenset(defaults.keys()) 169 invalid = [] 170 for key, value in options.items(): 171 if key_path: 172 new_path = "%s/%s" % (key_path, key) 173 else: 174 new_path = key 175 176 if key not in defaults_keys: 177 invalid.append(new_path) 178 elif isinstance(value, dict): 179 invalid.extend(_ComputeMissingKeys(new_path, value, defaults[key])) 180 181 return invalid
182 183
184 -def VerifyDictOptions(options, defaults):
185 """Verify a dict has only keys set which also are in the defaults dict. 186 187 @param options: The user provided options 188 @param defaults: The default dictionary 189 @raise error.OpPrereqError: If one of the keys is not supported 190 191 """ 192 invalid = _ComputeMissingKeys("", options, defaults) 193 194 if invalid: 195 raise errors.OpPrereqError("Provided option keys not supported: %s" % 196 CommaJoin(invalid), errors.ECODE_INVAL)
197 198
199 -def ListVolumeGroups():
200 """List volume groups and their size 201 202 @rtype: dict 203 @return: 204 Dictionary with keys volume name and values 205 the size of the volume 206 207 """ 208 command = "vgs --noheadings --units m --nosuffix -o name,size" 209 result = RunCmd(command) 210 retval = {} 211 if result.failed: 212 return retval 213 214 for line in result.stdout.splitlines(): 215 try: 216 name, size = line.split() 217 size = int(float(size)) 218 except (IndexError, ValueError), err: 219 logging.error("Invalid output from vgs (%s): %s", err, line) 220 continue 221 222 retval[name] = size 223 224 return retval
225 226
227 -def BridgeExists(bridge):
228 """Check whether the given bridge exists in the system 229 230 @type bridge: str 231 @param bridge: the bridge name to check 232 @rtype: boolean 233 @return: True if it does 234 235 """ 236 return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
237 238
239 -def TryConvert(fn, val):
240 """Try to convert a value ignoring errors. 241 242 This function tries to apply function I{fn} to I{val}. If no 243 C{ValueError} or C{TypeError} exceptions are raised, it will return 244 the result, else it will return the original value. Any other 245 exceptions are propagated to the caller. 246 247 @type fn: callable 248 @param fn: function to apply to the value 249 @param val: the value to be converted 250 @return: The converted value if the conversion was successful, 251 otherwise the original value. 252 253 """ 254 try: 255 nv = fn(val) 256 except (ValueError, TypeError): 257 nv = val 258 return nv
259 260
261 -def ParseCpuMask(cpu_mask):
262 """Parse a CPU mask definition and return the list of CPU IDs. 263 264 CPU mask format: comma-separated list of CPU IDs 265 or dash-separated ID ranges 266 Example: "0-2,5" -> "0,1,2,5" 267 268 @type cpu_mask: str 269 @param cpu_mask: CPU mask definition 270 @rtype: list of int 271 @return: list of CPU IDs 272 273 """ 274 if not cpu_mask: 275 return [] 276 cpu_list = [] 277 for range_def in cpu_mask.split(","): 278 boundaries = range_def.split("-") 279 n_elements = len(boundaries) 280 if n_elements > 2: 281 raise errors.ParseError("Invalid CPU ID range definition" 282 " (only one hyphen allowed): %s" % range_def) 283 try: 284 lower = int(boundaries[0]) 285 except (ValueError, TypeError), err: 286 raise errors.ParseError("Invalid CPU ID value for lower boundary of" 287 " CPU ID range: %s" % str(err)) 288 try: 289 higher = int(boundaries[-1]) 290 except (ValueError, TypeError), err: 291 raise errors.ParseError("Invalid CPU ID value for higher boundary of" 292 " CPU ID range: %s" % str(err)) 293 if lower > higher: 294 raise errors.ParseError("Invalid CPU ID range definition" 295 " (%d > %d): %s" % (lower, higher, range_def)) 296 cpu_list.extend(range(lower, higher + 1)) 297 return cpu_list
298 299
300 -def ParseMultiCpuMask(cpu_mask):
301 """Parse a multiple CPU mask definition and return the list of CPU IDs. 302 303 CPU mask format: colon-separated list of comma-separated list of CPU IDs 304 or dash-separated ID ranges, with optional "all" as CPU value 305 Example: "0-2,5:all:1,5,6:2" -> [ [ 0,1,2,5 ], [ -1 ], [ 1, 5, 6 ], [ 2 ] ] 306 307 @type cpu_mask: str 308 @param cpu_mask: multiple CPU mask definition 309 @rtype: list of lists of int 310 @return: list of lists of CPU IDs 311 312 """ 313 if not cpu_mask: 314 return [] 315 cpu_list = [] 316 for range_def in cpu_mask.split(constants.CPU_PINNING_SEP): 317 if range_def == constants.CPU_PINNING_ALL: 318 cpu_list.append([constants.CPU_PINNING_ALL_VAL, ]) 319 else: 320 # Uniquify and sort the list before adding 321 cpu_list.append(sorted(set(ParseCpuMask(range_def)))) 322 323 return cpu_list
324 325
326 -def GetHomeDir(user, default=None):
327 """Try to get the homedir of the given user. 328 329 The user can be passed either as a string (denoting the name) or as 330 an integer (denoting the user id). If the user is not found, the 331 C{default} argument is returned, which defaults to C{None}. 332 333 """ 334 try: 335 if isinstance(user, basestring): 336 result = pwd.getpwnam(user) 337 elif isinstance(user, (int, long)): 338 result = pwd.getpwuid(user) 339 else: 340 raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" % 341 type(user)) 342 except KeyError: 343 return default 344 return result.pw_dir
345 346
347 -def FirstFree(seq, base=0):
348 """Returns the first non-existing integer from seq. 349 350 The seq argument should be a sorted list of positive integers. The 351 first time the index of an element is smaller than the element 352 value, the index will be returned. 353 354 The base argument is used to start at a different offset, 355 i.e. C{[3, 4, 6]} with I{offset=3} will return 5. 356 357 Example: C{[0, 1, 3]} will return I{2}. 358 359 @type seq: sequence 360 @param seq: the sequence to be analyzed. 361 @type base: int 362 @param base: use this value as the base index of the sequence 363 @rtype: int 364 @return: the first non-used index in the sequence 365 366 """ 367 for idx, elem in enumerate(seq): 368 assert elem >= base, "Passed element is higher than base offset" 369 if elem > idx + base: 370 # idx is not used 371 return idx + base 372 return None
373 374
375 -def SingleWaitForFdCondition(fdobj, event, timeout):
376 """Waits for a condition to occur on the socket. 377 378 Immediately returns at the first interruption. 379 380 @type fdobj: integer or object supporting a fileno() method 381 @param fdobj: entity to wait for events on 382 @type event: integer 383 @param event: ORed condition (see select module) 384 @type timeout: float or None 385 @param timeout: Timeout in seconds 386 @rtype: int or None 387 @return: None for timeout, otherwise occured conditions 388 389 """ 390 check = (event | select.POLLPRI | 391 select.POLLNVAL | select.POLLHUP | select.POLLERR) 392 393 if timeout is not None: 394 # Poller object expects milliseconds 395 timeout *= 1000 396 397 poller = select.poll() 398 poller.register(fdobj, event) 399 try: 400 # TODO: If the main thread receives a signal and we have no timeout, we 401 # could wait forever. This should check a global "quit" flag or something 402 # every so often. 403 io_events = poller.poll(timeout) 404 except select.error, err: 405 if err[0] != errno.EINTR: 406 raise 407 io_events = [] 408 if io_events and io_events[0][1] & check: 409 return io_events[0][1] 410 else: 411 return None
412 413
414 -class FdConditionWaiterHelper(object):
415 """Retry helper for WaitForFdCondition. 416 417 This class contains the retried and wait functions that make sure 418 WaitForFdCondition can continue waiting until the timeout is actually 419 expired. 420 421 """ 422
423 - def __init__(self, timeout):
424 self.timeout = timeout
425
426 - def Poll(self, fdobj, event):
427 result = SingleWaitForFdCondition(fdobj, event, self.timeout) 428 if result is None: 429 raise RetryAgain() 430 else: 431 return result
432
433 - def UpdateTimeout(self, timeout):
434 self.timeout = timeout
435 436
437 -def WaitForFdCondition(fdobj, event, timeout):
438 """Waits for a condition to occur on the socket. 439 440 Retries until the timeout is expired, even if interrupted. 441 442 @type fdobj: integer or object supporting a fileno() method 443 @param fdobj: entity to wait for events on 444 @type event: integer 445 @param event: ORed condition (see select module) 446 @type timeout: float or None 447 @param timeout: Timeout in seconds 448 @rtype: int or None 449 @return: None for timeout, otherwise occured conditions 450 451 """ 452 if timeout is not None: 453 retrywaiter = FdConditionWaiterHelper(timeout) 454 try: 455 result = Retry(retrywaiter.Poll, RETRY_REMAINING_TIME, timeout, 456 args=(fdobj, event), wait_fn=retrywaiter.UpdateTimeout) 457 except RetryTimeout: 458 result = None 459 else: 460 result = None 461 while result is None: 462 result = SingleWaitForFdCondition(fdobj, event, timeout) 463 return result
464 465
466 -def EnsureDaemon(name):
467 """Check for and start daemon if not alive. 468 469 """ 470 result = RunCmd([pathutils.DAEMON_UTIL, "check-and-start", name]) 471 if result.failed: 472 logging.error("Can't start daemon '%s', failure %s, output: %s", 473 name, result.fail_reason, result.output) 474 return False 475 476 return True
477 478
479 -def StopDaemon(name):
480 """Stop daemon 481 482 """ 483 result = RunCmd([pathutils.DAEMON_UTIL, "stop", name]) 484 if result.failed: 485 logging.error("Can't stop daemon '%s', failure %s, output: %s", 486 name, result.fail_reason, result.output) 487 return False 488 489 return True
490 491
492 -def SplitTime(value):
493 """Splits time as floating point number into a tuple. 494 495 @param value: Time in seconds 496 @type value: int or float 497 @return: Tuple containing (seconds, microseconds) 498 499 """ 500 (seconds, microseconds) = divmod(int(value * 1000000), 1000000) 501 502 assert 0 <= seconds, \ 503 "Seconds must be larger than or equal to 0, but are %s" % seconds 504 assert 0 <= microseconds <= 999999, \ 505 "Microseconds must be 0-999999, but are %s" % microseconds 506 507 return (int(seconds), int(microseconds))
508 509
510 -def MergeTime(timetuple):
511 """Merges a tuple into time as a floating point number. 512 513 @param timetuple: Time as tuple, (seconds, microseconds) 514 @type timetuple: tuple 515 @return: Time as a floating point number expressed in seconds 516 517 """ 518 (seconds, microseconds) = timetuple 519 520 assert 0 <= seconds, \ 521 "Seconds must be larger than or equal to 0, but are %s" % seconds 522 assert 0 <= microseconds <= 999999, \ 523 "Microseconds must be 0-999999, but are %s" % microseconds 524 525 return float(seconds) + (float(microseconds) * 0.000001)
526 527
528 -def EpochNano():
529 """Return the current timestamp expressed as number of nanoseconds since the 530 unix epoch 531 532 @return: nanoseconds since the Unix epoch 533 534 """ 535 return int(time.time() * 1000000000)
536 537
538 -def FindMatch(data, name):
539 """Tries to find an item in a dictionary matching a name. 540 541 Callers have to ensure the data names aren't contradictory (e.g. a regexp 542 that matches a string). If the name isn't a direct key, all regular 543 expression objects in the dictionary are matched against it. 544 545 @type data: dict 546 @param data: Dictionary containing data 547 @type name: string 548 @param name: Name to look for 549 @rtype: tuple; (value in dictionary, matched groups as list) 550 551 """ 552 if name in data: 553 return (data[name], []) 554 555 for key, value in data.items(): 556 # Regex objects 557 if hasattr(key, "match"): 558 m = key.match(name) 559 if m: 560 return (value, list(m.groups())) 561 562 return None
563 564
565 -def GetMounts(filename=constants.PROC_MOUNTS):
566 """Returns the list of mounted filesystems. 567 568 This function is Linux-specific. 569 570 @param filename: path of mounts file (/proc/mounts by default) 571 @rtype: list of tuples 572 @return: list of mount entries (device, mountpoint, fstype, options) 573 574 """ 575 # TODO(iustin): investigate non-Linux options (e.g. via mount output) 576 data = [] 577 mountlines = ReadFile(filename).splitlines() 578 for line in mountlines: 579 device, mountpoint, fstype, options, _ = line.split(None, 4) 580 data.append((device, mountpoint, fstype, options)) 581 582 return data
583 584
585 -def SignalHandled(signums):
586 """Signal Handled decoration. 587 588 This special decorator installs a signal handler and then calls the target 589 function. The function must accept a 'signal_handlers' keyword argument, 590 which will contain a dict indexed by signal number, with SignalHandler 591 objects as values. 592 593 The decorator can be safely stacked with iself, to handle multiple signals 594 with different handlers. 595 596 @type signums: list 597 @param signums: signals to intercept 598 599 """ 600 def wrap(fn): 601 def sig_function(*args, **kwargs): 602 assert "signal_handlers" not in kwargs or \ 603 kwargs["signal_handlers"] is None or \ 604 isinstance(kwargs["signal_handlers"], dict), \ 605 "Wrong signal_handlers parameter in original function call" 606 if "signal_handlers" in kwargs and kwargs["signal_handlers"] is not None: 607 signal_handlers = kwargs["signal_handlers"] 608 else: 609 signal_handlers = {} 610 kwargs["signal_handlers"] = signal_handlers 611 sighandler = SignalHandler(signums) 612 try: 613 for sig in signums: 614 signal_handlers[sig] = sighandler 615 return fn(*args, **kwargs) 616 finally: 617 sighandler.Reset()
618 return sig_function 619 return wrap 620 621
622 -def TimeoutExpired(epoch, timeout, _time_fn=time.time):
623 """Checks whether a timeout has expired. 624 625 """ 626 return _time_fn() > (epoch + timeout)
627 628
629 -class SignalWakeupFd(object):
630 try: 631 # This is only supported in Python 2.5 and above (some distributions 632 # backported it to Python 2.4) 633 _set_wakeup_fd_fn = signal.set_wakeup_fd 634 except AttributeError: 635 # Not supported 636
637 - def _SetWakeupFd(self, _): # pylint: disable=R0201
638 return -1
639 else: 640
641 - def _SetWakeupFd(self, fd):
642 return self._set_wakeup_fd_fn(fd)
643
644 - def __init__(self):
645 """Initializes this class. 646 647 """ 648 (read_fd, write_fd) = os.pipe() 649 650 # Once these succeeded, the file descriptors will be closed automatically. 651 # Buffer size 0 is important, otherwise .read() with a specified length 652 # might buffer data and the file descriptors won't be marked readable. 653 self._read_fh = os.fdopen(read_fd, "r", 0) 654 self._write_fh = os.fdopen(write_fd, "w", 0) 655 656 self._previous = self._SetWakeupFd(self._write_fh.fileno()) 657 658 # Utility functions 659 self.fileno = self._read_fh.fileno 660 self.read = self._read_fh.read
661
662 - def Reset(self):
663 """Restores the previous wakeup file descriptor. 664 665 """ 666 if hasattr(self, "_previous") and self._previous is not None: 667 self._SetWakeupFd(self._previous) 668 self._previous = None
669
670 - def Notify(self):
671 """Notifies the wakeup file descriptor. 672 673 """ 674 self._write_fh.write("\0")
675
676 - def __del__(self):
677 """Called before object deletion. 678 679 """ 680 self.Reset()
681 682
683 -class SignalHandler(object):
684 """Generic signal handler class. 685 686 It automatically restores the original handler when deconstructed or 687 when L{Reset} is called. You can either pass your own handler 688 function in or query the L{called} attribute to detect whether the 689 signal was sent. 690 691 @type signum: list 692 @ivar signum: the signals we handle 693 @type called: boolean 694 @ivar called: tracks whether any of the signals have been raised 695 696 """
697 - def __init__(self, signum, handler_fn=None, wakeup=None):
698 """Constructs a new SignalHandler instance. 699 700 @type signum: int or list of ints 701 @param signum: Single signal number or set of signal numbers 702 @type handler_fn: callable 703 @param handler_fn: Signal handling function 704 705 """ 706 assert handler_fn is None or callable(handler_fn) 707 708 self.signum = set(signum) 709 self.called = False 710 711 self._handler_fn = handler_fn 712 self._wakeup = wakeup 713 714 self._previous = {} 715 try: 716 for signum in self.signum: 717 # Setup handler 718 prev_handler = signal.signal(signum, self._HandleSignal) 719 try: 720 self._previous[signum] = prev_handler 721 except: 722 # Restore previous handler 723 signal.signal(signum, prev_handler) 724 raise 725 except: 726 # Reset all handlers 727 self.Reset() 728 # Here we have a race condition: a handler may have already been called, 729 # but there's not much we can do about it at this point. 730 raise
731
732 - def __del__(self):
733 self.Reset()
734
735 - def Reset(self):
736 """Restore previous handler. 737 738 This will reset all the signals to their previous handlers. 739 740 """ 741 for signum, prev_handler in self._previous.items(): 742 signal.signal(signum, prev_handler) 743 # If successful, remove from dict 744 del self._previous[signum]
745
746 - def Clear(self):
747 """Unsets the L{called} flag. 748 749 This function can be used in case a signal may arrive several times. 750 751 """ 752 self.called = False
753
754 - def _HandleSignal(self, signum, frame):
755 """Actual signal handling function. 756 757 """ 758 # This is not nice and not absolutely atomic, but it appears to be the only 759 # solution in Python -- there are no atomic types. 760 self.called = True 761 762 if self._wakeup: 763 # Notify whoever is interested in signals 764 self._wakeup.Notify() 765 766 if self._handler_fn: 767 self._handler_fn(signum, frame)
768 769
770 -class FieldSet(object):
771 """A simple field set. 772 773 Among the features are: 774 - checking if a string is among a list of static string or regex objects 775 - checking if a whole list of string matches 776 - returning the matching groups from a regex match 777 778 Internally, all fields are held as regular expression objects. 779 780 """
781 - def __init__(self, *items):
782 self.items = [re.compile("^%s$" % value) for value in items]
783
784 - def Extend(self, other_set):
785 """Extend the field set with the items from another one""" 786 self.items.extend(other_set.items)
787
788 - def Matches(self, field):
789 """Checks if a field matches the current set 790 791 @type field: str 792 @param field: the string to match 793 @return: either None or a regular expression match object 794 795 """ 796 for m in itertools.ifilter(None, (val.match(field) for val in self.items)): 797 return m 798 return None
799
800 - def NonMatching(self, items):
801 """Returns the list of fields not matching the current set 802 803 @type items: list 804 @param items: the list of fields to check 805 @rtype: list 806 @return: list of non-matching fields 807 808 """ 809 return [val for val in items if not self.Matches(val)]
810 811
812 -def ValidateDeviceNames(kind, container):
813 """Validate instance device names. 814 815 Check that a device container contains only unique and valid names. 816 817 @type kind: string 818 @param kind: One-word item description 819 @type container: list 820 @param container: Container containing the devices 821 822 """ 823 824 valid = [] 825 for device in container: 826 if isinstance(device, dict): 827 if kind == "NIC": 828 name = device.get(constants.INIC_NAME, None) 829 elif kind == "disk": 830 name = device.get(constants.IDISK_NAME, None) 831 else: 832 raise errors.OpPrereqError("Invalid container kind '%s'" % kind, 833 errors.ECODE_INVAL) 834 else: 835 name = device.name 836 # Check that a device name is not the UUID of another device 837 valid.append(device.uuid) 838 839 try: 840 int(name) 841 except (ValueError, TypeError): 842 pass 843 else: 844 raise errors.OpPrereqError("Invalid name '%s'. Purely numeric %s names" 845 " are not allowed" % (name, kind), 846 errors.ECODE_INVAL) 847 848 if name is not None and name.lower() != constants.VALUE_NONE: 849 if name in valid: 850 raise errors.OpPrereqError("%s name '%s' already used" % (kind, name), 851 errors.ECODE_NOTUNIQUE) 852 else: 853 valid.append(name)
854