Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 60af751d

History | View | Annotate | Download (25.6 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
class XenHypervisor(hv_base.BaseHypervisor):
38
  """Xen generic hypervisor interface
39

40
  This is the Xen base class used for both Xen PVM and HVM. It contains
41
  all the functionality that is identical for both.
42

43
  """
44
  CAN_MIGRATE = True
45
  REBOOT_RETRY_COUNT = 60
46
  REBOOT_RETRY_INTERVAL = 10
47

    
48
  ANCILLARY_FILES = [
49
    "/etc/xen/xend-config.sxp",
50
    "/etc/xen/scripts/vif-bridge",
51
    ]
52

    
53
  @classmethod
54
  def _WriteConfigFile(cls, instance, block_devices):
55
    """Write the Xen config file for the instance.
56

57
    """
58
    raise NotImplementedError
59

    
60
  @staticmethod
61
  def _WriteConfigFileStatic(instance_name, data):
62
    """Write the Xen config file for the instance.
63

64
    This version of the function just writes the config file from static data.
65

66
    """
67
    utils.WriteFile("/etc/xen/%s" % instance_name, data=data)
68

    
69
  @staticmethod
70
  def _ReadConfigFile(instance_name):
71
    """Returns the contents of the instance config file.
72

73
    """
74
    try:
75
      file_content = utils.ReadFile("/etc/xen/%s" % instance_name)
76
    except EnvironmentError, err:
77
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
78
    return file_content
79

    
80
  @staticmethod
81
  def _RemoveConfigFile(instance_name):
82
    """Remove the xen configuration file.
83

84
    """
85
    utils.RemoveFile("/etc/xen/%s" % instance_name)
86

    
87
  @classmethod
88
  def _CreateConfigCpus(cls, cpu_mask):
89
    """Create a CPU config string that's compatible with Xen's
90
    configuration file.
91

92
    """
93
    # Convert the string CPU mask to a list of list of int's
94
    cpu_list = utils.ParseMultiCpuMask(cpu_mask)
95

    
96
    if len(cpu_list) == 1:
97
      all_cpu_mapping = cpu_list[0]
98
      if all_cpu_mapping == constants.CPU_PINNING_OFF:
99
        # If CPU pinning has 1 entry that's "all", then remove the
100
        # parameter from the config file
101
        return None
102
      else:
103
        # If CPU pinning has one non-all entry, mapping all vCPUS (the entire
104
        # VM) to one physical CPU, using format 'cpu = "C"'
105
        return "cpu = \"%s\"" % ",".join(map(str, all_cpu_mapping))
106
    else:
107
      def _GetCPUMap(vcpu):
108
        if vcpu[0] == constants.CPU_PINNING_ALL_VAL:
109
          cpu_map = constants.CPU_PINNING_ALL_XEN
110
        else:
111
          cpu_map = ",".join(map(str, vcpu))
112
        return "\"%s\"" % cpu_map
113

    
114
      # build the result string in format 'cpus = [ "c", "c", "c" ]',
115
      # where each c is a physical CPU number, a range, a list, or any
116
      # combination
117
      return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list))
118

    
119
  @staticmethod
120
  def _RunXmList(xmlist_errors):
121
    """Helper function for L{_GetXMList} to run "xm list".
122

123
    """
124
    result = utils.RunCmd(["xm", "list"])
125
    if result.failed:
126
      logging.error("xm list failed (%s): %s", result.fail_reason,
127
                    result.output)
128
      xmlist_errors.append(result)
129
      raise utils.RetryAgain()
130

    
131
    # skip over the heading
132
    return result.stdout.splitlines()[1:]
133

    
134
  @classmethod
135
  def _GetXMList(cls, include_node):
136
    """Return the list of running instances.
137

138
    If the include_node argument is True, then we return information
139
    for dom0 also, otherwise we filter that from the return value.
140

141
    @return: list of (name, id, memory, vcpus, state, time spent)
142

143
    """
144
    xmlist_errors = []
145
    try:
146
      lines = utils.Retry(cls._RunXmList, 1, 5, args=(xmlist_errors, ))
147
    except utils.RetryTimeout:
148
      if xmlist_errors:
149
        xmlist_result = xmlist_errors.pop()
150

    
151
        errmsg = ("xm list failed, timeout exceeded (%s): %s" %
152
                  (xmlist_result.fail_reason, xmlist_result.output))
153
      else:
154
        errmsg = "xm list failed"
155

    
156
      raise errors.HypervisorError(errmsg)
157

    
158
    result = []
159
    for line in lines:
160
      # The format of lines is:
161
      # Name      ID Mem(MiB) VCPUs State  Time(s)
162
      # Domain-0   0  3418     4 r-----    266.2
163
      data = line.split()
164
      if len(data) != 6:
165
        raise errors.HypervisorError("Can't parse output of xm list,"
166
                                     " line: %s" % line)
167
      try:
168
        data[1] = int(data[1])
169
        data[2] = int(data[2])
170
        data[3] = int(data[3])
171
        data[5] = float(data[5])
172
      except (TypeError, ValueError), err:
173
        raise errors.HypervisorError("Can't parse output of xm list,"
174
                                     " line: %s, error: %s" % (line, err))
175

    
176
      # skip the Domain-0 (optional)
177
      if include_node or data[0] != "Domain-0":
178
        result.append(data)
179

    
180
    return result
181

    
182
  def ListInstances(self):
183
    """Get the list of running instances.
184

185
    """
186
    xm_list = self._GetXMList(False)
187
    names = [info[0] for info in xm_list]
188
    return names
189

    
190
  def GetInstanceInfo(self, instance_name):
191
    """Get instance properties.
192

193
    @param instance_name: the instance name
194

195
    @return: tuple (name, id, memory, vcpus, stat, times)
196

197
    """
198
    xm_list = self._GetXMList(instance_name == "Domain-0")
199
    result = None
200
    for data in xm_list:
201
      if data[0] == instance_name:
202
        result = data
203
        break
204
    return result
205

    
206
  def GetAllInstancesInfo(self):
207
    """Get properties of all instances.
208

209
    @return: list of tuples (name, id, memory, vcpus, stat, times)
210

211
    """
212
    xm_list = self._GetXMList(False)
213
    return xm_list
214

    
215
  def StartInstance(self, instance, block_devices, startup_paused):
216
    """Start an instance.
217

218
    """
219
    self._WriteConfigFile(instance, block_devices)
220
    cmd = ["xm", "create"]
221
    if startup_paused:
222
      cmd.extend(["--paused"])
223
    cmd.extend([instance.name])
224
    result = utils.RunCmd(cmd)
225

    
226
    if result.failed:
227
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
228
                                   (instance.name, result.fail_reason,
229
                                    result.output))
230

    
231
  def StopInstance(self, instance, force=False, retry=False, name=None):
232
    """Stop an instance.
233

234
    """
235
    if name is None:
236
      name = instance.name
237
    self._RemoveConfigFile(name)
238
    if force:
239
      command = ["xm", "destroy", name]
240
    else:
241
      command = ["xm", "shutdown", name]
242
    result = utils.RunCmd(command)
243

    
244
    if result.failed:
245
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
246
                                   (name, result.fail_reason, result.output))
247

    
248
  def RebootInstance(self, instance):
249
    """Reboot an instance.
250

251
    """
252
    ini_info = self.GetInstanceInfo(instance.name)
253

    
254
    if ini_info is None:
255
      raise errors.HypervisorError("Failed to reboot instance %s,"
256
                                   " not running" % instance.name)
257

    
258
    result = utils.RunCmd(["xm", "reboot", instance.name])
259
    if result.failed:
260
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
261
                                   (instance.name, result.fail_reason,
262
                                    result.output))
263

    
264
    def _CheckInstance():
265
      new_info = self.GetInstanceInfo(instance.name)
266

    
267
      # check if the domain ID has changed or the run time has decreased
