Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 06c9a520

History | View | Annotate | Download (30.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
def _RunXmList(fn, xmllist_errors):
79
  """Helper function for L{_GetXmList} to run "xm list".
80

81
  @type fn: callable
82
  @param fn: Function returning result of running C{xm list}
83
  @type xmllist_errors: list
84
  @param xmllist_errors: Error list
85
  @rtype: list
86

87
  """
88
  result = fn()
89
  if result.failed:
90
    logging.error("xm list failed (%s): %s", result.fail_reason,
91
                  result.output)
92
    xmllist_errors.append(result)
93
    raise utils.RetryAgain()
94

    
95
  # skip over the heading
96
  return result.stdout.splitlines()
97

    
98

    
99
def _ParseXmList(lines, include_node):
100
  """Parses the output of C{xm list}.
101

102
  @type lines: list
103
  @param lines: Output lines of C{xm list}
104
  @type include_node: boolean
105
  @param include_node: If True, return information for Dom0
106
  @return: list of tuple containing (name, id, memory, vcpus, state, time
107
    spent)
108

109
  """
110
  result = []
111

    
112
  # Iterate through all lines while ignoring header
113
  for line in lines[1:]:
114
    # The format of lines is:
115
    # Name      ID Mem(MiB) VCPUs State  Time(s)
116
    # Domain-0   0  3418     4 r-----    266.2
117
    data = line.split()
118
    if len(data) != 6:
119
      raise errors.HypervisorError("Can't parse output of xm list,"
120
                                   " line: %s" % line)
121
    try:
122
      data[1] = int(data[1])
123
      data[2] = int(data[2])
124
      data[3] = int(data[3])
125
      data[5] = float(data[5])
126
    except (TypeError, ValueError), err:
127
      raise errors.HypervisorError("Can't parse output of xm list,"
128
                                   " line: %s, error: %s" % (line, err))
129

    
130
    # skip the Domain-0 (optional)
131
    if include_node or data[0] != _DOM0_NAME:
132
      result.append(data)
133

    
134
  return result
135

    
136

    
137
def _GetXmList(fn, include_node, _timeout=5):
138
  """Return the list of running instances.
139

140
  See L{_RunXmList} and L{_ParseXmList} for parameter details.
141

142
  """
143
  xmllist_errors = []
144
  try:
145
    lines = utils.Retry(_RunXmList, (0.3, 1.5, 1.0), _timeout,
146
                        args=(fn, xmllist_errors))
147
  except utils.RetryTimeout:
148
    if xmllist_errors:
149
      xmlist_result = xmllist_errors.pop()
150

    
151
      errmsg = ("xm list failed, timeout exceeded (%s): %s" %
152
                (xmlist_result.fail_reason, xmlist_result.output))
153
    else:
154
      errmsg = "xm list failed"
155

    
156
    raise errors.HypervisorError(errmsg)
157

    
158
  return _ParseXmList(lines, include_node)
159

    
160

    
161
def _ParseNodeInfo(info):
162
  """Return information about the node.
163

164
  @return: a dict with the following keys (memory values in MiB):
165
        - memory_total: the total memory size on the node
166
        - memory_free: the available memory on the node for instances
167
        - nr_cpus: total number of CPUs
168
        - nr_nodes: in a NUMA system, the number of domains
169
        - nr_sockets: the number of physical CPU sockets in the node
170
        - hv_version: the hypervisor version in the form (major, minor)
171

172
  """
173
  result = {}
174
  cores_per_socket = threads_per_core = nr_cpus = None
175
  xen_major, xen_minor = None, None
176
  memory_total = None
177
  memory_free = None
178

    
179
  for line in info.splitlines():
180
    fields = line.split(":", 1)
181

    
182
    if len(fields) < 2:
183
      continue
184

    
185
    (key, val) = map(lambda s: s.strip(), fields)
186

    
187
    # Note: in Xen 3, memory has changed to total_memory
188
    if key in ("memory", "total_memory"):
189
      memory_total = int(val)
190
    elif key == "free_memory":
191
      memory_free = int(val)
192
    elif key == "nr_cpus":
193
      nr_cpus = result["cpu_total"] = int(val)
194
    elif key == "nr_nodes":
195
      result["cpu_nodes"] = int(val)
196
    elif key == "cores_per_socket":
197
      cores_per_socket = int(val)
198
    elif key == "threads_per_core":
199
      threads_per_core = int(val)
200
    elif key == "xen_major":
201
      xen_major = int(val)
202
    elif key == "xen_minor":
203
      xen_minor = int(val)
204

    
205
  if None not in [cores_per_socket, threads_per_core, nr_cpus]:
206
    result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
207

    
208
  if memory_free is not None:
209
    result["memory_free"] = memory_free
210

    
211
  if memory_total is not None:
212
    result["memory_total"] = memory_total
213

    
214
  if not (xen_major is None or xen_minor is None):
215
    result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
216

    
217
  return result
218

    
219

    
220
def _MergeInstanceInfo(info, fn):
221
  """Updates node information from L{_ParseNodeInfo} with instance info.
222

223
  @type info: dict
224
  @param info: Result from L{_ParseNodeInfo}
225
  @type fn: callable
226
  @param fn: Function returning result of running C{xm list}
227
  @rtype: dict
228

229
  """
230
  total_instmem = 0
231

    
232
  for (name, _, mem, vcpus, _, _) in fn(True):
233
    if name == _DOM0_NAME:
234
      info["memory_dom0"] = mem
235
      info["dom0_cpus"] = vcpus
236

    
237
    # Include Dom0 in total memory usage
238
    total_instmem += mem
239

    
240
  memory_free = info.get("memory_free")
241
  memory_total = info.get("memory_total")
242

    
243
  # Calculate memory used by hypervisor
244
  if None not in [memory_total, memory_free, total_instmem]:
245
    info["memory_hv"] = memory_total - memory_free - total_instmem
246

    
247
  return info
248

    
249

    
250
def _GetNodeInfo(info, fn):
251
  """Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}.
252

253
  """
254
  return _MergeInstanceInfo(_ParseNodeInfo(info), fn)
255

    
256

    
257
class XenHypervisor(hv_base.BaseHypervisor):
258
  """Xen generic hypervisor interface
259

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

263
  """
264
  CAN_MIGRATE = True
265
  REBOOT_RETRY_COUNT = 60
266
  REBOOT_RETRY_INTERVAL = 10
267

    
268
  ANCILLARY_FILES = [
269
    XEND_CONFIG_FILE,
270
    XL_CONFIG_FILE,
271
    VIF_BRIDGE_SCRIPT,
272
    ]
273
  ANCILLARY_FILES_OPT = [
274
    XL_CONFIG_FILE,
275
    ]
276

    
277
  @staticmethod
278
  def _ConfigFileName(instance_name):
279
    """Get the config file name for an instance.
280

281
    @param instance_name: instance name
282
    @type instance_name: str
283
    @return: fully qualified path to instance config file
284
    @rtype: str
285

286
    """
287
    return utils.PathJoin(pathutils.XEN_CONFIG_DIR, instance_name)
288

    
289
  @classmethod
290
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
291
    """Write the Xen config file for the instance.
292

293
    """
294
    raise NotImplementedError
295

    
296
  @staticmethod
297
  def _WriteConfigFileStatic(instance_name, data):
298
    """Write the Xen config file for the instance.
299

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

302
    """
303
    # just in case it exists
304
    utils.RemoveFile(utils.PathJoin(pathutils.XEN_CONFIG_DIR, "auto",
305
                                    instance_name))
306

    
307
    cfg_file = XenHypervisor._ConfigFileName(instance_name)
308
    try:
309
      utils.WriteFile(cfg_file, data=data)
310
    except EnvironmentError, err:
311
      raise errors.HypervisorError("Cannot write Xen instance configuration"
312
                                   " file %s: %s" % (cfg_file, err))
313

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

318
    """
319
    filename = XenHypervisor._ConfigFileName(instance_name)
320

    
321
    try:
322
      file_content = utils.ReadFile(filename)
323
    except EnvironmentError, err:
324
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
325

    
326
    return file_content
327

    
328
  @staticmethod
329
  def _RemoveConfigFile(instance_name):
330
    """Remove the xen configuration file.
331

332
    """
333
    utils.RemoveFile(XenHypervisor._ConfigFileName(instance_name))
334

    
335
  @staticmethod
336
  def _GetXmList(include_node):
337
    """Wrapper around module level L{_GetXmList}.
338

339
    """
340
    # TODO: Abstract running Xen command for testing
341
    return _GetXmList(lambda: utils.RunCmd([constants.XEN_CMD, "list"]),
342
                      include_node)
343

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

347
    """
348
    xm_list = self._GetXmList(False)
349
    names = [info[0] for info in xm_list]
350
    return names
351

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

355
    @param instance_name: the instance name
356

357
    @return: tuple (name, id, memory, vcpus, stat, times)
358

359
    """
360
    xm_list = self._GetXmList(instance_name == _DOM0_NAME)
361
    result = None
362
    for data in xm_list:
363
      if data[0] == instance_name:
364
        result = data
365
        break
366
    return result
367

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

371
    @return: list of tuples (name, id, memory, vcpus, stat, times)
372

373
    """
374
    xm_list = self._GetXmList(False)
375
    return xm_list
376

    
377
  def StartInstance(self, instance, block_devices, startup_paused):
378
    """Start an instance.
379

380
    """
381
    startup_memory = self._InstanceStartupMemory(instance)
382
    self._WriteConfigFile(instance, startup_memory, block_devices)
383
    cmd = [constants.XEN_CMD, "create"]
384
    if startup_paused:
385
      cmd.extend(["-p"])
386
    cmd.extend([self._ConfigFileName(instance.name)])
387
    result = utils.RunCmd(cmd)
388

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

    
394
  def StopInstance(self, instance, force=False, retry=False, name=None):
395
    """Stop an instance.
396

397
    """
398
    if name is None:
399
      name = instance.name
400
    self._RemoveConfigFile(name)
401
    if force:
402
      command = [constants.XEN_CMD, "destroy", name]
403
    else:
404
      command = [constants.XEN_CMD, "shutdown", name]
405
    result = utils.RunCmd(command)
406

    
407
    if result.failed:
408
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
409
                                   (name, result.fail_reason, result.output))
