Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ cffbbae7

History | View | Annotate | Download (28.9 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 pathutils
36
from ganeti import vcluster
37

    
38

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

    
44

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

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

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

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

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

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

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

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

81
    """
82
    raise NotImplementedError
83

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
188
      raise errors.HypervisorError(errmsg)
189

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

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

    
212
    return result
213

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

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

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

225
    @param instance_name: the instance name
226

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
305
      raise utils.RetryAgain()
306

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
414
    return result
415

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

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

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

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

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

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

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

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

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

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

    
475
    return disk_data
476

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

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

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

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

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

498
    """
499
    pass
500

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

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

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

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

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

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

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

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

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

    
537
    if 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
      args.extend(["-C", self._ConfigFileName(instance.name)])
551
    else:
552
      raise errors.HypervisorError("Unsupported xen command: %s" %
553
                                   constants.XEN_CMD)
554

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

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

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

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

    
580
  def GetMigrationStatus(self, instance):
581
    """Get the migration status
582

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

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

594
    """
595
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
596

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

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

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

    
614

    
615
class XenPvmHypervisor(XenHypervisor):
616
  """Xen PVM hypervisor interface"""
617

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

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

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

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

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

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

    
668
    # rest of the settings
669
    config.write("memory = %d\n" % startup_memory)
670
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
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

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

    
688
    disk_data = cls._GetConfigFileDiskData(block_devices,
689
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
690

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

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

    
705
    return True
706

    
707

    
708
class XenHvmHypervisor(XenHypervisor):
709
  """Xen HVM hypervisor interface"""
710

    
711
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
712
    pathutils.VNC_PASSWORD_FILE,
713
    ]
714
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
715
    pathutils.VNC_PASSWORD_FILE,
716
    ]
717

    
718
  PARAMETERS = {
719
    constants.HV_ACPI: hv_base.NO_CHECK,
720
    constants.HV_BOOT_ORDER: (True, ) +
721
      (lambda x: x and len(x.strip("acdn")) == 0,
722
       "Invalid boot order specified, must be one or more of [acdn]",
723
       None, None),
724
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
725
    constants.HV_DISK_TYPE:
726
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
727
    constants.HV_NIC_TYPE:
728
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
729
    constants.HV_PAE: hv_base.NO_CHECK,
730
    constants.HV_VNC_BIND_ADDRESS:
731
      (False, netutils.IP4Address.IsValid,
732
       "VNC bind address is not a valid IP address", None, None),
733
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
734
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
735
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
736
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
737
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
738
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
739
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
740
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
741
    # Add PCI passthrough
742
    constants.HV_PASSTHROUGH: 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
    # Add PCI passthrough
842
    pci_pass_arr = []
843
    pci_pass = hvp[constants.HV_PASSTHROUGH]
844
    if pci_pass:
845
      pci_pass_arr = pci_pass.split(";")
846
      config.write("pci = %s\n" % pci_pass_arr)
847
    config.write("on_poweroff = 'destroy'\n")
848
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
849
      config.write("on_reboot = 'restart'\n")
850
    else:
851
      config.write("on_reboot = 'destroy'\n")
852
    config.write("on_crash = 'restart'\n")
853
    cls._WriteConfigFileStatic(instance.name, config.getvalue())
854

    
855
    return True