Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 0bbec3af

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=None):
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 not None:
351
        cmd = hvparams[constants.HV_XEN_CMD]
352
      else:
353
        # TODO: Remove autoconf option once retrieving the command from
354
        # the hvparams is fully implemented.
355
        cmd = constants.XEN_CMD
356
    else:
357
      cmd = self._cmd
358

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

    
362
    return cmd
363

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

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

371
    """
372
    cmd = [self._GetCommand(hvparams=hvparams)]
373
    cmd.extend(args)
374

    
375
    return self._run_cmd_fn(cmd)
376

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

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

385
    """
386
    return utils.PathJoin(self._cfgdir, instance_name)
387

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

392
    """
393
    raise NotImplementedError
394

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

398
    This version of the function just writes the config file from static data.
399

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

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

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

414
    """
415
    filename = self._ConfigFileName(instance_name)
416

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

    
422
    return file_content
423

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

427
    """
428
    utils.RemoveFile(self._ConfigFileName(instance_name))
429

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

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

    
441
  def _GetInstanceList(self, include_node, hvparams=None):
442
    """Wrapper around module level L{_GetInstanceList}.
443

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

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

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

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

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

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

466
    """
467
    instance_list = self._GetInstanceList(instance_name == _DOM0_NAME,
468
                                          hvparams=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):
477
    """Get properties of all instances.
478

479
    @return: list of tuples (name, id, memory, vcpus, stat, times)
480

481
    """
482
    return self._GetInstanceList(False)
483

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

487
    See L{_GetConfig} for arguments.
488

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

    
496
    self._WriteConfigFile(instance.name, buf.getvalue())
497

    
498
  def StartInstance(self, instance, block_devices, startup_paused):
499
    """Start an instance.
500

501
    """
502
    startup_memory = self._InstanceStartupMemory(instance,
503
                                                 hvparams=instance.hvparams)
504

    
505
    self._MakeConfigFile(instance, startup_memory, block_devices)
506

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

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

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

525
    """
526
    if name is None:
527
      name = instance.name
528

    
529
    return self._StopInstance(name, force, instance.hvparams)
530

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

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

541
    """
542
    if force:
543
      action = "destroy"
544
    else:
545
      action = "shutdown"
546

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

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

    
555
  def RebootInstance(self, instance):
556
    """Reboot an instance.
557

558
    """
559
    ini_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
560

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

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

    
571
    def _CheckInstance():
572
      new_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
573

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

    
579
      raise utils.RetryAgain()
580

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
670
    return None
671

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

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

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

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

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

693
    """
694
    pass
695

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

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

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

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

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

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

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

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

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

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

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

739
    @see: L{MigrateInstance} for details
740

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

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

    
748
    cmd = self._GetCommand(hvparams=hvparams)
749

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

    
755
    args = ["migrate"]
756

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
873

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

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

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

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

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

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

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

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

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

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

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

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

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

    
973
    return config.getvalue()
974

    
975

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

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

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

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

1026
    """
1027
    hvp = instance.hvparams
1028

    
1029
    config = StringIO()
1030

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1142
    return config.getvalue()