Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 78f99abb

History | View | Annotate | Download (40 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 errno
28
import string # pylint: disable=W0402
29
import shutil
30
from cStringIO import StringIO
31

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

    
40

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

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

    
53

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

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

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

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

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

    
85

    
86
def _RunInstanceList(fn, instance_list_errors):
87
  """Helper function for L{_GetInstanceList} to retrieve the list of instances
88
  from xen.
89

90
  @type fn: callable
91
  @param fn: Function to query xen for the list of instances
92
  @type instance_list_errors: list
93
  @param instance_list_errors: Error list
94
  @rtype: list
95

96
  """
97
  result = fn()
98
  if result.failed:
99
    logging.error("Retrieving the instance list from xen failed (%s): %s",
100
                  result.fail_reason, result.output)
101
    instance_list_errors.append(result)
102
    raise utils.RetryAgain()
103

    
104
  # skip over the heading
105
  return result.stdout.splitlines()
106

    
107

    
108
def _ParseInstanceList(lines, include_node):
109
  """Parses the output of listing instances by xen.
110

111
  @type lines: list
112
  @param lines: Result of retrieving the instance list from xen
113
  @type include_node: boolean
114
  @param include_node: If True, return information for Dom0
115
  @return: list of tuple containing (name, id, memory, vcpus, state, time
116
    spent)
117

118
  """
119
  result = []
120

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

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

    
143
  return result
144

    
145

    
146
def _GetInstanceList(fn, include_node, _timeout=5):
147
  """Return the list of running instances.
148

149
  See L{_RunInstanceList} and L{_ParseInstanceList} for parameter details.
150

151
  """
152
  instance_list_errors = []
153
  try:
154
    lines = utils.Retry(_RunInstanceList, (0.3, 1.5, 1.0), _timeout,
155
                        args=(fn, instance_list_errors))
156
  except utils.RetryTimeout:
157
    if instance_list_errors:
158
      instance_list_result = instance_list_errors.pop()
159

    
160
      errmsg = ("listing instances failed, timeout exceeded (%s): %s" %
161
                (instance_list_result.fail_reason, instance_list_result.output))
162
    else:
163
      errmsg = "listing instances failed"
164

    
165
    raise errors.HypervisorError(errmsg)
166

    
167
  return _ParseInstanceList(lines, include_node)
168

    
169

    
170
def _ParseNodeInfo(info):
171
  """Return information about the node.
172

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

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

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

    
191
    if len(fields) < 2:
192
      continue
193

    
194
    (key, val) = map(lambda s: s.strip(), fields)
195

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

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

    
217
  if memory_free is not None:
218
    result["memory_free"] = memory_free
219

    
220
  if memory_total is not None:
221
    result["memory_total"] = memory_total
222

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

    
226
  return result
227

    
228

    
229
def _MergeInstanceInfo(info, instance_list):
230
  """Updates node information from L{_ParseNodeInfo} with instance info.
231

232
  @type info: dict
233
  @param info: Result from L{_ParseNodeInfo}
234
  @type instance_list: list of tuples
235
  @param instance_list: list of instance information; one tuple per instance
236
  @rtype: dict
237

238
  """
239
  total_instmem = 0
240

    
241
  for (name, _, mem, vcpus, _, _) in instance_list:
242
    if name == _DOM0_NAME:
243
      info["memory_dom0"] = mem
244
      info["cpu_dom0"] = vcpus
245

    
246
    # Include Dom0 in total memory usage
247
    total_instmem += mem
248

    
249
  memory_free = info.get("memory_free")
250
  memory_total = info.get("memory_total")
251

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

    
256
  return info
257

    
258

    
259
def _GetNodeInfo(info, instance_list):
260
  """Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}.
261

262
  @type instance_list: list of tuples
263
  @param instance_list: list of instance information; one tuple per instance
264

265
  """
266
  return _MergeInstanceInfo(_ParseNodeInfo(info), instance_list)
267

    
268

    
269
def _GetConfigFileDiskData(block_devices, blockdev_prefix,
270
                           _letters=_DISK_LETTERS):
271
  """Get disk directives for Xen config file.
272

273
  This method builds the xen config disk directive according to the
274
  given disk_template and block_devices.
275

276
  @param block_devices: list of tuples (cfdev, rldev):
277
      - cfdev: dict containing ganeti config disk part
278
      - rldev: ganeti.block.bdev.BlockDev object
279
  @param blockdev_prefix: a string containing blockdevice prefix,
280
                          e.g. "sd" for /dev/sda
281

282
  @return: string containing disk directive for xen instance config file
283

