Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_base.py @ 6915bc28

History | View | Annotate | Download (11.7 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008 Google Inc.
5
#
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.
10
#
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.
15
#
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
19
# 02110-1301, USA.
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

    
48

    
49
# Read the BaseHypervisor.PARAMETERS docstring for the syntax of the
50
# _CHECK values
51

    
52
# must be afile
53
_FILE_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
54
              os.path.isfile, "not found or not a file")
55

    
56
# must be a directory
57
_DIR_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
58
             os.path.isdir, "not found or not a directory")
59

    
60
# nice wrappers for users
61
REQ_FILE_CHECK = (True, ) + _FILE_CHECK
62
OPT_FILE_CHECK = (False, ) + _FILE_CHECK
63
REQ_DIR_CHECK = (True, ) + _DIR_CHECK
64
OPT_DIR_CHECK = (False, ) + _DIR_CHECK
65

    
66
# no checks at all
67
NO_CHECK = (False, None, None, None, None)
68

    
69
# required, but no other checks
70
REQUIRED_CHECK = (True, None, None, None, None)
71

    
72

    
73
def ParamInSet(required, my_set):
74
  """Builds parameter checker for set membership.
75

76
  @type required: boolean
77
  @param required: whether this is a required parameter
78
  @type my_set: tuple, list or set
79
  @param my_set: allowed values set
80

81
  """
82
  fn = lambda x: x in my_set
83
  err = ("The value must be one of: %s" % " ,".join(my_set))
84
  return (required, fn, err, None, None)
85

    
86

    
87
class BaseHypervisor(object):
88
  """Abstract virtualisation technology interface
89

90
  The goal is that all aspects of the virtualisation technology are
91
  abstracted away from the rest of code.
92

93
  @cvar PARAMETERS: a dict of parameter name: check type; the check type is
94
      a five-tuple containing:
95
          - the required flag (boolean)
96
          - a function to check for syntax, that will be used in
97
            L{CheckParameterSyntax}, in the master daemon process
98
          - an error message for the above function
99
          - a function to check for parameter validity on the remote node,
100
            in the L{ValidateParameters} function
101
          - an error message for the above function
102

103
  """
104
  PARAMETERS = {}
105
  ANCILLARY_FILES = []
106

    
107
  def __init__(self):
108
    pass
109

    
110
  def StartInstance(self, instance, block_devices):
111
    """Start an instance."""
112
    raise NotImplementedError
113

    
114
  def StopInstance(self, instance, force=False):
115
    """Stop an instance."""
116
    raise NotImplementedError
117

    
118
  def RebootInstance(self, instance):
119
    """Reboot an instance."""
120
    raise NotImplementedError
121

    
122
  def ListInstances(self):
123
    """Get the list of running instances."""
124
    raise NotImplementedError
125

    
126
  def GetInstanceInfo(self, instance_name):
127
    """Get instance properties.
128

129
    @type instance_name: string
130
    @param instance_name: the instance name
131

132
    @return: tuple (name, id, memory, vcpus, state, times)
133

134
    """
135
    raise NotImplementedError
136

    
137
  def GetAllInstancesInfo(self):
138
    """Get properties of all instances.
139

140
    @return: list of tuples (name, id, memory, vcpus, stat, times)
141

142
    """
143
    raise NotImplementedError
144

    
145
  def GetNodeInfo(self):
146
    """Return information about the node.
147

148
    @return: a dict with the following keys (values in MiB):
149
          - memory_total: the total memory size on the node
150
          - memory_free: the available memory on the node for instances
151
          - memory_dom0: the memory used by the node itself, if available
152

153
    """
154
    raise NotImplementedError
155

    
156
  @classmethod
157
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
158
    """Return a command for connecting to the console of an instance.
159

160
    """
161
    raise NotImplementedError
162

    
163
  @classmethod
164
  def GetAncillaryFiles(cls):
165
    """Return a list of ancillary files to be copied to all nodes as ancillary
166
    configuration files.
167

168
    @rtype: list of strings
169
    @return: list of absolute paths of files to ship cluster-wide
170

171
    """
172
    # By default we return a member variable, so that if an hypervisor has just
173
    # a static list of files it doesn't have to override this function.
174
    return cls.ANCILLARY_FILES
175

    
176
  def Verify(self):
177
    """Verify the hypervisor.
178

179
    """
180
    raise NotImplementedError
181

    
182
  def MigrationInfo(self, instance):
183
    """Get instance information to perform a migration.
184

185
    By default assume no information is needed.
186

187
    @type instance: L{objects.Instance}
188
    @param instance: instance to be migrated
189
    @rtype: string/data (opaque)
190
    @return: instance migration information - serialized form
191

192
    """
193
    return ''
194

    
195
  def AcceptInstance(self, instance, info, target):
196
    """Prepare to accept an instance.
197

198
    By default assume no preparation is needed.
199

200
    @type instance: L{objects.Instance}
201
    @param instance: instance to be accepted
202
    @type info: string/data (opaque)
203
    @param info: migration information, from the source node
204
    @type target: string
205
    @param target: target host (usually ip), on this node
206

207
    """
208
    pass
209

    
210
  def FinalizeMigration(self, instance, info, success):
211
    """Finalized an instance migration.
212

213
    Should finalize or revert any preparation done to accept the instance.
214
    Since by default we do no preparation, we also don't have anything to do
215

216
    @type instance: L{objects.Instance}
217
    @param instance: instance whose migration is being aborted
218
    @type info: string/data (opaque)
219
    @param info: migration information, from the source node
220
    @type success: boolean
221
    @param success: whether the migration was a success or a failure
222

223
    """
