Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_base.py @ 364e1664

History | View | Annotate | Download (20.8 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2012, 2013 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
  """Verifies if the given single CPU mask is valid
52

53
  The single CPU mask should be in the form "a,b,c,d", where each
54
  letter is a positive number or range.
55

56
  """
57
  try:
58
    cpu_list = utils.ParseCpuMask(cpu_mask)
59
  except errors.ParseError, _:
60
    return False
61
  return isinstance(cpu_list, list) and len(cpu_list) > 0
62

    
63

    
64
def _IsMultiCpuMaskWellFormed(cpu_mask):
65
  """Verifies if the given multiple CPU mask is valid
66

67
  A valid multiple CPU mask is in the form "a:b:c:d", where each
68
  letter is a single CPU mask.
69

70
  """
71
  try:
72
    utils.ParseMultiCpuMask(cpu_mask)
73
  except errors.ParseError, _:
74
    return False
75

    
76
  return True
77

    
78

    
79
# Read the BaseHypervisor.PARAMETERS docstring for the syntax of the
80
# _CHECK values
81

    
82
# must be a file
83
_FILE_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
84
               os.path.isfile, "not found or not a file")
85

    
86
# must be a file or a URL
87
_FILE_OR_URL_CHECK = (lambda x: utils.IsNormAbsPath(x) or utils.IsUrl(x),
88
                      "must be an absolute normalized path or a URL",
89
                      lambda x: os.path.isfile(x) or utils.IsUrl(x),
90
                      "not found or not a file or URL")
91

    
92
# must be a directory
93
_DIR_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
94
              os.path.isdir, "not found or not a directory")
95

    
96
# CPU mask must be well-formed
97
# TODO: implement node level check for the CPU mask
98
_CPU_MASK_CHECK = (_IsCpuMaskWellFormed,
99
                   "CPU mask definition is not well-formed",
100
                   None, None)
101

    
102
# Multiple CPU mask must be well-formed
103
_MULTI_CPU_MASK_CHECK = (_IsMultiCpuMaskWellFormed,
104
                         "Multiple CPU mask definition is not well-formed",
105
                         None, None)
106

    
107
# Check for validity of port number
108
_NET_PORT_CHECK = (lambda x: 0 < x < 65535, "invalid port number",
109
                   None, None)
110

    
111
# Check that an integer is non negative
112
_NONNEGATIVE_INT_CHECK = (lambda x: x >= 0, "cannot be negative", None, None)
113

    
114
# nice wrappers for users
115
REQ_FILE_CHECK = (True, ) + _FILE_CHECK
116
OPT_FILE_CHECK = (False, ) + _FILE_CHECK
117
REQ_FILE_OR_URL_CHECK = (True, ) + _FILE_OR_URL_CHECK
118
OPT_FILE_OR_URL_CHECK = (False, ) + _FILE_OR_URL_CHECK
119
REQ_DIR_CHECK = (True, ) + _DIR_CHECK
120
OPT_DIR_CHECK = (False, ) + _DIR_CHECK
121
REQ_NET_PORT_CHECK = (True, ) + _NET_PORT_CHECK
122
OPT_NET_PORT_CHECK = (False, ) + _NET_PORT_CHECK
123
REQ_CPU_MASK_CHECK = (True, ) + _CPU_MASK_CHECK
124
OPT_CPU_MASK_CHECK = (False, ) + _CPU_MASK_CHECK
125
REQ_MULTI_CPU_MASK_CHECK = (True, ) + _MULTI_CPU_MASK_CHECK
126
OPT_MULTI_CPU_MASK_CHECK = (False, ) + _MULTI_CPU_MASK_CHECK
127
REQ_NONNEGATIVE_INT_CHECK = (True, ) + _NONNEGATIVE_INT_CHECK
128
OPT_NONNEGATIVE_INT_CHECK = (False, ) + _NONNEGATIVE_INT_CHECK
129

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

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

    
136
# migration type
137
MIGRATION_MODE_CHECK = (True, lambda x: x in constants.HT_MIGRATION_MODES,
138
                        "invalid migration mode", None, None)
