Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 406a9c91

History | View | Annotate | Download (27.5 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
    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
    memory_total = None
329
    memory_free = None
330

    
331
    for line in xmoutput:
332
      splitfields = line.split(":", 1)
333

    
334
      if len(splitfields) > 1:
335
        key = splitfields[0].strip()
336
        val = splitfields[1].strip()
337

    
338
        # note: in xen 3, memory has changed to total_memory
339
        if key == "memory" or key == "total_memory":
340
          memory_total = int(val)
341
        elif key == "free_memory":
342
          memory_free = int(val)
343
        elif key == "nr_cpus":
344
          nr_cpus = result["cpu_total"] = int(val)
345
        elif key == "nr_nodes":
346
          result["cpu_nodes"] = int(val)
347
        elif key == "cores_per_socket":
348
          cores_per_socket = int(val)
349
        elif key == "threads_per_core":
350
          threads_per_core = int(val)
351
        elif key == "xen_major":
352
          xen_major = int(val)
353
        elif key == "xen_minor":
354
          xen_minor = int(val)
355

    
356
    if None not in [cores_per_socket, threads_per_core, nr_cpus]:
357
      result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
358

    
359
    total_instmem = 0
360
    for (name, _, mem, vcpus, _, _) in self._GetXMList(True):
361
      if name == _DOM0_NAME:
362
        result["memory_dom0"] = mem
363
        result["dom0_cpus"] = vcpus
364

    
365
      # Include Dom0 in total memory usage
366
      total_instmem += mem
367

    
368
    if memory_free is not None:
369
      result["memory_free"] = memory_free
370

    
371
    if memory_total is not None:
372
      result["memory_total"] = memory_total
373

    
374
    # Calculate memory used by hypervisor
375
    if None not in [memory_total, memory_free, total_instmem]:
376
      result["memory_hv"] = memory_total - memory_free - total_instmem
377

    
378
    if not (xen_major is None or xen_minor is None):
379
      result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
380

    
381
    return result
382

    
383
  @classmethod
384
  def GetInstanceConsole(cls, instance, hvparams, beparams):
385
    """Return a command for connecting to the console of an instance.
386

387
    """
388
    return objects.InstanceConsole(instance=instance.name,
389
                                   kind=constants.CONS_SSH,
390
                                   host=instance.primary_node,
391
                                   user=constants.GANETI_RUNAS,
392
                                   command=[constants.XM_CONSOLE_WRAPPER,
393
                                            instance.name])
394

    
395
  def Verify(self):
396
    """Verify the hypervisor.
397

398
    For Xen, this verifies that the xend process is running.
399

400
    """
401
    result = utils.RunCmd([constants.XEN_CMD, "info"])
402
    if result.failed:
403
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
404

    
405
  @staticmethod
406
  def _GetConfigFileDiskData(block_devices, blockdev_prefix):
407
    """Get disk directive for xen config file.
408

409
    This method builds the xen config disk directive according to the
410
    given disk_template and block_devices.
411

412
    @param block_devices: list of tuples (cfdev, rldev):
413
        - cfdev: dict containing ganeti config disk part
414
        - rldev: ganeti.bdev.BlockDev object
415
    @param blockdev_prefix: a string containing blockdevice prefix,
416
                            e.g. "sd" for /dev/sda
417

418
    @return: string containing disk directive for xen instance config file
419

420
    """
421
    FILE_DRIVER_MAP = {
422
      constants.FD_LOOP: "file",
423
      constants.FD_BLKTAP: "tap:aio",
424
      }
425
    disk_data = []
426
    if len(block_devices) > 24:
427
      # 'z' - 'a' = 24
428
      raise errors.HypervisorError("Too many disks")
429
    namespace = [blockdev_prefix + chr(i + ord("a")) for i in range(24)]
430
    for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
431
      if cfdev.mode == constants.DISK_RDWR:
432
        mode = "w"
433
      else:
434
        mode = "r"
435
      if cfdev.dev_type == constants.LD_FILE:
436
        line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
437
                                  dev_path, sd_name, mode)
438
      else:
