Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 931419e5

History | View | Annotate | Download (41.1 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 in constants.DTS_FILEBASED:
299
      driver = _FILE_DRIVER_MAP[cfdev.logical_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
def _QuoteCpuidField(data):
309
  """Add quotes around the CPUID field only if necessary.
310

311
  Xen CPUID fields come in two shapes: LIBXL strings, which need quotes around
312
  them, and lists of XEND strings, which don't.
313

314
  @param data: Either type of parameter.
315
  @return: The quoted version thereof.
316

317
  """
318
  return "'%s'" % data if data.startswith("host") else data
319

    
320

    
321
class XenHypervisor(hv_base.BaseHypervisor):
322
  """Xen generic hypervisor interface
323

324
  This is the Xen base class used for both Xen PVM and HVM. It contains
325
  all the functionality that is identical for both.
326

327
  """
328
  CAN_MIGRATE = True
329
  REBOOT_RETRY_COUNT = 60
330
  REBOOT_RETRY_INTERVAL = 10
331
  _ROOT_DIR = pathutils.RUN_DIR + "/xen-hypervisor"
332
  _NICS_DIR = _ROOT_DIR + "/nic" # contains NICs' info
333
  _DIRS = [_ROOT_DIR, _NICS_DIR]
334

    
335
  ANCILLARY_FILES = [
336
    XEND_CONFIG_FILE,
337
    XL_CONFIG_FILE,
338
    VIF_BRIDGE_SCRIPT,
339
    ]
340
  ANCILLARY_FILES_OPT = [
341
    XL_CONFIG_FILE,
342
    ]
343

    
344
  def __init__(self, _cfgdir=None, _run_cmd_fn=None, _cmd=None):
345
    hv_base.BaseHypervisor.__init__(self)
346

    
347
    if _cfgdir is None:
348
      self._cfgdir = pathutils.XEN_CONFIG_DIR
349
    else:
350
      self._cfgdir = _cfgdir
351

    
352
    if _run_cmd_fn is None:
353
      self._run_cmd_fn = utils.RunCmd
354
    else:
355
      self._run_cmd_fn = _run_cmd_fn
356

    
357
    self._cmd = _cmd
358

    
359
  def _GetCommand(self, hvparams):
360
    """Returns Xen command to use.
361

362
    @type hvparams: dict of strings
363
    @param hvparams: hypervisor parameters
364

365
    """
366
    if self._cmd is None:
367
      if hvparams is None or constants.HV_XEN_CMD not in hvparams:
368
        raise errors.HypervisorError("Cannot determine xen command.")
369
      else:
370
        cmd = hvparams[constants.HV_XEN_CMD]
371
    else:
372
      cmd = self._cmd
373

    
374
    if cmd not in constants.KNOWN_XEN_COMMANDS:
375
      raise errors.ProgrammerError("Unknown Xen command '%s'" % cmd)
376

    
377
    return cmd
378

    
379
  def _RunXen(self, args, hvparams):
380
    """Wrapper around L{utils.process.RunCmd} to run Xen command.
381

382
    @type hvparams: dict of strings
383
    @param hvparams: dictionary of hypervisor params
384
    @see: L{utils.process.RunCmd}
385

386
    """
387
    cmd = [self._GetCommand(hvparams)]
388
    cmd.extend(args)
389

    
390
    return self._run_cmd_fn(cmd)
391

    
392
  def _ConfigFileName(self, instance_name):
393
    """Get the config file name for an instance.
394

395
    @param instance_name: instance name
396
    @type instance_name: str
397
    @return: fully qualified path to instance config file
398
    @rtype: str
399

400
    """
401
    return utils.PathJoin(self._cfgdir, instance_name)
402

    
403
  @classmethod
404
  def _WriteNICInfoFile(cls, instance_name, idx, nic):
405
    """Write the Xen config file for the instance.
406

407
    This version of the function just writes the config file from static data.
408

409
    """
410
    dirs = [(dname, constants.RUN_DIRS_MODE)
411
            for dname in cls._DIRS + [cls._InstanceNICDir(instance_name)]]
412
    utils.EnsureDirs(dirs)
413

    
414
    cfg_file = cls._InstanceNICFile(instance_name, idx)
415
    data = StringIO()
416

    
417
    if nic.netinfo:
418
      netinfo = objects.Network.FromDict(nic.netinfo)
419
      data.write("NETWORK_NAME=%s\n" % netinfo.name)
420
      if netinfo.network:
421
        data.write("NETWORK_SUBNET=%s\n" % netinfo.network)
422
      if netinfo.gateway:
423
        data.write("NETWORK_GATEWAY=%s\n" % netinfo.gateway)
424
      if netinfo.network6:
425
        data.write("NETWORK_SUBNET6=%s\n" % netinfo.network6)
426
      if netinfo.gateway6:
427
        data.write("NETWORK_GATEWAY6=%s\n" % netinfo.gateway6)
428
      if netinfo.mac_prefix:
429
        data.write("NETWORK_MAC_PREFIX=%s\n" % netinfo.mac_prefix)
430
      if netinfo.tags:
431
        data.write("NETWORK_TAGS=%s\n" % r"\ ".join(netinfo.tags))
432

    
433
    data.write("MAC=%s\n" % nic.mac)
434
    data.write("IP=%s\n" % nic.ip)
435
    data.write("MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
436
    data.write("LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
437

    
438
    try:
439
      utils.WriteFile(cfg_file, data=data.getvalue())
440
    except EnvironmentError, err:
441
      raise errors.HypervisorError("Cannot write Xen instance configuration"
442
                                   " file %s: %s" % (cfg_file, err))
443

    
444
  @classmethod
445
  def _InstanceNICDir(cls, instance_name):
446
    """Returns the directory holding the tap device files for a given instance.
447

448
    """
449
    return utils.PathJoin(cls._NICS_DIR, instance_name)
450

    
451
  @classmethod
452
  def _InstanceNICFile(cls, instance_name, seq):
453
    """Returns the name of the file containing the tap device for a given NIC
454

455
    """
456
    return utils.PathJoin(cls._InstanceNICDir(instance_name), str(seq))
457

    
458
  @classmethod
459
  def _GetConfig(cls, instance, startup_memory, block_devices):
460
    """Build Xen configuration for an instance.
461

462
    """
463
    raise NotImplementedError
464

    
465
  def _WriteConfigFile(self, instance_name, data):
466
    """Write the Xen config file for the instance.
467

468
    This version of the function just writes the config file from static data.
469

470
    """
471
    # just in case it exists
472
    utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
473

    
474
    cfg_file = self._ConfigFileName(instance_name)
475
    try:
476
      utils.WriteFile(cfg_file, data=data)
477
    except EnvironmentError, err:
478
      raise errors.HypervisorError("Cannot write Xen instance configuration"
479
                                   " file %s: %s" % (cfg_file, err))
480

    
481
  def _ReadConfigFile(self, instance_name):
482
    """Returns the contents of the instance config file.
483

484
    """
485
    filename = self._ConfigFileName(instance_name)
486

    
487
    try:
488
      file_content = utils.ReadFile(filename)
489
    except EnvironmentError, err:
490
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
491

    
492
    return file_content
493

    
494
  def _RemoveConfigFile(self, instance_name):
495
    """Remove the xen configuration file.
496

497
    """
498
    utils.RemoveFile(self._ConfigFileName(instance_name))
499
    try:
500
      shutil.rmtree(self._InstanceNICDir(instance_name))
501
    except OSError, err:
502
      if err.errno != errno.ENOENT:
503
        raise
504

    
505
  def _StashConfigFile(self, instance_name):
506
    """Move the Xen config file to the log directory and return its new path.
507

508
    """
509
    old_filename = self._ConfigFileName(instance_name)
510
    base = ("%s-%s" %
511
            (instance_name, utils.TimestampForFilename()))
512
    new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base)
513
    utils.RenameFile(old_filename, new_filename)
514
    return new_filename
515

    
516
  def _GetInstanceList(self, include_node, hvparams):
517
    """Wrapper around module level L{_GetInstanceList}.
518

519
    @type hvparams: dict of strings
520
    @param hvparams: hypervisor parameters to be used on this node
521

522
    """
523
    return _GetInstanceList(lambda: self._RunXen(["list"], hvparams),
524
                            include_node)
525

    
526
  def ListInstances(self, hvparams=None):
527
    """Get the list of running instances.
528

529
    """
530
    instance_list = self._GetInstanceList(False, hvparams)
531
    names = [info[0] for info in instance_list]
532
    return names
533

    
534
  def GetInstanceInfo(self, instance_name, hvparams=None):
535
    """Get instance properties.
536

537
    @type instance_name: string
538
    @param instance_name: the instance name
539
    @type hvparams: dict of strings
540
    @param hvparams: the instance's hypervisor params
541

542
    @return: tuple (name, id, memory, vcpus, stat, times)
543

544
    """
545
    instance_list = self._GetInstanceList(instance_name == _DOM0_NAME, hvparams)
546
    result = None
547
    for data in instance_list:
548
      if data[0] == instance_name:
549
        result = data
550
        break
551
    return result
552

    
553
  def GetAllInstancesInfo(self, hvparams=None):
554
    """Get properties of all instances.
555

556
    @type hvparams: dict of strings
557
    @param hvparams: hypervisor parameters
558
    @return: list of tuples (name, id, memory, vcpus, stat, times)
559

560
    """
561
    return self._GetInstanceList(False, hvparams)
562

    
563
  def _MakeConfigFile(self, instance, startup_memory, block_devices):
564
    """Gather configuration details and write to disk.
565

566
    See L{_GetConfig} for arguments.
567

568
    """
569
    buf = StringIO()
570
    buf.write("# Automatically generated by Ganeti. Do not edit!\n")
571
    buf.write("\n")
572
    buf.write(self._GetConfig(instance, startup_memory, block_devices))
573
    buf.write("\n")
574

    
575
    self._WriteConfigFile(instance.name, buf.getvalue())
576

    
577
  def StartInstance(self, instance, block_devices, startup_paused):
578
    """Start an instance.
579

580
    """
581
    startup_memory = self._InstanceStartupMemory(instance,
582
                                                 hvparams=instance.hvparams)
583

    
584
    self._MakeConfigFile(instance, startup_memory, block_devices)
585

    
586
    cmd = ["create"]
587
    if startup_paused:
588
      cmd.append("-p")
589
    cmd.append(self._ConfigFileName(instance.name))
590

    
591
    result = self._RunXen(cmd, instance.hvparams)
592
    if result.failed:
593
      # Move the Xen configuration file to the log directory to avoid
594
      # leaving a stale config file behind.
595
      stashed_config = self._StashConfigFile(instance.name)
596
      raise errors.HypervisorError("Failed to start instance %s: %s (%s). Moved"
597
                                   " config file to %s" %
598
                                   (instance.name, result.fail_reason,
599
                                    result.output, stashed_config))
600

    
601
  def StopInstance(self, instance, force=False, retry=False, name=None):
602
    """Stop an instance.
603

604
    """
605
    if name is None:
606
      name = instance.name
607

    
608
    return self._StopInstance(name, force, instance.hvparams)
609

    
610
  def _StopInstance(self, name, force, hvparams):
611
    """Stop an instance.
612

613
    @type name: string
614
    @param name: name of the instance to be shutdown
615
    @type force: boolean
616
    @param force: flag specifying whether shutdown should be forced
617
    @type hvparams: dict of string
618
    @param hvparams: hypervisor parameters of the instance
619

620
    """
621
    if force:
622
      action = "destroy"
623
    else:
624
      action = "shutdown"
625

    
626
    result = self._RunXen([action, name], hvparams)
627
    if result.failed:
628
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
629
                                   (name, result.fail_reason, result.output))
630

    
631
    # Remove configuration file if stopping/starting instance was successful
632
    self._RemoveConfigFile(name)
633

    
634
  def RebootInstance(self, instance):
635
    """Reboot an instance.
636

637
    """
638
    ini_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
639

    
640
    if ini_info is None:
641
      raise errors.HypervisorError("Failed to reboot instance %s,"
642
                                   " not running" % instance.name)
643

    
644
    result = self._RunXen(["reboot", instance.name], instance.hvparams)
645
    if result.failed:
646
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
647
                                   (instance.name, result.fail_reason,
648
                                    result.output))
649

    
650
    def _CheckInstance():
651
      new_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
652

    
653
      # check if the domain ID has changed or the run time has decreased
654
      if (new_info is not None and
655
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
656
        return
657

    
658
      raise utils.RetryAgain()
659

    
660
    try:
661
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
662
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
663
    except utils.RetryTimeout:
664
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
665
                                   " did not reboot in the expected interval" %
666
                                   (instance.name, ))
667

    
668
  def BalloonInstanceMemory(self, instance, mem):
669
    """Balloon an instance memory to a certain value.
670

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

676
    """
677
    result = self._RunXen(["mem-set", instance.name, mem], instance.hvparams)
678
    if result.failed:
679
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
680
                                   (instance.name, result.fail_reason,
681
                                    result.output))
