Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 860bf930

History | View | Annotate | Download (37.5 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_name, 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
    dirs = [(dname, constants.RUN_DIRS_MODE)
398
            for dname in cls._DIRS + [cls._InstanceNICDir(instance_name)]]
399
    utils.EnsureDirs(dirs)
400

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

    
404
    if nic.netinfo:
405
      netinfo = objects.Network.FromDict(nic.netinfo)
406
      data.write("NETWORK_NAME=%s\n" % netinfo.name)
407
      if netinfo.network:
408
        data.write("NETWORK_SUBNET=%s\n" % netinfo.network)
409
      if netinfo.gateway:
410
        data.write("NETWORK_GATEWAY=%s\n" % netinfo.gateway)
411
      if netinfo.network6:
412
        data.write("NETWORK_SUBNET6=%s\n" % netinfo.network6)
413
      if netinfo.gateway6:
414
        data.write("NETWORK_GATEWAY6=%s\n" % netinfo.gateway6)
415
      if netinfo.mac_prefix:
416
        data.write("NETWORK_MAC_PREFIX=%s\n" % netinfo.mac_prefix)
417
      if netinfo.tags:
418
        data.write("NETWORK_TAGS=%s\n" % "\ ".join(netinfo.tags))
419

    
420
    data.write("MAC=%s\n" % nic.mac)
421
    data.write("IP=%s\n" % nic.ip)
422
    data.write("MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
423
    data.write("LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
424

    
425
    try:
426
      utils.WriteFile(cfg_file, data=data.getvalue())
427
    except EnvironmentError, err:
428
      raise errors.HypervisorError("Cannot write Xen instance configuration"
429
                                   " file %s: %s" % (cfg_file, err))
430

    
431
  @classmethod
432
  def _InstanceNICDir(cls, instance_name):
433
    """Returns the directory holding the tap device files for a given instance.
434

435
    """
436
    return utils.PathJoin(cls._NICS_DIR, instance_name)
437

    
438
  @classmethod
439
  def _InstanceNICFile(cls, instance_name, seq):
440
    """Returns the name of the file containing the tap device for a given NIC
441

442
    """
443
    return utils.PathJoin(cls._InstanceNICDir(instance_name), str(seq))
444

    
445
  @classmethod
446
  def _GetConfig(cls, instance, startup_memory, block_devices):
447
    """Build Xen configuration for an instance.
448

449
    """
450
    raise NotImplementedError
451

    
452
  def _WriteConfigFile(self, instance_name, data):
453
    """Write the Xen config file for the instance.
454

455
    This version of the function just writes the config file from static data.
456

457
    """
458
    # just in case it exists
459
    utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
460

    
461
    cfg_file = self._ConfigFileName(instance_name)
462
    try:
463
      utils.WriteFile(cfg_file, data=data)
464
    except EnvironmentError, err:
465
      raise errors.HypervisorError("Cannot write Xen instance configuration"
466
                                   " file %s: %s" % (cfg_file, err))
467

    
468
  def _ReadConfigFile(self, instance_name):
469
    """Returns the contents of the instance config file.
470

471
    """
472
    filename = self._ConfigFileName(instance_name)
473

    
474
    try:
475
      file_content = utils.ReadFile(filename)
476
    except EnvironmentError, err:
477
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
478

    
479
    return file_content
480

    
481
  def _RemoveConfigFile(self, instance_name):
482
    """Remove the xen configuration file.
483

484
    """
485
    utils.RemoveFile(self._ConfigFileName(instance_name))
486
    try:
487
      shutil.rmtree(self._InstanceNICDir(instance_name))
488
    except OSError, err:
489
      if err.errno != errno.ENOENT:
490
        raise
491

    
492
  def _StashConfigFile(self, instance_name):
493
    """Move the Xen config file to the log directory and return its new path.
494

495
    """
496
    old_filename = self._ConfigFileName(instance_name)
497
    base = ("%s-%s" %
498
            (instance_name, utils.TimestampForFilename()))
499
    new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base)
500
    utils.RenameFile(old_filename, new_filename)
501
    return new_filename
502

    
503
  def _GetXmList(self, include_node):
504
    """Wrapper around module level L{_GetXmList}.
505

506
    """
507
    return _GetXmList(lambda: self._RunXen(["list"]), include_node)
508

    
509
  def ListInstances(self):
510
    """Get the list of running instances.
511

512
    """
513
    xm_list = self._GetXmList(False)
514
    names = [info[0] for info in xm_list]
515
    return names
516

    
517
  def GetInstanceInfo(self, instance_name):
518
    """Get instance properties.
519

520
    @param instance_name: the instance name
521

522
    @return: tuple (name, id, memory, vcpus, stat, times)
523

524
    """
525
    xm_list = self._GetXmList(instance_name == _DOM0_NAME)
526
    result = None
527
    for data in xm_list:
528
      if data[0] == instance_name:
529
        result = data
530
        break
531
    return result
532

    
533
  def GetAllInstancesInfo(self):
534
    """Get properties of all instances.
535

536
    @return: list of tuples (name, id, memory, vcpus, stat, times)
537

538
    """
539
    xm_list = self._GetXmList(False)
540
    return xm_list
541

    
542
  def _MakeConfigFile(self, instance, startup_memory, block_devices):
543
    """Gather configuration details and write to disk.
544

545
    See L{_GetConfig} for arguments.
546

547
    """
548
    buf = StringIO()
549
    buf.write("# Automatically generated by Ganeti. Do not edit!\n")
550
    buf.write("\n")
551
    buf.write(self._GetConfig(instance, startup_memory, block_devices))
552
    buf.write("\n")
553

    
554
    self._WriteConfigFile(instance.name, buf.getvalue())
555

    
556
  def StartInstance(self, instance, block_devices, startup_paused):
557
    """Start an instance.
558

559
    """
560
    startup_memory = self._InstanceStartupMemory(instance)
561

    
562
    self._MakeConfigFile(instance, startup_memory, block_devices)
563

    
564
    cmd = ["create"]
565
    if startup_paused:
566
      cmd.append("-p")
567
    cmd.append(self._ConfigFileName(instance.name))
568

    
569
    result = self._RunXen(cmd)
570
    if result.failed:
571
      # Move the Xen configuration file to the log directory to avoid
572
      # leaving a stale config file behind.
573
      stashed_config = self._StashConfigFile(instance.name)
574
      raise errors.HypervisorError("Failed to start instance %s: %s (%s). Moved"
575
                                   " config file to %s" %
576
                                   (instance.name, result.fail_reason,
577
                                    result.output, stashed_config))
578

    
579
  def StopInstance(self, instance, force=False, retry=False, name=None):
580
    """Stop an instance.
581

582
    """
583
    if name is None:
584
      name = instance.name
585

    
586
    return self._StopInstance(name, force)
587

    
588
  def _ShutdownInstance(self, name):
589
    """Shutdown an instance if the instance is running.
590

591
    @type name: string
592
    @param name: name of the instance to stop
593

594
    The '-w' flag waits for shutdown to complete which avoids the need
595
    to poll in the case where we want to destroy the domain
596
    immediately after shutdown.
597

598
    """
599
    instance_info = self.GetInstanceInfo(name)
600

    
601
    if instance_info is None or _IsInstanceShutdown(instance_info[4]):
602
      logging.info("Failed to shutdown instance %s, not running", name)
603
      return None
604

    
605
    return self._RunXen(["shutdown", "-w", name])
606

    
607
  def _DestroyInstance(self, name):
608
    """Destroy an instance if the instance if the instance exists.
609

610
    @type name: string
611
    @param name: name of the instance to destroy
612

613
    """
614
    instance_info = self.GetInstanceInfo(name)
615

    
616
    if instance_info is None:
617
      logging.info("Failed to destroy instance %s, does not exist", name)
618
      return None
619

    
620
    return self._RunXen(["destroy", name])
621

    
622
  def _StopInstance(self, name, force):
623
    """Stop an instance.
624

625
    @type name: string
626
    @param name: name of the instance to destroy
627

628
    @type force: boolean
629
    @param force: whether to do a "hard" stop (destroy)
630

631
    """
632
    if force:
633
      result = self._DestroyInstance(name)
634
    else:
635
      self._ShutdownInstance(name)
636
      result = self._DestroyInstance(name)
637

    
638
    if result is not None and result.failed and \
639
          self.GetInstanceInfo(name) is not None:
640
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
641
                                   (name, result.fail_reason, result.output))
642

    
643
    # Remove configuration file if stopping/starting instance was successful
644
    self._RemoveConfigFile(name)
645

    
646
  def RebootInstance(self, instance):
647
    """Reboot an instance.
648

649
    """
650
    ini_info = self.GetInstanceInfo(instance.name)
651

    
652
    if ini_info is None:
653
      raise errors.HypervisorError("Failed to reboot instance %s,"
654
                                   " not running" % instance.name)
655

    
656
    result = self._RunXen(["reboot", instance.name])
657
    if result.failed:
658
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
659
                                   (instance.name, result.fail_reason,
660
                                    result.output))