439
        line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
440
      disk_data.append(line)
441

    
442
    return disk_data
443

    
444
  def MigrationInfo(self, instance):
445
    """Get instance information to perform a migration.
446

447
    @type instance: L{objects.Instance}
448
    @param instance: instance to be migrated
449
    @rtype: string
450
    @return: content of the xen config file
451

452
    """
453
    return self._ReadConfigFile(instance.name)
454

    
455
  def AcceptInstance(self, instance, info, target):
456
    """Prepare to accept an instance.
457

458
    @type instance: L{objects.Instance}
459
    @param instance: instance to be accepted
460
    @type info: string
461
    @param info: content of the xen config file on the source node
462
    @type target: string
463
    @param target: target host (usually ip), on this node
464

465
    """
466
    pass
467

    
468
  def FinalizeMigrationDst(self, instance, info, success):
469
    """Finalize an instance migration.
470

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

474
    @type instance: L{objects.Instance}
475
    @param instance: instance whose migration is being finalized
476
    @type info: string
477
    @param info: content of the xen config file on the source node
478
    @type success: boolean
479
    @param success: whether the migration was a success or a failure
480

481
    """
482
    if success:
483
      self._WriteConfigFileStatic(instance.name, info)
484

    
485
  def MigrateInstance(self, instance, target, live):
486
    """Migrate an instance to a target node.
487

488
    The migration will not be attempted if the instance is not
489
    currently running.
490

491
    @type instance: L{objects.Instance}
492
    @param instance: the instance to be migrated
493
    @type target: string
494
    @param target: ip address of the target node
495
    @type live: boolean
496
    @param live: perform a live migration
497

498
    """
499
    if self.GetInstanceInfo(instance.name) is None:
500
      raise errors.HypervisorError("Instance not running, cannot migrate")
501

    
502
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
503

    
504
    if not netutils.TcpPing(target, port, live_port_needed=True):
505
      raise errors.HypervisorError("Remote host %s not listening on port"
506
                                   " %s, cannot migrate" % (target, port))
507

    
508
    # FIXME: migrate must be upgraded for transitioning to "xl" (xen 4.1).
509
    #  -l doesn't exist anymore
510
    #  -p doesn't exist anymore
511
    #  -C config_file must be passed
512
    #  ssh must recognize the key of the target host for the migration
513
    args = [constants.XEN_CMD, "migrate", "-p", "%d" % port]
514
    if live:
515
      args.append("-l")
516
    args.extend([instance.name, target])
517
    result = utils.RunCmd(args)
518
    if result.failed:
519
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
520
                                   (instance.name, result.output))
521

    
522
  def FinalizeMigrationSource(self, instance, success, live):
523
    """Finalize the instance migration on the source node.
524

525
    @type instance: L{objects.Instance}
526
    @param instance: the instance that was migrated
527
    @type success: bool
528
    @param success: whether the migration succeeded or not
529
    @type live: bool
530
    @param live: whether the user requested a live migration or not
531

532
    """
533
    # pylint: disable=W0613
534
    if success:
535
      # remove old xen file after migration succeeded
536
      try:
537
        self._RemoveConfigFile(instance.name)
538
      except EnvironmentError:
539
        logging.exception("Failure while removing instance config file")
540

    
541
  def GetMigrationStatus(self, instance):
542
    """Get the migration status
543

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

548
    @type instance: L{objects.Instance}
549
    @param instance: the instance that is being migrated
550
    @rtype: L{objects.MigrationStatus}
551
    @return: the status of the current migration (one of
552
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
553
             progress info that can be retrieved from the hypervisor
554

555
    """
556
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
557

    
558
  @classmethod
559
  def PowercycleNode(cls):
560
    """Xen-specific powercycle.
561

562
    This first does a Linux reboot (which triggers automatically a Xen
563
    reboot), and if that fails it tries to do a Xen reboot. The reason
564
    we don't try a Xen reboot first is that the xen reboot launches an
565
    external command which connects to the Xen hypervisor, and that
566
    won't work in case the root filesystem is broken and/or the xend
567
    daemon is not working.
568

569
    """
