Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ d8784f7d

History | View | Annotate | Download (31.9 kB)

1
#
2
#
3

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

    
21

    
22
"""Xen hypervisors
23

24
"""
25

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

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

    
39

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

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

    
52

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

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

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

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

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

    
84

    
85
def _RunXmList(fn, xmllist_errors):
86
  """Helper function for L{_GetXmList} 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 _GetXmList(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.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):
339
    """Returns Xen command to use.
340

341
    """
342
    if self._cmd is None:
343
      # TODO: Make command a hypervisor parameter
344
      cmd = constants.XEN_CMD
345
    else:
346
      cmd = self._cmd
347

    
348
    if cmd not in constants.KNOWN_XEN_COMMANDS:
349
      raise errors.ProgrammerError("Unknown Xen command '%s'" % cmd)
350

    
351
    return cmd
352

    
353
  def _RunXen(self, args):
354
    """Wrapper around L{utils.RunCmd} to run Xen command.
355

356
    @see: L{utils.RunCmd}
357

358
    """
359
    cmd = [self._GetCommand()]
360
    cmd.extend(args)
361

    
362
    return self._run_cmd_fn(cmd)
363

    
364
  def _ConfigFileName(self, instance_name):
365
    """Get the config file name for an instance.
366

367
    @param instance_name: instance name
368
    @type instance_name: str
369
    @return: fully qualified path to instance config file
370
    @rtype: str
371

372
    """
373
    return utils.PathJoin(self._cfgdir, instance_name)
374

    
375
  @classmethod
376
  def _GetConfig(cls, instance, startup_memory, block_devices):
377
    """Build Xen configuration for an instance.
378

379
    """
380
    raise NotImplementedError
381

    
382
  def _WriteConfigFile(self, instance_name, data):
383
    """Write the Xen config file for the instance.
384

385
    This version of the function just writes the config file from static data.
386

387
    """
388
    # just in case it exists
389
    utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
390

    
391
    cfg_file = self._ConfigFileName(instance_name)
392
    try:
393
      utils.WriteFile(cfg_file, data=data)
394
    except EnvironmentError, err:
395
      raise errors.HypervisorError("Cannot write Xen instance configuration"
396
                                   " file %s: %s" % (cfg_file, err))
397

    
398
  def _ReadConfigFile(self, instance_name):
399
    """Returns the contents of the instance config file.
400

401
    """
402
    filename = self._ConfigFileName(instance_name)
403

    
404
    try:
405
      file_content = utils.ReadFile(filename)
406
    except EnvironmentError, err:
407
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
408

    
409
    return file_content
410

    
411
  def _RemoveConfigFile(self, instance_name):
412
    """Remove the xen configuration file.
413

414
    """
415
    utils.RemoveFile(self._ConfigFileName(instance_name))
416

    
417
  def _GetXmList(self, include_node):
418
    """Wrapper around module level L{_GetXmList}.
419

420
    """
421
    return _GetXmList(lambda: self._RunXen(["list"]), include_node)
422

    
423
  def ListInstances(self):
424
    """Get the list of running instances.
425

426
    """
427
    xm_list = self._GetXmList(False)
428
    names = [info[0] for info in xm_list]
429
    return names
430

    
431
  def GetInstanceInfo(self, instance_name):
432
    """Get instance properties.
433

434
    @param instance_name: the instance name
435

436
    @return: tuple (name, id, memory, vcpus, stat, times)
437

438
    """
439
    xm_list = self._GetXmList(instance_name == _DOM0_NAME)
440
    result = None
441
    for data in xm_list:
442
      if data[0] == instance_name:
443
        result = data
444
        break
445
    return result
446

    
447
  def GetAllInstancesInfo(self):
448
    """Get properties of all instances.
449

450
    @return: list of tuples (name, id, memory, vcpus, stat, times)
451

452
    """
453
    xm_list = self._GetXmList(False)
454
    return xm_list
455

    
456
  def _MakeConfigFile(self, instance, startup_memory, block_devices):
457
    """Gather configuration details and write to disk.
458

459
    See L{_GetConfig} for arguments.
460

