Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 1d60fec6

History | View | Annotate | Download (27.1 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010 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, 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
    utils.WriteFile(XenHypervisor._ConfigFileName(instance_name), data=data)
90

    
91
  @staticmethod
92
  def _ReadConfigFile(instance_name):
93
    """Returns the contents of the instance config file.
94

95
    """
96
    try:
97
      file_content = utils.ReadFile(
98
                       XenHypervisor._ConfigFileName(instance_name))
99
    except EnvironmentError, err:
100
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
101
    return file_content
102

    
103
  @staticmethod
104
  def _RemoveConfigFile(instance_name):
105
    """Remove the xen configuration file.
106

107
    """
108
    utils.RemoveFile(XenHypervisor._ConfigFileName(instance_name))
109

    
110
  @classmethod
111
  def _CreateConfigCpus(cls, cpu_mask):
112
    """Create a CPU config string that's compatible with Xen's
113
    configuration file.
114

115
    """
116
    # Convert the string CPU mask to a list of list of int's
117
    cpu_list = utils.ParseMultiCpuMask(cpu_mask)
118

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

    
137
      # build the result string in format 'cpus = [ "c", "c", "c" ]',
138
      # where each c is a physical CPU number, a range, a list, or any
139
      # combination
140
      return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list))
141

    
142
  @staticmethod
143
  def _RunXmList(xmlist_errors):
144
    """Helper function for L{_GetXMList} to run "xm list".
145

146
    """
147
    result = utils.RunCmd([constants.XEN_CMD, "list"])
148
    if result.failed:
149
      logging.error("xm list failed (%s): %s", result.fail_reason,
150
                    result.output)
151
      xmlist_errors.append(result)
152
      raise utils.RetryAgain()
153

    
154
    # skip over the heading
155
    return result.stdout.splitlines()[1:]
156

    
157
  @classmethod
158
  def _GetXMList(cls, include_node):
159
    """Return the list of running instances.
160

161
    If the include_node argument is True, then we return information
162
    for dom0 also, otherwise we filter that from the return value.
163

164
    @return: list of (name, id, memory, vcpus, state, time spent)
165

166
    """
167
    xmlist_errors = []
168
    try:
169
      lines = utils.Retry(cls._RunXmList, 1, 5, args=(xmlist_errors, ))
170
    except utils.RetryTimeout:
171
      if xmlist_errors:
172
        xmlist_result = xmlist_errors.pop()
173

    
174
        errmsg = ("xm list failed, timeout exceeded (%s): %s" %
175
                  (xmlist_result.fail_reason, xmlist_result.output))
176
      else:
177
        errmsg = "xm list failed"
178

    
179
      raise errors.HypervisorError(errmsg)
180

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

    
199
      # skip the Domain-0 (optional)
200
      if include_node or data[0] != _DOM0_NAME:
201
        result.append(data)
202

    
203
    return result
204

    
205
  def ListInstances(self):
206
    """Get the list of running instances.
207

208
    """
209
    xm_list = self._GetXMList(False)
210
    names = [info[0] for info in xm_list]
211
    return names
212

    
213
  def GetInstanceInfo(self, instance_name):
214
    """Get instance properties.
215

216
    @param instance_name: the instance name
217

218
    @return: tuple (name, id, memory, vcpus, stat, times)
219

220
    """
221
    xm_list = self._GetXMList(instance_name == _DOM0_NAME)
222
    result = None
223
    for data in xm_list:
224
      if data[0] == instance_name:
225
        result = data
226
        break
227
    return result
228

    
229
  def GetAllInstancesInfo(self):
230
    """Get properties of all instances.
231

232
    @return: list of tuples (name, id, memory, vcpus, stat, times)
233

234
    """
235
    xm_list = self._GetXMList(False)
236
    return xm_list
237

    
238
  def StartInstance(self, instance, block_devices, startup_paused):
239
    """Start an instance.
240

241
    """
242
    self._WriteConfigFile(instance, block_devices)
243
    cmd = [constants.XEN_CMD, "create"]
244
    if startup_paused:
245
      cmd.extend(["-p"])
246
    cmd.extend([self._ConfigFileName(instance.name)])
247
    result = utils.RunCmd(cmd)
248

    
249
    if result.failed:
250
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
251
                                   (instance.name, result.fail_reason,
252
                                    result.output))
253

    
254
  def StopInstance(self, instance, force=False, retry=False, name=None):
255
    """Stop an instance.
256

257
    """
258
    if name is None:
259
      name = instance.name