570
    try:
571
      cls.LinuxPowercycle()
572
    finally:
573
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
574

    
575

    
576
class XenPvmHypervisor(XenHypervisor):
577
  """Xen PVM hypervisor interface"""
578

    
579
  PARAMETERS = {
580
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
581
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
582
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
583
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
584
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
585
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
586
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
587
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
588
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
589
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
590
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
591
    constants.HV_REBOOT_BEHAVIOR:
592
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
593
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
594
    }
595

    
596
  @classmethod
597
  def _WriteConfigFile(cls, instance, block_devices):
598
    """Write the Xen config file for the instance.
599

600
    """
601
    hvp = instance.hvparams
602
    config = StringIO()
603
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
604

    
605
    # if bootloader is True, use bootloader instead of kernel and ramdisk
606
    # parameters.
607
    if hvp[constants.HV_USE_BOOTLOADER]:
608
      # bootloader handling
609
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
610
      if bootloader_path:
611
        config.write("bootloader = '%s'\n" % bootloader_path)
612
      else:
613
        raise errors.HypervisorError("Bootloader enabled, but missing"
614
                                     " bootloader path")
615

    
616
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
617
      if bootloader_args:
618
        config.write("bootargs = '%s'\n" % bootloader_args)
619
    else:
620
      # kernel handling
621
      kpath = hvp[constants.HV_KERNEL_PATH]
622
      config.write("kernel = '%s'\n" % kpath)
623

    
624
      # initrd handling
625
      initrd_path = hvp[constants.HV_INITRD_PATH]
626
      if initrd_path:
627
        config.write("ramdisk = '%s'\n" % initrd_path)
628

    
629
    # rest of the settings
630
    config.write("memory = %d\n" % instance.beparams[constants.BE_MAXMEM])
631
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
632
    cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK])
633
    if cpu_pinning:
634
      config.write("%s\n" % cpu_pinning)
635

    
636
    config.write("name = '%s'\n" % instance.name)
637

    
638
    vif_data = []
639
    for nic in instance.nics:
640
      nic_str = "mac=%s" % (nic.mac)
641
      ip = getattr(nic, "ip", None)
642
      if ip is not None:
643
        nic_str += ", ip=%s" % ip
644
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
645
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
646
      vif_data.append("'%s'" % nic_str)
647

    
648
    disk_data = cls._GetConfigFileDiskData(block_devices,
649
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
650

    
651
    config.write("vif = [%s]\n" % ",".join(vif_data))
652
    config.write("disk = [%s]\n" % ",".join(disk_data))
653

    
654
    if hvp[constants.HV_ROOT_PATH]:
655
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
656
    config.write("on_poweroff = 'destroy'\n")
657
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
658
      config.write("on_reboot = 'restart'\n")
659
    else:
660
      config.write("on_reboot = 'destroy'\n")
661
    config.write("on_crash = 'restart'\n")
662
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
663
    # just in case it exists
664
    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
665
    try:
666
      utils.WriteFile(cls._ConfigFileName(instance.name),
667
                      data=config.getvalue())
668
    except EnvironmentError, err:
669
      raise errors.HypervisorError("Cannot write Xen instance confile"
670
                                   " file %s: %s" %
671
                                   (cls._ConfigFileName(instance.name), err))
672

    
673
    return True
674

    
675

    
676
class XenHvmHypervisor(XenHypervisor):
677
  """Xen HVM hypervisor interface"""
678

    
679
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
680
    constants.VNC_PASSWORD_FILE,
681
    ]
682
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
683
    constants.VNC_PASSWORD_FILE,
684
    ]
685

    
686
  PARAMETERS = {
687
    constants.HV_ACPI: hv_base.NO_CHECK,
688
    constants.HV_BOOT_ORDER: (True, ) +
689
      (lambda x: x and len(x.strip("acdn")) == 0,
690
       "Invalid boot order specified, must be one or more of [acdn]",
691
       None, None),
692
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
693
    constants.HV_DISK_TYPE:
694
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
695
    constants.HV_NIC_TYPE:
696
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
697
    constants.HV_PAE: hv_base.NO_CHECK,
698
    constants.HV_VNC_BIND_ADDRESS:
699
      (False, netutils.IP4Address.IsValid,
700
       "VNC bind address is not a valid IP address", None, None),
701
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
702
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
703
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
704
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
705
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
706
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
707
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
708
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
709
    constants.HV_REBOOT_BEHAVIOR:
710
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
711
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
712
    }