682

    
683
    # Update configuration file
684
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
685
    cmd.append(self._ConfigFileName(instance.name))
686

    
687
    result = utils.RunCmd(cmd)
688
    if result.failed:
689
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
690
                                   (instance.name, result.fail_reason,
691
                                    result.output))
692

    
693
  def GetNodeInfo(self, hvparams=None):
694
    """Return information about the node.
695

696
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
697

698
    """
699
    result = self._RunXen(["info"], hvparams)
700
    if result.failed:
701
      logging.error("Can't retrieve xen hypervisor information (%s): %s",
702
                    result.fail_reason, result.output)
703
      return None
704

    
705
    instance_list = self._GetInstanceList(True, hvparams)
706
    return _GetNodeInfo(result.stdout, instance_list)
707

    
708
  def GetInstanceConsole(self, instance, primary_node, hvparams, beparams):
709
    """Return a command for connecting to the console of an instance.
710

711
    """
712
    xen_cmd = self._GetCommand(hvparams)
713
    return objects.InstanceConsole(instance=instance.name,
714
                                   kind=constants.CONS_SSH,
715
                                   host=primary_node.name,
716
                                   user=constants.SSH_CONSOLE_USER,
717
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
718
                                            xen_cmd, instance.name])
719

    
720
  def Verify(self, hvparams=None):
