Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 6e043e60

History | View | Annotate | Download (29.8 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
from cStringIO import StringIO
28

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

    
38

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

    
45

    
46
class XenHypervisor(hv_base.BaseHypervisor):
47
  """Xen generic hypervisor interface
48

49
  This is the Xen base class used for both Xen PVM and HVM. It contains
50
  all the functionality that is identical for both.
51

52
  """
53
  CAN_MIGRATE = True
54
  REBOOT_RETRY_COUNT = 60
55
  REBOOT_RETRY_INTERVAL = 10
56

    
57
  ANCILLARY_FILES = [
58
    XEND_CONFIG_FILE,
59
    XL_CONFIG_FILE,
60
    VIF_BRIDGE_SCRIPT,
61
    ]
62
  ANCILLARY_FILES_OPT = [
63
    XL_CONFIG_FILE,
64
    ]
65

    
66
  @staticmethod
67
  def _ConfigFileName(instance_name):
68
    """Get the config file name for an instance.
69

70
    @param instance_name: instance name
71
    @type instance_name: str
72
    @return: fully qualified path to instance config file
73
    @rtype: str
74

75
    """
76
    return utils.PathJoin(pathutils.XEN_CONFIG_DIR, instance_name)
77

    
78
  @classmethod
79
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
80
    """Write the Xen config file for the instance.
81

82
    """
83
    raise NotImplementedError
84

    
85
  @staticmethod
86
  def _WriteConfigFileStatic(instance_name, data):
87
    """Write the Xen config file for the instance.
88

89
    This version of the function just writes the config file from static data.
90

91
    """
92
    # just in case it exists
93
    utils.RemoveFile(utils.PathJoin(pathutils.XEN_CONFIG_DIR, "auto",
94
                                    instance_name))
95

    
96
    cfg_file = XenHypervisor._ConfigFileName(instance_name)
97
    try:
98
      utils.WriteFile(cfg_file, data=data)
99
    except EnvironmentError, err:
100
      raise errors.HypervisorError("Cannot write Xen instance configuration"
101
                                   " file %s: %s" % (cfg_file, err))
102

    
103
  @staticmethod
104
  def _ReadConfigFile(instance_name):
105
    """Returns the contents of the instance config file.
106

107
    """
108
    try:
109
      file_content = utils.ReadFile(
110
                       XenHypervisor._ConfigFileName(instance_name))
111
    except EnvironmentError, err:
112
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
113
    return file_content
114

    
115
  @staticmethod
116
  def _RemoveConfigFile(instance_name):
117
    """Remove the xen configuration file.
118

119
    """
120
    utils.RemoveFile(XenHypervisor._ConfigFileName(instance_name))
121

    
122
  @classmethod
123
  def _CreateConfigCpus(cls, cpu_mask):
124
    """Create a CPU config string that's compatible with Xen's
125
    configuration file.
126

127
    """
128
    # Convert the string CPU mask to a list of list of int's
129
    cpu_list = utils.ParseMultiCpuMask(cpu_mask)
130

    
131
    if len(cpu_list) == 1:
132
      all_cpu_mapping = cpu_list[0]
133
      if all_cpu_mapping == constants.CPU_PINNING_OFF:
134
        # If CPU pinning has 1 entry that's "all", then remove the
135
        # parameter from the config file
136
        return None
137
      else:
138
        # If CPU pinning has one non-all entry, mapping all vCPUS (the entire
139
        # VM) to one physical CPU, using format 'cpu = "C"'
140
        return "cpu = \"%s\"" % ",".join(map(str, all_cpu_mapping))
141
    else:
142

    
143
      def _GetCPUMap(vcpu):
144
        if vcpu[0] == constants.CPU_PINNING_ALL_VAL:
145
          cpu_map = constants.CPU_PINNING_ALL_XEN
146
        else:
147
          cpu_map = ",".join(map(str, vcpu))
148
        return "\"%s\"" % cpu_map
149

    
150
      # build the result string in format 'cpus = [ "c", "c", "c" ]',
151
      # where each c is a physical CPU number, a range, a list, or any
152
      # combination
153
      return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list))
