Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 71fe51f8

History | View | Annotate | Download (37.3 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Xen hypervisors
23

24
"""
25

    
26
import logging
27
import errno
28
import string # pylint: disable=W0402
29
import shutil
30
from cStringIO import StringIO
31

    
32
from ganeti import constants
33
from ganeti import errors
34
from ganeti import utils
35
from ganeti.hypervisor import hv_base
36
from ganeti import netutils
37
from ganeti import objects
38
from ganeti import pathutils
39
from ganeti import ssconf
40

    
41

    
42
XEND_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xend-config.sxp")
43
XL_CONFIG_FILE = utils.PathJoin(pathutils.XEN_CONFIG_DIR, "xen/xl.conf")
44
VIF_BRIDGE_SCRIPT = utils.PathJoin(pathutils.XEN_CONFIG_DIR,
45
                                   "scripts/vif-bridge")
46
_DOM0_NAME = "Domain-0"
47
_DISK_LETTERS = string.ascii_lowercase
48

    
49
_FILE_DRIVER_MAP = {
50
  constants.FD_LOOP: "file",
51
  constants.FD_BLKTAP: "tap:aio",
52
  }
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_name, 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
    dirs = [(dname, constants.RUN_DIRS_MODE)
397
            for dname in cls._DIRS + [cls._InstanceNICDir(instance_name)]]
398
    utils.EnsureDirs(dirs)
399

    
400
    cfg_file = cls._InstanceNICFile(instance_name, idx)
401
    data = StringIO()
402

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

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

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

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

434
    """
435
    return utils.PathJoin(cls._NICS_DIR, instance_name)
436

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

441
    """
442
    return utils.PathJoin(cls._InstanceNICDir(instance_name), str(seq))
443

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

448
    """
449
    raise NotImplementedError
450

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

454
    This version of the function just writes the config file from static data.
455

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

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

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

470
    """
471
    filename = self._ConfigFileName(instance_name)
472

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

    
478
    return file_content
479

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

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

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

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

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

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

    
508
  def ListInstances(self):
509
    """Get the list of running instances.
510

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

    
516
  def GetInstanceInfo(self, instance_name):
517
    """Get instance properties.
518

519
    @param instance_name: the instance name
520

521
    @return: tuple (name, id, memory, vcpus, stat, times)
522

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

    
532
  def GetAllInstancesInfo(self):
533
    """Get properties of all instances.
534

535
    @return: list of tuples (name, id, memory, vcpus, stat, times)
536

537
    """
538
    xm_list = self._GetXmList(False)
539
    return xm_list
540

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

544
    See L{_GetConfig} for arguments.
545

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

    
553
    self._WriteConfigFile(instance.name, buf.getvalue())
554

    
555
  def StartInstance(self, instance, block_devices, startup_paused):
556
    """Start an instance.
557

558
    """
559
    startup_memory = self._InstanceStartupMemory(instance)
560

    
561
    self._MakeConfigFile(instance, startup_memory, block_devices)
562

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

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

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

581
    """
582
    if name is None:
583
      name = instance.name
584

    
585
    return self._StopInstance(name, force)
586

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

590
    @type name: string
591
    @param name: name of the instance to stop
592

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

597
    """
598
    instance_info = self.GetInstanceInfo(name)
599

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

    
604
    return self._RunXen(["shutdown", "-w", name])
605

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

609
    @type name: string
610
    @param name: name of the instance to destroy
611

612
    """
613
    instance_info = self.GetInstanceInfo(name)
614

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

    
619
    return self._RunXen(["destroy", name])
620

    
621
  def _StopInstance(self, name, force):
622
    """Stop an instance.
623

624
    @type name: string
625
    @param name: name of the instance to destroy
626

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

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

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

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

    
645
  def RebootInstance(self, instance):
646
    """Reboot an instance.
647

648
    """
649
    ini_info = self.GetInstanceInfo(instance.name)
650

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

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

    
661
    def _CheckInstance():
662
      new_info = self.GetInstanceInfo(instance.name)
663

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

    
669
      raise utils.RetryAgain()
670

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

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

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

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

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

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

    
704
  def GetNodeInfo(self):
705
    """Return information about the node.
706

707
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
708

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

    
716
    return _GetNodeInfo(result.stdout, self._GetXmList)
717

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

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

    
730
  def Verify(self):
731
    """Verify the hypervisor.
732

733
    For Xen, this verifies that the xend process is running.
734

735
    @return: Problem description if something is wrong, C{None} otherwise
736

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

    
742
    return None
743

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

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

752
    """
753
    return self._ReadConfigFile(instance.name)
754

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

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

765
    """
766
    pass
767

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

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

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

781
    """
782
    if success:
783
      self._WriteConfigFile(instance.name, info)
784

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

788
    The migration will not be attempted if the instance is not
789
    currently running.
790

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

798
    """
799
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
800

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

    
804
    return self._MigrateInstance(cluster_name, instance.name, target, port,
805
                                 live)
806

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

811
    @see: L{MigrateInstance} for details
812

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

    
817
    cmd = self._GetCommand()
818

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

    
824
    args = ["migrate"]
825

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

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

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

    
840
    args.extend([instance_name, target])
841

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

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

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

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

    
866
  def GetMigrationStatus(self, instance):
867
    """Get the migration status
868

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

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

880
    """
881
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
882

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

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

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

    
900

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1002
    return config.getvalue()
1003

    
1004

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

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

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

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

1055
    """
1056
    hvp = instance.hvparams
1057

    
1058
    config = StringIO()
1059

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1179
    return config.getvalue()