661

    
662
    def _CheckInstance():
663
      new_info = self.GetInstanceInfo(instance.name)
664

    
665
      # check if the domain ID has changed or the run time has decreased
666
      if (new_info is not None and
667
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
668
        return
669

    
670
      raise utils.RetryAgain()
671

    
672
    try:
673
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
674
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
675
    except utils.RetryTimeout:
676
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
677
                                   " did not reboot in the expected interval" %
678
                                   (instance.name, ))
679

    
680
  def BalloonInstanceMemory(self, instance, mem):
681
    """Balloon an instance memory to a certain value.
682

683
    @type instance: L{objects.Instance}
684
    @param instance: instance to be accepted
685
    @type mem: int
686
    @param mem: actual memory size to use for instance runtime
687

688
    """
689
    result = self._RunXen(["mem-set", instance.name, mem])
690
    if result.failed:
691
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
692
                                   (instance.name, result.fail_reason,
693
                                    result.output))
694

    
695
    # Update configuration file
696
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
697
    cmd.append(self._ConfigFileName(instance.name))
698

    
699
    result = utils.RunCmd(cmd)
700
    if result.failed:
701
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
702
                                   (instance.name, result.fail_reason,
703
                                    result.output))
704

    
705
  def GetNodeInfo(self):
706
    """Return information about the node.
707

708
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
709

710
    """
