Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 398fd4f6

History | View | Annotate | Download (37.2 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 string # pylint: disable=W0402
28
from cStringIO import StringIO
29

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

    
39

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

    
47
_FILE_DRIVER_MAP = {
48
  constants.FD_LOOP: "file",
49
  constants.FD_BLKTAP: "tap:aio",
50
  }
51

    
52

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

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

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

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

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

    
84

    
85
def _RunInstanceList(fn, instance_list_errors):
86
  """Helper function for L{_GetInstanceList} to retrieve the list of instances
87
  from xen.
88

89
  @type fn: callable
90
  @param fn: Function to query xen for the list of instances
91
  @type instance_list_errors: list
92
  @param instance_list_errors: Error list
93
  @rtype: list
94

95
  """
96
  result = fn()
97
  if result.failed:
98
    logging.error("Retrieving the instance list from xen failed (%s): %s",
99
                  result.fail_reason, result.output)
100
    instance_list_errors.append(result)
101
    raise utils.RetryAgain()
102

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

    
106

    
107
def _ParseInstanceList(lines, include_node):
108
  """Parses the output of listing instances by xen.
109

110
  @type lines: list
111
  @param lines: Result of retrieving the instance list from xen
112
  @type include_node: boolean
113
  @param include_node: If True, return information for Dom0
114
  @return: list of tuple containing (name, id, memory, vcpus, state, time
115
    spent)
116

117
  """
118
  result = []
119

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

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

    
142
  return result
143

    
144

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

148
  See L{_RunInstanceList} and L{_ParseInstanceList} for parameter details.
149

150
  """
151
  instance_list_errors = []
152
  try:
153
    lines = utils.Retry(_RunInstanceList, (0.3, 1.5, 1.0), _timeout,
154
                        args=(fn, instance_list_errors))
155
  except utils.RetryTimeout:
156
    if instance_list_errors:
157
      instance_list_result = instance_list_errors.pop()
158

    
159
      errmsg = ("listing instances failed, timeout exceeded (%s): %s" %
160
                (instance_list_result.fail_reason, instance_list_result.output))
161
    else:
162
      errmsg = "listing instances failed"
163

    
164
    raise errors.HypervisorError(errmsg)
165

    
166
  return _ParseInstanceList(lines, include_node)
167

    
168

    
169
def _ParseNodeInfo(info):
170
  """Return information about the node.
171

172
  @return: a dict with the following keys (memory values in MiB):
173
        - memory_total: the total memory size on the node
174
        - memory_free: the available memory on the node for instances
175
        - nr_cpus: total number of CPUs
176
        - nr_nodes: in a NUMA system, the number of domains
177
        - nr_sockets: the number of physical CPU sockets in the node
178
        - hv_version: the hypervisor version in the form (major, minor)
179

180
  """
181
  result = {}
182
  cores_per_socket = threads_per_core = nr_cpus = None
183
  xen_major, xen_minor = None, None
184
  memory_total = None
185
  memory_free = None
186

    
187
  for line in info.splitlines():
188
    fields = line.split(":", 1)
189

    
190
    if len(fields) < 2:
191
      continue
192

    
193
    (key, val) = map(lambda s: s.strip(), fields)
194

    
195
    # Note: in Xen 3, memory has changed to total_memory
196
    if key in ("memory", "total_memory"):
197
      memory_total = int(val)
198
    elif key == "free_memory":
199
      memory_free = int(val)
200
    elif key == "nr_cpus":
201
      nr_cpus = result["cpu_total"] = int(val)
202
    elif key == "nr_nodes":
203
      result["cpu_nodes"] = int(val)
204
    elif key == "cores_per_socket":
205
      cores_per_socket = int(val)
206
    elif key == "threads_per_core":
207
      threads_per_core = int(val)
208
    elif key == "xen_major":
209
      xen_major = int(val)
210
    elif key == "xen_minor":
211
      xen_minor = int(val)
212

    
213
  if None not in [cores_per_socket, threads_per_core, nr_cpus]:
214
    result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
215

    
216
  if memory_free is not None:
217
    result["memory_free"] = memory_free
218

    
219
  if memory_total is not None:
220
    result["memory_total"] = memory_total
221

    
222
  if not (xen_major is None or xen_minor is None):
223
    result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
224

    
225
  return result
226

    
227

    
228
def _MergeInstanceInfo(info, instance_list):
229
  """Updates node information from L{_ParseNodeInfo} with instance info.
230

231
  @type info: dict
232
  @param info: Result from L{_ParseNodeInfo}
233
  @type instance_list: list of tuples
234
  @param instance_list: list of instance information; one tuple per instance
235
  @rtype: dict
236

237
  """
238
  total_instmem = 0
239

    
240
  for (name, _, mem, vcpus, _, _) in instance_list:
241
    if name == _DOM0_NAME:
242
      info["memory_dom0"] = mem
243
      info["dom0_cpus"] = vcpus
244

    
245
    # Include Dom0 in total memory usage
246
    total_instmem += mem
247

    
248
  memory_free = info.get("memory_free")
249
  memory_total = info.get("memory_total")
250

    
251
  # Calculate memory used by hypervisor
252
  if None not in [memory_total, memory_free, total_instmem]:
253
    info["memory_hv"] = memory_total - memory_free - total_instmem
254

    
255
  return info
256

    
257

    
258
def _GetNodeInfo(info, instance_list):
259
  """Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}.
