Statistics
| Branch: | Tag: | Revision:

root / lib / utils / __init__.py @ 07e68848

History | View | Annotate | Download (23.9 kB)

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.version import *
60
from ganeti.utils.x509 import *
61

    
62

    
63
_VALID_SERVICE_NAME_RE = re.compile("^[-_.a-zA-Z0-9]{1,128}$")
64

    
65
UUID_RE = re.compile(constants.UUID_REGEX)
66

    
67

    
68
def ForceDictType(target, key_types, allowed_values=None):
69
  """Force the values of a dict to have certain types.
70

71
  @type target: dict
72
  @param target: the dict to update
73
  @type key_types: dict
74
  @param key_types: dict mapping target dict keys to types
75
                    in constants.ENFORCEABLE_TYPES
76
  @type allowed_values: list
77
  @keyword allowed_values: list of specially allowed values
78

79
  """
80
  if allowed_values is None:
81
    allowed_values = []
82

    
83
  if not isinstance(target, dict):
84
    msg = "Expected dictionary, got '%s'" % target
85
    raise errors.TypeEnforcementError(msg)
86

    
87
  for key in target:
88
    if key not in key_types:
89
      msg = "Unknown parameter '%s'" % key
90
      raise errors.TypeEnforcementError(msg)
91

    
92
    if target[key] in allowed_values:
93
      continue
94

    
95
    ktype = key_types[key]
96
    if ktype not in constants.ENFORCEABLE_TYPES:
97
      msg = "'%s' has non-enforceable type %s" % (key, ktype)
98
      raise errors.ProgrammerError(msg)
99

    
100
    if ktype in (constants.VTYPE_STRING, constants.VTYPE_MAYBE_STRING):
101
      if target[key] is None and ktype == constants.VTYPE_MAYBE_STRING:
102
        pass
103
      elif not isinstance(target[key], basestring):
104
        if isinstance(target[key], bool) and not target[key]:
105
          target[key] = ""
106
        else:
107
          msg = "'%s' (value %s) is not a valid string" % (key, target[key])
108
          raise errors.TypeEnforcementError(msg)
109
    elif ktype == constants.VTYPE_BOOL:
110
      if isinstance(target[key], basestring) and target[key]:
111
        if target[key].lower() == constants.VALUE_FALSE:
112
          target[key] = False
113
        elif target[key].lower() == constants.VALUE_TRUE:
114
          target[key] = True
115
        else:
116
          msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
117
          raise errors.TypeEnforcementError(msg)
118
      elif target[key]:
119
        target[key] = True
120
      else:
121
        target[key] = False
122
    elif ktype == constants.VTYPE_SIZE:
123
      try:
124
        target[key] = ParseUnit(target[key])
125
      except errors.UnitParseError, err:
126
        msg = "'%s' (value %s) is not a valid size. error: %s" % \
127
              (key, target[key], err)
128
        raise errors.TypeEnforcementError(msg)
129
    elif ktype == constants.VTYPE_INT:
130
      try:
131
        target[key] = int(target[key])
132
      except (ValueError, TypeError):
133
        msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
134
        raise errors.TypeEnforcementError(msg)
135

    
136

    
137
def ValidateServiceName(name):
138
  """Validate the given service name.
139

140
  @type name: number or string
141
  @param name: Service name or port specification
142

143
  """
144
  try:
145
    numport = int(name)
146
  except (ValueError, TypeError):
147
    # Non-numeric service name
148
    valid = _VALID_SERVICE_NAME_RE.match(name)
149
  else:
150
    # Numeric port (protocols other than TCP or UDP might need adjustments
151
    # here)
152
    valid = (numport >= 0 and numport < (1 << 16))
153

    
154
  if not valid:
155
    raise errors.OpPrereqError("Invalid service name '%s'" % name,
156
                               errors.ECODE_INVAL)
157

    
158
  return name
159

    
160

    
161
def _ComputeMissingKeys(key_path, options, defaults):
162
  """Helper functions to compute which keys a invalid.
163

164
  @param key_path: The current key path (if any)
165
  @param options: The user provided options
166
  @param defaults: The default dictionary
167
  @return: A list of invalid keys
168

169
  """
170
  defaults_keys = frozenset(defaults.keys())
171
  invalid = []
172
  for key, value in options.items():
173
    if key_path:
174
      new_path = "%s/%s" % (key_path, key)
175
    else:
176
      new_path = key
