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