hv_base.py 13.6 KB
Newer Older
1
2
3
#
#

4
# Copyright (C) 2006, 2007, 2008, 2009, 2010 Google Inc.
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.


"""Base class for all hypervisors

24
25
26
27
28
29
30
31
32
33
34
35
36
37
The syntax for the _CHECK variables and the contents of the PARAMETERS
dict is the same, see the docstring for L{BaseHypervisor.PARAMETERS}.

@var _FILE_CHECK: stub for file checks, without the required flag
@var _DIR_CHECK: stub for directory checks, without the required flag
@var REQ_FILE_CHECK: mandatory file parameter
@var OPT_FILE_CHECK: optional file parameter
@var REQ_DIR_CHECK: mandatory directory parametr
@var OPT_DIR_CHECK: optional directory parameter
@var NO_CHECK: parameter without any checks at all
@var REQUIRED_CHECK: parameter required to exist (and non-false), but
    without other checks; beware that this can't be used for boolean
    parameters, where you should use NO_CHECK or a custom checker

38
39
"""

40
import os
41
import re
Iustin Pop's avatar
Iustin Pop committed
42
import logging
43
44


45
from ganeti import errors
46
from ganeti import utils
47
from ganeti import constants
48
49


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


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

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

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

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

75
76
77
78
# Check for validity of port number
_NET_PORT_CHECK = (lambda x: 0 < x < 65535, "invalid port number",
                   None, None)

79
80
81
82
83
# nice wrappers for users
REQ_FILE_CHECK = (True, ) + _FILE_CHECK
OPT_FILE_CHECK = (False, ) + _FILE_CHECK
REQ_DIR_CHECK = (True, ) + _DIR_CHECK
OPT_DIR_CHECK = (False, ) + _DIR_CHECK
84
85
REQ_NET_PORT_CHECK = (True, ) + _NET_PORT_CHECK
OPT_NET_PORT_CHECK = (False, ) + _NET_PORT_CHECK
86
REQ_CPU_MASK_CHECK = (True, ) + _CPU_MASK_CHECK
87
OPT_CPU_MASK_CHECK = (False, ) + _CPU_MASK_CHECK
88
89
90
91
92
93
94

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

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

95
# migration type
96
97
MIGRATION_MODE_CHECK = (True, lambda x: x in constants.HT_MIGRATION_MODES,
                        "invalid migration mode", None, None)
98

Michael Hanselmann's avatar
Michael Hanselmann committed
99

100
101
102
103
104
105
106
107
108
109
def ParamInSet(required, my_set):
  """Builds parameter checker for set membership.

  @type required: boolean
  @param required: whether this is a required parameter
  @type my_set: tuple, list or set
  @param my_set: allowed values set

  """
  fn = lambda x: x in my_set
110
  err = ("The value must be one of: %s" % utils.CommaJoin(my_set))
111
  return (required, fn, err, None, None)
112
113


114
115
116
class BaseHypervisor(object):
  """Abstract virtualisation technology interface

117
118
  The goal is that all aspects of the virtualisation technology are
  abstracted away from the rest of code.
119

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

133
  """
134
  PARAMETERS = {}
135
  ANCILLARY_FILES = []
136
  CAN_MIGRATE = False
137

138
139
140
  def __init__(self):
    pass

141
  def StartInstance(self, instance, block_devices, startup_paused):
142
143
144
    """Start an instance."""
    raise NotImplementedError

145
  def StopInstance(self, instance, force=False, retry=False, name=None):
146
147
148
149
150
151
152
153
    """Stop an instance

    @type instance: L{objects.Instance}
    @param instance: instance to stop
    @type force: boolean
    @param force: whether to do a "hard" stop (destroy)
    @type retry: boolean
    @param retry: whether this is just a retry call
154
155
156
157
    @type name: string or None
    @param name: if this parameter is passed, the the instance object
        should not be used (will be passed as None), and the shutdown
        must be done by name only
158
159

    """
160
161
    raise NotImplementedError

162
163
164
165
166
167
168
169
170
171
172
173
  def CleanupInstance(self, instance_name):
    """Cleanup after a stopped instance

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

    @type instance_name: string
    @param instance_name: instance name to cleanup after

    """
    pass

174
175
176
177
178
179
180
181
182
183
184
  def RebootInstance(self, instance):
    """Reboot an instance."""
    raise NotImplementedError

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

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

185
    @type instance_name: string
Iustin Pop's avatar
Iustin Pop committed
186
    @param instance_name: the instance name
187

Iustin Pop's avatar
Iustin Pop committed
188
    @return: tuple (name, id, memory, vcpus, state, times)
189
190
191
192
193
194
195

    """
    raise NotImplementedError

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

Iustin Pop's avatar
Iustin Pop committed
196
197
    @return: list of tuples (name, id, memory, vcpus, stat, times)

198
199
200
201
202
203
    """
    raise NotImplementedError

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

Iustin Pop's avatar
Iustin Pop committed
204
205
206
207
    @return: a dict with the following keys (values in MiB):
          - memory_total: the total memory size on the node
          - memory_free: the available memory on the node for instances
          - memory_dom0: the memory used by the node itself, if available
208
209
210
211

    """
    raise NotImplementedError

212
  @classmethod
213
214
  def GetInstanceConsole(cls, instance, hvparams, beparams):
    """Return information for connecting to the console of an instance.
