Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_base.py @ 55cc0a44

History | View | Annotate | Download (13.5 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010 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
from ganeti import constants
48

    
49

    
50
def _IsCpuMaskWellFormed(cpu_mask):
51
  try:
52
    cpu_list = utils.ParseCpuMask(cpu_mask)
53
  except errors.ParseError, _:
54
    return False
55
  return isinstance(cpu_list, list) and len(cpu_list) > 0
56

    
57

    
58
# Read the BaseHypervisor.PARAMETERS docstring for the syntax of the
59
# _CHECK values
60

    
61
# must be afile
62
_FILE_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
63
              os.path.isfile, "not found or not a file")
64

    
65
# must be a directory
66
_DIR_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
67
             os.path.isdir, "not found or not a directory")
68

    
69
# CPU mask must be well-formed
70
# TODO: implement node level check for the CPU mask
71
_CPU_MASK_CHECK = (_IsCpuMaskWellFormed,
72
                   "CPU mask definition is not well-formed",
73
                   None, None)
74

    
75
# nice wrappers for users
76
REQ_FILE_CHECK = (True, ) + _FILE_CHECK
77
OPT_FILE_CHECK = (False, ) + _FILE_CHECK
78
REQ_DIR_CHECK = (True, ) + _DIR_CHECK
79
OPT_DIR_CHECK = (False, ) + _DIR_CHECK
80
NET_PORT_CHECK = (True, lambda x: x > 0 and x < 65535, "invalid port number",
81
                  None, None)
82
OPT_CPU_MASK_CHECK = (False, ) + _CPU_MASK_CHECK
83
REQ_CPU_MASK_CHECK = (True, ) + _CPU_MASK_CHECK
84

    
85
# no checks at all
86
NO_CHECK = (False, None, None, None, None)
87

    
88
# required, but no other checks
89
REQUIRED_CHECK = (True, None, None, None, None)
90

    
91
# migration type
92
MIGRATION_MODE_CHECK = (True, lambda x: x in constants.HT_MIGRATION_MODES,
93
                        "invalid migration mode", None, None)
94

    
95

    
96
def ParamInSet(required, my_set):
97
  """Builds parameter checker for set membership.
98

99
  @type required: boolean
100
  @param required: whether this is a required parameter
101
  @type my_set: tuple, list or set
102
  @param my_set: allowed values set
103

104
  """
105
  fn = lambda x: x in my_set
106
  err = ("The value must be one of: %s" % utils.CommaJoin(my_set))
107
  return (required, fn, err, None, None)
108

    
109

    
110
class BaseHypervisor(object):
111
  """Abstract virtualisation technology interface
112

113
  The goal is that all aspects of the virtualisation technology are
114
  abstracted away from the rest of code.
115

116
  @cvar PARAMETERS: a dict of parameter name: check type; the check type is
117
      a five-tuple containing:
118
          - the required flag (boolean)
119
          - a function to check for syntax, that will be used in
120
            L{CheckParameterSyntax}, in the master daemon process
121
          - an error message for the above function
122
          - a function to check for parameter validity on the remote node,
123
            in the L{ValidateParameters} function
124
          - an error message for the above function
125
  @type CAN_MIGRATE: boolean
126
  @cvar CAN_MIGRATE: whether this hypervisor can do migration (either
127
      live or non-live)
128

129
  """
130
  PARAMETERS = {}
131
  ANCILLARY_FILES = []
132
  CAN_MIGRATE = False
133

    
134
  def __init__(self):
135
    pass
136

    
137
  def StartInstance(self, instance, block_devices):
138
    """Start an instance."""
139
    raise NotImplementedError
140

    
141
  def StopInstance(self, instance, force=False, retry=False, name=None):
142
    """Stop an instance
143

144
    @type instance: L{objects.Instance}
145
    @param instance: instance to stop
146
    @type force: boolean
147
    @param force: whether to do a "hard" stop (destroy)
148
    @type retry: boolean
149
    @param retry: whether this is just a retry call
150
    @type name: string or None
151
    @param name: if this parameter is passed, the the instance object
152
        should not be used (will be passed as None), and the shutdown
153
        must be done by name only
154

155
    """
156
    raise NotImplementedError
157

    
158
  def CleanupInstance(self, instance_name):
