Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_base.py @ 58d38b02

History | View | Annotate | Download (12 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, retry=False):
115
    """Stop an instance
116

117
    @type instance: L{objects.Instance}
118
    @param instance: instance to stop
119
    @type force: boolean
120
    @param force: whether to do a "hard" stop (destroy)
121
    @type retry: boolean
122
    @param retry: whether this is just a retry call
123

124
    """
125
    raise NotImplementedError
126

    
127
  def RebootInstance(self, instance):
128
    """Reboot an instance."""
129
    raise NotImplementedError
130

    
131
  def ListInstances(self):
132
    """Get the list of running instances."""
133
    raise NotImplementedError
134

    
135
  def GetInstanceInfo(self, instance_name):
136
    """Get instance properties.
137

138
    @type instance_name: string
139
    @param instance_name: the instance name
140

141
    @return: tuple (name, id, memory, vcpus, state, times)
142

143
    """
144
    raise NotImplementedError
145

    
146
  def GetAllInstancesInfo(self):
147
    """Get properties of all instances.
148

149
    @return: list of tuples (name, id, memory, vcpus, stat, times)
150

151
    """
152
    raise NotImplementedError
153

    
154
  def GetNodeInfo(self):
155
    """Return information about the node.
156

157
    @return: a dict with the following keys (values in MiB):
158
          - memory_total: the total memory size on the node
159
          - memory_free: the available memory on the node for instances
160
          - memory_dom0: the memory used by the node itself, if available
161

162
    """
163
    raise NotImplementedError
164

    
165
  @classmethod
166
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
167
    """Return a command for connecting to the console of an instance.
168

169
    """
170
    raise NotImplementedError
171

    
172
  @classmethod
173
  def GetAncillaryFiles(cls):
174
    """Return a list of ancillary files to be copied to all nodes as ancillary
175
    configuration files.
176

177
    @rtype: list of strings
178
    @return: list of absolute paths of files to ship cluster-wide
179

180
    """
181
    # By default we return a member variable, so that if an hypervisor has just
182
    # a static list of files it doesn't have to override this function.
183
    return cls.ANCILLARY_FILES
184

    
185
  def Verify(self):
186
    """Verify the hypervisor.
187

188
    """
189
    raise NotImplementedError
190

    
191
  def MigrationInfo(self, instance):
192
    """Get instance information to perform a migration.
193

194
    By default assume no information is needed.
195

196
    @type instance: L{objects.Instance}
197
    @param instance: instance to be migrated
198
    @rtype: string/data (opaque)
199
    @return: instance migration information - serialized form
200

201
    """
202
    return ''
203

    
204
  def AcceptInstance(self, instance, info, target):
205
    """Prepare to accept an instance.
206

207
    By default assume no preparation is needed.
208

209
    @type instance: L{objects.Instance}
210
    @param instance: instance to be accepted
211
    @type info: string/data (opaque)
212
    @param info: migration information, from the source node
213
    @type target: string
214
    @param target: target host (usually ip), on this node
215

216
    """
217
    pass
218

    
219
  def FinalizeMigration(self, instance, info, success):
220
    """Finalized an instance migration.
221

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

225
    @type instance: L{objects.Instance}
226
    @param instance: instance whose migration is being aborted
227
    @type info: string/data (opaque)
228
    @param info: migration information, from the source node
229
    @type success: boolean
230
    @param success: whether the migration was a success or a failure
231

232
    """
233
    pass
234

    
235
  def MigrateInstance(self, instance, target, live):
236
    """Migrate an instance.
237

238
    @type instance: L{object.Instance}
239
    @param name: the instance to be migrated
240
    @type target: string
241
    @param target: hostname (usually ip) of the target node
242
    @type live: boolean
243
    @param live: whether to do a live or non-live migration
244

245
    """
246
    raise NotImplementedError
247

    
248
  @classmethod
249
  def CheckParameterSyntax(cls, hvparams):
