Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ cd04dfd2

History | View | Annotate | Download (29.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
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
    filename = XenHypervisor._ConfigFileName(instance_name)
141

    
142
    try:
143
      file_content = utils.ReadFile(filename)
144
    except EnvironmentError, err:
145
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
146

    
147
    return file_content
148

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

153
    """
154
    utils.RemoveFile(XenHypervisor._ConfigFileName(instance_name))
155

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

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

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

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

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

178
    @return: list of (name, id, memory, vcpus, state, time spent)
179

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

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

    
193
      raise errors.HypervisorError(errmsg)
194

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

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

    
217
    return result
218

    
219
  def ListInstances(self):
220
    """Get the list of running instances.
221

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

    
227
  def GetInstanceInfo(self, instance_name):
228
    """Get instance properties.
229

230
    @param instance_name: the instance name
231

232
    @return: tuple (name, id, memory, vcpus, stat, times)
233

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

    
243
  def GetAllInstancesInfo(self):
244
    """Get properties of all instances.
245

246
    @return: list of tuples (name, id, memory, vcpus, stat, times)
247

248
    """
249
    xm_list = self._GetXMList(False)
250
    return xm_list
251

    
252
  def StartInstance(self, instance, block_devices, startup_paused):
253
    """Start an instance.
254

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

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

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

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

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

    
286
  def RebootInstance(self, instance):
287
    """Reboot an instance.
288

289
    """
290
    ini_info = self.GetInstanceInfo(instance.name)
291

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

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

    
302
    def _CheckInstance():
303
      new_info = self.GetInstanceInfo(instance.name)
304

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

    
310
      raise utils.RetryAgain()
311

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

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

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

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

    
343
  def GetNodeInfo(self):
344
    """Return information about the node.
345

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

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

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

    
369
    for line in xmoutput:
370
      splitfields = line.split(":", 1)
371

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

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

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

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

    
403
      # Include Dom0 in total memory usage
404
      total_instmem += mem
405

    
406
    if memory_free is not None:
407
      result["memory_free"] = memory_free
408

    
409
    if memory_total is not None:
410
      result["memory_total"] = memory_total
411

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

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

    
419
    return result
420

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

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

    
433
  def Verify(self):
434
    """Verify the hypervisor.
435

436
    For Xen, this verifies that the xend process is running.
437

438
    @return: Problem description if something is wrong, C{None} otherwise
439

440
    """
441
    result = utils.RunCmd([constants.XEN_CMD, "info"])
442
    if result.failed:
443
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
444

    
445
    return None
446

    
447
  @staticmethod
448
  def _GetConfigFileDiskData(block_devices, blockdev_prefix):
449
    """Get disk directive for xen config file.
450

451
    This method builds the xen config disk directive according to the
452
    given disk_template and block_devices.
453

454
    @param block_devices: list of tuples (cfdev, rldev):
455
        - cfdev: dict containing ganeti config disk part
456
        - rldev: ganeti.bdev.BlockDev object
457
    @param blockdev_prefix: a string containing blockdevice prefix,
458
                            e.g. "sd" for /dev/sda
459

460
    @return: string containing disk directive for xen instance config file
461

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

    
484
    return disk_data
485

    
486
  def MigrationInfo(self, instance):
487
    """Get instance information to perform a migration.
488

489
    @type instance: L{objects.Instance}
490
    @param instance: instance to be migrated
491
    @rtype: string
492
    @return: content of the xen config file
493

494
    """
495
    return self._ReadConfigFile(instance.name)
496

    
497
  def AcceptInstance(self, instance, info, target):
498
    """Prepare to accept an instance.
499

500
    @type instance: L{objects.Instance}
501
    @param instance: instance to be accepted
502
    @type info: string
503
    @param info: content of the xen config file on the source node
504
    @type target: string
505
    @param target: target host (usually ip), on this node
506

507
    """
508
    pass
509

    
510
  def FinalizeMigrationDst(self, instance, info, success):
511
    """Finalize an instance migration.
512

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

516
    @type instance: L{objects.Instance}
517
    @param instance: instance whose migration is being finalized
518
    @type info: string
519
    @param info: content of the xen config file on the source node
520
    @type success: boolean
521
    @param success: whether the migration was a success or a failure
522

523
    """
524
    if success:
525
      self._WriteConfigFileStatic(instance.name, info)
526

    
527
  def MigrateInstance(self, instance, target, live):
528
    """Migrate an instance to a target node.
529

530
    The migration will not be attempted if the instance is not
531
    currently running.
532

533
    @type instance: L{objects.Instance}
534
    @param instance: the instance to be migrated
535
    @type target: string
536
    @param target: ip address of the target node
537
    @type live: boolean
538
    @param live: perform a live migration
539

540
    """
541
    if self.GetInstanceInfo(instance.name) is None:
542
      raise errors.HypervisorError("Instance not running, cannot migrate")
543

    
544
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
545

    
546
    if (constants.XEN_CMD == constants.XEN_CMD_XM and
547
        not netutils.TcpPing(target, port, live_port_needed=True)):
548
      raise errors.HypervisorError("Remote host %s not listening on port"
549
                                   " %s, cannot migrate" % (target, port))
550

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

    
564
    args.extend([instance.name, target])
565
    result = utils.RunCmd(args)
566
    if result.failed:
567
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
568
                                   (instance.name, result.output))
569

    
570
  def FinalizeMigrationSource(self, instance, success, live):
571
    """Finalize the instance migration on the source node.
572

573
    @type instance: L{objects.Instance}
574
    @param instance: the instance that was migrated
575
    @type success: bool
576
    @param success: whether the migration succeeded or not
577
    @type live: bool
578
    @param live: whether the user requested a live migration or not
579

580
    """