260
    self._RemoveConfigFile(name)
261
    if force:
262
      command = [constants.XEN_CMD, "destroy", name]
263
    else:
264
      command = [constants.XEN_CMD, "shutdown", name]
265
    result = utils.RunCmd(command)
266

    
267
    if result.failed:
268
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
269
                                   (name, result.fail_reason, result.output))
270

    
271
  def RebootInstance(self, instance):
272
    """Reboot an instance.
273

274
    """
275
    ini_info = self.GetInstanceInfo(instance.name)
276

    
277
    if ini_info is None:
278
      raise errors.HypervisorError("Failed to reboot instance %s,"
279
                                   " not running" % instance.name)
280

    
281
    result = utils.RunCmd([constants.XEN_CMD, "reboot", instance.name])
282
    if result.failed:
283
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
284
                                   (instance.name, result.fail_reason,
285
                                    result.output))
286

    
287
    def _CheckInstance():
288
      new_info = self.GetInstanceInfo(instance.name)
289

    
290
      # check if the domain ID has changed or the run time has decreased
291
      if (new_info is not None and
292
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
293
        return
294

    
295
      raise utils.RetryAgain()
296

    
297
    try:
298
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
299
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
300
    except utils.RetryTimeout:
301
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
302
                                   " did not reboot in the expected interval" %
303
                                   (instance.name, ))
304

    
305
  def GetNodeInfo(self):
306
    """Return information about the node.
307

308
    @return: a dict with the following keys (memory values in MiB):
309
          - memory_total: the total memory size on the node
310
          - memory_free: the available memory on the node for instances
311
          - memory_dom0: the memory used by the node itself, if available
312
          - nr_cpus: total number of CPUs
313
          - nr_nodes: in a NUMA system, the number of domains
314
          - nr_sockets: the number of physical CPU sockets in the node
315
          - hv_version: the hypervisor version in the form (major, minor)
316

317
    """
318
    # note: in xen 3, memory has changed to total_memory
319
    result = utils.RunCmd([constants.XEN_CMD, "info"])
320
    if result.failed:
321
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
322
                    result.output)
323
      return None
324

    
325
    xmoutput = result.stdout.splitlines()
326
    result = {}
327
    cores_per_socket = threads_per_core = nr_cpus = None
328
    xen_major, xen_minor = None, None
329
    for line in xmoutput:
330
      splitfields = line.split(":", 1)
331

    
332
      if len(splitfields) > 1:
333
        key = splitfields[0].strip()
334
        val = splitfields[1].strip()
335
        if key == "memory" or key == "total_memory":
336
          result["memory_total"] = int(val)
337
        elif key == "free_memory":
338
          result["memory_free"] = int(val)
339
        elif key == "nr_cpus":
340
          nr_cpus = result["cpu_total"] = int(val)
341
        elif key == "nr_nodes":
342
          result["cpu_nodes"] = int(val)
343
        elif key == "cores_per_socket":
344
          cores_per_socket = int(val)
345
        elif key == "threads_per_core":
346
          threads_per_core = int(val)
347
        elif key == "xen_major":
348
          xen_major = int(val)
349
        elif key == "xen_minor":
350
          xen_minor = int(val)
351

    
352
    if (cores_per_socket is not None and
353
        threads_per_core is not None and nr_cpus is not None):
354
      result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
355

    
356
    dom0_info = self.GetInstanceInfo(_DOM0_NAME)
357
    if dom0_info is not None:
358
      result["memory_dom0"] = dom0_info[2]
359
      result["dom0_cpus"] = dom0_info[3]
360

    
361
    if not (xen_major is None or xen_minor is None):
362
      result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
363

    
364
    return result
365

    
366
  @classmethod
367
  def GetInstanceConsole(cls, instance, hvparams, beparams):
368
    """Return a command for connecting to the console of an instance.
369

370
    """
371
    return objects.InstanceConsole(instance=instance.name,
372
                                   kind=constants.CONS_SSH,
373
                                   host=instance.primary_node,
374
                                   user=constants.GANETI_RUNAS,
375
                                   command=[constants.XM_CONSOLE_WRAPPER,
376
                                            instance.name])
377

    
378
  def Verify(self):
379
    """Verify the hypervisor.
380

381
    For Xen, this verifies that the xend process is running.
382

383
    """
384
    result = utils.RunCmd([constants.XEN_CMD, "info"])
385
    if result.failed:
386
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
387

    
388
  @staticmethod
389
  def _GetConfigFileDiskData(block_devices, blockdev_prefix):