260

261
  @type instance_list: list of tuples
262
  @param instance_list: list of instance information; one tuple per instance
263

264
  """
265
  return _MergeInstanceInfo(_ParseNodeInfo(info), instance_list)
266

    
267

    
268
def _GetConfigFileDiskData(block_devices, blockdev_prefix,
269
                           _letters=_DISK_LETTERS):
270
  """Get disk directives for Xen config file.
271

272
  This method builds the xen config disk directive according to the
273
  given disk_template and block_devices.
274

275
  @param block_devices: list of tuples (cfdev, rldev):
276
      - cfdev: dict containing ganeti config disk part
277
      - rldev: ganeti.block.bdev.BlockDev object
278
  @param blockdev_prefix: a string containing blockdevice prefix,
279
                          e.g. "sd" for /dev/sda
280

281
  @return: string containing disk directive for xen instance config file
282

283
  """
284
  if len(block_devices) > len(_letters):
285
    raise errors.HypervisorError("Too many disks")
286

    
287
  disk_data = []
288

    
289
  for sd_suffix, (cfdev, dev_path) in zip(_letters, block_devices):
290
    sd_name = blockdev_prefix + sd_suffix
291

    
292
    if cfdev.mode == constants.DISK_RDWR:
293
      mode = "w"
294
    else:
295
      mode = "r"
296

    
297
    if cfdev.dev_type == constants.LD_FILE:
298
      driver = _FILE_DRIVER_MAP[cfdev.physical_id[0]]
299
    else:
300
      driver = "phy"
301

    
302
    disk_data.append("'%s:%s,%s,%s'" % (driver, dev_path, sd_name, mode))
303

    
304
  return disk_data
305

    
306

    
307
class XenHypervisor(hv_base.BaseHypervisor):
308
  """Xen generic hypervisor interface
309

310
  This is the Xen base class used for both Xen PVM and HVM. It contains
311
  all the functionality that is identical for both.
312

313
  """
314
  CAN_MIGRATE = True
315
  REBOOT_RETRY_COUNT = 60
316
  REBOOT_RETRY_INTERVAL = 10
317

    
318
  ANCILLARY_FILES = [
319
    XEND_CONFIG_FILE,
320
    XL_CONFIG_FILE,
321
    VIF_BRIDGE_SCRIPT,
322
    ]
323
  ANCILLARY_FILES_OPT = [
324
    XL_CONFIG_FILE,
325
    ]
326

    
327
  def __init__(self, _cfgdir=None, _run_cmd_fn=None, _cmd=None):
328
    hv_base.BaseHypervisor.__init__(self)
329

    
330
    if _cfgdir is None:
331
      self._cfgdir = pathutils.XEN_CONFIG_DIR
332
    else:
333
      self._cfgdir = _cfgdir
334

    
335
    if _run_cmd_fn is None:
336
      self._run_cmd_fn = utils.RunCmd
337
    else:
338
      self._run_cmd_fn = _run_cmd_fn
339

    
340
    self._cmd = _cmd
341

    
342
  def _GetCommand(self, hvparams):
343
    """Returns Xen command to use.
344

345
    @type hvparams: dict of strings
346
    @param hvparams: hypervisor parameters
347

