Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 347fa0f1

History | View | Annotate | Download (29.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 pathutils
36
from ganeti import ssconf
37

    
38

    
39
XEND_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xend-config.sxp")
40
XL_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xen/xl.conf")
41
VIF_BRIDGE_SCRIPT = utils.PathJoin(pathutils.XEN_CONFIG_DIR,
42
                                   "scripts/vif-bridge")
43
_DOM0_NAME = "Domain-0"
44

    
45

    
46
def _CreateConfigCpus(cpu_mask):
47
  """Create a CPU config string for Xen's config file.
48

49
  """
50
  # Convert the string CPU mask to a list of list of int's
51
  cpu_list = utils.ParseMultiCpuMask(cpu_mask)
52

    
53
  if len(cpu_list) == 1:
54
    all_cpu_mapping = cpu_list[0]
55
    if all_cpu_mapping == constants.CPU_PINNING_OFF:
56
      # If CPU pinning has 1 entry that's "all", then remove the
57
      # parameter from the config file
58
      return None
59
    else:
60
      # If CPU pinning has one non-all entry, mapping all vCPUS (the entire
61
      # VM) to one physical CPU, using format 'cpu = "C"'
62
      return "cpu = \"%s\"" % ",".join(map(str, all_cpu_mapping))
63
  else:
64

    
65
    def _GetCPUMap(vcpu):
66
      if vcpu[0] == constants.CPU_PINNING_ALL_VAL:
67
        cpu_map = constants.CPU_PINNING_ALL_XEN
68
      else:
69
        cpu_map = ",".join(map(str, vcpu))
70
      return "\"%s\"" % cpu_map
71

    
72
    # build the result string in format 'cpus = [ "c", "c", "c" ]',
73
    # where each c is a physical CPU number, a range, a list, or any
74
    # combination
75
    return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list))
76

    
77

    
78
class XenHypervisor(hv_base.BaseHypervisor):
79
  """Xen generic hypervisor interface
80

81
  This is the Xen base class used for both Xen PVM and HVM. It contains
82
  all the functionality that is identical for both.
83

84
  """
85
  CAN_MIGRATE = True
86
  REBOOT_RETRY_COUNT = 60
87
  REBOOT_RETRY_INTERVAL = 10
88

    
89
  ANCILLARY_FILES = [
90
    XEND_CONFIG_FILE,
91
    XL_CONFIG_FILE,
92
    VIF_BRIDGE_SCRIPT,
93
    ]
94
  ANCILLARY_FILES_OPT = [
95
    XL_CONFIG_FILE,
96
    ]
97

    
98
  @staticmethod
99
  def _ConfigFileName(instance_name):
100
    """Get the config file name for an instance.
101

102
    @param instance_name: instance name
103
    @type instance_name: str
104
    @return: fully qualified path to instance config file
105
    @rtype: str
106

107
    """
108
    return utils.PathJoin(pathutils.XEN_CONFIG_DIR, instance_name)
109

    
110
  @classmethod
111
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
112
    """Write the Xen config file for the instance.
113

114
    """
115
    raise NotImplementedError
116

    
117
  @staticmethod
118
  def _WriteConfigFileStatic(instance_name, data):
119
    """Write the Xen config file for the instance.
120

121
    This version of the function just writes the config file from static data.
122

123
    """
124
    # just in case it exists
125
    utils.RemoveFile(utils.PathJoin(pathutils.XEN_CONFIG_DIR, "auto",
126
                                    instance_name))
127

    
128
    cfg_file = XenHypervisor._ConfigFileName(instance_name)
129
    try:
130
      utils.WriteFile(cfg_file, data=data)
131
    except EnvironmentError, err:
132
      raise errors.HypervisorError("Cannot write Xen instance configuration"
133
                                   " file %s: %s" % (cfg_file, err))
134

    
135
  @staticmethod
136
  def _ReadConfigFile(instance_name):
137
    """Returns the contents of the instance config file.
138

139
    """
140
    try:
141
      file_content = utils.ReadFile(
142
                       XenHypervisor._ConfigFileName(instance_name))
143
    except EnvironmentError, err:
144
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
145
    return file_content
146

    
147
  @staticmethod
148
  def _RemoveConfigFile(instance_name):
149
    """Remove the xen configuration file.
150

151
    """
152
    utils.RemoveFile(XenHypervisor._ConfigFileName(instance_name))
153

    
154
  @staticmethod
155
  def _RunXmList(xmlist_errors):
156
    """Helper function for L{_GetXMList} to run "xm list".
157

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

    
166
    # skip over the heading
167
    return result.stdout.splitlines()[1:]
168

    
169
  @classmethod
170
  def _GetXMList(cls, include_node):
171
    """Return the list of running instances.
