Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ c3d839f5

History | View | Annotate | Download (31 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
import string # pylint: disable=W0402
28
from cStringIO import StringIO
29

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

    
39

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

    
47
_FILE_DRIVER_MAP = {
48
  constants.FD_LOOP: "file",
49
  constants.FD_BLKTAP: "tap:aio",
50
  }
51

    
52

    
53
def _CreateConfigCpus(cpu_mask):
54
  """Create a CPU config string for Xen's config file.
55

56
  """
57
  # Convert the string CPU mask to a list of list of int's
58
  cpu_list = utils.ParseMultiCpuMask(cpu_mask)
59

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

    
72
    def _GetCPUMap(vcpu):
73
      if vcpu[0] == constants.CPU_PINNING_ALL_VAL:
74
        cpu_map = constants.CPU_PINNING_ALL_XEN
75
      else:
76
        cpu_map = ",".join(map(str, vcpu))
77
      return "\"%s\"" % cpu_map
78

    
79
    # build the result string in format 'cpus = [ "c", "c", "c" ]',
80
    # where each c is a physical CPU number, a range, a list, or any
81
    # combination
82
    return "cpus = [ %s ]" % ", ".join(map(_GetCPUMap, cpu_list))
83

    
84

    
85
def _RunXmList(fn, xmllist_errors):
86
  """Helper function for L{_GetXmList} to run "xm list".
87

88
  @type fn: callable
89
  @param fn: Function returning result of running C{xm list}
90
  @type xmllist_errors: list
91
  @param xmllist_errors: Error list
92
  @rtype: list
93

94
  """
95
  result = fn()
96
  if result.failed:
97
    logging.error("xm list failed (%s): %s", result.fail_reason,
98
                  result.output)
99
    xmllist_errors.append(result)
100
    raise utils.RetryAgain()
101

    
102
  # skip over the heading
103
  return result.stdout.splitlines()
104

    
105

    
106
def _ParseXmList(lines, include_node):
107
  """Parses the output of C{xm list}.
108

109
  @type lines: list
110
  @param lines: Output lines of C{xm list}
111
  @type include_node: boolean
112
  @param include_node: If True, return information for Dom0
113
  @return: list of tuple containing (name, id, memory, vcpus, state, time
114
    spent)
115

116
  """
117
  result = []
118

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

    
137
    # skip the Domain-0 (optional)
138
    if include_node or data[0] != _DOM0_NAME:
139
      result.append(data)
140

    
141
  return result
142

    
143

    
144
def _GetXmList(fn, include_node, _timeout=5):
145
  """Return the list of running instances.
146

147
  See L{_RunXmList} and L{_ParseXmList} for parameter details.
148

149
  """
150
  xmllist_errors = []
151
  try:
152
    lines = utils.Retry(_RunXmList, (0.3, 1.5, 1.0), _timeout,
153
                        args=(fn, xmllist_errors))
154
  except utils.RetryTimeout:
155
    if xmllist_errors:
156
      xmlist_result = xmllist_errors.pop()
157

    
158
      errmsg = ("xm list failed, timeout exceeded (%s): %s" %
159
                (xmlist_result.fail_reason, xmlist_result.output))
160
    else:
161
      errmsg = "xm list failed"
162

    
163
    raise errors.HypervisorError(errmsg)
164

    
165
  return _ParseXmList(lines, include_node)
166

    
167

    
168
def _ParseNodeInfo(info):
169
  """Return information about the node.
170

171
  @return: a dict with the following keys (memory values in MiB):
172
        - memory_total: the total memory size on the node
173
        - memory_free: the available memory on the node for instances
174
        - nr_cpus: total number of CPUs
175
        - nr_nodes: in a NUMA system, the number of domains
176
        - nr_sockets: the number of physical CPU sockets in the node
177
        - hv_version: the hypervisor version in the form (major, minor)
178

179
  """
180
  result = {}
181
  cores_per_socket = threads_per_core = nr_cpus = None
182
  xen_major, xen_minor = None, None
183
  memory_total = None
184
  memory_free = None
185

    
186
  for line in info.splitlines():
187
    fields = line.split(":", 1)
188

    
189
    if len(fields) < 2:
190
      continue
191

    
192
    (key, val) = map(lambda s: s.strip(), fields)
193

    
194
    # Note: in Xen 3, memory has changed to total_memory
195
    if key in ("memory", "total_memory"):
196
      memory_total = int(val)
197
    elif key == "free_memory":
198
      memory_free = int(val)
199
    elif key == "nr_cpus":
200
      nr_cpus = result["cpu_total"] = int(val)
201
    elif key == "nr_nodes":
202
      result["cpu_nodes"] = int(val)
203
    elif key == "cores_per_socket":
204
      cores_per_socket = int(val)
205
    elif key == "threads_per_core":
206
      threads_per_core = int(val)
207
    elif key == "xen_major":
208
      xen_major = int(val)
209
    elif key == "xen_minor":
210
      xen_minor = int(val)
211

    
212
  if None not in [cores_per_socket, threads_per_core, nr_cpus]:
213
    result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
214

    
215
  if memory_free is not None:
216
    result["memory_free"] = memory_free
217

    
218
  if memory_total is not None:
219
    result["memory_total"] = memory_total
220

    
221
  if not (xen_major is None or xen_minor is None):
222
    result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
223

    
224
  return result
225

    
226

    
227
def _MergeInstanceInfo(info, fn):
228
  """Updates node information from L{_ParseNodeInfo} with instance info.
229

230
  @type info: dict
231
  @param info: Result from L{_ParseNodeInfo}
232
  @type fn: callable
233
  @param fn: Function returning result of running C{xm list}
234
  @rtype: dict
235

236
  """
237
  total_instmem = 0
238

    
239
  for (name, _, mem, vcpus, _, _) in fn(True):
240
    if name == _DOM0_NAME:
241
      info["memory_dom0"] = mem
242
      info["dom0_cpus"] = vcpus
243

    
244
    # Include Dom0 in total memory usage
245
    total_instmem += mem
246

    
247
  memory_free = info.get("memory_free")
248
  memory_total = info.get("memory_total")
249

    
250
  # Calculate memory used by hypervisor
251
  if None not in [memory_total, memory_free, total_instmem]:
252
    info["memory_hv"] = memory_total - memory_free - total_instmem
253

    
254
  return info
255

    
256

    
257
def _GetNodeInfo(info, fn):
258
  """Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}.
259

260
  """
261
  return _MergeInstanceInfo(_ParseNodeInfo(info), fn)
262

    
263

    
264
def _GetConfigFileDiskData(block_devices, blockdev_prefix,
265
                           _letters=_DISK_LETTERS):
266
  """Get disk directives for Xen config file.
267

268
  This method builds the xen config disk directive according to the
269
  given disk_template and block_devices.
270

271
  @param block_devices: list of tuples (cfdev, rldev):
272
      - cfdev: dict containing ganeti config disk part
273
      - rldev: ganeti.bdev.BlockDev object
274
  @param blockdev_prefix: a string containing blockdevice prefix,
275
                          e.g. "sd" for /dev/sda
276

277
  @return: string containing disk directive for xen instance config file
278

279
  """
280
  if len(block_devices) > len(_letters):
281
    raise errors.HypervisorError("Too many disks")
282

    
283
  disk_data = []
284

    
285
  for sd_suffix, (cfdev, dev_path) in zip(_letters, block_devices):
286
    sd_name = blockdev_prefix + sd_suffix
287

    
288
    if cfdev.mode == constants.DISK_RDWR:
289
      mode = "w"
290
    else:
291
      mode = "r"
292

    
293
    if cfdev.dev_type == constants.LD_FILE:
294
      driver = _FILE_DRIVER_MAP[cfdev.physical_id[0]]
295
    else:
296
      driver = "phy"
297

    
298
    disk_data.append("'%s:%s,%s,%s'" % (driver, dev_path, sd_name, mode))
299

    
300
  return disk_data
301

    
302

    
303
class XenHypervisor(hv_base.BaseHypervisor):
304
  """Xen generic hypervisor interface
305

306
  This is the Xen base class used for both Xen PVM and HVM. It contains
307
  all the functionality that is identical for both.
308

309
  """
310
  CAN_MIGRATE = True
311
  REBOOT_RETRY_COUNT = 60
312
  REBOOT_RETRY_INTERVAL = 10
313

    
314
  ANCILLARY_FILES = [
315
    XEND_CONFIG_FILE,
316
    XL_CONFIG_FILE,
317
    VIF_BRIDGE_SCRIPT,
318
    ]
319
  ANCILLARY_FILES_OPT = [
320
    XL_CONFIG_FILE,
321
    ]
322

    
323
  def __init__(self, _cfgdir=None):
324
    hv_base.BaseHypervisor.__init__(self)
325

    
326
    if _cfgdir is None:
327
      self._cfgdir = pathutils.XEN_CONFIG_DIR
328
    else:
329
      self._cfgdir = _cfgdir
330

    
331
  def _ConfigFileName(self, instance_name):
332
    """Get the config file name for an instance.
333

334
    @param instance_name: instance name
335
    @type instance_name: str
336
    @return: fully qualified path to instance config file
337
    @rtype: str
338

339
    """
340
    return utils.PathJoin(self._cfgdir, instance_name)
341

    
342
  @classmethod
343
  def _GetConfig(cls, instance, startup_memory, block_devices):
344
    """Build Xen configuration for an instance.
345

346
    """
347
    raise NotImplementedError
348

    
349
  def _WriteConfigFile(self, instance_name, data):
350
    """Write the Xen config file for the instance.
351

352
    This version of the function just writes the config file from static data.
353

354
    """
355
    # just in case it exists
356
    utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
357

    
358
    cfg_file = self._ConfigFileName(instance_name)
359
    try:
360
      utils.WriteFile(cfg_file, data=data)
361
    except EnvironmentError, err:
362
      raise errors.HypervisorError("Cannot write Xen instance configuration"
363
                                   " file %s: %s" % (cfg_file, err))
364

    
365
  def _ReadConfigFile(self, instance_name):
366
    """Returns the contents of the instance config file.
367

368
    """
369
    filename = self._ConfigFileName(instance_name)
370

    
371
    try:
372
      file_content = utils.ReadFile(filename)
373
    except EnvironmentError, err:
374
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
375

    
376
    return file_content
377

    
378
  def _RemoveConfigFile(self, instance_name):
379
    """Remove the xen configuration file.
380

381
    """
382
    utils.RemoveFile(self._ConfigFileName(instance_name))
383

    
384
  @staticmethod
385
  def _GetXmList(include_node):
386
    """Wrapper around module level L{_GetXmList}.
387

388
    """
389
    # TODO: Abstract running Xen command for testing
390
    return _GetXmList(lambda: utils.RunCmd([constants.XEN_CMD, "list"]),
391
                      include_node)
392

    
393
  def ListInstances(self):
394
    """Get the list of running instances.
395

396
    """
397
    xm_list = self._GetXmList(False)
398
    names = [info[0] for info in xm_list]
399
    return names
400

    
401
  def GetInstanceInfo(self, instance_name):
402
    """Get instance properties.
403

404
    @param instance_name: the instance name
405

406
    @return: tuple (name, id, memory, vcpus, stat, times)
407

408
    """
409
    xm_list = self._GetXmList(instance_name == _DOM0_NAME)
410
    result = None
411
    for data in xm_list:
412
      if data[0] == instance_name:
413
        result = data
414
        break
415
    return result
416

    
417
  def GetAllInstancesInfo(self):
418
    """Get properties of all instances.
419

420
    @return: list of tuples (name, id, memory, vcpus, stat, times)
421

422
    """
423
    xm_list = self._GetXmList(False)
424
    return xm_list
425

    
426
  def _MakeConfigFile(self, instance, startup_memory, block_devices):
427
    """Gather configuration details and write to disk.
428

429
    See L{_GetConfig} for arguments.
430

431
    """
432
    buf = StringIO()
433
    buf.write("# Automatically generated by Ganeti. Do not edit!\n")
434
    buf.write("\n")
435
    buf.write(self._GetConfig(instance, startup_memory, block_devices))
436
    buf.write("\n")
437

    
438
    self._WriteConfigFile(instance.name, buf.getvalue())
439

    
440
  def StartInstance(self, instance, block_devices, startup_paused):
441
    """Start an instance.
442

443
    """
444
    startup_memory = self._InstanceStartupMemory(instance)
445

    
446
    self._MakeConfigFile(instance, startup_memory, block_devices)
447

    
448
    cmd = [constants.XEN_CMD, "create"]
449
    if startup_paused:
450
      cmd.extend(["-p"])
451
    cmd.extend([self._ConfigFileName(instance.name)])
452
    result = utils.RunCmd(cmd)
453

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

    
459
  def StopInstance(self, instance, force=False, retry=False, name=None):
460
    """Stop an instance.
461

462
    """
463
    if name is None:
464
      name = instance.name
465
    self._RemoveConfigFile(name)
466
    if force:
467
      command = [constants.XEN_CMD, "destroy", name]
468
    else:
469
      command = [constants.XEN_CMD, "shutdown", name]
470
    result = utils.RunCmd(command)
471

    
472
    if result.failed:
473
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
474
                                   (name, result.fail_reason, result.output))