284
  """
285
  if len(block_devices) > len(_letters):
286
    raise errors.HypervisorError("Too many disks")
287

    
288
  disk_data = []
289

    
290
  for sd_suffix, (cfdev, dev_path) in zip(_letters, block_devices):
291
    sd_name = blockdev_prefix + sd_suffix
292

    
293
    if cfdev.mode == constants.DISK_RDWR:
294
      mode = "w"
295
    else:
296
      mode = "r"
297

    
298
    if cfdev.dev_type == constants.LD_FILE:
299
      driver = _FILE_DRIVER_MAP[cfdev.physical_id[0]]
300
    else:
301
      driver = "phy"
302

    
303
    disk_data.append("'%s:%s,%s,%s'" % (driver, dev_path, sd_name, mode))
304

    
305
  return disk_data
306

    
307

    
308
class XenHypervisor(hv_base.BaseHypervisor):
309
  """Xen generic hypervisor interface
310

311
  This is the Xen base class used for both Xen PVM and HVM. It contains
312
  all the functionality that is identical for both.
313

314
  """
315
  CAN_MIGRATE = True
316
  REBOOT_RETRY_COUNT = 60
317
  REBOOT_RETRY_INTERVAL = 10
318
  _ROOT_DIR = pathutils.RUN_DIR + "/xen-hypervisor"
319
  _NICS_DIR = _ROOT_DIR + "/nic" # contains NICs' info
320
  _DIRS = [_ROOT_DIR, _NICS_DIR]
321

    
322
  ANCILLARY_FILES = [
323
    XEND_CONFIG_FILE,
324
    XL_CONFIG_FILE,
325
    VIF_BRIDGE_SCRIPT,
326
    ]
327
  ANCILLARY_FILES_OPT = [
328
    XL_CONFIG_FILE,
329
    ]
330

    
331
  def __init__(self, _cfgdir=None, _run_cmd_fn=None, _cmd=None):
332
    hv_base.BaseHypervisor.__init__(self)
333

    
334
    if _cfgdir is None:
335
      self._cfgdir = pathutils.XEN_CONFIG_DIR
336
    else:
337
      self._cfgdir = _cfgdir
338

    
339
    if _run_cmd_fn is None:
340
      self._run_cmd_fn = utils.RunCmd
341
    else:
342
      self._run_cmd_fn = _run_cmd_fn
343

    
344
    self._cmd = _cmd
345

    
346
  def _GetCommand(self, hvparams):
347
    """Returns Xen command to use.
348

349
    @type hvparams: dict of strings
350
    @param hvparams: hypervisor parameters
351

352
    """
353
    if self._cmd is None:
354
      if hvparams is None or constants.HV_XEN_CMD not in hvparams:
355
        raise errors.HypervisorError("Cannot determine xen command.")
356
      else:
357
        cmd = hvparams[constants.HV_XEN_CMD]
358
    else:
359
      cmd = self._cmd
360

    
361
    if cmd not in constants.KNOWN_XEN_COMMANDS:
362
      raise errors.ProgrammerError("Unknown Xen command '%s'" % cmd)
363

    
364
    return cmd
365

    
366
  def _RunXen(self, args, hvparams):
367
    """Wrapper around L{utils.process.RunCmd} to run Xen command.
368

369
    @type hvparams: dict of strings
370
    @param hvparams: dictionary of hypervisor params
371
    @see: L{utils.process.RunCmd}
372

373
    """
374
    cmd = [self._GetCommand(hvparams)]
375
    cmd.extend(args)
376

    
377
    return self._run_cmd_fn(cmd)
378

    
379
  def _ConfigFileName(self, instance_name):
380
    """Get the config file name for an instance.
381

382
    @param instance_name: instance name
383
    @type instance_name: str
384
    @return: fully qualified path to instance config file
385
    @rtype: str
386

387
    """
388
    return utils.PathJoin(self._cfgdir, instance_name)
389

    
390
  @classmethod
391
  def _WriteNICInfoFile(cls, instance_name, idx, nic):
392
    """Write the Xen config file for the instance.
393

394
    This version of the function just writes the config file from static data.
395