410

    
411
  def RebootInstance(self, instance):
412
    """Reboot an instance.
413

414
    """
415
    ini_info = self.GetInstanceInfo(instance.name)
416

    
417
    if ini_info is None:
418
      raise errors.HypervisorError("Failed to reboot instance %s,"
419
                                   " not running" % instance.name)
420

    
421
    result = utils.RunCmd([constants.XEN_CMD, "reboot", instance.name])
422
    if result.failed:
423
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
424
                                   (instance.name, result.fail_reason,
425
                                    result.output))
426

    
427
    def _CheckInstance():
428
      new_info = self.GetInstanceInfo(instance.name)
429

    
430
      # check if the domain ID has changed or the run time has decreased
431
      if (new_info is not None and
432
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
433
        return
434

    
435
      raise utils.RetryAgain()
436

    
437
    try:
438
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
439
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
440
    except utils.RetryTimeout:
441
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
442
                                   " did not reboot in the expected interval" %
443
                                   (instance.name, ))
444

    
445
  def BalloonInstanceMemory(self, instance, mem):
446
    """Balloon an instance memory to a certain value.
447

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

453
    """
454
    cmd = [constants.XEN_CMD, "mem-set", instance.name, mem]
455
    result = utils.RunCmd(cmd)
456
    if result.failed:
457
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
458
                                   (instance.name, result.fail_reason,
459
                                    result.output))
460
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
461
    cmd.append(XenHypervisor._ConfigFileName(instance.name))
462
    result = utils.RunCmd(cmd)
463
    if result.failed:
464
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
465
                                   (instance.name, result.fail_reason,
466
                                    result.output))
467

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

471
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
472

473
    """
474
    # TODO: Abstract running Xen command for testing
475
    result = utils.RunCmd([constants.XEN_CMD, "info"])
476
    if result.failed:
477
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
478
                    result.output)
479
      return None
480

    
481
    return _GetNodeInfo(result.stdout, self._GetXmList)
482

    
483
  @classmethod
484
  def GetInstanceConsole(cls, instance, hvparams, beparams):
485
    """Return a command for connecting to the console of an instance.
486

487
    """
488
    return objects.InstanceConsole(instance=instance.name,
489
                                   kind=constants.CONS_SSH,
490
                                   host=instance.primary_node,
491
                                   user=constants.SSH_CONSOLE_USER,
492
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
493
                                            constants.XEN_CMD, instance.name])
494

    
495
  def Verify(self):
496
    """Verify the hypervisor.
497

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

500
    @return: Problem description if something is wrong, C{None} otherwise
501

502
    """
503
    result = utils.RunCmd([constants.XEN_CMD, "info"])
504
    if result.failed:
505
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
506

    
507
    return None
508

    
509
  @staticmethod
510
  def _GetConfigFileDiskData(block_devices, blockdev_prefix):
511
    """Get disk directive for xen config file.