711
    result = self._RunXen(["info"])
712
    if result.failed:
713
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
714
                    result.output)
715
      return None
716

    
717
    return _GetNodeInfo(result.stdout, self._GetXmList)
718

    
719
  @classmethod
720
  def GetInstanceConsole(cls, instance, hvparams, beparams):
721
    """Return a command for connecting to the console of an instance.
722

723
    """
724
    return objects.InstanceConsole(instance=instance.name,
725
                                   kind=constants.CONS_SSH,
726
                                   host=instance.primary_node,
727
                                   user=constants.SSH_CONSOLE_USER,
728
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
729
                                            constants.XEN_CMD, instance.name])
730

    
731
  def Verify(self):
732
    """Verify the hypervisor.
733

734
    For Xen, this verifies that the xend process is running.
735

736
    @return: Problem description if something is wrong, C{None} otherwise
737

738
    """
739
    result = self._RunXen(["info"])
740
    if result.failed:
741
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
742

    
743
    return None
744

    
745
  def MigrationInfo(self, instance):
746
    """Get instance information to perform a migration.
747

748
    @type instance: L{objects.Instance}
749
    @param instance: instance to be migrated
750
    @rtype: string
751
    @return: content of the xen config file
752

753
    """
754
    return self._ReadConfigFile(instance.name)
755

    
756
  def AcceptInstance(self, instance, info, target):
757
    """Prepare to accept an instance.
758

759
    @type instance: L{objects.Instance}
760
    @param instance: instance to be accepted
761
    @type info: string
762
    @param info: content of the xen config file on the source node
763
    @type target: string
764
    @param target: target host (usually ip), on this node
765

766
    """
767
    pass
768

    
769
  def FinalizeMigrationDst(self, instance, info, success):
770
    """Finalize an instance migration.
771

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

775
    @type instance: L{objects.Instance}
776
    @param instance: instance whose migration is being finalized
777
    @type info: string
778
    @param info: content of the xen config file on the source node
779
    @type success: boolean
780
    @param success: whether the migration was a success or a failure
781

782
    """
783
    if success:
784
      self._WriteConfigFile(instance.name, info)
785

    
786
  def MigrateInstance(self, instance, target, live):
787
    """Migrate an instance to a target node.
788

789
    The migration will not be attempted if the instance is not
790
    currently running.
791

792
    @type instance: L{objects.Instance}
793
    @param instance: the instance to be migrated
794
    @type target: string
795
    @param target: ip address of the target node
796
    @type live: boolean
797
    @param live: perform a live migration
798

799
    """
800
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
801

    
802
    # TODO: Pass cluster name via RPC
803
    cluster_name = ssconf.SimpleStore().GetClusterName()