475

    
476
  def RebootInstance(self, instance):
477
    """Reboot an instance.
478

479
    """
480
    ini_info = self.GetInstanceInfo(instance.name)
481

    
482
    if ini_info is None:
483
      raise errors.HypervisorError("Failed to reboot instance %s,"
484
                                   " not running" % instance.name)
485

    
486
    result = utils.RunCmd([constants.XEN_CMD, "reboot", instance.name])
487
    if result.failed:
488
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
489
                                   (instance.name, result.fail_reason,
490
                                    result.output))
491

    
492
    def _CheckInstance():
493
      new_info = self.GetInstanceInfo(instance.name)
494

    
495
      # check if the domain ID has changed or the run time has decreased
496
      if (new_info is not None and
497
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
498
        return
499

    
500
      raise utils.RetryAgain()
501

    
502
    try:
503
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
504
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
505
    except utils.RetryTimeout:
506
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
507
                                   " did not reboot in the expected interval" %
508
                                   (instance.name, ))
509

    
510
  def BalloonInstanceMemory(self, instance, mem):
511
    """Balloon an instance memory to a certain value.
512

513
    @type instance: L{objects.Instance}
514
    @param instance: instance to be accepted
515
    @type mem: int
516
    @param mem: actual memory size to use for instance runtime
517

518
    """
