Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_base.py @ 3374afa9

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
def ParamInSet(required, my_set):
73
  """Builds parameter checker for set membership.
74

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

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

    
85

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

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

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

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

    
106
  def __init__(self):
107
    pass
108

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

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

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

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

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

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

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

133
    """
134
    raise NotImplementedError
135

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

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

141
    """
142
    raise NotImplementedError
143

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

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

152
    """
153
    raise NotImplementedError
154

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

159
    """
160
    raise NotImplementedError
161

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

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

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

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

178
    """
179
    raise NotImplementedError
180

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

184
    By default assume no information is needed.
185

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

191
    """
192
    return ''
193

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

197
    By default assume no preparation is needed.
198

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

206
    """
207
    pass
208

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

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

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

222
    """
223
    pass
224

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

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

235
    """
236
    raise NotImplementedError
237

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

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

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

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

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

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

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

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

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

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

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

298
    """
299
    raise NotImplementedError
300

    
301

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

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

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

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

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

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

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

    
357
    return result
358

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

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