139

    
140

    
141
def ParamInSet(required, my_set):
142
  """Builds parameter checker for set membership.
143

144
  @type required: boolean
145
  @param required: whether this is a required parameter
146
  @type my_set: tuple, list or set
147
  @param my_set: allowed values set
148

149
  """
150
  fn = lambda x: x in my_set
151
  err = ("The value must be one of: %s" % utils.CommaJoin(my_set))
152
  return (required, fn, err, None, None)
153

    
154

    
155
class HvInstanceState(object):
156
  RUNNING = 0
157
  SHUTDOWN = 1
158

    
159
  @staticmethod
160
  def IsRunning(s):
161
    return s == HvInstanceState.RUNNING
162

    
163
  @staticmethod
164
  def IsShutdown(s):
165
    return s == HvInstanceState.SHUTDOWN
166

    
167

    
168
class BaseHypervisor(object):
169
  """Abstract virtualisation technology interface
170

171
  The goal is that all aspects of the virtualisation technology are
172
  abstracted away from the rest of code.
173

174
  @cvar PARAMETERS: a dict of parameter name: check type; the check type is
175
      a five-tuple containing:
176
          - the required flag (boolean)
177
          - a function to check for syntax, that will be used in
178
            L{CheckParameterSyntax}, in the master daemon process
179
          - an error message for the above function
180
          - a function to check for parameter validity on the remote node,
181
            in the L{ValidateParameters} function
182
          - an error message for the above function
183
  @type CAN_MIGRATE: boolean
184
  @cvar CAN_MIGRATE: whether this hypervisor can do migration (either
185
      live or non-live)
186

187
  """
188
  PARAMETERS = {}
189
  ANCILLARY_FILES = []
190
  ANCILLARY_FILES_OPT = []
191
  CAN_MIGRATE = False
192

    
193
  def StartInstance(self, instance, block_devices, startup_paused):
194
    """Start an instance."""
195
    raise NotImplementedError
196

    
197
  def StopInstance(self, instance, force=False, retry=False, name=None):
198
    """Stop an instance
199

200
    @type instance: L{objects.Instance}
201
    @param instance: instance to stop
202
    @type force: boolean
203
    @param force: whether to do a "hard" stop (destroy)
204
    @type retry: boolean
205
    @param retry: whether this is just a retry call
206
    @type name: string or None
207
    @param name: if this parameter is passed, the the instance object
208
        should not be used (will be passed as None), and the shutdown
209
        must be done by name only
210
    @raise errors.HypervisorError: when a parameter is not valid or
211
        the instance failed to be stopped
212

213
    """
214
    raise NotImplementedError
215

    
216
  def CleanupInstance(self, instance_name):
217
    """Cleanup after a stopped instance
218

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

222
    @type instance_name: string
223
    @param instance_name: instance name to cleanup after
224

225
    """
226
    pass
227

    
228
  def RebootInstance(self, instance):
229
    """Reboot an instance."""
230
    raise NotImplementedError
231

    
232
  def ListInstances(self, hvparams=None):
233
    """Get the list of running instances."""
234
    raise NotImplementedError
235

    
236
  def GetInstanceInfo(self, instance_name, hvparams=None):
237
    """Get instance properties.
238

239
    @type instance_name: string
240
    @param instance_name: the instance name
241
    @type hvparams: dict of strings
242
    @param hvparams: hvparams to be used with this instance
243

244
    @rtype: (string, string, int, int, HvInstanceState, int)
245
    @return: tuple (name, id, memory, vcpus, state, times)
246

247
    """
248
    raise NotImplementedError
249

    
250
  def GetAllInstancesInfo(self, hvparams=None):
251
    """Get properties of all instances.
252

253
    @type hvparams: dict of strings
254
    @param hvparams: hypervisor parameter
255

256
    @rtype: (string, string, int, int, HvInstanceState, int)
257
    @return: list of tuples (name, id, memory, vcpus, state, times)
258

259
    """
260
    raise NotImplementedError