396
    """
397
    dirs = [(dname, constants.RUN_DIRS_MODE)
398
            for dname in cls._DIRS + [cls._InstanceNICDir(instance_name)]]
399
    utils.EnsureDirs(dirs)
400

    
401
    cfg_file = cls._InstanceNICFile(instance_name, idx)
402
    data = StringIO()
403

    
404
    if nic.netinfo:
405
      netinfo = objects.Network.FromDict(nic.netinfo)
406
      data.write("NETWORK_NAME=%s\n" % netinfo.name)
407
      if netinfo.network:
408
        data.write("NETWORK_SUBNET=%s\n" % netinfo.network)
409
      if netinfo.gateway:
410
        data.write("NETWORK_GATEWAY=%s\n" % netinfo.gateway)
411
      if netinfo.network6:
412
        data.write("NETWORK_SUBNET6=%s\n" % netinfo.network6)
413
      if netinfo.gateway6:
414
        data.write("NETWORK_GATEWAY6=%s\n" % netinfo.gateway6)
415
      if netinfo.mac_prefix:
416
        data.write("NETWORK_MAC_PREFIX=%s\n" % netinfo.mac_prefix)
417
      if netinfo.tags:
418
        data.write("NETWORK_TAGS=%s\n" % r"\ ".join(netinfo.tags))
419

    
420
    data.write("MAC=%s\n" % nic.mac)
421
    data.write("IP=%s\n" % nic.ip)
422
    data.write("MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
423
    data.write("LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
424

    
425
    try:
426
      utils.WriteFile(cfg_file, data=data.getvalue())
427
    except EnvironmentError, err:
428
      raise errors.HypervisorError("Cannot write Xen instance configuration"
429
                                   " file %s: %s" % (cfg_file, err))
430

    
431
  @classmethod
432
  def _InstanceNICDir(cls, instance_name):
433
    """Returns the directory holding the tap device files for a given instance.
434

435
    """
436
    return utils.PathJoin(cls._NICS_DIR, instance_name)
437

    
438
  @classmethod
439
  def _InstanceNICFile(cls, instance_name, seq):
440
    """Returns the name of the file containing the tap device for a given NIC
441

442
    """
443
    return utils.PathJoin(cls._InstanceNICDir(instance_name), str(seq))
444

    
445
  @classmethod
446
  def _GetConfig(cls, instance, startup_memory, block_devices):
447
    """Build Xen configuration for an instance.
448

449
    """
450
    raise NotImplementedError
451

    
452
  def _WriteConfigFile(self, instance_name, data):
453
    """Write the Xen config file for the instance.
454

455
    This version of the function just writes the config file from static data.
456

457
    """
458
    # just in case it exists
459
    utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
460

    
461
    cfg_file = self._ConfigFileName(instance_name)
462
    try:
463
      utils.WriteFile(cfg_file, data=data)
464
    except EnvironmentError, err:
465
      raise errors.HypervisorError("Cannot write Xen instance configuration"
466
                                   " file %s: %s" % (cfg_file, err))
467

    
468
  def _ReadConfigFile(self, instance_name):
469
    """Returns the contents of the instance config file.
470

471
    """
472
    filename = self._ConfigFileName(instance_name)
473

    
474
    try:
475
      file_content = utils.ReadFile(filename)
476
    except EnvironmentError, err:
477
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
478

    
479
    return file_content
480

    
481
  def _RemoveConfigFile(self, instance_name):
482
    """Remove the xen configuration file.
483

484
    """
485
    utils.RemoveFile(self._ConfigFileName(instance_name))
486
    try:
487
      shutil.rmtree(self._InstanceNICDir(instance_name))
488
    except OSError, err:
489
      if err.errno != errno.ENOENT:
490
        raise
491

    
492
  def _StashConfigFile(self, instance_name):
493
    """Move the Xen config file to the log directory and return its new path.
494

495
    """
496
    old_filename = self._ConfigFileName(instance_name)
497
    base = ("%s-%s" %
498
            (instance_name, utils.TimestampForFilename()))
499
    new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base)
500
    utils.RenameFile(old_filename, new_filename)
501
    return new_filename
502

    
503
  def _GetInstanceList(self, include_node, hvparams):
504
    """Wrapper around module level L{_GetInstanceList}.
505

506
    @type hvparams: dict of strings
507
    @param hvparams: hypervisor parameters to be used on this node
508

509
    """
510
    return _GetInstanceList(lambda: self._RunXen(["list"], hvparams),
511
                            include_node)
512

    
513
  def ListInstances(self, hvparams=None):
514
    """Get the list of running instances.
515

516
    """
517
    instance_list = self._GetInstanceList(False, hvparams)
518
    names = [info[0] for info in instance_list]
519
    return names
520

    
521
  def GetInstanceInfo(self, instance_name, hvparams=None):
522
    """Get instance properties.
523

524
    @type instance_name: string
525
    @param instance_name: the instance name
526
    @type hvparams: dict of strings
527
    @param hvparams: the instance's hypervisor params
528

529
    @return: tuple (name, id, memory, vcpus, stat, times)
530

