Check that device names are unique and valid
[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 FindMatch(data, name):
529   """Tries to find an item in a dictionary matching a name.
530
531   Callers have to ensure the data names aren't contradictory (e.g. a regexp
532   that matches a string). If the name isn't a direct key, all regular
533   expression objects in the dictionary are matched against it.
534
535   @type data: dict
536   @param data: Dictionary containing data
537   @type name: string
538   @param name: Name to look for
539   @rtype: tuple; (value in dictionary, matched groups as list)
540
541   """
542   if name in data:
543     return (data[name], [])
544
545   for key, value in data.items():
546     # Regex objects
547     if hasattr(key, "match"):
548       m = key.match(name)
549       if m:
550         return (value, list(m.groups()))
551
552   return None
553
554
555 def GetMounts(filename=constants.PROC_MOUNTS):
556   """Returns the list of mounted filesystems.
557
558   This function is Linux-specific.
559
560   @param filename: path of mounts file (/proc/mounts by default)
561   @rtype: list of tuples
562   @return: list of mount entries (device, mountpoint, fstype, options)
563
564   """
565   # TODO(iustin): investigate non-Linux options (e.g. via mount output)
566   data = []
567   mountlines = ReadFile(filename).splitlines()
568   for line in mountlines:
569     device, mountpoint, fstype, options, _ = line.split(None, 4)
570     data.append((device, mountpoint, fstype, options))
571
572   return data
573
574
575 def SignalHandled(signums):
576   """Signal Handled decoration.
577
578   This special decorator installs a signal handler and then calls the target
579   function. The function must accept a 'signal_handlers' keyword argument,
580   which will contain a dict indexed by signal number, with SignalHandler
581   objects as values.
582
583   The decorator can be safely stacked with iself, to handle multiple signals
584   with different handlers.
585
586   @type signums: list
587   @param signums: signals to intercept
588
589   """
590   def wrap(fn):
591     def sig_function(*args, **kwargs):
592       assert "signal_handlers" not in kwargs or \
593              kwargs["signal_handlers"] is None or \
594              isinstance(kwargs["signal_handlers"], dict), \
595              "Wrong signal_handlers parameter in original function call"
596       if "signal_handlers" in kwargs and kwargs["signal_handlers"] is not None:
597         signal_handlers = kwargs["signal_handlers"]
598       else:
599         signal_handlers = {}
600         kwargs["signal_handlers"] = signal_handlers
601       sighandler = SignalHandler(signums)
602       try:
603         for sig in signums:
604           signal_handlers[sig] = sighandler
605         return fn(*args, **kwargs)
606       finally:
607         sighandler.Reset()
608     return sig_function
609   return wrap
610
611
612 def TimeoutExpired(epoch, timeout, _time_fn=time.time):
613   """Checks whether a timeout has expired.
614
615   """
616   return _time_fn() > (epoch + timeout)
617
618
619 class SignalWakeupFd(object):
620   try:
621     # This is only supported in Python 2.5 and above (some distributions
622     # backported it to Python 2.4)
623     _set_wakeup_fd_fn = signal.set_wakeup_fd
624   except AttributeError:
625     # Not supported
626
627     def _SetWakeupFd(self, _): # pylint: disable=R0201
628       return -1
629   else:
630
631     def _SetWakeupFd(self, fd):
632       return self._set_wakeup_fd_fn(fd)
633
634   def __init__(self):
635     """Initializes this class.
636
637     """
638     (read_fd, write_fd) = os.pipe()
639
640     # Once these succeeded, the file descriptors will be closed automatically.
641     # Buffer size 0 is important, otherwise .read() with a specified length
642     # might buffer data and the file descriptors won't be marked readable.
643     self._read_fh = os.fdopen(read_fd, "r", 0)
644     self._write_fh = os.fdopen(write_fd, "w", 0)
645
646     self._previous = self._SetWakeupFd(self._write_fh.fileno())
647
648     # Utility functions
649     self.fileno = self._read_fh.fileno
650     self.read = self._read_fh.read
651
652   def Reset(self):
653     """Restores the previous wakeup file descriptor.
654
655     """
656     if hasattr(self, "_previous") and self._previous is not None:
657       self._SetWakeupFd(self._previous)
658       self._previous = None
659
660   def Notify(self):
661     """Notifies the wakeup file descriptor.
662
663     """
664     self._write_fh.write("\0")
665
666   def __del__(self):
667     """Called before object deletion.
668
669     """
670     self.Reset()
671
672
673 class SignalHandler(object):
674   """Generic signal handler class.
675
676   It automatically restores the original handler when deconstructed or
677   when L{Reset} is called. You can either pass your own handler
678   function in or query the L{called} attribute to detect whether the
679   signal was sent.
680
681   @type signum: list
682   @ivar signum: the signals we handle
683   @type called: boolean
684   @ivar called: tracks whether any of the signals have been raised
685
686   """
687   def __init__(self, signum, handler_fn=None, wakeup=None):
688     """Constructs a new SignalHandler instance.
689
690     @type signum: int or list of ints
691     @param signum: Single signal number or set of signal numbers
692     @type handler_fn: callable
693     @param handler_fn: Signal handling function
694
695     """
696     assert handler_fn is None or callable(handler_fn)
697
698     self.signum = set(signum)
699     self.called = False
700
701     self._handler_fn = handler_fn
702     self._wakeup = wakeup
703
704     self._previous = {}
705     try:
706       for signum in self.signum:
707         # Setup handler
708         prev_handler = signal.signal(signum, self._HandleSignal)
709         try:
710           self._previous[signum] = prev_handler
711         except:
712           # Restore previous handler
713           signal.signal(signum, prev_handler)
714           raise
715     except:
716       # Reset all handlers
717       self.Reset()
718       # Here we have a race condition: a handler may have already been called,
719       # but there's not much we can do about it at this point.
720       raise
721
722   def __del__(self):
723     self.Reset()
724
725   def Reset(self):
726     """Restore previous handler.
727
728     This will reset all the signals to their previous handlers.
729
730     """
731     for signum, prev_handler in self._previous.items():
732       signal.signal(signum, prev_handler)
733       # If successful, remove from dict
734       del self._previous[signum]
735
736   def Clear(self):
737     """Unsets the L{called} flag.
738
739     This function can be used in case a signal may arrive several times.
740
741     """
742     self.called = False
743
744   def _HandleSignal(self, signum, frame):
745     """Actual signal handling function.
746
747     """
748     # This is not nice and not absolutely atomic, but it appears to be the only
749     # solution in Python -- there are no atomic types.
750     self.called = True
751
752     if self._wakeup:
753       # Notify whoever is interested in signals
754       self._wakeup.Notify()
755
756     if self._handler_fn:
757       self._handler_fn(signum, frame)
758
759
760 class FieldSet(object):
761   """A simple field set.
762
763   Among the features are:
764     - checking if a string is among a list of static string or regex objects
765     - checking if a whole list of string matches
766     - returning the matching groups from a regex match
767
768   Internally, all fields are held as regular expression objects.
769
770   """
771   def __init__(self, *items):
772     self.items = [re.compile("^%s$" % value) for value in items]
773
774   def Extend(self, other_set):
775     """Extend the field set with the items from another one"""
776     self.items.extend(other_set.items)
777
778   def Matches(self, field):
779     """Checks if a field matches the current set
780
781     @type field: str
782     @param field: the string to match
783     @return: either None or a regular expression match object
784
785     """
786     for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
787       return m
788     return None
789
790   def NonMatching(self, items):
791     """Returns the list of fields not matching the current set
792
793     @type items: list
794     @param items: the list of fields to check
795     @rtype: list
796     @return: list of non-matching fields
797
798     """
799     return [val for val in items if not self.Matches(val)]
800
801
802 def ValidateDeviceNames(kind, container):
803   """Validate instance device names.
804
805   Check that a device container contains only unique and valid names.
806
807   @type kind: string
808   @param kind: One-word item description
809   @type container: list
810   @param container: Container containing the devices
811
812   """
813
814   valid = []
815   for device in container:
816     if isinstance(device, dict):
817       if kind == "NIC":
818         name = device.get(constants.INIC_NAME, None)
819       elif kind == "disk":
820         name = device.get(constants.IDISK_NAME, None)
821       else:
822         raise errors.OpPrereqError("Invalid container kind '%s'" % kind,
823                                    errors.ECODE_INVAL)
824     else:
825       name = device.name
826       # Check that a device name is not the UUID of another device
827       valid.append(device.uuid)
828
829     try:
830       int(name)
831     except (ValueError, TypeError):
832       pass
833     else:
834       raise errors.OpPrereqError("Invalid name '%s'. Purely numeric %s names"
835                                  " are not allowed" % (name, kind),
836                                  errors.ECODE_INVAL)
837
838     if name is not None and name.lower() != constants.VALUE_NONE:
839       if name in valid:
840         raise errors.OpPrereqError("%s name '%s' already used" % (kind, name),
841                                    errors.ECODE_NOTUNIQUE)
842       else:
843         valid.append(name)