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
56
57
58
59
60
61
62 _FILE_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
63 os.path.isfile, "not found or not a file")
64
65
66 _DIR_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
67 os.path.isdir, "not found or not a directory")
68
69
70
71 _CPU_MASK_CHECK = (_IsCpuMaskWellFormed,
72 "CPU mask definition is not well-formed",
73 None, None)
74
75
76 _NET_PORT_CHECK = (lambda x: 0 < x < 65535, "invalid port number",
77 None, None)
78
79
80 REQ_FILE_CHECK = (True, ) + _FILE_CHECK
81 OPT_FILE_CHECK = (False, ) + _FILE_CHECK
82 REQ_DIR_CHECK = (True, ) + _DIR_CHECK
83 OPT_DIR_CHECK = (False, ) + _DIR_CHECK
84 REQ_NET_PORT_CHECK = (True, ) + _NET_PORT_CHECK
85 OPT_NET_PORT_CHECK = (False, ) + _NET_PORT_CHECK
86 REQ_CPU_MASK_CHECK = (True, ) + _CPU_MASK_CHECK
87 OPT_CPU_MASK_CHECK = (False, ) + _CPU_MASK_CHECK
88
89
90 NO_CHECK = (False, None, None, None, None)
91
92
93 REQUIRED_CHECK = (True, None, None, None, None)
94
95
96 MIGRATION_MODE_CHECK = (True, lambda x: x in constants.HT_MIGRATION_MODES,
97 "invalid migration mode", None, None)
101 """Builds parameter checker for set membership.
102
103 @type required: boolean
104 @param required: whether this is a required parameter
105 @type my_set: tuple, list or set
106 @param my_set: allowed values set
107
108 """
109 fn = lambda x: x in my_set
110 err = ("The value must be one of: %s" % utils.CommaJoin(my_set))
111 return (required, fn, err, None, None)
112
115 """Abstract virtualisation technology interface
116
117 The goal is that all aspects of the virtualisation technology are
118 abstracted away from the rest of code.
119
120 @cvar PARAMETERS: a dict of parameter name: check type; the check type is
121 a five-tuple containing:
122 - the required flag (boolean)
123 - a function to check for syntax, that will be used in
124 L{CheckParameterSyntax}, in the master daemon process
125 - an error message for the above function
126 - a function to check for parameter validity on the remote node,
127 in the L{ValidateParameters} function
128 - an error message for the above function
129 @type CAN_MIGRATE: boolean
130 @cvar CAN_MIGRATE: whether this hypervisor can do migration (either
131 live or non-live)
132
133 """
134 PARAMETERS = {}
135 ANCILLARY_FILES = []
136 CAN_MIGRATE = False
137
140
141 - def StartInstance(self, instance, block_devices, startup_paused):
142 """Start an instance."""
143 raise NotImplementedError
144
145 - def StopInstance(self, instance, force=False, retry=False, name=None):
146 """Stop an instance
147
148 @type instance: L{objects.Instance}
149 @param instance: instance to stop
150 @type force: boolean
151 @param force: whether to do a "hard" stop (destroy)
152 @type retry: boolean
153 @param retry: whether this is just a retry call
154 @type name: string or None
155 @param name: if this parameter is passed, the the instance object
156 should not be used (will be passed as None), and the shutdown
157 must be done by name only
158
159 """
160 raise NotImplementedError
161
163 """Cleanup after a stopped instance
164
165 This is an optional method, used by hypervisors that need to cleanup after
166 an instance has been stopped.
167
168 @type instance_name: string
169 @param instance_name: instance name to cleanup after
170
171 """
172 pass
173
175 """Reboot an instance."""
176 raise NotImplementedError
177
179 """Get the list of running instances."""
180 raise NotImplementedError
181
183 """Get instance properties.
184
185 @type instance_name: string
186 @param instance_name: the instance name
187
188 @return: tuple (name, id, memory, vcpus, state, times)
189
190 """
191 raise NotImplementedError
192
194 """Get properties of all instances.
195
196 @return: list of tuples (name, id, memory, vcpus, stat, times)
197
198 """
199 raise NotImplementedError
200
202 """Return information about the node.
203
204 @return: a dict with the following keys (values in MiB):
205 - memory_total: the total memory size on the node
206 - memory_free: the available memory on the node for instances
207 - memory_dom0: the memory used by the node itself, if available
208
209 """
210 raise NotImplementedError
211
212 @classmethod
214 """Return information for connecting to the console of an instance.
215
216 """
217 raise NotImplementedError
218
219 @classmethod
221 """Return a list of ancillary files to be copied to all nodes as ancillary
222 configuration files.
223
224 @rtype: list of strings
225 @return: list of absolute paths of files to ship cluster-wide
226
227 """
228
229
230 return cls.ANCILLARY_FILES
231
233 """Verify the hypervisor.
234
235 """
236 raise NotImplementedError
237
239 """Get instance information to perform a migration.
240
241 By default assume no information is needed.
242
243 @type instance: L{objects.Instance}
244 @param instance: instance to be migrated
245 @rtype: string/data (opaque)
246 @return: instance migration information - serialized form
247
248 """
249 return ""
250
252 """Prepare to accept an instance.
253
254 By default assume no preparation is needed.
255
256 @type instance: L{objects.Instance}
257 @param instance: instance to be accepted
258 @type info: string/data (opaque)
259 @param info: migration information, from the source node
260 @type target: string
261 @param target: target host (usually ip), on this node
262
263 """
264 pass
265
267 """Finalized an instance migration.
268
269 Should finalize or revert any preparation done to accept the instance.
270 Since by default we do no preparation, we also don't have anything to do
271
272 @type instance: L{objects.Instance}
273 @param instance: instance whose migration is being finalized
274 @type info: string/data (opaque)
275 @param info: migration information, from the source node
276 @type success: boolean
277 @param success: whether the migration was a success or a failure
278
279 """
280 pass
281
283 """Migrate an instance.
284
285 @type instance: L{objects.Instance}
286 @param instance: the instance to be migrated
287 @type target: string
288 @param target: hostname (usually ip) of the target node
289 @type live: boolean
290 @param live: whether to do a live or non-live migration
291
292 """
293 raise NotImplementedError
294
295 @classmethod
297 """Check the given parameters for validity.
298
299 This should check the passed set of parameters for
300 validity. Classes should extend, not replace, this function.
301
302 @type hvparams: dict
303 @param hvparams: dictionary with parameter names/value
304 @raise errors.HypervisorError: when a parameter is not valid
305
306 """
307 for key in hvparams:
308 if key not in cls.PARAMETERS:
309 raise errors.HypervisorError("Parameter '%s' is not supported" % key)
310
311
312 for name, (required, check_fn, errstr, _, _) in cls.PARAMETERS.items():
313 if name not in hvparams:
314 raise errors.HypervisorError("Parameter '%s' is missing" % name)
315 value = hvparams[name]
316 if not required and not value:
317 continue
318 if not value:
319 raise errors.HypervisorError("Parameter '%s' is required but"
320 " is currently not defined" % (name, ))
321 if check_fn is not None and not check_fn(value):
322 raise errors.HypervisorError("Parameter '%s' fails syntax"
323 " check: %s (current value: '%s')" %
324 (name, errstr, value))
325
326 @classmethod
328 """Check the given parameters for validity.
329
330 This should check the passed set of parameters for
331 validity. Classes should extend, not replace, this function.
332
333 @type hvparams: dict
334 @param hvparams: dictionary with parameter names/value
335 @raise errors.HypervisorError: when a parameter is not valid
336
337 """
338 for name, (required, _, _, check_fn, errstr) in cls.PARAMETERS.items():
339 value = hvparams[name]
340 if not required and not value:
341 continue
342 if check_fn is not None and not check_fn(value):
343 raise errors.HypervisorError("Parameter '%s' fails"
344 " validation: %s (current value: '%s')" %
345 (name, errstr, value))
346
347 @classmethod
349 """Hard powercycle a node using hypervisor specific methods.
350
351 This method should hard powercycle the node, using whatever
352 methods the hypervisor provides. Note that this means that all
353 instances running on the node must be stopped too.
354
355 """
356 raise NotImplementedError
357
358 @staticmethod
360 """For linux systems, return actual OS information.
361
362 This is an abstraction for all non-hypervisor-based classes, where
363 the node actually sees all the memory and CPUs via the /proc
364 interface and standard commands. The other case if for example
365 xen, where you only see the hardware resources via xen-specific
366 tools.
367
368 @return: a dict with the following keys (values in MiB):
369 - memory_total: the total memory size on the node
370 - memory_free: the available memory on the node for instances
371 - memory_dom0: the memory used by the node itself, if available
372
373 """
374 try:
375 data = utils.ReadFile("/proc/meminfo").splitlines()
376 except EnvironmentError, err:
377 raise errors.HypervisorError("Failed to list node info: %s" % (err,))
378
379 result = {}
380 sum_free = 0
381 try:
382 for line in data:
383 splitfields = line.split(":", 1)
384
385 if len(splitfields) > 1:
386 key = splitfields[0].strip()
387 val = splitfields[1].strip()
388 if key == "MemTotal":
389 result["memory_total"] = int(val.split()[0]) / 1024
390 elif key in ("MemFree", "Buffers", "Cached"):
391 sum_free += int(val.split()[0]) / 1024
392 elif key == "Active":
393 result["memory_dom0"] = int(val.split()[0]) / 1024
394 except (ValueError, TypeError), err:
395 raise errors.HypervisorError("Failed to compute memory usage: %s" %
396 (err,))
397 result["memory_free"] = sum_free
398
399 cpu_total = 0
400 try:
401 fh = open("/proc/cpuinfo")
402 try:
403 cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
404 fh.read()))
405 finally:
406 fh.close()
407 except EnvironmentError, err:
408 raise errors.HypervisorError("Failed to list node info: %s" % (err,))
409 result["cpu_total"] = cpu_total
410
411 result["cpu_nodes"] = 1
412 result["cpu_sockets"] = 1
413
414 return result
415
416 @classmethod
418 """Linux-specific powercycle method.
419
420 """
421 try:
422 fd = os.open("/proc/sysrq-trigger", os.O_WRONLY)
423 try:
424 os.write(fd, "b")
425 finally:
426 fd.close()
427 except OSError:
428 logging.exception("Can't open the sysrq-trigger file")
429 result = utils.RunCmd(["reboot", "-n", "-f"])
430 if not result:
431 logging.error("Can't run shutdown: %s", result.output)
432