261

    
262
  def GetNodeInfo(self, hvparams=None):
263
    """Return information about the node.
264

265
    @type hvparams: dict of strings
266
    @param hvparams: hypervisor parameters
267

268
    @return: a dict with at least the following keys (memory values in MiB):
269
          - memory_total: the total memory size on the node
270
          - memory_free: the available memory on the node for instances
271
          - memory_dom0: the memory used by the node itself, if available
272
          - cpu_total: total number of CPUs
273
          - cpu_dom0: number of CPUs used by the node OS
274
          - cpu_nodes: number of NUMA domains
275
          - cpu_sockets: number of physical CPU sockets
276

277
    """
278
    raise NotImplementedError
279

    
280
  @classmethod
281
  def GetInstanceConsole(cls, instance, primary_node, node_group,
282
                         hvparams, beparams):
283
    """Return information for connecting to the console of an instance.
284

285
    """
286
    raise NotImplementedError
287

    
288
  @classmethod
289
  def GetAncillaryFiles(cls):
290
    """Return a list of ancillary files to be copied to all nodes as ancillary
291
    configuration files.
292

293
    @rtype: (list of absolute paths, list of absolute paths)
294
    @return: (all files, optional files)
295

296
    """
297
    # By default we return a member variable, so that if an hypervisor has just
298
    # a static list of files it doesn't have to override this function.
299
    assert set(cls.ANCILLARY_FILES).issuperset(cls.ANCILLARY_FILES_OPT), \
300
      "Optional ancillary files must be a subset of ancillary files"
301

    
302
    return (cls.ANCILLARY_FILES, cls.ANCILLARY_FILES_OPT)
303

    
304
  def Verify(self, hvparams=None):
305
    """Verify the hypervisor.
306

307
    @type hvparams: dict of strings
308
    @param hvparams: hypervisor parameters to be verified against
309

310
    @return: Problem description if something is wrong, C{None} otherwise
311

312
    """
313
    raise NotImplementedError
314

    
315
  def MigrationInfo(self, instance): # pylint: disable=R0201,W0613
316
    """Get instance information to perform a migration.
317

318
    By default assume no information is needed.
319

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

325
    """
326
    return ""
327

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

331
    By default assume no preparation is needed.
332

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

340
    """
341
    pass
342

    
343
  def BalloonInstanceMemory(self, instance, mem):
344
    """Balloon an instance memory to a certain value.
345

346
    @type instance: L{objects.Instance}
347
    @param instance: instance to be accepted
348
    @type mem: int
349
    @param mem: actual memory size to use for instance runtime
350

351
    """
352
    raise NotImplementedError
353

    
354
  def FinalizeMigrationDst(self, instance, info, success):
355
    """Finalize the instance migration on the target node.
356

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

360
    @type instance: L{objects.Instance}
361
    @param instance: instance whose migration is being finalized
362
    @type info: string/data (opaque)
363
    @param info: migration information, from the source node
364
    @type success: boolean
365
    @param success: whether the migration was a success or a failure
366

367
    """
368
    pass
369

    
370
  def MigrateInstance(self, cluster_name, instance, target, live):
371
    """Migrate an instance.
372

373
    @type cluster_name: string
374
    @param cluster_name: name of the cluster
375
    @type instance: L{objects.Instance}
376
    @param instance: the instance to be migrated
377
    @type target: string
378
    @param target: hostname (usually ip) of the target node
379
    @type live: boolean
380
    @param live: whether to do a live or non-live migration
381

382
    """
383
    raise NotImplementedError
384

    
385
  def FinalizeMigrationSource(self, instance, success, live):
386
    """Finalize the instance migration on the source node.
387

388
    @type instance: L{objects.Instance}
389
    @param instance: the instance that was migrated
390
    @type success: bool
391
    @param success: whether the migration succeeded or not
392
    @type live: bool
393
    @param live: whether the user requested a live migration or not
394

395
    """
396
    pass
397

    
398
  def GetMigrationStatus(self, instance):
399
    """Get the migration status
400