512

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

516
    @param block_devices: list of tuples (cfdev, rldev):
517
        - cfdev: dict containing ganeti config disk part
518
        - rldev: ganeti.bdev.BlockDev object
519
    @param blockdev_prefix: a string containing blockdevice prefix,
520
                            e.g. "sd" for /dev/sda
521

522
    @return: string containing disk directive for xen instance config file
523

524
    """
525
    FILE_DRIVER_MAP = {
526
      constants.FD_LOOP: "file",
527
      constants.FD_BLKTAP: "tap:aio",
528
      }
529
    disk_data = []
530
    if len(block_devices) > 24:
531
      # 'z' - 'a' = 24
532
      raise errors.HypervisorError("Too many disks")
533
    namespace = [blockdev_prefix + chr(i + ord("a")) for i in range(24)]
534
    for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
535
      if cfdev.mode == constants.DISK_RDWR:
536
        mode = "w"
537
      else:
538
        mode = "r"
539
      if cfdev.dev_type == constants.LD_FILE:
540
        line = "'%s:%s,%s,%s'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
541
                                  dev_path, sd_name, mode)
542
      else:
543
        line = "'phy:%s,%s,%s'" % (dev_path, sd_name, mode)
544
      disk_data.append(line)
545

    
546
    return disk_data
547

    
548
  def MigrationInfo(self, instance):
549
    """Get instance information to perform a migration.
550

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

556
    """
557
    return self._ReadConfigFile(instance.name)
558

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

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

569
    """
570
    pass
571

    
572
  def FinalizeMigrationDst(self, instance, info, success):
573
    """Finalize an instance migration.
574

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

578
    @type instance: L{objects.Instance}
579
    @param instance: instance whose migration is being finalized
580
    @type info: string
581
    @param info: content of the xen config file on the source node
582
    @type success: boolean
583
    @param success: whether the migration was a success or a failure
584

585
    """
586
    if success:
587
      self._WriteConfigFileStatic(instance.name, info)
588

    
589
  def MigrateInstance(self, instance, target, live):
590
    """Migrate an instance to a target node.
591

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

595
    @type instance: L{objects.Instance}
596
    @param instance: the instance to be migrated
597
    @type target: string
598
    @param target: ip address of the target node
599
    @type live: boolean
600
    @param live: perform a live migration
601

602
    """
603
    if self.GetInstanceInfo(instance.name) is None:
604
      raise errors.HypervisorError("Instance not running, cannot migrate")
605

    
606
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
607

    
608
    if (constants.XEN_CMD == constants.XEN_CMD_XM and
609
        not netutils.TcpPing(target, port, live_port_needed=True)):
610
      raise errors.HypervisorError("Remote host %s not listening on port"
611
                                   " %s, cannot migrate" % (target, port))
612

    
613
    args = [constants.XEN_CMD, "migrate"]
614
    if constants.XEN_CMD == constants.XEN_CMD_XM:
615
      args.extend(["-p", "%d" % port])
616
      if live:
617
        args.append("-l")
618
    elif constants.XEN_CMD == constants.XEN_CMD_XL:
619
      cluster_name = ssconf.SimpleStore().GetClusterName()
620
      args.extend(["-s", constants.XL_SSH_CMD % cluster_name])
621
      args.extend(["-C", self._ConfigFileName(instance.name)])
622
    else:
623
      raise errors.HypervisorError("Unsupported xen command: %s" %
624
                                   constants.XEN_CMD)
625

    
626
    args.extend([instance.name, target])
627
    result = utils.RunCmd(args)
628
    if result.failed:
629
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
630
                                   (instance.name, result.output))
631

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

635
    @type instance: L{objects.Instance}
636
    @param instance: the instance that was migrated
637
    @type success: bool
638
    @param success: whether the migration succeeded or not
639
    @type live: bool
640
    @param live: whether the user requested a live migration or not
641