519
    cmd = [constants.XEN_CMD, "mem-set", instance.name, mem]
520
    result = utils.RunCmd(cmd)
521
    if result.failed:
522
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
523
                                   (instance.name, result.fail_reason,
524
                                    result.output))
525
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
526
    cmd.append(self._ConfigFileName(instance.name))
527
    result = utils.RunCmd(cmd)
528
    if result.failed:
529
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
530
                                   (instance.name, result.fail_reason,
531
                                    result.output))
532

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

536
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
537

538
    """
539
    # TODO: Abstract running Xen command for testing
540
    result = utils.RunCmd([constants.XEN_CMD, "info"])
541
    if result.failed:
542
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
543
                    result.output)
544
      return None
545

    
546
    return _GetNodeInfo(result.stdout, self._GetXmList)
547

    
548
  @classmethod
549
  def GetInstanceConsole(cls, instance, hvparams, beparams):
550
    """Return a command for connecting to the console of an instance.
551

552
    """
553
    return objects.InstanceConsole(instance=instance.name,
554
                                   kind=constants.CONS_SSH,
555
                                   host=instance.primary_node,
556
                                   user=constants.SSH_CONSOLE_USER,
557
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
558
                                            constants.XEN_CMD, instance.name])
559

    
560
  def Verify(self):
561
    """Verify the hypervisor.
