Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 3891c95e

History | View | Annotate | Download (28.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

    
36

    
37
XEND_CONFIG_FILE = "/etc/xen/xend-config.sxp"
38
XL_CONFIG_FILE = "/etc/xen/xl.conf"
39
VIF_BRIDGE_SCRIPT = "/etc/xen/scripts/vif-bridge"
40
_DOM0_NAME = "Domain-0"
41

    
42

    
43
class XenHypervisor(hv_base.BaseHypervisor):
44
  """Xen generic hypervisor interface
45

46
  This is the Xen base class used for both Xen PVM and HVM. It contains
47
  all the functionality that is identical for both.
48

49
  """
50
  CAN_MIGRATE = True
51
  REBOOT_RETRY_COUNT = 60
52
  REBOOT_RETRY_INTERVAL = 10
53

    
54
  ANCILLARY_FILES = [
55
    XEND_CONFIG_FILE,
56
    XL_CONFIG_FILE,
57
    VIF_BRIDGE_SCRIPT,
58
    ]
59
  ANCILLARY_FILES_OPT = [
60
    XL_CONFIG_FILE,
61
    ]
62

    
63
  @staticmethod
64
  def _ConfigFileName(instance_name):
65
    """Get the config file name for an instance.
66

67
    @param instance_name: instance name
68
    @type instance_name: str
69
    @return: fully qualified path to instance config file
70
    @rtype: str
71

72
    """
73
    return "/etc/xen/%s" % instance_name
74

    
75
  @classmethod
76
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
77
    """Write the Xen config file for the instance.
78

79
    """
80
    raise NotImplementedError
81

    
82
  @staticmethod
83
  def _WriteConfigFileStatic(instance_name, data):
84
    """Write the Xen config file for the instance.
85

86
    This version of the function just writes the config file from static data.
87

88
    """
89
    # just in case it exists
90
    utils.RemoveFile("/etc/xen/auto/%s" % instance_name)
91
    cfg_file = XenHypervisor._ConfigFileName(instance_name)
92
    try:
93
      utils.WriteFile(cfg_file, data=data)
94
    except EnvironmentError, err:
95
      raise errors.HypervisorError("Cannot write Xen instance configuration"
96
                                   " file %s: %s" % (cfg_file, err))
97

    
98
  @staticmethod
99
  def _ReadConfigFile(instance_name):
100
    """Returns the contents of the instance config file.
101

102
    """
103
    try:
104
      file_content = utils.ReadFile(
105
                       XenHypervisor._ConfigFileName(instance_name))
106
    except EnvironmentError, err:
107
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
108
    return file_content
109

    
110
  @staticmethod
111
  def _RemoveConfigFile(instance_name):
112
    """Remove the xen configuration file.
113

114
    """
115
    utils.RemoveFile(XenHypervisor._ConfigFileName(instance_name))
116

    
117
  @classmethod
118
  def _CreateConfigCpus(cls, cpu_mask):
119
    """Create a CPU config string that's compatible with Xen's
120
    configuration file.
121

122
    """
123
    # Convert the string CPU mask to a list of list of int's
124
    cpu_list = utils.ParseMultiCpuMask(cpu_mask)
125

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

    
144
      # build the result string in format 'cpus = [ "c", "c", "c" ]',
145
      # where each c is a physical CPU number, a range, a list, or any
146
      # combination
147
      return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list))
148

    
149
  @staticmethod
150
  def _RunXmList(xmlist_errors):
151
    """Helper function for L{_GetXMList} to run "xm list".
152

153
    """
154
    result = utils.RunCmd([constants.XEN_CMD, "list"])
155
    if result.failed:
156
      logging.error("xm list failed (%s): %s", result.fail_reason,
157
                    result.output)
158
      xmlist_errors.append(result)
159
      raise utils.RetryAgain()
160

    
161
    # skip over the heading
162
    return result.stdout.splitlines()[1:]
163

    
164
  @classmethod
165
  def _GetXMList(cls, include_node):
166
    """Return the list of running instances.
167

168
    If the include_node argument is True, then we return information
169
    for dom0 also, otherwise we filter that from the return value.
170

171
    @return: list of (name, id, memory, vcpus, state, time spent)
172

173
    """
174
    xmlist_errors = []
175
    try:
176
      lines = utils.Retry(cls._RunXmList, 1, 5, args=(xmlist_errors, ))
177
    except utils.RetryTimeout:
178
      if xmlist_errors:
179
        xmlist_result = xmlist_errors.pop()