713

    
714
  @classmethod
715
  def _WriteConfigFile(cls, instance, block_devices):
716
    """Create a Xen 3.1 HVM config file.
717

718
    """
719
    hvp = instance.hvparams
720

    
721
    config = StringIO()
722
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
723

    
724
    # kernel handling
725
    kpath = hvp[constants.HV_KERNEL_PATH]
726
    config.write("kernel = '%s'\n" % kpath)
727

    
728
    config.write("builder = 'hvm'\n")
729
    config.write("memory = %d\n" % instance.beparams[constants.BE_MAXMEM])
730
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
731
    cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK])
732
    if cpu_pinning:
733
      config.write("%s\n" % cpu_pinning)
734

    
735
    config.write("name = '%s'\n" % instance.name)
736
    if hvp[constants.HV_PAE]:
737
      config.write("pae = 1\n")
738
    else:
739
      config.write("pae = 0\n")
740
    if hvp[constants.HV_ACPI]:
741
      config.write("acpi = 1\n")
742
    else:
743
      config.write("acpi = 0\n")
744
    config.write("apic = 1\n")
745
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
746
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
747
    config.write("sdl = 0\n")
748
    config.write("usb = 1\n")
749
    config.write("usbdevice = 'tablet'\n")
750
    config.write("vnc = 1\n")
751
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
752
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
753
    else:
754
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
755

    
756
    if instance.network_port > constants.VNC_BASE_PORT:
757
      display = instance.network_port - constants.VNC_BASE_PORT
758
      config.write("vncdisplay = %s\n" % display)
759
      config.write("vncunused = 0\n")
760
    else:
761
      config.write("# vncdisplay = 1\n")
762
      config.write("vncunused = 1\n")
763

    
764
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
765
    try:
766
      password = utils.ReadFile(vnc_pwd_file)
767
    except EnvironmentError, err:
768
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
769
                                   (vnc_pwd_file, err))
770

    
771
    config.write("vncpasswd = '%s'\n" % password.rstrip())
772

    
773
    config.write("serial = 'pty'\n")
774
    if hvp[constants.HV_USE_LOCALTIME]:
775
      config.write("localtime = 1\n")
776

    
777
    vif_data = []
778
    nic_type = hvp[constants.HV_NIC_TYPE]
779
    if nic_type is None:
780
      # ensure old instances don't change
781
      nic_type_str = ", type=ioemu"
782
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
783
      nic_type_str = ", type=paravirtualized"
784
    else:
785
      nic_type_str = ", model=%s, type=ioemu" % nic_type
786
    for nic in instance.nics:
787
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
788
      ip = getattr(nic, "ip", None)
789
      if ip is not None:
790
        nic_str += ", ip=%s" % ip
791
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
792
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
793
      vif_data.append("'%s'" % nic_str)
794

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

    
797
    disk_data = cls._GetConfigFileDiskData(block_devices,
798
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
799

    
800
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
801
    if iso_path:
802
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
803
      disk_data.append(iso)
804

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

    
807
    config.write("on_poweroff = 'destroy'\n")
808
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
809
      config.write("on_reboot = 'restart'\n")
810
    else:
811
      config.write("on_reboot = 'destroy'\n")
812
    config.write("on_crash = 'restart'\n")
813
    # just in case it exists
814
    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
815
    try:
816
      utils.WriteFile(cls._ConfigFileName(instance.name),
817
                      data=config.getvalue())
818
    except EnvironmentError, err:
819
      raise errors.HypervisorError("Cannot write Xen instance confile"
820
                                   " file %s: %s" %
821
                                   (cls._ConfigFileName(instance.name), err))
822

    
823
    return True