Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 2c7a0373

History | View | Annotate | Download (28.5 kB)

1
#
2
#
3

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

    
36

    
37
XEND_CONFIG_FILE = "/etc/xen/xend-config.sxp"
38
XL_CONFIG_FILE = "/etc/xen/xl.conf"
39
VIF_BRIDGE_SCRIPT = "/etc/xen/scripts/vif-bridge"
40
_DOM0_NAME = "Domain-0"
41

    
42

    
43
class XenHypervisor(hv_base.BaseHypervisor):
44
  """Xen generic hypervisor interface
45

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

49
  """
50
  CAN_MIGRATE = True
51
  REBOOT_RETRY_COUNT = 60
52
  REBOOT_RETRY_INTERVAL = 10
53

    
54
  ANCILLARY_FILES = [
55
    XEND_CONFIG_FILE,
56
    XL_CONFIG_FILE,
57
    VIF_BRIDGE_SCRIPT,
58
    ]
59
  ANCILLARY_FILES_OPT = [
60
    XL_CONFIG_FILE,
61
    ]
62

    
63
  @staticmethod
64
  def _ConfigFileName(instance_name):
65
    """Get the config file name for an instance.
66

67
    @param instance_name: instance name
68
    @type instance_name: str
69
    @return: fully qualified path to instance config file
70
    @rtype: str
71

72
    """
73
    return "/etc/xen/%s" % instance_name
74

    
75
  @classmethod
76
  def _WriteConfigFile(cls, instance, block_devices):
77
    """Write the Xen config file for the instance.
78

79
    """
80
    raise NotImplementedError
81

    
82
  @staticmethod
83
  def _WriteConfigFileStatic(instance_name, data):
84
    """Write the Xen config file for the instance.
85

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

88
    """
89
    utils.WriteFile(XenHypervisor._ConfigFileName(instance_name), data=data)
90

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

95
    """
96
    try:
97
      file_content = utils.ReadFile(
98
                       XenHypervisor._ConfigFileName(instance_name))
99
    except EnvironmentError, err:
100
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
101
    return file_content
102

    
103
  @staticmethod
104
  def _RemoveConfigFile(instance_name):
105
    """Remove the xen configuration file.
106

107
    """
108
    utils.RemoveFile(XenHypervisor._ConfigFileName(instance_name))
109

    
110
  @classmethod
111
  def _CreateConfigCpus(cls, cpu_mask):
112
    """Create a CPU config string that's compatible with Xen's
113
    configuration file.
114

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

    
119
    if len(cpu_list) == 1:
120
      all_cpu_mapping = cpu_list[0]
121
      if all_cpu_mapping == constants.CPU_PINNING_OFF:
122
        # If CPU pinning has 1 entry that's "all", then remove the
123
        # parameter from the config file
124
        return None
125
      else:
126
        # If CPU pinning has one non-all entry, mapping all vCPUS (the entire
127
        # VM) to one physical CPU, using format 'cpu = "C"'
128
        return "cpu = \"%s\"" % ",".join(map(str, all_cpu_mapping))
129
    else:
130
      def _GetCPUMap(vcpu):
131
        if vcpu[0] == constants.CPU_PINNING_ALL_VAL:
132
          cpu_map = constants.CPU_PINNING_ALL_XEN
133
        else:
134
          cpu_map = ",".join(map(str, vcpu))
135
        return "\"%s\"" % cpu_map
136

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

    
142
  @staticmethod
143
  def _RunXmList(xmlist_errors):
144
    """Helper function for L{_GetXMList} to run "xm list".
145

146
    """
147
    result = utils.RunCmd([constants.XEN_CMD, "list"])
148
    if result.failed:
149
      logging.error("xm list failed (%s): %s", result.fail_reason,
150
                    result.output)
151
      xmlist_errors.append(result)
152
      raise utils.RetryAgain()
153

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

    
157
  @classmethod
158
  def _GetXMList(cls, include_node):
