Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ d0bb3f24

History | View | Annotate | Download (30.7 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 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
"""Xen hypervisors
23

24
"""
25

    
26
import logging
27
import string # pylint: disable=W0402
28
from cStringIO import StringIO
29

    
30
from ganeti import constants
31
from ganeti import errors
32
from ganeti import utils
33
from ganeti.hypervisor import hv_base
34
from ganeti import netutils
35
from ganeti import objects
36
from ganeti import pathutils
37
from ganeti import ssconf
38

    
39

    
40
XEND_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xend-config.sxp")
41
XL_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xen/xl.conf")
42
VIF_BRIDGE_SCRIPT = utils.PathJoin(pathutils.XEN_CONFIG_DIR,
43
                                   "scripts/vif-bridge")
44
_DOM0_NAME = "Domain-0"
45
_DISK_LETTERS = string.ascii_lowercase
46

    
47
_FILE_DRIVER_MAP = {
48
  constants.FD_LOOP: "file",
49
  constants.FD_BLKTAP: "tap:aio",
50
  }
51

    
52

    
53
def _CreateConfigCpus(cpu_mask):
54
  """Create a CPU config string for Xen's config file.
55

56
  """
57
  # Convert the string CPU mask to a list of list of int's
58
  cpu_list = utils.ParseMultiCpuMask(cpu_mask)
59

    
60
  if len(cpu_list) == 1:
61
    all_cpu_mapping = cpu_list[0]
62
    if all_cpu_mapping == constants.CPU_PINNING_OFF:
63
      # If CPU pinning has 1 entry that's "all", then remove the
64
      # parameter from the config file
65
      return None
66
    else:
67
      # If CPU pinning has one non-all entry, mapping all vCPUS (the entire
68
      # VM) to one physical CPU, using format 'cpu = "C"'
69
      return "cpu = \"%s\"" % ",".join(map(str, all_cpu_mapping))
70
  else:
71

    
72
    def _GetCPUMap(vcpu):
73
      if vcpu[0] == constants.CPU_PINNING_ALL_VAL:
74
        cpu_map = constants.CPU_PINNING_ALL_XEN
75
      else:
76
        cpu_map = ",".join(map(str, vcpu))
77
      return "\"%s\"" % cpu_map
78

    
79
    # build the result string in format 'cpus = [ "c", "c", "c" ]',
80
    # where each c is a physical CPU number, a range, a list, or any
81
    # combination
82
    return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list))
83

    
84

    
85
def _RunXmList(fn, xmllist_errors):
86
  """Helper function for L{_GetXmList} to run "xm list".
87

88
  @type fn: callable
89
  @param fn: Function returning result of running C{xm list}
90
  @type xmllist_errors: list
91
  @param xmllist_errors: Error list
92
  @rtype: list
93

94
  """
95
  result = fn()
96
  if result.failed:
97
    logging.error("xm list failed (%s): %s", result.fail_reason,
98
                  result.output)
99
    xmllist_errors.append(result)
100
    raise utils.RetryAgain()
101

    
102
  # skip over the heading
103
  return result.stdout.splitlines()
104

    
105

    
106
def _ParseXmList(lines, include_node):
107
  """Parses the output of C{xm list}.
108

109
  @type lines: list
110
  @param lines: Output lines of C{xm list}
111
  @type include_node: boolean
112
  @param include_node: If True, return information for Dom0
113
  @return: list of tuple containing (name, id, memory, vcpus, state, time
114
    spent)
115

116
  """
117
  result = []
118

    
119
  # Iterate through all lines while ignoring header
120
  for line in lines[1:]:
121
    # The format of lines is:
122
    # Name      ID Mem(MiB) VCPUs State  Time(s)
123
    # Domain-0   0  3418     4 r-----    266.2
124
    data = line.split()
125
    if len(data) != 6:
126
      raise errors.HypervisorError("Can't parse output of xm list,"
127
                                   " line: %s" % line)
128
    try:
129
      data[1] = int(data[1])
130
      data[2] = int(data[2])
131
      data[3] = int(data[3])
132
      data[5] = float(data[5])
133
    except (TypeError, ValueError), err:
134
      raise errors.HypervisorError("Can't parse output of xm list,"
135
                                   " line: %s, error: %s" % (line, err))
136

    
137
    # skip the Domain-0 (optional)
