Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 312a1928

History | View | Annotate | Download (37.4 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
      data.write("NETWORK_NAME=%s\n" % netinfo.name)
408
      if netinfo.network:
409
        data.write("NETWORK_SUBNET=%s\n" % netinfo.network)
410
      if netinfo.gateway:
411
        data.write("NETWORK_GATEWAY=%s\n" % netinfo.gateway)
412
      if netinfo.network6:
413
        data.write("NETWORK_SUBNET6=%s\n" % netinfo.network6)
414
      if netinfo.gateway6:
415
        data.write("NETWORK_GATEWAY6=%s\n" % netinfo.gateway6)
416
      if netinfo.mac_prefix:
417
        data.write("NETWORK_MAC_PREFIX=%s\n" % netinfo.mac_prefix)
418
      if netinfo.tags:
419
        data.write("NETWORK_TAGS=%s\n" % "\ ".join(netinfo.tags))
420

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

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

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

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

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

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

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

450
    """
451
    raise NotImplementedError
452

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

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

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

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

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

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

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

    
480
    return file_content
481

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

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

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

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

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

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

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

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

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

521
    @param instance_name: the instance name
522

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

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

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

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

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

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

546
    See L{_GetConfig} for arguments.
547

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

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

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

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

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

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

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

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

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

    
587
    return self._StopInstance(name, force)
588

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
671
      raise utils.RetryAgain()
672

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
744
    return None
745

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

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

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

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

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

767
    """
768
    pass
769

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

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

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

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

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

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

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

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

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

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

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

813
    @see: L{MigrateInstance} for details
814

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

    
819
    cmd = self._GetCommand()
820

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

    
826
    args = ["migrate"]
827

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
902

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1004
    return config.getvalue()
1005

    
1006

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

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

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

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

1057
    """
1058
    hvp = instance.hvparams
1059

    
1060
    config = StringIO()
1061

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1181
    return config.getvalue()