348
    """
349
    if self._cmd is None:
350
      if hvparams is None or constants.HV_XEN_CMD not in hvparams:
351
        raise errors.HypervisorError("Cannot determine xen command.")
352
      else:
353
        cmd = hvparams[constants.HV_XEN_CMD]
354
    else:
355
      cmd = self._cmd
356

    
357
    if cmd not in constants.KNOWN_XEN_COMMANDS:
358
      raise errors.ProgrammerError("Unknown Xen command '%s'" % cmd)
359

    
360
    return cmd
361

    
362
  def _RunXen(self, args, hvparams):
363
    """Wrapper around L{utils.process.RunCmd} to run Xen command.
364

365
    @type hvparams: dict of strings
366
    @param hvparams: dictionary of hypervisor params
367
    @see: L{utils.process.RunCmd}
368

369
    """
370
    cmd = [self._GetCommand(hvparams)]
371
    cmd.extend(args)
372

    
373
    return self._run_cmd_fn(cmd)
374

    
375
  def _ConfigFileName(self, instance_name):
376
    """Get the config file name for an instance.
377

378
    @param instance_name: instance name
379
    @type instance_name: str
380
    @return: fully qualified path to instance config file
381
    @rtype: str
382

383
    """
384
    return utils.PathJoin(self._cfgdir, instance_name)
385

    
386
  @classmethod
387
  def _GetConfig(cls, instance, startup_memory, block_devices):
388
    """Build Xen configuration for an instance.
389

390
    """
391
    raise NotImplementedError
392

    
393
  def _WriteConfigFile(self, instance_name, data):
394
    """Write the Xen config file for the instance.
395

396
    This version of the function just writes the config file from static data.
397

398
    """
399
    # just in case it exists
400
    utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
401

    
402
    cfg_file = self._ConfigFileName(instance_name)
403
    try:
404
      utils.WriteFile(cfg_file, data=data)
405
    except EnvironmentError, err:
406
      raise errors.HypervisorError("Cannot write Xen instance configuration"
407
                                   " file %s: %s" % (cfg_file, err))
408

    
409
  def _ReadConfigFile(self, instance_name):
410
    """Returns the contents of the instance config file.
411

412
    """
413
    filename = self._ConfigFileName(instance_name)
414

    
415
    try:
416
      file_content = utils.ReadFile(filename)
417
    except EnvironmentError, err:
418
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
419

    
420
    return file_content
421

    
422
  def _RemoveConfigFile(self, instance_name):
423
    """Remove the xen configuration file.
424

425
    """
426
    utils.RemoveFile(self._ConfigFileName(instance_name))
427

    
428
  def _StashConfigFile(self, instance_name):
429
    """Move the Xen config file to the log directory and return its new path.
430

431
    """
432
    old_filename = self._ConfigFileName(instance_name)
433
    base = ("%s-%s" %
434
            (instance_name, utils.TimestampForFilename()))
435
    new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base)
436
    utils.RenameFile(old_filename, new_filename)
437
    return new_filename
438

    
439
  def _GetInstanceList(self, include_node, hvparams):
440
    """Wrapper around module level L{_GetInstanceList}.
441

442
    @type hvparams: dict of strings
443
    @param hvparams: hypervisor parameters to be used on this node
444

445
    """
446
    return _GetInstanceList(lambda: self._RunXen(["list"], hvparams),
447
                            include_node)
448

    
449
  def ListInstances(self, hvparams=None):
450
    """Get the list of running instances.
451

452
    """
453
    instance_list = self._GetInstanceList(False, hvparams)
454
    names = [info[0] for info in instance_list]
455
    return names
456

    
457
  def GetInstanceInfo(self, instance_name, hvparams=None):
458
    """Get instance properties.
459

460
    @type instance_name: string
461
    @param instance_name: the instance name
462
    @type hvparams: dict of strings
463
    @param hvparams: the instance's hypervisor params
464

465
    @return: tuple (name, id, memory, vcpus, stat, times)
466

467
    """
468
    instance_list = self._GetInstanceList(instance_name == _DOM0_NAME, hvparams)
469
    result = None
470
    for data in instance_list:
471
      if data[0] == instance_name:
472
        result = data
473
        break
474
    return result
475

    
476
  def GetAllInstancesInfo(self, hvparams=None):
477
    """Get properties of all instances.
478

479
    @type hvparams: dict of strings
480
    @param hvparams: hypervisor parameters
481
    @return: list of tuples (name, id, memory, vcpus, stat, times)
482

483
    """
484
    return self._GetInstanceList(False, hvparams)
485

    
486
  def _MakeConfigFile(self, instance, startup_memory, block_devices):
487
    """Gather configuration details and write to disk.