461
    """
462
    buf = StringIO()
463
    buf.write("# Automatically generated by Ganeti. Do not edit!\n")
464
    buf.write("\n")
465
    buf.write(self._GetConfig(instance, startup_memory, block_devices))
466
    buf.write("\n")
467

    
468
    self._WriteConfigFile(instance.name, buf.getvalue())
469

    
470
  def StartInstance(self, instance, block_devices, startup_paused):
471
    """Start an instance.
472

473
    """
474
    startup_memory = self._InstanceStartupMemory(instance)
475

    
476
    self._MakeConfigFile(instance, startup_memory, block_devices)
477

    
478
    cmd = ["create"]
479
    if startup_paused:
480
      cmd.append("-p")
481
    cmd.append(self._ConfigFileName(instance.name))
482

    
483
    result = self._RunXen(cmd)
484
    if result.failed:
485
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
486
                                   (instance.name, result.fail_reason,
487
                                    result.output))
488

    
489
  def StopInstance(self, instance, force=False, retry=False, name=None):
490
    """Stop an instance.
491

492
    """
493
    if name is None:
494
      name = instance.name
495

    
496
    return self._StopInstance(name, force)
497

    
498
  def _StopInstance(self, name, force):
499
    """Stop an instance.
500

501
    """
502
    if force:
503
      action = "destroy"
504
    else:
505
      action = "shutdown"
506

    
507
    result = self._RunXen([action, name])
508
    if result.failed:
509
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
510
                                   (name, result.fail_reason, result.output))
511

    
512
    # Remove configuration file if stopping/starting instance was successful
513
    self._RemoveConfigFile(name)
514

    
515
  def RebootInstance(self, instance):
516
    """Reboot an instance.
517

518
    """
519
    ini_info = self.GetInstanceInfo(instance.name)
520

    
521
    if ini_info is None:
522
      raise errors.HypervisorError("Failed to reboot instance %s,"
523
                                   " not running" % instance.name)
524

    
525
    result = self._RunXen(["reboot", instance.name])
526
    if result.failed:
527
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
528
                                   (instance.name, result.fail_reason,
529
                                    result.output))
530

    
531
    def _CheckInstance():
532
      new_info = self.GetInstanceInfo(instance.name)
533

    
534
      # check if the domain ID has changed or the run time has decreased
535
      if (new_info is not None and
536
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
537
        return
538

    
539
      raise utils.RetryAgain()
540

    
541
    try:
542
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
543
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
544
    except utils.RetryTimeout:
545
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
546
                                   " did not reboot in the expected interval" %
547
                                   (instance.name, ))
548

    
549
  def BalloonInstanceMemory(self, instance, mem):
550
    """Balloon an instance memory to a certain value.
551

552
    @type instance: L{objects.Instance}
553
    @param instance: instance to be accepted
554
    @type mem: int
555
    @param mem: actual memory size to use for instance runtime
556

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

    
564
    # Update configuration file
565
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
566
    cmd.append(self._ConfigFileName(instance.name))
567

    
568
    result = utils.RunCmd(cmd)
569
    if result.failed:
570
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
571
                                   (instance.name, result.fail_reason,
572
                                    result.output))
573

    
574
  def GetNodeInfo(self):
575
    """Return information about the node.
576

577
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
578

579
    """
580
    result = self._RunXen(["info"])
581
    if result.failed:
582
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
583
                    result.output)
584
      return None
585

    
586
    return _GetNodeInfo(result.stdout, self._GetXmList)
587

    
588
  @classmethod
589
  def GetInstanceConsole(cls, instance, hvparams, beparams):
590
    """Return a command for connecting to the console of an instance.
591

592
    """
593
    return objects.InstanceConsole(instance=instance.name,
594
                                   kind=constants.CONS_SSH,
595
                                   host=instance.primary_node,
596
                                   user=constants.SSH_CONSOLE_USER,
597
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
598
                                            constants.XEN_CMD, instance.name])
599

    
600
  def Verify(self):
601
    """Verify the hypervisor.
602

603
    For Xen, this verifies that the xend process is running.
604

605
    @return: Problem description if something is wrong, C{None} otherwise
606

607
    """
608
    result = self._RunXen(["info"])
609
    if result.failed:
610
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
611

    
612
    return None
