Statistics
| Branch: | Tag: | Revision:

root / lib / utils / __init__.py @ 178ad717

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.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.security import *
57
from ganeti.utils.storage import *
58
from ganeti.utils.text import *
59
from ganeti.utils.wrapper import *
60
from ganeti.utils.version import *
61
from ganeti.utils.x509 import *
62

    
63

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

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

    
68

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

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

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

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

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

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

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

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

    
137

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

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

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

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

    
159
  return name
160

    
161

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

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

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

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

    
184
  return invalid
185

    
186

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

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

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

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

    
201

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

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

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

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

    
225
    retval[name] = size
226

    
227
  return retval
228

    
229

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

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

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

    
241

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

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

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

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

    
263

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

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

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

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

    
302

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

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

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

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

    
326
  return cpu_list
327

    
328

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

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

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

    
349

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

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

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

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

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

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

    
377

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

381
  Immediately returns at the first interruption.
382

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

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

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

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

    
416

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

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

424
  """
425

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

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

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

    
439

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

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

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

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

    
468

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

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

    
479
  return True
480

    
481

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

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

    
492
  return True
493

    
494

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

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

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

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

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

    
512

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

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

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

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

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

    
530

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

535
  @return: nanoseconds since the Unix epoch
536

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

    
540

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

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

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

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

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

    
565
  return None
566

    
567

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

571
  This function is Linux-specific.
572

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

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

    
585
  return data
586

    
587

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

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

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

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

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

    
624

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

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

    
631

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

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

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

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

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

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

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

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

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

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

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

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

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

682
    """
683
    self.Reset()
684

    
685

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

754
    """
755
    self.called = False
756

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

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

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

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

    
772

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

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

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

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

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

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

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

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

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

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

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

    
814

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

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

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

825
  """
826

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

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

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