172

173
    If the include_node argument is True, then we return information
174
    for dom0 also, otherwise we filter that from the return value.
175

176
    @return: list of (name, id, memory, vcpus, state, time spent)
177

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

    
186
        errmsg = ("xm list failed, timeout exceeded (%s): %s" %
187
                  (xmlist_result.fail_reason, xmlist_result.output))
188
      else:
189
        errmsg = "xm list failed"
190

    
191
      raise errors.HypervisorError(errmsg)
192

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

    
211
      # skip the Domain-0 (optional)
212
      if include_node or data[0] != _DOM0_NAME:
213
        result.append(data)
214

    
215
    return result
216

    
217
  def ListInstances(self):
218
    """Get the list of running instances.
219

220
    """
221
    xm_list = self._GetXMList(False)
222
    names = [info[0] for info in xm_list]
223
    return names
224

    
225
  def GetInstanceInfo(self, instance_name):
226
    """Get instance properties.
227

228
    @param instance_name: the instance name
229

230
    @return: tuple (name, id, memory, vcpus, stat, times)
231

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

    
241
  def GetAllInstancesInfo(self):
242
    """Get properties of all instances.
243

244
    @return: list of tuples (name, id, memory, vcpus, stat, times)
245

246
    """
247
    xm_list = self._GetXMList(False)
248
    return xm_list
249

    
250
  def StartInstance(self, instance, block_devices, startup_paused):
251
    """Start an instance.
252

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

    
262
    if result.failed:
263
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
264
                                   (instance.name, result.fail_reason,
265
                                    result.output))
266

    
267
  def StopInstance(self, instance, force=False, retry=False, name=None):
268
    """Stop an instance.
269

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

    
280
    if result.failed:
281
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
282
                                   (name, result.fail_reason, result.output))
283

    
284
  def RebootInstance(self, instance):
285
    """Reboot an instance.
286

287
    """
288
    ini_info = self.GetInstanceInfo(instance.name)
289

    
290
    if ini_info is None:
291
      raise errors.HypervisorError("Failed to reboot instance %s,"
292
                                   " not running" % instance.name)
293

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

    
300
    def _CheckInstance():
301
      new_info = self.GetInstanceInfo(instance.name)
302

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

    
308
      raise utils.RetryAgain()
309

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

    
318
  def BalloonInstanceMemory(self, instance, mem):
319
    """Balloon an instance memory to a certain value.
320

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

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

    
341
  def GetNodeInfo(self):
342
    """Return information about the node.
343

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

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

    
360
    xmoutput = result.stdout.splitlines()
361
    result = {}
362
    cores_per_socket = threads_per_core = nr_cpus = None
363
    xen_major, xen_minor = None, None
364
    memory_total = None
365
    memory_free = None
366

    
367
    for line in xmoutput:
368
      splitfields = line.split(":", 1)
369

    
370
      if len(splitfields) > 1:
371
        key = splitfields[0].strip()
372
        val = splitfields[1].strip()
373

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

    
392
    if None not in [cores_per_socket, threads_per_core, nr_cpus]:
393
      result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
394

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

    
401
      # Include Dom0 in total memory usage
402
      total_instmem += mem
403

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

    
407
    if memory_total is not None:
408
      result["memory_total"] = memory_total
409

    
410
    # Calculate memory used by hypervisor
411
    if None not in [memory_total, memory_free, total_instmem]:
412
      result["memory_hv"] = memory_total - memory_free - total_instmem
413

    
414
    if not (xen_major is None or xen_minor is None):
415
      result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
416

    
417
    return result
418

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

423
    """
424
    return objects.InstanceConsole(instance=instance.name,
425
                                   kind=constants.CONS_SSH,
426
                                   host=instance.primary_node,
427
                                   user=constants.SSH_CONSOLE_USER,
428
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
429
                                            constants.XEN_CMD, instance.name])
430

    
431
  def Verify(self):
432
    """Verify the hypervisor.
433

434
    For Xen, this verifies that the xend process is running.
435

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

    
441
  @staticmethod
442
  def _GetConfigFileDiskData(block_devices, blockdev_prefix):
443
    """Get disk directive for xen config file.
444

445
    This method builds the xen config disk directive according to the
446
    given disk_template and block_devices.
447

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

454
    @return: string containing disk directive for xen instance config file
455

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

    
478
    return disk_data
479

    
480
  def MigrationInfo(self, instance):
481
    """Get instance information to perform a migration.
482

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

488
    """
489
    return self._ReadConfigFile(instance.name)
490

    
491
  def AcceptInstance(self, instance, info, target):
492
    """Prepare to accept an instance.
493

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

501
    """
502
    pass
503

    
504
  def FinalizeMigrationDst(self, instance, info, success):
