Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ fac489a5

History | View | Annotate | Download (36.9 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):
457
    """Get instance properties.
458

459
    @param instance_name: the instance name
460

461
    @return: tuple (name, id, memory, vcpus, stat, times)
462

463
    """
464
    instance_list = self._GetInstanceList(instance_name == _DOM0_NAME)
465
    result = None
466
    for data in instance_list:
467
      if data[0] == instance_name:
468
        result = data
469
        break
470
    return result
471

    
472
  def GetAllInstancesInfo(self):
473
    """Get properties of all instances.
474

475
    @return: list of tuples (name, id, memory, vcpus, stat, times)
476

477
    """
478
    return self._GetInstanceList(False)
479

    
480
  def _MakeConfigFile(self, instance, startup_memory, block_devices):
481
    """Gather configuration details and write to disk.
482

483
    See L{_GetConfig} for arguments.
484

485
    """
486
    buf = StringIO()
487
    buf.write("# Automatically generated by Ganeti. Do not edit!\n")
488
    buf.write("\n")
489
    buf.write(self._GetConfig(instance, startup_memory, block_devices))
490
    buf.write("\n")
491

    
492
    self._WriteConfigFile(instance.name, buf.getvalue())
493

    
494
  def StartInstance(self, instance, block_devices, startup_paused):
495
    """Start an instance.
496

497
    """
498
    startup_memory = self._InstanceStartupMemory(instance)
499

    
500
    self._MakeConfigFile(instance, startup_memory, block_devices)
501

    
502
    cmd = ["create"]
503
    if startup_paused:
504
      cmd.append("-p")
505
    cmd.append(self._ConfigFileName(instance.name))
506

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

    
517
  def StopInstance(self, instance, force=False, retry=False, name=None):
518
    """Stop an instance.
519

520
    """
521
    if name is None:
522
      name = instance.name
523

    
524
    return self._StopInstance(name, force, instance.hvparams)
525

    
526
  def _StopInstance(self, name, force, hvparams):
527
    """Stop an instance.
528

529
    @type name: string
530
    @param name: name of the instance to be shutdown
531
    @type force: boolean
532
    @param force: flag specifying whether shutdown should be forced
533
    @type hvparams: dict of string
534
    @param hvparams: hypervisor parameters of the instance
535

536
    """
537
    if force:
538
      action = "destroy"
539
    else:
540
      action = "shutdown"
541

    
542
    result = self._RunXen([action, name], hvparams=hvparams)
543
    if result.failed:
544
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
545
                                   (name, result.fail_reason, result.output))
546

    
547
    # Remove configuration file if stopping/starting instance was successful
548
    self._RemoveConfigFile(name)
549

    
550
  def RebootInstance(self, instance):
551
    """Reboot an instance.
552

553
    """
554
    ini_info = self.GetInstanceInfo(instance.name)
555

    
556
    if ini_info is None:
557
      raise errors.HypervisorError("Failed to reboot instance %s,"
558
                                   " not running" % instance.name)
559

    
560
    result = self._RunXen(["reboot", instance.name], hvparams=instance.hvparams)
561
    if result.failed:
562
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
563
                                   (instance.name, result.fail_reason,
564
                                    result.output))
565

    
566
    def _CheckInstance():
567
      new_info = self.GetInstanceInfo(instance.name)
568

    
569
      # check if the domain ID has changed or the run time has decreased
570
      if (new_info is not None and
571
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
572
        return
573

    
574
      raise utils.RetryAgain()
575

    
576
    try:
577
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
578
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
579
    except utils.RetryTimeout:
580
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
581
                                   " did not reboot in the expected interval" %
582
                                   (instance.name, ))
583

    
584
  def BalloonInstanceMemory(self, instance, mem):
585
    """Balloon an instance memory to a certain value.
586

587
    @type instance: L{objects.Instance}
588
    @param instance: instance to be accepted
589
    @type mem: int
590
    @param mem: actual memory size to use for instance runtime
591

592
    """