180

    
181
        errmsg = ("xm list failed, timeout exceeded (%s): %s" %
182
                  (xmlist_result.fail_reason, xmlist_result.output))
183
      else:
184
        errmsg = "xm list failed"
185

    
186
      raise errors.HypervisorError(errmsg)
187

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

    
206
      # skip the Domain-0 (optional)
207
      if include_node or data[0] != _DOM0_NAME:
208
        result.append(data)
209

    
210
    return result
211

    
212
  def ListInstances(self):
213
    """Get the list of running instances.
214

215
    """
216
    xm_list = self._GetXMList(False)
217
    names = [info[0] for info in xm_list]
218
    return names
219

    
220
  def GetInstanceInfo(self, instance_name):
221
    """Get instance properties.
222

223
    @param instance_name: the instance name
224

225
    @return: tuple (name, id, memory, vcpus, stat, times)
226

227
    """
228
    xm_list = self._GetXMList(instance_name == _DOM0_NAME)
229
    result = None
230
    for data in xm_list:
231
      if data[0] == instance_name:
232
        result = data
233
        break
234
    return result
235

    
236
  def GetAllInstancesInfo(self):
237
    """Get properties of all instances.
238

239
    @return: list of tuples (name, id, memory, vcpus, stat, times)
240

241
    """
242
    xm_list = self._GetXMList(False)
243
    return xm_list
244

    
245
  def StartInstance(self, instance, block_devices, startup_paused):
246
    """Start an instance.
247

248
    """
249
    startup_memory = self._InstanceStartupMemory(instance)
250
    self._WriteConfigFile(instance, startup_memory, block_devices)
251
    cmd = [constants.XEN_CMD, "create"]
252
    if startup_paused:
253
      cmd.extend(["-p"])
254
    cmd.extend([self._ConfigFileName(instance.name)])
255
    result = utils.RunCmd(cmd)
256

    
257
    if result.failed:
258
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
259
                                   (instance.name, result.fail_reason,
260
                                    result.output))
261

    
262
  def StopInstance(self, instance, force=False, retry=False, name=None):
263
    """Stop an instance.
264

265
    """
266
    if name is None:
267
      name = instance.name
268
    self._RemoveConfigFile(name)
269
    if force:
270
      command = [constants.XEN_CMD, "destroy", name]
271
    else:
272
      command = [constants.XEN_CMD, "shutdown", name]
273
    result = utils.RunCmd(command)
274

    
275
    if result.failed:
276
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
277
                                   (name, result.fail_reason, result.output))
278

    
279
  def RebootInstance(self, instance):
280
    """Reboot an instance.
281

282
    """
283
    ini_info = self.GetInstanceInfo(instance.name)
284

    
285
    if ini_info is None:
286
      raise errors.HypervisorError("Failed to reboot instance %s,"
287
                                   " not running" % instance.name)
288

    
289
    result = utils.RunCmd([constants.XEN_CMD, "reboot", instance.name])
290
    if result.failed:
291
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
292
                                   (instance.name, result.fail_reason,
293
                                    result.output))
294

    
295
    def _CheckInstance():
296
      new_info = self.GetInstanceInfo(instance.name)
297

    
298
      # check if the domain ID has changed or the run time has decreased
299
      if (new_info is not None and
300
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
301
        return
302

    
303
      raise utils.RetryAgain()
304

    
305
    try:
306
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
307
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
308
    except utils.RetryTimeout:
309
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
310
                                   " did not reboot in the expected interval" %
311
                                   (instance.name, ))
312

    
313
  def BalloonInstanceMemory(self, instance, mem):
314
    """Balloon an instance memory to a certain value.
315

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

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

    
336
  def GetNodeInfo(self):
337
    """Return information about the node.
338

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