505
    """Finalize an instance migration.
506

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

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

517
    """
518
    if success:
519
      self._WriteConfigFileStatic(instance.name, info)
520

    
521
  def MigrateInstance(self, instance, target, live):
522
    """Migrate an instance to a target node.
523

524
    The migration will not be attempted if the instance is not
525
    currently running.
526

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

534
    """
535
    if self.GetInstanceInfo(instance.name) is None:
536
      raise errors.HypervisorError("Instance not running, cannot migrate")
537

    
538
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
539

    
540
    if (constants.XEN_CMD == constants.XEN_CMD_XM and
541
        not netutils.TcpPing(target, port, live_port_needed=True)):
542
      raise errors.HypervisorError("Remote host %s not listening on port"
543
                                   " %s, cannot migrate" % (target, port))
544

    
545
    args = [constants.XEN_CMD, "migrate"]
546
    if constants.XEN_CMD == constants.XEN_CMD_XM:
547
      args.extend(["-p", "%d" % port])
548
      if live:
549
        args.append("-l")
550
    elif constants.XEN_CMD == constants.XEN_CMD_XL:
551
      cluster_name = ssconf.SimpleStore().GetClusterName()
552
      args.extend(["-s", constants.XL_SSH_CMD % cluster_name])
553
      args.extend(["-C", self._ConfigFileName(instance.name)])
554
    else:
555
      raise errors.HypervisorError("Unsupported xen command: %s" %
556
                                   constants.XEN_CMD)
557

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

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

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

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

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

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

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

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

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

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

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

    
617

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

    
621
  PARAMETERS = {
622
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
623
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
624
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
625
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
626
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
627
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
628
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
629
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
630
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
631
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
632
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
633
    constants.HV_REBOOT_BEHAVIOR:
634
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
635
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
636
    constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
637
    constants.HV_CPU_WEIGHT:
638
      (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
639
    }
640

    
641
  @classmethod
642
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
643
    """Write the Xen config file for the instance.
644

645
    """
646
    hvp = instance.hvparams
647
    config = StringIO()
648
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
649

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

    
661
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
662
      if bootloader_args:
663
        config.write("bootargs = '%s'\n" % bootloader_args)
664
    else:
665
      # kernel handling
666
      kpath = hvp[constants.HV_KERNEL_PATH]
667
      config.write("kernel = '%s'\n" % kpath)
668

    
669
      # initrd handling
670
      initrd_path = hvp[constants.HV_INITRD_PATH]
671
      if initrd_path:
672
        config.write("ramdisk = '%s'\n" % initrd_path)
673

    
674
    # rest of the settings
675
    config.write("memory = %d\n" % startup_memory)
676
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
677
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
678
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
679
    if cpu_pinning:
680
      config.write("%s\n" % cpu_pinning)
681
    cpu_cap = hvp[constants.HV_CPU_CAP]
682
    if cpu_cap:
683
      config.write("cpu_cap=%d\n" % cpu_cap)
684
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
685
    if cpu_weight:
686
      config.write("cpu_weight=%d\n" % cpu_weight)
687

    
688
    config.write("name = '%s'\n" % instance.name)
689

    
690
    vif_data = []
691
    for nic in instance.nics:
692
      nic_str = "mac=%s" % (nic.mac)
693
      ip = getattr(nic, "ip", None)
694
      if ip is not None:
695
        nic_str += ", ip=%s" % ip
696
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
697
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
698
      vif_data.append("'%s'" % nic_str)
699

    
700
    disk_data = cls._GetConfigFileDiskData(block_devices,
701
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
702

    
703
    config.write("vif = [%s]\n" % ",".join(vif_data))
704
    config.write("disk = [%s]\n" % ",".join(disk_data))
705

    
706
    if hvp[constants.HV_ROOT_PATH]:
707
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
708
    config.write("on_poweroff = 'destroy'\n")
709
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
710
      config.write("on_reboot = 'restart'\n")
711
    else:
712
      config.write("on_reboot = 'destroy'\n")
713
    config.write("on_crash = 'restart'\n")
714
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
715
    cls._WriteConfigFileStatic(instance.name, config.getvalue())
716

    
717
    return True
718

    
719

    
720
class XenHvmHypervisor(XenHypervisor):
721
  """Xen HVM hypervisor interface"""
722

    
723
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
724
    pathutils.VNC_PASSWORD_FILE,
725
    ]
726
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
727
    pathutils.VNC_PASSWORD_FILE,
728
    ]