390
    """Get disk directive for xen config file.
391

392
    This method builds the xen config disk directive according to the
393
    given disk_template and block_devices.
394

395
    @param block_devices: list of tuples (cfdev, rldev):
396
        - cfdev: dict containing ganeti config disk part
397
        - rldev: ganeti.bdev.BlockDev object
398
    @param blockdev_prefix: a string containing blockdevice prefix,
399
                            e.g. "sd" for /dev/sda
400

401
    @return: string containing disk directive for xen instance config file
402

403
    """
404
    FILE_DRIVER_MAP = {
405
      constants.FD_LOOP: "file",
406
      constants.FD_BLKTAP: "tap:aio",
407
      }
408
    disk_data = []
409
    if len(block_devices) > 24:
410
      # 'z' - 'a' = 24
411
      raise errors.HypervisorError("Too many disks")
412
    namespace = [blockdev_prefix + chr(i + ord("a")) for i in range(24)]
413
    for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
414
      if cfdev.mode == constants.DISK_RDWR:
415
        mode = "w"
416
      else:
417
        mode = "r"
418
      if cfdev.dev_type == constants.LD_FILE:
419
        line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
420
                                  dev_path, sd_name, mode)
421
      else:
422
        line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
423
      disk_data.append(line)
424

    
425
    return disk_data
426

    
427
  def MigrationInfo(self, instance):
428
    """Get instance information to perform a migration.
429

430
    @type instance: L{objects.Instance}
431
    @param instance: instance to be migrated
432
    @rtype: string
433
    @return: content of the xen config file
434

435
    """
436
    return self._ReadConfigFile(instance.name)
437

    
438
  def AcceptInstance(self, instance, info, target):
439
    """Prepare to accept an instance.
440

441
    @type instance: L{objects.Instance}
442
    @param instance: instance to be accepted
443
    @type info: string
444
    @param info: content of the xen config file on the source node
445
    @type target: string
446
    @param target: target host (usually ip), on this node
447

448
    """
449
    pass
450

    
451
  def FinalizeMigrationDst(self, instance, info, success):
452
    """Finalize an instance migration.
453

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

457
    @type instance: L{objects.Instance}
458
    @param instance: instance whose migration is being finalized
459
    @type info: string
460
    @param info: content of the xen config file on the source node
461
    @type success: boolean
462
    @param success: whether the migration was a success or a failure
463

464
    """
465
    if success:
466
      self._WriteConfigFileStatic(instance.name, info)
467

    
468
  def MigrateInstance(self, instance, target, live):
469
    """Migrate an instance to a target node.
470

471
    The migration will not be attempted if the instance is not
472
    currently running.
473

474
    @type instance: L{objects.Instance}
475
    @param instance: the instance to be migrated
476
    @type target: string
477
    @param target: ip address of the target node
478
    @type live: boolean
479
    @param live: perform a live migration
480

481
    """
482
    if self.GetInstanceInfo(instance.name) is None:
483
      raise errors.HypervisorError("Instance not running, cannot migrate")
484

    
485
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
486

    
487
    if not netutils.TcpPing(target, port, live_port_needed=True):
488
      raise errors.HypervisorError("Remote host %s not listening on port"
489
                                   " %s, cannot migrate" % (target, port))
490

    
491
    # FIXME: migrate must be upgraded for transitioning to "xl" (xen 4.1).
492
    #  -l doesn't exist anymore
493
    #  -p doesn't exist anymore
494
    #  -C config_file must be passed
495
    #  ssh must recognize the key of the target host for the migration
496
    args = [constants.XEN_CMD, "migrate", "-p", "%d" % port]
497
    if live:
498
      args.append("-l")
499
    args.extend([instance.name, target])
500
    result = utils.RunCmd(args)
501
    if result.failed:
502
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
503
                                   (instance.name, result.output))
504

    
505
  def FinalizeMigrationSource(self, instance, success, live):
506
    """Finalize the instance migration on the source node.
507

508
    @type instance: L{objects.Instance}
509
    @param instance: the instance that was migrated
510
    @type success: bool
511
    @param success: whether the migration succeeded or not
512
    @type live: bool
513
    @param live: whether the user requested a live migration or not
514

515
    """
516
    # pylint: disable=W0613
517
    if success:
518
      # remove old xen file after migration succeeded
519
      try:
520
        self._RemoveConfigFile(instance.name)
521
      except EnvironmentError:
522
        logging.exception("Failure while removing instance config file")
523

    
524
  def GetMigrationStatus(self, instance):
