4 # Copyright (C) 2006, 2007, 2008, 2009, 2010 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
47 from ganeti import constants
50 def _IsCpuMaskWellFormed(cpu_mask):
52 cpu_list = utils.ParseCpuMask(cpu_mask)
53 except errors.ParseError, _:
55 return isinstance(cpu_list, list) and len(cpu_list) > 0
58 # Read the BaseHypervisor.PARAMETERS docstring for the syntax of the
62 _FILE_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
63 os.path.isfile, "not found or not a file")
66 _DIR_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
67 os.path.isdir, "not found or not a directory")
69 # CPU mask must be well-formed
70 # TODO: implement node level check for the CPU mask
71 _CPU_MASK_CHECK = (_IsCpuMaskWellFormed,
72 "CPU mask definition is not well-formed",
75 # nice wrappers for users
76 REQ_FILE_CHECK = (True, ) + _FILE_CHECK
77 OPT_FILE_CHECK = (False, ) + _FILE_CHECK
78 REQ_DIR_CHECK = (True, ) + _DIR_CHECK
79 OPT_DIR_CHECK = (False, ) + _DIR_CHECK
80 NET_PORT_CHECK = (True, lambda x: x > 0 and x < 65535, "invalid port number",
82 OPT_CPU_MASK_CHECK = (False, ) + _CPU_MASK_CHECK
83 REQ_CPU_MASK_CHECK = (True, ) + _CPU_MASK_CHECK
86 NO_CHECK = (False, None, None, None, None)
88 # required, but no other checks
89 REQUIRED_CHECK = (True, None, None, None, None)
92 MIGRATION_MODE_CHECK = (True, lambda x: x in constants.HT_MIGRATION_MODES,
93 "invalid migration mode", None, None)
96 def ParamInSet(required, my_set):
97 """Builds parameter checker for set membership.
99 @type required: boolean
100 @param required: whether this is a required parameter
101 @type my_set: tuple, list or set
102 @param my_set: allowed values set
105 fn = lambda x: x in my_set
106 err = ("The value must be one of: %s" % utils.CommaJoin(my_set))
107 return (required, fn, err, None, None)
110 class BaseHypervisor(object):
111 """Abstract virtualisation technology interface
113 The goal is that all aspects of the virtualisation technology are
114 abstracted away from the rest of code.
116 @cvar PARAMETERS: a dict of parameter name: check type; the check type is
117 a five-tuple containing:
118 - the required flag (boolean)
119 - a function to check for syntax, that will be used in
120 L{CheckParameterSyntax}, in the master daemon process
121 - an error message for the above function
122 - a function to check for parameter validity on the remote node,
123 in the L{ValidateParameters} function
124 - an error message for the above function
125 @type CAN_MIGRATE: boolean
126 @cvar CAN_MIGRATE: whether this hypervisor can do migration (either
137 def StartInstance(self, instance, block_devices):
138 """Start an instance."""
139 raise NotImplementedError
141 def StopInstance(self, instance, force=False, retry=False, name=None):
144 @type instance: L{objects.Instance}
145 @param instance: instance to stop
147 @param force: whether to do a "hard" stop (destroy)
149 @param retry: whether this is just a retry call
150 @type name: string or None
151 @param name: if this parameter is passed, the the instance object
152 should not be used (will be passed as None), and the shutdown
153 must be done by name only
156 raise NotImplementedError
158 def CleanupInstance(self, instance_name):
159 """Cleanup after a stopped instance
161 This is an optional method, used by hypervisors that need to cleanup after
162 an instance has been stopped.
164 @type instance_name: string
165 @param instance_name: instance name to cleanup after
170 def RebootInstance(self, instance):
171 """Reboot an instance."""
172 raise NotImplementedError
174 def ListInstances(self):
175 """Get the list of running instances."""
176 raise NotImplementedError
178 def GetInstanceInfo(self, instance_name):
179 """Get instance properties.
181 @type instance_name: string
182 @param instance_name: the instance name
184 @return: tuple (name, id, memory, vcpus, state, times)
187 raise NotImplementedError
189 def GetAllInstancesInfo(self):
190 """Get properties of all instances.
192 @return: list of tuples (name, id, memory, vcpus, stat, times)
195 raise NotImplementedError
197 def GetNodeInfo(self):
198 """Return information about the node.
200 @return: a dict with the following keys (values in MiB):
201 - memory_total: the total memory size on the node
202 - memory_free: the available memory on the node for instances
203 - memory_dom0: the memory used by the node itself, if available
206 raise NotImplementedError
209 def GetInstanceConsole(cls, instance, hvparams, beparams):
210 """Return information for connecting to the console of an instance.
213 raise NotImplementedError
216 def GetAncillaryFiles(cls):
217 """Return a list of ancillary files to be copied to all nodes as ancillary
220 @rtype: list of strings
221 @return: list of absolute paths of files to ship cluster-wide
224 # By default we return a member variable, so that if an hypervisor has just
225 # a static list of files it doesn't have to override this function.
226 return cls.ANCILLARY_FILES
229 """Verify the hypervisor.
232 raise NotImplementedError
234 def MigrationInfo(self, instance): # pylint: disable-msg=R0201,W0613
235 """Get instance information to perform a migration.
237 By default assume no information is needed.
239 @type instance: L{objects.Instance}
240 @param instance: instance to be migrated
241 @rtype: string/data (opaque)
242 @return: instance migration information - serialized form
247 def AcceptInstance(self, instance, info, target):
248 """Prepare to accept an instance.
250 By default assume no preparation is needed.
252 @type instance: L{objects.Instance}
253 @param instance: instance to be accepted
254 @type info: string/data (opaque)
255 @param info: migration information, from the source node
257 @param target: target host (usually ip), on this node
262 def FinalizeMigration(self, instance, info, success):
263 """Finalized an instance migration.
265 Should finalize or revert any preparation done to accept the instance.
266 Since by default we do no preparation, we also don't have anything to do
268 @type instance: L{objects.Instance}
269 @param instance: instance whose migration is being finalized
270 @type info: string/data (opaque)
271 @param info: migration information, from the source node
272 @type success: boolean
273 @param success: whether the migration was a success or a failure
278 def MigrateInstance(self, instance, target, live):
279 """Migrate an instance.
281 @type instance: L{objects.Instance}
282 @param instance: the instance to be migrated
284 @param target: hostname (usually ip) of the target node
286 @param live: whether to do a live or non-live migration
289 raise NotImplementedError
292 def CheckParameterSyntax(cls, hvparams):
293 """Check the given parameters for validity.
295 This should check the passed set of parameters for
296 validity. Classes should extend, not replace, this function.
299 @param hvparams: dictionary with parameter names/value
300 @raise errors.HypervisorError: when a parameter is not valid
304 if key not in cls.PARAMETERS:
305 raise errors.HypervisorError("Parameter '%s' is not supported" % key)
307 # cheap tests that run on the master, should not access the world
308 for name, (required, check_fn, errstr, _, _) in cls.PARAMETERS.items():
309 if name not in hvparams:
310 raise errors.HypervisorError("Parameter '%s' is missing" % name)
311 value = hvparams[name]
312 if not required and not value:
315 raise errors.HypervisorError("Parameter '%s' is required but"
316 " is currently not defined" % (name, ))
317 if check_fn is not None and not check_fn(value):
318 raise errors.HypervisorError("Parameter '%s' fails syntax"
319 " check: %s (current value: '%s')" %
320 (name, errstr, value))
323 def ValidateParameters(cls, hvparams):
324 """Check the given parameters for validity.
326 This should check the passed set of parameters for
327 validity. Classes should extend, not replace, this function.
330 @param hvparams: dictionary with parameter names/value
331 @raise errors.HypervisorError: when a parameter is not valid
334 for name, (required, _, _, check_fn, errstr) in cls.PARAMETERS.items():
335 value = hvparams[name]
336 if not required and not value:
338 if check_fn is not None and not check_fn(value):
339 raise errors.HypervisorError("Parameter '%s' fails"
340 " validation: %s (current value: '%s')" %
341 (name, errstr, value))
344 def PowercycleNode(cls):
345 """Hard powercycle a node using hypervisor specific methods.
347 This method should hard powercycle the node, using whatever
348 methods the hypervisor provides. Note that this means that all
349 instances running on the node must be stopped too.
352 raise NotImplementedError
355 def GetLinuxNodeInfo():
356 """For linux systems, return actual OS information.
358 This is an abstraction for all non-hypervisor-based classes, where
359 the node actually sees all the memory and CPUs via the /proc
360 interface and standard commands. The other case if for example
361 xen, where you only see the hardware resources via xen-specific
364 @return: a dict with the following keys (values in MiB):
365 - memory_total: the total memory size on the node
366 - memory_free: the available memory on the node for instances
367 - memory_dom0: the memory used by the node itself, if available
371 data = utils.ReadFile("/proc/meminfo").splitlines()
372 except EnvironmentError, err:
373 raise errors.HypervisorError("Failed to list node info: %s" % (err,))
379 splitfields = line.split(":", 1)
381 if len(splitfields) > 1:
382 key = splitfields[0].strip()
383 val = splitfields[1].strip()
384 if key == 'MemTotal':
385 result['memory_total'] = int(val.split()[0])/1024
386 elif key in ('MemFree', 'Buffers', 'Cached'):
387 sum_free += int(val.split()[0])/1024
388 elif key == 'Active':
389 result['memory_dom0'] = int(val.split()[0])/1024
390 except (ValueError, TypeError), err:
391 raise errors.HypervisorError("Failed to compute memory usage: %s" %
393 result['memory_free'] = sum_free
397 fh = open("/proc/cpuinfo")
399 cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
403 except EnvironmentError, err:
404 raise errors.HypervisorError("Failed to list node info: %s" % (err,))
405 result['cpu_total'] = cpu_total
406 # FIXME: export correct data here
407 result['cpu_nodes'] = 1
408 result['cpu_sockets'] = 1
413 def LinuxPowercycle(cls):
414 """Linux-specific powercycle method.
418 fd = os.open("/proc/sysrq-trigger", os.O_WRONLY)
424 logging.exception("Can't open the sysrq-trigger file")
425 result = utils.RunCmd(["reboot", "-n", "-f"])
427 logging.error("Can't run shutdown: %s", result.output)