Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 45ba54c4

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

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

    
41

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

    
49
_FILE_DRIVER_MAP = {
50
  constants.FD_LOOP: "file",
51
  constants.FD_BLKTAP: "tap:aio",
52
  constants.FD_BLKTAP2: "tap2:tapdisk:aio",
53
  }
54

    
55

    
56
def _CreateConfigCpus(cpu_mask):
57
  """Create a CPU config string for Xen's config file.
58

59
  """
60
  # Convert the string CPU mask to a list of list of int's
61
  cpu_list = utils.ParseMultiCpuMask(cpu_mask)
62

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

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

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

    
87

    
88
def _RunXmList(fn, xmllist_errors):
89
  """Helper function for L{_GetXmList} to run "xm list".
90

91
  @type fn: callable
92
  @param fn: Function returning result of running C{xm list}
93
  @type xmllist_errors: list
94
  @param xmllist_errors: Error list
95
  @rtype: list
96

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

    
105
  # skip over the heading
106
  return result.stdout.splitlines()
107

    
108

    
109
def _ParseXmList(lines, include_node):
110
  """Parses the output of C{xm list}.
111

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

119
  """
120
  result = []
121

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

    
140
    # skip the Domain-0 (optional)
141
    if include_node or data[0] != _DOM0_NAME:
142
      result.append(data)
143

    
144
  return result
145

    
146

    
147
def _GetXmList(fn, include_node, _timeout=5):
148
  """Return the list of running instances.
149

150
  See L{_RunXmList} and L{_ParseXmList} for parameter details.
151

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

    
161
      errmsg = ("xm list failed, timeout exceeded (%s): %s" %
162
                (xmlist_result.fail_reason, xmlist_result.output))
163
    else:
164
      errmsg = "xm list failed"
165

    
166
    raise errors.HypervisorError(errmsg)
167

    
168
  return _ParseXmList(lines, include_node)
169

    
170

    
171
def _IsInstanceRunning(instance_info):
172
  return instance_info == "r-----" \
173
      or instance_info == "-b----"
174

    
175

    
176
def _IsInstanceShutdown(instance_info):
177
  return instance_info == "---s--"
178

    
179

    
180
def _ParseNodeInfo(info):
181
  """Return information about the node.
182

183
  @return: a dict with the following keys (memory values in MiB):
184
        - memory_total: the total memory size on the node
185
        - memory_free: the available memory on the node for instances
186
        - nr_cpus: total number of CPUs
187
        - nr_nodes: in a NUMA system, the number of domains
188
        - nr_sockets: the number of physical CPU sockets in the node
189
        - hv_version: the hypervisor version in the form (major, minor)
190

191
  """
192
  result = {}
193
  cores_per_socket = threads_per_core = nr_cpus = None
194
  xen_major, xen_minor = None, None
195
  memory_total = None
196
  memory_free = None
197

    
198
  for line in info.splitlines():
199
    fields = line.split(":", 1)
200

    
201
    if len(fields) < 2:
202
      continue
203

    
204
    (key, val) = map(lambda s: s.strip(), fields)
205

    
206
    # Note: in Xen 3, memory has changed to total_memory
207
    if key in ("memory", "total_memory"):
208
      memory_total = int(val)
209
    elif key == "free_memory":
210
      memory_free = int(val)
211
    elif key == "nr_cpus":
212
      nr_cpus = result["cpu_total"] = int(val)
213
    elif key == "nr_nodes":
214
      result["cpu_nodes"] = int(val)
215
    elif key == "cores_per_socket":
216
      cores_per_socket = int(val)
217
    elif key == "threads_per_core":
218
      threads_per_core = int(val)
219
    elif key == "xen_major":
220
      xen_major = int(val)
221
    elif key == "xen_minor":
222
      xen_minor = int(val)
223

    
224
  if None not in [cores_per_socket, threads_per_core, nr_cpus]:
225
    result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
226

    
227
  if memory_free is not None:
228
    result["memory_free"] = memory_free
229

    
230
  if memory_total is not None:
231
    result["memory_total"] = memory_total
232

    
233
  if not (xen_major is None or xen_minor is None):
234
    result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
235

    
236
  return result
237

    
238

    
239
def _MergeInstanceInfo(info, fn):
240
  """Updates node information from L{_ParseNodeInfo} with instance info.
241

242
  @type info: dict
243
  @param info: Result from L{_ParseNodeInfo}
244
  @type fn: callable
245
  @param fn: Function returning result of running C{xm list}
246
  @rtype: dict
247

248
  """