488

489
    See L{_GetConfig} for arguments.
490

491
    """
492
    buf = StringIO()
493
    buf.write("# Automatically generated by Ganeti. Do not edit!\n")
494
    buf.write("\n")
495
    buf.write(self._GetConfig(instance, startup_memory, block_devices))
496
    buf.write("\n")
497

    
498
    self._WriteConfigFile(instance.name, buf.getvalue())
499

    
500
  def StartInstance(self, instance, block_devices, startup_paused):
501
    """Start an instance.
502

503
    """
504
    startup_memory = self._InstanceStartupMemory(instance,
505
                                                 hvparams=instance.hvparams)
506

    
507
    self._MakeConfigFile(instance, startup_memory, block_devices)
508

    
509
    cmd = ["create"]
510
    if startup_paused:
511
      cmd.append("-p")
512
    cmd.append(self._ConfigFileName(instance.name))
513

    
514
    result = self._RunXen(cmd, instance.hvparams)
515
    if result.failed:
516
      # Move the Xen configuration file to the log directory to avoid
517
      # leaving a stale config file behind.
518
      stashed_config = self._StashConfigFile(instance.name)
519
      raise errors.HypervisorError("Failed to start instance %s: %s (%s). Moved"
520
                                   " config file to %s" %
521
                                   (instance.name, result.fail_reason,
522
                                    result.output, stashed_config))
523

    
524
  def StopInstance(self, instance, force=False, retry=False, name=None):
525
    """Stop an instance.
526

527
    """
528
    if name is None:
529
      name = instance.name
530

    
531
    return self._StopInstance(name, force, instance.hvparams)
532

    
533
  def _StopInstance(self, name, force, hvparams):
534
    """Stop an instance.
535

536
    @type name: string
537
    @param name: name of the instance to be shutdown
538
    @type force: boolean
539
    @param force: flag specifying whether shutdown should be forced
540
    @type hvparams: dict of string
541
    @param hvparams: hypervisor parameters of the instance
542

543
    """
544
    if force:
545
      action = "destroy"
546
    else:
547
      action = "shutdown"
548

    
549
    result = self._RunXen([action, name], hvparams)
550
    if result.failed:
551
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
552
                                   (name, result.fail_reason, result.output))
553

    
554
    # Remove configuration file if stopping/starting instance was successful
555
    self._RemoveConfigFile(name)
556

    
557
  def RebootInstance(self, instance):
558
    """Reboot an instance.
559

560
    """
561
    ini_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
562

    
563
    if ini_info is None:
564
      raise errors.HypervisorError("Failed to reboot instance %s,"
565
                                   " not running" % instance.name)
566

    
567
    result = self._RunXen(["reboot", instance.name], instance.hvparams)
568
    if result.failed:
569
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
570
                                   (instance.name, result.fail_reason,
571
                                    result.output))
572

    
573
    def _CheckInstance():
574
      new_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
575

    
576
      # check if the domain ID has changed or the run time has decreased
577
      if (new_info is not None and
578
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
579
        return
580

    
581
      raise utils.RetryAgain()
582

    
583
    try:
584
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
585
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
586
    except utils.RetryTimeout:
587
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
588
                                   " did not reboot in the expected interval" %
589
                                   (instance.name, ))
590

    
591
  def BalloonInstanceMemory(self, instance, mem):
592
    """Balloon an instance memory to a certain value.
593

594
    @type instance: L{objects.Instance}
595
    @param instance: instance to be accepted
596
    @type mem: int
597
    @param mem: actual memory size to use for instance runtime
598

599
    """
600
    result = self._RunXen(["mem-set", instance.name, mem], instance.hvparams)
601
    if result.failed:
602
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
603
                                   (instance.name, result.fail_reason,
604
                                    result.output))
605

    
606
    # Update configuration file
607
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
608
    cmd.append(self._ConfigFileName(instance.name))
609

    
610
    result = utils.RunCmd(cmd)
611
    if result.failed:
612
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
613
                                   (instance.name, result.fail_reason,
614
                                    result.output))
615

    
616
  def GetNodeInfo(self, hvparams=None):
617
    """Return information about the node.
618

619
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
620

