Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ dbb4f850

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 vcluster
37
from ganeti import ssconf
38

    
39

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

    
45

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

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

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

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

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

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

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

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

82
    """
83
    raise NotImplementedError
84

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

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

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

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

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

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

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

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

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

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

    
141
      def _GetCPUMap(vcpu):
142
        if vcpu[0] == constants.CPU_PINNING_ALL_VAL:
143
          cpu_map = constants.CPU_PINNING_ALL_XEN
144
        else:
145
          cpu_map = ",".join(map(str, vcpu))
146
        return "\"%s\"" % cpu_map
147

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

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

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

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

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

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

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

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

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

    
190
      raise errors.HypervisorError(errmsg)
191

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

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

    
214
    return result
215

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

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

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

227
    @param instance_name: the instance name
228

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
307
      raise utils.RetryAgain()
308

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
416
    return result
417

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

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

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

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

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

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

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

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

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

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

    
477
    return disk_data
478

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

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

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

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

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

500
    """
501
    pass
502

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
616

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

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

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

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

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

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

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

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

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

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

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

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

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

    
716
    return True
717

    
718

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

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

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

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

766
    """
767
    hvp = instance.hvparams
768

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

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

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

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

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

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

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

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

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

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

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

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

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

    
875
    return True