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
65 NET_PORT_CHECK = (True, lambda x: x > 0 and x < 65535, "invalid port number",
69 NO_CHECK = (False, None, None, None, None)
71 # required, but no other checks
72 REQUIRED_CHECK = (True, None, None, None, None)
75 def ParamInSet(required, my_set):
76 """Builds parameter checker for set membership.
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
84 fn = lambda x: x in my_set
85 err = ("The value must be one of: %s" % utils.CommaJoin(my_set))
86 return (required, fn, err, None, None)
89 class BaseHypervisor(object):
90 """Abstract virtualisation technology interface
92 The goal is that all aspects of the virtualisation technology are
93 abstracted away from the rest of code.
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
112 def StartInstance(self, instance, block_devices):
113 """Start an instance."""
114 raise NotImplementedError
116 def StopInstance(self, instance, force=False, retry=False):
119 @type instance: L{objects.Instance}
120 @param instance: instance to stop
122 @param force: whether to do a "hard" stop (destroy)
124 @param retry: whether this is just a retry call
127 raise NotImplementedError
129 def RebootInstance(self, instance):
130 """Reboot an instance."""
131 raise NotImplementedError
133 def ListInstances(self):
134 """Get the list of running instances."""
135 raise NotImplementedError
137 def GetInstanceInfo(self, instance_name):
138 """Get instance properties.
140 @type instance_name: string
141 @param instance_name: the instance name
143 @return: tuple (name, id, memory, vcpus, state, times)
146 raise NotImplementedError
148 def GetAllInstancesInfo(self):
149 """Get properties of all instances.
151 @return: list of tuples (name, id, memory, vcpus, stat, times)
154 raise NotImplementedError
156 def GetNodeInfo(self):
157 """Return information about the node.
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
165 raise NotImplementedError
168 def GetShellCommandForConsole(cls, instance, hvparams, beparams):
169 """Return a command for connecting to the console of an instance.
172 raise NotImplementedError
175 def GetAncillaryFiles(cls):
176 """Return a list of ancillary files to be copied to all nodes as ancillary
179 @rtype: list of strings
180 @return: list of absolute paths of files to ship cluster-wide
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
188 """Verify the hypervisor.
191 raise NotImplementedError
193 def MigrationInfo(self, instance): # pylint: disable-msg=R0201,W0613
194 """Get instance information to perform a migration.
196 By default assume no information is needed.
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
206 def AcceptInstance(self, instance, info, target):
207 """Prepare to accept an instance.
209 By default assume no preparation is needed.
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
216 @param target: target host (usually ip), on this node
221 def FinalizeMigration(self, instance, info, success):
222 """Finalized an instance migration.
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
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
237 def MigrateInstance(self, instance, target, live):
238 """Migrate an instance.
240 @type instance: L{object.Instance}
241 @param instance: the instance to be migrated
243 @param target: hostname (usually ip) of the target node
245 @param live: whether to do a live or non-live migration
248 raise NotImplementedError
251 def CheckParameterSyntax(cls, hvparams):
252 """Check the given parameters for validity.
254 This should check the passed set of parameters for
255 validity. Classes should extend, not replace, this function.
258 @param hvparams: dictionary with parameter names/value
259 @raise errors.HypervisorError: when a parameter is not valid
263 if key not in cls.PARAMETERS:
264 raise errors.HypervisorError("Parameter '%s' is not supported" % key)
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:
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))
282 def ValidateParameters(cls, hvparams):
283 """Check the given parameters for validity.
285 This should check the passed set of parameters for
286 validity. Classes should extend, not replace, this function.
289 @param hvparams: dictionary with parameter names/value
290 @raise errors.HypervisorError: when a parameter is not valid
293 for name, (required, _, _, check_fn, errstr) in cls.PARAMETERS.items():
294 value = hvparams[name]
295 if not required and not value:
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))
303 def PowercycleNode(cls):
304 """Hard powercycle a node using hypervisor specific methods.
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.
311 raise NotImplementedError
314 def GetLinuxNodeInfo():
315 """For linux systems, return actual OS information.
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
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
330 data = utils.ReadFile("/proc/meminfo").splitlines()
331 except EnvironmentError, err:
332 raise errors.HypervisorError("Failed to list node info: %s" % (err,))
338 splitfields = line.split(":", 1)
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" %
352 result['memory_free'] = sum_free
356 fh = open("/proc/cpuinfo")
358 cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
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
372 def LinuxPowercycle(cls):
373 """Linux-specific powercycle method.
377 fd = os.open("/proc/sysrq-trigger", os.O_WRONLY)
383 logging.exception("Can't open the sysrq-trigger file")
384 result = utils.RunCmd(["reboot", "-n", "-f"])
386 logging.error("Can't run shutdown: %s", result.output)