Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ d6f07e0e

History | View | Annotate | Download (37.1 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
  }
53

    
54

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

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

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

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

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

    
86

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

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

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

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

    
107

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

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

118
  """
119
  result = []
120

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

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

    
143
  return result
144

    
145

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

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

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

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

    
165
    raise errors.HypervisorError(errmsg)
166

    
167
  return _ParseXmList(lines, include_node)
168

    
169

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

    
174

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

    
178

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

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

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

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

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

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

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

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

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

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

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

    
235
  return result
236

    
237

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

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

247
  """
248
  total_instmem = 0
249

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

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

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

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

    
265
  return info
266

    
267

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

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

    
274

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

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

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

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

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

    
294
  disk_data = []
295

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

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

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

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

    
311
  return disk_data
312

    
313

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

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

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

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

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

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

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

    
350
    self._cmd = _cmd
351

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

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

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

    
365
    return cmd
366

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

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

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

    
376
    return self._run_cmd_fn(cmd)
377

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

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

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

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

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

395
    """
396
    instance_name = instance.name
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
    data.write("TAGS=%s\n" % "\ ".join(instance.GetTags()))
405
    if nic.netinfo:
406
      netinfo = objects.Network.FromDict(nic.netinfo)
407
      for k, v in netinfo.HooksDict().iteritems():
408
        data.write("%s=%s\n" % (k, v))
409

    
410
    data.write("MAC=%s\n" % nic.mac)
411
    if nic.ip:
412
      data.write("IP=%s\n" % nic.ip)
413
    data.write("INTERFACE_INDEX=%s\n" % str(idx))
414
    if nic.name:
415
      data.write("INTERFACE_NAME=%s\n" % nic.name)
416
    data.write("INTERFACE_UUID=%s\n" % nic.uuid)
417
    data.write("MODE=%s\n" % nic.nicparams[constants.NIC_MODE])
418
    data.write("LINK=%s\n" % nic.nicparams[constants.NIC_LINK])
419

    
420
    try:
421
      utils.WriteFile(cfg_file, data=data.getvalue())
422
    except EnvironmentError, err:
423
      raise errors.HypervisorError("Cannot write Xen instance configuration"
424
                                   " file %s: %s" % (cfg_file, err))
425

    
426
  @classmethod
427
  def _InstanceNICDir(cls, instance_name):
428
    """Returns the directory holding the tap device files for a given instance.
429

430
    """
431
    return utils.PathJoin(cls._NICS_DIR, instance_name)
432

    
433
  @classmethod
434
  def _InstanceNICFile(cls, instance_name, seq):
435
    """Returns the name of the file containing the tap device for a given NIC
436

437
    """
438
    return utils.PathJoin(cls._InstanceNICDir(instance_name), str(seq))
439

    
440
  @classmethod
441
  def _GetConfig(cls, instance, startup_memory, block_devices):
442
    """Build Xen configuration for an instance.
443

444
    """
445
    raise NotImplementedError
446

    
447
  def _WriteConfigFile(self, instance_name, data):
448
    """Write the Xen config file for the instance.
449

450
    This version of the function just writes the config file from static data.
451

452
    """
453
    # just in case it exists
454
    utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
455

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

    
463
  def _ReadConfigFile(self, instance_name):
464
    """Returns the contents of the instance config file.
465

466
    """
467
    filename = self._ConfigFileName(instance_name)
468

    
469
    try:
470
      file_content = utils.ReadFile(filename)
471
    except EnvironmentError, err:
472
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
473

    
474
    return file_content
475

    
476
  def _RemoveConfigFile(self, instance_name):
477
    """Remove the xen configuration file.
478

479
    """
480
    utils.RemoveFile(self._ConfigFileName(instance_name))
481
    try:
482
      shutil.rmtree(self._InstanceNICDir(instance_name))
483
    except OSError, err:
484
      if err.errno != errno.ENOENT:
485
        raise
486

    
487
  def _StashConfigFile(self, instance_name):
488
    """Move the Xen config file to the log directory and return its new path.
489

490
    """
491
    old_filename = self._ConfigFileName(instance_name)
492
    base = ("%s-%s" %
493
            (instance_name, utils.TimestampForFilename()))
494
    new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base)
495
    utils.RenameFile(old_filename, new_filename)
496
    return new_filename
497

    
498
  def _GetXmList(self, include_node):
499
    """Wrapper around module level L{_GetXmList}.