721
    """Verify the hypervisor.
722

723
    For Xen, this verifies that the xend process is running.
724

725
    @type hvparams: dict of strings
726
    @param hvparams: hypervisor parameters to be verified against
727

728
    @return: Problem description if something is wrong, C{None} otherwise
729

730
    """
731
    if hvparams is None:
732
      return "Could not verify the hypervisor, because no hvparams were" \
733
             " provided."
734

    
735
    if constants.HV_XEN_CMD in hvparams:
736
      xen_cmd = hvparams[constants.HV_XEN_CMD]
737
      try:
738
        self._CheckToolstack(xen_cmd)
739
      except errors.HypervisorError:
740
        return "The configured xen toolstack '%s' is not available on this" \
741
               " node." % xen_cmd
742

    
743
    result = self._RunXen(["info"], hvparams)
744
    if result.failed:
745
      return "Retrieving information from xen failed: %s, %s" % \
746
        (result.fail_reason, result.output)
747

    
748
    return None
749

    
750
  def MigrationInfo(self, instance):
751
    """Get instance information to perform a migration.
752

753
    @type instance: L{objects.Instance}
754
    @param instance: instance to be migrated
755
    @rtype: string
756
    @return: content of the xen config file
757

758
    """
759
    return self._ReadConfigFile(instance.name)