621
    """
622
    result = self._RunXen(["info"], hvparams)
623
    if result.failed:
624
      logging.error("Can't retrieve xen hypervisor information (%s): %s",
625
                    result.fail_reason, result.output)
626
      return None
627

    
628
    instance_list = self._GetInstanceList(True, hvparams)
629
    return _GetNodeInfo(result.stdout, instance_list)
630

    
631
  @classmethod
632
  def GetInstanceConsole(cls, instance, hvparams, beparams):
633
    """Return a command for connecting to the console of an instance.
634

635
    """
636
    return objects.InstanceConsole(instance=instance.name,
637
                                   kind=constants.CONS_SSH,
638
                                   host=instance.primary_node,
639
                                   user=constants.SSH_CONSOLE_USER,
640
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
641
                                            constants.XEN_CMD, instance.name])
642

    
643
  def Verify(self, hvparams=None):
644
    """Verify the hypervisor.
645

646
    For Xen, this verifies that the xend process is running.
647

648
    @type hvparams: dict of strings
649
    @param hvparams: hypervisor parameters to be verified against
650

651
    @return: Problem description if something is wrong, C{None} otherwise
652

653
    """
654
    if hvparams is None:
655
      return "Could not verify the hypervisor, because no hvparams were" \
656
             " provided."
657

    
658
    if constants.HV_XEN_CMD in hvparams:
659
      xen_cmd = hvparams[constants.HV_XEN_CMD]
660
      try:
661
        self._CheckToolstack(xen_cmd)
662
      except errors.HypervisorError:
663
        return "The configured xen toolstack '%s' is not available on this" \
664
               " node." % xen_cmd
665

    
666
    result = self._RunXen(["info"], hvparams)
667
    if result.failed:
668
      return "Retrieving information from xen failed: %s, %s" % \
669
        (result.fail_reason, result.output)
670

    
671
    return None
672

    
673
  def MigrationInfo(self, instance):
674
    """Get instance information to perform a migration.
675

676
    @type instance: L{objects.Instance}
677
    @param instance: instance to be migrated
678
    @rtype: string
679
    @return: content of the xen config file
680

681
    """
682
    return self._ReadConfigFile(instance.name)
683

    
684
  def AcceptInstance(self, instance, info, target):
685
    """Prepare to accept an instance.
686

687
    @type instance: L{objects.Instance}
688
    @param instance: instance to be accepted
689
    @type info: string
690
    @param info: content of the xen config file on the source node
691
    @type target: string
692
    @param target: target host (usually ip), on this node
693

694
    """
695
    pass
696

    
697
  def FinalizeMigrationDst(self, instance, info, success):
698
    """Finalize an instance migration.
699

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

703
    @type instance: L{objects.Instance}
704
    @param instance: instance whose migration is being finalized
705
    @type info: string
706
    @param info: content of the xen config file on the source node
707
    @type success: boolean
708
    @param success: whether the migration was a success or a failure
709

710
    """
711
    if success:
712
      self._WriteConfigFile(instance.name, info)
713

    
714
  def MigrateInstance(self, instance, target, live):
715
    """Migrate an instance to a target node.
716

717
    The migration will not be attempted if the instance is not
718
    currently running.
719

720
    @type instance: L{objects.Instance}
721
    @param instance: the instance to be migrated
722
    @type target: string
723
    @param target: ip address of the target node
724
    @type live: boolean
725
    @param live: perform a live migration
726

727
    """
728
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
729

    
730
    # TODO: Pass cluster name via RPC
731
    cluster_name = ssconf.SimpleStore().GetClusterName()
732

    
733
    return self._MigrateInstance(cluster_name, instance.name, target, port,
734
                                 live, instance.hvparams)
735

    
736
  def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
737
                       hvparams, _ping_fn=netutils.TcpPing):
738
    """Migrate an instance to a target node.
739

740
    @see: L{MigrateInstance} for details
741