500

501
    """
502
    return _GetXmList(lambda: self._RunXen(["list"]), include_node)
503

    
504
  def ListInstances(self):
505
    """Get the list of running instances.
506

507
    """
508
    xm_list = self._GetXmList(False)
509
    names = [info[0] for info in xm_list]
510
    return names
511

    
512
  def GetInstanceInfo(self, instance_name):
513
    """Get instance properties.
514

515
    @param instance_name: the instance name
516

517
    @return: tuple (name, id, memory, vcpus, stat, times)
518

519
    """
520
    xm_list = self._GetXmList(instance_name == _DOM0_NAME)
521
    result = None
522
    for data in xm_list:
523
      if data[0] == instance_name:
524
        result = data
525
        break
526
    return result
527

    
528
  def GetAllInstancesInfo(self):
529
    """Get properties of all instances.
530

531
    @return: list of tuples (name, id, memory, vcpus, stat, times)
532

533
    """
534
    xm_list = self._GetXmList(False)
535
    return xm_list
536

    
537
  def _MakeConfigFile(self, instance, startup_memory, block_devices):
538
    """Gather configuration details and write to disk.
539

540
    See L{_GetConfig} for arguments.
541

542
    """
543
    buf = StringIO()
544
    buf.write("# Automatically generated by Ganeti. Do not edit!\n")
545
    buf.write("\n")
546
    buf.write(self._GetConfig(instance, startup_memory, block_devices))
547
    buf.write("\n")
548

    
549
    self._WriteConfigFile(instance.name, buf.getvalue())
550

    
551
  def StartInstance(self, instance, block_devices, startup_paused):
552
    """Start an instance.
553

554
    """
555
    startup_memory = self._InstanceStartupMemory(instance)
556

    
557
    self._MakeConfigFile(instance, startup_memory, block_devices)
558

    
559
    cmd = ["create"]
560
    if startup_paused:
561
      cmd.append("-p")
562
    cmd.append(self._ConfigFileName(instance.name))
563

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

    
574
  def StopInstance(self, instance, force=False, retry=False, name=None):
575
    """Stop an instance.
576

577
    """
578
    if name is None:
579
      name = instance.name
580

    
581
    return self._StopInstance(name, force)
582

    
583
  def _ShutdownInstance(self, name):
584
    """Shutdown an instance if the instance is running.
585

586
    @type name: string
587
    @param name: name of the instance to stop
588

589
    The '-w' flag waits for shutdown to complete which avoids the need
590
    to poll in the case where we want to destroy the domain
591
    immediately after shutdown.
592

593
    """
594
    instance_info = self.GetInstanceInfo(name)
595

    
596
    if instance_info is None or _IsInstanceShutdown(instance_info[4]):
597
      logging.info("Failed to shutdown instance %s, not running", name)
598
      return None
599

    
600
    return self._RunXen(["shutdown", "-w", name])
601

    
602
  def _DestroyInstance(self, name):
603
    """Destroy an instance if the instance if the instance exists.
604

605
    @type name: string
606
    @param name: name of the instance to destroy
607

608
    """
609
    instance_info = self.GetInstanceInfo(name)
610

    
611
    if instance_info is None:
612
      logging.info("Failed to destroy instance %s, does not exist", name)
613
      return None
614

    
615
    return self._RunXen(["destroy", name])
616

    
617
  def _StopInstance(self, name, force):
618
    """Stop an instance.
619

620
    @type name: string
621
    @param name: name of the instance to destroy
622

623
    @type force: boolean
624
    @param force: whether to do a "hard" stop (destroy)
625

626
    """
627
    if force:
628
      result = self._DestroyInstance(name)
629
    else:
630
      self._ShutdownInstance(name)
631
      result = self._DestroyInstance(name)
632

    
633
    if result is not None and result.failed and \
634
          self.GetInstanceInfo(name) is not None:
635
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
636
                                   (name, result.fail_reason, result.output))
637

    
638
    # Remove configuration file if stopping/starting instance was successful
639
    self._RemoveConfigFile(name)
640

    
641
  def RebootInstance(self, instance):
642
    """Reboot an instance.
643