177

    
178
    if key not in defaults_keys:
179
      invalid.append(new_path)
180
    elif isinstance(value, dict):
181
      invalid.extend(_ComputeMissingKeys(new_path, value, defaults[key]))
182

    
183
  return invalid
184

    
185

    
186
def VerifyDictOptions(options, defaults):
187
  """Verify a dict has only keys set which also are in the defaults dict.
188

189
  @param options: The user provided options
190
  @param defaults: The default dictionary
191
  @raise error.OpPrereqError: If one of the keys is not supported
192

193
  """
194
  invalid = _ComputeMissingKeys("", options, defaults)
195

    
196
  if invalid:
197
    raise errors.OpPrereqError("Provided option keys not supported: %s" %
198
                               CommaJoin(invalid), errors.ECODE_INVAL)
199

    
200

    
201
def ListVolumeGroups():
202
  """List volume groups and their size
203

204
  @rtype: dict
205
  @return:
206
       Dictionary with keys volume name and values
207
       the size of the volume
208

209
  """
210
  command = "vgs --noheadings --units m --nosuffix -o name,size"
211
  result = RunCmd(command)
212
  retval = {}
213
  if result.failed:
214
    return retval
215

    
216
  for line in result.stdout.splitlines():
217
    try:
218
      name, size = line.split()
219
      size = int(float(size))
220
    except (IndexError, ValueError), err:
221
      logging.error("Invalid output from vgs (%s): %s", err, line)
222
      continue
223

    
224
    retval[name] = size
225

    
226
  return retval
227

    
228

    
229
def BridgeExists(bridge):
230
  """Check whether the given bridge exists in the system
231

232
  @type bridge: str
233
  @param bridge: the bridge name to check
234
  @rtype: boolean
235
  @return: True if it does
236

237
  """
238
  return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
239

    
240

    
241
def TryConvert(fn, val):
242
  """Try to convert a value ignoring errors.
243

244
  This function tries to apply function I{fn} to I{val}. If no
245
  C{ValueError} or C{TypeError} exceptions are raised, it will return
246
  the result, else it will return the original value. Any other
247
  exceptions are propagated to the caller.
248

249
  @type fn: callable
250
  @param fn: function to apply to the value
251
  @param val: the value to be converted
252
  @return: The converted value if the conversion was successful,
253
      otherwise the original value.
254

255
  """
256
  try:
257
    nv = fn(val)
258
  except (ValueError, TypeError):
259
    nv = val
260
  return nv
261

    
262

    
263
def ParseCpuMask(cpu_mask):
264
  """Parse a CPU mask definition and return the list of CPU IDs.
265

266
  CPU mask format: comma-separated list of CPU IDs
267
  or dash-separated ID ranges
268
  Example: "0-2,5" -> "0,1,2,5"
269

270
  @type cpu_mask: str
271
  @param cpu_mask: CPU mask definition
272
  @rtype: list of int
273
  @return: list of CPU IDs
274

275
  """
276
  if not cpu_mask:
277
    return []
278
  cpu_list = []
279
  for range_def in cpu_mask.split(","):
280
    boundaries = range_def.split("-")
281
    n_elements = len(boundaries)
282
    if n_elements > 2:
283
      raise errors.ParseError("Invalid CPU ID range definition"
284
                              " (only one hyphen allowed): %s" % range_def)
285
    try:
286
      lower = int(boundaries[0])
287
    except (ValueError, TypeError), err:
288
      raise errors.ParseError("Invalid CPU ID value for lower boundary of"
289
                              " CPU ID range: %s" % str(err))
290
    try:
291
      higher = int(boundaries[-1])
292
    except (ValueError, TypeError), err:
293
      raise errors.ParseError("Invalid CPU ID value for higher boundary of"
294
                              " CPU ID range: %s" % str(err))
295
    if lower > higher:
296
      raise errors.ParseError("Invalid CPU ID range definition"
297
                              " (%d > %d): %s" % (lower, higher, range_def))
298
    cpu_list.extend(range(lower, higher + 1))
299
  return cpu_list
300

    
301

    
302
def ParseMultiCpuMask(cpu_mask):
303
  """Parse a multiple CPU mask definition and return the list of CPU IDs.
304

305
  CPU mask format: colon-separated list of comma-separated list of CPU IDs
306
  or dash-separated ID ranges, with optional "all" as CPU value
307
  Example: "0-2,5:all:1,5,6:2" -> [ [ 0,1,2,5 ], [ -1 ], [ 1, 5, 6 ], [ 2 ] ]
308

309
  @type cpu_mask: str
310
  @param cpu_mask: multiple CPU mask definition
311
  @rtype: list of lists of int
312
  @return: list of lists of CPU IDs
313

314
  """