250
    """Check the given parameters for validity.
251

252
    This should check the passed set of parameters for
253
    validity. Classes should extend, not replace, this function.
254

255
    @type hvparams:  dict
256
    @param hvparams: dictionary with parameter names/value
257
    @raise errors.HypervisorError: when a parameter is not valid
258

259
    """
260
    for key in hvparams:
261
      if key not in cls.PARAMETERS:
262
        raise errors.HypervisorError("Parameter '%s' is not supported" % key)
263

    
264
    # cheap tests that run on the master, should not access the world
265
    for name, (required, check_fn, errstr, _, _) in cls.PARAMETERS.items():
266
      if name not in hvparams:
267
        raise errors.HypervisorError("Parameter '%s' is missing" % name)
268
      value = hvparams[name]
269
      if not required and not value:
270
        continue
271
      if not value:
272
        raise errors.HypervisorError("Parameter '%s' is required but"
273
                                     " is currently not defined" % (name, ))
274
      if check_fn is not None and not check_fn(value):
275
        raise errors.HypervisorError("Parameter '%s' fails syntax"
276
                                     " check: %s (current value: '%s')" %
277
                                     (name, errstr, value))
278

    
279
  @classmethod
280
  def ValidateParameters(cls, hvparams):
281
    """Check the given parameters for validity.
282

283
    This should check the passed set of parameters for
284
    validity. Classes should extend, not replace, this function.
285

286
    @type hvparams:  dict
287
    @param hvparams: dictionary with parameter names/value
288
    @raise errors.HypervisorError: when a parameter is not valid
289

290
    """
291
    for name, (required, _, _, check_fn, errstr) in cls.PARAMETERS.items():
292
      value = hvparams[name]
293
      if not required and not value:
294
        continue
295
      if check_fn is not None and not check_fn(value):
296
        raise errors.HypervisorError("Parameter '%s' fails"
297
                                     " validation: %s (current value: '%s')" %
298
                                     (name, errstr, value))
299

    
300
  @classmethod
301
  def PowercycleNode(cls):
302
    """Hard powercycle a node using hypervisor specific methods.
303

304
    This method should hard powercycle the node, using whatever
305
    methods the hypervisor provides. Note that this means that all
306
    instances running on the node must be stopped too.
307

308
    """
309
    raise NotImplementedError
310

    
311

    
312
  def GetLinuxNodeInfo(self):
313
    """For linux systems, return actual OS information.
314

315
    This is an abstraction for all non-hypervisor-based classes, where
316
    the node actually sees all the memory and CPUs via the /proc
317
    interface and standard commands. The other case if for example
318
    xen, where you only see the hardware resources via xen-specific
319
    tools.
320

321
    @return: a dict with the following keys (values in MiB):
322
          - memory_total: the total memory size on the node
323
          - memory_free: the available memory on the node for instances
324
          - memory_dom0: the memory used by the node itself, if available
325

326
    """
327
    try:
328
      data = utils.ReadFile("/proc/meminfo").splitlines()
329
    except EnvironmentError, err:
330
      raise errors.HypervisorError("Failed to list node info: %s" % (err,))
331

    
332
    result = {}
333
    sum_free = 0
334
    try:
335
      for line in data:
336
        splitfields = line.split(":", 1)
337

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

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

    
367
    return result
368

    
369
  @classmethod
370
  def LinuxPowercycle(cls):
371
    """Linux-specific powercycle method.
372

373
    """
374
    try:
375
      fd = os.open("/proc/sysrq-trigger", os.O_WRONLY)
376
      try:
377
        os.write(fd, "b")
378
      finally:
379
        fd.close()
380
    except OSError:
381
      logging.exception("Can't open the sysrq-trigger file")
382
      result = utils.RunCmd(["reboot", "-n", "-f"])
383
      if not result:
384
        logging.error("Can't run shutdown: %s", result.output)