224
    pass
225

    
226
  def MigrateInstance(self, name, target, live):
227
    """Migrate an instance.
228

229
    @type name: string
230
    @param name: name of the instance to be migrated
231
    @type target: string
232
    @param target: hostname (usually ip) of the target node
233
    @type live: boolean
234
    @param live: whether to do a live or non-live migration
235

236
    """
237
    raise NotImplementedError
238

    
239
  @classmethod
240
  def CheckParameterSyntax(cls, hvparams):
241
    """Check the given parameters for validity.
242

243
    This should check the passed set of parameters for
244
    validity. Classes should extend, not replace, this function.
245

246
    @type hvparams:  dict
247
    @param hvparams: dictionary with parameter names/value
248
    @raise errors.HypervisorError: when a parameter is not valid
249

250
    """
251
    for key in hvparams:
252
      if key not in cls.PARAMETERS:
253
        raise errors.HypervisorError("Parameter '%s' is not supported" % key)
254

    
255
    # cheap tests that run on the master, should not access the world
256
    for name, (required, check_fn, errstr, _, _) in cls.PARAMETERS.items():
257
      if name not in hvparams:
258
        raise errors.HypervisorError("Parameter '%s' is missing" % name)
259
      value = hvparams[name]
260
      if not required and not value:
261
        continue
262
      if not value:
263
        raise errors.HypervisorError("Parameter '%s' is required but"
264
                                     " is currently not defined" % (name, ))
265
      if check_fn is not None and not check_fn(value):
266
        raise errors.HypervisorError("Parameter '%s' fails syntax"
267
                                     " check: %s (current value: '%s')" %
268
                                     (name, errstr, value))
269

    
270
  @classmethod
271
  def ValidateParameters(cls, hvparams):
272
    """Check the given parameters for validity.
273

274
    This should check the passed set of parameters for
275
    validity. Classes should extend, not replace, this function.
276

277
    @type hvparams:  dict
278
    @param hvparams: dictionary with parameter names/value
279
    @raise errors.HypervisorError: when a parameter is not valid
280

281
    """
282
    for name, (required, _, _, check_fn, errstr) in cls.PARAMETERS.items():
283
      value = hvparams[name]
284
      if not required and not value:
285
        continue
286
      if check_fn is not None and not check_fn(value):
287
        raise errors.HypervisorError("Parameter '%s' fails"
288
                                     " validation: %s (current value: '%s')" %
289
                                     (name, errstr, value))
290

    
291
  @classmethod
292
  def PowercycleNode(cls):
293
    """Hard powercycle a node using hypervisor specific methods.
294

295
    This method should hard powercycle the node, using whatever
296
    methods the hypervisor provides. Note that this means that all
297
    instances running on the node must be stopped too.
298

299
    """
300
    raise NotImplementedError
301

    
302

    
303
  def GetLinuxNodeInfo(self):
304
    """For linux systems, return actual OS information.
305

306
    This is an abstraction for all non-hypervisor-based classes, where
307
    the node actually sees all the memory and CPUs via the /proc
308
    interface and standard commands. The other case if for example
309
    xen, where you only see the hardware resources via xen-specific
310
    tools.
311

312
    @return: a dict with the following keys (values in MiB):
313
          - memory_total: the total memory size on the node
314
          - memory_free: the available memory on the node for instances
315
          - memory_dom0: the memory used by the node itself, if available
316

317
    """
318
    try:
319
      data = utils.ReadFile("/proc/meminfo").splitlines()
320
    except EnvironmentError, err:
321
      raise errors.HypervisorError("Failed to list node info: %s" % (err,))
322

    
323
    result = {}
324
    sum_free = 0
325
    try:
326
      for line in data:
327
        splitfields = line.split(":", 1)
328

    
329
        if len(splitfields) > 1:
330
          key = splitfields[0].strip()
331
          val = splitfields[1].strip()
332
          if key == 'MemTotal':
333
            result['memory_total'] = int(val.split()[0])/1024
334
          elif key in ('MemFree', 'Buffers', 'Cached'):
335
            sum_free += int(val.split()[0])/1024
336
          elif key == 'Active':
337
            result['memory_dom0'] = int(val.split()[0])/1024
338
    except (ValueError, TypeError), err:
339
      raise errors.HypervisorError("Failed to compute memory usage: %s" %
340
                                   (err,))
341
    result['memory_free'] = sum_free
342

    
343
    cpu_total = 0
344
    try:
345
      fh = open("/proc/cpuinfo")
346
      try:
347
        cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
348
                                   fh.read()))
349
      finally:
350
        fh.close()
351
    except EnvironmentError, err:
352
      raise errors.HypervisorError("Failed to list node info: %s" % (err,))
353
    result['cpu_total'] = cpu_total
354
    # FIXME: export correct data here
355
    result['cpu_nodes'] = 1
356
    result['cpu_sockets'] = 1
357

    
358
    return result
359

    
360
  @classmethod
361
  def LinuxPowercycle(cls):
362
    """Linux-specific powercycle method.
363

364
    """
365
    try:
366
      fd = os.open("/proc/sysrq-trigger", os.O_WRONLY)
367
      try:
368
        os.write(fd, "b")
369
      finally:
370
        fd.close()
371
    except OSError:
372
      logging.exception("Can't open the sysrq-trigger file")
373
      result = utils.RunCmd(["reboot", "-n", "-f"])
374
      if not result:
375
        logging.error("Can't run shutdown: %s", result.output)