642
    """
643
    # pylint: disable=W0613
644
    if success:
645
      # remove old xen file after migration succeeded
646
      try:
647
        self._RemoveConfigFile(instance.name)
648
      except EnvironmentError:
649
        logging.exception("Failure while removing instance config file")
650

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

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

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

665
    """
666
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
667

    
668
  @classmethod
669
  def PowercycleNode(cls):
670
    """Xen-specific powercycle.
671

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

679
    """
680
    try:
681
      cls.LinuxPowercycle()
682
    finally:
683
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
684

    
685

    
686
class XenPvmHypervisor(XenHypervisor):
687
  """Xen PVM hypervisor interface"""
688

    
689
  PARAMETERS = {
690
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
691
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
692
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
693
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
694
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
695
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
696
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
697
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
698
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
699
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
700
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
701
    constants.HV_REBOOT_BEHAVIOR:
702
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
703
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
704
    constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
705
    constants.HV_CPU_WEIGHT:
706
      (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
707
    }
708

    
709
  @classmethod
710
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
711
    """Write the Xen config file for the instance.
712

713
    """
714
    hvp = instance.hvparams
715
    config = StringIO()
716
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
717

    
718
    # if bootloader is True, use bootloader instead of kernel and ramdisk
719
    # parameters.
720
    if hvp[constants.HV_USE_BOOTLOADER]:
721
      # bootloader handling
722
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
723
      if bootloader_path:
724
        config.write("bootloader = '%s'\n" % bootloader_path)
725
      else:
726
        raise errors.HypervisorError("Bootloader enabled, but missing"
727
                                     " bootloader path")
728

    
729
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
730
      if bootloader_args:
731
        config.write("bootargs = '%s'\n" % bootloader_args)
732
    else:
733
      # kernel handling
734
      kpath = hvp[constants.HV_KERNEL_PATH]
735
      config.write("kernel = '%s'\n" % kpath)
736

    
737
      # initrd handling
738
      initrd_path = hvp[constants.HV_INITRD_PATH]
739
      if initrd_path:
740
        config.write("ramdisk = '%s'\n" % initrd_path)
741

    
742
    # rest of the settings
743
    config.write("memory = %d\n" % startup_memory)
744
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
745
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
746
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
747
    if cpu_pinning:
748
      config.write("%s\n" % cpu_pinning)
749
    cpu_cap = hvp[constants.HV_CPU_CAP]
750
    if cpu_cap:
751
      config.write("cpu_cap=%d\n" % cpu_cap)
752
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
753
    if cpu_weight:
754
      config.write("cpu_weight=%d\n" % cpu_weight)
755

    
756
    config.write("name = '%s'\n" % instance.name)
757

    
758
    vif_data = []
759
    for nic in instance.nics:
760
      nic_str = "mac=%s" % (nic.mac)
761
      ip = getattr(nic, "ip", None)
762
      if ip is not None:
763
        nic_str += ", ip=%s" % ip
764
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
765
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
766
      vif_data.append("'%s'" % nic_str)
767

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

    
771
    config.write("vif = [%s]\n" % ",".join(vif_data))
772
    config.write("disk = [%s]\n" % ",".join(disk_data))
773

    
774
    if hvp[constants.HV_ROOT_PATH]:
775
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
776
    config.write("on_poweroff = 'destroy'\n")
777
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
778
      config.write("on_reboot = 'restart'\n")
779
    else:
780
      config.write("on_reboot = 'destroy'\n")
781
    config.write("on_crash = 'restart'\n")
782
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
783
    cls._WriteConfigFileStatic(instance.name, config.getvalue())
784

    
785
    return True
786

    
787

    
788
class XenHvmHypervisor(XenHypervisor):
789
  """Xen HVM hypervisor interface"""
790

    
791
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
792
    pathutils.VNC_PASSWORD_FILE,
793
    ]
794
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
795
    pathutils.VNC_PASSWORD_FILE,
796
    ]
