Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 2609da63

History | View | Annotate | Download (36.6 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, fn):
229
  """Updates node information from L{_ParseNodeInfo} with instance info.
230

231
  @type info: dict
232
  @param info: Result from L{_ParseNodeInfo}
233
  @type fn: callable
234
  @param fn: Function retrieving the instance list
235
  @rtype: dict
236

237
  """
238
  total_instmem = 0
239

    
240
  for (name, _, mem, vcpus, _, _) in fn(True):
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, fn):
259
  """Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}.
260

261
  """
262
  return _MergeInstanceInfo(_ParseNodeInfo(info), fn)
263

    
264

    
265
def _GetConfigFileDiskData(block_devices, blockdev_prefix,
266
                           _letters=_DISK_LETTERS):
267
  """Get disk directives for Xen config file.
268

269
  This method builds the xen config disk directive according to the
270
  given disk_template and block_devices.
271

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

278
  @return: string containing disk directive for xen instance config file
279

280
  """
281
  if len(block_devices) > len(_letters):
282
    raise errors.HypervisorError("Too many disks")
283

    
284
  disk_data = []
285

    
286
  for sd_suffix, (cfdev, dev_path) in zip(_letters, block_devices):
287
    sd_name = blockdev_prefix + sd_suffix
288

    
289
    if cfdev.mode == constants.DISK_RDWR:
290
      mode = "w"
291
    else:
292
      mode = "r"
293

    
294
    if cfdev.dev_type == constants.LD_FILE:
295
      driver = _FILE_DRIVER_MAP[cfdev.physical_id[0]]
296
    else:
297
      driver = "phy"
298

    
299
    disk_data.append("'%s:%s,%s,%s'" % (driver, dev_path, sd_name, mode))
300

    
301
  return disk_data
302

    
303

    
304
class XenHypervisor(hv_base.BaseHypervisor):
305
  """Xen generic hypervisor interface
306

307
  This is the Xen base class used for both Xen PVM and HVM. It contains
308
  all the functionality that is identical for both.
309

310
  """
311
  CAN_MIGRATE = True
312
  REBOOT_RETRY_COUNT = 60
313
  REBOOT_RETRY_INTERVAL = 10
314

    
315
  ANCILLARY_FILES = [
316
    XEND_CONFIG_FILE,
317
    XL_CONFIG_FILE,
318
    VIF_BRIDGE_SCRIPT,
319
    ]
320
  ANCILLARY_FILES_OPT = [
321
    XL_CONFIG_FILE,
322
    ]
323

    
324
  def __init__(self, _cfgdir=None, _run_cmd_fn=None, _cmd=None):
325
    hv_base.BaseHypervisor.__init__(self)
326

    
327
    if _cfgdir is None:
328
      self._cfgdir = pathutils.XEN_CONFIG_DIR
329
    else:
330
      self._cfgdir = _cfgdir
331

    
332
    if _run_cmd_fn is None:
333
      self._run_cmd_fn = utils.RunCmd
334
    else:
335
      self._run_cmd_fn = _run_cmd_fn
336

    
337
    self._cmd = _cmd
338

    
339
  def _GetCommand(self, hvparams=None):
340
    """Returns Xen command to use.
341

342
    @type hvparams: dict of strings
343
    @param hvparams: hypervisor parameters
344

345
    """
346
    if self._cmd is None:
347
      if hvparams is not None:
348
        cmd = hvparams[constants.HV_XEN_CMD]
349
      else:
350
        # TODO: Remove autoconf option once retrieving the command from
351
        # the hvparams is fully implemented.
352
        cmd = constants.XEN_CMD
353
    else:
354
      cmd = self._cmd
355

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

    
359
    return cmd
360

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

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

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

    
372
    return self._run_cmd_fn(cmd)
373

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

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

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

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

389
    """
390
    raise NotImplementedError
391

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

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

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

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

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

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

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

    
419
    return file_content
420

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

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

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

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

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

441
    """
442
    return _GetInstanceList(lambda: self._RunXen(["list"], hvparams=hvparams),
443
                            include_node)
444

    
445
  def ListInstances(self, hvparams=None):
446
    """Get the list of running instances.
447

448
    """
449
    instance_list = self._GetInstanceList(False, hvparams=hvparams)
450
    names = [info[0] for info in instance_list]
451
    return names
452

    
453
  def GetInstanceInfo(self, instance_name):
454
    """Get instance properties.
455

456
    @param instance_name: the instance name
457

458
    @return: tuple (name, id, memory, vcpus, stat, times)
459

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

    
469
  def GetAllInstancesInfo(self):
470
    """Get properties of all instances.
471

472
    @return: list of tuples (name, id, memory, vcpus, stat, times)
473

474
    """
475
    return self._GetInstanceList(False)
476

    
477
  def _MakeConfigFile(self, instance, startup_memory, block_devices):
478
    """Gather configuration details and write to disk.
479

480
    See L{_GetConfig} for arguments.
481

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

    
489
    self._WriteConfigFile(instance.name, buf.getvalue())