760

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

764
    @type instance: L{objects.Instance}
765
    @param instance: instance to be accepted
766
    @type info: string
767
    @param info: content of the xen config file on the source node
768
    @type target: string
769
    @param target: target host (usually ip), on this node
770

771
    """
772
    pass
773

    
774
  def FinalizeMigrationDst(self, instance, info, success):
775
    """Finalize an instance migration.
776

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

780
    @type instance: L{objects.Instance}
781
    @param instance: instance whose migration is being finalized
782
    @type info: string
783
    @param info: content of the xen config file on the source node
784
    @type success: boolean
785
    @param success: whether the migration was a success or a failure
786

787
    """
788
    if success:
789
      self._WriteConfigFile(instance.name, info)
790

    
791
  def MigrateInstance(self, cluster_name, instance, target, live):
792
    """Migrate an instance to a target node.
793

794
    The migration will not be attempted if the instance is not
795
    currently running.
796

797
    @type instance: L{objects.Instance}
798
    @param instance: the instance to be migrated
799
    @type target: string
800
    @param target: ip address of the target node
801
    @type live: boolean
802
    @param live: perform a live migration
803

804
    """
805
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
806

    
807
    return self._MigrateInstance(cluster_name, instance.name, target, port,
808
                                 live, instance.hvparams)
809

    
810
  def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
811
                       hvparams, _ping_fn=netutils.TcpPing):
812
    """Migrate an instance to a target node.