593
    result = self._RunXen(["mem-set", instance.name, mem],
594
                          hvparams=instance.hvparams)
595
    if result.failed:
596
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
597
                                   (instance.name, result.fail_reason,
598
                                    result.output))
599

    
600
    # Update configuration file
601
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
602
    cmd.append(self._ConfigFileName(instance.name))
603

    
604
    result = utils.RunCmd(cmd)
605
    if result.failed:
606
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
607
                                   (instance.name, result.fail_reason,
608
                                    result.output))
609

    
610
  def GetNodeInfo(self, hvparams=None):
611
    """Return information about the node.
612

613
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
614

615
    """
616
    result = self._RunXen(["info"], hvparams=hvparams)
617
    if result.failed:
618
      logging.error("Can't retrieve xen hypervisor information (%s): %s",
619
                    result.fail_reason, result.output)
620
      return None
621

    
622
    instance_list = self._GetInstanceList(True, hvparams=hvparams)
623
    return _GetNodeInfo(result.stdout, instance_list)
624

    
625
  @classmethod
626
  def GetInstanceConsole(cls, instance, hvparams, beparams):
627
    """Return a command for connecting to the console of an instance.
628

629
    """
630
    return objects.InstanceConsole(instance=instance.name,
631
                                   kind=constants.CONS_SSH,
632
                                   host=instance.primary_node,
633
                                   user=constants.SSH_CONSOLE_USER,
634
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
635
                                            constants.XEN_CMD, instance.name])
636

    
637
  def Verify(self, hvparams=None):
638
    """Verify the hypervisor.
639

640
    For Xen, this verifies that the xend process is running.
641

642
    @type hvparams: dict of strings
643
    @param hvparams: hypervisor parameters to be verified against
644

645
    @return: Problem description if something is wrong, C{None} otherwise
646

647
    """
648
    if hvparams is None:
649
      return "Could not verify the hypervisor, because no hvparams were" \
650
             " provided."
651

    
652
    if constants.HV_XEN_CMD in hvparams:
653
      xen_cmd = hvparams[constants.HV_XEN_CMD]
654
      try:
655
        self._CheckToolstack(xen_cmd)
656
      except errors.HypervisorError:
657
        return "The configured xen toolstack '%s' is not available on this" \
658
               " node." % xen_cmd
659

    
660
    result = self._RunXen(["info"], hvparams=hvparams)
661
    if result.failed:
662
      return "Retrieving information from xen failed: %s, %s" % \
663
        (result.fail_reason, result.output)
664

    
665
    return None
666

    
667
  def MigrationInfo(self, instance):
668
    """Get instance information to perform a migration.
669

670
    @type instance: L{objects.Instance}
671
    @param instance: instance to be migrated
672
    @rtype: string
673
    @return: content of the xen config file
674

675
    """
676
    return self._ReadConfigFile(instance.name)
677

    
678
  def AcceptInstance(self, instance, info, target):
679
    """Prepare to accept an instance.
680

681
    @type instance: L{objects.Instance}
682
    @param instance: instance to be accepted
683
    @type info: string
684
    @param info: content of the xen config file on the source node
685
    @type target: string
686
    @param target: target host (usually ip), on this node
687

688
    """
689
    pass
690

    
691
  def FinalizeMigrationDst(self, instance, info, success):
692
    """Finalize an instance migration.
693

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

697
    @type instance: L{objects.Instance}
698
    @param instance: instance whose migration is being finalized
699
    @type info: string
700
    @param info: content of the xen config file on the source node
701
    @type success: boolean
702
    @param success: whether the migration was a success or a failure
703

704
    """
705
    if success:
706
      self._WriteConfigFile(instance.name, info)
707

    
708
  def MigrateInstance(self, instance, target, live):
709
    """Migrate an instance to a target node.
710

711
    The migration will not be attempted if the instance is not
712
    currently running.
713

714
    @type instance: L{objects.Instance}
715
    @param instance: the instance to be migrated
716
    @type target: string
717
    @param target: ip address of the target node
718
    @type live: boolean
719
    @param live: perform a live migration
720

721
    """