138
    if include_node or data[0] != _DOM0_NAME:
139
      result.append(data)
140

    
141
  return result
142

    
143

    
144
def _GetXmList(fn, include_node, _timeout=5):
145
  """Return the list of running instances.
146

147
  See L{_RunXmList} and L{_ParseXmList} for parameter details.
148

149
  """
150
  xmllist_errors = []
151
  try:
152
    lines = utils.Retry(_RunXmList, (0.3, 1.5, 1.0), _timeout,
153
                        args=(fn, xmllist_errors))
154
  except utils.RetryTimeout:
155
    if xmllist_errors:
156
      xmlist_result = xmllist_errors.pop()
157

    
158
      errmsg = ("xm list failed, timeout exceeded (%s): %s" %
159
                (xmlist_result.fail_reason, xmlist_result.output))
160
    else:
161
      errmsg = "xm list failed"
162

    
163
    raise errors.HypervisorError(errmsg)
164

    
165
  return _ParseXmList(lines, include_node)
166

    
167

    
168
def _ParseNodeInfo(info):
169
  """Return information about the node.
170

171
  @return: a dict with the following keys (memory values in MiB):
172
        - memory_total: the total memory size on the node
173
        - memory_free: the available memory on the node for instances
174
        - nr_cpus: total number of CPUs
175
        - nr_nodes: in a NUMA system, the number of domains
176
        - nr_sockets: the number of physical CPU sockets in the node
177
        - hv_version: the hypervisor version in the form (major, minor)
178

179
  """
180
  result = {}
181
  cores_per_socket = threads_per_core = nr_cpus = None
182
  xen_major, xen_minor = None, None
183
  memory_total = None
184
  memory_free = None
185

    
186
  for line in info.splitlines():
187
    fields = line.split(":", 1)
188

    
189
    if len(fields) < 2:
190
      continue
191

    
192
    (key, val) = map(lambda s: s.strip(), fields)
193

    
194
    # Note: in Xen 3, memory has changed to total_memory
195
    if key in ("memory", "total_memory"):
196
      memory_total = int(val)
197
    elif key == "free_memory":
198
      memory_free = int(val)
199
    elif key == "nr_cpus":
200
      nr_cpus = result["cpu_total"] = int(val)
201
    elif key == "nr_nodes":
202
      result["cpu_nodes"] = int(val)
203
    elif key == "cores_per_socket":
204
      cores_per_socket = int(val)
205
    elif key == "threads_per_core":
206
      threads_per_core = int(val)
207
    elif key == "xen_major":
208
      xen_major = int(val)
209
    elif key == "xen_minor":
210
      xen_minor = int(val)
211

    
212
  if None not in [cores_per_socket, threads_per_core, nr_cpus]:
213
    result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
214

    
215
  if memory_free is not None:
216
    result["memory_free"] = memory_free
217

    
218
  if memory_total is not None:
219
    result["memory_total"] = memory_total
220

    
221
  if not (xen_major is None or xen_minor is None):
222
    result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
223

    
224
  return result
225

    
226

    
227
def _MergeInstanceInfo(info, fn):
228
  """Updates node information from L{_ParseNodeInfo} with instance info.
229

230
  @type info: dict
231
  @param info: Result from L{_ParseNodeInfo}
232
  @type fn: callable
233
  @param fn: Function returning result of running C{xm list}
234
  @rtype: dict
235

236
  """
237
  total_instmem = 0
238

    
239
  for (name, _, mem, vcpus, _, _) in fn(True):
240
    if name == _DOM0_NAME:
241
      info["memory_dom0"] = mem
242
      info["dom0_cpus"] = vcpus
243

    
244
    # Include Dom0 in total memory usage
245
    total_instmem += mem
246

    
247
  memory_free = info.get("memory_free")
248
  memory_total = info.get("memory_total")
249

    
250
  # Calculate memory used by hypervisor
251
  if None not in [memory_total, memory_free, total_instmem]:
252
    info["memory_hv"] = memory_total - memory_free - total_instmem
253

    
254
  return info
255

    
256

    
257
def _GetNodeInfo(info, fn):
258
  """Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}.
259

260
  """
261
  return _MergeInstanceInfo(_ParseNodeInfo(info), fn)
262

    
263

    
264
def _GetConfigFileDiskData(block_devices, blockdev_prefix,
265
                           _letters=_DISK_LETTERS):