613

    
614
  def MigrationInfo(self, instance):
615
    """Get instance information to perform a migration.
616

617
    @type instance: L{objects.Instance}
618
    @param instance: instance to be migrated
619
    @rtype: string
620
    @return: content of the xen config file
621

622
    """
623
    return self._ReadConfigFile(instance.name)
624

    
625
  def AcceptInstance(self, instance, info, target):
626
    """Prepare to accept an instance.
627

628
    @type instance: L{objects.Instance}
629
    @param instance: instance to be accepted
630
    @type info: string
631
    @param info: content of the xen config file on the source node
632
    @type target: string
633
    @param target: target host (usually ip), on this node
634

635
    """
636
    pass
637

    
638
  def FinalizeMigrationDst(self, instance, info, success):
639
    """Finalize an instance migration.
640

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

644
    @type instance: L{objects.Instance}
645
    @param instance: instance whose migration is being finalized
646
    @type info: string
647
    @param info: content of the xen config file on the source node
648
    @type success: boolean
649
    @param success: whether the migration was a success or a failure
650

651
    """
652
    if success:
653
      self._WriteConfigFile(instance.name, info)
654

    
655
  def MigrateInstance(self, instance, target, live):
656
    """Migrate an instance to a target node.
657

658
    The migration will not be attempted if the instance is not
659
    currently running.
660

661
    @type instance: L{objects.Instance}
662
    @param instance: the instance to be migrated
663
    @type target: string
664
    @param target: ip address of the target node
665
    @type live: boolean
666
    @param live: perform a live migration
667

668
    """
669
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
670

    
671
    # TODO: Pass cluster name via RPC
672
    cluster_name = ssconf.SimpleStore().GetClusterName()
673

    
674
    return self._MigrateInstance(cluster_name, instance.name, target, port,
675
                                 live)
676

    
677
  def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
678
                       _ping_fn=netutils.TcpPing):
679
    """Migrate an instance to a target node.
680

681
    @see: L{MigrateInstance} for details
682

683
    """
684
    if self.GetInstanceInfo(instance_name) is None:
685
      raise errors.HypervisorError("Instance not running, cannot migrate")
686

    
687
    cmd = self._GetCommand()
688

    
689
    if (cmd == constants.XEN_CMD_XM and
690
        not _ping_fn(target, port, live_port_needed=True)):
691
      raise errors.HypervisorError("Remote host %s not listening on port"
692
                                   " %s, cannot migrate" % (target, port))
693

    
694
    args = ["migrate"]
695

    
696
    if cmd == constants.XEN_CMD_XM:
697
      args.extend(["-p", "%d" % port])
698
      if live:
699
        args.append("-l")
700

    
701
    elif cmd == constants.XEN_CMD_XL:
702
      args.extend([
703
        "-s", constants.XL_SSH_CMD % cluster_name,
704
        "-C", self._ConfigFileName(instance_name),
705
        ])
706

    
707
    else:
708
      raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
709

    
710
    args.extend([instance_name, target])
711

    
712
    result = self._RunXen(args)
713
    if result.failed:
714
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
715
                                   (instance_name, result.output))
716

    
717
  def FinalizeMigrationSource(self, instance, success, live):
718
    """Finalize the instance migration on the source node.
719

720
    @type instance: L{objects.Instance}
721
    @param instance: the instance that was migrated
722
    @type success: bool
723
    @param success: whether the migration succeeded or not
724
    @type live: bool
725
    @param live: whether the user requested a live migration or not
726

727
    """
728
    # pylint: disable=W0613
729
    if success:
730
      # remove old xen file after migration succeeded
731
      try:
732
        self._RemoveConfigFile(instance.name)
733
      except EnvironmentError:
734
        logging.exception("Failure while removing instance config file")
735

    
736
  def GetMigrationStatus(self, instance):
737
    """Get the migration status
738

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

743
    @type instance: L{objects.Instance}
744
    @param instance: the instance that is being migrated
745
    @rtype: L{objects.MigrationStatus}
746
    @return: the status of the current migration (one of
747
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
748
             progress info that can be retrieved from the hypervisor
749

750
    """
751
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
752

    
753
  @classmethod
754
  def PowercycleNode(cls):