490

    
491
  def StartInstance(self, instance, block_devices, startup_paused):
492
    """Start an instance.
493

494
    """
495
    startup_memory = self._InstanceStartupMemory(instance)
496

    
497
    self._MakeConfigFile(instance, startup_memory, block_devices)
498

    
499
    cmd = ["create"]
500
    if startup_paused:
501
      cmd.append("-p")
502
    cmd.append(self._ConfigFileName(instance.name))
503

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

    
514
  def StopInstance(self, instance, force=False, retry=False, name=None):
515
    """Stop an instance.
516

517
    """
518
    if name is None:
519
      name = instance.name
520

    
521
    return self._StopInstance(name, force, instance.hvparams)
522

    
523
  def _StopInstance(self, name, force, hvparams):
524
    """Stop an instance.
525

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

533
    """
534
    if force:
535
      action = "destroy"
536
    else:
537
      action = "shutdown"
538

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

    
544
    # Remove configuration file if stopping/starting instance was successful
545
    self._RemoveConfigFile(name)
546

    
547
  def RebootInstance(self, instance):
548
    """Reboot an instance.
549

550
    """
551
    ini_info = self.GetInstanceInfo(instance.name)
552

    
553
    if ini_info is None:
554
      raise errors.HypervisorError("Failed to reboot instance %s,"
555
                                   " not running" % instance.name)
556

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

    
563
    def _CheckInstance():
564
      new_info = self.GetInstanceInfo(instance.name)
565

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

    
571
      raise utils.RetryAgain()
572

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

    
581
  def BalloonInstanceMemory(self, instance, mem):
582
    """Balloon an instance memory to a certain value.
583

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

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

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

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

    
607
  def GetNodeInfo(self):
608
    """Return information about the node.
609

610
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
611

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

    
619
    return _GetNodeInfo(result.stdout, self._GetInstanceList)
620

    
621
  @classmethod
622
  def GetInstanceConsole(cls, instance, hvparams, beparams):
623
    """Return a command for connecting to the console of an instance.
624

625
    """
626
    return objects.InstanceConsole(instance=instance.name,
627
                                   kind=constants.CONS_SSH,
628
                                   host=instance.primary_node,
629
                                   user=constants.SSH_CONSOLE_USER,
630
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
631
                                            constants.XEN_CMD, instance.name])
632

    
633
  def Verify(self, hvparams=None):
634
    """Verify the hypervisor.
635

636
    For Xen, this verifies that the xend process is running.
637

638
    @type hvparams: dict of strings
639
    @param hvparams: hypervisor parameters to be verified against
640

641
    @return: Problem description if something is wrong, C{None} otherwise
642

643
    """
644
    if hvparams is None:
645
      return "Could not verify the hypervisor, because no hvparams were" \
646
             " provided."
647

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

    
656
    result = self._RunXen(["info"], hvparams=hvparams)
657
    if result.failed:
658
      return "Retrieving information from xen failed: %s, %s" % \
659
        (result.fail_reason, result.output)
660

    
661
    return None
662

    
663
  def MigrationInfo(self, instance):
664
    """Get instance information to perform a migration.
665

666
    @type instance: L{objects.Instance}
667
    @param instance: instance to be migrated
668
    @rtype: string
669
    @return: content of the xen config file
670

671
    """
672
    return self._ReadConfigFile(instance.name)
673

    
674
  def AcceptInstance(self, instance, info, target):
675
    """Prepare to accept an instance.
676

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

684
    """
685
    pass
686

    
687
  def FinalizeMigrationDst(self, instance, info, success):
688
    """Finalize an instance migration.
689

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

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

700
    """
701
    if success:
702
      self._WriteConfigFile(instance.name, info)
703

    
704
  def MigrateInstance(self, instance, target, live):
705
    """Migrate an instance to a target node.
706

707
    The migration will not be attempted if the instance is not
708
    currently running.
709

710
    @type instance: L{objects.Instance}
711
    @param instance: the instance to be migrated
712
    @type target: string
713
    @param target: ip address of the target node
714
    @type live: boolean
715
    @param live: perform a live migration
716

717
    """
718
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
719

    
720
    # TODO: Pass cluster name via RPC
721
    cluster_name = ssconf.SimpleStore().GetClusterName()
722

    
723
    return self._MigrateInstance(cluster_name, instance.name, target, port,
724
                                 live, instance.hvparams)
725

    
726
  def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
727
                       hvparams, _ping_fn=netutils.TcpPing):
728
    """Migrate an instance to a target node.
729

730
    @see: L{MigrateInstance} for details
731

732
    """
733
    if hvparams is None:
734
      raise errors.HypervisorError("No hvparams provided.")
735

    
736
    if self.GetInstanceInfo(instance_name, hvparams=hvparams) is None:
737
      raise errors.HypervisorError("Instance not running, cannot migrate")
738

    
739
    cmd = self._GetCommand(hvparams=hvparams)
740

    
741
    if (cmd == constants.XEN_CMD_XM and
742
        not _ping_fn(target, port, live_port_needed=True)):