797

    
798
  PARAMETERS = {
799
    constants.HV_ACPI: hv_base.NO_CHECK,
800
    constants.HV_BOOT_ORDER: (True, ) +
801
      (lambda x: x and len(x.strip("acdn")) == 0,
802
       "Invalid boot order specified, must be one or more of [acdn]",
803
       None, None),
804
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
805
    constants.HV_DISK_TYPE:
806
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
807
    constants.HV_NIC_TYPE:
808
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
809
    constants.HV_PAE: hv_base.NO_CHECK,
810
    constants.HV_VNC_BIND_ADDRESS:
811
      (False, netutils.IP4Address.IsValid,
812
       "VNC bind address is not a valid IP address", None, None),
813
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
814
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
815
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
816
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
817
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
818
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
819
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
820
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
821
    # Add PCI passthrough
822
    constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
823
    constants.HV_REBOOT_BEHAVIOR:
824
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
825
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
826
    constants.HV_CPU_CAP: hv_base.NO_CHECK,
827
    constants.HV_CPU_WEIGHT:
828
      (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
829
    }
830

    
831
  @classmethod
832
  def _WriteConfigFile(cls, instance, startup_memory, block_devices):
833
    """Create a Xen 3.1 HVM config file.
834

835
    """
836
    hvp = instance.hvparams
837

    
838
    config = StringIO()
839
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
840

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

    
845
    config.write("builder = 'hvm'\n")
846
    config.write("memory = %d\n" % startup_memory)
847
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
848
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
849
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
850
    if cpu_pinning:
851
      config.write("%s\n" % cpu_pinning)
852
    cpu_cap = hvp[constants.HV_CPU_CAP]
853
    if cpu_cap:
854
      config.write("cpu_cap=%d\n" % cpu_cap)
855
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
856
    if cpu_weight:
857
      config.write("cpu_weight=%d\n" % cpu_weight)
858

    
859
    config.write("name = '%s'\n" % instance.name)
860
    if hvp[constants.HV_PAE]:
861
      config.write("pae = 1\n")
862
    else:
863
      config.write("pae = 0\n")
864
    if hvp[constants.HV_ACPI]:
865
      config.write("acpi = 1\n")
866
    else:
867
      config.write("acpi = 0\n")
868
    config.write("apic = 1\n")
869
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
870
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
871
    config.write("sdl = 0\n")
872
    config.write("usb = 1\n")
873
    config.write("usbdevice = 'tablet'\n")
874
    config.write("vnc = 1\n")
875
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
876
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
877
    else:
878
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
879

    
880
    if instance.network_port > constants.VNC_BASE_PORT:
881
      display = instance.network_port - constants.VNC_BASE_PORT
882
      config.write("vncdisplay = %s\n" % display)
883
      config.write("vncunused = 0\n")
884
    else:
885
      config.write("# vncdisplay = 1\n")
886
      config.write("vncunused = 1\n")
887

    
888
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
889
    try:
890
      password = utils.ReadFile(vnc_pwd_file)
891
    except EnvironmentError, err:
892
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
893
                                   (vnc_pwd_file, err))
894

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

    
897
    config.write("serial = 'pty'\n")
898
    if hvp[constants.HV_USE_LOCALTIME]:
899
      config.write("localtime = 1\n")
900

    
901
    vif_data = []
902
    nic_type = hvp[constants.HV_NIC_TYPE]
903
    if nic_type is None:
904
      # ensure old instances don't change
905
      nic_type_str = ", type=ioemu"
906
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
907
      nic_type_str = ", type=paravirtualized"
908
    else:
909
      nic_type_str = ", model=%s, type=ioemu" % nic_type
910
    for nic in instance.nics:
911
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
912
      ip = getattr(nic, "ip", None)
913
      if ip is not None:
914
        nic_str += ", ip=%s" % ip
915
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
916
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
917
      vif_data.append("'%s'" % nic_str)
918

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

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

    
924
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
925
    if iso_path:
926
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
927
      disk_data.append(iso)
928

    
929
    config.write("disk = [%s]\n" % (",".join(disk_data)))
930
    # Add PCI passthrough
931
    pci_pass_arr = []
932
    pci_pass = hvp[constants.HV_PASSTHROUGH]
933
    if pci_pass:
934
      pci_pass_arr = pci_pass.split(";")
935
      config.write("pci = %s\n" % pci_pass_arr)
936
    config.write("on_poweroff = 'destroy'\n")
937
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
938
      config.write("on_reboot = 'restart'\n")
939
    else:
940
      config.write("on_reboot = 'destroy'\n")
941
    config.write("on_crash = 'restart'\n")
942
    cls._WriteConfigFileStatic(instance.name, config.getvalue())
943

    
944
    return True