348
    """
349
    result = utils.RunCmd([constants.XEN_CMD, "info"])
350
    if result.failed:
351
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
352
                    result.output)
353
      return None
354

    
355
    xmoutput = result.stdout.splitlines()
356
    result = {}
357
    cores_per_socket = threads_per_core = nr_cpus = None
358
    xen_major, xen_minor = None, None
359
    memory_total = None
360
    memory_free = None
361

    
362
    for line in xmoutput:
363
      splitfields = line.split(":", 1)
364

    
365
      if len(splitfields) > 1:
366
        key = splitfields[0].strip()
367
        val = splitfields[1].strip()
368

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

    
387
    if None not in [cores_per_socket, threads_per_core, nr_cpus]:
388
      result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
389

    
390
    total_instmem = 0
391
    for (name, _, mem, vcpus, _, _) in self._GetXMList(True):
392
      if name == _DOM0_NAME:
393
        result["memory_dom0"] = mem
394
        result["dom0_cpus"] = vcpus
395

    
396
      # Include Dom0 in total memory usage
397
      total_instmem += mem
398

    
399
    if memory_free is not None:
400
      result["memory_free"] = memory_free
401

    
402
    if memory_total is not None:
403
      result["memory_total"] = memory_total
404

    
405
    # Calculate memory used by hypervisor
406
    if None not in [memory_total, memory_free, total_instmem]:
407
      result["memory_hv"] = memory_total - memory_free - total_instmem
408

    
409
    if not (xen_major is None or xen_minor is None):
410
      result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
411

    
412
    return result
413

    
414
  @classmethod
415
  def GetInstanceConsole(cls, instance, hvparams, beparams):
416
    """Return a command for connecting to the console of an instance.
417

418
    """
419
    return objects.InstanceConsole(instance=instance.name,
420
                                   kind=constants.CONS_SSH,
421
                                   host=instance.primary_node,
422
                                   user=constants.GANETI_RUNAS,
423
                                   command=[constants.XM_CONSOLE_WRAPPER,
424
                                            instance.name])
425

    
426
  def Verify(self):
427
    """Verify the hypervisor.
428

429
    For Xen, this verifies that the xend process is running.
430

431
    """
432
    result = utils.RunCmd([constants.XEN_CMD, "info"])
433
    if result.failed:
434
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
435

    
436
  @staticmethod
437
  def _GetConfigFileDiskData(block_devices, blockdev_prefix):
438
    """Get disk directive for xen config file.
439

440
    This method builds the xen config disk directive according to the
441
    given disk_template and block_devices.
442

443
    @param block_devices: list of tuples (cfdev, rldev):
444
        - cfdev: dict containing ganeti config disk part
445
        - rldev: ganeti.bdev.BlockDev object
446
    @param blockdev_prefix: a string containing blockdevice prefix,
447
                            e.g. "sd" for /dev/sda
448

449
    @return: string containing disk directive for xen instance config file
450

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

    
473
    return disk_data
474

    
475
  def MigrationInfo(self, instance):
476
    """Get instance information to perform a migration.
477

478
    @type instance: L{objects.Instance}
479
    @param instance: instance to be migrated
480
    @rtype: string
481
    @return: content of the xen config file
482

483
    """
484
    return self._ReadConfigFile(instance.name)
485

    
486
  def AcceptInstance(self, instance, info, target):
487
    """Prepare to accept an instance.
488

489
    @type instance: L{objects.Instance}
490
    @param instance: instance to be accepted
491
    @type info: string
492
    @param info: content of the xen config file on the source node
493
    @type target: string
494
    @param target: target host (usually ip), on this node
495

496
    """
497
    pass
498

    
499
  def FinalizeMigrationDst(self, instance, info, success):
500
    """Finalize an instance migration.
501

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

505
    @type instance: L{objects.Instance}
506
    @param instance: instance whose migration is being finalized
507
    @type info: string
508
    @param info: content of the xen config file on the source node
509
    @type success: boolean
510
    @param success: whether the migration was a success or a failure
511

512
    """
513
    if success:
514
      self._WriteConfigFileStatic(instance.name, info)
515

    
516
  def MigrateInstance(self, instance, target, live):
517
    """Migrate an instance to a target node.
518

519
    The migration will not be attempted if the instance is not
520
    currently running.
521

522
    @type instance: L{objects.Instance}
523
    @param instance: the instance to be migrated
524
    @type target: string
525
    @param target: ip address of the target node
526
    @type live: boolean
527
    @param live: perform a live migration
528

529
    """
530
    if self.GetInstanceInfo(instance.name) is None:
531
      raise errors.HypervisorError("Instance not running, cannot migrate")
532

    
533
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
534

    
535
    if not netutils.TcpPing(target, port, live_port_needed=True):
536
      raise errors.HypervisorError("Remote host %s not listening on port"
537
                                   " %s, cannot migrate" % (target, port))
538

    
539
    # FIXME: migrate must be upgraded for transitioning to "xl" (xen 4.1).
540
    #        This should be reworked in Ganeti 2.7
541
    #  ssh must recognize the key of the target host for the migration
542
    args = [constants.XEN_CMD, "migrate"]
543
    if constants.XEN_CMD == constants.XEN_CMD_XM:
544
      args.extend(["-p", "%d" % port])
545
      if live:
546
        args.append("-l")