813

814
    @see: L{MigrateInstance} for details
815

816
    """
817
    if hvparams is None:
818
      raise errors.HypervisorError("No hvparams provided.")
819

    
820
    if self.GetInstanceInfo(instance_name, hvparams=hvparams) is None:
821
      raise errors.HypervisorError("Instance not running, cannot migrate")
822

    
823
    cmd = self._GetCommand(hvparams)
824

    
825
    if (cmd == constants.XEN_CMD_XM and
826
        not _ping_fn(target, port, live_port_needed=True)):
827
      raise errors.HypervisorError("Remote host %s not listening on port"
828
                                   " %s, cannot migrate" % (target, port))
829

    
830
    args = ["migrate"]
831

    
832
    if cmd == constants.XEN_CMD_XM:
833
      args.extend(["-p", "%d" % port])
834
      if live:
835
        args.append("-l")
836

    
837
    elif cmd == constants.XEN_CMD_XL:
838
      args.extend([
839
        "-s", constants.XL_SSH_CMD % cluster_name,
840
        "-C", self._ConfigFileName(instance_name),
841
        ])
842

    
843
    else:
844
      raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
845

    
846
    args.extend([instance_name, target])
847

    
848
    result = self._RunXen(args, hvparams)
849
    if result.failed:
850
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
851
                                   (instance_name, result.output))
852

    
853
  def FinalizeMigrationSource(self, instance, success, live):
854
    """Finalize the instance migration on the source node.
855

856
    @type instance: L{objects.Instance}
857
    @param instance: the instance that was migrated
858
    @type success: bool
859
    @param success: whether the migration succeeded or not
860
    @type live: bool
861
    @param live: whether the user requested a live migration or not
862

863
    """
864
    # pylint: disable=W0613
865
    if success:
866
      # remove old xen file after migration succeeded
867
      try:
868
        self._RemoveConfigFile(instance.name)
869
      except EnvironmentError:
870
        logging.exception("Failure while removing instance config file")
871

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

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

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

886
    """
887
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
888

    
889
  def PowercycleNode(self, hvparams=None):
890
    """Xen-specific powercycle.
891

892
    This first does a Linux reboot (which triggers automatically a Xen
893
    reboot), and if that fails it tries to do a Xen reboot. The reason
894
    we don't try a Xen reboot first is that the xen reboot launches an
895
    external command which connects to the Xen hypervisor, and that
896
    won't work in case the root filesystem is broken and/or the xend
897
    daemon is not working.
898

899
    @type hvparams: dict of strings
900
    @param hvparams: hypervisor params to be used on this node
901

902
    """
903
    try:
904
      self.LinuxPowercycle()
905
    finally:
906
      xen_cmd = self._GetCommand(hvparams)
907
      utils.RunCmd([xen_cmd, "debug", "R"])
908

    
909
  def _CheckToolstack(self, xen_cmd):
910
    """Check whether the given toolstack is available on the node.
911

912
    @type xen_cmd: string
913
    @param xen_cmd: xen command (e.g. 'xm' or 'xl')
914

915
    """
916
    binary_found = self._CheckToolstackBinary(xen_cmd)
917
    if not binary_found:
918
      raise errors.HypervisorError("No '%s' binary found on node." % xen_cmd)
919
    elif xen_cmd == constants.XEN_CMD_XL:
920
      if not self._CheckToolstackXlConfigured():
921
        raise errors.HypervisorError("Toolstack '%s' is not enabled on this"
922
                                     "node." % xen_cmd)