315
  if not cpu_mask:
316
    return []
317
  cpu_list = []
318
  for range_def in cpu_mask.split(constants.CPU_PINNING_SEP):
319
    if range_def == constants.CPU_PINNING_ALL:
320
      cpu_list.append([constants.CPU_PINNING_ALL_VAL, ])
321
    else:
322
      # Uniquify and sort the list before adding
323
      cpu_list.append(sorted(set(ParseCpuMask(range_def))))
324

    
325
  return cpu_list
326

    
327

    
328
def GetHomeDir(user, default=None):
329
  """Try to get the homedir of the given user.
330

331
  The user can be passed either as a string (denoting the name) or as
332
  an integer (denoting the user id). If the user is not found, the
333
  C{default} argument is returned, which defaults to C{None}.
334

335
  """
336
  try:
337
    if isinstance(user, basestring):
338
      result = pwd.getpwnam(user)
339
    elif isinstance(user, (int, long)):
340
      result = pwd.getpwuid(user)
341
    else:
342
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
343
                                   type(user))
344
  except KeyError:
345
    return default
346
  return result.pw_dir
347

    
348

    
349
def FirstFree(seq, base=0):
350
  """Returns the first non-existing integer from seq.
351

352
  The seq argument should be a sorted list of positive integers. The
353
  first time the index of an element is smaller than the element
354
  value, the index will be returned.
355

356
  The base argument is used to start at a different offset,
357
  i.e. C{[3, 4, 6]} with I{offset=3} will return 5.
358

359
  Example: C{[0, 1, 3]} will return I{2}.
360

361
  @type seq: sequence
362
  @param seq: the sequence to be analyzed.
363
  @type base: int
364
  @param base: use this value as the base index of the sequence
365
  @rtype: int
366
  @return: the first non-used index in the sequence
367

368
  """
369
  for idx, elem in enumerate(seq):
370
    assert elem >= base, "Passed element is higher than base offset"
371
    if elem > idx + base:
372
      # idx is not used
373
      return idx + base
374
  return None
375

    
376

    
377
def SingleWaitForFdCondition(fdobj, event, timeout):
378
  """Waits for a condition to occur on the socket.
379

380
  Immediately returns at the first interruption.
381

382
  @type fdobj: integer or object supporting a fileno() method
383
  @param fdobj: entity to wait for events on
384
  @type event: integer
385
  @param event: ORed condition (see select module)
386
  @type timeout: float or None
387
  @param timeout: Timeout in seconds
388
  @rtype: int or None
389
  @return: None for timeout, otherwise occured conditions
390

391
  """
392
  check = (event | select.POLLPRI |
393
           select.POLLNVAL | select.POLLHUP | select.POLLERR)
394

    
395
  if timeout is not None:
396
    # Poller object expects milliseconds
397
    timeout *= 1000
398

    
399
  poller = select.poll()
400
  poller.register(fdobj, event)
401
  try:
402
    # TODO: If the main thread receives a signal and we have no timeout, we
403
    # could wait forever. This should check a global "quit" flag or something
404
    # every so often.
405
    io_events = poller.poll(timeout)
406
  except select.error, err:
407
    if err[0] != errno.EINTR:
408
      raise
409
    io_events = []
410
  if io_events and io_events[0][1] & check:
411
    return io_events[0][1]
412
  else:
413
    return None
414

    
415

    
416
class FdConditionWaiterHelper(object):
417
  """Retry helper for WaitForFdCondition.
418

419
  This class contains the retried and wait functions that make sure
420
  WaitForFdCondition can continue waiting until the timeout is actually
421
  expired.
422

423
  """
424

    
425
  def __init__(self, timeout):
426
    self.timeout = timeout
427

    
428
  def Poll(self, fdobj, event):
429
    result = SingleWaitForFdCondition(fdobj, event, self.timeout)
430
    if result is None:
431
      raise RetryAgain()
432
    else:
433
      return result
434

    
435
  def UpdateTimeout(self, timeout):
436
    self.timeout = timeout