249
  total_instmem = 0
250

    
251
  for (name, _, mem, vcpus, _, _) in fn(True):
252
    if name == _DOM0_NAME:
253
      info["memory_dom0"] = mem
254
      info["dom0_cpus"] = vcpus
255

    
256
    # Include Dom0 in total memory usage
257
    total_instmem += mem
258

    
259
  memory_free = info.get("memory_free")
260
  memory_total = info.get("memory_total")
261

    
262
  # Calculate memory used by hypervisor
263
  if None not in [memory_total, memory_free, total_instmem]:
264
    info["memory_hv"] = memory_total - memory_free - total_instmem
265

    
266
  return info
267

    
268

    
269
def _GetNodeInfo(info, fn):
270
  """Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}.
271

272
  """
273
  return _MergeInstanceInfo(_ParseNodeInfo(info), fn)
274

    
275

    
276
def _GetConfigFileDiskData(block_devices, blockdev_prefix,
277
                           _letters=_DISK_LETTERS):
278
  """Get disk directives for Xen config file.
279

280
  This method builds the xen config disk directive according to the
281
  given disk_template and block_devices.
282

283
  @param block_devices: list of tuples (cfdev, rldev):
284
      - cfdev: dict containing ganeti config disk part
285
      - rldev: ganeti.bdev.BlockDev object
286
  @param blockdev_prefix: a string containing blockdevice prefix,
287
                          e.g. "sd" for /dev/sda
288

289
  @return: string containing disk directive for xen instance config file
290

291
  """
292
  if len(block_devices) > len(_letters):
293
    raise errors.HypervisorError("Too many disks")
294

    
295
  disk_data = []
296

    
297
  for sd_suffix, (cfdev, dev_path) in zip(_letters, block_devices):
298
    sd_name = blockdev_prefix + sd_suffix
299

    
300
    if cfdev.mode == constants.DISK_RDWR:
301
      mode = "w"
302
    else:
303
      mode = "r"
304

    
305
    if cfdev.dev_type == constants.LD_FILE:
306
      driver = _FILE_DRIVER_MAP[cfdev.physical_id[0]]
307
    else:
308
      driver = "phy"
309

    
310
    disk_data.append("'%s:%s,%s,%s'" % (driver, dev_path, sd_name, mode))
311

    
312
  return disk_data
313

    
314

    
315
class XenHypervisor(hv_base.BaseHypervisor):
316
  """Xen generic hypervisor interface
317

318
  This is the Xen base class used for both Xen PVM and HVM. It contains
319
  all the functionality that is identical for both.
320

321
  """
322
  CAN_MIGRATE = True
323
  REBOOT_RETRY_COUNT = 60
324
  REBOOT_RETRY_INTERVAL = 10
325
  _ROOT_DIR = pathutils.RUN_DIR + "/xen-hypervisor"
326
  _NICS_DIR = _ROOT_DIR + "/nic" # contains NICs' info
327
  _DIRS = [_ROOT_DIR, _NICS_DIR]
328

    
329
  ANCILLARY_FILES = [
330
    XEND_CONFIG_FILE,
331
    XL_CONFIG_FILE,
332
    VIF_BRIDGE_SCRIPT,
333
    ]
334
  ANCILLARY_FILES_OPT = [
335
    XL_CONFIG_FILE,
336
    ]
337

    
338
  def __init__(self, _cfgdir=None, _run_cmd_fn=None, _cmd=None):
339
    hv_base.BaseHypervisor.__init__(self)
340

    
341
    if _cfgdir is None:
342
      self._cfgdir = pathutils.XEN_CONFIG_DIR
343
    else:
344
      self._cfgdir = _cfgdir
345

    
346
    if _run_cmd_fn is None:
347
      self._run_cmd_fn = utils.RunCmd
348
    else:
349
      self._run_cmd_fn = _run_cmd_fn
350

    
351
    self._cmd = _cmd
352

    
353
  def _GetCommand(self):
354
    """Returns Xen command to use.
355

356
    """
357
    if self._cmd is None:
358
      # TODO: Make command a hypervisor parameter
359
      cmd = constants.XEN_CMD
360
    else:
361
      cmd = self._cmd
362

    
363
    if cmd not in constants.KNOWN_XEN_COMMANDS:
