Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 75bf3149

History | View | Annotate | Download (36.4 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 _ParseXmList(lines, include_node):
108
  """Parses the output of C{xm list}.
109

110
  @type lines: list
111
  @param lines: Output lines of C{xm list}
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 output of xm 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 output of xm 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{_ParseXmList} 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 _ParseXmList(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 returning result of running C{xm 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 run 'xm info' (%s): %s", result.fail_reason,
616
                    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)
725

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

730
    @see: L{MigrateInstance} for details
731

732
    """
733
    if self.GetInstanceInfo(instance_name) is None:
734
      raise errors.HypervisorError("Instance not running, cannot migrate")
735

    
736
    cmd = self._GetCommand()
737

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

    
743
    args = ["migrate"]
744

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

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

    
756
    else:
757
      raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
758

    
759
    args.extend([instance_name, target])
760

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

    
766
  def FinalizeMigrationSource(self, instance, success, live):
767
    """Finalize the instance migration on the source node.
768

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

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

    
785
  def GetMigrationStatus(self, instance):
786
    """Get the migration status
787

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

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

799
    """
800
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
801

    
802
  @classmethod
803
  def PowercycleNode(cls):
804
    """Xen-specific powercycle.
805

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

813
    """
814
    try:
815
      cls.LinuxPowercycle()
816
    finally:
817
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
818

    
819
  def _CheckToolstack(self, xen_cmd):
820
    """Check whether the given toolstack is available on the node.
821

822
    @type xen_cmd: string
823
    @param xen_cmd: xen command (e.g. 'xm' or 'xl')
824

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

    
834
  def _CheckToolstackBinary(self, xen_cmd):
835
    """Checks whether the xen command's binary is found on the machine.
836

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

    
843
  def _CheckToolstackXlConfigured(self):
844
    """Checks whether xl is enabled on an xl-capable node.
845

846
    @rtype: bool
847
    @returns: C{True} if 'xl' is enabled, C{False} otherwise
848

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

    
861

    
862
class XenPvmHypervisor(XenHypervisor):
863
  """Xen PVM hypervisor interface"""
864

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

    
887
  def _GetConfig(self, instance, startup_memory, block_devices):
888
    """Write the Xen config file for the instance.
889

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

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

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

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

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

    
933
    config.write("name = '%s'\n" % instance.name)
934

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

    
945
    disk_data = \
946
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
947

    
948
    config.write("vif = [%s]\n" % ",".join(vif_data))
949
    config.write("disk = [%s]\n" % ",".join(disk_data))
950

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

    
961
    return config.getvalue()
962

    
963

    
964
class XenHvmHypervisor(XenHypervisor):
965
  """Xen HVM hypervisor interface"""
966

    
967
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
968
    pathutils.VNC_PASSWORD_FILE,
969
    ]
970
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
971
    pathutils.VNC_PASSWORD_FILE,
972
    ]
973

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

    
1011
  def _GetConfig(self, instance, startup_memory, block_devices):
1012
    """Create a Xen 3.1 HVM config file.
1013

1014
    """
1015
    hvp = instance.hvparams
1016

    
1017
    config = StringIO()
1018

    
1019
    # kernel handling
1020
    kpath = hvp[constants.HV_KERNEL_PATH]
1021
    config.write("kernel = '%s'\n" % kpath)
1022

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

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

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

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

    
1073
    config.write("vncpasswd = '%s'\n" % password.rstrip())
1074

    
1075
    config.write("serial = 'pty'\n")
1076
    if hvp[constants.HV_USE_LOCALTIME]:
1077
      config.write("localtime = 1\n")
1078

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

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

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

    
1108
    disk_data = \
1109
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1110

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

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

    
1130
    return config.getvalue()