437

    
438

    
439
def WaitForFdCondition(fdobj, event, timeout):
440
  """Waits for a condition to occur on the socket.
441

442
  Retries until the timeout is expired, even if interrupted.
443

444
  @type fdobj: integer or object supporting a fileno() method
445
  @param fdobj: entity to wait for events on
446
  @type event: integer
447
  @param event: ORed condition (see select module)
448
  @type timeout: float or None
449
  @param timeout: Timeout in seconds
450
  @rtype: int or None
451
  @return: None for timeout, otherwise occured conditions
452

453
  """
454
  if timeout is not None:
455
    retrywaiter = FdConditionWaiterHelper(timeout)
456
    try:
457
      result = Retry(retrywaiter.Poll, RETRY_REMAINING_TIME, timeout,
458
                     args=(fdobj, event), wait_fn=retrywaiter.UpdateTimeout)
459
    except RetryTimeout:
460
      result = None
461
  else:
462
    result = None
463
    while result is None:
464
      result = SingleWaitForFdCondition(fdobj, event, timeout)
465
  return result
466

    
467

    
468
def EnsureDaemon(name):
469
  """Check for and start daemon if not alive.
470

471
  """
472
  result = RunCmd([pathutils.DAEMON_UTIL, "check-and-start", name])
473
  if result.failed:
474
    logging.error("Can't start daemon '%s', failure %s, output: %s",
475
                  name, result.fail_reason, result.output)
476
    return False
477

    
478
  return True
479

    
480

    
481
def StopDaemon(name):
482
  """Stop daemon
483

484
  """
485
  result = RunCmd([pathutils.DAEMON_UTIL, "stop", name])
486
  if result.failed:
487
    logging.error("Can't stop daemon '%s', failure %s, output: %s",
488
                  name, result.fail_reason, result.output)
489
    return False
490

    
491
  return True
492

    
493

    
494
def SplitTime(value):
495
  """Splits time as floating point number into a tuple.
496

497
  @param value: Time in seconds
498
  @type value: int or float
499
  @return: Tuple containing (seconds, microseconds)
500

501
  """
502
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
503

    
504
  assert 0 <= seconds, \
505
    "Seconds must be larger than or equal to 0, but are %s" % seconds
506
  assert 0 <= microseconds <= 999999, \
507
    "Microseconds must be 0-999999, but are %s" % microseconds
508

    
509
  return (int(seconds), int(microseconds))
510

    
511

    
512
def MergeTime(timetuple):
513
  """Merges a tuple into time as a floating point number.
514

515
  @param timetuple: Time as tuple, (seconds, microseconds)
516
  @type timetuple: tuple
517
  @return: Time as a floating point number expressed in seconds
518

519
  """
520
  (seconds, microseconds) = timetuple
521

    
522
  assert 0 <= seconds, \
523
    "Seconds must be larger than or equal to 0, but are %s" % seconds
524
  assert 0 <= microseconds <= 999999, \
525
    "Microseconds must be 0-999999, but are %s" % microseconds
526

    
527
  return float(seconds) + (float(microseconds) * 0.000001)
528

    
529

    
530
def EpochNano():
531
  """Return the current timestamp expressed as number of nanoseconds since the
532
  unix epoch
533

534
  @return: nanoseconds since the Unix epoch
535

536
  """
537
  return int(time.time() * 1000000000)
538

    
539

    
540
def FindMatch(data, name):
541
  """Tries to find an item in a dictionary matching a name.
542

543
  Callers have to ensure the data names aren't contradictory (e.g. a regexp
544
  that matches a string). If the name isn't a direct key, all regular
545
  expression objects in the dictionary are matched against it.
546

547
  @type data: dict
548
  @param data: Dictionary containing data
549
  @type name: string
550
  @param name: Name to look for
551
  @rtype: tuple; (value in dictionary, matched groups as list)
552

553
  """
554
  if name in data:
555
    return (data[name], [])
556

    
557
  for key, value in data.items():
558
    # Regex objects
559
    if hasattr(key, "match"):
560
      m = key.match(name)
561
      if m:
562
        return (value, list(m.groups()))
563

    
564
  return None
565

    
566

    
567
def GetMounts(filename=constants.PROC_MOUNTS):
568
  """Returns the list of mounted filesystems.
569

570
  This function is Linux-specific.
571

572
  @param filename: path of mounts file (/proc/mounts by default)
573
  @rtype: list of tuples
574
  @return: list of mount entries (device, mountpoint, fstype, options)
575

576
  """