804

    
805
    return self._MigrateInstance(cluster_name, instance.name, target, port,
806
                                 live)
807

    
808
  def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
809
                       _ping_fn=netutils.TcpPing):
810
    """Migrate an instance to a target node.
811

812
    @see: L{MigrateInstance} for details
813

814
    """
815
    if self.GetInstanceInfo(instance_name) is None:
816
      raise errors.HypervisorError("Instance not running, cannot migrate")
817

    
818
    cmd = self._GetCommand()
819

    
820
    if (cmd == constants.XEN_CMD_XM and
821
        not _ping_fn(target, port, live_port_needed=True)):
822
      raise errors.HypervisorError("Remote host %s not listening on port"
823
                                   " %s, cannot migrate" % (target, port))
824

    
825
    args = ["migrate"]
826

    
827
    if cmd == constants.XEN_CMD_XM:
828
      args.extend(["-p", "%d" % port])
829
      if live:
830
        args.append("-l")
831

    
832
    elif cmd == constants.XEN_CMD_XL:
833
      args.extend([
834
        "-s", constants.XL_SSH_CMD % cluster_name,
835
        "-C", self._ConfigFileName(instance_name),
836
        ])
837

    
838
    else:
839
      raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
840

    
841
    args.extend([instance_name, target])
842

    
843
    result = self._RunXen(args)
844
    if result.failed:
845
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
846
                                   (instance_name, result.output))
847

    
848
  def FinalizeMigrationSource(self, instance, success, live):
849
    """Finalize the instance migration on the source node.
850

851
    @type instance: L{objects.Instance}
852
    @param instance: the instance that was migrated
853
    @type success: bool
854
    @param success: whether the migration succeeded or not
855
    @type live: bool
856
    @param live: whether the user requested a live migration or not
857

858
    """
859
    # pylint: disable=W0613
860
    if success:
861
      # remove old xen file after migration succeeded
862
      try:
863
        self._RemoveConfigFile(instance.name)
864
      except EnvironmentError:
865
        logging.exception("Failure while removing instance config file")
866

    
867
  def GetMigrationStatus(self, instance):
868
    """Get the migration status
869

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

874
    @type instance: L{objects.Instance}
875
    @param instance: the instance that is being migrated
876
    @rtype: L{objects.MigrationStatus}
877
    @return: the status of the current migration (one of
878
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
879
             progress info that can be retrieved from the hypervisor
880

881
    """
882
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
883

    
884
  @classmethod
885
  def PowercycleNode(cls):
886
    """Xen-specific powercycle.
887

888
    This first does a Linux reboot (which triggers automatically a Xen
889
    reboot), and if that fails it tries to do a Xen reboot. The reason
890
    we don't try a Xen reboot first is that the xen reboot launches an
891
    external command which connects to the Xen hypervisor, and that
892
    won't work in case the root filesystem is broken and/or the xend
893
    daemon is not working.
894

895
    """
896
    try:
897
      cls.LinuxPowercycle()
898
    finally:
899
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
900

    
901
  def HotplugSupported(self, instance, action, dev_type):
902
    """Whether hotplug is supported.
903

904
    """
905
    raise errors.HypervisorError("Hotplug not supported by the xen hypervisor")
906

    
907

    
908
class XenPvmHypervisor(XenHypervisor):
909
  """Xen PVM hypervisor interface"""