729

    
730
  PARAMETERS = {
731
    constants.HV_ACPI: hv_base.NO_CHECK,
732
    constants.HV_BOOT_ORDER: (True, ) +
733
      (lambda x: x and len(x.strip("acdn")) == 0,
734
       "Invalid boot order specified, must be one or more of [acdn]",
735
       None, None),
736
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
737
    constants.HV_DISK_TYPE:
738
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
739
    constants.HV_NIC_TYPE:
740
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
741
    constants.HV_PAE: hv_base.NO_CHECK,
742
    constants.HV_VNC_BIND_ADDRESS:
743
      (False, netutils.IP4Address.IsValid,
744
       "VNC bind address is not a valid IP address", None, None),
745
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
746
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
747
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
748
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
749
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
750
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
751
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
752
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
753
    # Add PCI passthrough
754
    constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
755
    constants.HV_REBOOT_BEHAVIOR:
756
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
757
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
758
    constants.HV_CPU_CAP: hv_base.NO_CHECK,
759
    constants.HV_CPU_WEIGHT:
760
      (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
761
    }
762

    
763
  @classmethod
764
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
765
    """Create a Xen 3.1 HVM config file.
766

767
    """
768
    hvp = instance.hvparams
769

    
770
    config = StringIO()
771
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
772

    
773
    # kernel handling
774
    kpath = hvp[constants.HV_KERNEL_PATH]
775
    config.write("kernel = '%s'\n" % kpath)
776

    
777
    config.write("builder = 'hvm'\n")
778
    config.write("memory = %d\n" % startup_memory)
779
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
780
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
781
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
782
    if cpu_pinning:
783
      config.write("%s\n" % cpu_pinning)
784
    cpu_cap = hvp[constants.HV_CPU_CAP]
785
    if cpu_cap:
786
      config.write("cpu_cap=%d\n" % cpu_cap)
787
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
788
    if cpu_weight:
789
      config.write("cpu_weight=%d\n" % cpu_weight)
790

    
791
    config.write("name = '%s'\n" % instance.name)
792
    if hvp[constants.HV_PAE]:
793
      config.write("pae = 1\n")
794
    else:
795
      config.write("pae = 0\n")
796
    if hvp[constants.HV_ACPI]:
797
      config.write("acpi = 1\n")
798
    else:
799
      config.write("acpi = 0\n")
800
    config.write("apic = 1\n")
801
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
802
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
803
    config.write("sdl = 0\n")
804
    config.write("usb = 1\n")
805
    config.write("usbdevice = 'tablet'\n")
806
    config.write("vnc = 1\n")
807
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
808
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
809
    else:
810
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
811

    
812
    if instance.network_port > constants.VNC_BASE_PORT:
813
      display = instance.network_port - constants.VNC_BASE_PORT
814
      config.write("vncdisplay = %s\n" % display)
815
      config.write("vncunused = 0\n")
816
    else:
817
      config.write("# vncdisplay = 1\n")
818
      config.write("vncunused = 1\n")
819

    
820
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
821
    try:
822
      password = utils.ReadFile(vnc_pwd_file)
823
    except EnvironmentError, err:
824
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
825
                                   (vnc_pwd_file, err))
826

    
827
    config.write("vncpasswd = '%s'\n" % password.rstrip())
828

    
829
    config.write("serial = 'pty'\n")
830
    if hvp[constants.HV_USE_LOCALTIME]:
831
      config.write("localtime = 1\n")
832

    
833
    vif_data = []
834
    nic_type = hvp[constants.HV_NIC_TYPE]
835
    if nic_type is None:
836
      # ensure old instances don't change
837
      nic_type_str = ", type=ioemu"
838
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
839
      nic_type_str = ", type=paravirtualized"
840
    else:
841
      nic_type_str = ", model=%s, type=ioemu" % nic_type
842
    for nic in instance.nics:
843
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
844
      ip = getattr(nic, "ip", None)
845
      if ip is not None:
846
        nic_str += ", ip=%s" % ip
847
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
848
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
849
      vif_data.append("'%s'" % nic_str)
850

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

    
853
    disk_data = cls._GetConfigFileDiskData(block_devices,
854
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
855

    
856
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
857
    if iso_path:
858
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
859
      disk_data.append(iso)
860

    
861
    config.write("disk = [%s]\n" % (",".join(disk_data)))
862
    # Add PCI passthrough
863
    pci_pass_arr = []
864
    pci_pass = hvp[constants.HV_PASSTHROUGH]
865
    if pci_pass:
866
      pci_pass_arr = pci_pass.split(";")
867
      config.write("pci = %s\n" % pci_pass_arr)
868
    config.write("on_poweroff = 'destroy'\n")
869
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
870
      config.write("on_reboot = 'restart'\n")
871
    else:
872
      config.write("on_reboot = 'destroy'\n")
873
    config.write("on_crash = 'restart'\n")
874
    cls._WriteConfigFileStatic(instance.name, config.getvalue())
875

    
876
    return True