577
  # TODO(iustin): investigate non-Linux options (e.g. via mount output)
578
  data = []
579
  mountlines = ReadFile(filename).splitlines()
580
  for line in mountlines:
581
    device, mountpoint, fstype, options, _ = line.split(None, 4)
582
    data.append((device, mountpoint, fstype, options))
583

    
584
  return data
585

    
586

    
587
def SignalHandled(signums):
588
  """Signal Handled decoration.
589

590
  This special decorator installs a signal handler and then calls the target
591
  function. The function must accept a 'signal_handlers' keyword argument,
592
  which will contain a dict indexed by signal number, with SignalHandler
593
  objects as values.
594

595
  The decorator can be safely stacked with iself, to handle multiple signals
596
  with different handlers.
597

598
  @type signums: list
599
  @param signums: signals to intercept
600

601
  """
602
  def wrap(fn):
603
    def sig_function(*args, **kwargs):
604
      assert "signal_handlers" not in kwargs or \
605
             kwargs["signal_handlers"] is None or \
606
             isinstance(kwargs["signal_handlers"], dict), \
607
             "Wrong signal_handlers parameter in original function call"
608
      if "signal_handlers" in kwargs and kwargs["signal_handlers"] is not None:
609
        signal_handlers = kwargs["signal_handlers"]
610
      else:
611
        signal_handlers = {}
612
        kwargs["signal_handlers"] = signal_handlers
613
      sighandler = SignalHandler(signums)
614
      try:
615
        for sig in signums:
616
          signal_handlers[sig] = sighandler
617
        return fn(*args, **kwargs)
618
      finally:
619
        sighandler.Reset()
620
    return sig_function
621
  return wrap
622

    
623

    
624
def TimeoutExpired(epoch, timeout, _time_fn=time.time):
625
  """Checks whether a timeout has expired.
626

627
  """
628
  return _time_fn() > (epoch + timeout)
629

    
630

    
631
class SignalWakeupFd(object):
632
  try:
633
    # This is only supported in Python 2.5 and above (some distributions
634
    # backported it to Python 2.4)
635
    _set_wakeup_fd_fn = signal.set_wakeup_fd
636
  except AttributeError:
637
    # Not supported
638

    
639
    def _SetWakeupFd(self, _): # pylint: disable=R0201
640
      return -1
641
  else:
642

    
643
    def _SetWakeupFd(self, fd):
644
      return self._set_wakeup_fd_fn(fd)
645

    
646
  def __init__(self):
647
    """Initializes this class.
648

649
    """
650
    (read_fd, write_fd) = os.pipe()
651

    
652
    # Once these succeeded, the file descriptors will be closed automatically.
653
    # Buffer size 0 is important, otherwise .read() with a specified length
654
    # might buffer data and the file descriptors won't be marked readable.
655
    self._read_fh = os.fdopen(read_fd, "r", 0)
656
    self._write_fh = os.fdopen(write_fd, "w", 0)
657

    
658
    self._previous = self._SetWakeupFd(self._write_fh.fileno())
659

    
660
    # Utility functions
661
    self.fileno = self._read_fh.fileno
662
    self.read = self._read_fh.read
663

    
664
  def Reset(self):
665
    """Restores the previous wakeup file descriptor.
666

667
    """
668
    if hasattr(self, "_previous") and self._previous is not None:
669
      self._SetWakeupFd(self._previous)
670
      self._previous = None
671

    
672
  def Notify(self):
673
    """Notifies the wakeup file descriptor.
674

675
    """
676
    self._write_fh.write(chr(0))
677

    
678
  def __del__(self):
679
    """Called before object deletion.
680

681
    """
682
    self.Reset()
683

    
684

    
685
class SignalHandler(object):
686
  """Generic signal handler class.
687

688
  It automatically restores the original handler when deconstructed or
689
  when L{Reset} is called. You can either pass your own handler
690
  function in or query the L{called} attribute to detect whether the
691
  signal was sent.
692

693
  @type signum: list
694
  @ivar signum: the signals we handle
695
  @type called: boolean
696
  @ivar called: tracks whether any of the signals have been raised
697

698
  """
699
  def __init__(self, signum, handler_fn=None, wakeup=None):
700
    """Constructs a new SignalHandler instance.
701

702
    @type signum: int or list of ints
703
    @param signum: Single signal number or set of signal numbers
704
    @type handler_fn: callable
705
    @param handler_fn: Signal handling function
706

707
    """