159
    """Return the list of running instances.
160

161
    If the include_node argument is True, then we return information
162
    for dom0 also, otherwise we filter that from the return value.
163

164
    @return: list of (name, id, memory, vcpus, state, time spent)
165

166
    """
167
    xmlist_errors = []
168
    try:
169
      lines = utils.Retry(cls._RunXmList, 1, 5, args=(xmlist_errors, ))
170
    except utils.RetryTimeout:
171
      if xmlist_errors:
172
        xmlist_result = xmlist_errors.pop()
173

    
174
        errmsg = ("xm list failed, timeout exceeded (%s): %s" %
175
                  (xmlist_result.fail_reason, xmlist_result.output))
176
      else:
177
        errmsg = "xm list failed"
178

    
179
      raise errors.HypervisorError(errmsg)
180

    
181
    result = []
182
    for line in lines:
183
      # The format of lines is:
184
      # Name      ID Mem(MiB) VCPUs State  Time(s)
185
      # Domain-0   0  3418     4 r-----    266.2
186
      data = line.split()
187
      if len(data) != 6:
188
        raise errors.HypervisorError("Can't parse output of xm list,"
189
                                     " line: %s" % line)
190
      try:
191
        data[1] = int(data[1])
192
        data[2] = int(data[2])
193
        data[3] = int(data[3])
194
        data[5] = float(data[5])
195
      except (TypeError, ValueError), err:
196
        raise errors.HypervisorError("Can't parse output of xm list,"
197
                                     " line: %s, error: %s" % (line, err))
198

    
199
      # skip the Domain-0 (optional)
200
      if include_node or data[0] != _DOM0_NAME:
201
        result.append(data)
202

    
203
    return result
204

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

208
    """
209
    xm_list = self._GetXMList(False)
210
    names = [info[0] for info in xm_list]
211
    return names
212

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

216
    @param instance_name: the instance name
217

218
    @return: tuple (name, id, memory, vcpus, stat, times)
219

220
    """
221
    xm_list = self._GetXMList(instance_name == _DOM0_NAME)
222
    result = None
223
    for data in xm_list:
224
      if data[0] == instance_name:
225
        result = data
226
        break
227
    return result
228

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

232
    @return: list of tuples (name, id, memory, vcpus, stat, times)
233

234
    """
235
    xm_list = self._GetXMList(False)
236
    return xm_list
237

    
238
  def StartInstance(self, instance, block_devices, startup_paused):
239
    """Start an instance.
240

241
    """
242
    self._WriteConfigFile(instance, block_devices)
243
    cmd = [constants.XEN_CMD, "create"]
244
    if startup_paused:
245
      cmd.extend(["-p"])
246
    cmd.extend([self._ConfigFileName(instance.name)])
247
    result = utils.RunCmd(cmd)
248

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

    
254
  def StopInstance(self, instance, force=False, retry=False, name=None):
255
    """Stop an instance.
256

257
    """
258
    if name is None:
259
      name = instance.name
260
    self._RemoveConfigFile(name)
261
    if force:
262
      command = [constants.XEN_CMD, "destroy", name]
263
    else:
264
      command = [constants.XEN_CMD, "shutdown", name]
265
    result = utils.RunCmd(command)
266

    
267
    if result.failed:
268
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
269
                                   (name, result.fail_reason, result.output))
270

    
271
  def RebootInstance(self, instance):
272
    """Reboot an instance.
273

274
    """
275
    ini_info = self.GetInstanceInfo(instance.name)
276

    
277
    if ini_info is None:
278
      raise errors.HypervisorError("Failed to reboot instance %s,"
279
                                   " not running" % instance.name)
280

    
281
    result = utils.RunCmd([constants.XEN_CMD, "reboot", instance.name])
282
    if result.failed:
283
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
284
                                   (instance.name, result.fail_reason,
285
                                    result.output))
286

    
287
    def _CheckInstance():
288
      new_info = self.GetInstanceInfo(instance.name)
289

    
290
      # check if the domain ID has changed or the run time has decreased
291
      if (new_info is not None and
292
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
293
        return
294

    
295
      raise utils.RetryAgain()
296

    
297
    try:
298
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
299
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
300
    except utils.RetryTimeout:
301
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
302
                                   " did not reboot in the expected interval" %
303
                                   (instance.name, ))
304

    
305
  def BalloonInstanceMemory(self, instance, mem):
306
    """Balloon an instance memory to a certain value.