525
    """Get the migration status
526

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

531
    @type instance: L{objects.Instance}
532
    @param instance: the instance that is being migrated
533
    @rtype: L{objects.MigrationStatus}
534
    @return: the status of the current migration (one of
535
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
536
             progress info that can be retrieved from the hypervisor
537

538
    """
539
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
540

    
541
  @classmethod
542
  def PowercycleNode(cls):
543
    """Xen-specific powercycle.
544

545
    This first does a Linux reboot (which triggers automatically a Xen
546
    reboot), and if that fails it tries to do a Xen reboot. The reason
547
    we don't try a Xen reboot first is that the xen reboot launches an
548
    external command which connects to the Xen hypervisor, and that
549
    won't work in case the root filesystem is broken and/or the xend
550
    daemon is not working.
551

552
    """
553
    try:
554
      cls.LinuxPowercycle()
555
    finally:
556
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
557

    
558

    
559
class XenPvmHypervisor(XenHypervisor):
560
  """Xen PVM hypervisor interface"""
561

    
562
  PARAMETERS = {
563
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
564
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
565
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
566
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
567
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
568
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
569
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
570
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
571
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
572
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
573
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
574
    constants.HV_REBOOT_BEHAVIOR:
575
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
576
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
577
    }
578

    
579
  @classmethod
580
  def _WriteConfigFile(cls, instance, block_devices):
581
    """Write the Xen config file for the instance.
582

583
    """
584
    hvp = instance.hvparams
585
    config = StringIO()
586
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
587

    
588
    # if bootloader is True, use bootloader instead of kernel and ramdisk
589
    # parameters.
590
    if hvp[constants.HV_USE_BOOTLOADER]:
591
      # bootloader handling
592
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
593
      if bootloader_path:
594
        config.write("bootloader = '%s'\n" % bootloader_path)
595
      else:
596
        raise errors.HypervisorError("Bootloader enabled, but missing"
597
                                     " bootloader path")
598

    
599
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
600
      if bootloader_args:
601
        config.write("bootargs = '%s'\n" % bootloader_args)
602
    else:
603
      # kernel handling
604
      kpath = hvp[constants.HV_KERNEL_PATH]
605
      config.write("kernel = '%s'\n" % kpath)
606

    
607
      # initrd handling
608
      initrd_path = hvp[constants.HV_INITRD_PATH]
609
      if initrd_path:
610
        config.write("ramdisk = '%s'\n" % initrd_path)
611

    
612
    # rest of the settings
613
    config.write("memory = %d\n" % instance.beparams[constants.BE_MAXMEM])
614
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
615
    cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK])
616
    if cpu_pinning:
617
      config.write("%s\n" % cpu_pinning)
618

    
619
    config.write("name = '%s'\n" % instance.name)
620

    
621
    vif_data = []
622
    for nic in instance.nics:
623
      nic_str = "mac=%s" % (nic.mac)
624
      ip = getattr(nic, "ip", None)
625
      if ip is not None:
626
        nic_str += ", ip=%s" % ip
627
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
628
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
629
      vif_data.append("'%s'" % nic_str)
630

    
631
    disk_data = cls._GetConfigFileDiskData(block_devices,
632
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
633

    
634
    config.write("vif = [%s]\n" % ",".join(vif_data))
635
    config.write("disk = [%s]\n" % ",".join(disk_data))
636

    
637
    if hvp[constants.HV_ROOT_PATH]:
638
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
639
    config.write("on_poweroff = 'destroy'\n")
640
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
641
      config.write("on_reboot = 'restart'\n")
642
    else:
643
      config.write("on_reboot = 'destroy'\n")
644
    config.write("on_crash = 'restart'\n")
645
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
646
    # just in case it exists
647
    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
648
    try:
649
      utils.WriteFile(cls._ConfigFileName(instance.name),
650
                      data=config.getvalue())
651
    except EnvironmentError, err:
652
      raise errors.HypervisorError("Cannot write Xen instance confile"
653
                                   " file %s: %s" %
654
                                   (cls._ConfigFileName(instance.name), err))
655

    
656
    return True
657

    
658

    
659
class XenHvmHypervisor(XenHypervisor):
660
  """Xen HVM hypervisor interface"""
661

    
662
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
663
    constants.VNC_PASSWORD_FILE,
664
    ]
665
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
666
    constants.VNC_PASSWORD_FILE,
667
    ]