923

    
924
  def _CheckToolstackBinary(self, xen_cmd):
925
    """Checks whether the xen command's binary is found on the machine.
926

927
    """
928
    if xen_cmd not in constants.KNOWN_XEN_COMMANDS:
929
      raise errors.HypervisorError("Unknown xen command '%s'." % xen_cmd)
930
    result = self._run_cmd_fn(["which", xen_cmd])
931
    return not result.failed
932

    
933
  def _CheckToolstackXlConfigured(self):
934
    """Checks whether xl is enabled on an xl-capable node.
935

936
    @rtype: bool
937
    @returns: C{True} if 'xl' is enabled, C{False} otherwise
938

939
    """
940
    result = self._run_cmd_fn([constants.XEN_CMD_XL, "help"])
941
    if not result.failed:
942
      return True
943
    elif result.failed:
944
      if "toolstack" in result.stderr:
945
        return False
946
      # xl fails for some other reason than the toolstack
947
      else:
948
        raise errors.HypervisorError("Cannot run xen ('%s'). Error: %s."
949
                                     % (constants.XEN_CMD_XL, result.stderr))
950

    
951

    
952
def WriteXenConfigEvents(config, hvp):
953
  config.write("on_poweroff = 'preserve'\n")
954
  if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
955
    config.write("on_reboot = 'restart'\n")
956
  else:
957
    config.write("on_reboot = 'destroy'\n")
958
  config.write("on_crash = 'restart'\n")
959

    
960

    
961
class XenPvmHypervisor(XenHypervisor):
962
  """Xen PVM hypervisor interface"""
963

    
964
  PARAMETERS = {
965
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
966
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
967
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
968
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
969
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
970
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
971
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
972
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
973
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
974
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
975
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
976
    constants.HV_REBOOT_BEHAVIOR:
977
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
978
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
979
    constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
980
    constants.HV_CPU_WEIGHT:
981
      (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
982
    constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
983
    constants.HV_XEN_CMD:
984
      hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
985
    constants.HV_XEN_CPUID: hv_base.NO_CHECK,
986
    constants.HV_SOUNDHW: hv_base.NO_CHECK,
987
    }
988

    
989
  def _GetConfig(self, instance, startup_memory, block_devices):
990
    """Write the Xen config file for the instance.
991

992
    """
993
    hvp = instance.hvparams
994
    config = StringIO()
995
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
996

    
997
    # if bootloader is True, use bootloader instead of kernel and ramdisk
998
    # parameters.
999
    if hvp[constants.HV_USE_BOOTLOADER]:
1000
      # bootloader handling
1001
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
1002
      if bootloader_path:
1003
        config.write("bootloader = '%s'\n" % bootloader_path)
1004
      else:
1005
        raise errors.HypervisorError("Bootloader enabled, but missing"
1006
                                     " bootloader path")
1007

    
1008
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
1009
      if bootloader_args:
1010
        config.write("bootargs = '%s'\n" % bootloader_args)
1011
    else:
1012
      # kernel handling
1013
      kpath = hvp[constants.HV_KERNEL_PATH]
1014
      config.write("kernel = '%s'\n" % kpath)
1015

    
1016
      # initrd handling
1017
      initrd_path = hvp[constants.HV_INITRD_PATH]
1018
      if initrd_path:
1019
        config.write("ramdisk = '%s'\n" % initrd_path)
1020

    
1021
    # rest of the settings
1022
    config.write("memory = %d\n" % startup_memory)
1023
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1024
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1025
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1026
    if cpu_pinning:
1027
      config.write("%s\n" % cpu_pinning)
1028
    cpu_cap = hvp[constants.HV_CPU_CAP]
1029
    if cpu_cap:
1030
      config.write("cpu_cap=%d\n" % cpu_cap)
1031
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1032
    if cpu_weight:
1033
      config.write("cpu_weight=%d\n" % cpu_weight)
1034

    
1035
    config.write("name = '%s'\n" % instance.name)
1036

    
1037
    vif_data = []
1038
    for idx, nic in enumerate(instance.nics):
1039
      nic_str = "mac=%s" % (nic.mac)
1040
      ip = getattr(nic, "ip", None)
1041
      if ip is not None:
1042
        nic_str += ", ip=%s" % ip
1043
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1044
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1045
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_OVS:
1046
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1047
        if nic.nicparams[constants.NIC_VLAN]:
1048
          nic_str += "%s" % nic.nicparams[constants.NIC_VLAN]
1049
      if hvp[constants.HV_VIF_SCRIPT]:
1050
        nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1051
      vif_data.append("'%s'" % nic_str)
1052
      self._WriteNICInfoFile(instance.name, idx, nic)
1053

    
1054
    disk_data = \
1055
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1056

    
1057
    config.write("vif = [%s]\n" % ",".join(vif_data))
1058
    config.write("disk = [%s]\n" % ",".join(disk_data))
1059

    
1060
    if hvp[constants.HV_ROOT_PATH]:
1061
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
1062

    
1063
    WriteXenConfigEvents(config, hvp)
1064
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
1065

    
1066
    cpuid = hvp[constants.HV_XEN_CPUID]
1067
    if cpuid:
1068
      config.write("cpuid = %s\n" % _QuoteCpuidField(cpuid))
1069

    
1070
    if hvp[constants.HV_SOUNDHW]:
1071
      config.write("soundhw = '%s'\n" % hvp[constants.HV_SOUNDHW])
1072

    
1073
    return config.getvalue()
1074

    
1075

    
1076
class XenHvmHypervisor(XenHypervisor):
1077
  """Xen HVM hypervisor interface"""
1078

    
1079
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
1080
    pathutils.VNC_PASSWORD_FILE,
1081
    ]