154

    
155
  @staticmethod
156
  def _RunXmList(xmlist_errors):
157
    """Helper function for L{_GetXMList} to run "xm list".
158

159
    """
160
    result = utils.RunCmd([constants.XEN_CMD, "list"])
161
    if result.failed:
162
      logging.error("xm list failed (%s): %s", result.fail_reason,
163
                    result.output)
164
      xmlist_errors.append(result)
165
      raise utils.RetryAgain()
166

    
167
    # skip over the heading
168
    return result.stdout.splitlines()[1:]
169

    
170
  @classmethod
171
  def _GetXMList(cls, include_node):
172
    """Return the list of running instances.
173

174
    If the include_node argument is True, then we return information
175
    for dom0 also, otherwise we filter that from the return value.
176

177
    @return: list of (name, id, memory, vcpus, state, time spent)
178

179
    """
180
    xmlist_errors = []
181
    try:
182
      lines = utils.Retry(cls._RunXmList, 1, 5, args=(xmlist_errors, ))
183
    except utils.RetryTimeout:
184
      if xmlist_errors:
185
        xmlist_result = xmlist_errors.pop()
186

    
187
        errmsg = ("xm list failed, timeout exceeded (%s): %s" %
188
                  (xmlist_result.fail_reason, xmlist_result.output))
189
      else:
190
        errmsg = "xm list failed"
191

    
192
      raise errors.HypervisorError(errmsg)
193

    
194
    result = []
195
    for line in lines:
196
      # The format of lines is:
197
      # Name      ID Mem(MiB) VCPUs State  Time(s)
198
      # Domain-0   0  3418     4 r-----    266.2
199
      data = line.split()
200
      if len(data) != 6:
201
        raise errors.HypervisorError("Can't parse output of xm list,"
202
                                     " line: %s" % line)
203
      try:
204
        data[1] = int(data[1])
205
        data[2] = int(data[2])
206
        data[3] = int(data[3])
207
        data[5] = float(data[5])
208
      except (TypeError, ValueError), err:
209
        raise errors.HypervisorError("Can't parse output of xm list,"
210
                                     " line: %s, error: %s" % (line, err))
211

    
212
      # skip the Domain-0 (optional)
213
      if include_node or data[0] != _DOM0_NAME:
214
        result.append(data)
215

    
216
    return result
217

    
218
  def ListInstances(self):
219
    """Get the list of running instances.
220

221
    """
222
    xm_list = self._GetXMList(False)
223
    names = [info[0] for info in xm_list]
224
    return names
225

    
226
  def GetInstanceInfo(self, instance_name):
227
    """Get instance properties.
228

229
    @param instance_name: the instance name
230

231
    @return: tuple (name, id, memory, vcpus, stat, times)
232

233
    """
234
    xm_list = self._GetXMList(instance_name == _DOM0_NAME)
235
    result = None
236
    for data in xm_list:
237
      if data[0] == instance_name:
238
        result = data
239
        break
240
    return result
241

    
242
  def GetAllInstancesInfo(self):
243
    """Get properties of all instances.
244

245
    @return: list of tuples (name, id, memory, vcpus, stat, times)
246

247
    """
248
    xm_list = self._GetXMList(False)
249
    return xm_list
250

    
251
  def StartInstance(self, instance, block_devices, startup_paused):
252
    """Start an instance.
253

254
    """
255
    startup_memory = self._InstanceStartupMemory(instance)
256
    self._WriteConfigFile(instance, startup_memory, block_devices)
257
    cmd = [constants.XEN_CMD, "create"]
258
    if startup_paused:
259
      cmd.extend(["-p"])
260
    cmd.extend([self._ConfigFileName(instance.name)])
261
    result = utils.RunCmd(cmd)
262

    
263
    if result.failed:
264
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
265
                                   (instance.name, result.fail_reason,
266
                                    result.output))
267

    
268
  def StopInstance(self, instance, force=False, retry=False, name=None):
269
    """Stop an instance.
270

271
    """
272
    if name is None:
273
      name = instance.name
274
    self._RemoveConfigFile(name)
275
    if force:
276
      command = [constants.XEN_CMD, "destroy", name]
277
    else:
278
      command = [constants.XEN_CMD, "shutdown", name]
279
    result = utils.RunCmd(command)
280

    
281
    if result.failed:
282
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
283
                                   (name, result.fail_reason, result.output))
284

    
285
  def RebootInstance(self, instance):
286
    """Reboot an instance.
287

288
    """
289
    ini_info = self.GetInstanceInfo(instance.name)
290

    
291
    if ini_info is None:
292
      raise errors.HypervisorError("Failed to reboot instance %s,"
293
                                   " not running" % instance.name)
294

    
295
    result = utils.RunCmd([constants.XEN_CMD, "reboot", instance.name])
296
    if result.failed:
297
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
298
                                   (instance.name, result.fail_reason,
299
                                    result.output))
300

    
301
    def _CheckInstance():
302
      new_info = self.GetInstanceInfo(instance.name)
303

    
304
      # check if the domain ID has changed or the run time has decreased
305
      if (new_info is not None and
306
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
307
        return
308

    
309
      raise utils.RetryAgain()
310

    
311
    try:
312
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
313
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
314
    except utils.RetryTimeout:
315
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
316
                                   " did not reboot in the expected interval" %
317
                                   (instance.name, ))
318

    
319
  def BalloonInstanceMemory(self, instance, mem):
320
    """Balloon an instance memory to a certain value.
321

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

327
    """
328
    cmd = [constants.XEN_CMD, "mem-set", instance.name, mem]
329
    result = utils.RunCmd(cmd)
330
    if result.failed:
331
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
332
                                   (instance.name, result.fail_reason,
333
                                    result.output))
334
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
335
    cmd.append(XenHypervisor._ConfigFileName(instance.name))
336
    result = utils.RunCmd(cmd)
337
    if result.failed:
338
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
339
                                   (instance.name, result.fail_reason,
340
                                    result.output))
341

    
342
  def GetNodeInfo(self):
343
    """Return information about the node.
344

345
    @return: a dict with the following keys (memory values in MiB):
346
          - memory_total: the total memory size on the node
347
          - memory_free: the available memory on the node for instances
348
          - memory_dom0: the memory used by the node itself, if available
349
          - nr_cpus: total number of CPUs
350
          - nr_nodes: in a NUMA system, the number of domains
351
          - nr_sockets: the number of physical CPU sockets in the node
352
          - hv_version: the hypervisor version in the form (major, minor)
353

354
    """
355
    result = utils.RunCmd([constants.XEN_CMD, "info"])
356
    if result.failed:
357
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
358
                    result.output)
359
      return None
360

    
361
    xmoutput = result.stdout.splitlines()
362
    result = {}
363
    cores_per_socket = threads_per_core = nr_cpus = None
364
    xen_major, xen_minor = None, None
365
    memory_total = None
366
    memory_free = None
367

    
368
    for line in xmoutput:
369
      splitfields = line.split(":", 1)
370

    
371
      if len(splitfields) > 1:
372
        key = splitfields[0].strip()
373
        val = splitfields[1].strip()
374

    
375
        # note: in xen 3, memory has changed to total_memory
376
        if key == "memory" or key == "total_memory":
377
          memory_total = int(val)
378
        elif key == "free_memory":
379
          memory_free = int(val)
380
        elif key == "nr_cpus":
381
          nr_cpus = result["cpu_total"] = int(val)
382
        elif key == "nr_nodes":
383
          result["cpu_nodes"] = int(val)
384
        elif key == "cores_per_socket":
385
          cores_per_socket = int(val)
386
        elif key == "threads_per_core":
387
          threads_per_core = int(val)
