Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 6f1e1921

History | View | Annotate | Download (29.9 kB)

1
#
2
#
3

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

    
21

    
22
"""Xen hypervisors
23

24
"""
25

    
26
import logging
27
from cStringIO import StringIO
28

    
29
from ganeti import constants
30
from ganeti import errors
31
from ganeti import utils
32
from ganeti.hypervisor import hv_base
33
from ganeti import netutils
34
from ganeti import objects
35
from ganeti import pathutils
36
from ganeti import ssconf
37

    
38

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

    
45

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

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

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

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

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

    
77

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

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

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

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

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

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

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

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

114
    """
115
    raise NotImplementedError
116

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

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

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

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

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

139
    """
140
    filename = XenHypervisor._ConfigFileName(instance_name)
141

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

    
147
    return file_content
148

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

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

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

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

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

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

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

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

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

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

    
193
      raise errors.HypervisorError(errmsg)
194

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

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

    
217
    return result
218

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

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

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

230
    @param instance_name: the instance name
231

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

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

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

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

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

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

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

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

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

272
    """
273
    if name is None:
274
      name = instance.name
275

    
276
    if force:
277
      command = [constants.XEN_CMD, "destroy", name]
278
    else:
279
      command = [constants.XEN_CMD, "shutdown", name]
280
    result = utils.RunCmd(command)
281

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

    
286
    # Remove configuration file if stopping/starting instance was successful
287
    self._RemoveConfigFile(name)
288

    
289
  def RebootInstance(self, instance):
290
    """Reboot an instance.
291

292
    """
293
    ini_info = self.GetInstanceInfo(instance.name)
294

    
295
    if ini_info is None:
296
      raise errors.HypervisorError("Failed to reboot instance %s,"
297
                                   " not running" % instance.name)
298

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

    
305
    def _CheckInstance():
306
      new_info = self.GetInstanceInfo(instance.name)
307

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

    
313
      raise utils.RetryAgain()
314

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

    
323
  def BalloonInstanceMemory(self, instance, mem):
324
    """Balloon an instance memory to a certain value.
325

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

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

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

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

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

    
365
    xmoutput = result.stdout.splitlines()
366
    result = {}
367
    cores_per_socket = threads_per_core = nr_cpus = None
368
    xen_major, xen_minor = None, None
369
    memory_total = None
370
    memory_free = None
371

    
372
    for line in xmoutput:
373
      splitfields = line.split(":", 1)
374

    
375
      if len(splitfields) > 1:
376
        key = splitfields[0].strip()
377
        val = splitfields[1].strip()
378

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

    
397
    if None not in [cores_per_socket, threads_per_core, nr_cpus]:
398
      result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
399

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

    
406
      # Include Dom0 in total memory usage
407
      total_instmem += mem
408

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

    
412
    if memory_total is not None:
413
      result["memory_total"] = memory_total
414

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

    
419
    if not (xen_major is None or xen_minor is None):
420
      result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
421

    
422
    return result
423

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

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

    
436
  def Verify(self):
437
    """Verify the hypervisor.
438

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

441
    @return: Problem description if something is wrong, C{None} otherwise
442

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

    
448
    return None
449

    
450
  @staticmethod
451
  def _GetConfigFileDiskData(block_devices, blockdev_prefix):
452
    """Get disk directive for xen config file.
453

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

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

463
    @return: string containing disk directive for xen instance config file
464

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

    
487
    return disk_data
488

    
489
  def MigrationInfo(self, instance):
490
    """Get instance information to perform a migration.
491

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

497
    """
498
    return self._ReadConfigFile(instance.name)
499

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

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

510
    """
511
    pass
512

    
513
  def FinalizeMigrationDst(self, instance, info, success):
514
    """Finalize an instance migration.
515

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

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

526
    """
527
    if success:
528
      self._WriteConfigFileStatic(instance.name, info)
529

    
530
  def MigrateInstance(self, instance, target, live):
531
    """Migrate an instance to a target node.
532

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

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

543
    """
544
    if self.GetInstanceInfo(instance.name) is None:
545
      raise errors.HypervisorError("Instance not running, cannot migrate")
546

    
547
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
548

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

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

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

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

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

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

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

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

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

606
    """
607
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
608

    
609
  @classmethod
610
  def PowercycleNode(cls):
611
    """Xen-specific powercycle.
612

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

620
    """
621
    try:
622
      cls.LinuxPowercycle()
623
    finally:
624
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
625

    
626

    
627
class XenPvmHypervisor(XenHypervisor):
628
  """Xen PVM hypervisor interface"""
629

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

    
650
  @classmethod
651
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
652
    """Write the Xen config file for the instance.
653

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

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

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

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

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

    
697
    config.write("name = '%s'\n" % instance.name)
698

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

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

    
712
    config.write("vif = [%s]\n" % ",".join(vif_data))
713
    config.write("disk = [%s]\n" % ",".join(disk_data))
714

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

    
726
    return True
727

    
728

    
729
class XenHvmHypervisor(XenHypervisor):
730
  """Xen HVM hypervisor interface"""
731

    
732
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
733
    pathutils.VNC_PASSWORD_FILE,
734
    ]
735
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
736
    pathutils.VNC_PASSWORD_FILE,
737
    ]
738

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

    
772
  @classmethod
773
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
774
    """Create a Xen 3.1 HVM config file.
775

776
    """
777
    hvp = instance.hvparams
778

    
779
    config = StringIO()
780
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
781

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

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

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

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

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

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

    
838
    config.write("serial = 'pty'\n")
839
    if hvp[constants.HV_USE_LOCALTIME]:
840
      config.write("localtime = 1\n")
841

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

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

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

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

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

    
885
    return True