hv_xen.py 29.9 KB
Newer Older
1
2
3
#
#

4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.


"""Xen hypervisors

"""

26
import logging
27
28
29
30
31
from cStringIO import StringIO

from ganeti import constants
from ganeti import errors
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
Guido Trotter's avatar
Guido Trotter committed
37
from ganeti import ssconf
38
39


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


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

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

  """
53
  CAN_MIGRATE = True
Iustin Pop's avatar
Iustin Pop committed
54
55
  REBOOT_RETRY_COUNT = 60
  REBOOT_RETRY_INTERVAL = 10
56

Guido Trotter's avatar
Guido Trotter committed
57
  ANCILLARY_FILES = [
58
59
60
    XEND_CONFIG_FILE,
    XL_CONFIG_FILE,
    VIF_BRIDGE_SCRIPT,
Guido Trotter's avatar
Guido Trotter committed
61
    ]
62
63
  ANCILLARY_FILES_OPT = [
    XL_CONFIG_FILE,
Guido Trotter's avatar
Guido Trotter committed
64
65
    ]

66
67
68
69
70
71
72
73
74
75
76
77
  @staticmethod
  def _ConfigFileName(instance_name):
    """Get the config file name for an instance.

    @param instance_name: instance name
    @type instance_name: str
    @return: fully qualified path to instance config file
    @rtype: str

    """
    return "/etc/xen/%s" % instance_name

Iustin Pop's avatar
Iustin Pop committed
78
  @classmethod
79
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
80
81
82
83
84
    """Write the Xen config file for the instance.

    """
    raise NotImplementedError

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Iustin Pop's avatar
Iustin Pop committed
172
    If the include_node argument is True, then we return information
173
174
    for dom0 also, otherwise we filter that from the return value.

Iustin Pop's avatar
Iustin Pop committed
175
    @return: list of (name, id, memory, vcpus, state, time spent)
176
177

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

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

      raise errors.HypervisorError(errmsg)
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205

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

      # skip the Domain-0 (optional)
211
      if include_node or data[0] != _DOM0_NAME:
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
        result.append(data)

    return result

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

    """
    xm_list = self._GetXMList(False)
    names = [info[0] for info in xm_list]
    return names

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

Iustin Pop's avatar
Iustin Pop committed
227
228
229
    @param instance_name: the instance name

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

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

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

Iustin Pop's avatar
Iustin Pop committed
243
244
    @return: list of tuples (name, id, memory, vcpus, stat, times)

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

249
  def StartInstance(self, instance, block_devices, startup_paused):
Iustin Pop's avatar
Iustin Pop committed
250
251
252
    """Start an instance.

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

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

266
  def StopInstance(self, instance, force=False, retry=False, name=None):
Iustin Pop's avatar
Iustin Pop committed
267
268
269
    """Stop an instance.

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

    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):
Iustin Pop's avatar
Iustin Pop committed
284
285
286
    """Reboot an instance.

    """
Iustin Pop's avatar
Iustin Pop committed
287
    ini_info = self.GetInstanceInfo(instance.name)
288

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

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

    def _CheckInstance():
Iustin Pop's avatar
Iustin Pop committed
300
      new_info = self.GetInstanceInfo(instance.name)
301
302

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

307
308
309
310
311
312
      raise utils.RetryAgain()

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

317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
  def BalloonInstanceMemory(self, instance, mem):
    """Balloon an instance memory to a certain value.

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

    """
    cmd = [constants.XEN_CMD, "mem-set", instance.name, mem]
    result = utils.RunCmd(cmd)
    if result.failed:
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
                                   (instance.name, result.fail_reason,
                                    result.output))
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
    cmd.append(XenHypervisor._ConfigFileName(instance.name))
    result = utils.RunCmd(cmd)
    if result.failed:
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
                                   (instance.name, result.fail_reason,
                                    result.output))

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

343
    @return: a dict with the following keys (memory values in MiB):
Iustin Pop's avatar
Iustin Pop committed
344
345
346
          - memory_total: the total memory size on the node
          - memory_free: the available memory on the node for instances
          - memory_dom0: the memory used by the node itself, if available
347
348
349
          - nr_cpus: total number of CPUs
          - nr_nodes: in a NUMA system, the number of domains
          - 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
356
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
                    result.output)
357
358
359
360
      return None

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

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

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

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

391
    if None not in [cores_per_socket, threads_per_core, nr_cpus]:
Iustin Pop's avatar
Iustin Pop committed
392
      result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
393

394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
    total_instmem = 0
    for (name, _, mem, vcpus, _, _) in self._GetXMList(True):
      if name == _DOM0_NAME:
        result["memory_dom0"] = mem
        result["dom0_cpus"] = vcpus

      # Include Dom0 in total memory usage
      total_instmem += mem

    if memory_free is not None:
      result["memory_free"] = memory_free

    if memory_total is not None:
      result["memory_total"] = memory_total

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

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

