Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 865cdc2e

History | View | Annotate | Download (37.3 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

    
902
class XenPvmHypervisor(XenHypervisor):
903
  """Xen PVM hypervisor interface"""
904

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

    
926
  def _GetConfig(self, instance, startup_memory, block_devices):
927
    """Write the Xen config file for the instance.
928

929
    """
930
    hvp = instance.hvparams
931
    config = StringIO()
932
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
933

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

    
945
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
946
      if bootloader_args:
947
        config.write("bootargs = '%s'\n" % bootloader_args)
948
    else:
949
      # kernel handling
950
      kpath = hvp[constants.HV_KERNEL_PATH]
951
      config.write("kernel = '%s'\n" % kpath)
952

    
953
      # initrd handling
954
      initrd_path = hvp[constants.HV_INITRD_PATH]
955
      if initrd_path:
956
        config.write("ramdisk = '%s'\n" % initrd_path)
957

    
958
    # rest of the settings
959
    config.write("memory = %d\n" % startup_memory)
960
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
961
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
962
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
963
    if cpu_pinning:
964
      config.write("%s\n" % cpu_pinning)
965
    cpu_cap = hvp[constants.HV_CPU_CAP]
966
    if cpu_cap:
967
      config.write("cpu_cap=%d\n" % cpu_cap)
968
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
969
    if cpu_weight:
970
      config.write("cpu_weight=%d\n" % cpu_weight)
971

    
972
    config.write("name = '%s'\n" % instance.name)
973

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

    
987
    disk_data = \
988
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
989

    
990
    config.write("vif = [%s]\n" % ",".join(vif_data))
991
    config.write("disk = [%s]\n" % ",".join(disk_data))
992

    
993
    if hvp[constants.HV_ROOT_PATH]:
994
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
995
    config.write("on_poweroff = 'destroy'\n")
996
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
997
      config.write("on_reboot = 'restart'\n")
998
    else:
999
      config.write("on_reboot = 'destroy'\n")
1000
    config.write("on_crash = 'restart'\n")
1001
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
1002

    
1003
    return config.getvalue()
1004

    
1005

    
1006
class XenHvmHypervisor(XenHypervisor):
1007
  """Xen HVM hypervisor interface"""
1008

    
1009
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
1010
    pathutils.VNC_PASSWORD_FILE,
1011
    ]
1012
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
1013
    pathutils.VNC_PASSWORD_FILE,
1014
    ]
1015

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

    
1053
  def _GetConfig(self, instance, startup_memory, block_devices):
1054
    """Create a Xen 3.1 HVM config file.
1055

1056
    """
1057
    hvp = instance.hvparams
1058

    
1059
    config = StringIO()
1060

    
1061
    # kernel handling
1062
    kpath = hvp[constants.HV_KERNEL_PATH]
1063
    config.write("kernel = '%s'\n" % kpath)
1064

    
1065
    config.write("builder = 'hvm'\n")
1066
    config.write("memory = %d\n" % startup_memory)
1067
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1068
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1069
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1070
    if cpu_pinning:
1071
      config.write("%s\n" % cpu_pinning)
1072
    cpu_cap = hvp[constants.HV_CPU_CAP]
1073
    if cpu_cap:
1074
      config.write("cpu_cap=%d\n" % cpu_cap)
1075
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1076
    if cpu_weight:
1077
      config.write("cpu_weight=%d\n" % cpu_weight)
1078

    
1079
    config.write("name = '%s'\n" % instance.name)
1080
    if hvp[constants.HV_PAE]:
1081
      config.write("pae = 1\n")
1082
    else:
1083
      config.write("pae = 0\n")
1084
    if hvp[constants.HV_ACPI]:
1085
      config.write("acpi = 1\n")
1086
    else:
1087
      config.write("acpi = 0\n")
1088
    if hvp[constants.HV_VIRIDIAN]:
1089
      config.write("viridian = 1\n")
1090
    else:
1091
      config.write("viridian = 0\n")
1092

    
1093
    config.write("apic = 1\n")
1094
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
1095
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
1096
    config.write("sdl = 0\n")
1097
    config.write("usb = 1\n")
1098
    config.write("usbdevice = 'tablet'\n")
1099
    config.write("vnc = 1\n")
1100
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
1101
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
1102
    else:
1103
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
1104

    
1105
    if instance.network_port > constants.VNC_BASE_PORT:
1106
      display = instance.network_port - constants.VNC_BASE_PORT
1107
      config.write("vncdisplay = %s\n" % display)
1108
      config.write("vncunused = 0\n")
1109
    else:
1110
      config.write("# vncdisplay = 1\n")
1111
      config.write("vncunused = 1\n")
1112

    
1113
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
1114
    try:
1115
      password = utils.ReadFile(vnc_pwd_file)
1116
    except EnvironmentError, err:
1117
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1118
                                   (vnc_pwd_file, err))
1119

    
1120
    config.write("vncpasswd = '%s'\n" % password.rstrip())
1121

    
1122
    config.write("serial = 'pty'\n")
1123
    if hvp[constants.HV_USE_LOCALTIME]:
1124
      config.write("localtime = 1\n")
1125

    
1126
    vif_data = []
1127
    # Note: what is called 'nic_type' here, is used as value for the xen nic
1128
    # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1129
    # the 'vif_type' to avoid a clash of notation.
1130
    nic_type = hvp[constants.HV_NIC_TYPE]
1131

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

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

    
1158
    disk_data = \
1159
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1160

    
1161
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1162
    if iso_path:
1163
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
1164
      disk_data.append(iso)
1165

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

    
1180
    return config.getvalue()