4 # Copyright (C) 2006, 2007, 2008 Google Inc.
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.
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.
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
22 """Base class for all hypervisors
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}.
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
45 from ganeti import errors
46 from ganeti import utils
49 # Read the BaseHypervisor.PARAMETERS docstring for the syntax of the
53 _FILE_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
54 os.path.isfile, "not found or not a file")
57 _DIR_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
58 os.path.isdir, "not found or not a directory")
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
67 NO_CHECK = (False, None, None, None, None)
69 # required, but no other checks
70 REQUIRED_CHECK = (True, None, None, None, None)
72 def ParamInSet(required, my_set):
73 """Builds parameter checker for set membership.
75 @type required: boolean
76 @param required: whether this is a required parameter
77 @type my_set: tuple, list or set
78 @param my_set: allowed values set
81 fn = lambda x: x in my_set
82 err = ("The value must be one of: %s" % utils.CommaJoin(my_set))
83 return (required, fn, err, None, None)
86 class BaseHypervisor(object):
87 """Abstract virtualisation technology interface
89 The goal is that all aspects of the virtualisation technology are
90 abstracted away from the rest of code.
92 @cvar PARAMETERS: a dict of parameter name: check type; the check type is
93 a five-tuple containing:
94 - the required flag (boolean)
95 - a function to check for syntax, that will be used in
96 L{CheckParameterSyntax}, in the master daemon process
97 - an error message for the above function
98 - a function to check for parameter validity on the remote node,
99 in the L{ValidateParameters} function
100 - an error message for the above function
109 def StartInstance(self, instance, block_devices):
110 """Start an instance."""
111 raise NotImplementedError
113 def StopInstance(self, instance, force=False):
114 """Stop an instance."""
115 raise NotImplementedError
117 def RebootInstance(self, instance):
118 """Reboot an instance."""
119 raise NotImplementedError
121 def ListInstances(self):
122 """Get the list of running instances."""
123 raise NotImplementedError
125 def GetInstanceInfo(self, instance_name):
126 """Get instance properties.
128 @type instance_name: string
129 @param instance_name: the instance name
131 @return: tuple (name, id, memory, vcpus, state, times)
134 raise NotImplementedError
136 def GetAllInstancesInfo(self):
137 """Get properties of all instances.
139 @return: list of tuples (name, id, memory, vcpus, stat, times)
142 raise NotImplementedError
144 def GetNodeInfo(self):
145 """Return information about the node.
147 @return: a dict with the following keys (values in MiB):
148 - memory_total: the total memory size on the node
149 - memory_free: the available memory on the node for instances
150 - memory_dom0: the memory used by the node itself, if available
153 raise NotImplementedError
156 def GetShellCommandForConsole(cls, instance, hvparams, beparams):
157 """Return a command for connecting to the console of an instance.
160 raise NotImplementedError
163 def GetAncillaryFiles(cls):
164 """Return a list of ancillary files to be copied to all nodes as ancillary
167 @rtype: list of strings
168 @return: list of absolute paths of files to ship cluster-wide
171 # By default we return a member variable, so that if an hypervisor has just
172 # a static list of files it doesn't have to override this function.
173 return cls.ANCILLARY_FILES
176 """Verify the hypervisor.
179 raise NotImplementedError
181 def MigrationInfo(self, instance):
182 """Get instance information to perform a migration.
184 By default assume no information is needed.
186 @type instance: L{objects.Instance}
187 @param instance: instance to be migrated
188 @rtype: string/data (opaque)
189 @return: instance migration information - serialized form
194 def AcceptInstance(self, instance, info, target):
195 """Prepare to accept an instance.
197 By default assume no preparation is needed.
199 @type instance: L{objects.Instance}
200 @param instance: instance to be accepted
201 @type info: string/data (opaque)
202 @param info: migration information, from the source node
204 @param target: target host (usually ip), on this node
209 def FinalizeMigration(self, instance, info, success):
210 """Finalized an instance migration.
212 Should finalize or revert any preparation done to accept the instance.
213 Since by default we do no preparation, we also don't have anything to do
215 @type instance: L{objects.Instance}
216 @param instance: instance whose migration is being aborted
217 @type info: string/data (opaque)
218 @param info: migration information, from the source node
219 @type success: boolean
220 @param success: whether the migration was a success or a failure
225 def MigrateInstance(self, name, target, live):
226 """Migrate an instance.
229 @param name: name of the instance to be migrated
231 @param target: hostname (usually ip) of the target node
233 @param live: whether to do a live or non-live migration
236 raise NotImplementedError
239 def CheckParameterSyntax(cls, hvparams):
240 """Check the given parameters for validity.
242 This should check the passed set of parameters for
243 validity. Classes should extend, not replace, this function.
246 @param hvparams: dictionary with parameter names/value
247 @raise errors.HypervisorError: when a parameter is not valid
251 if key not in cls.PARAMETERS:
252 raise errors.HypervisorError("Parameter '%s' is not supported" % key)
254 # cheap tests that run on the master, should not access the world
255 for name, (required, check_fn, errstr, _, _) in cls.PARAMETERS.items():
256 if name not in hvparams:
257 raise errors.HypervisorError("Parameter '%s' is missing" % name)
258 value = hvparams[name]
259 if not required and not value:
262 raise errors.HypervisorError("Parameter '%s' is required but"
263 " is currently not defined" % (name, ))
264 if check_fn is not None and not check_fn(value):
265 raise errors.HypervisorError("Parameter '%s' fails syntax"
266 " check: %s (current value: '%s')" %
267 (name, errstr, value))
270 def ValidateParameters(cls, hvparams):
271 """Check the given parameters for validity.
273 This should check the passed set of parameters for
274 validity. Classes should extend, not replace, this function.
277 @param hvparams: dictionary with parameter names/value
278 @raise errors.HypervisorError: when a parameter is not valid
281 for name, (required, _, _, check_fn, errstr) in cls.PARAMETERS.items():
282 value = hvparams[name]
283 if not required and not value:
285 if check_fn is not None and not check_fn(value):
286 raise errors.HypervisorError("Parameter '%s' fails"
287 " validation: %s (current value: '%s')" %
288 (name, errstr, value))
291 def PowercycleNode(cls):
292 """Hard powercycle a node using hypervisor specific methods.
294 This method should hard powercycle the node, using whatever
295 methods the hypervisor provides. Note that this means that all
296 instances running on the node must be stopped too.
299 raise NotImplementedError
302 def GetLinuxNodeInfo(self):
303 """For linux systems, return actual OS information.
305 This is an abstraction for all non-hypervisor-based classes, where
306 the node actually sees all the memory and CPUs via the /proc
307 interface and standard commands. The other case if for example
308 xen, where you only see the hardware resources via xen-specific
311 @return: a dict with the following keys (values in MiB):
312 - memory_total: the total memory size on the node
313 - memory_free: the available memory on the node for instances
314 - memory_dom0: the memory used by the node itself, if available
318 data = utils.ReadFile("/proc/meminfo").splitlines()
319 except EnvironmentError, err:
320 raise errors.HypervisorError("Failed to list node info: %s" % (err,))
326 splitfields = line.split(":", 1)
328 if len(splitfields) > 1:
329 key = splitfields[0].strip()
330 val = splitfields[1].strip()
331 if key == 'MemTotal':
332 result['memory_total'] = int(val.split()[0])/1024
333 elif key in ('MemFree', 'Buffers', 'Cached'):
334 sum_free += int(val.split()[0])/1024
335 elif key == 'Active':
336 result['memory_dom0'] = int(val.split()[0])/1024
337 except (ValueError, TypeError), err:
338 raise errors.HypervisorError("Failed to compute memory usage: %s" %
340 result['memory_free'] = sum_free
344 fh = open("/proc/cpuinfo")
346 cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
350 except EnvironmentError, err:
351 raise errors.HypervisorError("Failed to list node info: %s" % (err,))
352 result['cpu_total'] = cpu_total
353 # FIXME: export correct data here
354 result['cpu_nodes'] = 1
355 result['cpu_sockets'] = 1
360 def LinuxPowercycle(cls):
361 """Linux-specific powercycle method.
365 fd = os.open("/proc/sysrq-trigger", os.O_WRONLY)
371 logging.exception("Can't open the sysrq-trigger file")
372 result = utils.RunCmd(["reboot", "-n", "-f"])
374 logging.error("Can't run shutdown: %s", result.output)