743
      raise errors.HypervisorError("Remote host %s not listening on port"
744
                                   " %s, cannot migrate" % (target, port))
745

    
746
    args = ["migrate"]
747

    
748
    if cmd == constants.XEN_CMD_XM:
749
      args.extend(["-p", "%d" % port])
750
      if live:
751
        args.append("-l")
752

    
753
    elif cmd == constants.XEN_CMD_XL:
754
      args.extend([
755
        "-s", constants.XL_SSH_CMD % cluster_name,
756
        "-C", self._ConfigFileName(instance_name),
757
        ])
758

    
759
    else:
760
      raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
761

    
762
    args.extend([instance_name, target])
763

    
764
    result = self._RunXen(args, hvparams=hvparams)
765
    if result.failed:
766
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
767
                                   (instance_name, result.output))
768

    
769
  def FinalizeMigrationSource(self, instance, success, live):
770
    """Finalize the instance migration on the source node.
771

772
    @type instance: L{objects.Instance}
773
    @param instance: the instance that was migrated
774
    @type success: bool
775
    @param success: whether the migration succeeded or not
776
    @type live: bool
777
    @param live: whether the user requested a live migration or not
778

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

    
788
  def GetMigrationStatus(self, instance):
789
    """Get the migration status
790

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

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

802
    """
803
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
804

    
805
  @classmethod
806
  def PowercycleNode(cls):
807
    """Xen-specific powercycle.
808

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

816
    """
817
    try:
818
      cls.LinuxPowercycle()
819
    finally:
820
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
821

    
822
  def _CheckToolstack(self, xen_cmd):
823
    """Check whether the given toolstack is available on the node.
824

825
    @type xen_cmd: string
826
    @param xen_cmd: xen command (e.g. 'xm' or 'xl')
827

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

    
837
  def _CheckToolstackBinary(self, xen_cmd):
838
    """Checks whether the xen command's binary is found on the machine.
839

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

    
846
  def _CheckToolstackXlConfigured(self):
847
    """Checks whether xl is enabled on an xl-capable node.
848

849
    @rtype: bool
850
    @returns: C{True} if 'xl' is enabled, C{False} otherwise
851

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

    
864

    
865
class XenPvmHypervisor(XenHypervisor):
866
  """Xen PVM hypervisor interface"""
867

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

    
890
  def _GetConfig(self, instance, startup_memory, block_devices):
891
    """Write the Xen config file for the instance.
892

893
    """
894
    hvp = instance.hvparams
895
    config = StringIO()
896
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
897

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

    
909
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
910
      if bootloader_args:
911
        config.write("bootargs = '%s'\n" % bootloader_args)
912
    else:
913
      # kernel handling
914
      kpath = hvp[constants.HV_KERNEL_PATH]
915
      config.write("kernel = '%s'\n" % kpath)
916

    
917
      # initrd handling
918
      initrd_path = hvp[constants.HV_INITRD_PATH]
919
      if initrd_path:
920
        config.write("ramdisk = '%s'\n" % initrd_path)
921

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

    
936
    config.write("name = '%s'\n" % instance.name)
937

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

    
948
    disk_data = \
949
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
950

    
951
    config.write("vif = [%s]\n" % ",".join(vif_data))
952
    config.write("disk = [%s]\n" % ",".join(disk_data))
953

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

    
964
    return config.getvalue()
965

    
966

    
967
class XenHvmHypervisor(XenHypervisor):
968
  """Xen HVM hypervisor interface"""
969

    
970
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
971
    pathutils.VNC_PASSWORD_FILE,
972
    ]
973
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
974
    pathutils.VNC_PASSWORD_FILE,
975
    ]
976

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

    
1014
  def _GetConfig(self, instance, startup_memory, block_devices):
1015
    """Create a Xen 3.1 HVM config file.
1016

1017
    """
1018
    hvp = instance.hvparams
1019

    
1020
    config = StringIO()
1021

    
1022
    # kernel handling
1023
    kpath = hvp[constants.HV_KERNEL_PATH]
1024
    config.write("kernel = '%s'\n" % kpath)
1025

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

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

    
1061
    if instance.network_port > constants.VNC_BASE_PORT:
1062
      display = instance.network_port - constants.VNC_BASE_PORT
1063
      config.write("vncdisplay = %s\n" % display)
1064
      config.write("vncunused = 0\n")
1065
    else:
1066
      config.write("# vncdisplay = 1\n")
1067
      config.write("vncunused = 1\n")
1068

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

    
1076
    config.write("vncpasswd = '%s'\n" % password.rstrip())
1077

    
1078
    config.write("serial = 'pty'\n")
1079
    if hvp[constants.HV_USE_LOCALTIME]:
1080
      config.write("localtime = 1\n")
1081

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

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

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

    
1111
    disk_data = \
1112
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1113

    
1114
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1115
    if iso_path:
1116
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
1117
      disk_data.append(iso)
1118

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

    
1133
    return config.getvalue()