1082
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
1083
    pathutils.VNC_PASSWORD_FILE,
1084
    ]
1085

    
1086
  PARAMETERS = {
1087
    constants.HV_ACPI: hv_base.NO_CHECK,
1088
    constants.HV_BOOT_ORDER: (True, ) +
1089
      (lambda x: x and len(x.strip("acdn")) == 0,
1090
       "Invalid boot order specified, must be one or more of [acdn]",
1091
       None, None),
1092
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
1093
    constants.HV_DISK_TYPE:
1094
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
1095
    constants.HV_NIC_TYPE:
1096
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
1097
    constants.HV_PAE: hv_base.NO_CHECK,
1098
    constants.HV_VNC_BIND_ADDRESS:
1099
      (False, netutils.IP4Address.IsValid,
1100
       "VNC bind address is not a valid IP address", None, None),
1101
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
1102
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
1103
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
1104
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
1105
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
1106
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
1107
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
1108
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
1109
    # Add PCI passthrough
1110
    constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
1111
    constants.HV_REBOOT_BEHAVIOR:
1112
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
1113
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
1114
    constants.HV_CPU_CAP: hv_base.NO_CHECK,
1115
    constants.HV_CPU_WEIGHT:
1116
      (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
1117
    constants.HV_VIF_TYPE:
1118
      hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES),
1119
    constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
1120
    constants.HV_VIRIDIAN: hv_base.NO_CHECK,
1121
    constants.HV_XEN_CMD:
1122
      hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
1123
    constants.HV_XEN_CPUID: hv_base.NO_CHECK,
1124
    constants.HV_SOUNDHW: hv_base.NO_CHECK,
1125
    }
1126

    
1127
  def _GetConfig(self, instance, startup_memory, block_devices):
1128
    """Create a Xen 3.1 HVM config file.
1129

1130
    """
1131
    hvp = instance.hvparams
1132

    
1133
    config = StringIO()
1134

    
1135
    # kernel handling
1136
    kpath = hvp[constants.HV_KERNEL_PATH]
1137
    config.write("kernel = '%s'\n" % kpath)
1138

    
1139
    config.write("builder = 'hvm'\n")
1140
    config.write("memory = %d\n" % startup_memory)
1141
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1142
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1143
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1144
    if cpu_pinning:
1145
      config.write("%s\n" % cpu_pinning)
1146
    cpu_cap = hvp[constants.HV_CPU_CAP]
1147
    if cpu_cap:
1148
      config.write("cpu_cap=%d\n" % cpu_cap)
