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