307

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

313
    """
314
    cmd = [constants.XEN_CMD, "mem-set", instance.name, mem]
315
    result = utils.RunCmd(cmd)
316
    if result.failed:
317
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
318
                                   (instance.name, result.fail_reason,
319
                                    result.output))
320
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
321
    cmd.append(XenHypervisor._ConfigFileName(instance.name))
322
    result = utils.RunCmd(cmd)
323
    if result.failed:
324
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
325
                                   (instance.name, result.fail_reason,
326
                                    result.output))
327

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

331
    @return: a dict with the following keys (memory values in MiB):
332
          - memory_total: the total memory size on the node
333
          - memory_free: the available memory on the node for instances
334
          - memory_dom0: the memory used by the node itself, if available
335
          - nr_cpus: total number of CPUs
336
          - nr_nodes: in a NUMA system, the number of domains
337
          - nr_sockets: the number of physical CPU sockets in the node
338
          - hv_version: the hypervisor version in the form (major, minor)
339

340
    """
341
    result = utils.RunCmd([constants.XEN_CMD, "info"])
342
    if result.failed:
343
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
344
                    result.output)
345
      return None
346

    
347
    xmoutput = result.stdout.splitlines()
348
    result = {}
349
    cores_per_socket = threads_per_core = nr_cpus = None
350
    xen_major, xen_minor = None, None
351
    memory_total = None
352
    memory_free = None
353

    
354
    for line in xmoutput:
355
      splitfields = line.split(":", 1)
356

    
357
      if len(splitfields) > 1:
358
        key = splitfields[0].strip()
359
        val = splitfields[1].strip()
360

    
361
        # note: in xen 3, memory has changed to total_memory
362
        if key == "memory" or key == "total_memory":
363
          memory_total = int(val)
364
        elif key == "free_memory":
365
          memory_free = int(val)
366
        elif key == "nr_cpus":
367
          nr_cpus = result["cpu_total"] = int(val)
368
        elif key == "nr_nodes":
369
          result["cpu_nodes"] = int(val)
370
        elif key == "cores_per_socket":
371
          cores_per_socket = int(val)
372
        elif key == "threads_per_core":
373
          threads_per_core = int(val)
374
        elif key == "xen_major":
375
          xen_major = int(val)
376
        elif key == "xen_minor":
377
          xen_minor = int(val)
378

    
379
    if None not in [cores_per_socket, threads_per_core, nr_cpus]:
380
      result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
381

    
382
    total_instmem = 0
383
    for (name, _, mem, vcpus, _, _) in self._GetXMList(True):
384
      if name == _DOM0_NAME:
385
        result["memory_dom0"] = mem
386
        result["dom0_cpus"] = vcpus
387

    
388
      # Include Dom0 in total memory usage
389
      total_instmem += mem
390

    
391
    if memory_free is not None:
392
      result["memory_free"] = memory_free
393

    
394
    if memory_total is not None:
395
      result["memory_total"] = memory_total
396

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

    
401
    if not (xen_major is None or xen_minor is None):
402
      result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
403

    
404
    return result
405

    
406
  @classmethod
407
  def GetInstanceConsole(cls, instance, hvparams, beparams):
408
    """Return a command for connecting to the console of an instance.
409

410
    """
411
    return objects.InstanceConsole(instance=instance.name,
412
                                   kind=constants.CONS_SSH,
413
                                   host=instance.primary_node,
414
                                   user=constants.GANETI_RUNAS,
415
                                   command=[constants.XM_CONSOLE_WRAPPER,
416
                                            instance.name])
417

    
418
  def Verify(self):