668

    
669
  PARAMETERS = {
670
    constants.HV_ACPI: hv_base.NO_CHECK,
671
    constants.HV_BOOT_ORDER: (True, ) +
672
      (lambda x: x and len(x.strip("acdn")) == 0,
673
       "Invalid boot order specified, must be one or more of [acdn]",
674
       None, None),
675
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
676
    constants.HV_DISK_TYPE:
677
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
678
    constants.HV_NIC_TYPE:
679
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
680
    constants.HV_PAE: hv_base.NO_CHECK,
681
    constants.HV_VNC_BIND_ADDRESS:
682
      (False, netutils.IP4Address.IsValid,
683
       "VNC bind address is not a valid IP address", None, None),
684
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
685
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
686
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
687
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
688
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
689
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
690
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
691
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
692
    constants.HV_REBOOT_BEHAVIOR:
693
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
694
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
695
    }
696

    
697
  @classmethod
698
  def _WriteConfigFile(cls, instance, block_devices):
699
    """Create a Xen 3.1 HVM config file.
700

701
    """
702
    hvp = instance.hvparams
703

    
704
    config = StringIO()
705
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
706

    
707
    # kernel handling
708
    kpath = hvp[constants.HV_KERNEL_PATH]
709
    config.write("kernel = '%s'\n" % kpath)
710

    
711
    config.write("builder = 'hvm'\n")
712
    config.write("memory = %d\n" % instance.beparams[constants.BE_MAXMEM])
713
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
714
    cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK])
715
    if cpu_pinning:
716
      config.write("%s\n" % cpu_pinning)
717

    
718
    config.write("name = '%s'\n" % instance.name)
719
    if hvp[constants.HV_PAE]:
720
      config.write("pae = 1\n")
721
    else:
722
      config.write("pae = 0\n")
723
    if hvp[constants.HV_ACPI]:
724
      config.write("acpi = 1\n")
725
    else:
726
      config.write("acpi = 0\n")
727
    config.write("apic = 1\n")
728
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
729
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
730
    config.write("sdl = 0\n")
731
    config.write("usb = 1\n")
732
    config.write("usbdevice = 'tablet'\n")
733
    config.write("vnc = 1\n")
734
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
735
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
736
    else:
737
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
738

    
739
    if instance.network_port > constants.VNC_BASE_PORT:
740
      display = instance.network_port - constants.VNC_BASE_PORT
741
      config.write("vncdisplay = %s\n" % display)
742
      config.write("vncunused = 0\n")
743
    else:
744
      config.write("# vncdisplay = 1\n")
745
      config.write("vncunused = 1\n")
746

    
747
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
748
    try:
749
      password = utils.ReadFile(vnc_pwd_file)
750
    except EnvironmentError, err:
751
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
752
                                   (vnc_pwd_file, err))
753

    
754
    config.write("vncpasswd = '%s'\n" % password.rstrip())
755

    
756
    config.write("serial = 'pty'\n")
757
    if hvp[constants.HV_USE_LOCALTIME]:
758
      config.write("localtime = 1\n")
759

    
760
    vif_data = []
761
    nic_type = hvp[constants.HV_NIC_TYPE]
762
    if nic_type is None:
763
      # ensure old instances don't change
764
      nic_type_str = ", type=ioemu"
765
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
766
      nic_type_str = ", type=paravirtualized"
767
    else:
768
      nic_type_str = ", model=%s, type=ioemu" % nic_type
769
    for nic in instance.nics:
770
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
771
      ip = getattr(nic, "ip", None)
772
      if ip is not None:
773
        nic_str += ", ip=%s" % ip
774
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
775
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
776
      vif_data.append("'%s'" % nic_str)
777

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

    
780
    disk_data = cls._GetConfigFileDiskData(block_devices,
781
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
782

    
783
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
784
    if iso_path:
785
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
786
      disk_data.append(iso)
787

    
788
    config.write("disk = [%s]\n" % (",".join(disk_data)))
789

    
790
    config.write("on_poweroff = 'destroy'\n")
791
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
792
      config.write("on_reboot = 'restart'\n")
793
    else:
794
      config.write("on_reboot = 'destroy'\n")
795
    config.write("on_crash = 'restart'\n")
796
    # just in case it exists
797
    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
798
    try:
799
      utils.WriteFile(cls._ConfigFileName(instance.name),
800
                      data=config.getvalue())
801
    except EnvironmentError, err:
802
      raise errors.HypervisorError("Cannot write Xen instance confile"
803
                                   " file %s: %s" %
804
                                   (cls._ConfigFileName(instance.name), err))
805

    
806
    return True