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