266
  """Get disk directives for Xen config file.
267

268
  This method builds the xen config disk directive according to the
269
  given disk_template and block_devices.
270

271
  @param block_devices: list of tuples (cfdev, rldev):
272
      - cfdev: dict containing ganeti config disk part
273
      - rldev: ganeti.bdev.BlockDev object
274
  @param blockdev_prefix: a string containing blockdevice prefix,
275
                          e.g. "sd" for /dev/sda
276

277
  @return: string containing disk directive for xen instance config file
278

279
  """
280
  if len(block_devices) > len(_letters):
281
    raise errors.HypervisorError("Too many disks")
282

    
283
  disk_data = []
284

    
285
  for sd_suffix, (cfdev, dev_path) in zip(_letters, block_devices):
286
    sd_name = blockdev_prefix + sd_suffix
287

    
288
    if cfdev.mode == constants.DISK_RDWR:
289
      mode = "w"
290
    else:
291
      mode = "r"
292

    
293
    if cfdev.dev_type == constants.LD_FILE:
294
      driver = _FILE_DRIVER_MAP[cfdev.physical_id[0]]
295
    else:
296
      driver = "phy"
297

    
298
    disk_data.append("'%s:%s,%s,%s'" % (driver, dev_path, sd_name, mode))
299

    
300
  return disk_data
301

    
302

    
303
class XenHypervisor(hv_base.BaseHypervisor):
304
  """Xen generic hypervisor interface
305

306
  This is the Xen base class used for both Xen PVM and HVM. It contains
307
  all the functionality that is identical for both.
308

309
  """
310
  CAN_MIGRATE = True
311
  REBOOT_RETRY_COUNT = 60
312
  REBOOT_RETRY_INTERVAL = 10
313

    
314
  ANCILLARY_FILES = [
315
    XEND_CONFIG_FILE,
316
    XL_CONFIG_FILE,
317
    VIF_BRIDGE_SCRIPT,
318
    ]
319
  ANCILLARY_FILES_OPT = [
320
    XL_CONFIG_FILE,
321
    ]
322

    
323
  @staticmethod
324
  def _ConfigFileName(instance_name):
325
    """Get the config file name for an instance.
326

327
    @param instance_name: instance name
328
    @type instance_name: str
329
    @return: fully qualified path to instance config file
330
    @rtype: str
331

332
    """
333
    return utils.PathJoin(pathutils.XEN_CONFIG_DIR, instance_name)
334

    
335
  @classmethod
336
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
337
    """Write the Xen config file for the instance.
338

339
    """
340
    raise NotImplementedError
341

    
342
  @staticmethod
343
  def _WriteConfigFileStatic(instance_name, data):
344
    """Write the Xen config file for the instance.
345

346
    This version of the function just writes the config file from static data.
347

348
    """
349
    # just in case it exists
350
    utils.RemoveFile(utils.PathJoin(pathutils.XEN_CONFIG_DIR, "auto",
351
                                    instance_name))
352

    
353
    cfg_file = XenHypervisor._ConfigFileName(instance_name)
354
    try:
355
      utils.WriteFile(cfg_file, data=data)
356
    except EnvironmentError, err:
357
      raise errors.HypervisorError("Cannot write Xen instance configuration"
358
                                   " file %s: %s" % (cfg_file, err))
359

    
360
  @staticmethod
361
  def _ReadConfigFile(instance_name):
362
    """Returns the contents of the instance config file.
363

364
    """
365
    filename = XenHypervisor._ConfigFileName(instance_name)
366

    
367
    try:
368
      file_content = utils.ReadFile(filename)
369
    except EnvironmentError, err:
370
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
371

    
372
    return file_content
373

    
374
  @staticmethod
375
  def _RemoveConfigFile(instance_name):
376
    """Remove the xen configuration file.
377

378
    """
379
    utils.RemoveFile(XenHypervisor._ConfigFileName(instance_name))
380

    
381
  @staticmethod
382
  def _GetXmList(include_node):
383
    """Wrapper around module level L{_GetXmList}.
384

385
    """
386
    # TODO: Abstract running Xen command for testing
387
    return _GetXmList(lambda: utils.RunCmd([constants.XEN_CMD, "list"]),
388
                      include_node)
389

    
390
  def ListInstances(self):
391
    """Get the list of running instances.
392

393
    """
