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

4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2012, 2013 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 constants
46
from ganeti import errors
47
from ganeti import objects
48
49
50
from ganeti import utils


51
def _IsCpuMaskWellFormed(cpu_mask):
52
53
54
55
56
57
  """Verifies if the given single CPU mask is valid

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

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


65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def _IsMultiCpuMaskWellFormed(cpu_mask):
  """Verifies if the given multiple CPU mask is valid

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

  """
  try:
    utils.ParseMultiCpuMask(cpu_mask)
  except errors.ParseError, _:
    return False

  return True


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

83
# must be a file
84
_FILE_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
Iustin Pop's avatar
Iustin Pop committed
85
               os.path.isfile, "not found or not a file")
86

Jose A. Lopes's avatar
Jose A. Lopes committed
87
# must be a file or a URL
88
_FILE_OR_URL_CHECK = (lambda x: utils.IsNormAbsPath(x) or utils.IsUrl(x),
Jose A. Lopes's avatar
Jose A. Lopes committed
89
                      "must be an absolute normalized path or a URL",
90
                      lambda x: os.path.isfile(x) or utils.IsUrl(x),
Jose A. Lopes's avatar
Jose A. Lopes committed
91
92
                      "not found or not a file or URL")

93
# must be a directory
94
_DIR_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
Iustin Pop's avatar
Iustin Pop committed
95
              os.path.isdir, "not found or not a directory")
96

97
98
99
100
101
102
# 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)

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

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

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

115
116
117
# nice wrappers for users
REQ_FILE_CHECK = (True, ) + _FILE_CHECK
OPT_FILE_CHECK = (False, ) + _FILE_CHECK
Jose A. Lopes's avatar
Jose A. Lopes committed
118
119
REQ_FILE_OR_URL_CHECK = (True, ) + _FILE_OR_URL_CHECK
OPT_FILE_OR_URL_CHECK = (False, ) + _FILE_OR_URL_CHECK
120
121
REQ_DIR_CHECK = (True, ) + _DIR_CHECK
OPT_DIR_CHECK = (False, ) + _DIR_CHECK
122
123
REQ_NET_PORT_CHECK = (True, ) + _NET_PORT_CHECK
OPT_NET_PORT_CHECK = (False, ) + _NET_PORT_CHECK
124
REQ_CPU_MASK_CHECK = (True, ) + _CPU_MASK_CHECK
125
OPT_CPU_MASK_CHECK = (False, ) + _CPU_MASK_CHECK
126
127
REQ_MULTI_CPU_MASK_CHECK = (True, ) + _MULTI_CPU_MASK_CHECK
OPT_MULTI_CPU_MASK_CHECK = (False, ) + _MULTI_CPU_MASK_CHECK
128
129
REQ_NONNEGATIVE_INT_CHECK = (True, ) + _NONNEGATIVE_INT_CHECK
OPT_NONNEGATIVE_INT_CHECK = (False, ) + _NONNEGATIVE_INT_CHECK
130
131
132
133
134
135
136

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

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

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

Michael Hanselmann's avatar
Michael Hanselmann committed
141

142
143
144
145
146
147
148
149
150
151
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
152
  err = ("The value must be one of: %s" % utils.CommaJoin(my_set))
153
  return (required, fn, err, None, None)
154
155


156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
def GenerateTapName():
  """Generate a TAP network interface name for a NIC.

  This helper function generates a special TAP network interface
  name for NICs that are meant to be used in instance communication.
  This function checks the existing TAP interfaces in order to find
  a unique name for the new TAP network interface.  The TAP network
  interface names are of the form 'gnt.com.%d', where '%d' is a
  unique number within the node.

  @rtype: string
  @return: TAP network interface name, or the empty string if the
           NIC is not used in instance communication

  """
  result = utils.RunCmd(["ip", "tuntap", "list"])

  if result.failed:
    raise errors.HypervisorError("Failed to list TUN/TAP interfaces")

  idxs = set()

  for line in result.output.splitlines():
    parts = line.split(": ", 1)

    if len(parts) < 2:
      raise errors.HypervisorError("Failed to parse TUN/TAP interfaces")

    r = re.match(r"gnt\.com\.([0-9]+)", parts[0])

    if r is not None:
      idxs.add(int(r.group(1)))

  if idxs:
    idx = max(idxs) + 1
  else:
    idx = 0

  return "gnt.com.%d" % idx


197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
def ConfigureNIC(cmd, instance, seq, nic, tap):
  """Run the network configuration script for a specified NIC

  @type cmd: string
  @param cmd: command to run
  @type instance: instance object
  @param instance: instance we're acting on
  @type seq: int
  @param seq: nic sequence number
  @type nic: nic object
  @param nic: nic we're acting on
  @type tap: str
  @param tap: the host's tap interface this NIC corresponds to

  """
  env = {
    "PATH": "%s:/sbin:/usr/sbin" % os.environ["PATH"],
    "INSTANCE": instance.name,
    "MAC": nic.mac,
    "MODE": nic.nicparams[constants.NIC_MODE],
    "INTERFACE": tap,
    "INTERFACE_INDEX": str(seq),
    "INTERFACE_UUID": nic.uuid,
    "TAGS": " ".join(instance.GetTags()),
  }

  if nic.ip:
    env["IP"] = nic.ip

  if nic.name:
    env["INTERFACE_NAME"] = nic.name

  if nic.nicparams[constants.NIC_LINK]:
    env["LINK"] = nic.nicparams[constants.NIC_LINK]

  if nic.nicparams[constants.NIC_VLAN]:
    env["VLAN"] = nic.nicparams[constants.NIC_VLAN]

  if nic.network:
    n = objects.Network.FromDict(nic.netinfo)
    env.update(n.HooksDict())

  if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
    env["BRIDGE"] = nic.nicparams[constants.NIC_LINK]

  result = utils.RunCmd(cmd, env=env)
  if result.failed:
    raise errors.HypervisorError("Failed to configure interface %s: %s;"
                                 " network configuration script output: %s" %
                                 (tap, result.fail_reason, result.output))


249
250
251
252
253
254
255
256
257
258
259
260
261
class HvInstanceState(object):
  RUNNING = 0
  SHUTDOWN = 1

  @staticmethod
  def IsRunning(s):
    return s == HvInstanceState.RUNNING

  @staticmethod
  def IsShutdown(s):
    return s == HvInstanceState.SHUTDOWN


262
263
264
class BaseHypervisor(object):
  """Abstract virtualisation technology interface