547
    elif constants.XEN_CMD == constants.XEN_CMD_XL:
548
      args.extend(["-C", self._ConfigFileName(instance.name)])
549
    else:
550
      raise errors.HypervisorError("Unsupported xen command: %s" %
551
                                   constants.XEN_CMD)
552

    
553
    args.extend([instance.name, target])
554
    result = utils.RunCmd(args)
555
    if result.failed:
556
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
557
                                   (instance.name, result.output))
558

    
559
  def FinalizeMigrationSource(self, instance, success, live):
560
    """Finalize the instance migration on the source node.
561

562
    @type instance: L{objects.Instance}
563
    @param instance: the instance that was migrated
564
    @type success: bool
565
    @param success: whether the migration succeeded or not
566
    @type live: bool
567
    @param live: whether the user requested a live migration or not
568

569
    """
570
    # pylint: disable=W0613
571
    if success:
572
      # remove old xen file after migration succeeded
573
      try:
574
        self._RemoveConfigFile(instance.name)
575
      except EnvironmentError:
576
        logging.exception("Failure while removing instance config file")
577

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

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

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

592
    """
593
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
594

    
595
  @classmethod
596
  def PowercycleNode(cls):
597
    """Xen-specific powercycle.
598

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

606
    """
607
    try:
608
      cls.LinuxPowercycle()
609
    finally:
610
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
611

    
612

    
613
class XenPvmHypervisor(XenHypervisor):
614
  """Xen PVM hypervisor interface"""
615

    
616
  PARAMETERS = {
617
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
618
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
619
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
620
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
621
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
622
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
623
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
624
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
625
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
626
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
627
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
628
    constants.HV_REBOOT_BEHAVIOR:
629
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
630
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
631
    }
632

    
633
  @classmethod
634
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
635
    """Write the Xen config file for the instance.
636

637
    """
638
    hvp = instance.hvparams
639
    config = StringIO()
640
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
641

    
642
    # if bootloader is True, use bootloader instead of kernel and ramdisk
643
    # parameters.
644
    if hvp[constants.HV_USE_BOOTLOADER]:
645
      # bootloader handling
646
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
647
      if bootloader_path:
648
        config.write("bootloader = '%s'\n" % bootloader_path)
649
      else:
650
        raise errors.HypervisorError("Bootloader enabled, but missing"
651
                                     " bootloader path")
652

    
653
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
654
      if bootloader_args:
655
        config.write("bootargs = '%s'\n" % bootloader_args)
656
    else:
657
      # kernel handling
658
      kpath = hvp[constants.HV_KERNEL_PATH]
659
      config.write("kernel = '%s'\n" % kpath)
660

    
661
      # initrd handling
662
      initrd_path = hvp[constants.HV_INITRD_PATH]
663
      if initrd_path:
664
        config.write("ramdisk = '%s'\n" % initrd_path)
665

    
666
    # rest of the settings
667
    config.write("memory = %d\n" % startup_memory)
668
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
669
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
670
    cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK])
671
    if cpu_pinning:
672
      config.write("%s\n" % cpu_pinning)
673

    
674
    config.write("name = '%s'\n" % instance.name)
675

    
676
    vif_data = []
677
    for nic in instance.nics:
678
      nic_str = "mac=%s" % (nic.mac)
679
      ip = getattr(nic, "ip", None)
680
      if ip is not None:
681
        nic_str += ", ip=%s" % ip
682
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
683
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
684
      vif_data.append("'%s'" % nic_str)
685

    
686
    disk_data = cls._GetConfigFileDiskData(block_devices,
687
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
688

    
689
    config.write("vif = [%s]\n" % ",".join(vif_data))
690
    config.write("disk = [%s]\n" % ",".join(disk_data))
691

    
692
    if hvp[constants.HV_ROOT_PATH]:
693
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
694
    config.write("on_poweroff = 'destroy'\n")
695
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
696
      config.write("on_reboot = 'restart'\n")
697
    else:
698
      config.write("on_reboot = 'destroy'\n")
699
    config.write("on_crash = 'restart'\n")
700
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
701
    cls._WriteConfigFileStatic(instance.name, config.getvalue())
702

    
703
    return True
704

    
705

    
706
class XenHvmHypervisor(XenHypervisor):
707
  """Xen HVM hypervisor interface"""
708

    
709
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
710
    constants.VNC_PASSWORD_FILE,
711
    ]
712
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
713
    constants.VNC_PASSWORD_FILE,
714
    ]
715

    
716
  PARAMETERS = {
717
    constants.HV_ACPI: hv_base.NO_CHECK,
718
    constants.HV_BOOT_ORDER: (True, ) +
719
      (lambda x: x and len(x.strip("acdn")) == 0,
720
       "Invalid boot order specified, must be one or more of [acdn]",
721
       None, None),
722
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
723
    constants.HV_DISK_TYPE:
724
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
725
    constants.HV_NIC_TYPE:
726
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
727
    constants.HV_PAE: hv_base.NO_CHECK,
728
    constants.HV_VNC_BIND_ADDRESS:
729
      (False, netutils.IP4Address.IsValid,
730
       "VNC bind address is not a valid IP address", None, None),
731
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
732
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
733
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
734
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
735
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
736
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
737
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
738
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
739
    # Add PCI passthrough
740
    constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
741
    constants.HV_REBOOT_BEHAVIOR:
742
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
743
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
744
    }
745

    
746
  @classmethod
747
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
748
    """Create a Xen 3.1 HVM config file.