755
    """Xen-specific powercycle.
756

757
    This first does a Linux reboot (which triggers automatically a Xen
758
    reboot), and if that fails it tries to do a Xen reboot. The reason
759
    we don't try a Xen reboot first is that the xen reboot launches an
760
    external command which connects to the Xen hypervisor, and that
761
    won't work in case the root filesystem is broken and/or the xend
762
    daemon is not working.
763

764
    """
765
    try:
766
      cls.LinuxPowercycle()
767
    finally:
768
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
769

    
770

    
771
class XenPvmHypervisor(XenHypervisor):
772
  """Xen PVM hypervisor interface"""
773

    
774
  PARAMETERS = {
775
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
776
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
777
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
778
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
779
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
780
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
781
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
782
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
783
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
784
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
785
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
786
    constants.HV_REBOOT_BEHAVIOR:
787
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
788
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
789
    constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
790
    constants.HV_CPU_WEIGHT:
791
      (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
792
    }
793

    
794
  def _GetConfig(self, instance, startup_memory, block_devices):
795
    """Write the Xen config file for the instance.
796

797
    """
798
    hvp = instance.hvparams
799
    config = StringIO()
800
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
801

    
802
    # if bootloader is True, use bootloader instead of kernel and ramdisk
803
    # parameters.
804
    if hvp[constants.HV_USE_BOOTLOADER]:
805
      # bootloader handling
806
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
807
      if bootloader_path:
808
        config.write("bootloader = '%s'\n" % bootloader_path)
809
      else:
810
        raise errors.HypervisorError("Bootloader enabled, but missing"
811
                                     " bootloader path")
812

    
813
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
814
      if bootloader_args:
815
        config.write("bootargs = '%s'\n" % bootloader_args)
816
    else:
817
      # kernel handling
818
      kpath = hvp[constants.HV_KERNEL_PATH]
819
      config.write("kernel = '%s'\n" % kpath)
820

    
821
      # initrd handling
822
      initrd_path = hvp[constants.HV_INITRD_PATH]
823
      if initrd_path:
824
        config.write("ramdisk = '%s'\n" % initrd_path)
825

    
826
    # rest of the settings
827
    config.write("memory = %d\n" % startup_memory)
828
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
829
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
830
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
831
    if cpu_pinning:
832
      config.write("%s\n" % cpu_pinning)
833
    cpu_cap = hvp[constants.HV_CPU_CAP]
834
    if cpu_cap:
835
      config.write("cpu_cap=%d\n" % cpu_cap)
836
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
837
    if cpu_weight:
838
      config.write("cpu_weight=%d\n" % cpu_weight)
839

    
840
    config.write("name = '%s'\n" % instance.name)
841

    
842
    vif_data = []
843
    for nic in instance.nics:
844
      nic_str = "mac=%s" % (nic.mac)
845
      ip = getattr(nic, "ip", None)
846
      if ip is not None:
847
        nic_str += ", ip=%s" % ip
848
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
849
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
850
      vif_data.append("'%s'" % nic_str)
851

    
852
    disk_data = \
853
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
854

    
855
    config.write("vif = [%s]\n" % ",".join(vif_data))
856
    config.write("disk = [%s]\n" % ",".join(disk_data))
857

    
858
    if hvp[constants.HV_ROOT_PATH]:
859
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
860
    config.write("on_poweroff = 'destroy'\n")
861
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
862
      config.write("on_reboot = 'restart'\n")
863
    else:
864
      config.write("on_reboot = 'destroy'\n")
865
    config.write("on_crash = 'restart'\n")
866
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
867

    
868
    return config.getvalue()
869

    
870

    
871
class XenHvmHypervisor(XenHypervisor):
872
  """Xen HVM hypervisor interface"""
873

    
874
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
875
    pathutils.VNC_PASSWORD_FILE,
876
    ]
877
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
878
    pathutils.VNC_PASSWORD_FILE,
879
    ]