265
266
  The goal is that all aspects of the virtualisation technology are
  abstracted away from the rest of code.
267

268
269
270
271
272
273
274
275
276
  @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
277
278
279
  @type CAN_MIGRATE: boolean
  @cvar CAN_MIGRATE: whether this hypervisor can do migration (either
      live or non-live)
280

281
  """
282
  PARAMETERS = {}
283
  ANCILLARY_FILES = []
284
  ANCILLARY_FILES_OPT = []
285
  CAN_MIGRATE = False
286

287
  def StartInstance(self, instance, block_devices, startup_paused):
288
289
290
    """Start an instance."""
    raise NotImplementedError

291
292
  def StopInstance(self, instance, force=False, retry=False, name=None,
                   timeout=None):
293
294
295
296
297
298
299
300
    """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
301
302
303
304
    @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
305
306
307
308
    @type timeout: int or None
    @param timeout: if the parameter is not None, a soft shutdown operation will
        be killed after the specified number of seconds. A hard (forced)
        shutdown cannot have a timeout
309
310
    @raise errors.HypervisorError: when a parameter is not valid or
        the instance failed to be stopped
311
312

    """
313
314
    raise NotImplementedError

315
316
317
318
319
320
321
322
323
324
325
326
  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

327
328
329
330
  def RebootInstance(self, instance):
    """Reboot an instance."""
    raise NotImplementedError

331
  def ListInstances(self, hvparams=None):
332
333
334
    """Get the list of running instances."""
    raise NotImplementedError

335
  def GetInstanceInfo(self, instance_name, hvparams=None):
336
337
    """Get instance properties.

338
    @type instance_name: string
Iustin Pop's avatar
Iustin Pop committed
339
    @param instance_name: the instance name
340
341
    @type hvparams: dict of strings
    @param hvparams: hvparams to be used with this instance
342

Jose A. Lopes's avatar
Jose A. Lopes committed
343
    @rtype: (string, string, int, int, HvInstanceState, int)
Iustin Pop's avatar
Iustin Pop committed
344
    @return: tuple (name, id, memory, vcpus, state, times)
345
346
347
348

    """
    raise NotImplementedError

349
  def GetAllInstancesInfo(self, hvparams=None):
350
351
    """Get properties of all instances.

352
353
    @type hvparams: dict of strings
    @param hvparams: hypervisor parameter
Jose A. Lopes's avatar
Jose A. Lopes committed
354
355
356

    @rtype: (string, string, int, int, HvInstanceState, int)
    @return: list of tuples (name, id, memory, vcpus, state, times)
Iustin Pop's avatar
Iustin Pop committed
357

358
359
360
    """
    raise NotImplementedError

361
  def GetNodeInfo(self, hvparams=None):
362
363
    """Return information about the node.