394
    xm_list = self._GetXmList(False)
395
    names = [info[0] for info in xm_list]
396
    return names
397

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

401
    @param instance_name: the instance name
402

403
    @return: tuple (name, id, memory, vcpus, stat, times)
404

405
    """
406
    xm_list = self._GetXmList(instance_name == _DOM0_NAME)
407
    result = None
408
    for data in xm_list:
409
      if data[0] == instance_name:
410
        result = data
411
        break
412
    return result
413

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

417
    @return: list of tuples (name, id, memory, vcpus, stat, times)
418

419
    """
420
    xm_list = self._GetXmList(False)
421
    return xm_list
422

    
423
  def StartInstance(self, instance, block_devices, startup_paused):
424
    """Start an instance.
425

426
    """
427
    startup_memory = self._InstanceStartupMemory(instance)
428
    self._WriteConfigFile(instance, startup_memory, block_devices)
429
    cmd = [constants.XEN_CMD, "create"]
430
    if startup_paused:
431
      cmd.extend(["-p"])
432
    cmd.extend([self._ConfigFileName(instance.name)])
433
    result = utils.RunCmd(cmd)
434

    
435
    if result.failed:
436
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
437
                                   (instance.name, result.fail_reason,
438
                                    result.output))
439

    
440
  def StopInstance(self, instance, force=False, retry=False, name=None):
441
    """Stop an instance.
442

443
    """
444
    if name is None:
445
      name = instance.name
446
    self._RemoveConfigFile(name)
447
    if force:
448
      command = [constants.XEN_CMD, "destroy", name]
449
    else:
450
      command = [constants.XEN_CMD, "shutdown", name]
451
    result = utils.RunCmd(command)
452

    
453
    if result.failed:
454
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
455
                                   (name, result.fail_reason, result.output))
456

    
457
  def RebootInstance(self, instance):
458
    """Reboot an instance.
459

460
    """
461
    ini_info = self.GetInstanceInfo(instance.name)
462

    
463
    if ini_info is None:
464
      raise errors.HypervisorError("Failed to reboot instance %s,"
465
                                   " not running" % instance.name)
466

    
467
    result = utils.RunCmd([constants.XEN_CMD, "reboot", instance.name])
468
    if result.failed:
469
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
470
                                   (instance.name, result.fail_reason,
471
                                    result.output))
472

    
473
    def _CheckInstance():
474
      new_info = self.GetInstanceInfo(instance.name)
475

    
476
      # check if the domain ID has changed or the run time has decreased
477
      if (new_info is not None and
478
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
479
        return
480

    
481
      raise utils.RetryAgain()
482

    
483
    try:
484
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
485
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
486
    except utils.RetryTimeout:
487
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
488
                                   " did not reboot in the expected interval" %
489
                                   (instance.name, ))
490

    
491
  def BalloonInstanceMemory(self, instance, mem):
492
    """Balloon an instance memory to a certain value.
493

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

499
    """
500
    cmd = [constants.XEN_CMD, "mem-set", instance.name, mem]
501
    result = utils.RunCmd(cmd)
502
    if result.failed:
503
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
504
                                   (instance.name, result.fail_reason,
505
                                    result.output))
506
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
507
    cmd.append(XenHypervisor._ConfigFileName(instance.name))
508
    result = utils.RunCmd(cmd)
509
    if result.failed:
510
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
511
                                   (instance.name, result.fail_reason,
512
                                    result.output))
513

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

517
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
518

519
    """
520
    # TODO: Abstract running Xen command for testing
521
    result = utils.RunCmd([constants.XEN_CMD, "info"])
522
    if result.failed:
523
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
524
                    result.output)
525
      return None
526

    
527
    return _GetNodeInfo(result.stdout, self._GetXmList)
528

    
529
  @classmethod
530
  def GetInstanceConsole(cls, instance, hvparams, beparams):
531
    """Return a command for connecting to the console of an instance.
532

533
    """
534
    return objects.InstanceConsole(instance=instance.name,
535
                                   kind=constants.CONS_SSH,
536
                                   host=instance.primary_node,
537
                                   user=constants.SSH_CONSOLE_USER,
538
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
539
                                            constants.XEN_CMD, instance.name])
540

    
541
  def Verify(self):
542
    """Verify the hypervisor.
543

544
    For Xen, this verifies that the xend process is running.