531
    """
532
    instance_list = self._GetInstanceList(instance_name == _DOM0_NAME, hvparams)
533
    result = None
534
    for data in instance_list:
535
      if data[0] == instance_name:
536
        result = data
537
        break
538
    return result
539

    
540
  def GetAllInstancesInfo(self, hvparams=None):
541
    """Get properties of all instances.
542

543
    @type hvparams: dict of strings
544
    @param hvparams: hypervisor parameters
545
    @return: list of tuples (name, id, memory, vcpus, stat, times)
546

547
    """
548
    return self._GetInstanceList(False, hvparams)
549

    
550
  def _MakeConfigFile(self, instance, startup_memory, block_devices):
551
    """Gather configuration details and write to disk.
552

553
    See L{_GetConfig} for arguments.
554

555
    """
556
    buf = StringIO()
557
    buf.write("# Automatically generated by Ganeti. Do not edit!\n")
558
    buf.write("\n")
559
    buf.write(self._GetConfig(instance, startup_memory, block_devices))
560
    buf.write("\n")
561

    
562
    self._WriteConfigFile(instance.name, buf.getvalue())
563

    
564
  def StartInstance(self, instance, block_devices, startup_paused):
565
    """Start an instance.
566

567
    """
568
    startup_memory = self._InstanceStartupMemory(instance,
569
                                                 hvparams=instance.hvparams)
570

    
571
    self._MakeConfigFile(instance, startup_memory, block_devices)
572

    
573
    cmd = ["create"]
574
    if startup_paused:
575
      cmd.append("-p")
576
    cmd.append(self._ConfigFileName(instance.name))
577

    
578
    result = self._RunXen(cmd, instance.hvparams)
579
    if result.failed:
580
      # Move the Xen configuration file to the log directory to avoid
581
      # leaving a stale config file behind.
582
      stashed_config = self._StashConfigFile(instance.name)
583
      raise errors.HypervisorError("Failed to start instance %s: %s (%s). Moved"
584
                                   " config file to %s" %
585
                                   (instance.name, result.fail_reason,
586
                                    result.output, stashed_config))
587

    
588
  def StopInstance(self, instance, force=False, retry=False, name=None):
589
    """Stop an instance.
590

591
    """
592
    if name is None:
593
      name = instance.name
594

    
595
    return self._StopInstance(name, force, instance.hvparams)
596

    
597
  def _StopInstance(self, name, force, hvparams):
598
    """Stop an instance.
599

600
    @type name: string
601
    @param name: name of the instance to be shutdown
602
    @type force: boolean
603
    @param force: flag specifying whether shutdown should be forced
604
    @type hvparams: dict of string
605
    @param hvparams: hypervisor parameters of the instance
606

607
    """
608
    if force:
609
      action = "destroy"
610
    else:
611
      action = "shutdown"
612

    
613
    result = self._RunXen([action, name], hvparams)
614
    if result.failed:
615
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
616
                                   (name, result.fail_reason, result.output))
617

    
618
    # Remove configuration file if stopping/starting instance was successful
619
    self._RemoveConfigFile(name)
620

    
621
  def RebootInstance(self, instance):
622
    """Reboot an instance.
623

624
    """
625
    ini_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
626

    
627
    if ini_info is None:
628
      raise errors.HypervisorError("Failed to reboot instance %s,"
629
                                   " not running" % instance.name)
630

    
631
    result = self._RunXen(["reboot", instance.name], instance.hvparams)
632
    if result.failed:
633
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
634
                                   (instance.name, result.fail_reason,
635
                                    result.output))
636

    
637
    def _CheckInstance():
638
      new_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
639

    
640
      # check if the domain ID has changed or the run time has decreased
641
      if (new_info is not None and
642
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
643
        return
644

    
645
      raise utils.RetryAgain()
646

    
647
    try:
648
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
649
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
650
    except utils.RetryTimeout:
651
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
652
                                   " did not reboot in the expected interval" %
653
                                   (instance.name, ))
654

    
655
  def BalloonInstanceMemory(self, instance, mem):
656
    """Balloon an instance memory to a certain value.
657

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

663
    """
664
    result = self._RunXen(["mem-set", instance.name, mem], instance.hvparams)
665
    if result.failed:
666
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
667
                                   (instance.name, result.fail_reason,
668
                                    result.output))
669

    
670
    # Update configuration file
671
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
672
    cmd.append(self._ConfigFileName(instance.name))
673

    
674
    result = utils.RunCmd(cmd)
675
    if result.failed:
676
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
677
                                   (instance.name, result.fail_reason,
678
                                    result.output))
679

    
680
  def GetNodeInfo(self, hvparams=None):
681
    """Return information about the node.