364
365
366
    @type hvparams: dict of strings
    @param hvparams: hypervisor parameters

367
    @return: a dict with at least the following keys (memory values in MiB):
Iustin Pop's avatar
Iustin Pop committed
368
369
370
          - 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
371
          - cpu_total: total number of CPUs
372
          - cpu_dom0: number of CPUs used by the node OS
373
374
          - cpu_nodes: number of NUMA domains
          - cpu_sockets: number of physical CPU sockets
375
376
377
378

    """
    raise NotImplementedError

379
  @classmethod
380
381
  def GetInstanceConsole(cls, instance, primary_node, node_group,
                         hvparams, beparams):
382
    """Return information for connecting to the console of an instance.
383
384
385
386

    """
    raise NotImplementedError

387
388
389
390
391
  @classmethod
  def GetAncillaryFiles(cls):
    """Return a list of ancillary files to be copied to all nodes as ancillary
    configuration files.

392
393
    @rtype: (list of absolute paths, list of absolute paths)
    @return: (all files, optional files)
394
395
396
397

    """
    # 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.
398
399
400
401
    assert set(cls.ANCILLARY_FILES).issuperset(cls.ANCILLARY_FILES_OPT), \
      "Optional ancillary files must be a subset of ancillary files"

    return (cls.ANCILLARY_FILES, cls.ANCILLARY_FILES_OPT)
402

403
  def Verify(self, hvparams=None):
404
405
    """Verify the hypervisor.

406
407
408
    @type hvparams: dict of strings
    @param hvparams: hypervisor parameters to be verified against

409
410
    @return: Problem description if something is wrong, C{None} otherwise

411
412
    """
    raise NotImplementedError
413

414
  def MigrationInfo(self, instance): # pylint: disable=R0201,W0613
415
416
417
418
419
420
421
422
423
424
    """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
425
    return ""
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441

  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

442
443
444
445
446
447
448
449
450
451
452
  def BalloonInstanceMemory(self, instance, mem):
    """Balloon an instance memory to a certain value.

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

    """
    raise NotImplementedError

453
454
  def FinalizeMigrationDst(self, instance, info, success):
    """Finalize the instance migration on the target node.
455
456
457
458
459

    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}
460
    @param instance: instance whose migration is being finalized
461
462
463
464
465
466
467
468
    @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

469
  def MigrateInstance(self, cluster_name, instance, target, live):
470
471
    """Migrate an instance.

472
473
    @type cluster_name: string
    @param cluster_name: name of the cluster
Iustin Pop's avatar
Iustin Pop committed
474
    @type instance: L{objects.Instance}
Michael Hanselmann's avatar
Michael Hanselmann committed
475
    @param instance: the instance to be migrated
476
477
478
479
    @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
480
481
482

    """
    raise NotImplementedError
483

484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
  def FinalizeMigrationSource(self, instance, success, live):
    """Finalize the instance migration on the source node.

    @type instance: L{objects.Instance}
    @param instance: the instance that was migrated
    @type success: bool
    @param success: whether the migration succeeded or not
    @type live: bool
    @param live: whether the user requested a live migration or not

    """
    pass

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

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

    """
    raise NotImplementedError

510
  def _InstanceStartupMemory(self, instance):
511
512
513
514
515
516
517
518
519
520
521
522
    """Get the correct startup memory for an instance

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

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

    """
523
    free_memory = self.GetNodeInfo(hvparams=instance.hvparams)["memory_free"]
524
525
526
527
    max_start_mem = min(instance.beparams[constants.BE_MAXMEM], free_memory)
    start_mem = max(instance.beparams[constants.BE_MINMEM], max_start_mem)
    return start_mem

528
529
530
531
532
533
534
535
536
537
538
539
540
541
  @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:
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
        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):
561
562
563
564
565
566
567
568
569
570
    """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

    """
571
572
573
574
575
576
577
578
    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))
579

Iustin Pop's avatar
Iustin Pop committed
580
  @classmethod
581
  def PowercycleNode(cls, hvparams=None):
Iustin Pop's avatar
Iustin Pop committed
582
583
584
585
586
587
    """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.

588
589
590
    @type hvparams: dict of strings
    @param hvparams: hypervisor params to be used on this node

Iustin Pop's avatar
Iustin Pop committed
591
592
593
    """
    raise NotImplementedError

594
  @staticmethod
595
  def GetLinuxNodeInfo(meminfo="/proc/meminfo", cpuinfo="/proc/cpuinfo"):