419
    """Verify the hypervisor.
420

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

423
    """
424
    result = utils.RunCmd([constants.XEN_CMD, "info"])
425
    if result.failed:
426
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
427

    
428
  @staticmethod
429
  def _GetConfigFileDiskData(block_devices, blockdev_prefix):
430
    """Get disk directive for xen config file.
431

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

435
    @param block_devices: list of tuples (cfdev, rldev):
436
        - cfdev: dict containing ganeti config disk part
437
        - rldev: ganeti.bdev.BlockDev object
438
    @param blockdev_prefix: a string containing blockdevice prefix,
439
                            e.g. "sd" for /dev/sda
440

441
    @return: string containing disk directive for xen instance config file
442

443
    """
444
    FILE_DRIVER_MAP = {
445
      constants.FD_LOOP: "file",
446
      constants.FD_BLKTAP: "tap:aio",
447
      }
448
    disk_data = []
449
    if len(block_devices) > 24:
450
      # 'z' - 'a' = 24
451
      raise errors.HypervisorError("Too many disks")
452
    namespace = [blockdev_prefix + chr(i + ord("a")) for i in range(24)]
453
    for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
454
      if cfdev.mode == constants.DISK_RDWR:
455
        mode = "w"
456
      else:
457
        mode = "r"
458
      if cfdev.dev_type == constants.LD_FILE:
459
        line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
460
                                  dev_path, sd_name, mode)
461
      else:
462
        line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
463
      disk_data.append(line)
464

    
465
    return disk_data
466

    
467
  def MigrationInfo(self, instance):
468
    """Get instance information to perform a migration.
469

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

475
    """
476
    return self._ReadConfigFile(instance.name)
477

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

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

488
    """
489
    pass
490

    
491
  def FinalizeMigrationDst(self, instance, info, success):
492
    """Finalize an instance migration.
493

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

497
    @type instance: L{objects.Instance}
498
    @param instance: instance whose migration is being finalized
499
    @type info: string
500
    @param info: content of the xen config file on the source node
501
    @type success: boolean
502
    @param success: whether the migration was a success or a failure
503

504
    """
505
    if success:
506
      self._WriteConfigFileStatic(instance.name, info)
507

    
508
  def MigrateInstance(self, instance, target, live):
509
    """Migrate an instance to a target node.
510

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

514
    @type instance: L{objects.Instance}
515
    @param instance: the instance to be migrated
516
    @type target: string
517
    @param target: ip address of the target node
518
    @type live: boolean
519
    @param live: perform a live migration
520

521
    """
522
    if self.GetInstanceInfo(instance.name) is None:
523
      raise errors.HypervisorError("Instance not running, cannot migrate")
524

    
525
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
526

    
527
    if not netutils.TcpPing(target, port, live_port_needed=True):
528
      raise errors.HypervisorError("Remote host %s not listening on port"
529
                                   " %s, cannot migrate" % (target, port))
530

    
531
    # FIXME: migrate must be upgraded for transitioning to "xl" (xen 4.1).
532
    #  -l doesn't exist anymore
533
    #  -p doesn't exist anymore
534
    #  -C config_file must be passed
535
    #  ssh must recognize the key of the target host for the migration
536
    args = [constants.XEN_CMD, "migrate", "-p", "%d" % port]
537
    if live:
538
      args.append("-l")
539
    args.extend([instance.name, target])
540
    result = utils.RunCmd(args)
541
    if result.failed:
542
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
543
                                   (instance.name, result.output))
544

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

548
    @type instance: L{objects.Instance}
549
    @param instance: the instance that was migrated
550
    @type success: bool
551
    @param success: whether the migration succeeded or not
552
    @type live: bool
553
    @param live: whether the user requested a live migration or not
554

555
    """
556
    # pylint: disable=W0613
557
    if success:
558
      # remove old xen file after migration succeeded
559
      try:
560
        self._RemoveConfigFile(instance.name)
561
      except EnvironmentError:
562
        logging.exception("Failure while removing instance config file")