682

683
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
684

685
    """
686
    result = self._RunXen(["info"], hvparams)
687
    if result.failed:
688
      logging.error("Can't retrieve xen hypervisor information (%s): %s",
689
                    result.fail_reason, result.output)
690
      return None
691

    
692
    instance_list = self._GetInstanceList(True, hvparams)
693
    return _GetNodeInfo(result.stdout, instance_list)
694

    
695
  def GetInstanceConsole(self, instance, primary_node, hvparams, beparams):
696
    """Return a command for connecting to the console of an instance.
697

698
    """
699
    xen_cmd = self._GetCommand(hvparams)
700
    return objects.InstanceConsole(instance=instance.name,
701
                                   kind=constants.CONS_SSH,
702
                                   host=primary_node.name,
703
                                   user=constants.SSH_CONSOLE_USER,
704
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
705
                                            xen_cmd, instance.name])
706

    
707
  def Verify(self, hvparams=None):
708
    """Verify the hypervisor.
709

710
    For Xen, this verifies that the xend process is running.
711

712
    @type hvparams: dict of strings
713
    @param hvparams: hypervisor parameters to be verified against
714

715
    @return: Problem description if something is wrong, C{None} otherwise
716

717
    """
718
    if hvparams is None:
719
      return "Could not verify the hypervisor, because no hvparams were" \
720
             " provided."
721

    
722
    if constants.HV_XEN_CMD in hvparams:
723
      xen_cmd = hvparams[constants.HV_XEN_CMD]
724
      try:
725
        self._CheckToolstack(xen_cmd)
726
      except errors.HypervisorError:
727
        return "The configured xen toolstack '%s' is not available on this" \
728
               " node." % xen_cmd
729

    
730
    result = self._RunXen(["info"], hvparams)
731
    if result.failed:
732
      return "Retrieving information from xen failed: %s, %s" % \
733
        (result.fail_reason, result.output)
734

    
735
    return None
736

    
737
  def MigrationInfo(self, instance):
738
    """Get instance information to perform a migration.
739

740
    @type instance: L{objects.Instance}
741
    @param instance: instance to be migrated
742
    @rtype: string
743
    @return: content of the xen config file
744

745
    """
746
    return self._ReadConfigFile(instance.name)
747

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

751
    @type instance: L{objects.Instance}
752
    @param instance: instance to be accepted
753
    @type info: string
754
    @param info: content of the xen config file on the source node
755
    @type target: string
756
    @param target: target host (usually ip), on this node
757

758
    """
759
    pass
760

    
761
  def FinalizeMigrationDst(self, instance, info, success):
762
    """Finalize an instance migration.
763

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

767
    @type instance: L{objects.Instance}
768
    @param instance: instance whose migration is being finalized
769
    @type info: string
770
    @param info: content of the xen config file on the source node
771
    @type success: boolean
772
    @param success: whether the migration was a success or a failure
773

774
    """
775
    if success:
776
      self._WriteConfigFile(instance.name, info)
777

    
778
  def MigrateInstance(self, cluster_name, instance, target, live):
779
    """Migrate an instance to a target node.
780

781
    The migration will not be attempted if the instance is not
782
    currently running.
783

784
    @type instance: L{objects.Instance}
785
    @param instance: the instance to be migrated
786
    @type target: string
787
    @param target: ip address of the target node
788
    @type live: boolean
789
    @param live: perform a live migration
790

791
    """
792
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
793

    
794
    return self._MigrateInstance(cluster_name, instance.name, target, port,
795
                                 live, instance.hvparams)
796

    
797
  def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
798
                       hvparams, _ping_fn=netutils.TcpPing):
799
    """Migrate an instance to a target node.
800

801
    @see: L{MigrateInstance} for details
802

803
    """
804
    if hvparams is None:
805
      raise errors.HypervisorError("No hvparams provided.")
806

    
807
    if self.GetInstanceInfo(instance_name, hvparams=hvparams) is None:
808
      raise errors.HypervisorError("Instance not running, cannot migrate")
809

    
810
    cmd = self._GetCommand(hvparams)
811

    
812
    if (cmd == constants.XEN_CMD_XM and
813
        not _ping_fn(target, port, live_port_needed=True)):
814
      raise errors.HypervisorError("Remote host %s not listening on port"
815
                                   " %s, cannot migrate" % (target, port))
816

    
817
    args = ["migrate"]
818

    
819
    if cmd == constants.XEN_CMD_XM:
820
      args.extend(["-p", "%d" % port])
821
      if live:
822
        args.append("-l")
823

    
824
    elif cmd == constants.XEN_CMD_XL:
825
      args.extend([
826
        "-s", constants.XL_SSH_CMD % cluster_name,
827
        "-C", self._ConfigFileName(instance_name),
828
        ])
829

    
830
    else:
831
      raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
832

    
833
    args.extend([instance_name, target])
834

    
835
    result = self._RunXen(args, hvparams)
836
    if result.failed:
837
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
838
                                   (instance_name, result.output))
839

    
840
  def FinalizeMigrationSource(self, instance, success, live):
841
    """Finalize the instance migration on the source node.
842

843
    @type instance: L{objects.Instance}
844
    @param instance: the instance that was migrated
845
    @type success: bool
846
    @param success: whether the migration succeeded or not
847
    @type live: bool
848
    @param live: whether the user requested a live migration or not
849

850
    """
851
    # pylint: disable=W0613
852
    if success:
853
      # remove old xen file after migration succeeded
854
      try:
855
        self._RemoveConfigFile(instance.name)
856
      except EnvironmentError:
857
        logging.exception("Failure while removing instance config file")
858

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

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

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

873
    """
874
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
875

    
876
  def PowercycleNode(self, hvparams=None):
877
    """Xen-specific powercycle.
878

879
    This first does a Linux reboot (which triggers automatically a Xen
880
    reboot), and if that fails it tries to do a Xen reboot. The reason
881
    we don't try a Xen reboot first is that the xen reboot launches an
882
    external command which connects to the Xen hypervisor, and that
883
    won't work in case the root filesystem is broken and/or the xend
884
    daemon is not working.
885

886
    @type hvparams: dict of strings
887
    @param hvparams: hypervisor params to be used on this node
888

889
    """
890
    try:
891
      self.LinuxPowercycle()
892
    finally:
893
      xen_cmd = self._GetCommand(hvparams)
894
      utils.RunCmd([xen_cmd, "debug", "R"])
895

    
896
  def _CheckToolstack(self, xen_cmd):
897
    """Check whether the given toolstack is available on the node.
898

899
    @type xen_cmd: string
900
    @param xen_cmd: xen command (e.g. 'xm' or 'xl')
901

902
    """
903
    binary_found = self._CheckToolstackBinary(xen_cmd)
904
    if not binary_found:
905
      raise errors.HypervisorError("No '%s' binary found on node." % xen_cmd)
906
    elif xen_cmd == constants.XEN_CMD_XL:
907
      if not self._CheckToolstackXlConfigured():
908
        raise errors.HypervisorError("Toolstack '%s' is not enabled on this"
909
                                     "node." % xen_cmd)
910

    
911
  def _CheckToolstackBinary(self, xen_cmd):
912
    """Checks whether the xen command's binary is found on the machine.
913

914
    """
915
    if xen_cmd not in constants.KNOWN_XEN_COMMANDS:
916
      raise errors.HypervisorError("Unknown xen command '%s'." % xen_cmd)
917
    result = self._run_cmd_fn(["which", xen_cmd])
918
    return not result.failed
919

    
920
  def _CheckToolstackXlConfigured(self):
921
    """Checks whether xl is enabled on an xl-capable node.
922

923
    @rtype: bool
924
    @returns: C{True} if 'xl' is enabled, C{False} otherwise
925

926
    """
927
    result = self._run_cmd_fn([constants.XEN_CMD_XL, "help"])
928
    if not result.failed:
929
      return True
930
    elif result.failed:
931
      if "toolstack" in result.stderr:
932
        return False
933
      # xl fails for some other reason than the toolstack
934
      else:
935
        raise errors.HypervisorError("Cannot run xen ('%s'). Error: %s."
936
                                     % (constants.XEN_CMD_XL, result.stderr))
937

    
938

    
939
class XenPvmHypervisor(XenHypervisor):
940
  """Xen PVM hypervisor interface"""
941

    
942
  PARAMETERS = {
943
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
944
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
945
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
946
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
947
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
948
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
949
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
950
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
951
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
952
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
953
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
954
    constants.HV_REBOOT_BEHAVIOR:
955
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
956
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
957
    constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
958
    constants.HV_CPU_WEIGHT:
959
      (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
960
    constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
961
    constants.HV_XEN_CMD:
962
      hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
963
    }
964

    
965
  def _GetConfig(self, instance, startup_memory, block_devices):
966
    """Write the Xen config file for the instance.
967

