Statistics
| Branch: | Tag: | Revision:

root / lib / utils / __init__.py @ 90e234a6

History | View | Annotate | Download (21.5 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

    
30
import os
31
import sys
32
import time
33
import subprocess
34
import re
35
import socket
36
import tempfile
37
import shutil
38
import errno
39
import pwd
40
import itertools
41
import select
42
import fcntl
43
import resource
44
import logging
45
import signal
46
import datetime
47
import calendar
48

    
49
from cStringIO import StringIO
50

    
51
from ganeti import errors
52
from ganeti import constants
53
from ganeti import compat
54

    
55
from ganeti.utils.algo import * # pylint: disable-msg=W0401
56
from ganeti.utils.retry import * # pylint: disable-msg=W0401
57
from ganeti.utils.text import * # pylint: disable-msg=W0401
58
from ganeti.utils.mlock import * # pylint: disable-msg=W0401
59
from ganeti.utils.log import * # pylint: disable-msg=W0401
60
from ganeti.utils.hash import * # pylint: disable-msg=W0401
61
from ganeti.utils.wrapper import * # pylint: disable-msg=W0401
62
from ganeti.utils.filelock import * # pylint: disable-msg=W0401
63
from ganeti.utils.io import * # pylint: disable-msg=W0401
64
from ganeti.utils.x509 import * # pylint: disable-msg=W0401
65
from ganeti.utils.nodesetup import * # pylint: disable-msg=W0401
66
from ganeti.utils.process import * # pylint: disable-msg=W0401
67

    
68

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

    
71
UUID_RE = re.compile('^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-'
72
                     '[a-f0-9]{4}-[a-f0-9]{12}$')
73

    
74

    
75
def ForceDictType(target, key_types, allowed_values=None):
76
  """Force the values of a dict to have certain types.
77

78
  @type target: dict
79
  @param target: the dict to update
80
  @type key_types: dict
81
  @param key_types: dict mapping target dict keys to types
82
                    in constants.ENFORCEABLE_TYPES
83
  @type allowed_values: list
84
  @keyword allowed_values: list of specially allowed values
85

86
  """
87
  if allowed_values is None:
88
    allowed_values = []
89

    
90
  if not isinstance(target, dict):
91
    msg = "Expected dictionary, got '%s'" % target
92
    raise errors.TypeEnforcementError(msg)
93

    
94
  for key in target:
95
    if key not in key_types:
96
      msg = "Unknown key '%s'" % key
97
      raise errors.TypeEnforcementError(msg)
98

    
99
    if target[key] in allowed_values:
100
      continue
101

    
102
    ktype = key_types[key]
103
    if ktype not in constants.ENFORCEABLE_TYPES:
104
      msg = "'%s' has non-enforceable type %s" % (key, ktype)
105
      raise errors.ProgrammerError(msg)
106

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

    
143

    
144
def ValidateServiceName(name):
145
  """Validate the given service name.
146

147
  @type name: number or string
148
  @param name: Service name or port specification
149

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

    
161
  if not valid:
162
    raise errors.OpPrereqError("Invalid service name '%s'" % name,
163
                               errors.ECODE_INVAL)
164

    
165
  return name
166

    
167

    
168
def ListVolumeGroups():
169
  """List volume groups and their size
170

171
  @rtype: dict
172
  @return:
173
       Dictionary with keys volume name and values
174
       the size of the volume
175

176
  """
177
  command = "vgs --noheadings --units m --nosuffix -o name,size"
178
  result = RunCmd(command)
179
  retval = {}
180
  if result.failed:
181
    return retval
182

    
183
  for line in result.stdout.splitlines():
184
    try:
185
      name, size = line.split()
186
      size = int(float(size))
187
    except (IndexError, ValueError), err:
188
      logging.error("Invalid output from vgs (%s): %s", err, line)
189
      continue
190

    
191
    retval[name] = size
192

    
193
  return retval
194

    
195

    
196
def BridgeExists(bridge):
197
  """Check whether the given bridge exists in the system
198

199
  @type bridge: str
200
  @param bridge: the bridge name to check
201
  @rtype: boolean
202
  @return: True if it does
203

204
  """
205
  return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
206

    
207

    
208
def TryConvert(fn, val):
209
  """Try to convert a value ignoring errors.
210

211
  This function tries to apply function I{fn} to I{val}. If no
212
  C{ValueError} or C{TypeError} exceptions are raised, it will return
213
  the result, else it will return the original value. Any other
214
  exceptions are propagated to the caller.
215

216
  @type fn: callable
217
  @param fn: function to apply to the value
218
  @param val: the value to be converted
219
  @return: The converted value if the conversion was successful,
220
      otherwise the original value.
221

222
  """
223
  try:
224
    nv = fn(val)
225
  except (ValueError, TypeError):
226
    nv = val
227
  return nv
228

    
229

    
230
def ParseCpuMask(cpu_mask):
231
  """Parse a CPU mask definition and return the list of CPU IDs.
232

233
  CPU mask format: comma-separated list of CPU IDs
234
  or dash-separated ID ranges
235
  Example: "0-2,5" -> "0,1,2,5"
236

237
  @type cpu_mask: str
238
  @param cpu_mask: CPU mask definition
239
  @rtype: list of int
240
  @return: list of CPU IDs
241

242
  """
243
  if not cpu_mask:
244
    return []
245
  cpu_list = []
246
  for range_def in cpu_mask.split(","):
247
    boundaries = range_def.split("-")
248
    n_elements = len(boundaries)
249
    if n_elements > 2:
250
      raise errors.ParseError("Invalid CPU ID range definition"
251
                              " (only one hyphen allowed): %s" % range_def)
252
    try:
253
      lower = int(boundaries[0])
254
    except (ValueError, TypeError), err:
255
      raise errors.ParseError("Invalid CPU ID value for lower boundary of"
256
                              " CPU ID range: %s" % str(err))
257
    try:
258
      higher = int(boundaries[-1])
259
    except (ValueError, TypeError), err:
260
      raise errors.ParseError("Invalid CPU ID value for higher boundary of"
261
                              " CPU ID range: %s" % str(err))
262
    if lower > higher:
263
      raise errors.ParseError("Invalid CPU ID range definition"
264
                              " (%d > %d): %s" % (lower, higher, range_def))
265
    cpu_list.extend(range(lower, higher + 1))
266
  return cpu_list
267

    
268

    
269
def GetHomeDir(user, default=None):
270
  """Try to get the homedir of the given user.
271

272
  The user can be passed either as a string (denoting the name) or as
273
  an integer (denoting the user id). If the user is not found, the
274
  'default' argument is returned, which defaults to None.
275

276
  """
277
  try:
278
    if isinstance(user, basestring):
279
      result = pwd.getpwnam(user)
280
    elif isinstance(user, (int, long)):
281
      result = pwd.getpwuid(user)
282
    else:
283
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
284
                                   type(user))
285
  except KeyError:
286
    return default
287
  return result.pw_dir
288

    
289

    
290
def FirstFree(seq, base=0):
291
  """Returns the first non-existing integer from seq.
292

293
  The seq argument should be a sorted list of positive integers. The
294
  first time the index of an element is smaller than the element
295
  value, the index will be returned.
296

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

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

302
  @type seq: sequence
303
  @param seq: the sequence to be analyzed.
304
  @type base: int
305
  @param base: use this value as the base index of the sequence
306
  @rtype: int
307
  @return: the first non-used index in the sequence
308

309
  """
310
  for idx, elem in enumerate(seq):
311
    assert elem >= base, "Passed element is higher than base offset"
312
    if elem > idx + base:
313
      # idx is not used
314
      return idx + base
315
  return None
316

    
317

    
318
def SingleWaitForFdCondition(fdobj, event, timeout):
319
  """Waits for a condition to occur on the socket.
320

321
  Immediately returns at the first interruption.
322

323
  @type fdobj: integer or object supporting a fileno() method
324
  @param fdobj: entity to wait for events on
325
  @type event: integer
326
  @param event: ORed condition (see select module)
327
  @type timeout: float or None
328
  @param timeout: Timeout in seconds
329
  @rtype: int or None
330
  @return: None for timeout, otherwise occured conditions
331

332
  """
333
  check = (event | select.POLLPRI |
334
           select.POLLNVAL | select.POLLHUP | select.POLLERR)
335

    
336
  if timeout is not None:
337
    # Poller object expects milliseconds
338
    timeout *= 1000
339

    
340
  poller = select.poll()
341
  poller.register(fdobj, event)
342
  try:
343
    # TODO: If the main thread receives a signal and we have no timeout, we
344
    # could wait forever. This should check a global "quit" flag or something
345
    # every so often.
346
    io_events = poller.poll(timeout)
347
  except select.error, err:
348
    if err[0] != errno.EINTR:
349
      raise
350
    io_events = []
351
  if io_events and io_events[0][1] & check:
352
    return io_events[0][1]
353
  else:
354
    return None
355

    
356

    
357
class FdConditionWaiterHelper(object):
358
  """Retry helper for WaitForFdCondition.
359

360
  This class contains the retried and wait functions that make sure
361
  WaitForFdCondition can continue waiting until the timeout is actually
362
  expired.
363

364
  """
365

    
366
  def __init__(self, timeout):
367
    self.timeout = timeout
368

    
369
  def Poll(self, fdobj, event):
370
    result = SingleWaitForFdCondition(fdobj, event, self.timeout)
371
    if result is None:
372
      raise RetryAgain()
373
    else:
374
      return result
375

    
376
  def UpdateTimeout(self, timeout):
377
    self.timeout = timeout
378

    
379

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

383
  Retries until the timeout is expired, even if interrupted.
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
  if timeout is not None:
396
    retrywaiter = FdConditionWaiterHelper(timeout)
397
    try:
398
      result = Retry(retrywaiter.Poll, RETRY_REMAINING_TIME, timeout,
399
                     args=(fdobj, event), wait_fn=retrywaiter.UpdateTimeout)
400
    except RetryTimeout:
401
      result = None
402
  else:
403
    result = None
404
    while result is None:
405
      result = SingleWaitForFdCondition(fdobj, event, timeout)
406
  return result
407

    
408

    
409
def EnsureDaemon(name):
410
  """Check for and start daemon if not alive.
411

412
  """
413
  result = RunCmd([constants.DAEMON_UTIL, "check-and-start", name])
414
  if result.failed:
415
    logging.error("Can't start daemon '%s', failure %s, output: %s",
416
                  name, result.fail_reason, result.output)
417
    return False
418

    
419
  return True
420

    
421

    
422
def StopDaemon(name):
423
  """Stop daemon
424

425
  """
426
  result = RunCmd([constants.DAEMON_UTIL, "stop", name])
427
  if result.failed:
428
    logging.error("Can't stop daemon '%s', failure %s, output: %s",
429
                  name, result.fail_reason, result.output)
430
    return False
431

    
432
  return True
433

    
434

    
435
def CheckVolumeGroupSize(vglist, vgname, minsize):
436
  """Checks if the volume group list is valid.
437

438
  The function will check if a given volume group is in the list of
439
  volume groups and has a minimum size.
440

441
  @type vglist: dict
442
  @param vglist: dictionary of volume group names and their size
443
  @type vgname: str
444
  @param vgname: the volume group we should check
445
  @type minsize: int
446
  @param minsize: the minimum size we accept
447
  @rtype: None or str
448
  @return: None for success, otherwise the error message
449

450
  """
451
  vgsize = vglist.get(vgname, None)
452
  if vgsize is None:
453
    return "volume group '%s' missing" % vgname
454
  elif vgsize < minsize:
455
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
456
            (vgname, minsize, vgsize))
457
  return None
458

    
459

    
460
def SplitTime(value):
461
  """Splits time as floating point number into a tuple.
462

463
  @param value: Time in seconds
464
  @type value: int or float
465
  @return: Tuple containing (seconds, microseconds)
466

467
  """
468
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
469

    
470
  assert 0 <= seconds, \
471
    "Seconds must be larger than or equal to 0, but are %s" % seconds
472
  assert 0 <= microseconds <= 999999, \
473
    "Microseconds must be 0-999999, but are %s" % microseconds
474

    
475
  return (int(seconds), int(microseconds))
476

    
477

    
478
def MergeTime(timetuple):
479
  """Merges a tuple into time as a floating point number.
480

481
  @param timetuple: Time as tuple, (seconds, microseconds)
482
  @type timetuple: tuple
483
  @return: Time as a floating point number expressed in seconds
484

485
  """
486
  (seconds, microseconds) = timetuple
487

    
488
  assert 0 <= seconds, \
489
    "Seconds must be larger than or equal to 0, but are %s" % seconds
490
  assert 0 <= microseconds <= 999999, \
491
    "Microseconds must be 0-999999, but are %s" % microseconds
492

    
493
  return float(seconds) + (float(microseconds) * 0.000001)
494

    
495

    
496
def FindMatch(data, name):
497
  """Tries to find an item in a dictionary matching a name.
498

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

503
  @type data: dict
504
  @param data: Dictionary containing data
505
  @type name: string
506
  @param name: Name to look for
507
  @rtype: tuple; (value in dictionary, matched groups as list)
508

509
  """
510
  if name in data:
511
    return (data[name], [])
512

    
513
  for key, value in data.items():
514
    # Regex objects
515
    if hasattr(key, "match"):
516
      m = key.match(name)
517
      if m:
518
        return (value, list(m.groups()))
519

    
520
  return None
521

    
522

    
523
def GetMounts(filename=constants.PROC_MOUNTS):
524
  """Returns the list of mounted filesystems.
525

526
  This function is Linux-specific.
527

528
  @param filename: path of mounts file (/proc/mounts by default)
529
  @rtype: list of tuples
530
  @return: list of mount entries (device, mountpoint, fstype, options)
531

532
  """
533
  # TODO(iustin): investigate non-Linux options (e.g. via mount output)
534
  data = []
535
  mountlines = ReadFile(filename).splitlines()
536
  for line in mountlines:
537
    device, mountpoint, fstype, options, _ = line.split(None, 4)
538
    data.append((device, mountpoint, fstype, options))
539

    
540
  return data
541

    
542

    
543
def SignalHandled(signums):
544
  """Signal Handled decoration.
545

546
  This special decorator installs a signal handler and then calls the target
547
  function. The function must accept a 'signal_handlers' keyword argument,
548
  which will contain a dict indexed by signal number, with SignalHandler
549
  objects as values.
550

551
  The decorator can be safely stacked with iself, to handle multiple signals
552
  with different handlers.
553

554
  @type signums: list
555
  @param signums: signals to intercept
556

557
  """
558
  def wrap(fn):
559
    def sig_function(*args, **kwargs):
560
      assert 'signal_handlers' not in kwargs or \
561
             kwargs['signal_handlers'] is None or \
562
             isinstance(kwargs['signal_handlers'], dict), \
563
             "Wrong signal_handlers parameter in original function call"
564
      if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
565
        signal_handlers = kwargs['signal_handlers']
566
      else:
567
        signal_handlers = {}
568
        kwargs['signal_handlers'] = signal_handlers
569
      sighandler = SignalHandler(signums)
570
      try:
571
        for sig in signums:
572
          signal_handlers[sig] = sighandler
573
        return fn(*args, **kwargs)
574
      finally:
575
        sighandler.Reset()
576
    return sig_function
577
  return wrap
578

    
579

    
580
class SignalWakeupFd(object):
581
  try:
582
    # This is only supported in Python 2.5 and above (some distributions
583
    # backported it to Python 2.4)
584
    _set_wakeup_fd_fn = signal.set_wakeup_fd
585
  except AttributeError:
586
    # Not supported
587
    def _SetWakeupFd(self, _): # pylint: disable-msg=R0201
588
      return -1
589
  else:
590
    def _SetWakeupFd(self, fd):
591
      return self._set_wakeup_fd_fn(fd)
592

    
593
  def __init__(self):
594
    """Initializes this class.
595

596
    """
597
    (read_fd, write_fd) = os.pipe()
598

    
599
    # Once these succeeded, the file descriptors will be closed automatically.
600
    # Buffer size 0 is important, otherwise .read() with a specified length
601
    # might buffer data and the file descriptors won't be marked readable.
602
    self._read_fh = os.fdopen(read_fd, "r", 0)
603
    self._write_fh = os.fdopen(write_fd, "w", 0)
604

    
605
    self._previous = self._SetWakeupFd(self._write_fh.fileno())
606

    
607
    # Utility functions
608
    self.fileno = self._read_fh.fileno
609
    self.read = self._read_fh.read
610

    
611
  def Reset(self):
612
    """Restores the previous wakeup file descriptor.
613

614
    """
615
    if hasattr(self, "_previous") and self._previous is not None:
616
      self._SetWakeupFd(self._previous)
617
      self._previous = None
618

    
619
  def Notify(self):
620
    """Notifies the wakeup file descriptor.
621

622
    """
623
    self._write_fh.write("\0")
624

    
625
  def __del__(self):
626
    """Called before object deletion.
627

628
    """
629
    self.Reset()
630

    
631

    
632
class SignalHandler(object):
633
  """Generic signal handler class.
634

635
  It automatically restores the original handler when deconstructed or
636
  when L{Reset} is called. You can either pass your own handler
637
  function in or query the L{called} attribute to detect whether the
638
  signal was sent.
639

640
  @type signum: list
641
  @ivar signum: the signals we handle
642
  @type called: boolean
643
  @ivar called: tracks whether any of the signals have been raised
644

645
  """
646
  def __init__(self, signum, handler_fn=None, wakeup=None):
647
    """Constructs a new SignalHandler instance.
648

649
    @type signum: int or list of ints
650
    @param signum: Single signal number or set of signal numbers
651
    @type handler_fn: callable
652
    @param handler_fn: Signal handling function
653

654
    """
655
    assert handler_fn is None or callable(handler_fn)
656

    
657
    self.signum = set(signum)
658
    self.called = False
659

    
660
    self._handler_fn = handler_fn
661
    self._wakeup = wakeup
662

    
663
    self._previous = {}
664
    try:
665
      for signum in self.signum:
666
        # Setup handler
667
        prev_handler = signal.signal(signum, self._HandleSignal)
668
        try:
669
          self._previous[signum] = prev_handler
670
        except:
671
          # Restore previous handler
672
          signal.signal(signum, prev_handler)
673
          raise
674
    except:
675
      # Reset all handlers
676
      self.Reset()
677
      # Here we have a race condition: a handler may have already been called,
678
      # but there's not much we can do about it at this point.
679
      raise
680

    
681
  def __del__(self):
682
    self.Reset()
683

    
684
  def Reset(self):
685
    """Restore previous handler.
686

687
    This will reset all the signals to their previous handlers.
688

689
    """
690
    for signum, prev_handler in self._previous.items():
691
      signal.signal(signum, prev_handler)
692
      # If successful, remove from dict
693
      del self._previous[signum]
694

    
695
  def Clear(self):
696
    """Unsets the L{called} flag.
697

698
    This function can be used in case a signal may arrive several times.
699

700
    """
701
    self.called = False
702

    
703
  def _HandleSignal(self, signum, frame):
704
    """Actual signal handling function.
705

706
    """
707
    # This is not nice and not absolutely atomic, but it appears to be the only
708
    # solution in Python -- there are no atomic types.
709
    self.called = True
710

    
711
    if self._wakeup:
712
      # Notify whoever is interested in signals
713
      self._wakeup.Notify()
714

    
715
    if self._handler_fn:
716
      self._handler_fn(signum, frame)
717

    
718

    
719
class FieldSet(object):
720
  """A simple field set.
721

722
  Among the features are:
723
    - checking if a string is among a list of static string or regex objects
724
    - checking if a whole list of string matches
725
    - returning the matching groups from a regex match
726

727
  Internally, all fields are held as regular expression objects.
728

729
  """
730
  def __init__(self, *items):
731
    self.items = [re.compile("^%s$" % value) for value in items]
732

    
733
  def Extend(self, other_set):
734
    """Extend the field set with the items from another one"""
735
    self.items.extend(other_set.items)
736

    
737
  def Matches(self, field):
738
    """Checks if a field matches the current set
739

740
    @type field: str
741
    @param field: the string to match
742
    @return: either None or a regular expression match object
743

744
    """
745
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
746
      return m
747
    return None
748

    
749
  def NonMatching(self, items):
750
    """Returns the list of fields not matching the current set
751

752
    @type items: list
753
    @param items: the list of fields to check
754
    @rtype: list
755
    @return: list of non-matching fields
756

757
    """
758
    return [val for val in items if not self.Matches(val)]