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

4
# Copyright (C) 2006, 2007, 2008, 2009, 2010 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
36


37
class XenHypervisor(hv_base.BaseHypervisor):
38
39
40
41
42
43
  """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.

  """
44
  CAN_MIGRATE = True
Iustin Pop's avatar
Iustin Pop committed
45
46
  REBOOT_RETRY_COUNT = 60
  REBOOT_RETRY_INTERVAL = 10
47

Guido Trotter's avatar
Guido Trotter committed
48
  ANCILLARY_FILES = [
Iustin Pop's avatar
Iustin Pop committed
49
    "/etc/xen/xend-config.sxp",
50
    "/etc/xen/xl.conf",
Iustin Pop's avatar
Iustin Pop committed
51
    "/etc/xen/scripts/vif-bridge",
Guido Trotter's avatar
Guido Trotter committed
52
53
    ]

54
55
56
57
58
59
60
61
62
63
64
65
  @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
66
  @classmethod
67
  def _WriteConfigFile(cls, instance, block_devices):
68
69
70
71
72
    """Write the Xen config file for the instance.

    """
    raise NotImplementedError

73
74
75
76
77
78
79
  @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.

    """
80
    utils.WriteFile(XenHypervisor._ConfigFileName(instance_name), data=data)
81
82
83
84
85
86
87

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

    """
    try:
88
89
      file_content = utils.ReadFile(
                       XenHypervisor._ConfigFileName(instance_name))
90
91
92
93
    except EnvironmentError, err:
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
    return file_content

94
  @staticmethod
95
  def _RemoveConfigFile(instance_name):
96
97
98
    """Remove the xen configuration file.

    """
99
    utils.RemoveFile(XenHypervisor._ConfigFileName(instance_name))
100

101
102
103
104
105
106
107
108
109
110
111
  @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]
112
      if all_cpu_mapping == constants.CPU_PINNING_OFF:
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
        # 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:
      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))

133
  @staticmethod
134
135
136
137
  def _RunXmList(xmlist_errors):
    """Helper function for L{_GetXMList} to run "xm list".

    """
138
    result = utils.RunCmd([constants.XEN_CMD, "list"])
139
140
141
142
143
144
145
146
147
148
149
    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):
150
151
    """Return the list of running instances.

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

Iustin Pop's avatar
Iustin Pop committed
155
    @return: list of (name, id, memory, vcpus, state, time spent)
156
157

    """
158
159
160
161
162
163
    xmlist_errors = []
    try:
      lines = utils.Retry(cls._RunXmList, 1, 5, args=(xmlist_errors, ))
    except utils.RetryTimeout:
      if xmlist_errors:
        xmlist_result = xmlist_errors.pop()
164

165
166
167
168
169
170
        errmsg = ("xm list failed, timeout exceeded (%s): %s" %
                  (xmlist_result.fail_reason, xmlist_result.output))
      else:
        errmsg = "xm list failed"

      raise errors.HypervisorError(errmsg)
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185

    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])
186
      except (TypeError, ValueError), err:
187
188
189
190
        raise errors.HypervisorError("Can't parse output of xm list,"
                                     " line: %s, error: %s" % (line, err))

      # skip the Domain-0 (optional)
Iustin Pop's avatar
Iustin Pop committed
191
      if include_node or data[0] != "Domain-0":
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
        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
207
208
209
    @param instance_name: the instance name

    @return: tuple (name, id, memory, vcpus, stat, times)
210
211

    """
Michael Hanselmann's avatar
Michael Hanselmann committed
212
    xm_list = self._GetXMList(instance_name == "Domain-0")
213
214
215
216
217
218
219
220
221
222
    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
223
224
    @return: list of tuples (name, id, memory, vcpus, stat, times)

225
226
227
228
    """
    xm_list = self._GetXMList(False)
    return xm_list

229
  def StartInstance(self, instance, block_devices, startup_paused):
Iustin Pop's avatar
Iustin Pop committed
230
231
232
    """Start an instance.

    """
233
    self._WriteConfigFile(instance, block_devices)
234
    cmd = [constants.XEN_CMD, "create"]
235
    if startup_paused:
236
237
      cmd.extend(["-p"])
    cmd.extend([self._ConfigFileName(instance.name)])
238
    result = utils.RunCmd(cmd)
239
240
241
242
243
244

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

245
  def StopInstance(self, instance, force=False, retry=False, name=None):
Iustin Pop's avatar
Iustin Pop committed
246
247
248
    """Stop an instance.

    """