268
      if (new_info is not None and
269
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
270
        return
271

    
272
      raise utils.RetryAgain()
273

    
274
    try:
275
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
276
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
277
    except utils.RetryTimeout:
278
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
279
                                   " did not reboot in the expected interval" %
280
                                   (instance.name, ))
281

    
282
  def GetNodeInfo(self):
283
    """Return information about the node.
284

285
    @return: a dict with the following keys (memory values in MiB):
286
          - memory_total: the total memory size on the node
287
          - memory_free: the available memory on the node for instances
288
          - memory_dom0: the memory used by the node itself, if available
289
          - nr_cpus: total number of CPUs
290
          - nr_nodes: in a NUMA system, the number of domains
291
          - nr_sockets: the number of physical CPU sockets in the node
292

293
    """
294
    # note: in xen 3, memory has changed to total_memory
295
    result = utils.RunCmd(["xm", "info"])
296
    if result.failed:
297
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
298
                    result.output)
299
      return None
300

    
301
    xmoutput = result.stdout.splitlines()
302
    result = {}
303
    cores_per_socket = threads_per_core = nr_cpus = None
304
    for line in xmoutput:
305
      splitfields = line.split(":", 1)
306

    
307
      if len(splitfields) > 1:
308
        key = splitfields[0].strip()
309
        val = splitfields[1].strip()
310
        if key == "memory" or key == "total_memory":
311
          result["memory_total"] = int(val)
312
        elif key == "free_memory":
313
          result["memory_free"] = int(val)
314
        elif key == "nr_cpus":
315
          nr_cpus = result["cpu_total"] = int(val)
316
        elif key == "nr_nodes":
317
          result["cpu_nodes"] = int(val)
318
        elif key == "cores_per_socket":
319
          cores_per_socket = int(val)
320
        elif key == "threads_per_core":
321
          threads_per_core = int(val)
322

    
323
    if (cores_per_socket is not None and
324
        threads_per_core is not None and nr_cpus is not None):
325
      result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
326

    
327
    dom0_info = self.GetInstanceInfo("Domain-0")
328
    if dom0_info is not None:
329
      result["memory_dom0"] = dom0_info[2]
330

    
331
    return result
332

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

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

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

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

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

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

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

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

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

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

    
392
    return disk_data
393

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

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

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

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

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

415
    """
416
    pass
417

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

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

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

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

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

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

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

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

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

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

    
458
    args = ["xm", "migrate", "-p", "%d" % port]
459
    if live:
460
      args.append("-l")
461
    args.extend([instance.name, target])
462
    result = utils.RunCmd(args)
463
    if result.failed:
464
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
465
                                   (instance.name, result.output))
466

    
467
  def FinalizeMigrationSource(self, instance, success, live):
468
    """Finalize the instance migration on the source node.
469

470
    @type instance: L{objects.Instance}
471
    @param instance: the instance that was migrated
472
    @type success: bool
473
    @param success: whether the migration succeeded or not
474
    @type live: bool
475
    @param live: whether the user requested a live migration or not
476

477
    """
478
    # pylint: disable=W0613
479
    if success:
480
      # remove old xen file after migration succeeded
481
      try:
482
        self._RemoveConfigFile(instance.name)
483
      except EnvironmentError:
484
        logging.exception("Failure while removing instance config file")
485

    
486
  def GetMigrationStatus(self, instance):
487
    """Get the migration status
488

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

493
    @type instance: L{objects.Instance}
494
    @param instance: the instance that is being migrated
495
    @rtype: L{objects.MigrationStatus}