742
    """
743
    if hvparams is None:
744
      raise errors.HypervisorError("No hvparams provided.")
745

    
746
    if self.GetInstanceInfo(instance_name, hvparams=hvparams) is None:
747
      raise errors.HypervisorError("Instance not running, cannot migrate")
748

    
749
    cmd = self._GetCommand(hvparams)
750

    
751
    if (cmd == constants.XEN_CMD_XM and
752
        not _ping_fn(target, port, live_port_needed=True)):
753
      raise errors.HypervisorError("Remote host %s not listening on port"
754
                                   " %s, cannot migrate" % (target, port))
755

    
756
    args = ["migrate"]
757

    
758
    if cmd == constants.XEN_CMD_XM:
759
      args.extend(["-p", "%d" % port])
760
      if live:
761
        args.append("-l")
762

    
763
    elif cmd == constants.XEN_CMD_XL:
764
      args.extend([
765
        "-s", constants.XL_SSH_CMD % cluster_name,
766
        "-C", self._ConfigFileName(instance_name),
767
        ])
768

    
769
    else:
770
      raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
771

    
772
    args.extend([instance_name, target])
773

    
774
    result = self._RunXen(args, hvparams)
775
    if result.failed:
776
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
777
                                   (instance_name, result.output))
778

    
779
  def FinalizeMigrationSource(self, instance, success, live):
780
    """Finalize the instance migration on the source node.
781

782
    @type instance: L{objects.Instance}
783
    @param instance: the instance that was migrated
784
    @type success: bool
785
    @param success: whether the migration succeeded or not
786
    @type live: bool
787
    @param live: whether the user requested a live migration or not
788

789
    """
790
    # pylint: disable=W0613
791
    if success:
792
      # remove old xen file after migration succeeded
793
      try:
794
        self._RemoveConfigFile(instance.name)
795
      except EnvironmentError:
796
        logging.exception("Failure while removing instance config file")
797

    
798
  def GetMigrationStatus(self, instance):
799
    """Get the migration status
800

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

805
    @type instance: L{objects.Instance}
806
    @param instance: the instance that is being migrated
807
    @rtype: L{objects.MigrationStatus}
