Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 69ab2e12

History | View | Annotate | Download (24.4 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
  @staticmethod
110
  def _RunXmList(xmlist_errors):
111
    """Helper function for L{_GetXMList} to run "xm list".
112

113
    """
114
    result = utils.RunCmd([constants.XEN_CMD, "list"])
115
    if result.failed:
116
      logging.error("xm list failed (%s): %s", result.fail_reason,
117
                    result.output)
118
      xmlist_errors.append(result)
119
      raise utils.RetryAgain()
120

    
121
    # skip over the heading
122
    return result.stdout.splitlines()[1:]
123

    
124
  @classmethod
125
  def _GetXMList(cls, include_node):
126
    """Return the list of running instances.
127

128
    If the include_node argument is True, then we return information
129
    for dom0 also, otherwise we filter that from the return value.
130

131
    @return: list of (name, id, memory, vcpus, state, time spent)
132

133
    """
134
    xmlist_errors = []
135
    try:
136
      lines = utils.Retry(cls._RunXmList, 1, 5, args=(xmlist_errors, ))
137
    except utils.RetryTimeout:
138
      if xmlist_errors:
139
        xmlist_result = xmlist_errors.pop()
140

    
141
        errmsg = ("xm list failed, timeout exceeded (%s): %s" %
142
                  (xmlist_result.fail_reason, xmlist_result.output))
143
      else:
144
        errmsg = "xm list failed"
145

    
146
      raise errors.HypervisorError(errmsg)
147

    
148
    result = []
149
    for line in lines:
150
      # The format of lines is:
151
      # Name      ID Mem(MiB) VCPUs State  Time(s)
152
      # Domain-0   0  3418     4 r-----    266.2
153
      data = line.split()
154
      if len(data) != 6:
155
        raise errors.HypervisorError("Can't parse output of xm list,"
156
                                     " line: %s" % line)
157
      try:
158
        data[1] = int(data[1])
159
        data[2] = int(data[2])
160
        data[3] = int(data[3])
161
        data[5] = float(data[5])
162
      except (TypeError, ValueError), err:
163
        raise errors.HypervisorError("Can't parse output of xm list,"
164
                                     " line: %s, error: %s" % (line, err))
165

    
166
      # skip the Domain-0 (optional)
167
      if include_node or data[0] != "Domain-0":
168
        result.append(data)
169

    
170
    return result
171

    
172
  def ListInstances(self):
173
    """Get the list of running instances.
174

175
    """
176
    xm_list = self._GetXMList(False)
177
    names = [info[0] for info in xm_list]
178
    return names
179

    
180
  def GetInstanceInfo(self, instance_name):
181
    """Get instance properties.
182

183
    @param instance_name: the instance name
184

185
    @return: tuple (name, id, memory, vcpus, stat, times)
186

187
    """
188
    xm_list = self._GetXMList(instance_name == "Domain-0")
189
    result = None
190
    for data in xm_list:
191
      if data[0] == instance_name:
192
        result = data
193
        break
194
    return result
195

    
196
  def GetAllInstancesInfo(self):
197
    """Get properties of all instances.
198

199
    @return: list of tuples (name, id, memory, vcpus, stat, times)
200

201
    """
202
    xm_list = self._GetXMList(False)
203
    return xm_list
204

    
205
  def StartInstance(self, instance, block_devices, startup_paused):
206
    """Start an instance.
207

208
    """
209
    self._WriteConfigFile(instance, block_devices)
210
    cmd = [constants.XEN_CMD, "create"]
211
    if startup_paused:
212
      cmd.extend(["-p"])
213
    cmd.extend([self._ConfigFileName(instance.name)])
214
    result = utils.RunCmd(cmd)
215

    
216
    if result.failed:
217
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
218
                                   (instance.name, result.fail_reason,
219
                                    result.output))
220

    
221
  def StopInstance(self, instance, force=False, retry=False, name=None):
222
    """Stop an instance.
223

224
    """
225
    if name is None:
226
      name = instance.name
227
    self._RemoveConfigFile(name)
228
    if force:
229
      command = [constants.XEN_CMD, "destroy", name]
230
    else:
231
      command = [constants.XEN_CMD, "shutdown", name]
232
    result = utils.RunCmd(command)
233

    
234
    if result.failed:
235
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
236
                                   (name, result.fail_reason, result.output))
237

    
238
  def RebootInstance(self, instance):
239
    """Reboot an instance.
240

241
    """
242
    ini_info = self.GetInstanceInfo(instance.name)
243

    
244
    if ini_info is None:
245
      raise errors.HypervisorError("Failed to reboot instance %s,"
246
                                   " not running" % instance.name)
247

    
248
    result = utils.RunCmd([constants.XEN_CMD, "reboot", instance.name])
249
    if result.failed:
250
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
251
                                   (instance.name, result.fail_reason,
252
                                    result.output))
253

    
254
    def _CheckInstance():
255
      new_info = self.GetInstanceInfo(instance.name)
256

    
257
      # check if the domain ID has changed or the run time has decreased
258
      if (new_info is not None and
259
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
260
        return
261

    
262
      raise utils.RetryAgain()
263

    
264
    try:
265
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
266
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
267
    except utils.RetryTimeout:
268
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
269
                                   " did not reboot in the expected interval" %
270
                                   (instance.name, ))
271

    
272
  def GetNodeInfo(self):
273
    """Return information about the node.
274

275
    @return: a dict with the following keys (memory values in MiB):
276
          - memory_total: the total memory size on the node
277
          - memory_free: the available memory on the node for instances
278
          - memory_dom0: the memory used by the node itself, if available
279
          - nr_cpus: total number of CPUs
280
          - nr_nodes: in a NUMA system, the number of domains
281
          - nr_sockets: the number of physical CPU sockets in the node
282
          - hv_version: the hypervisor version in the form (major, minor)
283

284
    """
285
    # note: in xen 3, memory has changed to total_memory
286
    result = utils.RunCmd([constants.XEN_CMD, "info"])
287
    if result.failed:
288
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
289
                    result.output)
290
      return None
291

    
292
    xmoutput = result.stdout.splitlines()
293
    result = {}
294
    cores_per_socket = threads_per_core = nr_cpus = None
295
    xen_major, xen_minor = None, None
296
    for line in xmoutput:
297
      splitfields = line.split(":", 1)
298

    
299
      if len(splitfields) > 1:
300
        key = splitfields[0].strip()
301
        val = splitfields[1].strip()
302
        if key == "memory" or key == "total_memory":
303
          result["memory_total"] = int(val)
304
        elif key == "free_memory":
305
          result["memory_free"] = int(val)
306
        elif key == "nr_cpus":
307
          nr_cpus = result["cpu_total"] = int(val)
308
        elif key == "nr_nodes":
309
          result["cpu_nodes"] = int(val)
310
        elif key == "cores_per_socket":
311
          cores_per_socket = int(val)
312
        elif key == "threads_per_core":
313
          threads_per_core = int(val)
314
        elif key == "xen_major":
315
          xen_major = int(val)
316
        elif key == "xen_minor":
317
          xen_minor = int(val)
318

    
319
    if (cores_per_socket is not None and
320
        threads_per_core is not None and nr_cpus is not None):
321
      result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
322

    
323
    dom0_info = self.GetInstanceInfo("Domain-0")
324
    if dom0_info is not None:
325
      result["memory_dom0"] = dom0_info[2]
326

    
327
    if not (xen_major is None or xen_minor is None):
328
      result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
329

    
330
    return result
331

    
332
  @classmethod
333
  def GetInstanceConsole(cls, instance, hvparams, beparams):
334
    """Return a command for connecting to the console of an instance.
335

336
    """
337
    return objects.InstanceConsole(instance=instance.name,
338
                                   kind=constants.CONS_SSH,
339
                                   host=instance.primary_node,
340
                                   user=constants.GANETI_RUNAS,
341
                                   command=[constants.XM_CONSOLE_WRAPPER,
342
                                            instance.name])
343

    
344
  def Verify(self):
345
    """Verify the hypervisor.
346

347
    For Xen, this verifies that the xend process is running.
348

349
    """
350
    result = utils.RunCmd([constants.XEN_CMD, "info"])