968
    """
969
    hvp = instance.hvparams
970
    config = StringIO()
971
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
972

    
973
    # if bootloader is True, use bootloader instead of kernel and ramdisk
974
    # parameters.
975
    if hvp[constants.HV_USE_BOOTLOADER]:
976
      # bootloader handling
977
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
978
      if bootloader_path:
979
        config.write("bootloader = '%s'\n" % bootloader_path)
980
      else:
981
        raise errors.HypervisorError("Bootloader enabled, but missing"
982
                                     " bootloader path")
983

    
984
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
985
      if bootloader_args:
986
        config.write("bootargs = '%s'\n" % bootloader_args)
987
    else:
988
      # kernel handling
989
      kpath = hvp[constants.HV_KERNEL_PATH]
990
      config.write("kernel = '%s'\n" % kpath)
991

    
992
      # initrd handling
993
      initrd_path = hvp[constants.HV_INITRD_PATH]
994
      if initrd_path:
995
        config.write("ramdisk = '%s'\n" % initrd_path)
996

    
997
    # rest of the settings
998
    config.write("memory = %d\n" % startup_memory)
999
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1000
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1001
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1002
    if cpu_pinning:
1003
      config.write("%s\n" % cpu_pinning)
1004
    cpu_cap = hvp[constants.HV_CPU_CAP]
1005
    if cpu_cap:
1006
      config.write("cpu_cap=%d\n" % cpu_cap)
1007
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1008
    if cpu_weight:
1009
      config.write("cpu_weight=%d\n" % cpu_weight)
1010

    
1011
    config.write("name = '%s'\n" % instance.name)
1012

    
1013
    vif_data = []
1014
    for idx, nic in enumerate(instance.nics):
1015
      nic_str = "mac=%s" % (nic.mac)
1016
      ip = getattr(nic, "ip", None)
1017
      if ip is not None:
1018
        nic_str += ", ip=%s" % ip
1019
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1020
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1021
      if hvp[constants.HV_VIF_SCRIPT]:
1022
        nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1023
      vif_data.append("'%s'" % nic_str)
1024
      self._WriteNICInfoFile(instance.name, idx, nic)
1025

    
1026
    disk_data = \
1027
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1028

    
1029
    config.write("vif = [%s]\n" % ",".join(vif_data))
1030
    config.write("disk = [%s]\n" % ",".join(disk_data))
1031

    
1032
    if hvp[constants.HV_ROOT_PATH]:
1033
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
1034
    config.write("on_poweroff = 'destroy'\n")
1035
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1036
      config.write("on_reboot = 'restart'\n")
1037
    else:
1038
      config.write("on_reboot = 'destroy'\n")
1039
    config.write("on_crash = 'restart'\n")
1040
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
1041

    
1042
    return config.getvalue()
1043

    
1044

    
1045
class XenHvmHypervisor(XenHypervisor):
1046
  """Xen HVM hypervisor interface"""
1047

    
1048
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
1049
    pathutils.VNC_PASSWORD_FILE,
1050
    ]
1051
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
1052
    pathutils.VNC_PASSWORD_FILE,
1053
    ]
1054

    
1055
  PARAMETERS = {
1056
    constants.HV_ACPI: hv_base.NO_CHECK,
1057
    constants.HV_BOOT_ORDER: (True, ) +
1058
      (lambda x: x and len(x.strip("acdn")) == 0,
1059
       "Invalid boot order specified, must be one or more of [acdn]",
1060
       None, None),
1061
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
1062
    constants.HV_DISK_TYPE:
1063
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
1064
    constants.HV_NIC_TYPE:
1065
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
1066
    constants.HV_PAE: hv_base.NO_CHECK,
1067
    constants.HV_VNC_BIND_ADDRESS:
1068
      (False, netutils.IP4Address.IsValid,
1069
       "VNC bind address is not a valid IP address", None, None),
1070
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
1071
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
1072
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
1073
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
1074
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
1075
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
1076
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
1077
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
1078
    # Add PCI passthrough
1079
    constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
1080
    constants.HV_REBOOT_BEHAVIOR:
1081
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
1082
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
1083
    constants.HV_CPU_CAP: hv_base.NO_CHECK,
1084
    constants.HV_CPU_WEIGHT:
1085
      (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
1086
    constants.HV_VIF_TYPE:
1087
      hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES),
1088
    constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
1089
    constants.HV_VIRIDIAN: hv_base.NO_CHECK,
1090
    constants.HV_XEN_CMD:
1091
      hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
1092
    }
1093

    
1094
  def _GetConfig(self, instance, startup_memory, block_devices):
1095
    """Create a Xen 3.1 HVM config file.
1096