562

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

565
    @return: Problem description if something is wrong, C{None} otherwise
566

567
    """
568
    result = utils.RunCmd([constants.XEN_CMD, "info"])
569
    if result.failed:
570
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
571

    
572
    return None
573

    
574
  def MigrationInfo(self, instance):
575
    """Get instance information to perform a migration.
576

577
    @type instance: L{objects.Instance}
578
    @param instance: instance to be migrated
579
    @rtype: string
580
    @return: content of the xen config file
581

582
    """
583
    return self._ReadConfigFile(instance.name)
584

    
585
  def AcceptInstance(self, instance, info, target):
586
    """Prepare to accept an instance.
587

588
    @type instance: L{objects.Instance}
589
    @param instance: instance to be accepted
590
    @type info: string
591
    @param info: content of the xen config file on the source node
592
    @type target: string
593
    @param target: target host (usually ip), on this node
594

595
    """
596
    pass
597

    
598
  def FinalizeMigrationDst(self, instance, info, success):
599
    """Finalize an instance migration.
600

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

604
    @type instance: L{objects.Instance}
605
    @param instance: instance whose migration is being finalized
606
    @type info: string
607
    @param info: content of the xen config file on the source node
608
    @type success: boolean
609
    @param success: whether the migration was a success or a failure
610

