1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 import logging
43
44
45 from ganeti import errors
46 from ganeti import utils
47 from ganeti import constants
62
77
78
79
80
81
82
83 _FILE_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
84 os.path.isfile, "not found or not a file")
85
86
87 _DIR_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
88 os.path.isdir, "not found or not a directory")
89
90
91
92 _CPU_MASK_CHECK = (_IsCpuMaskWellFormed,
93 "CPU mask definition is not well-formed",
94 None, None)
95
96
97 _MULTI_CPU_MASK_CHECK = (_IsMultiCpuMaskWellFormed,
98 "Multiple CPU mask definition is not well-formed",
99 None, None)
100
101
102 _NET_PORT_CHECK = (lambda x: 0 < x < 65535, "invalid port number",
103 None, None)
104
105
106 _NONNEGATIVE_INT_CHECK = (lambda x: x >= 0, "cannot be negative", None, None)
107
108
109 REQ_FILE_CHECK = (True, ) + _FILE_CHECK
110 OPT_FILE_CHECK = (False, ) + _FILE_CHECK
111 REQ_DIR_CHECK = (True, ) + _DIR_CHECK
112 OPT_DIR_CHECK = (False, ) + _DIR_CHECK
113 REQ_NET_PORT_CHECK = (True, ) + _NET_PORT_CHECK
114 OPT_NET_PORT_CHECK = (False, ) + _NET_PORT_CHECK
115 REQ_CPU_MASK_CHECK = (True, ) + _CPU_MASK_CHECK
116 OPT_CPU_MASK_CHECK = (False, ) + _CPU_MASK_CHECK
117 REQ_MULTI_CPU_MASK_CHECK = (True, ) + _MULTI_CPU_MASK_CHECK
118 OPT_MULTI_CPU_MASK_CHECK = (False, ) + _MULTI_CPU_MASK_CHECK
119 REQ_NONNEGATIVE_INT_CHECK = (True, ) + _NONNEGATIVE_INT_CHECK
120 OPT_NONNEGATIVE_INT_CHECK = (False, ) + _NONNEGATIVE_INT_CHECK
121
122
123 NO_CHECK = (False, None, None, None, None)
124
125
126 REQUIRED_CHECK = (True, None, None, None, None)
127
128
129 MIGRATION_MODE_CHECK = (True, lambda x: x in constants.HT_MIGRATION_MODES,
130 "invalid migration mode", None, None)
134 """Builds parameter checker for set membership.
135
136 @type required: boolean
137 @param required: whether this is a required parameter
138 @type my_set: tuple, list or set
139 @param my_set: allowed values set
140
141 """
142 fn = lambda x: x in my_set
143 err = ("The value must be one of: %s" % utils.CommaJoin(my_set))
144 return (required, fn, err, None, None)
145
148 """Abstract virtualisation technology interface
149
150 The goal is that all aspects of the virtualisation technology are
151 abstracted away from the rest of code.
152
153 @cvar PARAMETERS: a dict of parameter name: check type; the check type is
154 a five-tuple containing:
155 - the required flag (boolean)
156 - a function to check for syntax, that will be used in
157 L{CheckParameterSyntax}, in the master daemon process
158 - an error message for the above function
159 - a function to check for parameter validity on the remote node,
160 in the L{ValidateParameters} function
161 - an error message for the above function
162 @type CAN_MIGRATE: boolean
163 @cvar CAN_MIGRATE: whether this hypervisor can do migration (either
164 live or non-live)
165
166 """
167 PARAMETERS = {}
168 ANCILLARY_FILES = []
169 ANCILLARY_FILES_OPT = []
170 CAN_MIGRATE = False
171
172 - def StartInstance(self, instance, block_devices, startup_paused):
173 """Start an instance."""
174 raise NotImplementedError
175
176 - def StopInstance(self, instance, force=False, retry=False, name=None,
177 timeout=None):
178 """Stop an instance
179
180 @type instance: L{objects.Instance}
181 @param instance: instance to stop
182 @type force: boolean
183 @param force: whether to do a "hard" stop (destroy)
184 @type retry: boolean
185 @param retry: whether this is just a retry call
186 @type name: string or None
187 @param name: if this parameter is passed, the the instance object
188 should not be used (will be passed as None), and the shutdown
189 must be done by name only
190 @type timeout: int or None
191 @param timeout: if the parameter is not None, a soft shutdown operation will
192 be killed after the specified number of seconds. A hard (forced)
193 shutdown cannot have a timeout
194
195 """
196 raise NotImplementedError
197
199 """Cleanup after a stopped instance
200
201 This is an optional method, used by hypervisors that need to cleanup after
202 an instance has been stopped.
203
204 @type instance_name: string
205 @param instance_name: instance name to cleanup after
206
207 """
208 pass
209
211 """Reboot an instance."""
212 raise NotImplementedError
213
215 """Get the list of running instances."""
216 raise NotImplementedError
217
219 """Get instance properties.
220
221 @type instance_name: string
222 @param instance_name: the instance name
223
224 @return: tuple (name, id, memory, vcpus, state, times)
225
226 """
227 raise NotImplementedError
228
230 """Get properties of all instances.
231
232 @return: list of tuples (name, id, memory, vcpus, stat, times)
233
234 """
235 raise NotImplementedError
236
238 """Return information about the node.
239
240 @return: a dict with the following keys (values in MiB):
241 - memory_total: the total memory size on the node
242 - memory_free: the available memory on the node for instances
243 - memory_dom0: the memory used by the node itself, if available
244
245 """
246 raise NotImplementedError
247
248 @classmethod
250 """Return information for connecting to the console of an instance.
251
252 """
253 raise NotImplementedError
254
255 @classmethod
257 """Return a list of ancillary files to be copied to all nodes as ancillary
258 configuration files.
259
260 @rtype: (list of absolute paths, list of absolute paths)
261 @return: (all files, optional files)
262
263 """
264
265
266 assert set(cls.ANCILLARY_FILES).issuperset(cls.ANCILLARY_FILES_OPT), \
267 "Optional ancillary files must be a subset of ancillary files"
268
269 return (cls.ANCILLARY_FILES, cls.ANCILLARY_FILES_OPT)
270
272 """Verify the hypervisor.
273
274 @return: Problem description if something is wrong, C{None} otherwise
275
276 """
277 raise NotImplementedError
278
280 """Get instance information to perform a migration.
281
282 By default assume no information is needed.
283
284 @type instance: L{objects.Instance}
285 @param instance: instance to be migrated
286 @rtype: string/data (opaque)
287 @return: instance migration information - serialized form
288
289 """
290 return ""
291
293 """Prepare to accept an instance.
294
295 By default assume no preparation is needed.
296
297 @type instance: L{objects.Instance}
298 @param instance: instance to be accepted
299 @type info: string/data (opaque)
300 @param info: migration information, from the source node
301 @type target: string
302 @param target: target host (usually ip), on this node
303
304 """
305 pass
306
308 """Balloon an instance memory to a certain value.
309
310 @type instance: L{objects.Instance}
311 @param instance: instance to be accepted
312 @type mem: int
313 @param mem: actual memory size to use for instance runtime
314
315 """
316 raise NotImplementedError
317
319 """Finalize the instance migration on the target node.
320
321 Should finalize or revert any preparation done to accept the instance.
322 Since by default we do no preparation, we also don't have anything to do
323
324 @type instance: L{objects.Instance}
325 @param instance: instance whose migration is being finalized
326 @type info: string/data (opaque)
327 @param info: migration information, from the source node
328 @type success: boolean
329 @param success: whether the migration was a success or a failure
330
331 """
332 pass
333
335 """Migrate an instance.
336
337 @type instance: L{objects.Instance}
338 @param instance: the instance to be migrated
339 @type target: string
340 @param target: hostname (usually ip) of the target node
341 @type live: boolean
342 @param live: whether to do a live or non-live migration
343
344 """
345 raise NotImplementedError
346
348 """Finalize the instance migration on the source node.
349
350 @type instance: L{objects.Instance}
351 @param instance: the instance that was migrated
352 @type success: bool
353 @param success: whether the migration succeeded or not
354 @type live: bool
355 @param live: whether the user requested a live migration or not
356
357 """
358 pass
359
361 """Get the migration status
362
363 @type instance: L{objects.Instance}
364 @param instance: the instance that is being migrated
365 @rtype: L{objects.MigrationStatus}
366 @return: the status of the current migration (one of
367 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
368 progress info that can be retrieved from the hypervisor
369
370 """
371 raise NotImplementedError
372
374 """Get the correct startup memory for an instance
375
376 This function calculates how much memory an instance should be started
377 with, making sure it's a value between the minimum and the maximum memory,
378 but also trying to use no more than the current free memory on the node.
379
380 @type instance: L{objects.Instance}
381 @param instance: the instance that is being started
382 @rtype: integer
383 @return: memory the instance should be started with
384
385 """
386 free_memory = self.GetNodeInfo()["memory_free"]
387 max_start_mem = min(instance.beparams[constants.BE_MAXMEM], free_memory)
388 start_mem = max(instance.beparams[constants.BE_MINMEM], max_start_mem)
389 return start_mem
390
391 @classmethod
393 """Check the given parameters for validity.
394
395 This should check the passed set of parameters for
396 validity. Classes should extend, not replace, this function.
397
398 @type hvparams: dict
399 @param hvparams: dictionary with parameter names/value
400 @raise errors.HypervisorError: when a parameter is not valid
401
402 """
403 for key in hvparams:
404 if key not in cls.PARAMETERS:
405 raise errors.HypervisorError("Parameter '%s' is not supported" % key)
406
407
408 for name, (required, check_fn, errstr, _, _) in cls.PARAMETERS.items():
409 if name not in hvparams:
410 raise errors.HypervisorError("Parameter '%s' is missing" % name)
411 value = hvparams[name]
412 if not required and not value:
413 continue
414 if not value:
415 raise errors.HypervisorError("Parameter '%s' is required but"
416 " is currently not defined" % (name, ))
417 if check_fn is not None and not check_fn(value):
418 raise errors.HypervisorError("Parameter '%s' fails syntax"
419 " check: %s (current value: '%s')" %
420 (name, errstr, value))
421
422 @classmethod
424 """Check the given parameters for validity.
425
426 This should check the passed set of parameters for
427 validity. Classes should extend, not replace, this function.
428
429 @type hvparams: dict
430 @param hvparams: dictionary with parameter names/value
431 @raise errors.HypervisorError: when a parameter is not valid
432
433 """
434 for name, (required, _, _, check_fn, errstr) in cls.PARAMETERS.items():
435 value = hvparams[name]
436 if not required and not value:
437 continue
438 if check_fn is not None and not check_fn(value):
439 raise errors.HypervisorError("Parameter '%s' fails"
440 " validation: %s (current value: '%s')" %
441 (name, errstr, value))
442
443 @classmethod
445 """Hard powercycle a node using hypervisor specific methods.
446
447 This method should hard powercycle the node, using whatever
448 methods the hypervisor provides. Note that this means that all
449 instances running on the node must be stopped too.
450
451 """
452 raise NotImplementedError
453
454 @staticmethod
456 """For linux systems, return actual OS information.
457
458 This is an abstraction for all non-hypervisor-based classes, where
459 the node actually sees all the memory and CPUs via the /proc
460 interface and standard commands. The other case if for example
461 xen, where you only see the hardware resources via xen-specific
462 tools.
463
464 @param meminfo: name of the file containing meminfo
465 @type meminfo: string
466 @param cpuinfo: name of the file containing cpuinfo
467 @type cpuinfo: string
468 @return: a dict with the following keys (values in MiB):
469 - memory_total: the total memory size on the node
470 - memory_free: the available memory on the node for instances
471 - memory_dom0: the memory used by the node itself, if available
472
473 """
474 try:
475 data = utils.ReadFile(meminfo).splitlines()
476 except EnvironmentError, err:
477 raise errors.HypervisorError("Failed to list node info: %s" % (err,))
478
479 result = {}
480 sum_free = 0
481 try:
482 for line in data:
483 splitfields = line.split(":", 1)
484
485 if len(splitfields) > 1:
486 key = splitfields[0].strip()
487 val = splitfields[1].strip()
488 if key == "MemTotal":
489 result["memory_total"] = int(val.split()[0]) / 1024
490 elif key in ("MemFree", "Buffers", "Cached"):
491 sum_free += int(val.split()[0]) / 1024
492 elif key == "Active":
493 result["memory_dom0"] = int(val.split()[0]) / 1024
494 except (ValueError, TypeError), err:
495 raise errors.HypervisorError("Failed to compute memory usage: %s" %
496 (err,))
497 result["memory_free"] = sum_free
498
499 cpu_total = 0
500 try:
501 fh = open(cpuinfo)
502 try:
503 cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
504 fh.read()))
505 finally:
506 fh.close()
507 except EnvironmentError, err:
508 raise errors.HypervisorError("Failed to list node info: %s" % (err,))
509 result["cpu_total"] = cpu_total
510
511 result["cpu_nodes"] = 1
512 result["cpu_sockets"] = 1
513
514 return result
515
516 @classmethod
518 """Linux-specific powercycle method.
519
520 """
521 try:
522 fd = os.open("/proc/sysrq-trigger", os.O_WRONLY)
523 try:
524 os.write(fd, "b")
525 finally:
526 fd.close()
527 except OSError:
528 logging.exception("Can't open the sysrq-trigger file")
529 result = utils.RunCmd(["reboot", "-n", "-f"])
530 if not result:
531 logging.error("Can't run shutdown: %s", result.output)
532
533 @staticmethod
546