910

    
911
  PARAMETERS = {
912
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
913
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
914
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
915
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
916
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
917
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
918
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
919
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
920
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
921
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
922
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
923
    constants.HV_REBOOT_BEHAVIOR:
924
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
925
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
926
    constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
927
    constants.HV_CPU_WEIGHT:
928
      (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
929
    constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
930
    }
931

    
932
  def _GetConfig(self, instance, startup_memory, block_devices):
933
    """Write the Xen config file for the instance.
934

935
    """
936
    hvp = instance.hvparams
937
    config = StringIO()
938
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
939

    
940
    # if bootloader is True, use bootloader instead of kernel and ramdisk
941
    # parameters.
942
    if hvp[constants.HV_USE_BOOTLOADER]:
943
      # bootloader handling
944
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
945
      if bootloader_path:
946
        config.write("bootloader = '%s'\n" % bootloader_path)
947
      else:
948
        raise errors.HypervisorError("Bootloader enabled, but missing"
949
                                     " bootloader path")
950

    
951
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
952
      if bootloader_args:
953
        config.write("bootargs = '%s'\n" % bootloader_args)
954
    else:
955
      # kernel handling
956
      kpath = hvp[constants.HV_KERNEL_PATH]
957
      config.write("kernel = '%s'\n" % kpath)
958

    
959
      # initrd handling
960
      initrd_path = hvp[constants.HV_INITRD_PATH]
961
      if initrd_path:
962
        config.write("ramdisk = '%s'\n" % initrd_path)
963

    
964
    # rest of the settings
965
    config.write("memory = %d\n" % startup_memory)
966
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
967
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
968
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
969
    if cpu_pinning:
970
      config.write("%s\n" % cpu_pinning)
971
    cpu_cap = hvp[constants.HV_CPU_CAP]
972
    if cpu_cap:
973
      config.write("cpu_cap=%d\n" % cpu_cap)
974
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
975
    if cpu_weight:
976
      config.write("cpu_weight=%d\n" % cpu_weight)
977

    
978
    config.write("name = '%s'\n" % instance.name)
979

    
980
    vif_data = []
981
    for idx, nic in enumerate(instance.nics):
982
      nic_str = "mac=%s" % (nic.mac)
983
      ip = getattr(nic, "ip", None)
984
      if ip is not None:
985
        nic_str += ", ip=%s" % ip
986
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
987
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
988
      if hvp[constants.HV_VIF_SCRIPT]:
989
        nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
990
      vif_data.append("'%s'" % nic_str)
991
      self._WriteNICInfoFile(instance.name, idx, nic)
992

    
993
    disk_data = \
994
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
995

    
996
    config.write("vif = [%s]\n" % ",".join(vif_data))
997
    config.write("disk = [%s]\n" % ",".join(disk_data))
998

    
999
    if hvp[constants.HV_ROOT_PATH]:
1000
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
1001
    config.write("on_poweroff = 'destroy'\n")
1002
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1003
      config.write("on_reboot = 'restart'\n")
1004
    else:
1005
      config.write("on_reboot = 'destroy'\n")
1006
    config.write("on_crash = 'restart'\n")
1007
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
1008

    
1009
    return config.getvalue()
1010

    
1011

    
1012
class XenHvmHypervisor(XenHypervisor):
1013
  """Xen HVM hypervisor interface"""
1014

    
1015
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
1016
    pathutils.VNC_PASSWORD_FILE,
1017
    ]
1018
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
1019
    pathutils.VNC_PASSWORD_FILE,
1020
    ]
1021

    
1022
  PARAMETERS = {
1023
    constants.HV_ACPI: hv_base.NO_CHECK,
1024
    constants.HV_BOOT_ORDER: (True, ) +
1025
      (lambda x: x and len(x.strip("acdn")) == 0,
1026
       "Invalid boot order specified, must be one or more of [acdn]",
1027
       None, None),
1028
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
1029
    constants.HV_DISK_TYPE:
1030
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
1031
    constants.HV_NIC_TYPE:
1032
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
1033
    constants.HV_PAE: hv_base.NO_CHECK,
1034
    constants.HV_VNC_BIND_ADDRESS:
1035
      (False, netutils.IP4Address.IsValid,
1036
       "VNC bind address is not a valid IP address", None, None),
1037
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
1038
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
1039
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
1040
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
1041
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
1042
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
1043
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
1044
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
1045
    # Add PCI passthrough
1046
    constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
1047
    constants.HV_REBOOT_BEHAVIOR:
1048
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
1049
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
1050
    constants.HV_CPU_CAP: hv_base.NO_CHECK,
1051
    constants.HV_CPU_WEIGHT:
1052
      (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
1053
    constants.HV_VIF_TYPE:
1054
      hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES),
1055
    constants.HV_VIRIDIAN: hv_base.NO_CHECK,
1056
    constants.HV_VIF_SCRIPT: hv_base.OPT_FILE_CHECK,
1057
    }
1058

    
1059
  def _GetConfig(self, instance, startup_memory, block_devices):
1060
    """Create a Xen 3.1 HVM config file.
1061

1062
    """
1063
    hvp = instance.hvparams
1064

    
1065
    config = StringIO()
1066

    
1067
    # kernel handling
1068
    kpath = hvp[constants.HV_KERNEL_PATH]
1069
    config.write("kernel = '%s'\n" % kpath)
1070

    
1071
    config.write("builder = 'hvm'\n")
1072
    config.write("memory = %d\n" % startup_memory)