249
250
251
    if name is None:
      name = instance.name
    self._RemoveConfigFile(name)
252
    if force:
253
      command = [constants.XEN_CMD, "destroy", name]
254
    else:
255
      command = [constants.XEN_CMD, "shutdown", name]
256
257
258
    result = utils.RunCmd(command)

    if result.failed:
259
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
260
                                   (name, result.fail_reason, result.output))
261
262

  def RebootInstance(self, instance):
Iustin Pop's avatar
Iustin Pop committed
263
264
265
    """Reboot an instance.

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

268
269
270
271
    if ini_info is None:
      raise errors.HypervisorError("Failed to reboot instance %s,"
                                   " not running" % instance.name)

272
    result = utils.RunCmd([constants.XEN_CMD, "reboot", instance.name])
273
    if result.failed:
274
275
276
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
                                   (instance.name, result.fail_reason,
                                    result.output))
277
278

    def _CheckInstance():
Iustin Pop's avatar
Iustin Pop committed
279
      new_info = self.GetInstanceInfo(instance.name)
280
281

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

286
287
288
289
290
291
      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
292
293
294
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
                                   " did not reboot in the expected interval" %
                                   (instance.name, ))
295
296
297
298

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

299
    @return: a dict with the following keys (memory values in MiB):
Iustin Pop's avatar
Iustin Pop committed
300
301
302
          - 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
303
304
305
          - 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
306
          - hv_version: the hypervisor version in the form (major, minor)
307
308
309

    """
    # note: in xen 3, memory has changed to total_memory
310
    result = utils.RunCmd([constants.XEN_CMD, "info"])
311
    if result.failed:
312
313
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
                    result.output)
314
315
316
317
      return None

    xmoutput = result.stdout.splitlines()
    result = {}
318
    cores_per_socket = threads_per_core = nr_cpus = None
319
    xen_major, xen_minor = None, None
320
321
322
323
324
325
    for line in xmoutput:
      splitfields = line.split(":", 1)

      if len(splitfields) > 1:
        key = splitfields[0].strip()
        val = splitfields[1].strip()
Iustin Pop's avatar
Iustin Pop committed
326
327
328
329
330
331
332
333
334
        if key == "memory" or key == "total_memory":
          result["memory_total"] = int(val)
        elif key == "free_memory":
          result["memory_free"] = int(val)
        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":
335
          cores_per_socket = int(val)
Iustin Pop's avatar
Iustin Pop committed
336
        elif key == "threads_per_core":
337
          threads_per_core = int(val)
338
339
340
341
        elif key == "xen_major":
          xen_major = int(val)
        elif key == "xen_minor":
          xen_minor = int(val)
342
343
344

    if (cores_per_socket is not None and
        threads_per_core is not None and nr_cpus is not None):
Iustin Pop's avatar
Iustin Pop committed
345
      result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
346

347
348
    dom0_info = self.GetInstanceInfo("Domain-0")
    if dom0_info is not None:
Iustin Pop's avatar
Iustin Pop committed
349
      result["memory_dom0"] = dom0_info[2]
350

351
352
353
    if not (xen_major is None or xen_minor is None):
      result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)

354
355
    return result

356
  @classmethod
357
  def GetInstanceConsole(cls, instance, hvparams, beparams):
358
359
360
    """Return a command for connecting to the console of an instance.

    """
361
362
363
364
    return objects.InstanceConsole(instance=instance.name,
                                   kind=constants.CONS_SSH,
                                   host=instance.primary_node,
                                   user=constants.GANETI_RUNAS,
365
366
                                   command=[constants.XM_CONSOLE_WRAPPER,
                                            instance.name])
367
368
369
370
371
372
373

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

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

    """
374
    result = utils.RunCmd([constants.XEN_CMD, "info"])
375
    if result.failed:
376
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
377
378

  @staticmethod
379
  def _GetConfigFileDiskData(block_devices, blockdev_prefix):
380
381
382
383
384
    """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
385
386
387
    @param block_devices: list of tuples (cfdev, rldev):
        - cfdev: dict containing ganeti config disk part
        - rldev: ganeti.bdev.BlockDev object
388
389
    @param blockdev_prefix: a string containing blockdevice prefix,
                            e.g. "sd" for /dev/sda
390

Iustin Pop's avatar
Iustin Pop committed
391
    @return: string containing disk directive for xen instance config file
