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