364
      raise errors.ProgrammerError("Unknown Xen command '%s'" % cmd)
365

    
366
    return cmd
367

    
368
  def _RunXen(self, args):
369
    """Wrapper around L{utils.process.RunCmd} to run Xen command.
370

371
    @see: L{utils.process.RunCmd}
372

373
    """
374
    cmd = [self._GetCommand()]
375
    cmd.extend(args)
376

    
377
    return self._run_cmd_fn(cmd)
378

    
379
  def _ConfigFileName(self, instance_name):
380
    """Get the config file name for an instance.
381

382
    @param instance_name: instance name
383
    @type instance_name: str
384
    @return: fully qualified path to instance config file
385
    @rtype: str
386

387
    """
388
    return utils.PathJoin(self._cfgdir, instance_name)
389

    
390
  @classmethod
391
  def _WriteNICInfoFile(cls, instance, idx, nic):
392
    """Write the Xen config file for the instance.
393

394
    This version of the function just writes the config file from static data.
395

396
    """
397
    instance_name = instance.name
398
    dirs = [(dname, constants.RUN_DIRS_MODE)
399
            for dname in cls._DIRS + [cls._InstanceNICDir(instance_name)]]
400
    utils.EnsureDirs(dirs)
401

    
402
    cfg_file = cls._InstanceNICFile(instance_name, idx)
403
    data = StringIO()
404

    
405
    data.write("TAGS=%s\n" % "\ ".join(instance.GetTags()))
406
    if nic.netinfo:
407
      netinfo = objects.Network.FromDict(nic.netinfo)
408
      for k, v in netinfo.HooksDict().iteritems():
409
        data.write("%s=%s\n" % (k, v))
410

    
411
    data.write("MAC=%s\n" % nic.mac)
412
    data.write("IP=%s\n" % nic.ip)