563

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

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

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

578
    """
579
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
580

    
581
  @classmethod
582
  def PowercycleNode(cls):
583
    """Xen-specific powercycle.
584

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

592
    """
593
    try:
594
      cls.LinuxPowercycle()
595
    finally:
596
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
597

    
598

    
599
class XenPvmHypervisor(XenHypervisor):
600
  """Xen PVM hypervisor interface"""
601

    
602
  PARAMETERS = {
603
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
604
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
605
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
606
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
607
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
608
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
609
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
610
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
611
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
612
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
613
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
614
    constants.HV_REBOOT_BEHAVIOR:
615
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
616
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
617
    }
618

    
619
  @classmethod
620
  def _WriteConfigFile(cls, instance, block_devices):
621
    """Write the Xen config file for the instance.
622

623
    """
624
    hvp = instance.hvparams
625
    config = StringIO()
626
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
627

    
628
    # if bootloader is True, use bootloader instead of kernel and ramdisk
629
    # parameters.
630
    if hvp[constants.HV_USE_BOOTLOADER]:
631
      # bootloader handling
632
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
633
      if bootloader_path:
634
        config.write("bootloader = '%s'\n" % bootloader_path)
635
      else:
636
        raise errors.HypervisorError("Bootloader enabled, but missing"
637
                                     " bootloader path")
638

    
639
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
640
      if bootloader_args:
641
        config.write("bootargs = '%s'\n" % bootloader_args)
642
    else:
643
      # kernel handling
644
      kpath = hvp[constants.HV_KERNEL_PATH]
645
      config.write("kernel = '%s'\n" % kpath)
646

    
647
      # initrd handling
648
      initrd_path = hvp[constants.HV_INITRD_PATH]
649
      if initrd_path:
650
        config.write("ramdisk = '%s'\n" % initrd_path)
651

    
652
    # rest of the settings
653
    config.write("memory = %d\n" % instance.beparams[constants.BE_MAXMEM])
654
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
655
    cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK])
656
    if cpu_pinning:
657
      config.write("%s\n" % cpu_pinning)
658

    
659
    config.write("name = '%s'\n" % instance.name)
660

    
661
    vif_data = []
662
    for nic in instance.nics:
663
      nic_str = "mac=%s" % (nic.mac)
664
      ip = getattr(nic, "ip", None)
665
      if ip is not None:
666
        nic_str += ", ip=%s" % ip
667
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
668
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
669
      vif_data.append("'%s'" % nic_str)
670

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

    
674
    config.write("vif = [%s]\n" % ",".join(vif_data))
675
    config.write("disk = [%s]\n" % ",".join(disk_data))
676

    
677
    if hvp[constants.HV_ROOT_PATH]:
678
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
679
    config.write("on_poweroff = 'destroy'\n")
680
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
681
      config.write("on_reboot = 'restart'\n")
682
    else:
683
      config.write("on_reboot = 'destroy'\n")
684
    config.write("on_crash = 'restart'\n")
685
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
686
    # just in case it exists
687
    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
688
    try:
689
      utils.WriteFile(cls._ConfigFileName(instance.name),
690
                      data=config.getvalue())
691
    except EnvironmentError, err:
692
      raise errors.HypervisorError("Cannot write Xen instance confile"
693
                                   " file %s: %s" %
694
                                   (cls._ConfigFileName(instance.name), err))
695

    
696
    return True
697

    
698

    
699
class XenHvmHypervisor(XenHypervisor):
700
  """Xen HVM hypervisor interface"""
701

    
702
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
703
    constants.VNC_PASSWORD_FILE,
704
    ]
705
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
706
    constants.VNC_PASSWORD_FILE,
707
    ]
708

    
709
  PARAMETERS = {
710
    constants.HV_ACPI: hv_base.NO_CHECK,
711
    constants.HV_BOOT_ORDER: (True, ) +
712
      (lambda x: x and len(x.strip("acdn")) == 0,
713
       "Invalid boot order specified, must be one or more of [acdn]",
714
       None, None),
715
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
716
    constants.HV_DISK_TYPE:
717
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
718
    constants.HV_NIC_TYPE:
719
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
720
    constants.HV_PAE: hv_base.NO_CHECK,
721
    constants.HV_VNC_BIND_ADDRESS:
722
      (False, netutils.IP4Address.IsValid,
723
       "VNC bind address is not a valid IP address", None, None),
724
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
725
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
726
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
727
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
728
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
729
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
730
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
731
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
732
    constants.HV_REBOOT_BEHAVIOR:
733
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
734
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
735
    }
736

    
737
  @classmethod
738
  def _WriteConfigFile(cls, instance, block_devices):
739
    """Create a Xen 3.1 HVM config file.