596
597
598
599
600
601
602
603
    """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.

604
605
606
607
    @param meminfo: name of the file containing meminfo
    @type meminfo: string
    @param cpuinfo: name of the file containing cpuinfo
    @type cpuinfo: string
608
609
610
611
    @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
612
          - cpu_total: total number of CPUs
613
          - cpu_dom0: number of CPUs used by the node OS
614
615
          - cpu_nodes: number of NUMA domains
          - cpu_sockets: number of physical CPU sockets
616
617
618

    """
    try:
619
      data = utils.ReadFile(meminfo).splitlines()
620
621
622
623
624
625
626
627
628
629
630
631
    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
632
          if key == "MemTotal":
Michael Hanselmann's avatar
Michael Hanselmann committed
633
            result["memory_total"] = int(val.split()[0]) / 1024
Iustin Pop's avatar
Iustin Pop committed
634
          elif key in ("MemFree", "Buffers", "Cached"):
Michael Hanselmann's avatar
Michael Hanselmann committed
635
            sum_free += int(val.split()[0]) / 1024
Iustin Pop's avatar
Iustin Pop committed
636
          elif key == "Active":
Michael Hanselmann's avatar
Michael Hanselmann committed
637
            result["memory_dom0"] = int(val.split()[0]) / 1024
638
639
640
    except (ValueError, TypeError), err:
      raise errors.HypervisorError("Failed to compute memory usage: %s" %
                                   (err,))
Iustin Pop's avatar
Iustin Pop committed
641
    result["memory_free"] = sum_free
642
643
644

    cpu_total = 0
    try:
645
      fh = open(cpuinfo)
646
      try:
Michele Tartara's avatar
Michele Tartara committed
647
        cpu_total = len(re.findall(r"(?m)^processor\s*:\s*[0-9]+\s*$",
648
649
650
651
652
                                   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
653
    result["cpu_total"] = cpu_total
654
655
    # We assume that the node OS can access all the CPUs
    result["cpu_dom0"] = cpu_total
656
    # FIXME: export correct data here
Iustin Pop's avatar
Iustin Pop committed
657
658
    result["cpu_nodes"] = 1
    result["cpu_sockets"] = 1
659
660

    return result
Iustin Pop's avatar
Iustin Pop committed
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677

  @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)
678
679
680
681
682
683
684
685
686
687
688
689
690
691

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

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

    """
    if msgs:
      return "; ".join(msgs)
    else:
      return None
Dimitris Aragiorgis's avatar
Dimitris Aragiorgis committed
692

693
  # pylint: disable=R0201,W0613
Dimitris Aragiorgis's avatar
Dimitris Aragiorgis committed
694
695
696
697
  def HotAddDevice(self, instance, dev_type, device, extra, seq):
    """Hot-add a device.

    """
698
    raise errors.HotplugError("Hotplug is not supported by this hypervisor")
Dimitris Aragiorgis's avatar
Dimitris Aragiorgis committed
699

700
  # pylint: disable=R0201,W0613
Dimitris Aragiorgis's avatar
Dimitris Aragiorgis committed
701
702
703
704
  def HotDelDevice(self, instance, dev_type, device, extra, seq):
    """Hot-del a device.

    """
705
    raise errors.HotplugError("Hotplug is not supported by this hypervisor")
Dimitris Aragiorgis's avatar
Dimitris Aragiorgis committed
706

707
  # pylint: disable=R0201,W0613
Dimitris Aragiorgis's avatar
Dimitris Aragiorgis committed
708
709
710
711
  def HotModDevice(self, instance, dev_type, device, extra, seq):
    """Hot-mod a device.

    """
712
713
714
715
716
    raise errors.HotplugError("Hotplug is not supported by this hypervisor")

  # pylint: disable=R0201,W0613
  def VerifyHotplugSupport(self, instance, action, dev_type):
    """Verifies that hotplug is supported.
Dimitris Aragiorgis's avatar
Dimitris Aragiorgis committed
717

718
719
    Given the target device and hotplug action checks if hotplug is
    actually supported.
720
721
722
723
724
725
726
727

    @type instance: L{objects.Instance}
    @param instance: the instance object
    @type action: string
    @param action: one of the supported hotplug commands
    @type dev_type: string
    @param dev_type: one of the supported device types to hotplug
    @raise errors.HotplugError: if hotplugging is not supported
Dimitris Aragiorgis's avatar
Dimitris Aragiorgis committed
728

729
730
731
732
733
734
735
736
    """
    raise errors.HotplugError("Hotplug is not supported.")

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

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

Dimitris Aragiorgis's avatar
Dimitris Aragiorgis committed
737
    """
738
    raise errors.HotplugError("Hotplug is not supported by this hypervisor")