1149
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1150
    if cpu_weight:
1151
      config.write("cpu_weight=%d\n" % cpu_weight)
1152

    
1153
    config.write("name = '%s'\n" % instance.name)
1154
    if hvp[constants.HV_PAE]:
1155
      config.write("pae = 1\n")
1156
    else:
1157
      config.write("pae = 0\n")
1158
    if hvp[constants.HV_ACPI]:
1159
      config.write("acpi = 1\n")
1160
    else:
1161
      config.write("acpi = 0\n")
1162
    if hvp[constants.HV_VIRIDIAN]:
1163
      config.write("viridian = 1\n")
1164
    else:
1165
      config.write("viridian = 0\n")
1166

    
1167
    config.write("apic = 1\n")
1168
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
1169
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
1170
    config.write("sdl = 0\n")
1171
    config.write("usb = 1\n")
1172
    config.write("usbdevice = 'tablet'\n")
1173
    config.write("vnc = 1\n")
1174
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
1175
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
1176
    else:
1177
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
1178

    
1179
    if instance.network_port > constants.VNC_BASE_PORT:
1180
      display = instance.network_port - constants.VNC_BASE_PORT
1181
      config.write("vncdisplay = %s\n" % display)
1182
      config.write("vncunused = 0\n")
1183
    else:
1184
      config.write("# vncdisplay = 1\n")
1185
      config.write("vncunused = 1\n")
1186

    
1187
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
1188
    try:
1189
      password = utils.ReadFile(vnc_pwd_file)
1190
    except EnvironmentError, err:
1191
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1192
                                   (vnc_pwd_file, err))
1193

    
1194
    config.write("vncpasswd = '%s'\n" % password.rstrip())
1195

    
1196
    config.write("serial = 'pty'\n")
1197
    if hvp[constants.HV_USE_LOCALTIME]:
1198
      config.write("localtime = 1\n")
1199

    
1200
    vif_data = []
1201
    # Note: what is called 'nic_type' here, is used as value for the xen nic
1202
    # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1203
    # the 'vif_type' to avoid a clash of notation.
1204
    nic_type = hvp[constants.HV_NIC_TYPE]
1205

    
1206
    if nic_type is None:
1207
      vif_type_str = ""
1208
      if hvp[constants.HV_VIF_TYPE]:
1209
        vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
1210
      # ensure old instances don't change
1211
      nic_type_str = vif_type_str
1212
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
1213
      nic_type_str = ", type=paravirtualized"
1214
    else:
1215
      # parameter 'model' is only valid with type 'ioemu'
1216
      nic_type_str = ", model=%s, type=%s" % \
1217
        (nic_type, constants.HT_HVM_VIF_IOEMU)
1218
    for idx, nic in enumerate(instance.nics):
1219
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1220
      ip = getattr(nic, "ip", None)
1221
      if ip is not None:
1222
        nic_str += ", ip=%s" % ip
1223
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1224
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1225
      if hvp[constants.HV_VIF_SCRIPT]:
1226
        nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1227
      vif_data.append("'%s'" % nic_str)
1228
      self._WriteNICInfoFile(instance.name, idx, nic)
1229

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

    
1232
    disk_data = \
1233
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1234

    
1235
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1236
    if iso_path:
1237
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
1238
      disk_data.append(iso)
1239

    
1240
    config.write("disk = [%s]\n" % (",".join(disk_data)))
1241
    # Add PCI passthrough
1242
    pci_pass_arr = []
1243
    pci_pass = hvp[constants.HV_PASSTHROUGH]
1244
    if pci_pass:
1245
      pci_pass_arr = pci_pass.split(";")
1246
      config.write("pci = %s\n" % pci_pass_arr)
1247

    
1248
    WriteXenConfigEvents(config, hvp)
1249

    
1250
    cpuid = hvp[constants.HV_XEN_CPUID]
1251
    if cpuid:
1252
      config.write("cpuid = %s\n" % _QuoteCpuidField(cpuid))
1253

    
1254
    if hvp[constants.HV_SOUNDHW]:
1255
      config.write("soundhw = '%s'\n" % hvp[constants.HV_SOUNDHW])
1256

    
1257
    return config.getvalue()