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 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",
81 None, None)
82 OPT_CPU_MASK_CHECK = (False, ) + _CPU_MASK_CHECK
83 REQ_CPU_MASK_CHECK = (True, ) + _CPU_MASK_CHECK
84
85
86 NO_CHECK = (False, None, None, None, None)
87
88
89 REQUIRED_CHECK = (True, None, None, None, None)
90
91
92 MIGRATION_MODE_CHECK = (True, lambda x: x in constants.HT_MIGRATION_MODES,
93 "invalid migration mode", None, None)
97 """Builds parameter checker for set membership.
98
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
103
104 """
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)
108
111 """Abstract virtualisation technology interface
112
113 The goal is that all aspects of the virtualisation technology are
114 abstracted away from the rest of code.
115
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
127 live or non-live)
128
129 """
130 PARAMETERS = {}
131 ANCILLARY_FILES = []
132 CAN_MIGRATE = False
133
136
138 """Start an instance."""
139 raise NotImplementedError
140
141 - def StopInstance(self, instance, force=False, retry=False, name=None):
142 """Stop an instance
143
144 @type instance: L{objects.Instance}
145 @param instance: instance to stop
146 @type force: boolean
147 @param force: whether to do a "hard" stop (destroy)
148 @type retry: boolean
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
154
155 """
156 raise NotImplementedError
157
159 """Cleanup after a stopped instance
160
161 This is an optional method, used by hypervisors that need to cleanup after
162 an instance has been stopped.
163
164 @type instance_name: string
165 @param instance_name: instance name to cleanup after
166
167 """
168 pass
169
171 """Reboot an instance."""
172 raise NotImplementedError
173
175 """Get the list of running instances."""
176 raise NotImplementedError
177
179 """Get instance properties.
180
181 @type instance_name: string
182 @param instance_name: the instance name
183
184 @return: tuple (name, id, memory, vcpus, state, times)
185
186 """
187 raise NotImplementedError
188
190 """Get properties of all instances.
191
192 @return: list of tuples (name, id, memory, vcpus, stat, times)
193
194 """
195 raise NotImplementedError
196
198 """Return information about the node.
199
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
204
205 """
206 raise NotImplementedError
207
208 @classmethod
210 """Return information for connecting to the console of an instance.
211
212 """
213 raise NotImplementedError
214
215 @classmethod
217 """Return a list of ancillary files to be copied to all nodes as ancillary
218 configuration files.
219
220 @rtype: list of strings
221 @return: list of absolute paths of files to ship cluster-wide
222
223 """
224
225
226 return cls.ANCILLARY_FILES
227
229 """Verify the hypervisor.
230
231 """
232 raise NotImplementedError
233
235 """Get instance information to perform a migration.
236
237 By default assume no information is needed.
238
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
243
244 """
245 return ''
246
248 """Prepare to accept an instance.
249
250 By default assume no preparation is needed.
251
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
256 @type target: string
257 @param target: target host (usually ip), on this node
258
259 """
260 pass
261
263 """Finalized an instance migration.
264
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
267
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
274
275 """
276 pass
277
279 """Migrate an instance.
280
281 @type instance: L{objects.Instance}
282 @param instance: the instance to be migrated
283 @type target: string
284 @param target: hostname (usually ip) of the target node
285 @type live: boolean
286 @param live: whether to do a live or non-live migration
287
288 """
289 raise NotImplementedError
290
291 @classmethod
293 """Check the given parameters for validity.
294
295 This should check the passed set of parameters for
296 validity. Classes should extend, not replace, this function.
297
298 @type hvparams: dict
299 @param hvparams: dictionary with parameter names/value
300 @raise errors.HypervisorError: when a parameter is not valid
301
302 """
303 for key in hvparams:
304 if key not in cls.PARAMETERS:
305 raise errors.HypervisorError("Parameter '%s' is not supported" % key)
306
307
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:
313 continue
314 if 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))
321
322 @classmethod
324 """Check the given parameters for validity.
325
326 This should check the passed set of parameters for
327 validity. Classes should extend, not replace, this function.
328
329 @type hvparams: dict
330 @param hvparams: dictionary with parameter names/value
331 @raise errors.HypervisorError: when a parameter is not valid
332
333 """
334 for name, (required, _, _, check_fn, errstr) in cls.PARAMETERS.items():
335 value = hvparams[name]
336 if not required and not value:
337 continue
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))
342
343 @classmethod
345 """Hard powercycle a node using hypervisor specific methods.
346
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.
350
351 """
352 raise NotImplementedError
353
354 @staticmethod
356 """For linux systems, return actual OS information.
357
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
362 tools.
363
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
368
369 """
370 try:
371 data = utils.ReadFile("/proc/meminfo").splitlines()
372 except EnvironmentError, err:
373 raise errors.HypervisorError("Failed to list node info: %s" % (err,))
374
375 result = {}
376 sum_free = 0
377 try:
378 for line in data:
379 splitfields = line.split(":", 1)
380
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" %
392 (err,))
393 result['memory_free'] = sum_free
394
395 cpu_total = 0
396 try:
397 fh = open("/proc/cpuinfo")
398 try:
399 cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
400 fh.read()))
401 finally:
402 fh.close()
403 except EnvironmentError, err:
404 raise errors.HypervisorError("Failed to list node info: %s" % (err,))
405 result['cpu_total'] = cpu_total
406
407 result['cpu_nodes'] = 1
408 result['cpu_sockets'] = 1
409
410 return result
411
412 @classmethod
414 """Linux-specific powercycle method.
415
416 """
417 try:
418 fd = os.open("/proc/sysrq-trigger", os.O_WRONLY)
419 try:
420 os.write(fd, "b")
421 finally:
422 fd.close()
423 except OSError:
424 logging.exception("Can't open the sysrq-trigger file")
425 result = utils.RunCmd(["reboot", "-n", "-f"])
426 if not result:
427 logging.error("Can't run shutdown: %s", result.output)
428