401
    @type instance: L{objects.Instance}
402
    @param instance: the instance that is being migrated
403
    @rtype: L{objects.MigrationStatus}
404
    @return: the status of the current migration (one of
405
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
406
             progress info that can be retrieved from the hypervisor
407

408
    """
409
    raise NotImplementedError
410

    
411
  def _InstanceStartupMemory(self, instance, hvparams=None):
412
    """Get the correct startup memory for an instance
413

414
    This function calculates how much memory an instance should be started
415
    with, making sure it's a value between the minimum and the maximum memory,
416
    but also trying to use no more than the current free memory on the node.
417

418
    @type instance: L{objects.Instance}
419
    @param instance: the instance that is being started
420
    @rtype: integer
421
    @return: memory the instance should be started with
422

423
    """
424
    free_memory = self.GetNodeInfo(hvparams=hvparams)["memory_free"]
425
    max_start_mem = min(instance.beparams[constants.BE_MAXMEM], free_memory)
426
    start_mem = max(instance.beparams[constants.BE_MINMEM], max_start_mem)
427
    return start_mem
428

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

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

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

440
    """
441
    for key in hvparams:
442
      if key not in cls.PARAMETERS:
443
        raise errors.HypervisorError("Parameter '%s' is not supported" % key)
444

    
445
    # cheap tests that run on the master, should not access the world
446
    for name, (required, check_fn, errstr, _, _) in cls.PARAMETERS.items():
447
      if name not in hvparams:
448
        raise errors.HypervisorError("Parameter '%s' is missing" % name)
449
      value = hvparams[name]
450
      if not required and not value:
451
        continue
452
      if not value:
453
        raise errors.HypervisorError("Parameter '%s' is required but"
454
                                     " is currently not defined" % (name, ))
455
      if check_fn is not None and not check_fn(value):
456
        raise errors.HypervisorError("Parameter '%s' fails syntax"
457
                                     " check: %s (current value: '%s')" %
458
                                     (name, errstr, value))
459

    
460
  @classmethod
461
  def ValidateParameters(cls, hvparams):
462
    """Check the given parameters for validity.
463

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

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

471
    """
472
    for name, (required, _, _, check_fn, errstr) in cls.PARAMETERS.items():
473
      value = hvparams[name]
474
      if not required and not value:
475
        continue
476
      if check_fn is not None and not check_fn(value):
477
        raise errors.HypervisorError("Parameter '%s' fails"
478
                                     " validation: %s (current value: '%s')" %
479
                                     (name, errstr, value))
480

    
481
  @classmethod
482
  def PowercycleNode(cls, hvparams=None):
483
    """Hard powercycle a node using hypervisor specific methods.
484

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

489
    @type hvparams: dict of strings
490
    @param hvparams: hypervisor params to be used on this node
491

492
    """
493
    raise NotImplementedError
494

    
495
  @staticmethod
496
  def GetLinuxNodeInfo(meminfo="/proc/meminfo", cpuinfo="/proc/cpuinfo"):
497
    """For linux systems, return actual OS information.
498

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

505
    @param meminfo: name of the file containing meminfo
506
    @type meminfo: string
507
    @param cpuinfo: name of the file containing cpuinfo
508
    @type cpuinfo: string
509
    @return: a dict with the following keys (values in MiB):
510
          - memory_total: the total memory size on the node
511
          - memory_free: the available memory on the node for instances
512
          - memory_dom0: the memory used by the node itself, if available
513
          - cpu_total: total number of CPUs
514
          - cpu_dom0: number of CPUs used by the node OS
515
          - cpu_nodes: number of NUMA domains
516
          - cpu_sockets: number of physical CPU sockets
517

518
    """
519
    try:
520
      data = utils.ReadFile(meminfo).splitlines()
521
    except EnvironmentError, err:
522
      raise errors.HypervisorError("Failed to list node info: %s" % (err,))
523

    
524
    result = {}
525
    sum_free = 0
526
    try:
527
      for line in data:
528
        splitfields = line.split(":", 1)
529

    
530
        if len(splitfields) > 1:
531
          key = splitfields[0].strip()
532
          val = splitfields[1].strip()
533
          if key == "MemTotal":
534
            result["memory_total"] = int(val.split()[0]) / 1024
535
          elif key in ("MemFree", "Buffers", "Cached"):
536
            sum_free += int(val.split()[0]) / 1024
537
          elif key == "Active":
538
            result["memory_dom0"] = int(val.split()[0]) / 1024
539
    except (ValueError, TypeError), err:
540
      raise errors.HypervisorError("Failed to compute memory usage: %s" %
541
                                   (err,))
542
    result["memory_free"] = sum_free
543

    
544
    cpu_total = 0
545
    try:
546
      fh = open(cpuinfo)
547
      try:
548
        cpu_total = len(re.findall(r"(?m)^processor\s*:\s*[0-9]+\s*$",
549
                                   fh.read()))
550
      finally:
551
        fh.close()
552
    except EnvironmentError, err:
553
      raise errors.HypervisorError("Failed to list node info: %s" % (err,))
554
    result["cpu_total"] = cpu_total
555
    # We assume that the node OS can access all the CPUs
556
    result["cpu_dom0"] = cpu_total
557
    # FIXME: export correct data here
558
    result["cpu_nodes"] = 1
559
    result["cpu_sockets"] = 1
560

    
561
    return result
562

    
563
  @classmethod
564
  def LinuxPowercycle(cls):
565
    """Linux-specific powercycle method.
566

567
    """
568
    try:
569
      fd = os.open("/proc/sysrq-trigger", os.O_WRONLY)
570
      try:
571
        os.write(fd, "b")
572
      finally:
573
        fd.close()
574
    except OSError:
575
      logging.exception("Can't open the sysrq-trigger file")
576
      result = utils.RunCmd(["reboot", "-n", "-f"])
577
      if not result:
578
        logging.error("Can't run shutdown: %s", result.output)
579

    
580
  @staticmethod
581
  def _FormatVerifyResults(msgs):
582
    """Formats the verification results, given a list of errors.
583

584
    @param msgs: list of errors, possibly empty
585
    @return: overall problem description if something is wrong,
586
        C{None} otherwise
587

588
    """
589
    if msgs:
590
      return "; ".join(msgs)
591
    else:
592
      return None
593

    
594
  # pylint: disable=R0201,W0613
595
  def HotAddDevice(self, instance, dev_type, device, extra, seq):
596
    """Hot-add a device.
597

598
    """
599
    raise errors.HotplugError("Hotplug is not supported by this hypervisor")
600

    
601
  # pylint: disable=R0201,W0613
602
  def HotDelDevice(self, instance, dev_type, device, extra, seq):
603
    """Hot-del a device.
604

605
    """
606
    raise errors.HotplugError("Hotplug is not supported by this hypervisor")
607

    
608
  # pylint: disable=R0201,W0613
609
  def HotModDevice(self, instance, dev_type, device, extra, seq):
610
    """Hot-mod a device.
611

612
    """
613
    raise errors.HotplugError("Hotplug is not supported by this hypervisor")
614

    
615
  # pylint: disable=R0201,W0613
616
  def VerifyHotplugSupport(self, instance, action, dev_type):
617
    """Verifies that hotplug is supported.
618

619
    Given the target device and hotplug action checks if hotplug is
620
    actually supported.
621

622
    @type instance: L{objects.Instance}
623
    @param instance: the instance object
624
    @type action: string
625
    @param action: one of the supported hotplug commands
626
    @type dev_type: string
627
    @param dev_type: one of the supported device types to hotplug
628
    @raise errors.HotplugError: if hotplugging is not supported
629

630
    """
631
    raise errors.HotplugError("Hotplug is not supported.")
632

    
633
  def HotplugSupported(self, instance):
634
    """Checks if hotplug is supported.
635

636
    By default is not. Currently only KVM hypervisor supports it.
637

638
    """
639
    raise errors.HotplugError("Hotplug is not supported by this hypervisor")