1073
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1074
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1075
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1076
    if cpu_pinning:
1077
      config.write("%s\n" % cpu_pinning)
1078
    cpu_cap = hvp[constants.HV_CPU_CAP]
1079
    if cpu_cap:
1080
      config.write("cpu_cap=%d\n" % cpu_cap)
1081
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1082
    if cpu_weight:
1083
      config.write("cpu_weight=%d\n" % cpu_weight)
1084

    
1085
    config.write("name = '%s'\n" % instance.name)
1086
    if hvp[constants.HV_PAE]:
1087
      config.write("pae = 1\n")
1088
    else:
1089
      config.write("pae = 0\n")
1090
    if hvp[constants.HV_ACPI]:
1091
      config.write("acpi = 1\n")
1092
    else:
1093
      config.write("acpi = 0\n")
1094
    if hvp[constants.HV_VIRIDIAN]:
1095
      config.write("viridian = 1\n")
1096
    else:
1097
      config.write("viridian = 0\n")
1098

    
1099
    config.write("apic = 1\n")
1100
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
1101
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
1102
    config.write("sdl = 0\n")
1103
    config.write("usb = 1\n")
1104
    config.write("usbdevice = 'tablet'\n")
1105
    config.write("vnc = 1\n")
1106
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
1107
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
1108
    else:
1109
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
1110

    
1111
    if instance.network_port > constants.VNC_BASE_PORT:
1112
      display = instance.network_port - constants.VNC_BASE_PORT
1113
      config.write("vncdisplay = %s\n" % display)
1114
      config.write("vncunused = 0\n")
1115
    else:
1116
      config.write("# vncdisplay = 1\n")
1117
      config.write("vncunused = 1\n")
1118

    
1119
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
1120
    try:
1121
      password = utils.ReadFile(vnc_pwd_file)
1122
    except EnvironmentError, err:
1123
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1124
                                   (vnc_pwd_file, err))
1125

    
1126
    config.write("vncpasswd = '%s'\n" % password.rstrip())
1127

    
1128
    config.write("serial = 'pty'\n")
1129
    if hvp[constants.HV_USE_LOCALTIME]:
1130
      config.write("localtime = 1\n")
1131

    
1132
    vif_data = []
1133
    # Note: what is called 'nic_type' here, is used as value for the xen nic
1134
    # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1135
    # the 'vif_type' to avoid a clash of notation.
1136
    nic_type = hvp[constants.HV_NIC_TYPE]
1137

    
1138
    if nic_type is None:
1139
      vif_type_str = ""
1140
      if hvp[constants.HV_VIF_TYPE]:
1141
        vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
1142
      # ensure old instances don't change
1143
      nic_type_str = vif_type_str
1144
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
1145
      nic_type_str = ", type=paravirtualized"
1146
    else:
1147
      # parameter 'model' is only valid with type 'ioemu'
1148
      nic_type_str = ", model=%s, type=%s" % \
1149
        (nic_type, constants.HT_HVM_VIF_IOEMU)
1150
    for idx, nic in enumerate(instance.nics):
1151
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1152
      ip = getattr(nic, "ip", None)
1153
      if ip is not None:
1154
        nic_str += ", ip=%s" % ip
1155
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1156
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1157
      if hvp[constants.HV_VIF_SCRIPT]:
1158
        nic_str += ", script=%s" % hvp[constants.HV_VIF_SCRIPT]
1159
      vif_data.append("'%s'" % nic_str)
1160
      self._WriteNICInfoFile(instance.name, idx, nic)
1161

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

    
1164
    disk_data = \
1165
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1166

    
1167
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1168
    if iso_path:
1169
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
1170
      disk_data.append(iso)
1171

    
1172
    config.write("disk = [%s]\n" % (",".join(disk_data)))
1173
    # Add PCI passthrough
1174
    pci_pass_arr = []
1175
    pci_pass = hvp[constants.HV_PASSTHROUGH]
1176
    if pci_pass:
1177
      pci_pass_arr = pci_pass.split(";")
1178
      config.write("pci = %s\n" % pci_pass_arr)
1179
    config.write("on_poweroff = 'destroy'\n")
1180
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1181
      config.write("on_reboot = 'restart'\n")
1182
    else:
1183
      config.write("on_reboot = 'destroy'\n")
1184
    config.write("on_crash = 'restart'\n")
1185

    
1186
    return config.getvalue()