413
    data.write("MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
414
    data.write("LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
415

    
416
    try:
417
      utils.WriteFile(cfg_file, data=data.getvalue())
418
    except EnvironmentError, err:
419
      raise errors.HypervisorError("Cannot write Xen instance configuration"
420
                                   " file %s: %s" % (cfg_file, err))
421

    
422
  @classmethod
423
  def _InstanceNICDir(cls, instance_name):
424
    """Returns the directory holding the tap device files for a given instance.
425

426
    """
427
    return utils.PathJoin(cls._NICS_DIR, instance_name)
428

    
429
  @classmethod
430
  def _InstanceNICFile(cls, instance_name, seq):
431
    """Returns the name of the file containing the tap device for a given NIC
432

433
    """
434
    return utils.PathJoin(cls._InstanceNICDir(instance_name), str(seq))
435

    
436
  @classmethod
437
  def _GetConfig(cls, instance, startup_memory, block_devices):
438
    """Build Xen configuration for an instance.
439

440
    """
441
    raise NotImplementedError
442

    
443
  def _WriteConfigFile(self, instance_name, data):
444
    """Write the Xen config file for the instance.
445

446
    This version of the function just writes the config file from static data.
447

448
    """
449
    # just in case it exists
450
    utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
451

    
452
    cfg_file = self._ConfigFileName(instance_name)
453
    try:
454
      utils.WriteFile(cfg_file, data=data)
455
    except EnvironmentError, err:
456
      raise errors.HypervisorError("Cannot write Xen instance configuration"
457
                                   " file %s: %s" % (cfg_file, err))
458

    
459
  def _ReadConfigFile(self, instance_name):
460
    """Returns the contents of the instance config file.
461

462
    """
463
    filename = self._ConfigFileName(instance_name)
464

    
465
    try:
466
      file_content = utils.ReadFile(filename)
467
    except EnvironmentError, err:
468
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
469

    
470
    return file_content
471

    
472
  def _RemoveConfigFile(self, instance_name):
473
    """Remove the xen configuration file.
474

475
    """
476
    utils.RemoveFile(self._ConfigFileName(instance_name))
477
    try:
478
      shutil.rmtree(self._InstanceNICDir(instance_name))
479
    except OSError, err:
480
      if err.errno != errno.ENOENT:
481
        raise
482

    
483
  def _StashConfigFile(self, instance_name):
484
    """Move the Xen config file to the log directory and return its new path.
485

486
    """
487
    old_filename = self._ConfigFileName(instance_name)
488
    base = ("%s-%s" %
489
            (instance_name, utils.TimestampForFilename()))
490
    new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base)
491
    utils.RenameFile(old_filename, new_filename)
492
    return new_filename
493

    
494
  def _GetXmList(self, include_node):
495
    """Wrapper around module level L{_GetXmList}.
496

497
    """
498
    return _GetXmList(lambda: self._RunXen(["list"]), include_node)
499

    
500
  def ListInstances(self):
501
    """Get the list of running instances.
502

503
    """
504
    xm_list = self._GetXmList(False)
505
    names = [info[0] for info in xm_list]
506
    return names
507

    
508
  def GetInstanceInfo(self, instance_name):
509
    """Get instance properties.
510

511
    @param instance_name: the instance name
512

513
    @return: tuple (name, id, memory, vcpus, stat, times)
514

515
    """
516
    xm_list = self._GetXmList(instance_name == _DOM0_NAME)
517
    result = None
518
    for data in xm_list:
519
      if data[0] == instance_name:
520
        result = data
521
        break
522
    return result
523

    
524
  def GetAllInstancesInfo(self):
525
    """Get properties of all instances.
526

527
    @return: list of tuples (name, id, memory, vcpus, stat, times)
528

529
    """
530
    xm_list = self._GetXmList(False)
531
    return xm_list
532

    
533
  def _MakeConfigFile(self, instance, startup_memory, block_devices):
534
    """Gather configuration details and write to disk.
535

536
    See L{_GetConfig} for arguments.
537

538
    """
539
    buf = StringIO()
540
    buf.write("# Automatically generated by Ganeti. Do not edit!\n")
541
    buf.write("\n")
542
    buf.write(self._GetConfig(instance, startup_memory, block_devices))
543
    buf.write("\n")
544

    
545
    self._WriteConfigFile(instance.name, buf.getvalue())
546

    
547
  def StartInstance(self, instance, block_devices, startup_paused):
548
    """Start an instance.
549

550
    """
551
    startup_memory = self._InstanceStartupMemory(instance)
552

    
553
    self._MakeConfigFile(instance, startup_memory, block_devices)
554

    
555
    cmd = ["create"]
556
    if startup_paused:
557
      cmd.append("-p")
558
    cmd.append(self._ConfigFileName(instance.name))
559

    
560
    result = self._RunXen(cmd)
561
    if result.failed:
562
      # Move the Xen configuration file to the log directory to avoid
563
      # leaving a stale config file behind.
564
      stashed_config = self._StashConfigFile(instance.name)
565
      raise errors.HypervisorError("Failed to start instance %s: %s (%s). Moved"
566
                                   " config file to %s" %
567
                                   (instance.name, result.fail_reason,
568
                                    result.output, stashed_config))
569

    
570
  def StopInstance(self, instance, force=False, retry=False, name=None):
571
    """Stop an instance.
572

573
    """
574
    if name is None:
575
      name = instance.name
576

    
577
    return self._StopInstance(name, force)
578

    
579
  def _ShutdownInstance(self, name):
580
    """Shutdown an instance if the instance is running.
581

582
    @type name: string
583
    @param name: name of the instance to stop
584

585
    The '-w' flag waits for shutdown to complete which avoids the need
586
    to poll in the case where we want to destroy the domain
587
    immediately after shutdown.
588

589
    """
590
    instance_info = self.GetInstanceInfo(name)
591

    
592
    if instance_info is None or _IsInstanceShutdown(instance_info[4]):
593
      logging.info("Failed to shutdown instance %s, not running", name)
594
      return None
595

    
596
    return self._RunXen(["shutdown", "-w", name])
597

    
598
  def _DestroyInstance(self, name):
599
    """Destroy an instance if the instance if the instance exists.
600

601
    @type name: string
602
    @param name: name of the instance to destroy
603

604
    """
605
    instance_info = self.GetInstanceInfo(name)
606

    
607
    if instance_info is None:
608
      logging.info("Failed to destroy instance %s, does not exist", name)
609
      return None
610

    
611
    return self._RunXen(["destroy", name])
612

    
613
  def _StopInstance(self, name, force):
614
    """Stop an instance.
615

616
    @type name: string
617
    @param name: name of the instance to destroy
618

619
    @type force: boolean
620
    @param force: whether to do a "hard" stop (destroy)
621

622
    """
623
    if force:
624
      result = self._DestroyInstance(name)
625
    else:
626
      self._ShutdownInstance(name)
627
      result = self._DestroyInstance(name)
628

    
629
    if result is not None and result.failed and \
630
          self.GetInstanceInfo(name) is not None:
631
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
632
                                   (name, result.fail_reason, result.output))
