Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_base.py @ f5118ade

History | View | Annotate | Download (11.8 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

    
43

    
44
from ganeti import errors
45
from ganeti import utils
46

    
47

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

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

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

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

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

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

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

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

79
  """
80
  fn = lambda x: x in my_set
81
  err = ("The value must be one of: %s" % utils.CommaJoin(my_set))
82
  return (required, fn, err, None, None)
83

    
84

    
85
class BaseHypervisor(object):
86
  """Abstract virtualisation technology interface
87

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

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

101
  """
102
  PARAMETERS = {}
103
  ANCILLARY_FILES = []
104

    
105
  def __init__(self):
106
    pass
107

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

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

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

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

    
124
  def GetInstanceInfo(self, instance_name):
125
    """Get instance properties.
126

127
    @type instance_name: string
128
    @param instance_name: the instance name
129

130
    @return: tuple (name, id, memory, vcpus, state, times)
131

132
    """
133
    raise NotImplementedError
134

    
135
  def GetAllInstancesInfo(self):
136
    """Get properties of all instances.
137

138
    @return: list of tuples (name, id, memory, vcpus, stat, times)
139

140
    """
141
    raise NotImplementedError
142

    
143
  def GetNodeInfo(self):
144
    """Return information about the node.
145

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

151
    """
152
    raise NotImplementedError
153

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

158
    """
159
    raise NotImplementedError
160

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

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

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

    
174
  def Verify(self):
175
    """Verify the hypervisor.
176

177
    """
178
    raise NotImplementedError
179

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

183
    By default assume no information is needed.
184

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

190
    """
191
    return ''
192

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

196
    By default assume no preparation is needed.
197

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

205
    """
206
    pass
207

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

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

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

221
    """
222
    pass
223

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

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

234
    """
235
    raise NotImplementedError
236

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

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

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

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

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

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

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

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

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

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

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

297
    """
298
    raise NotImplementedError
299

    
300

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

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

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

315
    """
316
    try:
317
      fh = file("/proc/meminfo")
318
      try:
319
        data = fh.readlines()
320
      finally:
321
        fh.close()
322
    except EnvironmentError, err:
323
      raise errors.HypervisorError("Failed to list node info: %s" % (err,))
324

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

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

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

    
360
    return result
361

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

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