1097
    """
1098
    hvp = instance.hvparams
1099

    
1100
    config = StringIO()
1101

    
1102
    # kernel handling
1103
    kpath = hvp[constants.HV_KERNEL_PATH]
1104
    config.write("kernel = '%s'\n" % kpath)
1105

    
1106
    config.write("builder = 'hvm'\n")
1107
    config.write("memory = %d\n" % startup_memory)
1108
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1109
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1110
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1111
    if cpu_pinning:
1112
      config.write("%s\n" % cpu_pinning)
1113
    cpu_cap = hvp[constants.HV_CPU_CAP]
1114
    if cpu_cap:
1115
      config.write("cpu_cap=%d\n" % cpu_cap)
1116
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1117
    if cpu_weight:
1118
      config.write("cpu_weight=%d\n" % cpu_weight)
1119

    
1120
    config.write("name = '%s'\n" % instance.name)
1121
    if hvp[constants.HV_PAE]:
1122
      config.write("pae = 1\n")
1123
    else:
1124
      config.write("pae = 0\n")
1125
    if hvp[constants.HV_ACPI]:
1126
      config.write("acpi = 1\n")
1127
    else:
1128
      config.write("acpi = 0\n")
1129
    if hvp[constants.HV_VIRIDIAN]:
1130
      config.write("viridian = 1\n")
1131
    else:
1132
      config.write("viridian = 0\n")
1133

    
1134
    config.write("apic = 1\n")
1135
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
1136
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
1137
    config.write("sdl = 0\n")
1138
    config.write("usb = 1\n")
1139
    config.write("usbdevice = 'tablet'\n")
1140
    config.write("vnc = 1\n")
1141
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
1142
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
1143
    else:
1144
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
1145

    
1146
    if instance.network_port > constants.VNC_BASE_PORT:
1147
      display = instance.network_port - constants.VNC_BASE_PORT
1148
      config.write("vncdisplay = %s\n" % display)
1149
      config.write("vncunused = 0\n")
1150
    else:
1151
      config.write("# vncdisplay = 1\n")
1152
      config.write("vncunused = 1\n")
1153

    
1154
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
1155
    try:
1156
      password = utils.ReadFile(vnc_pwd_file)
1157
    except EnvironmentError, err:
1158
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1159
                                   (vnc_pwd_file, err))
1160

    
1161
    config.write("vncpasswd = '%s'\n" % password.rstrip())
1162

    
1163
    config.write("serial = 'pty'\n")
1164
    if hvp[constants.HV_USE_LOCALTIME]:
1165
      config.write("localtime = 1\n")
1166

    
1167
    vif_data = []
1168
    # Note: what is called 'nic_type' here, is used as value for the xen nic
1169
    # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1170
    # the 'vif_type' to avoid a clash of notation.
1171
    nic_type = hvp[constants.HV_NIC_TYPE]
1172

    
1173
    if nic_type is None:
1174
      vif_type_str = ""
1175
      if hvp[constants.HV_VIF_TYPE]:
1176
        vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
1177
      # ensure old instances don't change
1178
      nic_type_str = vif_type_str
1179
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
1180
      nic_type_str = ", type=paravirtualized"
1181
    else:
1182
      # parameter 'model' is only valid with type 'ioemu'
1183
      nic_type_str = ", model=%s, type=%s" % \
1184
        (nic_type, constants.HT_HVM_VIF_IOEMU)
1185
    for idx, nic in enumerate(instance.nics):
1186
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1187
      ip = getattr(nic, "ip", None)
1188
      if ip is not None:
1189
        nic_str += ", ip=%s" % ip
1190
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1191
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1192
      if hvp[constants.HV_VIF_SCRIPT]:
1193
        nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1194
      vif_data.append("'%s'" % nic_str)
1195
      self._WriteNICInfoFile(instance.name, idx, nic)
1196

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

    
1199
    disk_data = \
1200
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1201

    
1202
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1203
    if iso_path:
1204
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
1205
      disk_data.append(iso)
1206

    
1207
    config.write("disk = [%s]\n" % (",".join(disk_data)))
1208
    # Add PCI passthrough
1209
    pci_pass_arr = []
1210
    pci_pass = hvp[constants.HV_PASSTHROUGH]
1211
    if pci_pass:
1212
      pci_pass_arr = pci_pass.split(";")
1213
      config.write("pci = %s\n" % pci_pass_arr)
1214
    config.write("on_poweroff = 'destroy'\n")
1215
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1216
      config.write("on_reboot = 'restart'\n")
1217
    else:
1218
      config.write("on_reboot = 'destroy'\n")
1219
    config.write("on_crash = 'restart'\n")
1220

    
1221
    return config.getvalue()