392
393
394
395
396
397
398

    """
    FILE_DRIVER_MAP = {
      constants.FD_LOOP: "file",
      constants.FD_BLKTAP: "tap:aio",
      }
    disk_data = []
399
400
401
    if len(block_devices) > 24:
      # 'z' - 'a' = 24
      raise errors.HypervisorError("Too many disks")
Iustin Pop's avatar
Iustin Pop committed
402
    namespace = [blockdev_prefix + chr(i + ord("a")) for i in range(24)]
403
    for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
404
405
406
407
      if cfdev.mode == constants.DISK_RDWR:
        mode = "w"
      else:
        mode = "r"
408
      if cfdev.dev_type == constants.LD_FILE:
409
410
        line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
                                  dev_path, sd_name, mode)
411
      else:
412
        line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
413
414
415
416
      disk_data.append(line)

    return disk_data

417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
  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

441
  def FinalizeMigrationDst(self, instance, info, success):
442
443
444
445
446
447
    """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}
448
    @param instance: instance whose migration is being finalized
449
450
451
452
453
454
455
456
457
    @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)

458
459
460
461
462
463
  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.

464
465
    @type instance: L{objects.Instance}
    @param instance: the instance to be migrated
466
467
468
469
470
    @type target: string
    @param target: ip address of the target node
    @type live: boolean
    @param live: perform a live migration

