Statistics
| Branch: | Tag: | Revision:

root / lib / utils / __init__.py @ 1cc324f0

History | View | Annotate | Download (24.1 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.x509 import *
60

    
61

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

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

    
66

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

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

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

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

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

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

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

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

    
139

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

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

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

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

    
161
  return name
162

    
163

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

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

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

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

    
186
  return invalid
187

    
188

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

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

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

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

    
203

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

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

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

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

    
227
    retval[name] = size
228

    
229
  return retval
230

    
231

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

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

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

    
243

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

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

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

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

    
265

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

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

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

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

    
304

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

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

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

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

    
328
  return cpu_list
329

    
330

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

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

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

    
351

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

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

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

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

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

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

    
379

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

383
  Immediately returns at the first interruption.
384

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

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

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

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

    
418

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

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

426
  """
427

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

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

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

    
441

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

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

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

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

    
470

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

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

    
481
  return True
482

    
483

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

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

    
494
  return True
495

    
496

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

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

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

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

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

    
514

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

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

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

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

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

    
532

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

537
  @return: nanoseconds since the Unix epoch
538

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

    
542

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

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

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

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

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

    
567
  return None
568

    
569

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

573
  This function is Linux-specific.
574

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

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

    
587
  return data
588

    
589

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

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

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

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

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

    
626

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

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

    
633

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

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

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

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

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

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

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

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

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

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

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

678
    """
679
    self._write_fh.write("\0")
680

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

684
    """
685
    self.Reset()
686

    
687

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

756
    """
757
    self.called = False
758

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

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

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

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

    
774

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

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

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

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

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

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

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

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

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

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

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

    
816

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

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

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

827
  """
828

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

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

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