388
        elif key == "xen_major":
389
          xen_major = int(val)
390
        elif key == "xen_minor":
391
          xen_minor = int(val)
392

    
393
    if None not in [cores_per_socket, threads_per_core, nr_cpus]:
394
      result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
395

    
396
    total_instmem = 0
397
    for (name, _, mem, vcpus, _, _) in self._GetXMList(True):
398
      if name == _DOM0_NAME:
399
        result["memory_dom0"] = mem
400
        result["dom0_cpus"] = vcpus
401

    
402
      # Include Dom0 in total memory usage
403
      total_instmem += mem
404

    
405
    if memory_free is not None:
406
      result["memory_free"] = memory_free
407

    
408
    if memory_total is not None:
409
      result["memory_total"] = memory_total
410

    
411
    # Calculate memory used by hypervisor
412
    if None not in [memory_total, memory_free, total_instmem]:
413
      result["memory_hv"] = memory_total - memory_free - total_instmem
414

    
415
    if not (xen_major is None or xen_minor is None):
416
      result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
417

    
418
    return result
419

    
420
  @classmethod
421
  def GetInstanceConsole(cls, instance, hvparams, beparams):
422
    """Return a command for connecting to the console of an instance.
423

424
    """
425
    return objects.InstanceConsole(instance=instance.name,
426
                                   kind=constants.CONS_SSH,
427
                                   host=instance.primary_node,
428
                                   user=constants.SSH_CONSOLE_USER,
429
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
430
                                            constants.XEN_CMD, instance.name])
431

    
432
  def Verify(self):
433
    """Verify the hypervisor.
434

435
    For Xen, this verifies that the xend process is running.
436

437
    """
438
    result = utils.RunCmd([constants.XEN_CMD, "info"])
439
    if result.failed:
440
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
441

    
442
  @staticmethod
443
  def _GetConfigFileDiskData(block_devices, blockdev_prefix):
444
    """Get disk directive for xen config file.
445

446
    This method builds the xen config disk directive according to the
447
    given disk_template and block_devices.
448

449
    @param block_devices: list of tuples (cfdev, rldev):
450
        - cfdev: dict containing ganeti config disk part
451
        - rldev: ganeti.bdev.BlockDev object
452
    @param blockdev_prefix: a string containing blockdevice prefix,
453
                            e.g. "sd" for /dev/sda
454

455
    @return: string containing disk directive for xen instance config file
456

457
    """
458
    FILE_DRIVER_MAP = {
459
      constants.FD_LOOP: "file",
460
      constants.FD_BLKTAP: "tap:aio",
461
      }
462
    disk_data = []
463
    if len(block_devices) > 24:
464
      # 'z' - 'a' = 24
465
      raise errors.HypervisorError("Too many disks")
466
    namespace = [blockdev_prefix + chr(i + ord("a")) for i in range(24)]
467
    for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
468
      if cfdev.mode == constants.DISK_RDWR:
469
        mode = "w"
470
      else:
471
        mode = "r"
472
      if cfdev.dev_type == constants.LD_FILE:
473
        line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
474
                                  dev_path, sd_name, mode)
475
      else:
476
        line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
477
      disk_data.append(line)
478

    
479
    return disk_data
480

    
481
  def MigrationInfo(self, instance):
482
    """Get instance information to perform a migration.
483

484
    @type instance: L{objects.Instance}
485
    @param instance: instance to be migrated
486
    @rtype: string
487
    @return: content of the xen config file
488

489
    """
490
    return self._ReadConfigFile(instance.name)
491

    
492
  def AcceptInstance(self, instance, info, target):
493
    """Prepare to accept an instance.
494

495
    @type instance: L{objects.Instance}
496
    @param instance: instance to be accepted
497
    @type info: string
498
    @param info: content of the xen config file on the source node
499
    @type target: string
500
    @param target: target host (usually ip), on this node
501

502
    """
503
    pass
504

    
505
  def FinalizeMigrationDst(self, instance, info, success):