611
    """
612
    if success:
613
      self._WriteConfigFile(instance.name, info)
614

    
615
  def MigrateInstance(self, instance, target, live):
616
    """Migrate an instance to a target node.
617

618
    The migration will not be attempted if the instance is not
619
    currently running.
620

621
    @type instance: L{objects.Instance}
622
    @param instance: the instance to be migrated
623
    @type target: string
624
    @param target: ip address of the target node
625
    @type live: boolean
626
    @param live: perform a live migration
627

628
    """
629
    if self.GetInstanceInfo(instance.name) is None:
630
      raise errors.HypervisorError("Instance not running, cannot migrate")
631

    
632
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
633

    
634
    if (constants.XEN_CMD == constants.XEN_CMD_XM and
635
        not netutils.TcpPing(target, port, live_port_needed=True)):
636
      raise errors.HypervisorError("Remote host %s not listening on port"
637
                                   " %s, cannot migrate" % (target, port))
638

    
639
    args = [constants.XEN_CMD, "migrate"]
640
    if constants.XEN_CMD == constants.XEN_CMD_XM:
641
      args.extend(["-p", "%d" % port])
642
      if live:
643
        args.append("-l")
644
    elif constants.XEN_CMD == constants.XEN_CMD_XL:
645
      cluster_name = ssconf.SimpleStore().GetClusterName()
646
      args.extend(["-s", constants.XL_SSH_CMD % cluster_name])
647
      args.extend(["-C", self._ConfigFileName(instance.name)])
648
    else:
649
      raise errors.HypervisorError("Unsupported xen command: %s" %
650
                                   constants.XEN_CMD)
651

    
652
    args.extend([instance.name, target])
653
    result = utils.RunCmd(args)
654
    if result.failed:
655
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
656
                                   (instance.name, result.output))
657

    
658
  def FinalizeMigrationSource(self, instance, success, live):
659
    """Finalize the instance migration on the source node.
660

661
    @type instance: L{objects.Instance}
662
    @param instance: the instance that was migrated
663
    @type success: bool
664
    @param success: whether the migration succeeded or not
665
    @type live: bool
666
    @param live: whether the user requested a live migration or not
667

668
    """
669
    # pylint: disable=W0613
670
    if success:
671
      # remove old xen file after migration succeeded
672
      try:
673
        self._RemoveConfigFile(instance.name)
674
      except EnvironmentError:
675
        logging.exception("Failure while removing instance config file")
676

    
677
  def GetMigrationStatus(self, instance):
678
    """Get the migration status
679

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

684
    @type instance: L{objects.Instance}
685
    @param instance: the instance that is being migrated
686
    @rtype: L{objects.MigrationStatus}