808
    @return: the status of the current migration (one of
809
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
810
             progress info that can be retrieved from the hypervisor
811

812
    """
813
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
814

    
815
  @classmethod
816
  def PowercycleNode(cls):
817
    """Xen-specific powercycle.
818

819
    This first does a Linux reboot (which triggers automatically a Xen
820
    reboot), and if that fails it tries to do a Xen reboot. The reason
821
    we don't try a Xen reboot first is that the xen reboot launches an
822
    external command which connects to the Xen hypervisor, and that
823
    won't work in case the root filesystem is broken and/or the xend
824
    daemon is not working.
825

826
    """
827
    try:
828
      cls.LinuxPowercycle()
829
    finally:
830
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
831

    
832
  def _CheckToolstack(self, xen_cmd):
833
    """Check whether the given toolstack is available on the node.
834

835
    @type xen_cmd: string
836
    @param xen_cmd: xen command (e.g. 'xm' or 'xl')
837

838
    """
839
    binary_found = self._CheckToolstackBinary(xen_cmd)
840
    if not binary_found:
841
      raise errors.HypervisorError("No '%s' binary found on node." % xen_cmd)
842
    elif xen_cmd == constants.XEN_CMD_XL:
843
      if not self._CheckToolstackXlConfigured():
844
        raise errors.HypervisorError("Toolstack '%s' is not enabled on this"
845
                                     "node." % xen_cmd)
846

    
847
  def _CheckToolstackBinary(self, xen_cmd):
848
    """Checks whether the xen command's binary is found on the machine.
849

850
    """
851
    if xen_cmd not in constants.KNOWN_XEN_COMMANDS:
852
      raise errors.HypervisorError("Unknown xen command '%s'." % xen_cmd)
853
    result = self._run_cmd_fn(["which", xen_cmd])
854
    return not result.failed
855

    
856
  def _CheckToolstackXlConfigured(self):
857
    """Checks whether xl is enabled on an xl-capable node.
858

859
    @rtype: bool
860
    @returns: C{True} if 'xl' is enabled, C{False} otherwise
861

862
    """
863
    result = self._run_cmd_fn([constants.XEN_CMD_XL, "help"])
864
    if not result.failed:
865
      return True
866
    elif result.failed:
867
      if "toolstack" in result.stderr:
868
        return False
869
      # xl fails for some other reason than the toolstack
870
      else:
871
        raise errors.HypervisorError("Cannot run xen ('%s'). Error: %s."
872
                                     % (constants.XEN_CMD_XL, result.stderr))
873

    
874

    
875
class XenPvmHypervisor(XenHypervisor):
876
  """Xen PVM hypervisor interface"""
877

    
878
  PARAMETERS = {
879
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
880
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
881
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
882
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
883
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
884
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
885
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
886
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
887
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
888
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
889
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
890
    constants.HV_REBOOT_BEHAVIOR:
891
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
892
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
893
    constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
894
    constants.HV_CPU_WEIGHT:
895
      (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
896
    constants.HV_XEN_CMD:
897
      hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
898
    }
899

    
900
  def _GetConfig(self, instance, startup_memory, block_devices):
901
    """Write the Xen config file for the instance.
902

903
    """
904
    hvp = instance.hvparams
905
    config = StringIO()
906
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
907

    
908
    # if bootloader is True, use bootloader instead of kernel and ramdisk
909
    # parameters.
910
    if hvp[constants.HV_USE_BOOTLOADER]:
911
      # bootloader handling
912
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
913
      if bootloader_path:
914
        config.write("bootloader = '%s'\n" % bootloader_path)
915
      else:
916
        raise errors.HypervisorError("Bootloader enabled, but missing"
917
                                     " bootloader path")
918

    
919
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
920
      if bootloader_args:
921
        config.write("bootargs = '%s'\n" % bootloader_args)
922
    else:
923
      # kernel handling
924
      kpath = hvp[constants.HV_KERNEL_PATH]
925
      config.write("kernel = '%s'\n" % kpath)
926

    
927
      # initrd handling
928
      initrd_path = hvp[constants.HV_INITRD_PATH]
929
      if initrd_path:
930
        config.write("ramdisk = '%s'\n" % initrd_path)
931

    
932
    # rest of the settings
933
    config.write("memory = %d\n" % startup_memory)
934
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
935
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
936
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
937
    if cpu_pinning:
938
      config.write("%s\n" % cpu_pinning)
939
    cpu_cap = hvp[constants.HV_CPU_CAP]
940
    if cpu_cap:
941
      config.write("cpu_cap=%d\n" % cpu_cap)
942
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
943
    if cpu_weight:
944
      config.write("cpu_weight=%d\n" % cpu_weight)
945

    
946
    config.write("name = '%s'\n" % instance.name)
947

    
948
    vif_data = []
949
    for nic in instance.nics:
950
      nic_str = "mac=%s" % (nic.mac)
951
      ip = getattr(nic, "ip", None)
952
      if ip is not None:
953
        nic_str += ", ip=%s" % ip
954
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
955
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
956
      vif_data.append("'%s'" % nic_str)
957

    
958
    disk_data = \
959
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
960

    
961
    config.write("vif = [%s]\n" % ",".join(vif_data))
962
    config.write("disk = [%s]\n" % ",".join(disk_data))
963

    
964
    if hvp[constants.HV_ROOT_PATH]:
965
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
966
    config.write("on_poweroff = 'destroy'\n")
967
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
968
      config.write("on_reboot = 'restart'\n")
969
    else:
970
      config.write("on_reboot = 'destroy'\n")
971
    config.write("on_crash = 'restart'\n")
972
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
973

    
974
    return config.getvalue()
975

    
976

    
977
class XenHvmHypervisor(XenHypervisor):
978
  """Xen HVM hypervisor interface"""
979

    
980
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
981
    pathutils.VNC_PASSWORD_FILE,
982
    ]
983
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
984
    pathutils.VNC_PASSWORD_FILE,
985
    ]
986

    
987
  PARAMETERS = {
988
    constants.HV_ACPI: hv_base.NO_CHECK,
989
    constants.HV_BOOT_ORDER: (True, ) +
990
      (lambda x: x and len(x.strip("acdn")) == 0,
991
       "Invalid boot order specified, must be one or more of [acdn]",
992
       None, None),
993
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
994
    constants.HV_DISK_TYPE:
995
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
996
    constants.HV_NIC_TYPE:
997
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
998
    constants.HV_PAE: hv_base.NO_CHECK,
999
    constants.HV_VNC_BIND_ADDRESS:
1000
      (False, netutils.IP4Address.IsValid,
1001
       "VNC bind address is not a valid IP address", None, None),
1002
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
1003
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
1004
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
1005
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
1006
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
1007
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
1008
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
1009
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
1010
    # Add PCI passthrough
1011
    constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
1012
    constants.HV_REBOOT_BEHAVIOR:
1013
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
1014
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
1015
    constants.HV_CPU_CAP: hv_base.NO_CHECK,
1016
    constants.HV_CPU_WEIGHT:
1017
      (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
1018
    constants.HV_VIF_TYPE:
1019
      hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES),
1020
    constants.HV_XEN_CMD:
1021
      hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
1022
    }
1023

    
1024
  def _GetConfig(self, instance, startup_memory, block_devices):
1025
    """Create a Xen 3.1 HVM config file.
1026

1027
    """
1028
    hvp = instance.hvparams
1029

    
1030
    config = StringIO()
1031

    
1032
    # kernel handling
1033
    kpath = hvp[constants.HV_KERNEL_PATH]
1034
    config.write("kernel = '%s'\n" % kpath)
1035

    
1036
    config.write("builder = 'hvm'\n")
1037
    config.write("memory = %d\n" % startup_memory)
1038
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1039
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1040
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1041
    if cpu_pinning:
1042
      config.write("%s\n" % cpu_pinning)
1043
    cpu_cap = hvp[constants.HV_CPU_CAP]
1044
    if cpu_cap:
1045
      config.write("cpu_cap=%d\n" % cpu_cap)
1046
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1047
    if cpu_weight:
1048
      config.write("cpu_weight=%d\n" % cpu_weight)
1049

    
1050
    config.write("name = '%s'\n" % instance.name)
1051
    if hvp[constants.HV_PAE]:
1052
      config.write("pae = 1\n")
1053
    else:
1054
      config.write("pae = 0\n")
1055
    if hvp[constants.HV_ACPI]:
1056
      config.write("acpi = 1\n")
1057
    else:
1058
      config.write("acpi = 0\n")
1059
    config.write("apic = 1\n")
1060
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
1061
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
1062
    config.write("sdl = 0\n")
1063
    config.write("usb = 1\n")
1064
    config.write("usbdevice = 'tablet'\n")
1065
    config.write("vnc = 1\n")
1066
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
1067
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
1068
    else:
1069
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
1070

    
1071
    if instance.network_port > constants.VNC_BASE_PORT:
1072
      display = instance.network_port - constants.VNC_BASE_PORT
1073
      config.write("vncdisplay = %s\n" % display)
1074
      config.write("vncunused = 0\n")
1075
    else:
1076
      config.write("# vncdisplay = 1\n")
1077
      config.write("vncunused = 1\n")
1078

    
1079
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
1080
    try:
1081
      password = utils.ReadFile(vnc_pwd_file)
1082
    except EnvironmentError, err:
1083
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1084
                                   (vnc_pwd_file, err))
1085

    
1086
    config.write("vncpasswd = '%s'\n" % password.rstrip())
1087

    
1088
    config.write("serial = 'pty'\n")
1089
    if hvp[constants.HV_USE_LOCALTIME]:
1090
      config.write("localtime = 1\n")
1091

    
1092
    vif_data = []
1093
    # Note: what is called 'nic_type' here, is used as value for the xen nic
1094
    # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1095
    # the 'vif_type' to avoid a clash of notation.
1096
    nic_type = hvp[constants.HV_NIC_TYPE]
1097

    
1098
    if nic_type is None:
1099
      vif_type_str = ""
1100
      if hvp[constants.HV_VIF_TYPE]:
1101
        vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
1102
      # ensure old instances don't change
1103
      nic_type_str = vif_type_str
1104
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
1105
      nic_type_str = ", type=paravirtualized"
1106
    else:
1107
      # parameter 'model' is only valid with type 'ioemu'
1108
      nic_type_str = ", model=%s, type=%s" % \
1109
        (nic_type, constants.HT_HVM_VIF_IOEMU)
1110
    for nic in instance.nics:
1111
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1112
      ip = getattr(nic, "ip", None)
1113
      if ip is not None:
1114
        nic_str += ", ip=%s" % ip
1115
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1116
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1117
      vif_data.append("'%s'" % nic_str)
1118

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

    
1121
    disk_data = \
1122
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1123

    
1124
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1125
    if iso_path:
1126
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
1127
      disk_data.append(iso)
1128

    
1129
    config.write("disk = [%s]\n" % (",".join(disk_data)))
1130
    # Add PCI passthrough
1131
    pci_pass_arr = []
1132
    pci_pass = hvp[constants.HV_PASSTHROUGH]
1133
    if pci_pass:
1134
      pci_pass_arr = pci_pass.split(";")
1135
      config.write("pci = %s\n" % pci_pass_arr)
1136
    config.write("on_poweroff = 'destroy'\n")
1137
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1138
      config.write("on_reboot = 'restart'\n")
1139
    else:
1140
      config.write("on_reboot = 'destroy'\n")
1141
    config.write("on_crash = 'restart'\n")
1142

    
1143
    return config.getvalue()