471
    """
472
    if self.GetInstanceInfo(instance.name) is None:
473
      raise errors.HypervisorError("Instance not running, cannot migrate")
474

475
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
476

477
    if not netutils.TcpPing(target, port, live_port_needed=True):
478
479
480
      raise errors.HypervisorError("Remote host %s not listening on port"
                                   " %s, cannot migrate" % (target, port))

481
482
483
484
485
    # FIXME: migrate must be upgraded for transitioning to "xl" (xen 4.1).
    #  -l doesn't exist anymore
    #  -p doesn't exist anymore
    #  -C config_file must be passed
    #  ssh must recognize the key of the target host for the migration
486
    args = [constants.XEN_CMD, "migrate", "-p", "%d" % port]
487
488
    if live:
      args.append("-l")
489
    args.extend([instance.name, target])
490
491
492
    result = utils.RunCmd(args)
    if result.failed:
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
493
                                   (instance.name, result.output))
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529

  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)
530

Iustin Pop's avatar
Iustin Pop committed
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
  @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:
546
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
Iustin Pop's avatar
Iustin Pop committed
547

548
549
550
551

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

552
  PARAMETERS = {
553
554
555
    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,
556
557
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
558
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
559
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
560
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
561
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
562
563
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
564
    constants.HV_REBOOT_BEHAVIOR:
565
566
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
567
    }
568

569
  @classmethod
570
  def _WriteConfigFile(cls, instance, block_devices):
571
572
573
    """Write the Xen config file for the instance.

    """
574
    hvp = instance.hvparams
575
576
577
    config = StringIO()
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")

578
579
580
581
582
583
584
585
586
587
    # 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")
588

589
590
591
592
593
594
595
596
597
598
599
600
      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)
601
602

    # rest of the settings
603
604
    config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
605
606
607
608
    cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK])
    if cpu_pinning:
      config.write("%s\n" % cpu_pinning)

609
610
611
612
    config.write("name = '%s'\n" % instance.name)

    vif_data = []
    for nic in instance.nics:
Guido Trotter's avatar
Guido Trotter committed
613
      nic_str = "mac=%s" % (nic.mac)
614
615
616
      ip = getattr(nic, "ip", None)
      if ip is not None:
        nic_str += ", ip=%s" % ip
Guido Trotter's avatar
Guido Trotter committed
617
618
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
619
      vif_data.append("'%s'" % nic_str)
620

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

624
    config.write("vif = [%s]\n" % ",".join(vif_data))
625
    config.write("disk = [%s]\n" % ",".join(disk_data))
626

627
628
    if hvp[constants.HV_ROOT_PATH]:
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
629
    config.write("on_poweroff = 'destroy'\n")
630
631
632
633
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
      config.write("on_reboot = 'restart'\n")
    else:
      config.write("on_reboot = 'destroy'\n")
634
    config.write("on_crash = 'restart'\n")
635
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
636
637
638
    # just in case it exists
    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
    try:
639
640
      utils.WriteFile(cls._ConfigFileName(instance.name),
                      data=config.getvalue())
641
642
    except EnvironmentError, err:
      raise errors.HypervisorError("Cannot write Xen instance confile"
643
644
                                   " file %s: %s" %
                                   (cls._ConfigFileName(instance.name), err))
645

646
647
648
649
650
651
    return True


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

652
653
654
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
    constants.VNC_PASSWORD_FILE,
    ]
Guido Trotter's avatar
Guido Trotter committed
655

656
657
  PARAMETERS = {
    constants.HV_ACPI: hv_base.NO_CHECK,
Michael Hanselmann's avatar
Michael Hanselmann committed
658
659
660
661
    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),
662
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
Michael Hanselmann's avatar
Michael Hanselmann committed
663
664
665
666
    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),
667
    constants.HV_PAE: hv_base.NO_CHECK,
Michael Hanselmann's avatar
Michael Hanselmann committed
668
    constants.HV_VNC_BIND_ADDRESS:
669
      (False, netutils.IP4Address.IsValid,
Michael Hanselmann's avatar
Michael Hanselmann committed
670
       "VNC bind address is not a valid IP address", None, None),
671
672
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
673
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
674
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
675
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
676
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
677
678
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
679
    constants.HV_REBOOT_BEHAVIOR:
680
681
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
682
    }
683

684
  @classmethod
685
  def _WriteConfigFile(cls, instance, block_devices):
686
687
688
    """Create a Xen 3.1 HVM config file.

    """
689
690
    hvp = instance.hvparams

691
692
    config = StringIO()
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
693
694
695
696
697

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

698
    config.write("builder = 'hvm'\n")
699
700
    config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
701
702
703
704
    cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK])
    if cpu_pinning:
      config.write("%s\n" % cpu_pinning)

705
    config.write("name = '%s'\n" % instance.name)
706
    if hvp[constants.HV_PAE]:
707
708
709
      config.write("pae = 1\n")
    else:
      config.write("pae = 0\n")
710
    if hvp[constants.HV_ACPI]:
711
712
713
      config.write("acpi = 1\n")
    else:
      config.write("acpi = 0\n")
714
    config.write("apic = 1\n")
715
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
716
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
717
    config.write("sdl = 0\n")
718
719
    config.write("usb = 1\n")
    config.write("usbdevice = 'tablet'\n")
720
    config.write("vnc = 1\n")
721
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
722
723
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
    else:
724
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
725

726
727
    if instance.network_port > constants.VNC_BASE_PORT:
      display = instance.network_port - constants.VNC_BASE_PORT
728
729
730
731
732
733
      config.write("vncdisplay = %s\n" % display)
      config.write("vncunused = 0\n")
    else:
      config.write("# vncdisplay = 1\n")
      config.write("vncunused = 1\n")

734
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
735
    try:
736
      password = utils.ReadFile(vnc_pwd_file)
737
738
    except EnvironmentError, err:
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
739
                                   (vnc_pwd_file, err))
740
741
742
743

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

    config.write("serial = 'pty'\n")
744
745
    if hvp[constants.HV_USE_LOCALTIME]:
      config.write("localtime = 1\n")
746
747

    vif_data = []
748
    nic_type = hvp[constants.HV_NIC_TYPE]
749
750
751
    if nic_type is None:
      # ensure old instances don't change
      nic_type_str = ", type=ioemu"
752
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
753
754
755
      nic_type_str = ", type=paravirtualized"
    else:
      nic_type_str = ", model=%s, type=ioemu" % nic_type
756
    for nic in instance.nics:
Guido Trotter's avatar
Guido Trotter committed
757
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
758
759
760
      ip = getattr(nic, "ip", None)
      if ip is not None:
        nic_str += ", ip=%s" % ip
Guido Trotter's avatar
Guido Trotter committed
761
762
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
763
      vif_data.append("'%s'" % nic_str)
764
765

    config.write("vif = [%s]\n" % ",".join(vif_data))
766
767
768
769

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

770
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
771
772
    if iso_path:
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
773
774
775
776
      disk_data.append(iso)

    config.write("disk = [%s]\n" % (",".join(disk_data)))

777
    config.write("on_poweroff = 'destroy'\n")
778
779
780
781
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
      config.write("on_reboot = 'restart'\n")
    else:
      config.write("on_reboot = 'destroy'\n")
782
783
784
785
    config.write("on_crash = 'restart'\n")
    # just in case it exists
    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
    try:
786
      utils.WriteFile(cls._ConfigFileName(instance.name),
787
788
789
                      data=config.getvalue())
    except EnvironmentError, err:
      raise errors.HypervisorError("Cannot write Xen instance confile"
790
791
                                   " file %s: %s" %
                                   (cls._ConfigFileName(instance.name), err))
792

793
    return True