506
    """Finalize an instance migration.
507

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

511
    @type instance: L{objects.Instance}
512
    @param instance: instance whose migration is being finalized
513
    @type info: string
514
    @param info: content of the xen config file on the source node
515
    @type success: boolean
516
    @param success: whether the migration was a success or a failure
517

518
    """
519
    if success:
520
      self._WriteConfigFileStatic(instance.name, info)
521

    
522
  def MigrateInstance(self, instance, target, live):
523
    """Migrate an instance to a target node.
524

525
    The migration will not be attempted if the instance is not
526
    currently running.
527

528
    @type instance: L{objects.Instance}
529
    @param instance: the instance to be migrated
530
    @type target: string
531
    @param target: ip address of the target node
532
    @type live: boolean
533
    @param live: perform a live migration
534

535
    """
536
    if self.GetInstanceInfo(instance.name) is None:
537
      raise errors.HypervisorError("Instance not running, cannot migrate")
538

    
539
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
540

    
541
    if (constants.XEN_CMD == constants.XEN_CMD_XM and
542
        not netutils.TcpPing(target, port, live_port_needed=True)):
543
      raise errors.HypervisorError("Remote host %s not listening on port"
544
                                   " %s, cannot migrate" % (target, port))
545

    
546
    args = [constants.XEN_CMD, "migrate"]
547
    if constants.XEN_CMD == constants.XEN_CMD_XM:
548
      args.extend(["-p", "%d" % port])
549
      if live:
550
        args.append("-l")
551
    elif constants.XEN_CMD == constants.XEN_CMD_XL:
552
      cluster_name = ssconf.SimpleStore().GetClusterName()
553
      args.extend(["-s", constants.XL_SSH_CMD % cluster_name])
554
      args.extend(["-C", self._ConfigFileName(instance.name)])
555
    else:
556
      raise errors.HypervisorError("Unsupported xen command: %s" %
557
                                   constants.XEN_CMD)
558

    
559
    args.extend([instance.name, target])
560
    result = utils.RunCmd(args)
561
    if result.failed:
562
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
563
                                   (instance.name, result.output))
564

    
565
  def FinalizeMigrationSource(self, instance, success, live):
566
    """Finalize the instance migration on the source node.
567

568
    @type instance: L{objects.Instance}
569
    @param instance: the instance that was migrated
570
    @type success: bool
571
    @param success: whether the migration succeeded or not
572
    @type live: bool
573
    @param live: whether the user requested a live migration or not
574

575
    """
576
    # pylint: disable=W0613
577
    if success:
578
      # remove old xen file after migration succeeded
579
      try:
580
        self._RemoveConfigFile(instance.name)
581
      except EnvironmentError:
582
        logging.exception("Failure while removing instance config file")
583

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

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

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

598
    """
599
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
600

    
601
  @classmethod
602
  def PowercycleNode(cls):
603
    """Xen-specific powercycle.
604

605
    This first does a Linux reboot (which triggers automatically a Xen
606
    reboot), and if that fails it tries to do a Xen reboot. The reason
607
    we don't try a Xen reboot first is that the xen reboot launches an
608
    external command which connects to the Xen hypervisor, and that
609
    won't work in case the root filesystem is broken and/or the xend
610
    daemon is not working.
611

612
    """
613
    try:
614
      cls.LinuxPowercycle()
615
    finally:
616
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
617

    
618

    
619
class XenPvmHypervisor(XenHypervisor):
620
  """Xen PVM hypervisor interface"""
621

    
622
  PARAMETERS = {
623
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
624
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
625
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
626
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
627
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
628
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
629
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
630
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
631
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
632
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
633
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
634
    constants.HV_REBOOT_BEHAVIOR:
635
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
636
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
637
    constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
638
    constants.HV_CPU_WEIGHT:
639
      (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
640
    }
641

    
642
  @classmethod
643
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
644
    """Write the Xen config file for the instance.
645

