Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 7d4ae909

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
  constants.FD_BLKTAP2: "tap2:tapdisk:aio",
53
  }
54

    
55

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

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

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

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

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

    
87

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

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

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

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

    
108

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

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

119
  """
120
  result = []
121

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

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

    
144
  return result
145

    
146

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

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

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

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

    
166
    raise errors.HypervisorError(errmsg)
167

    
168
  return _ParseXmList(lines, include_node)
169

    
170

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

    
175

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

    
179

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

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

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

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

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

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

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

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

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

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

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

    
236
  return result
237

    
238

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

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

248
  """
249
  total_instmem = 0
250

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

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

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

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

    
266
  return info
267

    
268

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

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

    
275

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

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

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

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

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

    
295
  disk_data = []
296

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

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

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

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

    
312
  return disk_data
313

    
314

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

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

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

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

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

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

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

    
351
    self._cmd = _cmd
352

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

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

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

    
366
    return cmd
367

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

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

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

    
377
    return self._run_cmd_fn(cmd)
378

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

451
    """
452
    raise NotImplementedError
453

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

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

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

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

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

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

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

    
481
    return file_content
482

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

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

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

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

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

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

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

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

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

522
    @param instance_name: the instance name
523

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

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

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

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

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

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

547
    See L{_GetConfig} for arguments.
548

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

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

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

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

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

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

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

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

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

    
588
    return self._StopInstance(name, force)
589

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
672
      raise utils.RetryAgain()
673

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
745
    return None
746

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

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

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

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

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

768
    """
769
    pass
770

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

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

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

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

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

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

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

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

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

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

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

814
    @see: L{MigrateInstance} for details
815

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

    
820
    cmd = self._GetCommand()
821

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

    
827
    args = ["migrate"]
828

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
903

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1005
    return config.getvalue()
1006

    
1007

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

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

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

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

1058
    """
1059
    hvp = instance.hvparams
1060

    
1061
    config = StringIO()
1062

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1182
    return config.getvalue()