Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ bc0a2284

History | View | Annotate | Download (37.2 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Xen hypervisors
23

24
"""
25

    
26
import logging
27
import string # pylint: disable=W0402
28
from cStringIO import StringIO
29

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

    
38

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

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

    
51

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

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

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

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

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

    
83

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

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

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

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

    
105

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

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

116
  """
117
  result = []
118

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

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

    
141
  return result
142

    
143

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

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

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

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

    
163
    raise errors.HypervisorError(errmsg)
164

    
165
  return _ParseInstanceList(lines, include_node)
166

    
167

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

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

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

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

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

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

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

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

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

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

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

    
224
  return result
225

    
226

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

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

236
  """
237
  total_instmem = 0
238

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

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

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

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

    
254
  return info
255

    
256

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

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

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

    
266

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

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

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

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

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

    
286
  disk_data = []
287

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

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

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

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

    
303
  return disk_data
304

    
305

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

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

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

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

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

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

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

    
339
    self._cmd = _cmd
340

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

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

347
    """
348
    if self._cmd is None:
349
      if hvparams is None or constants.HV_XEN_CMD not in hvparams:
350
        raise errors.HypervisorError("Cannot determine xen command.")
351
      else:
352
        cmd = hvparams[constants.HV_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):
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)]
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):
439
    """Wrapper around module level L{_GetInstanceList}.
440

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

444
    """
445
    return _GetInstanceList(lambda: self._RunXen(["list"], 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)
453
    names = [info[0] for info in instance_list]
454
    return names
455

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

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

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

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

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

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

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

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

488
    See L{_GetConfig} for arguments.
489

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
580
      raise utils.RetryAgain()
581

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
670
    return None
671

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

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

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

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

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

693
    """
694
    pass
695

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

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

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

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

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

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

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

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

    
729
    return self._MigrateInstance(cluster_name, instance.name, target, port,
730
                                 live, instance.hvparams)
731

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

736
    @see: L{MigrateInstance} for details
737

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

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

    
745
    cmd = self._GetCommand(hvparams)
746

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

    
752
    args = ["migrate"]
753

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

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

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

    
768
    args.extend([instance_name, target])
769

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

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

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

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

    
794
  def GetMigrationStatus(self, instance):
795
    """Get the migration status
796

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

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

808
    """
809
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
810

    
811
  def PowercycleNode(self, hvparams=None):
812
    """Xen-specific powercycle.
813

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

821
    @type hvparams: dict of strings
822
    @param hvparams: hypervisor params to be used on this node
823

824
    """
825
    try:
826
      self.LinuxPowercycle()
827
    finally:
828
      xen_cmd = self._GetCommand(hvparams)
829
      utils.RunCmd([xen_cmd, "debug", "R"])
830

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

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

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

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

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

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

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

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

    
873

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

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

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

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

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

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

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

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

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

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

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

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

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

    
973
    return config.getvalue()
974

    
975

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

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

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

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

1026
    """
1027
    hvp = instance.hvparams
1028

    
1029
    config = StringIO()
1030

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1142
    return config.getvalue()