Version bump for 2.8.4 and NEWS update
[ganeti-local] / lib / utils / __init__.py
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)