Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 053c356a

History | View | Annotate | Download (28.7 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 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
from ganeti import ssconf
36

    
37

    
38
XEND_CONFIG_FILE = "/etc/xen/xend-config.sxp"
39
XL_CONFIG_FILE = "/etc/xen/xl.conf"
40
VIF_BRIDGE_SCRIPT = "/etc/xen/scripts/vif-bridge"
41
_DOM0_NAME = "Domain-0"
42

    
43

    
44
class XenHypervisor(hv_base.BaseHypervisor):
45
  """Xen generic hypervisor interface
46

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

50
  """
51
  CAN_MIGRATE = True
52
  REBOOT_RETRY_COUNT = 60
53
  REBOOT_RETRY_INTERVAL = 10
54

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

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

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

73
    """
74
    return "/etc/xen/%s" % instance_name
75

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

80
    """
81
    raise NotImplementedError
82

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

87
    This version of the function just writes the config file from static data.
88

89
    """
90
    # just in case it exists
91
    utils.RemoveFile("/etc/xen/auto/%s" % instance_name)
92
    cfg_file = XenHypervisor._ConfigFileName(instance_name)
93
    try:
94
      utils.WriteFile(cfg_file, data=data)
95
    except EnvironmentError, err:
96
      raise errors.HypervisorError("Cannot write Xen instance configuration"
97
                                   " file %s: %s" % (cfg_file, err))
98

    
99
  @staticmethod
100
  def _ReadConfigFile(instance_name):
101
    """Returns the contents of the instance config file.
102

103
    """
104
    try:
105
      file_content = utils.ReadFile(
106
                       XenHypervisor._ConfigFileName(instance_name))
107
    except EnvironmentError, err:
108
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
109
    return file_content
110

    
111
  @staticmethod
112
  def _RemoveConfigFile(instance_name):
113
    """Remove the xen configuration file.
114

115
    """
116
    utils.RemoveFile(XenHypervisor._ConfigFileName(instance_name))
117

    
118
  @classmethod
119
  def _CreateConfigCpus(cls, cpu_mask):
120
    """Create a CPU config string that's compatible with Xen's
121
    configuration file.
122

123
    """
124
    # Convert the string CPU mask to a list of list of int's
125
    cpu_list = utils.ParseMultiCpuMask(cpu_mask)
126

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

    
145
      # build the result string in format 'cpus = [ "c", "c", "c" ]',
146
      # where each c is a physical CPU number, a range, a list, or any
147
      # combination
148
      return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list))
149

    
150
  @staticmethod
151
  def _RunXmList(xmlist_errors):
152
    """Helper function for L{_GetXMList} to run "xm list".
153

154
    """
155
    result = utils.RunCmd([constants.XEN_CMD, "list"])
156
    if result.failed:
157
      logging.error("xm list failed (%s): %s", result.fail_reason,
158
                    result.output)
159
      xmlist_errors.append(result)
160
      raise utils.RetryAgain()
161

    
162
    # skip over the heading
163
    return result.stdout.splitlines()[1:]
164

    
165
  @classmethod
166
  def _GetXMList(cls, include_node):
167
    """Return the list of running instances.
168

169
    If the include_node argument is True, then we return information
170
    for dom0 also, otherwise we filter that from the return value.
171

172
    @return: list of (name, id, memory, vcpus, state, time spent)
173

174
    """
175
    xmlist_errors = []
176
    try:
177
      lines = utils.Retry(cls._RunXmList, 1, 5, args=(xmlist_errors, ))
178
    except utils.RetryTimeout:
179
      if xmlist_errors:
180
        xmlist_result = xmlist_errors.pop()
181

    
182
        errmsg = ("xm list failed, timeout exceeded (%s): %s" %
183
                  (xmlist_result.fail_reason, xmlist_result.output))
184
      else:
185
        errmsg = "xm list failed"
186

    
187
      raise errors.HypervisorError(errmsg)
188

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

    
207
      # skip the Domain-0 (optional)
208
      if include_node or data[0] != _DOM0_NAME:
209
        result.append(data)
210

    
211
    return result
212

    
213
  def ListInstances(self):
214
    """Get the list of running instances.
215

216
    """
217
    xm_list = self._GetXMList(False)
218
    names = [info[0] for info in xm_list]
219
    return names
220

    
221
  def GetInstanceInfo(self, instance_name):
222
    """Get instance properties.
223

224
    @param instance_name: the instance name
225

226
    @return: tuple (name, id, memory, vcpus, stat, times)
227

228
    """
229
    xm_list = self._GetXMList(instance_name == _DOM0_NAME)
230
    result = None
231
    for data in xm_list:
232
      if data[0] == instance_name:
233
        result = data
234
        break
235
    return result
236

    
237
  def GetAllInstancesInfo(self):
238
    """Get properties of all instances.
239

240
    @return: list of tuples (name, id, memory, vcpus, stat, times)
241

242
    """
243
    xm_list = self._GetXMList(False)
244
    return xm_list
245

    
246
  def StartInstance(self, instance, block_devices, startup_paused):
247
    """Start an instance.
248

249
    """
250
    startup_memory = self._InstanceStartupMemory(instance)
251
    self._WriteConfigFile(instance, startup_memory, block_devices)
252
    cmd = [constants.XEN_CMD, "create"]
253
    if startup_paused:
254
      cmd.extend(["-p"])
255
    cmd.extend([self._ConfigFileName(instance.name)])
256
    result = utils.RunCmd(cmd)
257

    
258
    if result.failed:
259
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
260
                                   (instance.name, result.fail_reason,
261
                                    result.output))
262

    
263
  def StopInstance(self, instance, force=False, retry=False, name=None):
264
    """Stop an instance.
265

266
    """
267
    if name is None:
268
      name = instance.name
269
    self._RemoveConfigFile(name)
270
    if force:
271
      command = [constants.XEN_CMD, "destroy", name]
272
    else:
273
      command = [constants.XEN_CMD, "shutdown", name]
274
    result = utils.RunCmd(command)
275

    
276
    if result.failed:
277
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
278
                                   (name, result.fail_reason, result.output))
279

    
280
  def RebootInstance(self, instance):
281
    """Reboot an instance.
282

283
    """
284
    ini_info = self.GetInstanceInfo(instance.name)
285

    
286
    if ini_info is None:
287
      raise errors.HypervisorError("Failed to reboot instance %s,"
288
                                   " not running" % instance.name)
289

    
290
    result = utils.RunCmd([constants.XEN_CMD, "reboot", instance.name])
291
    if result.failed:
292
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
293
                                   (instance.name, result.fail_reason,
294
                                    result.output))
295

    
296
    def _CheckInstance():
297
      new_info = self.GetInstanceInfo(instance.name)
298

    
299
      # check if the domain ID has changed or the run time has decreased
300
      if (new_info is not None and
301
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
302
        return
303

    
304
      raise utils.RetryAgain()
305

    
306
    try:
307
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
308
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
309
    except utils.RetryTimeout:
310
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
311
                                   " did not reboot in the expected interval" %
312
                                   (instance.name, ))
313

    
314
  def BalloonInstanceMemory(self, instance, mem):
315
    """Balloon an instance memory to a certain value.
316

317
    @type instance: L{objects.Instance}
318
    @param instance: instance to be accepted
319
    @type mem: int
320
    @param mem: actual memory size to use for instance runtime
321

322
    """
323
    cmd = [constants.XEN_CMD, "mem-set", instance.name, mem]
324
    result = utils.RunCmd(cmd)
325
    if result.failed:
326
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
327
                                   (instance.name, result.fail_reason,
328
                                    result.output))
329
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
330
    cmd.append(XenHypervisor._ConfigFileName(instance.name))
331
    result = utils.RunCmd(cmd)
332
    if result.failed:
333
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
334
                                   (instance.name, result.fail_reason,
335
                                    result.output))
336

    
337
  def GetNodeInfo(self):
338
    """Return information about the node.
339

340
    @return: a dict with the following keys (memory values in MiB):
341
          - memory_total: the total memory size on the node
342
          - memory_free: the available memory on the node for instances
343
          - memory_dom0: the memory used by the node itself, if available
344
          - nr_cpus: total number of CPUs
345
          - nr_nodes: in a NUMA system, the number of domains
346
          - nr_sockets: the number of physical CPU sockets in the node
347
          - hv_version: the hypervisor version in the form (major, minor)
348

349
    """
350
    result = utils.RunCmd([constants.XEN_CMD, "info"])
351
    if result.failed:
352
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
353
                    result.output)
354
      return None
355

    
356
    xmoutput = result.stdout.splitlines()
357
    result = {}
358
    cores_per_socket = threads_per_core = nr_cpus = None
359
    xen_major, xen_minor = None, None
360
    memory_total = None
361
    memory_free = None
362

    
363
    for line in xmoutput:
364
      splitfields = line.split(":", 1)
365

    
366
      if len(splitfields) > 1:
367
        key = splitfields[0].strip()
368
        val = splitfields[1].strip()
369

    
370
        # note: in xen 3, memory has changed to total_memory
371
        if key == "memory" or key == "total_memory":
372
          memory_total = int(val)
373
        elif key == "free_memory":
374
          memory_free = int(val)
375
        elif key == "nr_cpus":
376
          nr_cpus = result["cpu_total"] = int(val)
377
        elif key == "nr_nodes":
378
          result["cpu_nodes"] = int(val)
379
        elif key == "cores_per_socket":
380
          cores_per_socket = int(val)
381
        elif key == "threads_per_core":
382
          threads_per_core = int(val)
383
        elif key == "xen_major":
384
          xen_major = int(val)
385
        elif key == "xen_minor":
386
          xen_minor = int(val)
387

    
388
    if None not in [cores_per_socket, threads_per_core, nr_cpus]:
389
      result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
390

    
391
    total_instmem = 0
392
    for (name, _, mem, vcpus, _, _) in self._GetXMList(True):
393
      if name == _DOM0_NAME:
394
        result["memory_dom0"] = mem
395
        result["dom0_cpus"] = vcpus
396

    
397
      # Include Dom0 in total memory usage
398
      total_instmem += mem
399

    
400
    if memory_free is not None:
401
      result["memory_free"] = memory_free
402

    
403
    if memory_total is not None:
404
      result["memory_total"] = memory_total
405

    
406
    # Calculate memory used by hypervisor
407
    if None not in [memory_total, memory_free, total_instmem]:
408
      result["memory_hv"] = memory_total - memory_free - total_instmem
409

    
410
    if not (xen_major is None or xen_minor is None):
411
      result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
412

    
413
    return result
414

    
415
  @classmethod
416
  def GetInstanceConsole(cls, instance, hvparams, beparams):
417
    """Return a command for connecting to the console of an instance.
418

419
    """
420
    return objects.InstanceConsole(instance=instance.name,
421
                                   kind=constants.CONS_SSH,
422
                                   host=instance.primary_node,
423
                                   user=constants.GANETI_RUNAS,
424
                                   command=[constants.XM_CONSOLE_WRAPPER,
425
                                            instance.name])
426

    
427
  def Verify(self):
428
    """Verify the hypervisor.
429

430
    For Xen, this verifies that the xend process is running.
431

432
    """
433
    result = utils.RunCmd([constants.XEN_CMD, "info"])
434
    if result.failed:
435
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
436

    
437
  @staticmethod
438
  def _GetConfigFileDiskData(block_devices, blockdev_prefix):
439
    """Get disk directive for xen config file.
440

441
    This method builds the xen config disk directive according to the
442
    given disk_template and block_devices.
443

444
    @param block_devices: list of tuples (cfdev, rldev):
445
        - cfdev: dict containing ganeti config disk part
446
        - rldev: ganeti.bdev.BlockDev object
447
    @param blockdev_prefix: a string containing blockdevice prefix,
448
                            e.g. "sd" for /dev/sda
449

450
    @return: string containing disk directive for xen instance config file
451

452
    """
453
    FILE_DRIVER_MAP = {
454
      constants.FD_LOOP: "file",
455
      constants.FD_BLKTAP: "tap:aio",
456
      }
457
    disk_data = []
458
    if len(block_devices) > 24:
459
      # 'z' - 'a' = 24
460
      raise errors.HypervisorError("Too many disks")
461
    namespace = [blockdev_prefix + chr(i + ord("a")) for i in range(24)]
462
    for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
463
      if cfdev.mode == constants.DISK_RDWR:
464
        mode = "w"
465
      else:
466
        mode = "r"
467
      if cfdev.dev_type == constants.LD_FILE:
468
        line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
469
                                  dev_path, sd_name, mode)
470
      else:
471
        line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
472
      disk_data.append(line)
473

    
474
    return disk_data
475

    
476
  def MigrationInfo(self, instance):
477
    """Get instance information to perform a migration.
478

479
    @type instance: L{objects.Instance}
480
    @param instance: instance to be migrated
481
    @rtype: string
482
    @return: content of the xen config file
483

484
    """
485
    return self._ReadConfigFile(instance.name)
486

    
487
  def AcceptInstance(self, instance, info, target):
488
    """Prepare to accept an instance.
489

490
    @type instance: L{objects.Instance}
491
    @param instance: instance to be accepted
492
    @type info: string
493
    @param info: content of the xen config file on the source node
494
    @type target: string
495
    @param target: target host (usually ip), on this node
496

497
    """
498
    pass
499

    
500
  def FinalizeMigrationDst(self, instance, info, success):
501
    """Finalize an instance migration.
502

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

506
    @type instance: L{objects.Instance}
507
    @param instance: instance whose migration is being finalized
508
    @type info: string
509
    @param info: content of the xen config file on the source node
510
    @type success: boolean
511
    @param success: whether the migration was a success or a failure
512

513
    """
514
    if success:
515
      self._WriteConfigFileStatic(instance.name, info)
516

    
517
  def MigrateInstance(self, instance, target, live):
518
    """Migrate an instance to a target node.
519

520
    The migration will not be attempted if the instance is not
521
    currently running.
522

523
    @type instance: L{objects.Instance}
524
    @param instance: the instance to be migrated
525
    @type target: string
526
    @param target: ip address of the target node
527
    @type live: boolean
528
    @param live: perform a live migration
529

530
    """
531
    if self.GetInstanceInfo(instance.name) is None:
532
      raise errors.HypervisorError("Instance not running, cannot migrate")
533

    
534
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
535

    
536
    if (constants.XEN_CMD == constants.XEN_CMD_XM and
537
        not netutils.TcpPing(target, port, live_port_needed=True)):
538
      raise errors.HypervisorError("Remote host %s not listening on port"
539
                                   " %s, cannot migrate" % (target, port))
540

    
541
    # FIXME: migrate must be upgraded for transitioning to "xl" (xen 4.1).
542
    #        This should be reworked in Ganeti 2.7
543
    #  ssh must recognize the key of the target host for the migration
544
    args = [constants.XEN_CMD, "migrate"]
545
    if constants.XEN_CMD == constants.XEN_CMD_XM:
546
      args.extend(["-p", "%d" % port])
547
      if live:
548
        args.append("-l")
549
    elif constants.XEN_CMD == constants.XEN_CMD_XL:
550
      cluster_name = ssconf.SimpleStore().GetClusterName()
551
      args.extend(["-s", constants.XL_SSH_CMD % cluster_name])
552
      args.extend(["-C", self._ConfigFileName(instance.name)])
553
    else:
554
      raise errors.HypervisorError("Unsupported xen command: %s" %
555
                                   constants.XEN_CMD)
556

    
557
    args.extend([instance.name, target])
558
    result = utils.RunCmd(args)
559
    if result.failed:
560
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
561
                                   (instance.name, result.output))
562

    
563
  def FinalizeMigrationSource(self, instance, success, live):
564
    """Finalize the instance migration on the source node.
565

566
    @type instance: L{objects.Instance}
567
    @param instance: the instance that was migrated
568
    @type success: bool
569
    @param success: whether the migration succeeded or not
570
    @type live: bool
571
    @param live: whether the user requested a live migration or not
572

573
    """
574
    # pylint: disable=W0613
575
    if success:
576
      # remove old xen file after migration succeeded
577
      try:
578
        self._RemoveConfigFile(instance.name)
579
      except EnvironmentError:
580
        logging.exception("Failure while removing instance config file")
581

    
582
  def GetMigrationStatus(self, instance):
583
    """Get the migration status
584

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

589
    @type instance: L{objects.Instance}
590
    @param instance: the instance that is being migrated
591
    @rtype: L{objects.MigrationStatus}
592
    @return: the status of the current migration (one of
593
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
594
             progress info that can be retrieved from the hypervisor
595

596
    """
597
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
598

    
599
  @classmethod
600
  def PowercycleNode(cls):
601
    """Xen-specific powercycle.
602

603
    This first does a Linux reboot (which triggers automatically a Xen
604
    reboot), and if that fails it tries to do a Xen reboot. The reason
605
    we don't try a Xen reboot first is that the xen reboot launches an
606
    external command which connects to the Xen hypervisor, and that
607
    won't work in case the root filesystem is broken and/or the xend
608
    daemon is not working.
609

610
    """
611
    try:
612
      cls.LinuxPowercycle()
613
    finally:
614
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
615

    
616

    
617
class XenPvmHypervisor(XenHypervisor):
618
  """Xen PVM hypervisor interface"""
619

    
620
  PARAMETERS = {
621
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
622
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
623
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
624
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
625
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
626
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
627
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
628
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
629
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
630
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
631
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
632
    constants.HV_REBOOT_BEHAVIOR:
633
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
634
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
635
    }
636

    
637
  @classmethod
638
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
639
    """Write the Xen config file for the instance.
640

641
    """
642
    hvp = instance.hvparams
643
    config = StringIO()
644
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
645

    
646
    # if bootloader is True, use bootloader instead of kernel and ramdisk
647
    # parameters.
648
    if hvp[constants.HV_USE_BOOTLOADER]:
649
      # bootloader handling
650
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
651
      if bootloader_path:
652
        config.write("bootloader = '%s'\n" % bootloader_path)
653
      else:
654
        raise errors.HypervisorError("Bootloader enabled, but missing"
655
                                     " bootloader path")
656

    
657
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
658
      if bootloader_args:
659
        config.write("bootargs = '%s'\n" % bootloader_args)
660
    else:
661
      # kernel handling
662
      kpath = hvp[constants.HV_KERNEL_PATH]
663
      config.write("kernel = '%s'\n" % kpath)
664

    
665
      # initrd handling
666
      initrd_path = hvp[constants.HV_INITRD_PATH]
667
      if initrd_path:
668
        config.write("ramdisk = '%s'\n" % initrd_path)
669

    
670
    # rest of the settings
671
    config.write("memory = %d\n" % startup_memory)
672
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
673
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
674
    cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK])
675
    if cpu_pinning:
676
      config.write("%s\n" % cpu_pinning)
677

    
678
    config.write("name = '%s'\n" % instance.name)
679

    
680
    vif_data = []
681
    for nic in instance.nics:
682
      nic_str = "mac=%s" % (nic.mac)
683
      ip = getattr(nic, "ip", None)
684
      if ip is not None:
685
        nic_str += ", ip=%s" % ip
686
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
687
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
688
      vif_data.append("'%s'" % nic_str)
689

    
690
    disk_data = cls._GetConfigFileDiskData(block_devices,
691
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
692

    
693
    config.write("vif = [%s]\n" % ",".join(vif_data))
694
    config.write("disk = [%s]\n" % ",".join(disk_data))
695

    
696
    if hvp[constants.HV_ROOT_PATH]:
697
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
698
    config.write("on_poweroff = 'destroy'\n")
699
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
700
      config.write("on_reboot = 'restart'\n")
701
    else:
702
      config.write("on_reboot = 'destroy'\n")
703
    config.write("on_crash = 'restart'\n")
704
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
705
    cls._WriteConfigFileStatic(instance.name, config.getvalue())
706

    
707
    return True
708

    
709

    
710
class XenHvmHypervisor(XenHypervisor):
711
  """Xen HVM hypervisor interface"""
712

    
713
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
714
    constants.VNC_PASSWORD_FILE,
715
    ]
716
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
717
    constants.VNC_PASSWORD_FILE,
718
    ]
719

    
720
  PARAMETERS = {
721
    constants.HV_ACPI: hv_base.NO_CHECK,
722
    constants.HV_BOOT_ORDER: (True, ) +
723
      (lambda x: x and len(x.strip("acdn")) == 0,
724
       "Invalid boot order specified, must be one or more of [acdn]",
725
       None, None),
726
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
727
    constants.HV_DISK_TYPE:
728
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
729
    constants.HV_NIC_TYPE:
730
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
731
    constants.HV_PAE: hv_base.NO_CHECK,
732
    constants.HV_VNC_BIND_ADDRESS:
733
      (False, netutils.IP4Address.IsValid,
734
       "VNC bind address is not a valid IP address", None, None),
735
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
736
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
737
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
738
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
739
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
740
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
741
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
742
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
743
    constants.HV_REBOOT_BEHAVIOR:
744
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
745
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
746
    }
747

    
748
  @classmethod
749
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
750
    """Create a Xen 3.1 HVM config file.
751

752
    """
753
    hvp = instance.hvparams
754

    
755
    config = StringIO()
756
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
757

    
758
    # kernel handling
759
    kpath = hvp[constants.HV_KERNEL_PATH]
760
    config.write("kernel = '%s'\n" % kpath)
761

    
762
    config.write("builder = 'hvm'\n")
763
    config.write("memory = %d\n" % startup_memory)
764
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
765
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
766
    cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK])
767
    if cpu_pinning:
768
      config.write("%s\n" % cpu_pinning)
769

    
770
    config.write("name = '%s'\n" % instance.name)
771
    if hvp[constants.HV_PAE]:
772
      config.write("pae = 1\n")
773
    else:
774
      config.write("pae = 0\n")
775
    if hvp[constants.HV_ACPI]:
776
      config.write("acpi = 1\n")
777
    else:
778
      config.write("acpi = 0\n")
779
    config.write("apic = 1\n")
780
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
781
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
782
    config.write("sdl = 0\n")
783
    config.write("usb = 1\n")
784
    config.write("usbdevice = 'tablet'\n")
785
    config.write("vnc = 1\n")
786
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
787
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
788
    else:
789
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
790

    
791
    if instance.network_port > constants.VNC_BASE_PORT:
792
      display = instance.network_port - constants.VNC_BASE_PORT
793
      config.write("vncdisplay = %s\n" % display)
794
      config.write("vncunused = 0\n")
795
    else:
796
      config.write("# vncdisplay = 1\n")
797
      config.write("vncunused = 1\n")
798

    
799
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
800
    try:
801
      password = utils.ReadFile(vnc_pwd_file)
802
    except EnvironmentError, err:
803
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
804
                                   (vnc_pwd_file, err))
805

    
806
    config.write("vncpasswd = '%s'\n" % password.rstrip())
807

    
808
    config.write("serial = 'pty'\n")
809
    if hvp[constants.HV_USE_LOCALTIME]:
810
      config.write("localtime = 1\n")
811

    
812
    vif_data = []
813
    nic_type = hvp[constants.HV_NIC_TYPE]
814
    if nic_type is None:
815
      # ensure old instances don't change
816
      nic_type_str = ", type=ioemu"
817
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
818
      nic_type_str = ", type=paravirtualized"
819
    else:
820
      nic_type_str = ", model=%s, type=ioemu" % nic_type
821
    for nic in instance.nics:
822
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
823
      ip = getattr(nic, "ip", None)
824
      if ip is not None:
825
        nic_str += ", ip=%s" % ip
826
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
827
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
828
      vif_data.append("'%s'" % nic_str)
829

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

    
832
    disk_data = cls._GetConfigFileDiskData(block_devices,
833
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
834

    
835
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
836
    if iso_path:
837
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
838
      disk_data.append(iso)
839

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

    
842
    config.write("on_poweroff = 'destroy'\n")
843
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
844
      config.write("on_reboot = 'restart'\n")
845
    else:
846
      config.write("on_reboot = 'destroy'\n")
847
    config.write("on_crash = 'restart'\n")
848
    cls._WriteConfigFileStatic(instance.name, config.getvalue())
849

    
850
    return True