646
    """
647
    hvp = instance.hvparams
648
    config = StringIO()
649
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
650

    
651
    # if bootloader is True, use bootloader instead of kernel and ramdisk
652
    # parameters.
653
    if hvp[constants.HV_USE_BOOTLOADER]:
654
      # bootloader handling
655
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
656
      if bootloader_path:
657
        config.write("bootloader = '%s'\n" % bootloader_path)
658
      else:
659
        raise errors.HypervisorError("Bootloader enabled, but missing"
660
                                     " bootloader path")
661

    
662
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
663
      if bootloader_args:
664
        config.write("bootargs = '%s'\n" % bootloader_args)
665
    else:
666
      # kernel handling
667
      kpath = hvp[constants.HV_KERNEL_PATH]
668
      config.write("kernel = '%s'\n" % kpath)
669

    
670
      # initrd handling
671
      initrd_path = hvp[constants.HV_INITRD_PATH]
672
      if initrd_path:
673
        config.write("ramdisk = '%s'\n" % initrd_path)
674

    
675
    # rest of the settings
676
    config.write("memory = %d\n" % startup_memory)
677
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
678
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
679
    cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK])
680
    if cpu_pinning:
681
      config.write("%s\n" % cpu_pinning)
682
    cpu_cap = hvp[constants.HV_CPU_CAP]
683
    if cpu_cap:
684
      config.write("cpu_cap=%d\n" % cpu_cap)
685
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
686
    if cpu_weight:
687
      config.write("cpu_weight=%d\n" % cpu_weight)
688

    
689
    config.write("name = '%s'\n" % instance.name)
690

    
691
    vif_data = []
692
    for nic in instance.nics:
693
      nic_str = "mac=%s" % (nic.mac)
694
      ip = getattr(nic, "ip", None)
695
      if ip is not None:
696
        nic_str += ", ip=%s" % ip
697
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
698
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
699
      vif_data.append("'%s'" % nic_str)
700

    
701
    disk_data = cls._GetConfigFileDiskData(block_devices,
702
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
703

    
704
    config.write("vif = [%s]\n" % ",".join(vif_data))
705
    config.write("disk = [%s]\n" % ",".join(disk_data))
706

    
707
    if hvp[constants.HV_ROOT_PATH]:
708
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
709
    config.write("on_poweroff = 'destroy'\n")
710
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
711
      config.write("on_reboot = 'restart'\n")
712
    else:
713
      config.write("on_reboot = 'destroy'\n")
714
    config.write("on_crash = 'restart'\n")
715
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
716
    cls._WriteConfigFileStatic(instance.name, config.getvalue())
717

    
718
    return True
719

    
720

    
721
class XenHvmHypervisor(XenHypervisor):
722
  """Xen HVM hypervisor interface"""
723

    
724
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
725
    pathutils.VNC_PASSWORD_FILE,
726
    ]
727
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
728
    pathutils.VNC_PASSWORD_FILE,
729
    ]
730

    
731
  PARAMETERS = {
732
    constants.HV_ACPI: hv_base.NO_CHECK,
733
    constants.HV_BOOT_ORDER: (True, ) +
734
      (lambda x: x and len(x.strip("acdn")) == 0,
735
       "Invalid boot order specified, must be one or more of [acdn]",
736
       None, None),
737
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
738
    constants.HV_DISK_TYPE:
739
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
740
    constants.HV_NIC_TYPE:
741
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
742
    constants.HV_PAE: hv_base.NO_CHECK,
743
    constants.HV_VNC_BIND_ADDRESS:
744
      (False, netutils.IP4Address.IsValid,
745
       "VNC bind address is not a valid IP address", None, None),
746
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
747
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
748
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
749
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
750
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
751
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
752
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
753
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
754
    # Add PCI passthrough
755
    constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
756
    constants.HV_REBOOT_BEHAVIOR:
757
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
758
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
759
    constants.HV_CPU_CAP: hv_base.NO_CHECK,
760
    constants.HV_CPU_WEIGHT:
761
      (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
762
    }
763

    
764
  @classmethod
765
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
766
    """Create a Xen 3.1 HVM config file.
