Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 6ba0093c

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
  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
      for k, v in netinfo.HooksDict().iteritems():
409
        data.write("%s=%s\n" % (k, v))
410

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

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

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

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

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

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

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

445
    """
446
    raise NotImplementedError
447

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

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

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

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

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

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

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

    
475
    return file_content
476

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

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

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

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

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

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

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

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

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

516
    @param instance_name: the instance name
517

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

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

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

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

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

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

541
    See L{_GetConfig} for arguments.
542

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

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

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

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

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

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

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

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

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

    
582
    return self._StopInstance(name, force)
583

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
666
      raise utils.RetryAgain()
667

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
739
    return None
740

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

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

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

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

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

762
    """
763
    pass
764

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

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

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

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

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

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

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

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

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

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

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

808
    @see: L{MigrateInstance} for details
809

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

    
814
    cmd = self._GetCommand()
815

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

    
821
    args = ["migrate"]
822

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
897

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

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

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

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

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

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

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

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

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

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

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

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

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

    
999
    return config.getvalue()
1000

    
1001

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

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

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

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

1052
    """
1053
    hvp = instance.hvparams
1054

    
1055
    config = StringIO()
1056

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1176
    return config.getvalue()