Merge branch 'devel-2.5'
[ganeti-local] / lib / hypervisor / hv_base.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2009, 2010 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 """Base class for all hypervisors
23
24 The syntax for the _CHECK variables and the contents of the PARAMETERS
25 dict is the same, see the docstring for L{BaseHypervisor.PARAMETERS}.
26
27 @var _FILE_CHECK: stub for file checks, without the required flag
28 @var _DIR_CHECK: stub for directory checks, without the required flag
29 @var REQ_FILE_CHECK: mandatory file parameter
30 @var OPT_FILE_CHECK: optional file parameter
31 @var REQ_DIR_CHECK: mandatory directory parametr
32 @var OPT_DIR_CHECK: optional directory parameter
33 @var NO_CHECK: parameter without any checks at all
34 @var REQUIRED_CHECK: parameter required to exist (and non-false), but
35     without other checks; beware that this can't be used for boolean
36     parameters, where you should use NO_CHECK or a custom checker
37
38 """
39
40 import os
41 import re
42 import logging
43
44
45 from ganeti import errors
46 from ganeti import utils
47 from ganeti import constants
48
49
50 def _IsCpuMaskWellFormed(cpu_mask):
51   """Verifies if the given single CPU mask is valid
52
53   The single CPU mask should be in the form "a,b,c,d", where each
54   letter is a positive number or range.
55
56   """
57   try:
58     cpu_list = utils.ParseCpuMask(cpu_mask)
59   except errors.ParseError, _:
60     return False
61   return isinstance(cpu_list, list) and len(cpu_list) > 0
62
63
64 def _IsMultiCpuMaskWellFormed(cpu_mask):
65   """Verifies if the given multiple CPU mask is valid
66
67   A valid multiple CPU mask is in the form "a:b:c:d", where each
68   letter is a single CPU mask.
69
70   """
71   try:
72     utils.ParseMultiCpuMask(cpu_mask)
73   except errors.ParseError, _:
74     return False
75
76   return True
77
78
79 # Read the BaseHypervisor.PARAMETERS docstring for the syntax of the
80 # _CHECK values
81
82 # must be afile
83 _FILE_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
84               os.path.isfile, "not found or not a file")
85
86 # must be a directory
87 _DIR_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
88              os.path.isdir, "not found or not a directory")
89
90 # CPU mask must be well-formed
91 # TODO: implement node level check for the CPU mask
92 _CPU_MASK_CHECK = (_IsCpuMaskWellFormed,
93                    "CPU mask definition is not well-formed",
94                    None, None)
95
96 # Multiple CPU mask must be well-formed
97 _MULTI_CPU_MASK_CHECK = (_IsMultiCpuMaskWellFormed,
98                          "Multiple CPU mask definition is not well-formed",
99                          None, None)
100
101 # Check for validity of port number
102 _NET_PORT_CHECK = (lambda x: 0 < x < 65535, "invalid port number",
103                    None, None)
104
105 # nice wrappers for users
106 REQ_FILE_CHECK = (True, ) + _FILE_CHECK
107 OPT_FILE_CHECK = (False, ) + _FILE_CHECK
108 REQ_DIR_CHECK = (True, ) + _DIR_CHECK
109 OPT_DIR_CHECK = (False, ) + _DIR_CHECK
110 REQ_NET_PORT_CHECK = (True, ) + _NET_PORT_CHECK
111 OPT_NET_PORT_CHECK = (False, ) + _NET_PORT_CHECK
112 REQ_CPU_MASK_CHECK = (True, ) + _CPU_MASK_CHECK
113 OPT_CPU_MASK_CHECK = (False, ) + _CPU_MASK_CHECK
114 REQ_MULTI_CPU_MASK_CHECK = (True, ) + _MULTI_CPU_MASK_CHECK
115 OPT_MULTI_CPU_MASK_CHECK = (False, ) + _MULTI_CPU_MASK_CHECK
116
117 # no checks at all
118 NO_CHECK = (False, None, None, None, None)
119
120 # required, but no other checks
121 REQUIRED_CHECK = (True, None, None, None, None)
122
123 # migration type
124 MIGRATION_MODE_CHECK = (True, lambda x: x in constants.HT_MIGRATION_MODES,
125                         "invalid migration mode", None, None)
126
127
128 def ParamInSet(required, my_set):
129   """Builds parameter checker for set membership.
130
131   @type required: boolean
132   @param required: whether this is a required parameter
133   @type my_set: tuple, list or set
134   @param my_set: allowed values set
135
136   """
137   fn = lambda x: x in my_set
138   err = ("The value must be one of: %s" % utils.CommaJoin(my_set))
139   return (required, fn, err, None, None)
140
141
142 class BaseHypervisor(object):
143   """Abstract virtualisation technology interface
144
145   The goal is that all aspects of the virtualisation technology are
146   abstracted away from the rest of code.
147
148   @cvar PARAMETERS: a dict of parameter name: check type; the check type is
149       a five-tuple containing:
150           - the required flag (boolean)
151           - a function to check for syntax, that will be used in
152             L{CheckParameterSyntax}, in the master daemon process
153           - an error message for the above function
154           - a function to check for parameter validity on the remote node,
155             in the L{ValidateParameters} function
156           - an error message for the above function
157   @type CAN_MIGRATE: boolean
158   @cvar CAN_MIGRATE: whether this hypervisor can do migration (either
159       live or non-live)
160
161   """
162   PARAMETERS = {}
163   ANCILLARY_FILES = []
164   ANCILLARY_FILES_OPT = []
165   CAN_MIGRATE = False
166
167   def __init__(self):
168     pass
169
170   def StartInstance(self, instance, block_devices, startup_paused):
171     """Start an instance."""
172     raise NotImplementedError
173
174   def StopInstance(self, instance, force=False, retry=False, name=None):
175     """Stop an instance
176
177     @type instance: L{objects.Instance}
178     @param instance: instance to stop
179     @type force: boolean
180     @param force: whether to do a "hard" stop (destroy)
181     @type retry: boolean
182     @param retry: whether this is just a retry call
183     @type name: string or None
184     @param name: if this parameter is passed, the the instance object
185         should not be used (will be passed as None), and the shutdown
186         must be done by name only
187
188     """
189     raise NotImplementedError
190
191   def CleanupInstance(self, instance_name):
192     """Cleanup after a stopped instance
193
194     This is an optional method, used by hypervisors that need to cleanup after
195     an instance has been stopped.
196
197     @type instance_name: string
198     @param instance_name: instance name to cleanup after
199
200     """
201     pass
202
203   def RebootInstance(self, instance):
204     """Reboot an instance."""
205     raise NotImplementedError
206
207   def ListInstances(self):
208     """Get the list of running instances."""
209     raise NotImplementedError
210
211   def GetInstanceInfo(self, instance_name):
212     """Get instance properties.
213
214     @type instance_name: string
215     @param instance_name: the instance name
216
217     @return: tuple (name, id, memory, vcpus, state, times)
218
219     """
220     raise NotImplementedError
221
222   def GetAllInstancesInfo(self):
223     """Get properties of all instances.
224
225     @return: list of tuples (name, id, memory, vcpus, stat, times)
226
227     """
228     raise NotImplementedError
229
230   def GetNodeInfo(self):
231     """Return information about the node.
232
233     @return: a dict with the following keys (values in MiB):
234           - memory_total: the total memory size on the node
235           - memory_free: the available memory on the node for instances
236           - memory_dom0: the memory used by the node itself, if available
237
238     """
239     raise NotImplementedError
240
241   @classmethod
242   def GetInstanceConsole(cls, instance, hvparams, beparams):
243     """Return information for connecting to the console of an instance.
244
245     """
246     raise NotImplementedError
247
248   @classmethod
249   def GetAncillaryFiles(cls):
250     """Return a list of ancillary files to be copied to all nodes as ancillary
251     configuration files.
252
253     @rtype: (list of absolute paths, list of absolute paths)
254     @return: (all files, optional files)
255
256     """
257     # By default we return a member variable, so that if an hypervisor has just
258     # a static list of files it doesn't have to override this function.
259     assert set(cls.ANCILLARY_FILES).issuperset(cls.ANCILLARY_FILES_OPT), \
260       "Optional ancillary files must be a subset of ancillary files"
261
262     return (cls.ANCILLARY_FILES, cls.ANCILLARY_FILES_OPT)
263
264   def Verify(self):
265     """Verify the hypervisor.
266
267     """
268     raise NotImplementedError
269
270   def MigrationInfo(self, instance): # pylint: disable=R0201,W0613
271     """Get instance information to perform a migration.
272
273     By default assume no information is needed.
274
275     @type instance: L{objects.Instance}
276     @param instance: instance to be migrated
277     @rtype: string/data (opaque)
278     @return: instance migration information - serialized form
279
280     """
281     return ""
282
283   def AcceptInstance(self, instance, info, target):
284     """Prepare to accept an instance.
285
286     By default assume no preparation is needed.
287
288     @type instance: L{objects.Instance}
289     @param instance: instance to be accepted
290     @type info: string/data (opaque)
291     @param info: migration information, from the source node
292     @type target: string
293     @param target: target host (usually ip), on this node
294
295     """
296     pass
297
298   def FinalizeMigrationDst(self, instance, info, success):
299     """Finalize the instance migration on the target node.
300
301     Should finalize or revert any preparation done to accept the instance.
302     Since by default we do no preparation, we also don't have anything to do
303
304     @type instance: L{objects.Instance}
305     @param instance: instance whose migration is being finalized
306     @type info: string/data (opaque)
307     @param info: migration information, from the source node
308     @type success: boolean
309     @param success: whether the migration was a success or a failure
310
311     """
312     pass
313
314   def MigrateInstance(self, instance, target, live):
315     """Migrate an instance.
316
317     @type instance: L{objects.Instance}
318     @param instance: the instance to be migrated
319     @type target: string
320     @param target: hostname (usually ip) of the target node
321     @type live: boolean
322     @param live: whether to do a live or non-live migration
323
324     """
325     raise NotImplementedError
326
327   def FinalizeMigrationSource(self, instance, success, live):
328     """Finalize the instance migration on the source node.
329
330     @type instance: L{objects.Instance}
331     @param instance: the instance that was migrated
332     @type success: bool
333     @param success: whether the migration succeeded or not
334     @type live: bool
335     @param live: whether the user requested a live migration or not
336
337     """
338     pass
339
340   def GetMigrationStatus(self, instance):
341     """Get the migration status
342
343     @type instance: L{objects.Instance}
344     @param instance: the instance that is being migrated
345     @rtype: L{objects.MigrationStatus}
346     @return: the status of the current migration (one of
347              L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
348              progress info that can be retrieved from the hypervisor
349
350     """
351     raise NotImplementedError
352
353   @classmethod
354   def CheckParameterSyntax(cls, hvparams):
355     """Check the given parameters for validity.
356
357     This should check the passed set of parameters for
358     validity. Classes should extend, not replace, this function.
359
360     @type hvparams:  dict
361     @param hvparams: dictionary with parameter names/value
362     @raise errors.HypervisorError: when a parameter is not valid
363
364     """
365     for key in hvparams:
366       if key not in cls.PARAMETERS:
367         raise errors.HypervisorError("Parameter '%s' is not supported" % key)
368
369     # cheap tests that run on the master, should not access the world
370     for name, (required, check_fn, errstr, _, _) in cls.PARAMETERS.items():
371       if name not in hvparams:
372         raise errors.HypervisorError("Parameter '%s' is missing" % name)
373       value = hvparams[name]
374       if not required and not value:
375         continue
376       if not value:
377         raise errors.HypervisorError("Parameter '%s' is required but"
378                                      " is currently not defined" % (name, ))
379       if check_fn is not None and not check_fn(value):
380         raise errors.HypervisorError("Parameter '%s' fails syntax"
381                                      " check: %s (current value: '%s')" %
382                                      (name, errstr, value))
383
384   @classmethod
385   def ValidateParameters(cls, hvparams):
386     """Check the given parameters for validity.
387
388     This should check the passed set of parameters for
389     validity. Classes should extend, not replace, this function.
390
391     @type hvparams:  dict
392     @param hvparams: dictionary with parameter names/value
393     @raise errors.HypervisorError: when a parameter is not valid
394
395     """
396     for name, (required, _, _, check_fn, errstr) in cls.PARAMETERS.items():
397       value = hvparams[name]
398       if not required and not value:
399         continue
400       if check_fn is not None and not check_fn(value):
401         raise errors.HypervisorError("Parameter '%s' fails"
402                                      " validation: %s (current value: '%s')" %
403                                      (name, errstr, value))
404
405   @classmethod
406   def PowercycleNode(cls):
407     """Hard powercycle a node using hypervisor specific methods.
408
409     This method should hard powercycle the node, using whatever
410     methods the hypervisor provides. Note that this means that all
411     instances running on the node must be stopped too.
412
413     """
414     raise NotImplementedError
415
416   @staticmethod
417   def GetLinuxNodeInfo():
418     """For linux systems, return actual OS information.
419
420     This is an abstraction for all non-hypervisor-based classes, where
421     the node actually sees all the memory and CPUs via the /proc
422     interface and standard commands. The other case if for example
423     xen, where you only see the hardware resources via xen-specific
424     tools.
425
426     @return: a dict with the following keys (values in MiB):
427           - memory_total: the total memory size on the node
428           - memory_free: the available memory on the node for instances
429           - memory_dom0: the memory used by the node itself, if available
430
431     """
432     try:
433       data = utils.ReadFile("/proc/meminfo").splitlines()
434     except EnvironmentError, err:
435       raise errors.HypervisorError("Failed to list node info: %s" % (err,))
436
437     result = {}
438     sum_free = 0
439     try:
440       for line in data:
441         splitfields = line.split(":", 1)
442
443         if len(splitfields) > 1:
444           key = splitfields[0].strip()
445           val = splitfields[1].strip()
446           if key == "MemTotal":
447             result["memory_total"] = int(val.split()[0]) / 1024
448           elif key in ("MemFree", "Buffers", "Cached"):
449             sum_free += int(val.split()[0]) / 1024
450           elif key == "Active":
451             result["memory_dom0"] = int(val.split()[0]) / 1024
452     except (ValueError, TypeError), err:
453       raise errors.HypervisorError("Failed to compute memory usage: %s" %
454                                    (err,))
455     result["memory_free"] = sum_free
456
457     cpu_total = 0
458     try:
459       fh = open("/proc/cpuinfo")
460       try:
461         cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
462                                    fh.read()))
463       finally:
464         fh.close()
465     except EnvironmentError, err:
466       raise errors.HypervisorError("Failed to list node info: %s" % (err,))
467     result["cpu_total"] = cpu_total
468     # FIXME: export correct data here
469     result["cpu_nodes"] = 1
470     result["cpu_sockets"] = 1
471
472     return result
473
474   @classmethod
475   def LinuxPowercycle(cls):
476     """Linux-specific powercycle method.
477
478     """
479     try:
480       fd = os.open("/proc/sysrq-trigger", os.O_WRONLY)
481       try:
482         os.write(fd, "b")
483       finally:
484         fd.close()
485     except OSError:
486       logging.exception("Can't open the sysrq-trigger file")
487       result = utils.RunCmd(["reboot", "-n", "-f"])
488       if not result:
489         logging.error("Can't run shutdown: %s", result.output)