159
    """Cleanup after a stopped instance
160

161
    This is an optional method, used by hypervisors that need to cleanup after
162
    an instance has been stopped.
163

164
    @type instance_name: string
165
    @param instance_name: instance name to cleanup after
166

167
    """
168
    pass
169

    
170
  def RebootInstance(self, instance):
171
    """Reboot an instance."""
172
    raise NotImplementedError
173

    
174
  def ListInstances(self):
175
    """Get the list of running instances."""
176
    raise NotImplementedError
177

    
178
  def GetInstanceInfo(self, instance_name):
179
    """Get instance properties.
180

181
    @type instance_name: string
182
    @param instance_name: the instance name
183

184
    @return: tuple (name, id, memory, vcpus, state, times)
185

186
    """
187
    raise NotImplementedError
188

    
189
  def GetAllInstancesInfo(self):
190
    """Get properties of all instances.
191

192
    @return: list of tuples (name, id, memory, vcpus, stat, times)
193

194
    """
195
    raise NotImplementedError
196

    
197
  def GetNodeInfo(self):
198
    """Return information about the node.
199

200
    @return: a dict with the following keys (values in MiB):
201
          - memory_total: the total memory size on the node
202
          - memory_free: the available memory on the node for instances
203
          - memory_dom0: the memory used by the node itself, if available
204

205
    """
206
    raise NotImplementedError
207

    
208
  @classmethod
209
  def GetInstanceConsole(cls, instance, hvparams, beparams):
210
    """Return information for connecting to the console of an instance.
211

212
    """
213
    raise NotImplementedError
214

    
215
  @classmethod
216
  def GetAncillaryFiles(cls):
217
    """Return a list of ancillary files to be copied to all nodes as ancillary
218
    configuration files.
219

220
    @rtype: list of strings
221
    @return: list of absolute paths of files to ship cluster-wide
222

223
    """
224
    # By default we return a member variable, so that if an hypervisor has just
225
    # a static list of files it doesn't have to override this function.
226
    return cls.ANCILLARY_FILES
227

    
228
  def Verify(self):
229
    """Verify the hypervisor.
230

231
    """
232
    raise NotImplementedError
233

    
234
  def MigrationInfo(self, instance): # pylint: disable-msg=R0201,W0613
235
    """Get instance information to perform a migration.
236

237
    By default assume no information is needed.
238

239
    @type instance: L{objects.Instance}
240
    @param instance: instance to be migrated
241
    @rtype: string/data (opaque)
242
    @return: instance migration information - serialized form
243

244
    """
245
    return ''
246

    
247
  def AcceptInstance(self, instance, info, target):
248
    """Prepare to accept an instance.
249

250
    By default assume no preparation is needed.
251

252
    @type instance: L{objects.Instance}
253
    @param instance: instance to be accepted
254
    @type info: string/data (opaque)
255
    @param info: migration information, from the source node
256
    @type target: string
257
    @param target: target host (usually ip), on this node
258

259
    """
260
    pass
261

    
262
  def FinalizeMigration(self, instance, info, success):
263
    """Finalized an instance migration.
264

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

268
    @type instance: L{objects.Instance}
269
    @param instance: instance whose migration is being finalized
270
    @type info: string/data (opaque)
271
    @param info: migration information, from the source node
272
    @type success: boolean
273
    @param success: whether the migration was a success or a failure
274

275
    """
276
    pass
277

    
278
  def MigrateInstance(self, instance, target, live):
279
    """Migrate an instance.
280

281
    @type instance: L{objects.Instance}
282
    @param instance: the instance to be migrated
283
    @type target: string
284
    @param target: hostname (usually ip) of the target node
285
    @type live: boolean
286
    @param live: whether to do a live or non-live migration
287

288
    """
289
    raise NotImplementedError
290

    
291
  @classmethod
292
  def CheckParameterSyntax(cls, hvparams):
293
    """Check the given parameters for validity.
294

295
    This should check the passed set of parameters for
296
    validity. Classes should extend, not replace, this function.
297

298
    @type hvparams:  dict
299
    @param hvparams: dictionary with parameter names/value
300
    @raise errors.HypervisorError: when a parameter is not valid
301

302
    """
303
    for key in hvparams:
304
      if key not in cls.PARAMETERS:
305
        raise errors.HypervisorError("Parameter '%s' is not supported" % key)
306

    
307
    # cheap tests that run on the master, should not access the world
308
    for name, (required, check_fn, errstr, _, _) in cls.PARAMETERS.items():