767

768
    """
769
    hvp = instance.hvparams
770

    
771
    config = StringIO()
772
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
773

    
774
    # kernel handling
775
    kpath = hvp[constants.HV_KERNEL_PATH]
776
    config.write("kernel = '%s'\n" % kpath)
777

    
778
    config.write("builder = 'hvm'\n")
779
    config.write("memory = %d\n" % startup_memory)
780
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
781
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
782
    cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK])
783
    if cpu_pinning:
784
      config.write("%s\n" % cpu_pinning)
785
    cpu_cap = hvp[constants.HV_CPU_CAP]
786
    if cpu_cap:
787
      config.write("cpu_cap=%d\n" % cpu_cap)
788
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
789
    if cpu_weight:
790
      config.write("cpu_weight=%d\n" % cpu_weight)
791

    
792
    config.write("name = '%s'\n" % instance.name)
793
    if hvp[constants.HV_PAE]:
794
      config.write("pae = 1\n")
795
    else:
796
      config.write("pae = 0\n")
797
    if hvp[constants.HV_ACPI]:
798
      config.write("acpi = 1\n")
799
    else:
800
      config.write("acpi = 0\n")
801
    config.write("apic = 1\n")
802
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
803
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
804
    config.write("sdl = 0\n")
805
    config.write("usb = 1\n")
806
    config.write("usbdevice = 'tablet'\n")
807
    config.write("vnc = 1\n")
808
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
809
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
810
    else:
811
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
812

    
813
    if instance.network_port > constants.VNC_BASE_PORT:
814
      display = instance.network_port - constants.VNC_BASE_PORT
815
      config.write("vncdisplay = %s\n" % display)
816
      config.write("vncunused = 0\n")
817
    else:
818
      config.write("# vncdisplay = 1\n")
819
      config.write("vncunused = 1\n")
820

    
821
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
822
    try:
823
      password = utils.ReadFile(vnc_pwd_file)
824
    except EnvironmentError, err:
825
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
826
                                   (vnc_pwd_file, err))
827

    
828
    config.write("vncpasswd = '%s'\n" % password.rstrip())
829

    
830
    config.write("serial = 'pty'\n")
831
    if hvp[constants.HV_USE_LOCALTIME]:
832
      config.write("localtime = 1\n")
833

    
834
    vif_data = []
835
    nic_type = hvp[constants.HV_NIC_TYPE]
836
    if nic_type is None:
837
      # ensure old instances don't change
838
      nic_type_str = ", type=ioemu"
839
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
840
      nic_type_str = ", type=paravirtualized"
841
    else:
842
      nic_type_str = ", model=%s, type=ioemu" % nic_type
843
    for nic in instance.nics:
844
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
845
      ip = getattr(nic, "ip", None)
846
      if ip is not None:
847
        nic_str += ", ip=%s" % ip
848
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
849
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
850
      vif_data.append("'%s'" % nic_str)
851

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

    
854
    disk_data = cls._GetConfigFileDiskData(block_devices,
855
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
856

    
857
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
858
    if iso_path:
859
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
860
      disk_data.append(iso)
861

    
862
    config.write("disk = [%s]\n" % (",".join(disk_data)))
863
    # Add PCI passthrough
864
    pci_pass_arr = []
865
    pci_pass = hvp[constants.HV_PASSTHROUGH]
866
    if pci_pass:
867
      pci_pass_arr = pci_pass.split(";")
868
      config.write("pci = %s\n" % pci_pass_arr)
869
    config.write("on_poweroff = 'destroy'\n")
870
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
871
      config.write("on_reboot = 'restart'\n")
872
    else:
873
      config.write("on_reboot = 'destroy'\n")
874
    config.write("on_crash = 'restart'\n")
875
    cls._WriteConfigFileStatic(instance.name, config.getvalue())
876

    
877
    return True