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