Instance policy command line support
[ganeti-local] / lib / utils / __init__.py
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
45 from ganeti.utils.algo import *
46 from ganeti.utils.filelock import *
47 from ganeti.utils.hash import *
48 from ganeti.utils.io import *
49 from ganeti.utils.log import *
50 from ganeti.utils.mlock import *
51 from ganeti.utils.nodesetup import *
52 from ganeti.utils.process import *
53 from ganeti.utils.retry import *
54 from ganeti.utils.text import *
55 from ganeti.utils.wrapper import *
56 from ganeti.utils.x509 import *
57
58
59 _VALID_SERVICE_NAME_RE = re.compile("^[-_.a-zA-Z0-9]{1,128}$")
60
61 UUID_RE = re.compile("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-"
62                      "[a-f0-9]{4}-[a-f0-9]{12}$")
63
64
65 def ForceDictType(target, key_types, allowed_values=None):
66   """Force the values of a dict to have certain types.
67
68   @type target: dict
69   @param target: the dict to update
70   @type key_types: dict
71   @param key_types: dict mapping target dict keys to types
72                     in constants.ENFORCEABLE_TYPES
73   @type allowed_values: list
74   @keyword allowed_values: list of specially allowed values
75
76   """
77   if allowed_values is None:
78     allowed_values = []
79
80   if not isinstance(target, dict):
81     msg = "Expected dictionary, got '%s'" % target
82     raise errors.TypeEnforcementError(msg)
83
84   for key in target:
85     if key not in key_types:
86       msg = "Unknown parameter '%s'" % key
87       raise errors.TypeEnforcementError(msg)
88
89     if target[key] in allowed_values:
90       continue
91
92     ktype = key_types[key]
93     if ktype not in constants.ENFORCEABLE_TYPES:
94       msg = "'%s' has non-enforceable type %s" % (key, ktype)
95       raise errors.ProgrammerError(msg)
96
97     if ktype in (constants.VTYPE_STRING, constants.VTYPE_MAYBE_STRING):
98       if target[key] is None and ktype == constants.VTYPE_MAYBE_STRING:
99         pass
100       elif not isinstance(target[key], basestring):
101         if isinstance(target[key], bool) and not target[key]:
102           target[key] = ""
103         else:
104           msg = "'%s' (value %s) is not a valid string" % (key, target[key])
105           raise errors.TypeEnforcementError(msg)
106     elif ktype == constants.VTYPE_BOOL:
107       if isinstance(target[key], basestring) and target[key]:
108         if target[key].lower() == constants.VALUE_FALSE:
109           target[key] = False
110         elif target[key].lower() == constants.VALUE_TRUE:
111           target[key] = True
112         else:
113           msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
114           raise errors.TypeEnforcementError(msg)
115       elif target[key]:
116         target[key] = True
117       else:
118         target[key] = False
119     elif ktype == constants.VTYPE_SIZE:
120       try:
121         target[key] = ParseUnit(target[key])
122       except errors.UnitParseError, err:
123         msg = "'%s' (value %s) is not a valid size. error: %s" % \
124               (key, target[key], err)
125         raise errors.TypeEnforcementError(msg)
126     elif ktype == constants.VTYPE_INT:
127       try:
128         target[key] = int(target[key])
129       except (ValueError, TypeError):
130         msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
131         raise errors.TypeEnforcementError(msg)
132
133
134 def ValidateServiceName(name):
135   """Validate the given service name.
136
137   @type name: number or string
138   @param name: Service name or port specification
139
140   """
141   try:
142     numport = int(name)
143   except (ValueError, TypeError):
144     # Non-numeric service name
145     valid = _VALID_SERVICE_NAME_RE.match(name)
146   else:
147     # Numeric port (protocols other than TCP or UDP might need adjustments
148     # here)
149     valid = (numport >= 0 and numport < (1 << 16))
150
151   if not valid:
152     raise errors.OpPrereqError("Invalid service name '%s'" % name,
153                                errors.ECODE_INVAL)
154
155   return name
156
157
158 def ListVolumeGroups():
159   """List volume groups and their size
160
161   @rtype: dict
162   @return:
163        Dictionary with keys volume name and values
164        the size of the volume
165
166   """
167   command = "vgs --noheadings --units m --nosuffix -o name,size"
168   result = RunCmd(command)
169   retval = {}
170   if result.failed:
171     return retval
172
173   for line in result.stdout.splitlines():
174     try:
175       name, size = line.split()
176       size = int(float(size))
177     except (IndexError, ValueError), err:
178       logging.error("Invalid output from vgs (%s): %s", err, line)
179       continue
180
181     retval[name] = size
182
183   return retval
184
185
186 def BridgeExists(bridge):
187   """Check whether the given bridge exists in the system
188
189   @type bridge: str
190   @param bridge: the bridge name to check
191   @rtype: boolean
192   @return: True if it does
193
194   """
195   return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
196
197
198 def TryConvert(fn, val):
199   """Try to convert a value ignoring errors.
200
201   This function tries to apply function I{fn} to I{val}. If no
202   C{ValueError} or C{TypeError} exceptions are raised, it will return
203   the result, else it will return the original value. Any other
204   exceptions are propagated to the caller.
205
206   @type fn: callable
207   @param fn: function to apply to the value
208   @param val: the value to be converted
209   @return: The converted value if the conversion was successful,
210       otherwise the original value.
211
212   """
213   try:
214     nv = fn(val)
215   except (ValueError, TypeError):
216     nv = val
217   return nv
218
219
220 def ParseCpuMask(cpu_mask):
221   """Parse a CPU mask definition and return the list of CPU IDs.
222
223   CPU mask format: comma-separated list of CPU IDs
224   or dash-separated ID ranges
225   Example: "0-2,5" -> "0,1,2,5"
226
227   @type cpu_mask: str
228   @param cpu_mask: CPU mask definition
229   @rtype: list of int
230   @return: list of CPU IDs
231
232   """
233   if not cpu_mask:
234     return []
235   cpu_list = []
236   for range_def in cpu_mask.split(","):
237     boundaries = range_def.split("-")
238     n_elements = len(boundaries)
239     if n_elements > 2:
240       raise errors.ParseError("Invalid CPU ID range definition"
241                               " (only one hyphen allowed): %s" % range_def)
242     try:
243       lower = int(boundaries[0])
244     except (ValueError, TypeError), err:
245       raise errors.ParseError("Invalid CPU ID value for lower boundary of"
246                               " CPU ID range: %s" % str(err))
247     try:
248       higher = int(boundaries[-1])
249     except (ValueError, TypeError), err:
250       raise errors.ParseError("Invalid CPU ID value for higher boundary of"
251                               " CPU ID range: %s" % str(err))
252     if lower > higher:
253       raise errors.ParseError("Invalid CPU ID range definition"
254                               " (%d > %d): %s" % (lower, higher, range_def))
255     cpu_list.extend(range(lower, higher + 1))
256   return cpu_list
257
258
259 def ParseMultiCpuMask(cpu_mask):
260   """Parse a multiple CPU mask definition and return the list of CPU IDs.
261
262   CPU mask format: colon-separated list of comma-separated list of CPU IDs
263   or dash-separated ID ranges, with optional "all" as CPU value
264   Example: "0-2,5:all:1,5,6:2" -> [ [ 0,1,2,5 ], [ -1 ], [ 1, 5, 6 ], [ 2 ] ]
265
266   @type cpu_mask: str
267   @param cpu_mask: multiple CPU mask definition
268   @rtype: list of lists of int
269   @return: list of lists of CPU IDs
270
271   """
272   if not cpu_mask:
273     return []
274   cpu_list = []
275   for range_def in cpu_mask.split(constants.CPU_PINNING_SEP):
276     if range_def == constants.CPU_PINNING_ALL:
277       cpu_list.append([constants.CPU_PINNING_ALL_VAL, ])
278     else:
279       # Uniquify and sort the list before adding
280       cpu_list.append(sorted(set(ParseCpuMask(range_def))))
281
282   return cpu_list
283
284
285 def GetHomeDir(user, default=None):
286   """Try to get the homedir of the given user.
287
288   The user can be passed either as a string (denoting the name) or as
289   an integer (denoting the user id). If the user is not found, the
290   'default' argument is returned, which defaults to None.
291
292   """
293   try:
294     if isinstance(user, basestring):
295       result = pwd.getpwnam(user)
296     elif isinstance(user, (int, long)):
297       result = pwd.getpwuid(user)
298     else:
299       raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
300                                    type(user))
301   except KeyError:
302     return default
303   return result.pw_dir
304
305
306 def FirstFree(seq, base=0):
307   """Returns the first non-existing integer from seq.
308
309   The seq argument should be a sorted list of positive integers. The
310   first time the index of an element is smaller than the element
311   value, the index will be returned.
312
313   The base argument is used to start at a different offset,
314   i.e. C{[3, 4, 6]} with I{offset=3} will return 5.
315
316   Example: C{[0, 1, 3]} will return I{2}.
317
318   @type seq: sequence
319   @param seq: the sequence to be analyzed.
320   @type base: int
321   @param base: use this value as the base index of the sequence
322   @rtype: int
323   @return: the first non-used index in the sequence
324
325   """
326   for idx, elem in enumerate(seq):
327     assert elem >= base, "Passed element is higher than base offset"
328     if elem > idx + base:
329       # idx is not used
330       return idx + base
331   return None
332
333
334 def SingleWaitForFdCondition(fdobj, event, timeout):
335   """Waits for a condition to occur on the socket.
336
337   Immediately returns at the first interruption.
338
339   @type fdobj: integer or object supporting a fileno() method
340   @param fdobj: entity to wait for events on
341   @type event: integer
342   @param event: ORed condition (see select module)
343   @type timeout: float or None
344   @param timeout: Timeout in seconds
345   @rtype: int or None
346   @return: None for timeout, otherwise occured conditions
347
348   """
349   check = (event | select.POLLPRI |
350            select.POLLNVAL | select.POLLHUP | select.POLLERR)
351
352   if timeout is not None:
353     # Poller object expects milliseconds
354     timeout *= 1000
355
356   poller = select.poll()
357   poller.register(fdobj, event)
358   try:
359     # TODO: If the main thread receives a signal and we have no timeout, we
360     # could wait forever. This should check a global "quit" flag or something
361     # every so often.
362     io_events = poller.poll(timeout)
363   except select.error, err:
364     if err[0] != errno.EINTR:
365       raise
366     io_events = []
367   if io_events and io_events[0][1] & check:
368     return io_events[0][1]
369   else:
370     return None
371
372
373 class FdConditionWaiterHelper(object):
374   """Retry helper for WaitForFdCondition.
375
376   This class contains the retried and wait functions that make sure
377   WaitForFdCondition can continue waiting until the timeout is actually
378   expired.
379
380   """
381
382   def __init__(self, timeout):
383     self.timeout = timeout
384
385   def Poll(self, fdobj, event):
386     result = SingleWaitForFdCondition(fdobj, event, self.timeout)
387     if result is None:
388       raise RetryAgain()
389     else:
390       return result
391
392   def UpdateTimeout(self, timeout):
393     self.timeout = timeout
394
395
396 def WaitForFdCondition(fdobj, event, timeout):
397   """Waits for a condition to occur on the socket.
398
399   Retries until the timeout is expired, even if interrupted.
400
401   @type fdobj: integer or object supporting a fileno() method
402   @param fdobj: entity to wait for events on
403   @type event: integer
404   @param event: ORed condition (see select module)
405   @type timeout: float or None
406   @param timeout: Timeout in seconds
407   @rtype: int or None
408   @return: None for timeout, otherwise occured conditions
409
410   """
411   if timeout is not None:
412     retrywaiter = FdConditionWaiterHelper(timeout)
413     try:
414       result = Retry(retrywaiter.Poll, RETRY_REMAINING_TIME, timeout,
415                      args=(fdobj, event), wait_fn=retrywaiter.UpdateTimeout)
416     except RetryTimeout:
417       result = None
418   else:
419     result = None
420     while result is None:
421       result = SingleWaitForFdCondition(fdobj, event, timeout)
422   return result
423
424
425 def EnsureDaemon(name):
426   """Check for and start daemon if not alive.
427
428   """
429   result = RunCmd([constants.DAEMON_UTIL, "check-and-start", name])
430   if result.failed:
431     logging.error("Can't start daemon '%s', failure %s, output: %s",
432                   name, result.fail_reason, result.output)
433     return False
434
435   return True
436
437
438 def StopDaemon(name):
439   """Stop daemon
440
441   """
442   result = RunCmd([constants.DAEMON_UTIL, "stop", name])
443   if result.failed:
444     logging.error("Can't stop daemon '%s', failure %s, output: %s",
445                   name, result.fail_reason, result.output)
446     return False
447
448   return True
449
450
451 def CheckVolumeGroupSize(vglist, vgname, minsize):
452   """Checks if the volume group list is valid.
453
454   The function will check if a given volume group is in the list of
455   volume groups and has a minimum size.
456
457   @type vglist: dict
458   @param vglist: dictionary of volume group names and their size
459   @type vgname: str
460   @param vgname: the volume group we should check
461   @type minsize: int
462   @param minsize: the minimum size we accept
463   @rtype: None or str
464   @return: None for success, otherwise the error message
465
466   """
467   vgsize = vglist.get(vgname, None)
468   if vgsize is None:
469     return "volume group '%s' missing" % vgname
470   elif vgsize < minsize:
471     return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
472             (vgname, minsize, vgsize))
473   return None
474
475
476 def SplitTime(value):
477   """Splits time as floating point number into a tuple.
478
479   @param value: Time in seconds
480   @type value: int or float
481   @return: Tuple containing (seconds, microseconds)
482
483   """
484   (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
485
486   assert 0 <= seconds, \
487     "Seconds must be larger than or equal to 0, but are %s" % seconds
488   assert 0 <= microseconds <= 999999, \
489     "Microseconds must be 0-999999, but are %s" % microseconds
490
491   return (int(seconds), int(microseconds))
492
493
494 def MergeTime(timetuple):
495   """Merges a tuple into time as a floating point number.
496
497   @param timetuple: Time as tuple, (seconds, microseconds)
498   @type timetuple: tuple
499   @return: Time as a floating point number expressed in seconds
500
501   """
502   (seconds, microseconds) = timetuple
503
504   assert 0 <= seconds, \
505     "Seconds must be larger than or equal to 0, but are %s" % seconds
506   assert 0 <= microseconds <= 999999, \
507     "Microseconds must be 0-999999, but are %s" % microseconds
508
509   return float(seconds) + (float(microseconds) * 0.000001)
510
511
512 def FindMatch(data, name):
513   """Tries to find an item in a dictionary matching a name.
514
515   Callers have to ensure the data names aren't contradictory (e.g. a regexp
516   that matches a string). If the name isn't a direct key, all regular
517   expression objects in the dictionary are matched against it.
518
519   @type data: dict
520   @param data: Dictionary containing data
521   @type name: string
522   @param name: Name to look for
523   @rtype: tuple; (value in dictionary, matched groups as list)
524
525   """
526   if name in data:
527     return (data[name], [])
528
529   for key, value in data.items():
530     # Regex objects
531     if hasattr(key, "match"):
532       m = key.match(name)
533       if m:
534         return (value, list(m.groups()))
535
536   return None
537
538
539 def GetMounts(filename=constants.PROC_MOUNTS):
540   """Returns the list of mounted filesystems.
541
542   This function is Linux-specific.
543
544   @param filename: path of mounts file (/proc/mounts by default)
545   @rtype: list of tuples
546   @return: list of mount entries (device, mountpoint, fstype, options)
547
548   """
549   # TODO(iustin): investigate non-Linux options (e.g. via mount output)
550   data = []
551   mountlines = ReadFile(filename).splitlines()
552   for line in mountlines:
553     device, mountpoint, fstype, options, _ = line.split(None, 4)
554     data.append((device, mountpoint, fstype, options))
555
556   return data
557
558
559 def SignalHandled(signums):
560   """Signal Handled decoration.
561
562   This special decorator installs a signal handler and then calls the target
563   function. The function must accept a 'signal_handlers' keyword argument,
564   which will contain a dict indexed by signal number, with SignalHandler
565   objects as values.
566
567   The decorator can be safely stacked with iself, to handle multiple signals
568   with different handlers.
569
570   @type signums: list
571   @param signums: signals to intercept
572
573   """
574   def wrap(fn):
575     def sig_function(*args, **kwargs):
576       assert "signal_handlers" not in kwargs or \
577              kwargs["signal_handlers"] is None or \
578              isinstance(kwargs["signal_handlers"], dict), \
579              "Wrong signal_handlers parameter in original function call"
580       if "signal_handlers" in kwargs and kwargs["signal_handlers"] is not None:
581         signal_handlers = kwargs["signal_handlers"]
582       else:
583         signal_handlers = {}
584         kwargs["signal_handlers"] = signal_handlers
585       sighandler = SignalHandler(signums)
586       try:
587         for sig in signums:
588           signal_handlers[sig] = sighandler
589         return fn(*args, **kwargs)
590       finally:
591         sighandler.Reset()
592     return sig_function
593   return wrap
594
595
596 def TimeoutExpired(epoch, timeout, _time_fn=time.time):
597   """Checks whether a timeout has expired.
598
599   """
600   return _time_fn() > (epoch + timeout)
601
602
603 class SignalWakeupFd(object):
604   try:
605     # This is only supported in Python 2.5 and above (some distributions
606     # backported it to Python 2.4)
607     _set_wakeup_fd_fn = signal.set_wakeup_fd
608   except AttributeError:
609     # Not supported
610     def _SetWakeupFd(self, _): # pylint: disable=R0201
611       return -1
612   else:
613     def _SetWakeupFd(self, fd):
614       return self._set_wakeup_fd_fn(fd)
615
616   def __init__(self):
617     """Initializes this class.
618
619     """
620     (read_fd, write_fd) = os.pipe()
621
622     # Once these succeeded, the file descriptors will be closed automatically.
623     # Buffer size 0 is important, otherwise .read() with a specified length
624     # might buffer data and the file descriptors won't be marked readable.
625     self._read_fh = os.fdopen(read_fd, "r", 0)
626     self._write_fh = os.fdopen(write_fd, "w", 0)
627
628     self._previous = self._SetWakeupFd(self._write_fh.fileno())
629
630     # Utility functions
631     self.fileno = self._read_fh.fileno
632     self.read = self._read_fh.read
633
634   def Reset(self):
635     """Restores the previous wakeup file descriptor.
636
637     """
638     if hasattr(self, "_previous") and self._previous is not None:
639       self._SetWakeupFd(self._previous)
640       self._previous = None
641
642   def Notify(self):
643     """Notifies the wakeup file descriptor.
644
645     """
646     self._write_fh.write("\0")
647
648   def __del__(self):
649     """Called before object deletion.
650
651     """
652     self.Reset()
653
654
655 class SignalHandler(object):
656   """Generic signal handler class.
657
658   It automatically restores the original handler when deconstructed or
659   when L{Reset} is called. You can either pass your own handler
660   function in or query the L{called} attribute to detect whether the
661   signal was sent.
662
663   @type signum: list
664   @ivar signum: the signals we handle
665   @type called: boolean
666   @ivar called: tracks whether any of the signals have been raised
667
668   """
669   def __init__(self, signum, handler_fn=None, wakeup=None):
670     """Constructs a new SignalHandler instance.
671
672     @type signum: int or list of ints
673     @param signum: Single signal number or set of signal numbers
674     @type handler_fn: callable
675     @param handler_fn: Signal handling function
676
677     """
678     assert handler_fn is None or callable(handler_fn)
679
680     self.signum = set(signum)
681     self.called = False
682
683     self._handler_fn = handler_fn
684     self._wakeup = wakeup
685
686     self._previous = {}
687     try:
688       for signum in self.signum:
689         # Setup handler
690         prev_handler = signal.signal(signum, self._HandleSignal)
691         try:
692           self._previous[signum] = prev_handler
693         except:
694           # Restore previous handler
695           signal.signal(signum, prev_handler)
696           raise
697     except:
698       # Reset all handlers
699       self.Reset()
700       # Here we have a race condition: a handler may have already been called,
701       # but there's not much we can do about it at this point.
702       raise
703
704   def __del__(self):
705     self.Reset()
706
707   def Reset(self):
708     """Restore previous handler.
709
710     This will reset all the signals to their previous handlers.
711
712     """
713     for signum, prev_handler in self._previous.items():
714       signal.signal(signum, prev_handler)
715       # If successful, remove from dict
716       del self._previous[signum]
717
718   def Clear(self):
719     """Unsets the L{called} flag.
720
721     This function can be used in case a signal may arrive several times.
722
723     """
724     self.called = False
725
726   def _HandleSignal(self, signum, frame):
727     """Actual signal handling function.
728
729     """
730     # This is not nice and not absolutely atomic, but it appears to be the only
731     # solution in Python -- there are no atomic types.
732     self.called = True
733
734     if self._wakeup:
735       # Notify whoever is interested in signals
736       self._wakeup.Notify()
737
738     if self._handler_fn:
739       self._handler_fn(signum, frame)
740
741
742 class FieldSet(object):
743   """A simple field set.
744
745   Among the features are:
746     - checking if a string is among a list of static string or regex objects
747     - checking if a whole list of string matches
748     - returning the matching groups from a regex match
749
750   Internally, all fields are held as regular expression objects.
751
752   """
753   def __init__(self, *items):
754     self.items = [re.compile("^%s$" % value) for value in items]
755
756   def Extend(self, other_set):
757     """Extend the field set with the items from another one"""
758     self.items.extend(other_set.items)
759
760   def Matches(self, field):
761     """Checks if a field matches the current set
762
763     @type field: str
764     @param field: the string to match
765     @return: either None or a regular expression match object
766
767     """
768     for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
769       return m
770     return None
771
772   def NonMatching(self, items):
773     """Returns the list of fields not matching the current set
774
775     @type items: list
776     @param items: the list of fields to check
777     @rtype: list
778     @return: list of non-matching fields
779
780     """
781     return [val for val in items if not self.Matches(val)]