416
417
    return result

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

    """
423
424
425
    return objects.InstanceConsole(instance=instance.name,
                                   kind=constants.CONS_SSH,
                                   host=instance.primary_node,
Michael Hanselmann's avatar
Michael Hanselmann committed
426
                                   user=constants.SSH_CONSOLE_USER,
427
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
428
                                            constants.XEN_CMD, instance.name])
429
430
431
432
433
434
435

  def Verify(self):
    """Verify the hypervisor.

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

    """
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
443
444
445
446
    """Get disk directive for xen config file.

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

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

Iustin Pop's avatar
Iustin Pop committed
453
    @return: string containing disk directive for xen instance config file
454
455
456
457
458
459
460

    """
    FILE_DRIVER_MAP = {
      constants.FD_LOOP: "file",
      constants.FD_BLKTAP: "tap:aio",
      }
    disk_data = []
461
462
463
    if len(block_devices) > 24:
      # 'z' - 'a' = 24
      raise errors.HypervisorError("Too many disks")
Iustin Pop's avatar
Iustin Pop committed
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
467
468
469
      if cfdev.mode == constants.DISK_RDWR:
        mode = "w"
      else:
        mode = "r"
470
      if cfdev.dev_type == constants.LD_FILE:
471
472
        line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
                                  dev_path, sd_name, mode)
473
      else:
474
        line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
475
476
477
478
      disk_data.append(line)

    return disk_data

479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
  def MigrationInfo(self, instance):
    """Get instance information to perform a migration.

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

    """
    return self._ReadConfigFile(instance.name)

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

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

    """
    pass

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

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

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

    """
    if success:
      self._WriteConfigFileStatic(instance.name, info)

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

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

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

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
540
    if (constants.XEN_CMD == constants.XEN_CMD_XM and
        not netutils.TcpPing(target, port, live_port_needed=True)):
541
542
543
      raise errors.HypervisorError("Remote host %s not listening on port"
                                   " %s, cannot migrate" % (target, port))

544
    # FIXME: migrate must be upgraded for transitioning to "xl" (xen 4.1).
545
    #        This should be reworked in Ganeti 2.7
546
    #  ssh must recognize the key of the target host for the migration
547
548
549
550
551
552
    args = [constants.XEN_CMD, "migrate"]
    if constants.XEN_CMD == constants.XEN_CMD_XM:
      args.extend(["-p", "%d" % port])
      if live:
        args.append("-l")
    elif constants.XEN_CMD == constants.XEN_CMD_XL:
Guido Trotter's avatar
Guido Trotter committed
553
554
      cluster_name = ssconf.SimpleStore().GetClusterName()
      args.extend(["-s", constants.XL_SSH_CMD % cluster_name])
555
556
557
558
559
      args.extend(["-C", self._ConfigFileName(instance.name)])
    else:
      raise errors.HypervisorError("Unsupported xen command: %s" %
                                   constants.XEN_CMD)

560
    args.extend([instance.name, target])
561
562
563
    result = utils.RunCmd(args)
    if result.failed:
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
564
                                   (instance.name, result.output))
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600

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

    @type instance: L{objects.Instance}
    @param instance: the instance that was migrated
    @type success: bool
    @param success: whether the migration succeeded or not
    @type live: bool
    @param live: whether the user requested a live migration or not

    """
    # pylint: disable=W0613
    if success:
      # remove old xen file after migration succeeded
      try:
        self._RemoveConfigFile(instance.name)
      except EnvironmentError:
        logging.exception("Failure while removing instance config file")

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

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

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

    """
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
601

Iustin Pop's avatar
Iustin Pop committed
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
  @classmethod
  def PowercycleNode(cls):
    """Xen-specific powercycle.

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

    """
    try:
      cls.LinuxPowercycle()
    finally:
617
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
Iustin Pop's avatar
Iustin Pop committed
618

619
620
621
622

class XenPvmHypervisor(XenHypervisor):
  """Xen PVM hypervisor interface"""

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

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

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

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

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

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

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

690
691
692
693
    config.write("name = '%s'\n" % instance.name)

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

702
703
    disk_data = cls._GetConfigFileDiskData(block_devices,
                                           hvp[constants.HV_BLOCKDEV_PREFIX])
704

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

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

719
720
721
722
723
724
    return True


class XenHvmHypervisor(XenHypervisor):
  """Xen HVM hypervisor interface"""

725
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
726
    pathutils.VNC_PASSWORD_FILE,
727
    ]
728
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
729
    pathutils.VNC_PASSWORD_FILE,
730
    ]
Guido Trotter's avatar
Guido Trotter committed
731

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

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

    """
770
771
    hvp = instance.hvparams

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

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

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

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

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

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

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

    config.write("serial = 'pty'\n")
832
833
    if hvp[constants.HV_USE_LOCALTIME]:
      config.write("localtime = 1\n")
834
835

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

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

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

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

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

878
    return True