633

    
634
    # Remove configuration file if stopping/starting instance was successful
635
    self._RemoveConfigFile(name)
636

    
637
  def RebootInstance(self, instance):
638
    """Reboot an instance.
639

640
    """
641
    ini_info = self.GetInstanceInfo(instance.name)
642

    
643
    if ini_info is None:
644
      raise errors.HypervisorError("Failed to reboot instance %s,"
645
                                   " not running" % instance.name)
646

    
647
    result = self._RunXen(["reboot", instance.name])
648
    if result.failed:
649
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
650
                                   (instance.name, result.fail_reason,
651
                                    result.output))
652

    
653
    def _CheckInstance():
654
      new_info = self.GetInstanceInfo(instance.name)
655

    
656
      # check if the domain ID has changed or the run time has decreased
657
      if (new_info is not None and
658
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
659
        return
660

    
661
      raise utils.RetryAgain()
662

    
663
    try:
664
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
665
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
666
    except utils.RetryTimeout:
667
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
668
                                   " did not reboot in the expected interval" %
669
                                   (instance.name, ))
670

    
671
  def BalloonInstanceMemory(self, instance, mem):
672
    """Balloon an instance memory to a certain value.
673

674
    @type instance: L{objects.Instance}
675
    @param instance: instance to be accepted
676
    @type mem: int
677
    @param mem: actual memory size to use for instance runtime
678

679
    """
680
    result = self._RunXen(["mem-set", instance.name, mem])
681
    if result.failed:
682
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
683
                                   (instance.name, result.fail_reason,
684
                                    result.output))
685

    
686
    # Update configuration file
687
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
688
    cmd.append(self._ConfigFileName(instance.name))
689

    
690
    result = utils.RunCmd(cmd)
691
    if result.failed:
692
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
693
                                   (instance.name, result.fail_reason,
694
                                    result.output))
695

    
696
  def GetNodeInfo(self):
697
    """Return information about the node.
698

699
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
700

701
    """
702
    result = self._RunXen(["info"])
703
    if result.failed:
704
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
705
                    result.output)
706
      return None
707

    
708
    return _GetNodeInfo(result.stdout, self._GetXmList)
709

    
710
  @classmethod
711
  def GetInstanceConsole(cls, instance, hvparams, beparams):
712
    """Return a command for connecting to the console of an instance.
713

714
    """
715
    return objects.InstanceConsole(instance=instance.name,
716
                                   kind=constants.CONS_SSH,
717
                                   host=instance.primary_node,
718
                                   user=constants.SSH_CONSOLE_USER,
719
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
720
                                            constants.XEN_CMD, instance.name])
721

    
722
  def Verify(self):
723
    """Verify the hypervisor.
724

725
    For Xen, this verifies that the xend process is running.
726

727
    @return: Problem description if something is wrong, C{None} otherwise
728

729
    """
730
    result = self._RunXen(["info"])
731
    if result.failed:
732
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
733

    
734
    return None
735

    
736
  def MigrationInfo(self, instance):
737
    """Get instance information to perform a migration.
738

739
    @type instance: L{objects.Instance}
740
    @param instance: instance to be migrated
741
    @rtype: string
742
    @return: content of the xen config file
743

744
    """
745
    return self._ReadConfigFile(instance.name)
746

    
747
  def AcceptInstance(self, instance, info, target):
748
    """Prepare to accept an instance.
749

750
    @type instance: L{objects.Instance}
751
    @param instance: instance to be accepted
752
    @type info: string
753
    @param info: content of the xen config file on the source node
754
    @type target: string
755
    @param target: target host (usually ip), on this node
756

757
    """
758
    pass
759

    
760
  def FinalizeMigrationDst(self, instance, info, success):
761
    """Finalize an instance migration.
762

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

766
    @type instance: L{objects.Instance}
767
    @param instance: instance whose migration is being finalized
768
    @type info: string
769
    @param info: content of the xen config file on the source node
770
    @type success: boolean
771
    @param success: whether the migration was a success or a failure
772

773
    """
774
    if success:
775
      self._WriteConfigFile(instance.name, info)
776

    
777
  def MigrateInstance(self, instance, target, live):