351
    if result.failed:
352
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
353

    
354
  @staticmethod
355
  def _GetConfigFileDiskData(block_devices, blockdev_prefix):
356
    """Get disk directive for xen config file.
357

358
    This method builds the xen config disk directive according to the
359
    given disk_template and block_devices.
360

361
    @param block_devices: list of tuples (cfdev, rldev):
362
        - cfdev: dict containing ganeti config disk part
363
        - rldev: ganeti.bdev.BlockDev object
364
    @param blockdev_prefix: a string containing blockdevice prefix,
365
                            e.g. "sd" for /dev/sda
366

367
    @return: string containing disk directive for xen instance config file
368

369
    """
370
    FILE_DRIVER_MAP = {
371
      constants.FD_LOOP: "file",
372
      constants.FD_BLKTAP: "tap:aio",
373
      }
374
    disk_data = []
375
    if len(block_devices) > 24:
376
      # 'z' - 'a' = 24
377
      raise errors.HypervisorError("Too many disks")
378
    namespace = [blockdev_prefix + chr(i + ord("a")) for i in range(24)]
379
    for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
380
      if cfdev.mode == constants.DISK_RDWR:
381
        mode = "w"
382
      else:
383
        mode = "r"
384
      if cfdev.dev_type == constants.LD_FILE:
385
        line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
386
                                  dev_path, sd_name, mode)
387
      else:
388
        line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
389
      disk_data.append(line)
390

    
391
    return disk_data
392

    
393
  def MigrationInfo(self, instance):
394
    """Get instance information to perform a migration.
395

396
    @type instance: L{objects.Instance}
397
    @param instance: instance to be migrated
398
    @rtype: string
399
    @return: content of the xen config file
400

401
    """
402
    return self._ReadConfigFile(instance.name)
403

    
404
  def AcceptInstance(self, instance, info, target):
405
    """Prepare to accept an instance.
406

407
    @type instance: L{objects.Instance}
408
    @param instance: instance to be accepted
409
    @type info: string
410
    @param info: content of the xen config file on the source node
411
    @type target: string
412
    @param target: target host (usually ip), on this node
413

414
    """
415
    pass
416

    
417
  def FinalizeMigration(self, instance, info, success):
418
    """Finalize an instance migration.
419

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

423
    @type instance: L{objects.Instance}
424
    @param instance: instance whose migration is being finalized
425
    @type info: string
426
    @param info: content of the xen config file on the source node
427
    @type success: boolean
428
    @param success: whether the migration was a success or a failure
429

430
    """
431
    if success:
432
      self._WriteConfigFileStatic(instance.name, info)
433

    
434
  def MigrateInstance(self, instance, target, live):
435
    """Migrate an instance to a target node.
436

437
    The migration will not be attempted if the instance is not
438
    currently running.
439

440
    @type instance: L{objects.Instance}
441
    @param instance: the instance to be migrated
442
    @type target: string
443
    @param target: ip address of the target node
444
    @type live: boolean
445
    @param live: perform a live migration
446

447
    """
448
    if self.GetInstanceInfo(instance.name) is None:
449
      raise errors.HypervisorError("Instance not running, cannot migrate")
450

    
451
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
452

    
453
    if not netutils.TcpPing(target, port, live_port_needed=True):
454
      raise errors.HypervisorError("Remote host %s not listening on port"
455
                                   " %s, cannot migrate" % (target, port))
456

    
457
    # FIXME: migrate must be upgraded for transitioning to "xl" (xen 4.1).
458
    #  -l doesn't exist anymore
459
    #  -p doesn't exist anymore
460
    #  -C config_file must be passed
461
    #  ssh must recognize the key of the target host for the migration
462
    args = [constants.XEN_CMD, "migrate", "-p", "%d" % port]
463
    if live:
464
      args.append("-l")
465
    args.extend([instance.name, target])
466
    result = utils.RunCmd(args)
467
    if result.failed:
468
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
469
                                   (instance.name, result.output))
470
    # remove old xen file after migration succeeded
471
    try:
472
      self._RemoveConfigFile(instance.name)
473
    except EnvironmentError:
474
      logging.exception("Failure while removing instance config file")
475

    
476
  @classmethod
477
  def PowercycleNode(cls):
478
    """Xen-specific powercycle.
479

480
    This first does a Linux reboot (which triggers automatically a Xen
481
    reboot), and if that fails it tries to do a Xen reboot. The reason
482
    we don't try a Xen reboot first is that the xen reboot launches an
483
    external command which connects to the Xen hypervisor, and that
484
    won't work in case the root filesystem is broken and/or the xend
485
    daemon is not working.
486

487
    """
488
    try:
489
      cls.LinuxPowercycle()
490
    finally:
491
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
492

    
493

    
494
class XenPvmHypervisor(XenHypervisor):
495
  """Xen PVM hypervisor interface"""
496

    
497
  PARAMETERS = {
498
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
499
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
500
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
501
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
502
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
503
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
504
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
505
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
506
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
507
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
508
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
509
    constants.HV_REBOOT_BEHAVIOR:
510
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS)
511
    }
512

    
513
  @classmethod
514
  def _WriteConfigFile(cls, instance, block_devices):
515
    """Write the Xen config file for the instance.
516

517
    """
518
    hvp = instance.hvparams
519
    config = StringIO()
520
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
521

    
522
    # if bootloader is True, use bootloader instead of kernel and ramdisk
523
    # parameters.
524
    if hvp[constants.HV_USE_BOOTLOADER]:
525
      # bootloader handling
526
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
527
      if bootloader_path:
528
        config.write("bootloader = '%s'\n" % bootloader_path)
529
      else:
530
        raise errors.HypervisorError("Bootloader enabled, but missing"
531
                                     " bootloader path")
532

    
533
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
534
      if bootloader_args:
535
        config.write("bootargs = '%s'\n" % bootloader_args)
536
    else:
537
      # kernel handling
538
      kpath = hvp[constants.HV_KERNEL_PATH]
539
      config.write("kernel = '%s'\n" % kpath)
540

    
541
      # initrd handling
542
      initrd_path = hvp[constants.HV_INITRD_PATH]
543
      if initrd_path:
544
        config.write("ramdisk = '%s'\n" % initrd_path)
545

    
546
    # rest of the settings
547
    config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
548
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
549
    config.write("name = '%s'\n" % instance.name)
550

    
551
    vif_data = []
552
    for nic in instance.nics:
553
      nic_str = "mac=%s" % (nic.mac)
554
      ip = getattr(nic, "ip", None)
555
      if ip is not None:
556
        nic_str += ", ip=%s" % ip
557
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
558
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
559
      vif_data.append("'%s'" % nic_str)
560

    
561
    disk_data = cls._GetConfigFileDiskData(block_devices,
562
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
563

    
564
    config.write("vif = [%s]\n" % ",".join(vif_data))
565
    config.write("disk = [%s]\n" % ",".join(disk_data))
566

    
567
    if hvp[constants.HV_ROOT_PATH]:
568
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
569
    config.write("on_poweroff = 'destroy'\n")
570
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
571
      config.write("on_reboot = 'restart'\n")
572
    else:
573
      config.write("on_reboot = 'destroy'\n")
574
    config.write("on_crash = 'restart'\n")
575
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
576
    # just in case it exists
577
    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
578
    try:
579
      utils.WriteFile(cls._ConfigFileName(instance.name),
580
                      data=config.getvalue())
581
    except EnvironmentError, err:
582
      raise errors.HypervisorError("Cannot write Xen instance confile"
583
                                   " file %s: %s" %
584
                                   (cls._ConfigFileName(instance.name), err))
585

    
586
    return True
587

    
588

    
589
class XenHvmHypervisor(XenHypervisor):
590
  """Xen HVM hypervisor interface"""
591

    
592
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
593
    constants.VNC_PASSWORD_FILE,
594
    ]
595
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
596
    constants.VNC_PASSWORD_FILE,
597
    ]
