Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 36bebc53

History | View | Annotate | Download (33.5 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 _RunXmList(fn, xmllist_errors):
86
  """Helper function for L{_GetInstanceList} to run "xm list".
87

88
  @type fn: callable
89
  @param fn: Function returning result of running C{xm list}
90
  @type xmllist_errors: list
91
  @param xmllist_errors: Error list
92
  @rtype: list
93

94
  """
95
  result = fn()
96
  if result.failed:
97
    logging.error("xm list failed (%s): %s", result.fail_reason,
98
                  result.output)
99
    xmllist_errors.append(result)
100
    raise utils.RetryAgain()
101

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

    
105

    
106
def _ParseXmList(lines, include_node):
107
  """Parses the output of C{xm list}.
108

109
  @type lines: list
110
  @param lines: Output lines of C{xm list}
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 output of xm 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 output of xm 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{_RunXmList} and L{_ParseXmList} for parameter details.
148

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

    
158
      errmsg = ("xm list failed, timeout exceeded (%s): %s" %
159
                (xmlist_result.fail_reason, xmlist_result.output))
160
    else:
161
      errmsg = "xm list failed"
162

    
163
    raise errors.HypervisorError(errmsg)
164

    
165
  return _ParseXmList(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, fn):
228
  """Updates node information from L{_ParseNodeInfo} with instance info.
229

230
  @type info: dict
231
  @param info: Result from L{_ParseNodeInfo}
232
  @type fn: callable
233
  @param fn: Function returning result of running C{xm list}
234
  @rtype: dict
235

236
  """
237
  total_instmem = 0
238

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

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

    
263

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

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

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

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

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

    
283
  disk_data = []
284

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

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

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

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

    
300
  return disk_data
301

    
302

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

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

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

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

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

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

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

    
336
    self._cmd = _cmd
337

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

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

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

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

    
358
    return cmd
359

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

363
    @see: L{utils.process.RunCmd}
364

365
    """
366
    cmd = [self._GetCommand()]
367
    cmd.extend(args)
368

    
369
    return self._run_cmd_fn(cmd)
370

    
371
  def _ConfigFileName(self, instance_name):
372
    """Get the config file name for an instance.
373

374
    @param instance_name: instance name
375
    @type instance_name: str
376
    @return: fully qualified path to instance config file
377
    @rtype: str
378

379
    """
380
    return utils.PathJoin(self._cfgdir, instance_name)
381

    
382
  @classmethod
383
  def _GetConfig(cls, instance, startup_memory, block_devices):
384
    """Build Xen configuration for an instance.
385

386
    """
387
    raise NotImplementedError
388

    
389
  def _WriteConfigFile(self, instance_name, data):
390
    """Write the Xen config file for the instance.
391

392
    This version of the function just writes the config file from static data.
393

394
    """
395
    # just in case it exists
396
    utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
397

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

    
405
  def _ReadConfigFile(self, instance_name):
406
    """Returns the contents of the instance config file.
407

408
    """
409
    filename = self._ConfigFileName(instance_name)
410

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

    
416
    return file_content
417

    
418
  def _RemoveConfigFile(self, instance_name):
419
    """Remove the xen configuration file.
420

421
    """
422
    utils.RemoveFile(self._ConfigFileName(instance_name))
423

    
424
  def _StashConfigFile(self, instance_name):
425
    """Move the Xen config file to the log directory and return its new path.
426

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

    
435
  def _GetInstanceList(self, include_node):
436
    """Wrapper around module level L{_GetInstanceList}.
437

438
    """
439
    return _GetInstanceList(lambda: self._RunXen(["list"]), include_node)
440

    
441
  def ListInstances(self):
442
    """Get the list of running instances.
443

444
    """
445
    instance_list = self._GetInstanceList(False)
446
    names = [info[0] for info in instance_list]
447
    return names
448

    
449
  def GetInstanceInfo(self, instance_name):
450
    """Get instance properties.
451

452
    @param instance_name: the instance name
453

454
    @return: tuple (name, id, memory, vcpus, stat, times)
455

456
    """
457
    instance_list = self._GetInstanceList(instance_name == _DOM0_NAME)
458
    result = None
459
    for data in instance_list:
460
      if data[0] == instance_name:
461
        result = data
462
        break
463
    return result
464

    
465
  def GetAllInstancesInfo(self):
466
    """Get properties of all instances.
467

468
    @return: list of tuples (name, id, memory, vcpus, stat, times)
469

470
    """
471
    return self._GetInstanceList(False)
472

    
473
  def _MakeConfigFile(self, instance, startup_memory, block_devices):
474
    """Gather configuration details and write to disk.
475

476
    See L{_GetConfig} for arguments.
477

478
    """
479
    buf = StringIO()
480
    buf.write("# Automatically generated by Ganeti. Do not edit!\n")
481
    buf.write("\n")
482
    buf.write(self._GetConfig(instance, startup_memory, block_devices))
483
    buf.write("\n")
484

    
485
    self._WriteConfigFile(instance.name, buf.getvalue())
486

    
487
  def StartInstance(self, instance, block_devices, startup_paused):
488
    """Start an instance.
489

490
    """
491
    startup_memory = self._InstanceStartupMemory(instance)
492

    
493
    self._MakeConfigFile(instance, startup_memory, block_devices)
494

    
495
    cmd = ["create"]
496
    if startup_paused:
497
      cmd.append("-p")
498
    cmd.append(self._ConfigFileName(instance.name))
499

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

    
510
  def StopInstance(self, instance, force=False, retry=False, name=None):
511
    """Stop an instance.
512

513
    """
514
    if name is None:
515
      name = instance.name
516

    
517
    return self._StopInstance(name, force)
518

    
519
  def _StopInstance(self, name, force):
520
    """Stop an instance.
521

522
    """
523
    if force:
524
      action = "destroy"
525
    else:
526
      action = "shutdown"
527

    
528
    result = self._RunXen([action, name])
529
    if result.failed:
530
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
531
                                   (name, result.fail_reason, result.output))
532

    
533
    # Remove configuration file if stopping/starting instance was successful
534
    self._RemoveConfigFile(name)
535

    
536
  def RebootInstance(self, instance):
537
    """Reboot an instance.
538

539
    """
540
    ini_info = self.GetInstanceInfo(instance.name)
541

    
542
    if ini_info is None:
543
      raise errors.HypervisorError("Failed to reboot instance %s,"
544
                                   " not running" % instance.name)
545

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

    
552
    def _CheckInstance():
553
      new_info = self.GetInstanceInfo(instance.name)
554

    
555
      # check if the domain ID has changed or the run time has decreased
556
      if (new_info is not None and
557
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
558
        return
559

    
560
      raise utils.RetryAgain()
561

    
562
    try:
563
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
564
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
565
    except utils.RetryTimeout:
566
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
567
                                   " did not reboot in the expected interval" %
568
                                   (instance.name, ))
569

    
570
  def BalloonInstanceMemory(self, instance, mem):
571
    """Balloon an instance memory to a certain value.
572

573
    @type instance: L{objects.Instance}
574
    @param instance: instance to be accepted
575
    @type mem: int
576
    @param mem: actual memory size to use for instance runtime
577

578
    """
579
    result = self._RunXen(["mem-set", instance.name, mem])
580
    if result.failed:
581
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
582
                                   (instance.name, result.fail_reason,
583
                                    result.output))
584

    
585
    # Update configuration file
586
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
587
    cmd.append(self._ConfigFileName(instance.name))
588

    
589
    result = utils.RunCmd(cmd)
590
    if result.failed:
591
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
592
                                   (instance.name, result.fail_reason,
593
                                    result.output))
594

    
595
  def GetNodeInfo(self):
596
    """Return information about the node.
597

598
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
599

600
    """
601
    result = self._RunXen(["info"])
602
    if result.failed:
603
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
604
                    result.output)
605
      return None
606

    
607
    return _GetNodeInfo(result.stdout, self._GetInstanceList)
608

    
609
  @classmethod
610
  def GetInstanceConsole(cls, instance, hvparams, beparams):
611
    """Return a command for connecting to the console of an instance.
612

613
    """
614
    return objects.InstanceConsole(instance=instance.name,
615
                                   kind=constants.CONS_SSH,
616
                                   host=instance.primary_node,
617
                                   user=constants.SSH_CONSOLE_USER,
618
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
619
                                            constants.XEN_CMD, instance.name])
620

    
621
  def Verify(self):
622
    """Verify the hypervisor.
623

624
    For Xen, this verifies that the xend process is running.
625

626
    @return: Problem description if something is wrong, C{None} otherwise
627

628
    """
629
    result = self._RunXen(["info"])
630
    if result.failed:
631
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
632

    
633
    return None
634

    
635
  def MigrationInfo(self, instance):
636
    """Get instance information to perform a migration.
637

638
    @type instance: L{objects.Instance}
639
    @param instance: instance to be migrated
640
    @rtype: string
641
    @return: content of the xen config file
642

643
    """
644
    return self._ReadConfigFile(instance.name)
645

    
646
  def AcceptInstance(self, instance, info, target):
647
    """Prepare to accept an instance.
648

649
    @type instance: L{objects.Instance}
650
    @param instance: instance to be accepted
651
    @type info: string
652
    @param info: content of the xen config file on the source node
653
    @type target: string
654
    @param target: target host (usually ip), on this node
655

656
    """
657
    pass
658

    
659
  def FinalizeMigrationDst(self, instance, info, success):
660
    """Finalize an instance migration.
661

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

665
    @type instance: L{objects.Instance}
666
    @param instance: instance whose migration is being finalized
667
    @type info: string
668
    @param info: content of the xen config file on the source node
669
    @type success: boolean
670
    @param success: whether the migration was a success or a failure
671

672
    """
673
    if success:
674
      self._WriteConfigFile(instance.name, info)
675

    
676
  def MigrateInstance(self, instance, target, live):
677
    """Migrate an instance to a target node.
678

679
    The migration will not be attempted if the instance is not
680
    currently running.
681

682
    @type instance: L{objects.Instance}
683
    @param instance: the instance to be migrated
684
    @type target: string
685
    @param target: ip address of the target node
686
    @type live: boolean
687
    @param live: perform a live migration
688

689
    """
690
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
691

    
692
    # TODO: Pass cluster name via RPC
693
    cluster_name = ssconf.SimpleStore().GetClusterName()
694

    
695
    return self._MigrateInstance(cluster_name, instance.name, target, port,
696
                                 live)
697

    
698
  def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
699
                       _ping_fn=netutils.TcpPing):
700
    """Migrate an instance to a target node.
701

702
    @see: L{MigrateInstance} for details
703

704
    """
705
    if self.GetInstanceInfo(instance_name) is None:
706
      raise errors.HypervisorError("Instance not running, cannot migrate")
707

    
708
    cmd = self._GetCommand()
709

    
710
    if (cmd == constants.XEN_CMD_XM and
711
        not _ping_fn(target, port, live_port_needed=True)):
712
      raise errors.HypervisorError("Remote host %s not listening on port"
713
                                   " %s, cannot migrate" % (target, port))
714

    
715
    args = ["migrate"]
716

    
717
    if cmd == constants.XEN_CMD_XM:
718
      args.extend(["-p", "%d" % port])
719
      if live:
720
        args.append("-l")
721

    
722
    elif cmd == constants.XEN_CMD_XL:
723
      args.extend([
724
        "-s", constants.XL_SSH_CMD % cluster_name,
725
        "-C", self._ConfigFileName(instance_name),
726
        ])
727

    
728
    else:
729
      raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
730

    
731
    args.extend([instance_name, target])
732

    
733
    result = self._RunXen(args)
734
    if result.failed:
735
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
736
                                   (instance_name, result.output))
737

    
738
  def FinalizeMigrationSource(self, instance, success, live):
739
    """Finalize the instance migration on the source node.
740

741
    @type instance: L{objects.Instance}
742
    @param instance: the instance that was migrated
743
    @type success: bool
744
    @param success: whether the migration succeeded or not
745
    @type live: bool
746
    @param live: whether the user requested a live migration or not
747

748
    """
749
    # pylint: disable=W0613
750
    if success:
751
      # remove old xen file after migration succeeded
752
      try:
753
        self._RemoveConfigFile(instance.name)
754
      except EnvironmentError:
755
        logging.exception("Failure while removing instance config file")
756

    
757
  def GetMigrationStatus(self, instance):
758
    """Get the migration status
759

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

764
    @type instance: L{objects.Instance}
765
    @param instance: the instance that is being migrated
766
    @rtype: L{objects.MigrationStatus}
767
    @return: the status of the current migration (one of
768
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
769
             progress info that can be retrieved from the hypervisor
770

771
    """
772
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
773

    
774
  @classmethod
775
  def PowercycleNode(cls):
776
    """Xen-specific powercycle.
777

778
    This first does a Linux reboot (which triggers automatically a Xen
779
    reboot), and if that fails it tries to do a Xen reboot. The reason
780
    we don't try a Xen reboot first is that the xen reboot launches an
781
    external command which connects to the Xen hypervisor, and that
782
    won't work in case the root filesystem is broken and/or the xend
783
    daemon is not working.
784

785
    """
786
    try:
787
      cls.LinuxPowercycle()
788
    finally:
789
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
790

    
791

    
792
class XenPvmHypervisor(XenHypervisor):
793
  """Xen PVM hypervisor interface"""
794

    
795
  PARAMETERS = {
796
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
797
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
798
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
799
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
800
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
801
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
802
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
803
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
804
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
805
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
806
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
807
    constants.HV_REBOOT_BEHAVIOR:
808
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
809
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
810
    constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
811
    constants.HV_CPU_WEIGHT:
812
      (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
813
    constants.HV_XEN_CMD:
814
      hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
815
    }
816

    
817
  def _GetConfig(self, instance, startup_memory, block_devices):
818
    """Write the Xen config file for the instance.
819

820
    """
821
    hvp = instance.hvparams
822
    config = StringIO()
823
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
824

    
825
    # if bootloader is True, use bootloader instead of kernel and ramdisk
826
    # parameters.
827
    if hvp[constants.HV_USE_BOOTLOADER]:
828
      # bootloader handling
829
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
830
      if bootloader_path:
831
        config.write("bootloader = '%s'\n" % bootloader_path)
832
      else:
833
        raise errors.HypervisorError("Bootloader enabled, but missing"
834
                                     " bootloader path")
835

    
836
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
837
      if bootloader_args:
838
        config.write("bootargs = '%s'\n" % bootloader_args)
839
    else:
840
      # kernel handling
841
      kpath = hvp[constants.HV_KERNEL_PATH]
842
      config.write("kernel = '%s'\n" % kpath)
843

    
844
      # initrd handling
845
      initrd_path = hvp[constants.HV_INITRD_PATH]
846
      if initrd_path:
847
        config.write("ramdisk = '%s'\n" % initrd_path)
848

    
849
    # rest of the settings
850
    config.write("memory = %d\n" % startup_memory)
851
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
852
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
853
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
854
    if cpu_pinning:
855
      config.write("%s\n" % cpu_pinning)
856
    cpu_cap = hvp[constants.HV_CPU_CAP]
857
    if cpu_cap:
858
      config.write("cpu_cap=%d\n" % cpu_cap)
859
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
860
    if cpu_weight:
861
      config.write("cpu_weight=%d\n" % cpu_weight)
862

    
863
    config.write("name = '%s'\n" % instance.name)
864

    
865
    vif_data = []
866
    for nic in instance.nics:
867
      nic_str = "mac=%s" % (nic.mac)
868
      ip = getattr(nic, "ip", None)
869
      if ip is not None:
870
        nic_str += ", ip=%s" % ip
871
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
872
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
873
      vif_data.append("'%s'" % nic_str)
874

    
875
    disk_data = \
876
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
877

    
878
    config.write("vif = [%s]\n" % ",".join(vif_data))
879
    config.write("disk = [%s]\n" % ",".join(disk_data))
880

    
881
    if hvp[constants.HV_ROOT_PATH]:
882
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
883
    config.write("on_poweroff = 'destroy'\n")
884
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
885
      config.write("on_reboot = 'restart'\n")
886
    else:
887
      config.write("on_reboot = 'destroy'\n")
888
    config.write("on_crash = 'restart'\n")
889
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
890

    
891
    return config.getvalue()
892

    
893

    
894
class XenHvmHypervisor(XenHypervisor):
895
  """Xen HVM hypervisor interface"""
896

    
897
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
898
    pathutils.VNC_PASSWORD_FILE,
899
    ]
900
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
901
    pathutils.VNC_PASSWORD_FILE,
902
    ]
903

    
904
  PARAMETERS = {
905
    constants.HV_ACPI: hv_base.NO_CHECK,
906
    constants.HV_BOOT_ORDER: (True, ) +
907
      (lambda x: x and len(x.strip("acdn")) == 0,
908
       "Invalid boot order specified, must be one or more of [acdn]",
909
       None, None),
910
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
911
    constants.HV_DISK_TYPE:
912
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
913
    constants.HV_NIC_TYPE:
914
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
915
    constants.HV_PAE: hv_base.NO_CHECK,
916
    constants.HV_VNC_BIND_ADDRESS:
917
      (False, netutils.IP4Address.IsValid,
918
       "VNC bind address is not a valid IP address", None, None),
919
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
920
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
921
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
922
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
923
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
924
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
925
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
926
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
927
    # Add PCI passthrough
928
    constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
929
    constants.HV_REBOOT_BEHAVIOR:
930
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
931
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
932
    constants.HV_CPU_CAP: hv_base.NO_CHECK,
933
    constants.HV_CPU_WEIGHT:
934
      (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
935
    constants.HV_VIF_TYPE:
936
      hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES),
937
    constants.HV_XEN_CMD:
938
      hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
939
    }
940

    
941
  def _GetConfig(self, instance, startup_memory, block_devices):
942
    """Create a Xen 3.1 HVM config file.
943

944
    """
945
    hvp = instance.hvparams
946

    
947
    config = StringIO()
948

    
949
    # kernel handling
950
    kpath = hvp[constants.HV_KERNEL_PATH]
951
    config.write("kernel = '%s'\n" % kpath)
952

    
953
    config.write("builder = 'hvm'\n")
954
    config.write("memory = %d\n" % startup_memory)
955
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
956
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
957
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
958
    if cpu_pinning:
959
      config.write("%s\n" % cpu_pinning)
960
    cpu_cap = hvp[constants.HV_CPU_CAP]
961
    if cpu_cap:
962
      config.write("cpu_cap=%d\n" % cpu_cap)
963
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
964
    if cpu_weight:
965
      config.write("cpu_weight=%d\n" % cpu_weight)
966

    
967
    config.write("name = '%s'\n" % instance.name)
968
    if hvp[constants.HV_PAE]:
969
      config.write("pae = 1\n")
970
    else:
971
      config.write("pae = 0\n")
972
    if hvp[constants.HV_ACPI]:
973
      config.write("acpi = 1\n")
974
    else:
975
      config.write("acpi = 0\n")
976
    config.write("apic = 1\n")
977
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
978
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
979
    config.write("sdl = 0\n")
980
    config.write("usb = 1\n")
981
    config.write("usbdevice = 'tablet'\n")
982
    config.write("vnc = 1\n")
983
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
984
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
985
    else:
986
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
987

    
988
    if instance.network_port > constants.VNC_BASE_PORT:
989
      display = instance.network_port - constants.VNC_BASE_PORT
990
      config.write("vncdisplay = %s\n" % display)
991
      config.write("vncunused = 0\n")
992
    else:
993
      config.write("# vncdisplay = 1\n")
994
      config.write("vncunused = 1\n")
995

    
996
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
997
    try:
998
      password = utils.ReadFile(vnc_pwd_file)
999
    except EnvironmentError, err:
1000
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1001
                                   (vnc_pwd_file, err))
1002

    
1003
    config.write("vncpasswd = '%s'\n" % password.rstrip())
1004

    
1005
    config.write("serial = 'pty'\n")
1006
    if hvp[constants.HV_USE_LOCALTIME]:
1007
      config.write("localtime = 1\n")
1008

    
1009
    vif_data = []
1010
    # Note: what is called 'nic_type' here, is used as value for the xen nic
1011
    # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1012
    # the 'vif_type' to avoid a clash of notation.
1013
    nic_type = hvp[constants.HV_NIC_TYPE]
1014

    
1015
    if nic_type is None:
1016
      vif_type_str = ""
1017
      if hvp[constants.HV_VIF_TYPE]:
1018
        vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
1019
      # ensure old instances don't change
1020
      nic_type_str = vif_type_str
1021
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
1022
      nic_type_str = ", type=paravirtualized"
1023
    else:
1024
      # parameter 'model' is only valid with type 'ioemu'
1025
      nic_type_str = ", model=%s, type=%s" % \
1026
        (nic_type, constants.HT_HVM_VIF_IOEMU)
1027
    for nic in instance.nics:
1028
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1029
      ip = getattr(nic, "ip", None)
1030
      if ip is not None:
1031
        nic_str += ", ip=%s" % ip
1032
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1033
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1034
      vif_data.append("'%s'" % nic_str)
1035

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

    
1038
    disk_data = \
1039
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1040

    
1041
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1042
    if iso_path:
1043
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
1044
      disk_data.append(iso)
1045

    
1046
    config.write("disk = [%s]\n" % (",".join(disk_data)))
1047
    # Add PCI passthrough
1048
    pci_pass_arr = []
1049
    pci_pass = hvp[constants.HV_PASSTHROUGH]
1050
    if pci_pass:
1051
      pci_pass_arr = pci_pass.split(";")
1052
      config.write("pci = %s\n" % pci_pass_arr)
1053
    config.write("on_poweroff = 'destroy'\n")
1054
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1055
      config.write("on_reboot = 'restart'\n")
1056
    else:
1057
      config.write("on_reboot = 'destroy'\n")
1058
    config.write("on_crash = 'restart'\n")
1059

    
1060
    return config.getvalue()