Bump pep8 version to 1.2
[ganeti-local] / lib / hypervisor / hv_base.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2012 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 BalloonInstanceMemory(self, instance, mem):
299     """Balloon an instance memory to a certain value.
300
301     @type instance: L{objects.Instance}
302     @param instance: instance to be accepted
303     @type mem: int
304     @param mem: actual memory size to use for instance runtime
305
306     """
307     raise NotImplementedError
308
309   def FinalizeMigrationDst(self, instance, info, success):
310     """Finalize the instance migration on the target node.
311
312     Should finalize or revert any preparation done to accept the instance.
313     Since by default we do no preparation, we also don't have anything to do
314
315     @type instance: L{objects.Instance}
316     @param instance: instance whose migration is being finalized
317     @type info: string/data (opaque)
318     @param info: migration information, from the source node
319     @type success: boolean
320     @param success: whether the migration was a success or a failure
321
322     """
323     pass
324
325   def MigrateInstance(self, instance, target, live):
326     """Migrate an instance.
327
328     @type instance: L{objects.Instance}
329     @param instance: the instance to be migrated
330     @type target: string
331     @param target: hostname (usually ip) of the target node
332     @type live: boolean
333     @param live: whether to do a live or non-live migration
334
335     """
336     raise NotImplementedError
337
338   def FinalizeMigrationSource(self, instance, success, live):
339     """Finalize the instance migration on the source node.
340
341     @type instance: L{objects.Instance}
342     @param instance: the instance that was migrated
343     @type success: bool
344     @param success: whether the migration succeeded or not
345     @type live: bool
346     @param live: whether the user requested a live migration or not
347
348     """
349     pass
350
351   def GetMigrationStatus(self, instance):
352     """Get the migration status
353
354     @type instance: L{objects.Instance}
355     @param instance: the instance that is being migrated
356     @rtype: L{objects.MigrationStatus}
357     @return: the status of the current migration (one of
358              L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
359              progress info that can be retrieved from the hypervisor
360
361     """
362     raise NotImplementedError
363
364   def _InstanceStartupMemory(self, instance):
365     """Get the correct startup memory for an instance
366
367     This function calculates how much memory an instance should be started
368     with, making sure it's a value between the minimum and the maximum memory,
369     but also trying to use no more than the current free memory on the node.
370
371     @type instance: L{objects.Instance}
372     @param instance: the instance that is being started
373     @rtype: integer
374     @return: memory the instance should be started with
375
376     """
377     free_memory = self.GetNodeInfo()["memory_free"]
378     max_start_mem = min(instance.beparams[constants.BE_MAXMEM], free_memory)
379     start_mem = max(instance.beparams[constants.BE_MINMEM], max_start_mem)
380     return start_mem
381
382   @classmethod
383   def CheckParameterSyntax(cls, hvparams):
384     """Check the given parameters for validity.
385
386     This should check the passed set of parameters for
387     validity. Classes should extend, not replace, this function.
388
389     @type hvparams:  dict
390     @param hvparams: dictionary with parameter names/value
391     @raise errors.HypervisorError: when a parameter is not valid
392
393     """
394     for key in hvparams:
395       if key not in cls.PARAMETERS:
396         raise errors.HypervisorError("Parameter '%s' is not supported" % key)
397
398     # cheap tests that run on the master, should not access the world
399     for name, (required, check_fn, errstr, _, _) in cls.PARAMETERS.items():
400       if name not in hvparams:
401         raise errors.HypervisorError("Parameter '%s' is missing" % name)
402       value = hvparams[name]
403       if not required and not value:
404         continue
405       if not value:
406         raise errors.HypervisorError("Parameter '%s' is required but"
407                                      " is currently not defined" % (name, ))
408       if check_fn is not None and not check_fn(value):
409         raise errors.HypervisorError("Parameter '%s' fails syntax"
410                                      " check: %s (current value: '%s')" %
411                                      (name, errstr, value))
412
413   @classmethod
414   def ValidateParameters(cls, hvparams):
415     """Check the given parameters for validity.
416
417     This should check the passed set of parameters for
418     validity. Classes should extend, not replace, this function.
419
420     @type hvparams:  dict
421     @param hvparams: dictionary with parameter names/value
422     @raise errors.HypervisorError: when a parameter is not valid
423
424     """
425     for name, (required, _, _, check_fn, errstr) in cls.PARAMETERS.items():
426       value = hvparams[name]
427       if not required and not value:
428         continue
429       if check_fn is not None and not check_fn(value):
430         raise errors.HypervisorError("Parameter '%s' fails"
431                                      " validation: %s (current value: '%s')" %
432                                      (name, errstr, value))
433
434   @classmethod
435   def PowercycleNode(cls):
436     """Hard powercycle a node using hypervisor specific methods.
437
438     This method should hard powercycle the node, using whatever
439     methods the hypervisor provides. Note that this means that all
440     instances running on the node must be stopped too.
441
442     """
443     raise NotImplementedError
444
445   @staticmethod
446   def GetLinuxNodeInfo():
447     """For linux systems, return actual OS information.
448
449     This is an abstraction for all non-hypervisor-based classes, where
450     the node actually sees all the memory and CPUs via the /proc
451     interface and standard commands. The other case if for example
452     xen, where you only see the hardware resources via xen-specific
453     tools.
454
455     @return: a dict with the following keys (values in MiB):
456           - memory_total: the total memory size on the node
457           - memory_free: the available memory on the node for instances
458           - memory_dom0: the memory used by the node itself, if available
459
460     """
461     try:
462       data = utils.ReadFile("/proc/meminfo").splitlines()
463     except EnvironmentError, err:
464       raise errors.HypervisorError("Failed to list node info: %s" % (err,))
465
466     result = {}
467     sum_free = 0
468     try:
469       for line in data:
470         splitfields = line.split(":", 1)
471
472         if len(splitfields) > 1:
473           key = splitfields[0].strip()
474           val = splitfields[1].strip()
475           if key == "MemTotal":
476             result["memory_total"] = int(val.split()[0]) / 1024
477           elif key in ("MemFree", "Buffers", "Cached"):
478             sum_free += int(val.split()[0]) / 1024
479           elif key == "Active":
480             result["memory_dom0"] = int(val.split()[0]) / 1024
481     except (ValueError, TypeError), err:
482       raise errors.HypervisorError("Failed to compute memory usage: %s" %
483                                    (err,))
484     result["memory_free"] = sum_free
485
486     cpu_total = 0
487     try:
488       fh = open("/proc/cpuinfo")
489       try:
490         cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
491                                    fh.read()))
492       finally:
493         fh.close()
494     except EnvironmentError, err:
495       raise errors.HypervisorError("Failed to list node info: %s" % (err,))
496     result["cpu_total"] = cpu_total
497     # FIXME: export correct data here
498     result["cpu_nodes"] = 1
499     result["cpu_sockets"] = 1
500
501     return result
502
503   @classmethod
504   def LinuxPowercycle(cls):
505     """Linux-specific powercycle method.
506
507     """
508     try:
509       fd = os.open("/proc/sysrq-trigger", os.O_WRONLY)
510       try:
511         os.write(fd, "b")
512       finally:
513         fd.close()
514     except OSError:
515       logging.exception("Can't open the sysrq-trigger file")
516       result = utils.RunCmd(["reboot", "-n", "-f"])
517       if not result:
518         logging.error("Can't run shutdown: %s", result.output)