598

    
599
  PARAMETERS = {
600
    constants.HV_ACPI: hv_base.NO_CHECK,
601
    constants.HV_BOOT_ORDER: (True, ) +
602
      (lambda x: x and len(x.strip("acdn")) == 0,
603
       "Invalid boot order specified, must be one or more of [acdn]",
604
       None, None),
605
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
606
    constants.HV_DISK_TYPE:
607
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
608
    constants.HV_NIC_TYPE:
609
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
610
    constants.HV_PAE: hv_base.NO_CHECK,
611
    constants.HV_VNC_BIND_ADDRESS:
612
      (False, netutils.IP4Address.IsValid,
613
       "VNC bind address is not a valid IP address", None, None),
614
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
615
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
616
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
617
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
618
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
619
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
620
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
621
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
622
    constants.HV_REBOOT_BEHAVIOR:
623
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS)
624
    }
625

    
626
  @classmethod
627
  def _WriteConfigFile(cls, instance, block_devices):
628
    """Create a Xen 3.1 HVM config file.
629

630
    """
631
    hvp = instance.hvparams
632

    
633
    config = StringIO()
634
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
635

    
636
    # kernel handling
637
    kpath = hvp[constants.HV_KERNEL_PATH]
638
    config.write("kernel = '%s'\n" % kpath)
639

    
640
    config.write("builder = 'hvm'\n")
641
    config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
642
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
643
    config.write("name = '%s'\n" % instance.name)
644
    if hvp[constants.HV_PAE]:
645
      config.write("pae = 1\n")
646
    else:
647
      config.write("pae = 0\n")
648
    if hvp[constants.HV_ACPI]:
649
      config.write("acpi = 1\n")
650
    else:
651
      config.write("acpi = 0\n")
652
    config.write("apic = 1\n")
653
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
654
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
655
    config.write("sdl = 0\n")
656
    config.write("usb = 1\n")
657
    config.write("usbdevice = 'tablet'\n")
658
    config.write("vnc = 1\n")
659
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
660
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
661
    else:
662
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
663

    
664
    if instance.network_port > constants.VNC_BASE_PORT:
665
      display = instance.network_port - constants.VNC_BASE_PORT
666
      config.write("vncdisplay = %s\n" % display)
667
      config.write("vncunused = 0\n")
668
    else:
669
      config.write("# vncdisplay = 1\n")
670
      config.write("vncunused = 1\n")
671

    
672
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
673
    try:
674
      password = utils.ReadFile(vnc_pwd_file)
675
    except EnvironmentError, err:
676
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
677
                                   (vnc_pwd_file, err))
678

    
679
    config.write("vncpasswd = '%s'\n" % password.rstrip())
680

    
681
    config.write("serial = 'pty'\n")
682
    if hvp[constants.HV_USE_LOCALTIME]:
683
      config.write("localtime = 1\n")
684

    
685
    vif_data = []
686
    nic_type = hvp[constants.HV_NIC_TYPE]
687
    if nic_type is None:
688
      # ensure old instances don't change
689
      nic_type_str = ", type=ioemu"
690
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
691
      nic_type_str = ", type=paravirtualized"
692
    else:
693
      nic_type_str = ", model=%s, type=ioemu" % nic_type
694
    for nic in instance.nics:
695
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
696
      ip = getattr(nic, "ip", None)
697
      if ip is not None:
698
        nic_str += ", ip=%s" % ip
699
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
700
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
701
      vif_data.append("'%s'" % nic_str)
702

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

    
705
    disk_data = cls._GetConfigFileDiskData(block_devices,
706
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
707

    
708
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
709
    if iso_path:
710
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
711
      disk_data.append(iso)
712

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

    
715
    config.write("on_poweroff = 'destroy'\n")
716
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
717
      config.write("on_reboot = 'restart'\n")
718
    else:
719
      config.write("on_reboot = 'destroy'\n")
720
    config.write("on_crash = 'restart'\n")
721
    # just in case it exists
722
    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
723
    try:
724
      utils.WriteFile(cls._ConfigFileName(instance.name),
725
                      data=config.getvalue())
726
    except EnvironmentError, err:
727
      raise errors.HypervisorError("Cannot write Xen instance confile"
728
                                   " file %s: %s" %
729
                                   (cls._ConfigFileName(instance.name), err))
730

    
731
    return True