Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_base.py @ 78411c60

History | View | Annotate | Download (12.1 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
NET_PORT_CHECK = (True, lambda x: x > 0 and x < 65535, "invalid port number",
66
                  None, None)
67

    
68
# no checks at all
69
NO_CHECK = (False, None, None, None, None)
70

    
71
# required, but no other checks
72
REQUIRED_CHECK = (True, None, None, None, None)
73

    
74

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

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

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

    
88

    
89
class BaseHypervisor(object):
90
  """Abstract virtualisation technology interface
91

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

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

105
  """
106
  PARAMETERS = {}
107
  ANCILLARY_FILES = []
108

    
109
  def __init__(self):
110
    pass
111

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

    
116
  def StopInstance(self, instance, force=False, retry=False):
117
    """Stop an instance
118

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

126
    """
127
    raise NotImplementedError
128

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

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

    
137
  def GetInstanceInfo(self, instance_name):
138
    """Get instance properties.
139

140
    @type instance_name: string
141
    @param instance_name: the instance name
142

143
    @return: tuple (name, id, memory, vcpus, state, times)
144

145
    """
146
    raise NotImplementedError
147

    
148
  def GetAllInstancesInfo(self):
149
    """Get properties of all instances.
150

151
    @return: list of tuples (name, id, memory, vcpus, stat, times)
152

153
    """
154
    raise NotImplementedError
155

    
156
  def GetNodeInfo(self):
157
    """Return information about the node.
158

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

164
    """
165
    raise NotImplementedError
166

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

171
    """
172
    raise NotImplementedError
173

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

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

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

    
187
  def Verify(self):
188
    """Verify the hypervisor.
189

190
    """
191
    raise NotImplementedError
192

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

196
    By default assume no information is needed.
197

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

203
    """
204
    return ''
205

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

209
    By default assume no preparation is needed.
210

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

218
    """
219
    pass
220

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

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

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

234
    """
235
    pass
236

    
237
  def MigrateInstance(self, instance, target, live):
238
    """Migrate an instance.
239

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

247
    """
248
    raise NotImplementedError
249

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

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

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

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

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

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

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

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

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

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

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

310
    """
311
    raise NotImplementedError
312

    
313

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

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

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

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

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

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

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

    
369
    return result
370

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

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