778
    """Migrate an instance to a target node.
779

780
    The migration will not be attempted if the instance is not
781
    currently running.
782

783
    @type instance: L{objects.Instance}
784
    @param instance: the instance to be migrated
785
    @type target: string
786
    @param target: ip address of the target node
787
    @type live: boolean
788
    @param live: perform a live migration
789

790
    """
791
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
792

    
793
    # TODO: Pass cluster name via RPC
794
    cluster_name = ssconf.SimpleStore().GetClusterName()
795

    
796
    return self._MigrateInstance(cluster_name, instance.name, target, port,
797
                                 live)
798

    
799
  def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
800
                       _ping_fn=netutils.TcpPing):
801
    """Migrate an instance to a target node.
802

803
    @see: L{MigrateInstance} for details
804

805
    """
806
    if self.GetInstanceInfo(instance_name) is None:
807
      raise errors.HypervisorError("Instance not running, cannot migrate")
808

    
809
    cmd = self._GetCommand()
810

    
811
    if (cmd == constants.XEN_CMD_XM and
812
        not _ping_fn(target, port, live_port_needed=True)):
813
      raise errors.HypervisorError("Remote host %s not listening on port"
814
                                   " %s, cannot migrate" % (target, port))
815

    
816
    args = ["migrate"]
817

    
818
    if cmd == constants.XEN_CMD_XM:
819
      args.extend(["-p", "%d" % port])
820
      if live:
821
        args.append("-l")
822

    
823
    elif cmd == constants.XEN_CMD_XL:
824
      args.extend([
825
        "-s", constants.XL_SSH_CMD % cluster_name,
826
        "-C", self._ConfigFileName(instance_name),
827
        ])
828

    
829
    else:
830
      raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
831

    
832
    args.extend([instance_name, target])
833

    
834
    result = self._RunXen(args)
835
    if result.failed:
836
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
837
                                   (instance_name, result.output))
838

    
839
  def FinalizeMigrationSource(self, instance, success, live):
840
    """Finalize the instance migration on the source node.
841

842
    @type instance: L{objects.Instance}
843
    @param instance: the instance that was migrated
844
    @type success: bool
845
    @param success: whether the migration succeeded or not
846
    @type live: bool
847
    @param live: whether the user requested a live migration or not
848

849
    """
850
    # pylint: disable=W0613
851
    if success:
852
      # remove old xen file after migration succeeded
853
      try:
854
        self._RemoveConfigFile(instance.name)
855
      except EnvironmentError:
856
        logging.exception("Failure while removing instance config file")
857

    
858
  def GetMigrationStatus(self, instance):
859
    """Get the migration status
860

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

865
    @type instance: L{objects.Instance}
866
    @param instance: the instance that is being migrated
867
    @rtype: L{objects.MigrationStatus}
868
    @return: the status of the current migration (one of
869
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
870
             progress info that can be retrieved from the hypervisor
871

872
    """
873
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
874

    
875
  @classmethod
876
  def PowercycleNode(cls):
877
    """Xen-specific powercycle.
878

879
    This first does a Linux reboot (which triggers automatically a Xen
880
    reboot), and if that fails it tries to do a Xen reboot. The reason
881
    we don't try a Xen reboot first is that the xen reboot launches an
882
    external command which connects to the Xen hypervisor, and that
883
    won't work in case the root filesystem is broken and/or the xend
884
    daemon is not working.
885

886
    """
887
    try:
888
      cls.LinuxPowercycle()
889
    finally:
890
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
891

    
892

    
893
class XenPvmHypervisor(XenHypervisor):
894
  """Xen PVM hypervisor interface"""