644
    """
645
    ini_info = self.GetInstanceInfo(instance.name)
646

    
647
    if ini_info is None:
648
      raise errors.HypervisorError("Failed to reboot instance %s,"
649
                                   " not running" % instance.name)
650

    
651
    result = self._RunXen(["reboot", instance.name])
652
    if result.failed:
653
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
654
                                   (instance.name, result.fail_reason,
655
                                    result.output))
656

    
657
    def _CheckInstance():
658
      new_info = self.GetInstanceInfo(instance.name)
659

    
660
      # check if the domain ID has changed or the run time has decreased
661
      if (new_info is not None and
662
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
663
        return
664

    
665
      raise utils.RetryAgain()
666

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

    
675
  def BalloonInstanceMemory(self, instance, mem):
676
    """Balloon an instance memory to a certain value.
677

678
    @type instance: L{objects.Instance}
679
    @param instance: instance to be accepted
680
    @type mem: int
681
    @param mem: actual memory size to use for instance runtime
682

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

    
690
    # Update configuration file
691
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
692
    cmd.append(self._ConfigFileName(instance.name))
693

    
694
    result = utils.RunCmd(cmd)
695
    if result.failed:
696
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
697
                                   (instance.name, result.fail_reason,
698
                                    result.output))
699

    
700
  def GetNodeInfo(self):
701
    """Return information about the node.
702

703
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
704

705
    """
706
    result = self._RunXen(["info"])
707
    if result.failed:
708
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
709
                    result.output)
710
      return None
711

    
712
    return _GetNodeInfo(result.stdout, self._GetXmList)
713

    
714
  @classmethod
715
  def GetInstanceConsole(cls, instance, hvparams, beparams):
716
    """Return a command for connecting to the console of an instance.
717

718
    """
719
    return objects.InstanceConsole(instance=instance.name,
720
                                   kind=constants.CONS_SSH,
721
                                   host=instance.primary_node,
722
                                   user=constants.SSH_CONSOLE_USER,
723
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
724
                                            constants.XEN_CMD, instance.name])
725

    
726
  def Verify(self):
727
    """Verify the hypervisor.
728

729
    For Xen, this verifies that the xend process is running.
730

731
    @return: Problem description if something is wrong, C{None} otherwise
732

733
    """
734
    result = self._RunXen(["info"])
735
    if result.failed:
736
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
737

    
738
    return None
739

    
740
  def MigrationInfo(self, instance):
741
    """Get instance information to perform a migration.
742

743
    @type instance: L{objects.Instance}
744
    @param instance: instance to be migrated
745
    @rtype: string
746
    @return: content of the xen config file
747

748
    """
749
    return self._ReadConfigFile(instance.name)
750

    
751
  def AcceptInstance(self, instance, info, target):
752
    """Prepare to accept an instance.
753

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

761
    """
762
    pass
763

    
764
  def FinalizeMigrationDst(self, instance, info, success):
765
    """Finalize an instance migration.
766

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

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

777
    """
778
    if success:
779
      self._WriteConfigFile(instance.name, info)
780

    
781
  def MigrateInstance(self, instance, target, live):
782
    """Migrate an instance to a target node.
783

784
    The migration will not be attempted if the instance is not
785
    currently running.
786

787
    @type instance: L{objects.Instance}
788
    @param instance: the instance to be migrated
789
    @type target: string
790
    @param target: ip address of the target node
791
    @type live: boolean
792
    @param live: perform a live migration
793

794
    """
795
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
796

    
797
    # TODO: Pass cluster name via RPC
798
    cluster_name = ssconf.SimpleStore().GetClusterName()
799

    
800
    return self._MigrateInstance(cluster_name, instance.name, target, port,
801
                                 live)
802

    
803
  def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
804
                       _ping_fn=netutils.TcpPing):
805
    """Migrate an instance to a target node.
806

807
    @see: L{MigrateInstance} for details
808

809
    """
810
    if self.GetInstanceInfo(instance_name) is None:
811
      raise errors.HypervisorError("Instance not running, cannot migrate")
812

    
813
    cmd = self._GetCommand()
814

    
815
    if (cmd == constants.XEN_CMD_XM and
816
        not _ping_fn(target, port, live_port_needed=True)):