880

    
881
  PARAMETERS = {
882
    constants.HV_ACPI: hv_base.NO_CHECK,
883
    constants.HV_BOOT_ORDER: (True, ) +
884
      (lambda x: x and len(x.strip("acdn")) == 0,
885
       "Invalid boot order specified, must be one or more of [acdn]",
886
       None, None),
887
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
888
    constants.HV_DISK_TYPE:
889
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
890
    constants.HV_NIC_TYPE:
891
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
892
    constants.HV_PAE: hv_base.NO_CHECK,
893
    constants.HV_VNC_BIND_ADDRESS:
894
      (False, netutils.IP4Address.IsValid,
895
       "VNC bind address is not a valid IP address", None, None),
896
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
897
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
898
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
899
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
900
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
901
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
902
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
903
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
904
    # Add PCI passthrough
905
    constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
906
    constants.HV_REBOOT_BEHAVIOR:
907
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
908
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
909
    constants.HV_CPU_CAP: hv_base.NO_CHECK,
910
    constants.HV_CPU_WEIGHT:
911
      (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
912
    }
913

    
914
  def _GetConfig(self, instance, startup_memory, block_devices):
915
    """Create a Xen 3.1 HVM config file.
916

917
    """
918
    hvp = instance.hvparams
919

    
920
    config = StringIO()
921

    
922
    # kernel handling
923
    kpath = hvp[constants.HV_KERNEL_PATH]
924
    config.write("kernel = '%s'\n" % kpath)
925

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

    
940
    config.write("name = '%s'\n" % instance.name)
941
    if hvp[constants.HV_PAE]:
942
      config.write("pae = 1\n")
943
    else:
944
      config.write("pae = 0\n")
945
    if hvp[constants.HV_ACPI]:
946
      config.write("acpi = 1\n")
947
    else:
948
      config.write("acpi = 0\n")
949
    config.write("apic = 1\n")
950
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
951
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
952
    config.write("sdl = 0\n")
953
    config.write("usb = 1\n")
954
    config.write("usbdevice = 'tablet'\n")
955
    config.write("vnc = 1\n")
956
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
957
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
958
    else:
959
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
960

    
961
    if instance.network_port > constants.VNC_BASE_PORT:
962
      display = instance.network_port - constants.VNC_BASE_PORT
963
      config.write("vncdisplay = %s\n" % display)
964
      config.write("vncunused = 0\n")
965
    else:
966
      config.write("# vncdisplay = 1\n")
967
      config.write("vncunused = 1\n")
968

    
969
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
970
    try:
971
      password = utils.ReadFile(vnc_pwd_file)
972
    except EnvironmentError, err:
973
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
974
                                   (vnc_pwd_file, err))
975

    
976
    config.write("vncpasswd = '%s'\n" % password.rstrip())
977

    
978
    config.write("serial = 'pty'\n")
979
    if hvp[constants.HV_USE_LOCALTIME]:
980
      config.write("localtime = 1\n")
981

    
982
    vif_data = []
983
    nic_type = hvp[constants.HV_NIC_TYPE]
984
    if nic_type is None:
985
      # ensure old instances don't change
986
      nic_type_str = ", type=ioemu"
987
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
988
      nic_type_str = ", type=paravirtualized"
989
    else:
990
      nic_type_str = ", model=%s, type=ioemu" % nic_type
991
    for nic in instance.nics:
992
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
993
      ip = getattr(nic, "ip", None)
994
      if ip is not None:
995
        nic_str += ", ip=%s" % ip
996
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
997
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
998
      vif_data.append("'%s'" % nic_str)
999

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

    
1002
    disk_data = \
1003
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1004

    
1005
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1006
    if iso_path:
1007
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
1008
      disk_data.append(iso)
1009

    
1010
    config.write("disk = [%s]\n" % (",".join(disk_data)))
1011
    # Add PCI passthrough
1012
    pci_pass_arr = []
1013
    pci_pass = hvp[constants.HV_PASSTHROUGH]
1014
    if pci_pass:
1015
      pci_pass_arr = pci_pass.split(";")
1016
      config.write("pci = %s\n" % pci_pass_arr)
1017
    config.write("on_poweroff = 'destroy'\n")
1018
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1019
      config.write("on_reboot = 'restart'\n")
1020
    else:
1021
      config.write("on_reboot = 'destroy'\n")
1022
    config.write("on_crash = 'restart'\n")
1023

    
1024
    return config.getvalue()