895

    
896
  PARAMETERS = {
897
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
898
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
899
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
900
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
901
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
902
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
903
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
904
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
905
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
906
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
907
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
908
    constants.HV_REBOOT_BEHAVIOR:
909
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
910
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
911
    constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
912
    constants.HV_CPU_WEIGHT:
913
      (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
914
    constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
915
    }
916

    
917
  def _GetConfig(self, instance, startup_memory, block_devices):
918
    """Write the Xen config file for the instance.
919

920
    """
921
    hvp = instance.hvparams
922
    config = StringIO()
923
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
924

    
925
    # if bootloader is True, use bootloader instead of kernel and ramdisk
926
    # parameters.
927
    if hvp[constants.HV_USE_BOOTLOADER]:
928
      # bootloader handling
929
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
930
      if bootloader_path:
931
        config.write("bootloader = '%s'\n" % bootloader_path)
932
      else:
933
        raise errors.HypervisorError("Bootloader enabled, but missing"
934
                                     " bootloader path")
935

    
936
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
937
      if bootloader_args:
938
        config.write("bootargs = '%s'\n" % bootloader_args)
939
    else:
940
      # kernel handling
941
      kpath = hvp[constants.HV_KERNEL_PATH]
942
      config.write("kernel = '%s'\n" % kpath)
943

    
944
      # initrd handling
945
      initrd_path = hvp[constants.HV_INITRD_PATH]
946
      if initrd_path:
947
        config.write("ramdisk = '%s'\n" % initrd_path)
948

    
949
    # rest of the settings
950
    config.write("memory = %d\n" % startup_memory)
951
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
952
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
953
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
954
    if cpu_pinning:
955
      config.write("%s\n" % cpu_pinning)
956
    cpu_cap = hvp[constants.HV_CPU_CAP]
957
    if cpu_cap:
958
      config.write("cpu_cap=%d\n" % cpu_cap)
959
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
960
    if cpu_weight:
961
      config.write("cpu_weight=%d\n" % cpu_weight)
962

    
963
    config.write("name = '%s'\n" % instance.name)
964

    
965
    vif_data = []
966
    for idx, nic in enumerate(instance.nics):
967
      nic_str = "mac=%s" % (nic.mac)
968
      ip = getattr(nic, "ip", None)
969
      if ip is not None:
970
        nic_str += ", ip=%s" % ip
971
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
972
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
973
      if hvp[constants.HV_VIF_SCRIPT]:
974
        nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
975
      vif_data.append("'%s'" % nic_str)
976
      self._WriteNICInfoFile(instance, idx, nic)
977

    
978
    disk_data = \
979
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
980

    
981
    config.write("vif = [%s]\n" % ",".join(vif_data))
982
    config.write("disk = [%s]\n" % ",".join(disk_data))
983

    
984
    if hvp[constants.HV_ROOT_PATH]:
985
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
986
    config.write("on_poweroff = 'destroy'\n")
987
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
988
      config.write("on_reboot = 'restart'\n")
989
    else:
990
      config.write("on_reboot = 'destroy'\n")
991
    config.write("on_crash = 'restart'\n")
992
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
993

    
994
    return config.getvalue()
995

    
996

    
997
class XenHvmHypervisor(XenHypervisor):
998
  """Xen HVM hypervisor interface"""
999

    
1000
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
1001
    pathutils.VNC_PASSWORD_FILE,
1002
    ]
1003
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
1004
    pathutils.VNC_PASSWORD_FILE,
1005
    ]
1006

    
1007
  PARAMETERS = {
1008
    constants.HV_ACPI: hv_base.NO_CHECK,
1009
    constants.HV_BOOT_ORDER: (True, ) +
1010
      (lambda x: x and len(x.strip("acdn")) == 0,
1011
       "Invalid boot order specified, must be one or more of [acdn]",
1012
       None, None),
1013
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
1014
    constants.HV_DISK_TYPE:
1015
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
1016
    constants.HV_NIC_TYPE:
1017
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
1018
    constants.HV_PAE: hv_base.NO_CHECK,
1019
    constants.HV_VNC_BIND_ADDRESS:
1020
      (False, netutils.IP4Address.IsValid,
1021
       "VNC bind address is not a valid IP address", None, None),
1022
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
1023
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
1024
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
1025
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
1026
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
1027
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
1028
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
1029
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
1030
    # Add PCI passthrough
1031
    constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
1032
    constants.HV_REBOOT_BEHAVIOR:
1033
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
1034
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
1035
    constants.HV_CPU_CAP: hv_base.NO_CHECK,
1036
    constants.HV_CPU_WEIGHT:
1037
      (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
1038
    constants.HV_VIF_TYPE:
1039
      hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES),
1040
    constants.HV_VIRIDIAN: hv_base.NO_CHECK,
1041
    constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
1042
    }
1043

    
1044
  def _GetConfig(self, instance, startup_memory, block_devices):
1045
    """Create a Xen 3.1 HVM config file.
1046

1047
    """
1048
    hvp = instance.hvparams
1049

    
1050
    config = StringIO()
1051

    
1052
    # kernel handling
1053
    kpath = hvp[constants.HV_KERNEL_PATH]
1054
    config.write("kernel = '%s'\n" % kpath)
