Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 0e1e0b6a

History | View | Annotate | Download (28.8 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

    
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=[pathutils.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 not netutils.TcpPing(target, port, live_port_needed=True):
537
      raise errors.HypervisorError("Remote host %s not listening on port"
538
                                   " %s, cannot migrate" % (target, port))
539

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

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

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

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

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

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

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

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

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

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

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

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

    
613

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

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

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

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

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

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

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

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

    
675
    config.write("name = '%s'\n" % instance.name)
676

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

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

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

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

    
704
    return True
705

    
706

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

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

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

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

751
    """
752
    hvp = instance.hvparams
753

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

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

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

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

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

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

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

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

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

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

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

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

    
839
    config.write("disk = [%s]\n" % (",".join(disk_data)))
840
    # Add PCI passthrough
841
    pci_pass_arr = []
842
    pci_pass = hvp[constants.HV_PASSTHROUGH]
843
    if pci_pass:
844
      pci_pass_arr = pci_pass.split(";")
845
      config.write("pci = %s\n" % pci_pass_arr)
846
    config.write("on_poweroff = 'destroy'\n")
847
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
848
      config.write("on_reboot = 'restart'\n")
849
    else:
850
      config.write("on_reboot = 'destroy'\n")
851
    config.write("on_crash = 'restart'\n")
852
    cls._WriteConfigFileStatic(instance.name, config.getvalue())
853

    
854
    return True