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