Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 61eb1a46

History | View | Annotate | Download (28.7 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, startup_memory, 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
    startup_memory = self._InstanceStartupMemory(instance)
243
    self._WriteConfigFile(instance, startup_memory, block_devices)
244
    cmd = [constants.XEN_CMD, "create"]
245
    if startup_paused:
246
      cmd.extend(["-p"])
247
    cmd.extend([self._ConfigFileName(instance.name)])
248
    result = utils.RunCmd(cmd)
249

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

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

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

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

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

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

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

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

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

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

    
296
      raise utils.RetryAgain()
297

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
405
    return result
406

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

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

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

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

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

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

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

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

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

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

    
466
    return disk_data
467

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

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

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

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

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

489
    """
490
    pass
491

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
599

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

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

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

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

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

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

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

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

    
661
    config.write("name = '%s'\n" % instance.name)
662

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

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

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

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

    
698
    return True
699

    
700

    
701
class XenHvmHypervisor(XenHypervisor):
702
  """Xen HVM hypervisor interface"""
703

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

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

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

743
    """
744
    hvp = instance.hvparams
745

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

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

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

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

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

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

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

    
799
    config.write("serial = 'pty'\n")
800
    if hvp[constants.HV_USE_LOCALTIME]:
801
      config.write("localtime = 1\n")
802

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

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

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

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

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

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

    
849
    return True