545

546
    @return: Problem description if something is wrong, C{None} otherwise
547

548
    """
549
    result = utils.RunCmd([constants.XEN_CMD, "info"])
550
    if result.failed:
551
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
552

    
553
    return None
554

    
555
  def MigrationInfo(self, instance):
556
    """Get instance information to perform a migration.
557

558
    @type instance: L{objects.Instance}
559
    @param instance: instance to be migrated
560
    @rtype: string
561
    @return: content of the xen config file
562

563
    """
564
    return self._ReadConfigFile(instance.name)
565

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

569
    @type instance: L{objects.Instance}
570
    @param instance: instance to be accepted
571
    @type info: string
572
    @param info: content of the xen config file on the source node
573
    @type target: string
574
    @param target: target host (usually ip), on this node
575

576
    """
577
    pass
578

    
579
  def FinalizeMigrationDst(self, instance, info, success):
580
    """Finalize an instance migration.
581

582
    After a successful migration we write the xen config file.
583
    We do nothing on a failure, as we did not change anything at accept time.
584

585
    @type instance: L{objects.Instance}
586
    @param instance: instance whose migration is being finalized
587
    @type info: string
588
    @param info: content of the xen config file on the source node
589
    @type success: boolean
590
    @param success: whether the migration was a success or a failure
591

592
    """
593
    if success:
594
      self._WriteConfigFileStatic(instance.name, info)
595

    
596
  def MigrateInstance(self, instance, target, live):
597
    """Migrate an instance to a target node.
598

599
    The migration will not be attempted if the instance is not
600
    currently running.
601

602
    @type instance: L{objects.Instance}
603
    @param instance: the instance to be migrated
604
    @type target: string
605
    @param target: ip address of the target node
606
    @type live: boolean
607
    @param live: perform a live migration
608

609
    """
610
    if self.GetInstanceInfo(instance.name) is None:
611
      raise errors.HypervisorError("Instance not running, cannot migrate")
612

    
613
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
614

    
615
    if (constants.XEN_CMD == constants.XEN_CMD_XM and
616
        not netutils.TcpPing(target, port, live_port_needed=True)):
617
      raise errors.HypervisorError("Remote host %s not listening on port"
618
                                   " %s, cannot migrate" % (target, port))
619

    
620
    args = [constants.XEN_CMD, "migrate"]
621
    if constants.XEN_CMD == constants.XEN_CMD_XM:
622
      args.extend(["-p", "%d" % port])
623
      if live:
624
        args.append("-l")
625
    elif constants.XEN_CMD == constants.XEN_CMD_XL:
626
      cluster_name = ssconf.SimpleStore().GetClusterName()
627
      args.extend(["-s", constants.XL_SSH_CMD % cluster_name])
628
      args.extend(["-C", self._ConfigFileName(instance.name)])
629
    else:
630
      raise errors.HypervisorError("Unsupported xen command: %s" %
631
                                   constants.XEN_CMD)
632

    
633
    args.extend([instance.name, target])
634
    result = utils.RunCmd(args)
635
    if result.failed:
636
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
637
                                   (instance.name, result.output))
638

    
639
  def FinalizeMigrationSource(self, instance, success, live):
640
    """Finalize the instance migration on the source node.
641

642
    @type instance: L{objects.Instance}
643
    @param instance: the instance that was migrated
644
    @type success: bool
645
    @param success: whether the migration succeeded or not
646
    @type live: bool
647
    @param live: whether the user requested a live migration or not
648

649
    """
650
    # pylint: disable=W0613
651
    if success:
652
      # remove old xen file after migration succeeded
653
      try:
654
        self._RemoveConfigFile(instance.name)
655
      except EnvironmentError:
656
        logging.exception("Failure while removing instance config file")
657

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

661
    As MigrateInstance for Xen is still blocking, if this method is called it
662
    means that MigrateInstance has completed successfully. So we can safely
663
    assume that the migration was successful and notify this fact to the client.
664

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

672
    """
673
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
674

    
675
  @classmethod
676
  def PowercycleNode(cls):
677
    """Xen-specific powercycle.
678

679
    This first does a Linux reboot (which triggers automatically a Xen
680
    reboot), and if that fails it tries to do a Xen reboot. The reason
681
    we don't try a Xen reboot first is that the xen reboot launches an
682
    external command which connects to the Xen hypervisor, and that
