Hypervisors: make absolute path checking strict
[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 normal 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 normal 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   def GetLinuxNodeInfo(self):
290     """For linux systems, return actual OS information.
291
292     This is an abstraction for all non-hypervisor-based classes, where
293     the node actually sees all the memory and CPUs via the /proc
294     interface and standard commands. The other case if for example
295     xen, where you only see the hardware resources via xen-specific
296     tools.
297
298     @return: a dict with the following keys (values in MiB):
299           - memory_total: the total memory size on the node
300           - memory_free: the available memory on the node for instances
301           - memory_dom0: the memory used by the node itself, if available
302
303     """
304     try:
305       fh = file("/proc/meminfo")
306       try:
307         data = fh.readlines()
308       finally:
309         fh.close()
310     except EnvironmentError, err:
311       raise errors.HypervisorError("Failed to list node info: %s" % (err,))
312
313     result = {}
314     sum_free = 0
315     try:
316       for line in data:
317         splitfields = line.split(":", 1)
318
319         if len(splitfields) > 1:
320           key = splitfields[0].strip()
321           val = splitfields[1].strip()
322           if key == 'MemTotal':
323             result['memory_total'] = int(val.split()[0])/1024
324           elif key in ('MemFree', 'Buffers', 'Cached'):
325             sum_free += int(val.split()[0])/1024
326           elif key == 'Active':
327             result['memory_dom0'] = int(val.split()[0])/1024
328     except (ValueError, TypeError), err:
329       raise errors.HypervisorError("Failed to compute memory usage: %s" %
330                                    (err,))
331     result['memory_free'] = sum_free
332
333     cpu_total = 0
334     try:
335       fh = open("/proc/cpuinfo")
336       try:
337         cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
338                                    fh.read()))
339       finally:
340         fh.close()
341     except EnvironmentError, err:
342       raise errors.HypervisorError("Failed to list node info: %s" % (err,))
343     result['cpu_total'] = cpu_total
344     # FIXME: export correct data here
345     result['cpu_nodes'] = 1
346     result['cpu_sockets'] = 1
347
348     return result