749

750
    """
751
    hvp = instance.hvparams
752

    
753
    config = StringIO()
754
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
755

    
756
    # kernel handling
757
    kpath = hvp[constants.HV_KERNEL_PATH]
758
    config.write("kernel = '%s'\n" % kpath)
759

    
760
    config.write("builder = 'hvm'\n")
761
    config.write("memory = %d\n" % startup_memory)
762
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
763
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
764
    cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK])
765
    if cpu_pinning:
766
      config.write("%s\n" % cpu_pinning)
767

    
768
    config.write("name = '%s'\n" % instance.name)
769
    if hvp[constants.HV_PAE]:
770
      config.write("pae = 1\n")
771
    else:
772
      config.write("pae = 0\n")
773
    if hvp[constants.HV_ACPI]:
774
      config.write("acpi = 1\n")
775
    else:
776
      config.write("acpi = 0\n")
777
    config.write("apic = 1\n")
778
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
779
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
780
    config.write("sdl = 0\n")
781
    config.write("usb = 1\n")
782
    config.write("usbdevice = 'tablet'\n")
783
    config.write("vnc = 1\n")
784
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
785
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
786
    else:
787
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
788

    
789
    if instance.network_port > constants.VNC_BASE_PORT:
790
      display = instance.network_port - constants.VNC_BASE_PORT
791
      config.write("vncdisplay = %s\n" % display)
792
      config.write("vncunused = 0\n")
793
    else:
794
      config.write("# vncdisplay = 1\n")
795
      config.write("vncunused = 1\n")
796

    
797
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
798
    try:
799
      password = utils.ReadFile(vnc_pwd_file)
800
    except EnvironmentError, err:
801
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
802
                                   (vnc_pwd_file, err))
803

    
804
    config.write("vncpasswd = '%s'\n" % password.rstrip())
805

    
806
    config.write("serial = 'pty'\n")
807
    if hvp[constants.HV_USE_LOCALTIME]:
808
      config.write("localtime = 1\n")
809

    
810
    vif_data = []
811
    nic_type = hvp[constants.HV_NIC_TYPE]
812
    if nic_type is None:
813
      # ensure old instances don't change
814
      nic_type_str = ", type=ioemu"
815
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
816
      nic_type_str = ", type=paravirtualized"
817
    else:
818
      nic_type_str = ", model=%s, type=ioemu" % nic_type
819
    for nic in instance.nics:
820
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
821
      ip = getattr(nic, "ip", None)
822
      if ip is not None:
823
        nic_str += ", ip=%s" % ip
824
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
825
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
826
      vif_data.append("'%s'" % nic_str)
827

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

    
830
    disk_data = cls._GetConfigFileDiskData(block_devices,
831
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
832

    
833
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
834
    if iso_path:
835
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
836
      disk_data.append(iso)
837

    
838
    config.write("disk = [%s]\n" % (",".join(disk_data)))
839
    # Add PCI passthrough
840
    pci_pass_arr = []
841
    pci_pass = hvp[constants.HV_PASSTHROUGH]
842
    if pci_pass:
843
      pci_pass_arr = pci_pass.split(";")
844
      config.write("pci = %s\n" % pci_pass_arr)
845
    config.write("on_poweroff = 'destroy'\n")
846
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
847
      config.write("on_reboot = 'restart'\n")
848
    else:
849
      config.write("on_reboot = 'destroy'\n")
850
    config.write("on_crash = 'restart'\n")
851
    cls._WriteConfigFileStatic(instance.name, config.getvalue())
852

    
853
    return True