Statistics
| Branch: | Tag: | Revision:

root / lib / utils / __init__.py @ a8c2197d

History | View | Annotate | Download (24 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.livelock import *
51
from ganeti.utils.log import *
52
from ganeti.utils.lvm import *
53
from ganeti.utils.mlock import *
54
from ganeti.utils.nodesetup import *
55
from ganeti.utils.process import *
56
from ganeti.utils.retry import *
57
from ganeti.utils.security import *
58
from ganeti.utils.storage import *
59
from ganeti.utils.text import *
60
from ganeti.utils.wrapper import *
61
from ganeti.utils.version import *
62
from ganeti.utils.x509 import *
63

    
64

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

    
67
UUID_RE = re.compile(constants.UUID_REGEX)
68

    
69

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

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

81
  """
82
  if allowed_values is None:
83
    allowed_values = []
84

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

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

    
94
    if target[key] in allowed_values:
95
      continue
96

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

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

    
138

    
139
def ValidateServiceName(name):
140
  """Validate the given service name.
141

142
  @type name: number or string
143
  @param name: Service name or port specification
144

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

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

    
160
  return name
161

    
162

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

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

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

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

    
185
  return invalid
186

    
187

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

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

195
  """
196
  invalid = _ComputeMissingKeys("", options, defaults)
197

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

    
202

    
203
def ListVolumeGroups():
204
  """List volume groups and their size
205

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

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

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

    
226
    retval[name] = size
227

    
228
  return retval
229

    
230

    
231
def BridgeExists(bridge):
232
  """Check whether the given bridge exists in the system
233

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

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

    
242

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

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

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

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

    
264

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

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

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

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

    
303

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

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

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

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

    
327
  return cpu_list
328

    
329

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

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

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

    
350

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

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

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

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

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

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

    
378

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

382
  Immediately returns at the first interruption.
383

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

393
  """
394
  check = (event | select.POLLPRI |
395
           select.POLLNVAL | select.POLLHUP | select.POLLERR)
396

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

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

    
417

    
418
class FdConditionWaiterHelper(object):
419
  """Retry helper for WaitForFdCondition.
420

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

425
  """
426

    
427
  def __init__(self, timeout):
428
    self.timeout = timeout
429

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

    
437
  def UpdateTimeout(self, timeout):
438
    self.timeout = timeout
439

    
440

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

444
  Retries until the timeout is expired, even if interrupted.
445

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

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

    
469

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

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

    
480
  return True
481

    
482

    
483
def StopDaemon(name):
484
  """Stop daemon
485

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

    
493
  return True
494

    
495

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

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

503
  """
504
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
505

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

    
511
  return (int(seconds), int(microseconds))
512

    
513

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

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

521
  """
522
  (seconds, microseconds) = timetuple
523

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

    
529
  return float(seconds) + (float(microseconds) * 0.000001)
530

    
531

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

536
  @return: nanoseconds since the Unix epoch
537

538
  """
539
  return int(time.time() * 1000000000)
540

    
541

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

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

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

555
  """
556
  if name in data:
557
    return (data[name], [])
558

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

    
566
  return None
567

    
568

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

572
  This function is Linux-specific.
573

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

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

    
586
  return data
587

    
588

    
589
def SignalHandled(signums):
590
  """Signal Handled decoration.
591

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

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

600
  @type signums: list
601
  @param signums: signals to intercept
602

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

    
625

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

629
  """
630
  return _time_fn() > (epoch + timeout)
631

    
632

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

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

    
645
    def _SetWakeupFd(self, fd):
646
      return self._set_wakeup_fd_fn(fd)
647

    
648
  def __init__(self):
649
    """Initializes this class.
650

651
    """
652
    (read_fd, write_fd) = os.pipe()
653

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

    
660
    self._previous = self._SetWakeupFd(self._write_fh.fileno())
661

    
662
    # Utility functions
663
    self.fileno = self._read_fh.fileno
664
    self.read = self._read_fh.read
665

    
666
  def Reset(self):
667
    """Restores the previous wakeup file descriptor.
668

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

    
674
  def Notify(self):
675
    """Notifies the wakeup file descriptor.
676

677
    """
678
    self._write_fh.write(chr(0))
679

    
680
  def __del__(self):
681
    """Called before object deletion.
682

683
    """
684
    self.Reset()
685

    
686

    
687
class SignalHandler(object):
688
  """Generic signal handler class.
689

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

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

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

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

709
    """
710
    assert handler_fn is None or callable(handler_fn)
711

    
712
    self.signum = set(signum)
713
    self.called = False
714

    
715
    self._handler_fn = handler_fn
716
    self._wakeup = wakeup
717

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

    
736
  def __del__(self):
737
    self.Reset()
738

    
739
  def Reset(self):
740
    """Restore previous handler.
741

742
    This will reset all the signals to their previous handlers.
743

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

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

753
    This function can be used in case a signal may arrive several times.
754

755
    """
756
    self.called = False
757

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

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

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

    
770
    if self._handler_fn:
771
      self._handler_fn(signum, frame)
772

    
773

    
774
class FieldSet(object):
775
  """A simple field set.
776

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

782
  Internally, all fields are held as regular expression objects.
783

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

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

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

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

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

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

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

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

    
815

    
816
def ValidateDeviceNames(kind, container):
817
  """Validate instance device names.
818

819
  Check that a device container contains only unique and valid names.
820

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

826
  """
827

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

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

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