Statistics
| Branch: | Tag: | Revision:

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

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

    
140

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

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

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

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

    
162
  return name
163

    
164

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

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

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

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

    
187
  return invalid
188

    
189

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

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

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

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

    
204

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

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

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

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

    
228
    retval[name] = size
229

    
230
  return retval
231

    
232

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

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

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

    
244

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

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

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

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

    
266

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

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

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

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

    
305

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

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

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

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

    
329
  return cpu_list
330

    
331

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

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

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

    
352

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

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

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

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

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

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

    
380

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

384
  Immediately returns at the first interruption.
385

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

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

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

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

    
419

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

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

427
  """
428

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

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

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

    
442

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

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

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

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

    
471

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

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

    
482
  return True
483

    
484

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

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

    
495
  return True
496

    
497

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

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

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

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

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

    
515

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

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

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

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

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

    
533

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

538
  @return: nanoseconds since the Unix epoch
539

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

    
543

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

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

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

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

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

    
568
  return None
569

    
570

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

574
  This function is Linux-specific.
575

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

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

    
588
  return data
589

    
590

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

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

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

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

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

    
627

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

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

    
634

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

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

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

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

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

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

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

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

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

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

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

679
    """
680
    self._write_fh.write(chr(0))
681

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

685
    """
686
    self.Reset()
687

    
688

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

757
    """
758
    self.called = False
759

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

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

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

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

    
775

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

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

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

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

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

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

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

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

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

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

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

    
817

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

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

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

828
  """
829

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

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

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