581
    # pylint: disable=W0613
582
    if success:
583
      # remove old xen file after migration succeeded
584
      try:
585
        self._RemoveConfigFile(instance.name)
586
      except EnvironmentError:
587
        logging.exception("Failure while removing instance config file")
588

    
589
  def GetMigrationStatus(self, instance):
590
    """Get the migration status
591

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

596
    @type instance: L{objects.Instance}
597
    @param instance: the instance that is being migrated
598
    @rtype: L{objects.MigrationStatus}
599
    @return: the status of the current migration (one of
600
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
601
             progress info that can be retrieved from the hypervisor
602

603
    """
604
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
605

    
606
  @classmethod
607
  def PowercycleNode(cls):
608
    """Xen-specific powercycle.
609

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

617
    """
618
    try:
619
      cls.LinuxPowercycle()
620
    finally:
621
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
622

    
623

    
624
class XenPvmHypervisor(XenHypervisor):
625
  """Xen PVM hypervisor interface"""
626

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

    
647
  @classmethod
648
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
649
    """Write the Xen config file for the instance.
650

651
    """
652
    hvp = instance.hvparams
653
    config = StringIO()
654
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
655

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

    
667
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
668
      if bootloader_args:
669
        config.write("bootargs = '%s'\n" % bootloader_args)
670
    else:
671
      # kernel handling
672
      kpath = hvp[constants.HV_KERNEL_PATH]
673
      config.write("kernel = '%s'\n" % kpath)
674

    
675
      # initrd handling
676
      initrd_path = hvp[constants.HV_INITRD_PATH]
677
      if initrd_path:
678
        config.write("ramdisk = '%s'\n" % initrd_path)
679

    
680
    # rest of the settings
681
    config.write("memory = %d\n" % startup_memory)
682
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
683
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
684
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
685
    if cpu_pinning:
686
      config.write("%s\n" % cpu_pinning)
687
    cpu_cap = hvp[constants.HV_CPU_CAP]
688
    if cpu_cap:
689
      config.write("cpu_cap=%d\n" % cpu_cap)
690
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
691
    if cpu_weight:
692
      config.write("cpu_weight=%d\n" % cpu_weight)
693

    
694
    config.write("name = '%s'\n" % instance.name)
695

    
696
    vif_data = []
697
    for nic in instance.nics:
698
      nic_str = "mac=%s" % (nic.mac)
699
      ip = getattr(nic, "ip", None)
700
      if ip is not None:
701
        nic_str += ", ip=%s" % ip
702
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
703
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
704
      vif_data.append("'%s'" % nic_str)
705

    
706
    disk_data = cls._GetConfigFileDiskData(block_devices,
707
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
708

    
709
    config.write("vif = [%s]\n" % ",".join(vif_data))
710
    config.write("disk = [%s]\n" % ",".join(disk_data))
711

    
712
    if hvp[constants.HV_ROOT_PATH]:
713
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
714
    config.write("on_poweroff = 'destroy'\n")
715
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
716
      config.write("on_reboot = 'restart'\n")
717
    else:
718
      config.write("on_reboot = 'destroy'\n")
719
    config.write("on_crash = 'restart'\n")
720
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
721
    cls._WriteConfigFileStatic(instance.name, config.getvalue())
722

    
723
    return True
724

    
725

    
726
class XenHvmHypervisor(XenHypervisor):
727
  """Xen HVM hypervisor interface"""
728

    
729
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
730
    pathutils.VNC_PASSWORD_FILE,
731
    ]
732
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
733
    pathutils.VNC_PASSWORD_FILE,
734
    ]
735

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

    
769
  @classmethod
770
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
771
    """Create a Xen 3.1 HVM config file.
772

773
    """
774
    hvp = instance.hvparams
775

    
776
    config = StringIO()
777
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
778

    
779
    # kernel handling
780
    kpath = hvp[constants.HV_KERNEL_PATH]
781
    config.write("kernel = '%s'\n" % kpath)
782

    
783
    config.write("builder = 'hvm'\n")
784
    config.write("memory = %d\n" % startup_memory)
785
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
786
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
787
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
788
    if cpu_pinning:
789
      config.write("%s\n" % cpu_pinning)
790
    cpu_cap = hvp[constants.HV_CPU_CAP]
791
    if cpu_cap:
792
      config.write("cpu_cap=%d\n" % cpu_cap)
793
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
794
    if cpu_weight:
795
      config.write("cpu_weight=%d\n" % cpu_weight)
796

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

    
818
    if instance.network_port > constants.VNC_BASE_PORT:
819
      display = instance.network_port - constants.VNC_BASE_PORT
820
      config.write("vncdisplay = %s\n" % display)
821
      config.write("vncunused = 0\n")
822
    else:
823
      config.write("# vncdisplay = 1\n")
824
      config.write("vncunused = 1\n")
825

    
826
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
827
    try:
828
      password = utils.ReadFile(vnc_pwd_file)
829
    except EnvironmentError, err:
830
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
831
                                   (vnc_pwd_file, err))
832

    
833
    config.write("vncpasswd = '%s'\n" % password.rstrip())
834

    
835
    config.write("serial = 'pty'\n")
836
    if hvp[constants.HV_USE_LOCALTIME]:
837
      config.write("localtime = 1\n")
838

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

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

    
859
    disk_data = cls._GetConfigFileDiskData(block_devices,
860
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
861

    
862
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
863
    if iso_path:
864
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
865
      disk_data.append(iso)
866

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

    
882
    return True