496
    @return: the status of the current migration (one of
497
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
498
             progress info that can be retrieved from the hypervisor
499

500
    """
501
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
502

    
503
  @classmethod
504
  def PowercycleNode(cls):
505
    """Xen-specific powercycle.
506

507
    This first does a Linux reboot (which triggers automatically a Xen
508
    reboot), and if that fails it tries to do a Xen reboot. The reason
509
    we don't try a Xen reboot first is that the xen reboot launches an
510
    external command which connects to the Xen hypervisor, and that
511
    won't work in case the root filesystem is broken and/or the xend
512
    daemon is not working.
513

514
    """
515
    try:
516
      cls.LinuxPowercycle()
517
    finally:
518
      utils.RunCmd(["xm", "debug", "R"])
519

    
520

    
521
class XenPvmHypervisor(XenHypervisor):
522
  """Xen PVM hypervisor interface"""
523

    
524
  PARAMETERS = {
525
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
526
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
527
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
528
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
529
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
530
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
531
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
532
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
533
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
534
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
535
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
536
    constants.HV_REBOOT_BEHAVIOR:
537
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
538
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
539
    }
540

    
541
  @classmethod
542
  def _WriteConfigFile(cls, instance, block_devices):
543
    """Write the Xen config file for the instance.
544

545
    """
546
    hvp = instance.hvparams
547
    config = StringIO()
548
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
549

    
550
    # if bootloader is True, use bootloader instead of kernel and ramdisk
551
    # parameters.
552
    if hvp[constants.HV_USE_BOOTLOADER]:
553
      # bootloader handling
554
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
555
      if bootloader_path:
556
        config.write("bootloader = '%s'\n" % bootloader_path)
557
      else:
558
        raise errors.HypervisorError("Bootloader enabled, but missing"
559
                                     " bootloader path")
560

    
561
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
562
      if bootloader_args:
563
        config.write("bootargs = '%s'\n" % bootloader_args)
564
    else:
565
      # kernel handling
566
      kpath = hvp[constants.HV_KERNEL_PATH]
567
      config.write("kernel = '%s'\n" % kpath)
568

    
569
      # initrd handling
570
      initrd_path = hvp[constants.HV_INITRD_PATH]
571
      if initrd_path:
572
        config.write("ramdisk = '%s'\n" % initrd_path)
573

    
574
    # rest of the settings
575
    config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
576
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
577
    cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK])
578
    if cpu_pinning:
579
      config.write("%s\n" % cpu_pinning)
580

    
581
    config.write("name = '%s'\n" % instance.name)
582

    
583
    vif_data = []
584
    for nic in instance.nics:
585
      nic_str = "mac=%s" % (nic.mac)
586
      ip = getattr(nic, "ip", None)
587
      if ip is not None:
588
        nic_str += ", ip=%s" % ip
589
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
590
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
591
      vif_data.append("'%s'" % nic_str)
592

    
593
    disk_data = cls._GetConfigFileDiskData(block_devices,
594
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
595

    
596
    config.write("vif = [%s]\n" % ",".join(vif_data))
597
    config.write("disk = [%s]\n" % ",".join(disk_data))
598

    
599
    if hvp[constants.HV_ROOT_PATH]:
600
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
601
    config.write("on_poweroff = 'destroy'\n")
602
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
603
      config.write("on_reboot = 'restart'\n")
604
    else:
605
      config.write("on_reboot = 'destroy'\n")
606
    config.write("on_crash = 'restart'\n")
607
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
608
    # just in case it exists
609
    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
610
    try:
611
      utils.WriteFile("/etc/xen/%s" % instance.name, data=config.getvalue())
612
    except EnvironmentError, err:
613
      raise errors.HypervisorError("Cannot write Xen instance confile"
614
                                   " file /etc/xen/%s: %s" %
615
                                   (instance.name, err))
616

    
617
    return True
618

    
619

    
620
class XenHvmHypervisor(XenHypervisor):
621
  """Xen HVM hypervisor interface"""
622

    
623
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
624
    constants.VNC_PASSWORD_FILE,
625
    ]
626

    
627
  PARAMETERS = {
628
    constants.HV_ACPI: hv_base.NO_CHECK,
629
    constants.HV_BOOT_ORDER: (True, ) +
630
      (lambda x: x and len(x.strip("acdn")) == 0,
631
       "Invalid boot order specified, must be one or more of [acdn]",
632
       None, None),
633
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
634
    constants.HV_DISK_TYPE:
635
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
636
    constants.HV_NIC_TYPE:
637
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
638
    constants.HV_PAE: hv_base.NO_CHECK,
639
    constants.HV_VNC_BIND_ADDRESS:
640
      (False, netutils.IP4Address.IsValid,
641
       "VNC bind address is not a valid IP address", None, None),
642
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
643
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
644
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
645
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
646
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
647
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
648
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
649
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
650
    constants.HV_REBOOT_BEHAVIOR:
651
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
652
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
653
    }
654

    
655
  @classmethod
656
  def _WriteConfigFile(cls, instance, block_devices):
657
    """Create a Xen 3.1 HVM config file.
658

659
    """
660
    hvp = instance.hvparams
661

    
662
    config = StringIO()
663
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
664

    
665
    # kernel handling
666
    kpath = hvp[constants.HV_KERNEL_PATH]
667
    config.write("kernel = '%s'\n" % kpath)
668

    
669
    config.write("builder = 'hvm'\n")
670
    config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
671
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
672
    cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK])
673
    if cpu_pinning:
674
      config.write("%s\n" % cpu_pinning)
675

    
676
    config.write("name = '%s'\n" % instance.name)
677
    if hvp[constants.HV_PAE]:
678
      config.write("pae = 1\n")
679
    else:
680
      config.write("pae = 0\n")
681
    if hvp[constants.HV_ACPI]:
682
      config.write("acpi = 1\n")
683
    else:
684
      config.write("acpi = 0\n")
685
    config.write("apic = 1\n")
686
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
687
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
688
    config.write("sdl = 0\n")
689
    config.write("usb = 1\n")
690
    config.write("usbdevice = 'tablet'\n")
691
    config.write("vnc = 1\n")
692
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
693
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
694
    else:
695
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
696

    
697
    if instance.network_port > constants.VNC_BASE_PORT:
698
      display = instance.network_port - constants.VNC_BASE_PORT
699
      config.write("vncdisplay = %s\n" % display)
700
      config.write("vncunused = 0\n")
701
    else:
702
      config.write("# vncdisplay = 1\n")
703
      config.write("vncunused = 1\n")
704

    
705
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
706
    try:
707
      password = utils.ReadFile(vnc_pwd_file)
708
    except EnvironmentError, err:
709
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
710
                                   (vnc_pwd_file, err))
711

    
712
    config.write("vncpasswd = '%s'\n" % password.rstrip())
713

    
714
    config.write("serial = 'pty'\n")
715
    if hvp[constants.HV_USE_LOCALTIME]:
716
      config.write("localtime = 1\n")
717

    
718
    vif_data = []
719
    nic_type = hvp[constants.HV_NIC_TYPE]
720
    if nic_type is None:
721
      # ensure old instances don't change
722
      nic_type_str = ", type=ioemu"
723
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
724
      nic_type_str = ", type=paravirtualized"
725
    else:
726
      nic_type_str = ", model=%s, type=ioemu" % nic_type
727
    for nic in instance.nics:
728
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
729
      ip = getattr(nic, "ip", None)
730
      if ip is not None:
731
        nic_str += ", ip=%s" % ip
732
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
733
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
734
      vif_data.append("'%s'" % nic_str)
735

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

    
738
    disk_data = cls._GetConfigFileDiskData(block_devices,
739
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
740

    
741
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
742
    if iso_path:
743
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
744
      disk_data.append(iso)
745

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

    
748
    config.write("on_poweroff = 'destroy'\n")
749
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
750
      config.write("on_reboot = 'restart'\n")
751
    else:
752
      config.write("on_reboot = 'destroy'\n")
753
    config.write("on_crash = 'restart'\n")
754
    # just in case it exists
755
    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
756
    try:
757
      utils.WriteFile("/etc/xen/%s" % instance.name,
758
                      data=config.getvalue())
759
    except EnvironmentError, err:
760
      raise errors.HypervisorError("Cannot write Xen instance confile"
761
                                   " file /etc/xen/%s: %s" %
762
                                   (instance.name, err))
763

    
764
    return True