817
      raise errors.HypervisorError("Remote host %s not listening on port"
818
                                   " %s, cannot migrate" % (target, port))
819

    
820
    args = ["migrate"]
821

    
822
    if cmd == constants.XEN_CMD_XM:
823
      args.extend(["-p", "%d" % port])
824
      if live:
825
        args.append("-l")
826

    
827
    elif cmd == constants.XEN_CMD_XL:
828
      args.extend([
829
        "-s", constants.XL_SSH_CMD % cluster_name,
830
        "-C", self._ConfigFileName(instance_name),
831
        ])
832

    
833
    else:
834
      raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
835

    
836
    args.extend([instance_name, target])
837

    
838
    result = self._RunXen(args)
839
    if result.failed:
840
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
841
                                   (instance_name, result.output))
842

    
843
  def FinalizeMigrationSource(self, instance, success, live):
844
    """Finalize the instance migration on the source node.
845

846
    @type instance: L{objects.Instance}
847
    @param instance: the instance that was migrated
848
    @type success: bool
849
    @param success: whether the migration succeeded or not
850
    @type live: bool
851
    @param live: whether the user requested a live migration or not
852

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

    
862
  def GetMigrationStatus(self, instance):
863
    """Get the migration status
864

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

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

876
    """
877
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
878

    
879
  @classmethod
880
  def PowercycleNode(cls):
881
    """Xen-specific powercycle.
882

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

890
    """
891
    try:
892
      cls.LinuxPowercycle()
893
    finally:
894
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
895

    
896

    
897
class XenPvmHypervisor(XenHypervisor):
898
  """Xen PVM hypervisor interface"""
899

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

    
921
  def _GetConfig(self, instance, startup_memory, block_devices):
922
    """Write the Xen config file for the instance.
923

924
    """
925
    hvp = instance.hvparams
926
    config = StringIO()
927
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
928

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

    
940
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
941
      if bootloader_args:
942
        config.write("bootargs = '%s'\n" % bootloader_args)
943
    else:
944
      # kernel handling
945
      kpath = hvp[constants.HV_KERNEL_PATH]
946
      config.write("kernel = '%s'\n" % kpath)
947

    
948
      # initrd handling
949
      initrd_path = hvp[constants.HV_INITRD_PATH]
950
      if initrd_path:
951
        config.write("ramdisk = '%s'\n" % initrd_path)
952

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

    
967
    config.write("name = '%s'\n" % instance.name)
968

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

    
982
    disk_data = \
983
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
984

    
985
    config.write("vif = [%s]\n" % ",".join(vif_data))
986
    config.write("disk = [%s]\n" % ",".join(disk_data))
987

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

    
998
    return config.getvalue()
999

    
1000

    
1001
class XenHvmHypervisor(XenHypervisor):
1002
  """Xen HVM hypervisor interface"""
1003

    
1004
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
1005
    pathutils.VNC_PASSWORD_FILE,
1006
    ]
1007
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
1008
    pathutils.VNC_PASSWORD_FILE,
1009
    ]
1010

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

    
1048
  def _GetConfig(self, instance, startup_memory, block_devices):
1049
    """Create a Xen 3.1 HVM config file.
1050

1051
    """
1052
    hvp = instance.hvparams
1053

    
1054
    config = StringIO()
1055

    
1056
    # kernel handling
1057
    kpath = hvp[constants.HV_KERNEL_PATH]
1058
    config.write("kernel = '%s'\n" % kpath)
1059

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

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

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

    
1100
    if instance.network_port > constants.VNC_BASE_PORT:
1101
      display = instance.network_port - constants.VNC_BASE_PORT
1102
      config.write("vncdisplay = %s\n" % display)
1103
      config.write("vncunused = 0\n")
1104
    else:
1105
      config.write("# vncdisplay = 1\n")
1106
      config.write("vncunused = 1\n")
1107

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

    
1115
    config.write("vncpasswd = '%s'\n" % password.rstrip())
1116

    
1117
    config.write("serial = 'pty'\n")
1118
    if hvp[constants.HV_USE_LOCALTIME]:
1119
      config.write("localtime = 1\n")
1120

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

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

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

    
1153
    disk_data = \
1154
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1155

    
1156
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1157
    if iso_path:
1158
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
1159
      disk_data.append(iso)
1160

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

    
1175
    return config.getvalue()