722
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
723

    
724
    # TODO: Pass cluster name via RPC
725
    cluster_name = ssconf.SimpleStore().GetClusterName()
726

    
727
    return self._MigrateInstance(cluster_name, instance.name, target, port,
728
                                 live, instance.hvparams)
729

    
730
  def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
731
                       hvparams, _ping_fn=netutils.TcpPing):
732
    """Migrate an instance to a target node.
733

734
    @see: L{MigrateInstance} for details
735

736
    """
737
    if hvparams is None:
738
      raise errors.HypervisorError("No hvparams provided.")
739

    
740
    if self.GetInstanceInfo(instance_name, hvparams=hvparams) is None:
741
      raise errors.HypervisorError("Instance not running, cannot migrate")
742

    
743
    cmd = self._GetCommand(hvparams=hvparams)
744

    
745
    if (cmd == constants.XEN_CMD_XM and
746
        not _ping_fn(target, port, live_port_needed=True)):
747
      raise errors.HypervisorError("Remote host %s not listening on port"
748
                                   " %s, cannot migrate" % (target, port))
749

    
750
    args = ["migrate"]
751

    
752
    if cmd == constants.XEN_CMD_XM:
753
      args.extend(["-p", "%d" % port])
754
      if live:
755
        args.append("-l")
756

    
757
    elif cmd == constants.XEN_CMD_XL:
758
      args.extend([
759
        "-s", constants.XL_SSH_CMD % cluster_name,
760
        "-C", self._ConfigFileName(instance_name),
761
        ])
762

    
763
    else:
764
      raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
765

    
766
    args.extend([instance_name, target])
767

    
768
    result = self._RunXen(args, hvparams=hvparams)
769
    if result.failed:
770
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
771
                                   (instance_name, result.output))
772

    
773
  def FinalizeMigrationSource(self, instance, success, live):
774
    """Finalize the instance migration on the source node.
775

776
    @type instance: L{objects.Instance}
777
    @param instance: the instance that was migrated
778
    @type success: bool
779
    @param success: whether the migration succeeded or not
780
    @type live: bool
781
    @param live: whether the user requested a live migration or not
782

783
    """
784
    # pylint: disable=W0613
785
    if success:
786
      # remove old xen file after migration succeeded
787
      try:
788
        self._RemoveConfigFile(instance.name)
789
      except EnvironmentError:
790
        logging.exception("Failure while removing instance config file")
791

    
792
  def GetMigrationStatus(self, instance):
793
    """Get the migration status
794

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

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

806
    """
807
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
808

    
809
  @classmethod
810
  def PowercycleNode(cls):
811
    """Xen-specific powercycle.
812

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

820
    """
821
    try:
822
      cls.LinuxPowercycle()
823
    finally:
824
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
825

    
826
  def _CheckToolstack(self, xen_cmd):
827
    """Check whether the given toolstack is available on the node.
828

829
    @type xen_cmd: string
830
    @param xen_cmd: xen command (e.g. 'xm' or 'xl')
831

832
    """
833
    binary_found = self._CheckToolstackBinary(xen_cmd)
834
    if not binary_found:
835
      raise errors.HypervisorError("No '%s' binary found on node." % xen_cmd)
836
    elif xen_cmd == constants.XEN_CMD_XL:
837
      if not self._CheckToolstackXlConfigured():
838
        raise errors.HypervisorError("Toolstack '%s' is not enabled on this"
839
                                     "node." % xen_cmd)
840

    
841
  def _CheckToolstackBinary(self, xen_cmd):
842
    """Checks whether the xen command's binary is found on the machine.
843

844
    """
845
    if xen_cmd not in constants.KNOWN_XEN_COMMANDS:
846
      raise errors.HypervisorError("Unknown xen command '%s'." % xen_cmd)
847
    result = self._run_cmd_fn(["which", xen_cmd])