1055

    
1056
    config.write("builder = 'hvm'\n")
1057
    config.write("memory = %d\n" % startup_memory)
1058
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1059
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1060
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1061
    if cpu_pinning:
1062
      config.write("%s\n" % cpu_pinning)
1063
    cpu_cap = hvp[constants.HV_CPU_CAP]
1064
    if cpu_cap:
1065
      config.write("cpu_cap=%d\n" % cpu_cap)
1066
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1067
    if cpu_weight:
1068
      config.write("cpu_weight=%d\n" % cpu_weight)
1069

    
1070
    config.write("name = '%s'\n" % instance.name)
1071
    if hvp[constants.HV_PAE]:
1072
      config.write("pae = 1\n")
1073
    else:
1074
      config.write("pae = 0\n")
1075
    if hvp[constants.HV_ACPI]:
1076
      config.write("acpi = 1\n")
1077
    else:
1078
      config.write("acpi = 0\n")
1079
    if hvp[constants.HV_VIRIDIAN]:
1080
      config.write("viridian = 1\n")
1081
    else:
1082
      config.write("viridian = 0\n")
1083

    
1084
    config.write("apic = 1\n")
1085
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
1086
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
1087
    config.write("sdl = 0\n")
1088
    config.write("usb = 1\n")
1089
    config.write("usbdevice = 'tablet'\n")
1090
    config.write("vnc = 1\n")
1091
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
1092
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
1093
    else:
1094
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
1095

    
1096
    if instance.network_port > constants.VNC_BASE_PORT:
1097
      display = instance.network_port - constants.VNC_BASE_PORT
1098
      config.write("vncdisplay = %s\n" % display)
1099
      config.write("vncunused = 0\n")
1100
    else:
1101
      config.write("# vncdisplay = 1\n")
1102
      config.write("vncunused = 1\n")
1103

    
1104
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
1105
    try:
1106
      password = utils.ReadFile(vnc_pwd_file)
1107
    except EnvironmentError, err:
1108
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1109
                                   (vnc_pwd_file, err))
1110

    
1111
    config.write("vncpasswd = '%s'\n" % password.rstrip())
1112

    
1113
    config.write("serial = 'pty'\n")
1114
    if hvp[constants.HV_USE_LOCALTIME]:
1115
      config.write("localtime = 1\n")
1116

    
1117
    vif_data = []
1118
    # Note: what is called 'nic_type' here, is used as value for the xen nic
1119
    # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1120
    # the 'vif_type' to avoid a clash of notation.
1121
    nic_type = hvp[constants.HV_NIC_TYPE]
1122

    
1123
    if nic_type is None:
1124
      vif_type_str = ""
1125
      if hvp[constants.HV_VIF_TYPE]:
1126
        vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
1127
      # ensure old instances don't change
1128
      nic_type_str = vif_type_str
1129
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
1130
      nic_type_str = ", type=paravirtualized"
1131
    else:
1132
      # parameter 'model' is only valid with type 'ioemu'
1133
      nic_type_str = ", model=%s, type=%s" % \
1134
        (nic_type, constants.HT_HVM_VIF_IOEMU)
1135
    for idx, nic in enumerate(instance.nics):
1136
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1137
      ip = getattr(nic, "ip", None)
1138
      if ip is not None:
1139
        nic_str += ", ip=%s" % ip
1140
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1141
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1142
      if hvp[constants.HV_VIF_SCRIPT]:
1143
        nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1144
      vif_data.append("'%s'" % nic_str)
1145
      self._WriteNICInfoFile(instance, idx, nic)
1146

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

    
1149
    disk_data = \
1150
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1151

    
1152
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1153
    if iso_path:
1154
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
1155
      disk_data.append(iso)
1156

    
1157
    config.write("disk = [%s]\n" % (",".join(disk_data)))
1158
    # Add PCI passthrough
1159
    pci_pass_arr = []
1160
    pci_pass = hvp[constants.HV_PASSTHROUGH]
1161
    if pci_pass:
1162
      pci_pass_arr = pci_pass.split(";")
1163
      config.write("pci = %s\n" % pci_pass_arr)
1164
    config.write("on_poweroff = 'destroy'\n")
1165
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1166
      config.write("on_reboot = 'restart'\n")
1167
    else:
1168
      config.write("on_reboot = 'destroy'\n")
1169
    config.write("on_crash = 'restart'\n")
1170

    
1171
    return config.getvalue()