740

741
    """
742
    hvp = instance.hvparams
743

    
744
    config = StringIO()
745
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
746

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

    
751
    config.write("builder = 'hvm'\n")
752
    config.write("memory = %d\n" % instance.beparams[constants.BE_MAXMEM])
753
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
754
    cpu_pinning = cls._CreateConfigCpus(hvp[constants.HV_CPU_MASK])
755
    if cpu_pinning:
756
      config.write("%s\n" % cpu_pinning)
757

    
758
    config.write("name = '%s'\n" % instance.name)
759
    if hvp[constants.HV_PAE]:
760
      config.write("pae = 1\n")
761
    else:
762
      config.write("pae = 0\n")
763
    if hvp[constants.HV_ACPI]:
764
      config.write("acpi = 1\n")
765
    else:
766
      config.write("acpi = 0\n")
767
    config.write("apic = 1\n")
768
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
769
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
770
    config.write("sdl = 0\n")
771
    config.write("usb = 1\n")
772
    config.write("usbdevice = 'tablet'\n")
773
    config.write("vnc = 1\n")
774
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
775
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
776
    else:
777
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
778

    
779
    if instance.network_port > constants.VNC_BASE_PORT:
780
      display = instance.network_port - constants.VNC_BASE_PORT
781
      config.write("vncdisplay = %s\n" % display)
782
      config.write("vncunused = 0\n")
783
    else:
784
      config.write("# vncdisplay = 1\n")
785
      config.write("vncunused = 1\n")
786

    
787
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
788
    try:
789
      password = utils.ReadFile(vnc_pwd_file)
790
    except EnvironmentError, err:
791
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
792
                                   (vnc_pwd_file, err))
793

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

    
796
    config.write("serial = 'pty'\n")
797
    if hvp[constants.HV_USE_LOCALTIME]:
798
      config.write("localtime = 1\n")
799

    
800
    vif_data = []
801
    nic_type = hvp[constants.HV_NIC_TYPE]
802
    if nic_type is None:
803
      # ensure old instances don't change
804
      nic_type_str = ", type=ioemu"
805
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
806
      nic_type_str = ", type=paravirtualized"
807
    else:
808
      nic_type_str = ", model=%s, type=ioemu" % nic_type
809
    for nic in instance.nics:
810
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
811
      ip = getattr(nic, "ip", None)
812
      if ip is not None:
813
        nic_str += ", ip=%s" % ip
814
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
815
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
816
      vif_data.append("'%s'" % nic_str)
817

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

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

    
823
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
824
    if iso_path:
825
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
826
      disk_data.append(iso)
827

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

    
830
    config.write("on_poweroff = 'destroy'\n")
831
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
832
      config.write("on_reboot = 'restart'\n")
833
    else:
834
      config.write("on_reboot = 'destroy'\n")
835
    config.write("on_crash = 'restart'\n")
836
    # just in case it exists
837
    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
838
    try:
839
      utils.WriteFile(cls._ConfigFileName(instance.name),
840
                      data=config.getvalue())
841
    except EnvironmentError, err:
842
      raise errors.HypervisorError("Cannot write Xen instance confile"
843
                                   " file %s: %s" %
844
                                   (cls._ConfigFileName(instance.name), err))
845

    
846
    return True