683
    won't work in case the root filesystem is broken and/or the xend
684
    daemon is not working.
685

686
    """
687
    try:
688
      cls.LinuxPowercycle()
689
    finally:
690
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
691

    
692

    
693
class XenPvmHypervisor(XenHypervisor):
694
  """Xen PVM hypervisor interface"""
695

    
696
  PARAMETERS = {
697
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
698
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
699
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
700
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
701
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
702
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
703
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
704
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
705
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
706
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
707
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
708
    constants.HV_REBOOT_BEHAVIOR:
709
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
710
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
711
    constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
712
    constants.HV_CPU_WEIGHT:
713
      (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
714
    }
715

    
716
  @classmethod
717
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
718
    """Write the Xen config file for the instance.
719

720
    """
721
    hvp = instance.hvparams
722
    config = StringIO()
723
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
724

    
725
    # if bootloader is True, use bootloader instead of kernel and ramdisk
726
    # parameters.
727
    if hvp[constants.HV_USE_BOOTLOADER]:
728
      # bootloader handling
729
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
730
      if bootloader_path:
731
        config.write("bootloader = '%s'\n" % bootloader_path)
732
      else:
733
        raise errors.HypervisorError("Bootloader enabled, but missing"
734
                                     " bootloader path")
735

    
736
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
737
      if bootloader_args:
738
        config.write("bootargs = '%s'\n" % bootloader_args)
739
    else:
740
      # kernel handling
741
      kpath = hvp[constants.HV_KERNEL_PATH]
742
      config.write("kernel = '%s'\n" % kpath)
743

    
744
      # initrd handling
745
      initrd_path = hvp[constants.HV_INITRD_PATH]
746
      if initrd_path:
747
        config.write("ramdisk = '%s'\n" % initrd_path)
748

    
749
    # rest of the settings
750
    config.write("memory = %d\n" % startup_memory)
751
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
752
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
753
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
754
    if cpu_pinning:
755
      config.write("%s\n" % cpu_pinning)
756
    cpu_cap = hvp[constants.HV_CPU_CAP]
757
    if cpu_cap:
758
      config.write("cpu_cap=%d\n" % cpu_cap)
759
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
760
    if cpu_weight:
761
      config.write("cpu_weight=%d\n" % cpu_weight)
762

    
763
    config.write("name = '%s'\n" % instance.name)
764

    
765
    vif_data = []
766
    for nic in instance.nics:
767
      nic_str = "mac=%s" % (nic.mac)
768
      ip = getattr(nic, "ip", None)
769
      if ip is not None:
770
        nic_str += ", ip=%s" % ip
771
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
772
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
773
      vif_data.append("'%s'" % nic_str)
774

    
775
    disk_data = \
776
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
777

    
778
    config.write("vif = [%s]\n" % ",".join(vif_data))
779
    config.write("disk = [%s]\n" % ",".join(disk_data))
780

    
781
    if hvp[constants.HV_ROOT_PATH]:
782
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
783
    config.write("on_poweroff = 'destroy'\n")
784
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
785
      config.write("on_reboot = 'restart'\n")
786
    else:
787
      config.write("on_reboot = 'destroy'\n")
788
    config.write("on_crash = 'restart'\n")
789
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
790
    cls._WriteConfigFileStatic(instance.name, config.getvalue())
791

    
792
    return True
793

    
794

    
795
class XenHvmHypervisor(XenHypervisor):
796
  """Xen HVM hypervisor interface"""
797

    
798
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
799
    pathutils.VNC_PASSWORD_FILE,
800
    ]
801
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
802
    pathutils.VNC_PASSWORD_FILE,
803
    ]
804

    
805
  PARAMETERS = {
806
    constants.HV_ACPI: hv_base.NO_CHECK,
807
    constants.HV_BOOT_ORDER: (True, ) +
808
      (lambda x: x and len(x.strip("acdn")) == 0,
809
       "Invalid boot order specified, must be one or more of [acdn]",
810
       None, None),
811
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
812
    constants.HV_DISK_TYPE:
813
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
814
    constants.HV_NIC_TYPE:
815
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
816
    constants.HV_PAE: hv_base.NO_CHECK,
817
    constants.HV_VNC_BIND_ADDRESS:
818
      (False, netutils.IP4Address.IsValid,
819
       "VNC bind address is not a valid IP address", None, None),
820
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
821
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
822
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
823
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
824
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
825
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
826
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
827
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
828
    # Add PCI passthrough
829
    constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
830
    constants.HV_REBOOT_BEHAVIOR:
831
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
832
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
833
    constants.HV_CPU_CAP: hv_base.NO_CHECK,
834
    constants.HV_CPU_WEIGHT:
835
      (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
836
    }
837

    
838
  @classmethod
839
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
840
    """Create a Xen 3.1 HVM config file.