215
216
217
218

    """
    raise NotImplementedError

219
220
221
222
223
224
225
226
227
228
229
230
231
  @classmethod
  def GetAncillaryFiles(cls):
    """Return a list of ancillary files to be copied to all nodes as ancillary
    configuration files.

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

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

232
233
234
235
236
  def Verify(self):
    """Verify the hypervisor.

    """
    raise NotImplementedError
237

Iustin Pop's avatar
Iustin Pop committed
238
  def MigrationInfo(self, instance): # pylint: disable-msg=R0201,W0613
239
240
241
242
243
244
245
246
247
248
    """Get instance information to perform a migration.

    By default assume no information is needed.

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

    """
Iustin Pop's avatar
Iustin Pop committed
249
    return ""
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272

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

    By default assume no preparation is needed.

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

    """
    pass

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

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

    @type instance: L{objects.Instance}
273
    @param instance: instance whose migration is being finalized
274
275
276
277
278
279
280
281
    @type info: string/data (opaque)
    @param info: migration information, from the source node
    @type success: boolean
    @param success: whether the migration was a success or a failure

    """
    pass

282
  def MigrateInstance(self, instance, target, live):
283
284
    """Migrate an instance.

Iustin Pop's avatar
Iustin Pop committed
285
    @type instance: L{objects.Instance}
Michael Hanselmann's avatar
Michael Hanselmann committed
286
    @param instance: the instance to be migrated
287
288
289
290
    @type target: string
    @param target: hostname (usually ip) of the target node
    @type live: boolean
    @param live: whether to do a live or non-live migration
291
292
293

    """
    raise NotImplementedError
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308

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

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

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

    """
    for key in hvparams:
      if key not in cls.PARAMETERS:
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
        raise errors.HypervisorError("Parameter '%s' is not supported" % key)

    # cheap tests that run on the master, should not access the world
    for name, (required, check_fn, errstr, _, _) in cls.PARAMETERS.items():
      if name not in hvparams:
        raise errors.HypervisorError("Parameter '%s' is missing" % name)
      value = hvparams[name]
      if not required and not value:
        continue
      if not value:
        raise errors.HypervisorError("Parameter '%s' is required but"
                                     " is currently not defined" % (name, ))
      if check_fn is not None and not check_fn(value):
        raise errors.HypervisorError("Parameter '%s' fails syntax"
                                     " check: %s (current value: '%s')" %
                                     (name, errstr, value))

  @classmethod
  def ValidateParameters(cls, hvparams):
328
329
330
331
332
333
334
335
336
337
    """Check the given parameters for validity.

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

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

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

Iustin Pop's avatar
Iustin Pop committed
347
348
349
350
351
352
353
354
355
356
357
  @classmethod
  def PowercycleNode(cls):
    """Hard powercycle a node using hypervisor specific methods.

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

    """
    raise NotImplementedError

358
359
  @staticmethod
  def GetLinuxNodeInfo():
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
    """For linux systems, return actual OS information.

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

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

    """
    try:
375
      data = utils.ReadFile("/proc/meminfo").splitlines()
376
377
378
379
380
381
382
383
384
385
386
387
    except EnvironmentError, err:
      raise errors.HypervisorError("Failed to list node info: %s" % (err,))

    result = {}
    sum_free = 0
    try:
      for line in data:
        splitfields = line.split(":", 1)

        if len(splitfields) > 1:
          key = splitfields[0].strip()
          val = splitfields[1].strip()
Iustin Pop's avatar
Iustin Pop committed
388
          if key == "MemTotal":
Michael Hanselmann's avatar
Michael Hanselmann committed
389
            result["memory_total"] = int(val.split()[0]) / 1024
Iustin Pop's avatar
Iustin Pop committed
390
          elif key in ("MemFree", "Buffers", "Cached"):
Michael Hanselmann's avatar
Michael Hanselmann committed
391
            sum_free += int(val.split()[0]) / 1024
Iustin Pop's avatar
Iustin Pop committed
392
          elif key == "Active":
Michael Hanselmann's avatar
Michael Hanselmann committed
393
            result["memory_dom0"] = int(val.split()[0]) / 1024
394
395
396
    except (ValueError, TypeError), err:
      raise errors.HypervisorError("Failed to compute memory usage: %s" %
                                   (err,))
Iustin Pop's avatar
Iustin Pop committed
397
    result["memory_free"] = sum_free
398
399
400
401
402
403
404
405
406
407
408

    cpu_total = 0
    try:
      fh = open("/proc/cpuinfo")
      try:
        cpu_total = len(re.findall("(?m)^processor\s*:\s*[0-9]+\s*$",
                                   fh.read()))
      finally:
        fh.close()
    except EnvironmentError, err:
      raise errors.HypervisorError("Failed to list node info: %s" % (err,))
Iustin Pop's avatar
Iustin Pop committed
409
    result["cpu_total"] = cpu_total
410
    # FIXME: export correct data here
Iustin Pop's avatar
Iustin Pop committed
411
412
    result["cpu_nodes"] = 1
    result["cpu_sockets"] = 1
413
414

    return result
Iustin Pop's avatar
Iustin Pop committed
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431

  @classmethod
  def LinuxPowercycle(cls):
    """Linux-specific powercycle method.

    """
    try:
      fd = os.open("/proc/sysrq-trigger", os.O_WRONLY)
      try:
        os.write(fd, "b")
      finally:
        fd.close()
    except OSError:
      logging.exception("Can't open the sysrq-trigger file")
      result = utils.RunCmd(["reboot", "-n", "-f"])
      if not result:
        logging.error("Can't run shutdown: %s", result.output)