708
    assert handler_fn is None or callable(handler_fn)
709

    
710
    self.signum = set(signum)
711
    self.called = False
712

    
713
    self._handler_fn = handler_fn
714
    self._wakeup = wakeup
715

    
716
    self._previous = {}
717
    try:
718
      for signum in self.signum:
719
        # Setup handler
720
        prev_handler = signal.signal(signum, self._HandleSignal)
721
        try:
722
          self._previous[signum] = prev_handler
723
        except:
724
          # Restore previous handler
725
          signal.signal(signum, prev_handler)
726
          raise
727
    except:
728
      # Reset all handlers
729
      self.Reset()
730
      # Here we have a race condition: a handler may have already been called,
731
      # but there's not much we can do about it at this point.
732
      raise
733

    
734
  def __del__(self):
735
    self.Reset()
736

    
737
  def Reset(self):
738
    """Restore previous handler.
739

740
    This will reset all the signals to their previous handlers.
741

742
    """
743
    for signum, prev_handler in self._previous.items():
744
      signal.signal(signum, prev_handler)
745
      # If successful, remove from dict
746
      del self._previous[signum]
747

    
748
  def Clear(self):
749
    """Unsets the L{called} flag.
750

751
    This function can be used in case a signal may arrive several times.
752

753
    """
754
    self.called = False
755

    
756
  def _HandleSignal(self, signum, frame):
757
    """Actual signal handling function.
758

759
    """
760
    # This is not nice and not absolutely atomic, but it appears to be the only
761
    # solution in Python -- there are no atomic types.
762
    self.called = True
763

    
764
    if self._wakeup:
765
      # Notify whoever is interested in signals
766
      self._wakeup.Notify()
767

    
768
    if self._handler_fn:
769
      self._handler_fn(signum, frame)
770

    
771

    
772
class FieldSet(object):
773
  """A simple field set.
774

775
  Among the features are:
776
    - checking if a string is among a list of static string or regex objects
777
    - checking if a whole list of string matches
778
    - returning the matching groups from a regex match
779

780
  Internally, all fields are held as regular expression objects.
781

782
  """
783
  def __init__(self, *items):
784
    self.items = [re.compile("^%s$" % value) for value in items]
785

    
786
  def Extend(self, other_set):
787
    """Extend the field set with the items from another one"""
788
    self.items.extend(other_set.items)
789

    
790
  def Matches(self, field):
791
    """Checks if a field matches the current set
792

793
    @type field: str
794
    @param field: the string to match
795
    @return: either None or a regular expression match object
796

797
    """
798
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
799
      return m
800
    return None
801

    
802
  def NonMatching(self, items):
803
    """Returns the list of fields not matching the current set
804

805
    @type items: list
806
    @param items: the list of fields to check
807
    @rtype: list
808
    @return: list of non-matching fields
809

810
    """
811
    return [val for val in items if not self.Matches(val)]
812

    
813

    
814
def ValidateDeviceNames(kind, container):
815
  """Validate instance device names.
816

817
  Check that a device container contains only unique and valid names.
818

819
  @type kind: string
820
  @param kind: One-word item description
821
  @type container: list
822
  @param container: Container containing the devices
823

824
  """
825

    
826
  valid = []
827
  for device in container:
828
    if isinstance(device, dict):
829
      if kind == "NIC":
830
        name = device.get(constants.INIC_NAME, None)
831
      elif kind == "disk":
832
        name = device.get(constants.IDISK_NAME, None)
833
      else:
834
        raise errors.OpPrereqError("Invalid container kind '%s'" % kind,
835
                                   errors.ECODE_INVAL)
836
    else:
837
      name = device.name
838
      # Check that a device name is not the UUID of another device
839
      valid.append(device.uuid)
840

    
841
    try:
842
      int(name)
843
    except (ValueError, TypeError):
844
      pass
845
    else:
846
      raise errors.OpPrereqError("Invalid name '%s'. Purely numeric %s names"
847
                                 " are not allowed" % (name, kind),
848
                                 errors.ECODE_INVAL)
849

    
850
    if name is not None and name.lower() != constants.VALUE_NONE:
851
      if name in valid:
852
        raise errors.OpPrereqError("%s name '%s' already used" % (kind, name),
853
                                   errors.ECODE_NOTUNIQUE)
854
      else:
855
        valid.append(name)