841

842
    """
843
    hvp = instance.hvparams
844

    
845
    config = StringIO()
846
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
847

    
848
    # kernel handling
849
    kpath = hvp[constants.HV_KERNEL_PATH]
850
    config.write("kernel = '%s'\n" % kpath)
851

    
852
    config.write("builder = 'hvm'\n")
853
    config.write("memory = %d\n" % startup_memory)
854
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
855
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
856
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
857
    if cpu_pinning:
858
      config.write("%s\n" % cpu_pinning)
859
    cpu_cap = hvp[constants.HV_CPU_CAP]
860
    if cpu_cap:
861
      config.write("cpu_cap=%d\n" % cpu_cap)
862
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
863
    if cpu_weight:
864
      config.write("cpu_weight=%d\n" % cpu_weight)
865

    
866
    config.write("name = '%s'\n" % instance.name)
867
    if hvp[constants.HV_PAE]:
868
      config.write("pae = 1\n")
869
    else:
870
      config.write("pae = 0\n")
871
    if hvp[constants.HV_ACPI]:
872
      config.write("acpi = 1\n")
873
    else:
874
      config.write("acpi = 0\n")
875
    config.write("apic = 1\n")
876
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
877
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
878
    config.write("sdl = 0\n")
879
    config.write("usb = 1\n")
880
    config.write("usbdevice = 'tablet'\n")
881
    config.write("vnc = 1\n")
882
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
883
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
884
    else:
885
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
886

    
887
    if instance.network_port > constants.VNC_BASE_PORT:
888
      display = instance.network_port - constants.VNC_BASE_PORT
889
      config.write("vncdisplay = %s\n" % display)
890
      config.write("vncunused = 0\n")
891
    else:
892
      config.write("# vncdisplay = 1\n")
893
      config.write("vncunused = 1\n")
894

    
895
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
896
    try:
897
      password = utils.ReadFile(vnc_pwd_file)
898
    except EnvironmentError, err:
899
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
900
                                   (vnc_pwd_file, err))
901

    
902
    config.write("vncpasswd = '%s'\n" % password.rstrip())
903

    
904
    config.write("serial = 'pty'\n")
905
    if hvp[constants.HV_USE_LOCALTIME]:
906
      config.write("localtime = 1\n")
907

    
908
    vif_data = []
909
    nic_type = hvp[constants.HV_NIC_TYPE]
910
    if nic_type is None:
911
      # ensure old instances don't change
912
      nic_type_str = ", type=ioemu"
913
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
914
      nic_type_str = ", type=paravirtualized"
915
    else:
916
      nic_type_str = ", model=%s, type=ioemu" % nic_type
917
    for nic in instance.nics:
918
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
919
      ip = getattr(nic, "ip", None)
920
      if ip is not None:
921
        nic_str += ", ip=%s" % ip
922
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
923
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
924
      vif_data.append("'%s'" % nic_str)
925

    
926
    config.write("vif = [%s]\n" % ",".join(vif_data))
927

    
928
    disk_data = \
929
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
930

    
931
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
932
    if iso_path:
933
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
934
      disk_data.append(iso)
935

    
936
    config.write("disk = [%s]\n" % (",".join(disk_data)))
937
    # Add PCI passthrough
938
    pci_pass_arr = []
939
    pci_pass = hvp[constants.HV_PASSTHROUGH]
940
    if pci_pass:
941
      pci_pass_arr = pci_pass.split(";")
942
      config.write("pci = %s\n" % pci_pass_arr)
943
    config.write("on_poweroff = 'destroy'\n")
944
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
945
      config.write("on_reboot = 'restart'\n")
946
    else:
947
      config.write("on_reboot = 'destroy'\n")
948
    config.write("on_crash = 'restart'\n")
949
    cls._WriteConfigFileStatic(instance.name, config.getvalue())
950

    
951
    return True