Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ f5a4b9ce

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

    
41

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

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

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

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

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

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

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

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

78
    """
79
    raise NotImplementedError
80

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

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

87
    """
88
    utils.WriteFile(XenHypervisor._ConfigFileName(instance_name), data=data)
89

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
178
      raise errors.HypervisorError(errmsg)
179

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

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

    
202
    return result
203

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

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

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

215
    @param instance_name: the instance name
216

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
294
      raise utils.RetryAgain()
295

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

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

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

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

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

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

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

    
355
    dom0_info = self.GetInstanceInfo("Domain-0")
356
    if dom0_info is not None:
357
      result["memory_dom0"] = dom0_info[2]
358

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

    
362
    return result
363

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

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

    
376
  def Verify(self):
377
    """Verify the hypervisor.
378

379
    For Xen, this verifies that the xend process is running.
380

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

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

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

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

399
    @return: string containing disk directive for xen instance config file
400

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

    
423
    return disk_data
424

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

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

433
    """
434
    return self._ReadConfigFile(instance.name)
435

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

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

446
    """
447
    pass
448

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

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

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

462
    """
463
    if success:
464
      self._WriteConfigFileStatic(instance.name, info)
465

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

469
    The migration will not be attempted if the instance is not
470
    currently running.
471

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

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

    
483
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
484

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

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

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

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

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

    
522
  def GetMigrationStatus(self, instance):
523
    """Get the migration status
524

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

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

536
    """
537
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
538

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

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

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

    
556

    
557
class XenPvmHypervisor(XenHypervisor):
558
  """Xen PVM hypervisor interface"""
559

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

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

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

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

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

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

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

    
617
    config.write("name = '%s'\n" % instance.name)
618

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

    
629
    disk_data = cls._GetConfigFileDiskData(block_devices,
630
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
631

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

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

    
654
    return True
655

    
656

    
657
class XenHvmHypervisor(XenHypervisor):
658
  """Xen HVM hypervisor interface"""
659

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

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

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

699
    """
700
    hvp = instance.hvparams
701

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

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

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

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

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

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

    
752
    config.write("vncpasswd = '%s'\n" % password.rstrip())
753

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

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

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

    
778
    disk_data = cls._GetConfigFileDiskData(block_devices,
779
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
780

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

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

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

    
804
    return True