848
    return not result.failed
849

    
850
  def _CheckToolstackXlConfigured(self):
851
    """Checks whether xl is enabled on an xl-capable node.
852

853
    @rtype: bool
854
    @returns: C{True} if 'xl' is enabled, C{False} otherwise
855

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

    
868

    
869
class XenPvmHypervisor(XenHypervisor):
870
  """Xen PVM hypervisor interface"""
871

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

    
894
  def _GetConfig(self, instance, startup_memory, block_devices):
895
    """Write the Xen config file for the instance.
896

897
    """
898
    hvp = instance.hvparams
899
    config = StringIO()
900
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
901

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

    
913
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
914
      if bootloader_args:
915
        config.write("bootargs = '%s'\n" % bootloader_args)
916
    else:
917
      # kernel handling
918
      kpath = hvp[constants.HV_KERNEL_PATH]
919
      config.write("kernel = '%s'\n" % kpath)
920

    
921
      # initrd handling
922
      initrd_path = hvp[constants.HV_INITRD_PATH]
923
      if initrd_path:
924
        config.write("ramdisk = '%s'\n" % initrd_path)
925

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

    
940
    config.write("name = '%s'\n" % instance.name)
941

    
942
    vif_data = []
943
    for nic in instance.nics:
944
      nic_str = "mac=%s" % (nic.mac)
945
      ip = getattr(nic, "ip", None)
946
      if ip is not None:
947
        nic_str += ", ip=%s" % ip
948
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
949
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
950
      vif_data.append("'%s'" % nic_str)
951

    
952
    disk_data = \
953
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
954

    
955
    config.write("vif = [%s]\n" % ",".join(vif_data))
956
    config.write("disk = [%s]\n" % ",".join(disk_data))
957

    
958
    if hvp[constants.HV_ROOT_PATH]:
959
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
960
    config.write("on_poweroff = 'destroy'\n")
961
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
962
      config.write("on_reboot = 'restart'\n")
963
    else:
964
      config.write("on_reboot = 'destroy'\n")
965
    config.write("on_crash = 'restart'\n")
966
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
967

    
968
    return config.getvalue()
969

    
970

    
971
class XenHvmHypervisor(XenHypervisor):
972
  """Xen HVM hypervisor interface"""
973

    
974
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
975
    pathutils.VNC_PASSWORD_FILE,
976
    ]
977
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
978
    pathutils.VNC_PASSWORD_FILE,
979
    ]
980

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

    
1018
  def _GetConfig(self, instance, startup_memory, block_devices):
1019
    """Create a Xen 3.1 HVM config file.
1020

1021
    """
1022
    hvp = instance.hvparams
1023

    
1024
    config = StringIO()
1025

    
1026
    # kernel handling
1027
    kpath = hvp[constants.HV_KERNEL_PATH]
1028
    config.write("kernel = '%s'\n" % kpath)
1029

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

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

    
1065
    if instance.network_port > constants.VNC_BASE_PORT:
1066
      display = instance.network_port - constants.VNC_BASE_PORT
1067
      config.write("vncdisplay = %s\n" % display)
1068
      config.write("vncunused = 0\n")
1069
    else:
1070
      config.write("# vncdisplay = 1\n")
1071
      config.write("vncunused = 1\n")
1072

    
1073
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
1074
    try:
1075
      password = utils.ReadFile(vnc_pwd_file)
1076
    except EnvironmentError, err:
1077
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1078
                                   (vnc_pwd_file, err))
1079

    
1080
    config.write("vncpasswd = '%s'\n" % password.rstrip())
1081

    
1082
    config.write("serial = 'pty'\n")
1083
    if hvp[constants.HV_USE_LOCALTIME]:
1084
      config.write("localtime = 1\n")
1085

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

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

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

    
1115
    disk_data = \
1116
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1117

    
1118
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1119
    if iso_path:
1120
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
1121
      disk_data.append(iso)
1122

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

    
1137
    return config.getvalue()