309
      if name not in hvparams:
310
        raise errors.HypervisorError("Parameter '%s' is missing" % name)
311
      value = hvparams[name]
312
      if not required and not value:
313
        continue
314
      if not value:
315
        raise errors.HypervisorError("Parameter '%s' is required but"
316
                                     " is currently not defined" % (name, ))
317
      if check_fn is not None and not check_fn(value):
318
        raise errors.HypervisorError("Parameter '%s' fails syntax"
319
                                     " check: %s (current value: '%s')" %
320
                                     (name, errstr, value))
321

    
322
  @classmethod
323
  def ValidateParameters(cls, hvparams):
324
    """Check the given parameters for validity.
325

326
    This should check the passed set of parameters for
327
    validity. Classes should extend, not replace, this function.
328

329
    @type hvparams:  dict
330
    @param hvparams: dictionary with parameter names/value
331
    @raise errors.HypervisorError: when a parameter is not valid
332

333
    """
334
    for name, (required, _, _, check_fn, errstr) in cls.PARAMETERS.items():
335
      value = hvparams[name]
336
      if not required and not value:
337
        continue
338
      if check_fn is not None and not check_fn(value):
339
        raise errors.HypervisorError("Parameter '%s' fails"
340
                                     " validation: %s (current value: '%s')" %
341
                                     (name, errstr, value))
342

    
343
  @classmethod
344
  def PowercycleNode(cls):
345
    """Hard powercycle a node using hypervisor specific methods.
346

347
    This method should hard powercycle the node, using whatever
348
    methods the hypervisor provides. Note that this means that all
349
    instances running on the node must be stopped too.
350

351
    """
352
    raise NotImplementedError
353

    
354
  @staticmethod
355
  def GetLinuxNodeInfo():
356
    """For linux systems, return actual OS information.
357

358
    This is an abstraction for all non-hypervisor-based classes, where
359
    the node actually sees all the memory and CPUs via the /proc
360
    interface and standard commands. The other case if for example
361
    xen, where you only see the hardware resources via xen-specific
362
    tools.
363

364
    @return: a dict with the following keys (values in MiB):
365
          - memory_total: the total memory size on the node
366
          - memory_free: the available memory on the node for instances
367
          - memory_dom0: the memory used by the node itself, if available
368

369
    """
370
    try:
371
      data = utils.ReadFile("/proc/meminfo").splitlines()
372
    except EnvironmentError, err:
373
      raise errors.HypervisorError("Failed to list node info: %s" % (err,))
374

    
375
    result = {}
376
    sum_free = 0
377
    try:
378
      for line in data:
379
        splitfields = line.split(":", 1)
380

    
381
        if len(splitfields) > 1:
382
          key = splitfields[0].strip()
383
          val = splitfields[1].strip()
384
          if key == 'MemTotal':
385
            result['memory_total'] = int(val.split()[0])/1024
386
          elif key in ('MemFree', 'Buffers', 'Cached'):
387
            sum_free += int(val.split()[0])/1024
388
          elif key == 'Active':
389
            result['memory_dom0'] = int(val.split()[0])/1024
390
    except (ValueError, TypeError), err:
391
      raise errors.HypervisorError("Failed to compute memory usage: %s" %
392
                                   (err,))
393
    result['memory_free'] = sum_free
394

    
395
    cpu_total = 0
396
    try:
397
      fh = open("/proc/cpuinfo")
398
      try:
399
        cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
400
                                   fh.read()))
401
      finally:
402
        fh.close()
403
    except EnvironmentError, err:
404
      raise errors.HypervisorError("Failed to list node info: %s" % (err,))
405
    result['cpu_total'] = cpu_total
406
    # FIXME: export correct data here
407
    result['cpu_nodes'] = 1
408
    result['cpu_sockets'] = 1
409

    
410
    return result
411

    
412
  @classmethod
413
  def LinuxPowercycle(cls):
414
    """Linux-specific powercycle method.
415

416
    """
417
    try:
418
      fd = os.open("/proc/sysrq-trigger", os.O_WRONLY)
419
      try:
420
        os.write(fd, "b")
421
      finally:
422
        fd.close()
423
    except OSError:
424
      logging.exception("Can't open the sysrq-trigger file")
425
      result = utils.RunCmd(["reboot", "-n", "-f"])
426
      if not result:
427
        logging.error("Can't run shutdown: %s", result.output)