687
    @return: the status of the current migration (one of
688
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
689
             progress info that can be retrieved from the hypervisor
690

691
    """
692
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
693

    
694
  @classmethod
695
  def PowercycleNode(cls):
696
    """Xen-specific powercycle.
697

698
    This first does a Linux reboot (which triggers automatically a Xen
699
    reboot), and if that fails it tries to do a Xen reboot. The reason
700
    we don't try a Xen reboot first is that the xen reboot launches an
701
    external command which connects to the Xen hypervisor, and that
702
    won't work in case the root filesystem is broken and/or the xend
703
    daemon is not working.
704

705
    """
706
    try:
707
      cls.LinuxPowercycle()
708
    finally:
709
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
710

    
711

    
712
class XenPvmHypervisor(XenHypervisor):
713
  """Xen PVM hypervisor interface"""
714

    
715
  PARAMETERS = {
716
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
717
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
718
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
719
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
720
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
721
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
722
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
723
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
724
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
725
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
726
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
727
    constants.HV_REBOOT_BEHAVIOR:
728
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
729
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
730
    constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
731
    constants.HV_CPU_WEIGHT:
732
      (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
733
    }
734

    
735
  def _GetConfig(self, instance, startup_memory, block_devices):
736
    """Write the Xen config file for the instance.
737

738
    """
739
    hvp = instance.hvparams
740
    config = StringIO()
741
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
742

    
743
    # if bootloader is True, use bootloader instead of kernel and ramdisk
744
    # parameters.
745
    if hvp[constants.HV_USE_BOOTLOADER]:
746
      # bootloader handling
747
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
748
      if bootloader_path:
749
        config.write("bootloader = '%s'\n" % bootloader_path)
750
      else:
751
        raise errors.HypervisorError("Bootloader enabled, but missing"
752
                                     " bootloader path")
753

    
754
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
755
      if bootloader_args:
756
        config.write("bootargs = '%s'\n" % bootloader_args)
757
    else:
758
      # kernel handling
759
      kpath = hvp[constants.HV_KERNEL_PATH]
760
      config.write("kernel = '%s'\n" % kpath)
761

    
762
      # initrd handling
763
      initrd_path = hvp[constants.HV_INITRD_PATH]
764
      if initrd_path:
765
        config.write("ramdisk = '%s'\n" % initrd_path)
766

    
767
    # rest of the settings
768
    config.write("memory = %d\n" % startup_memory)
769
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
770
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
771
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
772
    if cpu_pinning:
773
      config.write("%s\n" % cpu_pinning)
774
    cpu_cap = hvp[constants.HV_CPU_CAP]
775
    if cpu_cap:
776
      config.write("cpu_cap=%d\n" % cpu_cap)
777
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
778
    if cpu_weight:
779
      config.write("cpu_weight=%d\n" % cpu_weight)
780

    
781
    config.write("name = '%s'\n" % instance.name)
782

    
783
    vif_data = []
784
    for nic in instance.nics:
785
      nic_str = "mac=%s" % (nic.mac)
786
      ip = getattr(nic, "ip", None)
787
      if ip is not None:
788
        nic_str += ", ip=%s" % ip
789
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
790
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
791
      vif_data.append("'%s'" % nic_str)
792

    
793
    disk_data = \
794
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
795

    
796
    config.write("vif = [%s]\n" % ",".join(vif_data))
797
    config.write("disk = [%s]\n" % ",".join(disk_data))
798

    
799
    if hvp[constants.HV_ROOT_PATH]:
800
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
801
    config.write("on_poweroff = 'destroy'\n")
802
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
803
      config.write("on_reboot = 'restart'\n")
804
    else:
805
      config.write("on_reboot = 'destroy'\n")
806
    config.write("on_crash = 'restart'\n")
807
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
808

    
809
    return config.getvalue()
810

    
811

    
812
class XenHvmHypervisor(XenHypervisor):
813
  """Xen HVM hypervisor interface"""
814

    
815
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
816
    pathutils.VNC_PASSWORD_FILE,
817
    ]
818
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
819
    pathutils.VNC_PASSWORD_FILE,
820
    ]
821

    
822
  PARAMETERS = {
823
    constants.HV_ACPI: hv_base.NO_CHECK,
824
    constants.HV_BOOT_ORDER: (True, ) +
825
      (lambda x: x and len(x.strip("acdn")) == 0,
826
       "Invalid boot order specified, must be one or more of [acdn]",
827
       None, None),
828
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
829
    constants.HV_DISK_TYPE:
830
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
831
    constants.HV_NIC_TYPE:
832
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
833
    constants.HV_PAE: hv_base.NO_CHECK,
834
    constants.HV_VNC_BIND_ADDRESS:
835
      (False, netutils.IP4Address.IsValid,
836
       "VNC bind address is not a valid IP address", None, None),
837
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
838
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
839
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
840
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
841
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
842
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
843
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
844
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
845
    # Add PCI passthrough
846
    constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
847
    constants.HV_REBOOT_BEHAVIOR:
848
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
849
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
850
    constants.HV_CPU_CAP: hv_base.NO_CHECK,
851
    constants.HV_CPU_WEIGHT:
852
      (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
853
    }
854

    
855
  def _GetConfig(self, instance, startup_memory, block_devices):
856
    """Create a Xen 3.1 HVM config file.
857

858
    """
859
    hvp = instance.hvparams
860

    
861
    config = StringIO()
862

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

    
867
    config.write("builder = 'hvm'\n")
868
    config.write("memory = %d\n" % startup_memory)
869
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
870
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
871
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
872
    if cpu_pinning:
873
      config.write("%s\n" % cpu_pinning)
874
    cpu_cap = hvp[constants.HV_CPU_CAP]
875
    if cpu_cap:
876
      config.write("cpu_cap=%d\n" % cpu_cap)
877
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
878
    if cpu_weight:
879
      config.write("cpu_weight=%d\n" % cpu_weight)
880

    
881
    config.write("name = '%s'\n" % instance.name)
882
    if hvp[constants.HV_PAE]:
883
      config.write("pae = 1\n")
884
    else:
885
      config.write("pae = 0\n")
886
    if hvp[constants.HV_ACPI]:
887
      config.write("acpi = 1\n")
888
    else:
889
      config.write("acpi = 0\n")
890
    config.write("apic = 1\n")
891
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
892
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
893
    config.write("sdl = 0\n")
894
    config.write("usb = 1\n")
895
    config.write("usbdevice = 'tablet'\n")
896
    config.write("vnc = 1\n")
897
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
898
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
899
    else:
900
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
901

    
902
    if instance.network_port > constants.VNC_BASE_PORT:
903
      display = instance.network_port - constants.VNC_BASE_PORT
904
      config.write("vncdisplay = %s\n" % display)
905
      config.write("vncunused = 0\n")
906
    else:
907
      config.write("# vncdisplay = 1\n")
908
      config.write("vncunused = 1\n")
909

    
910
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
911
    try:
912
      password = utils.ReadFile(vnc_pwd_file)
913
    except EnvironmentError, err:
914
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
915
                                   (vnc_pwd_file, err))
916

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

    
919
    config.write("serial = 'pty'\n")
920
    if hvp[constants.HV_USE_LOCALTIME]:
921
      config.write("localtime = 1\n")
922

    
923
    vif_data = []
924
    nic_type = hvp[constants.HV_NIC_TYPE]
925
    if nic_type is None:
926
      # ensure old instances don't change
927
      nic_type_str = ", type=ioemu"
928
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
929
      nic_type_str = ", type=paravirtualized"
930
    else:
931
      nic_type_str = ", model=%s, type=ioemu" % nic_type
932
    for nic in instance.nics:
933
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
934
      ip = getattr(nic, "ip", None)
935
      if ip is not None:
936
        nic_str += ", ip=%s" % ip
937
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
938
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
939
      vif_data.append("'%s'" % nic_str)
940

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

    
943
    disk_data = \
944
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
945

    
946
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
947
    if iso_path:
948
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
949
      disk_data.append(iso)
950

    
951
    config.write("disk = [%s]\n" % (",".join(disk_data)))
952
    # Add PCI passthrough
953
    pci_pass_arr = []
954
    pci_pass = hvp[constants.HV_PASSTHROUGH]
955
    if pci_pass:
956
      pci_pass_arr = pci_pass.split(";")
957
      config.write("pci = %s\n" % pci_pass_arr)
958
    config.write("on_poweroff = 'destroy'\n")
959
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
960
      config.write("on_reboot = 'restart'\n")
961
    else:
962
      config.write("on_reboot = 'destroy'\n")
963
    config.write("on_crash = 'restart'\n")
964

    
965
    return config.getvalue()