Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 874f6148

History | View | Annotate | Download (35.4 kB)

1
#
2
#
3

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

    
21

    
22
"""Xen hypervisors
23

24
"""
25

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

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

    
39

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

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

    
53

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

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

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

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

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

    
85

    
86
def _RunXmList(fn, xmllist_errors):
87
  """Helper function for L{_GetXmList} to run "xm list".
88

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

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

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

    
106

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

110
  @type lines: list
111
  @param lines: Output lines of C{xm list}
112
  @type include_node: boolean
113
  @param include_node: If True, return information for Dom0
114
  @return: list of tuple containing (name, id, memory, vcpus, state, time
115
    spent)
116

117
  """
118
  result = []
119

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

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

    
142
  return result
143

    
144

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

148
  See L{_RunXmList} and L{_ParseXmList} for parameter details.
149

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

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

    
164
    raise errors.HypervisorError(errmsg)
165

    
166
  return _ParseXmList(lines, include_node)
167

    
168

    
169
def _IsInstanceRunning(instance_info):
170
  return instance_info == "r-----" \
171
      or instance_info == "-b----"
172

    
173

    
174
def _IsInstanceShutdown(instance_info):
175
  return instance_info == "---s--"
176

    
177

    
178
def _ParseNodeInfo(info):
179
  """Return information about the node.
180

181
  @return: a dict with the following keys (memory values in MiB):
182
        - memory_total: the total memory size on the node
183
        - memory_free: the available memory on the node for instances
184
        - nr_cpus: total number of CPUs
185
        - nr_nodes: in a NUMA system, the number of domains
186
        - nr_sockets: the number of physical CPU sockets in the node
187
        - hv_version: the hypervisor version in the form (major, minor)
188

189
  """
190
  result = {}
191
  cores_per_socket = threads_per_core = nr_cpus = None
192
  xen_major, xen_minor = None, None
193
  memory_total = None
194
  memory_free = None
195

    
196
  for line in info.splitlines():
197
    fields = line.split(":", 1)
198

    
199
    if len(fields) < 2:
200
      continue
201

    
202
    (key, val) = map(lambda s: s.strip(), fields)
203

    
204
    # Note: in Xen 3, memory has changed to total_memory
205
    if key in ("memory", "total_memory"):
206
      memory_total = int(val)
207
    elif key == "free_memory":
208
      memory_free = int(val)
209
    elif key == "nr_cpus":
210
      nr_cpus = result["cpu_total"] = int(val)
211
    elif key == "nr_nodes":
212
      result["cpu_nodes"] = int(val)
213
    elif key == "cores_per_socket":
214
      cores_per_socket = int(val)
215
    elif key == "threads_per_core":
216
      threads_per_core = int(val)
217
    elif key == "xen_major":
218
      xen_major = int(val)
219
    elif key == "xen_minor":
220
      xen_minor = int(val)
221

    
222
  if None not in [cores_per_socket, threads_per_core, nr_cpus]:
223
    result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
224

    
225
  if memory_free is not None:
226
    result["memory_free"] = memory_free
227

    
228
  if memory_total is not None:
229
    result["memory_total"] = memory_total
230

    
231
  if not (xen_major is None or xen_minor is None):
232
    result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
233

    
234
  return result
235

    
236

    
237
def _MergeInstanceInfo(info, fn):
238
  """Updates node information from L{_ParseNodeInfo} with instance info.
239

240
  @type info: dict
241
  @param info: Result from L{_ParseNodeInfo}
242
  @type fn: callable
243
  @param fn: Function returning result of running C{xm list}
244
  @rtype: dict
245

246
  """
247
  total_instmem = 0
248

    
249
  for (name, _, mem, vcpus, _, _) in fn(True):
250
    if name == _DOM0_NAME:
251
      info["memory_dom0"] = mem
252
      info["dom0_cpus"] = vcpus
253

    
254
    # Include Dom0 in total memory usage
255
    total_instmem += mem
256

    
257
  memory_free = info.get("memory_free")
258
  memory_total = info.get("memory_total")
259

    
260
  # Calculate memory used by hypervisor
261
  if None not in [memory_total, memory_free, total_instmem]:
262
    info["memory_hv"] = memory_total - memory_free - total_instmem
263

    
264
  return info
265

    
266

    
267
def _GetNodeInfo(info, fn):
268
  """Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}.
269

270
  """
271
  return _MergeInstanceInfo(_ParseNodeInfo(info), fn)
272

    
273

    
274
def _GetConfigFileDiskData(block_devices, blockdev_prefix,
275
                           _letters=_DISK_LETTERS):
276
  """Get disk directives for Xen config file.
277

278
  This method builds the xen config disk directive according to the
279
  given disk_template and block_devices.
280

281
  @param block_devices: list of tuples (cfdev, rldev):
282
      - cfdev: dict containing ganeti config disk part
283
      - rldev: ganeti.bdev.BlockDev object
284
  @param blockdev_prefix: a string containing blockdevice prefix,
285
                          e.g. "sd" for /dev/sda
286

287
  @return: string containing disk directive for xen instance config file
288

289
  """
290
  if len(block_devices) > len(_letters):
291
    raise errors.HypervisorError("Too many disks")
292

    
293
  disk_data = []
294

    
295
  for sd_suffix, (cfdev, dev_path) in zip(_letters, block_devices):
296
    sd_name = blockdev_prefix + sd_suffix
297

    
298
    if cfdev.mode == constants.DISK_RDWR:
299
      mode = "w"
300
    else:
301
      mode = "r"
302

    
303
    if cfdev.dev_type == constants.LD_FILE:
304
      driver = _FILE_DRIVER_MAP[cfdev.physical_id[0]]
305
    else:
306
      driver = "phy"
307

    
308
    disk_data.append("'%s:%s,%s,%s'" % (driver, dev_path, sd_name, mode))
309

    
310
  return disk_data
311

    
312

    
313
class XenHypervisor(hv_base.BaseHypervisor):
314
  """Xen generic hypervisor interface
315

316
  This is the Xen base class used for both Xen PVM and HVM. It contains
317
  all the functionality that is identical for both.
318

319
  """
320
  CAN_MIGRATE = True
321
  REBOOT_RETRY_COUNT = 60
322
  REBOOT_RETRY_INTERVAL = 10
323

    
324
  ANCILLARY_FILES = [
325
    XEND_CONFIG_FILE,
326
    XL_CONFIG_FILE,
327
    VIF_BRIDGE_SCRIPT,
328
    ]
329
  ANCILLARY_FILES_OPT = [
330
    XL_CONFIG_FILE,
331
    ]
332

    
333
  def __init__(self, _cfgdir=None, _run_cmd_fn=None, _cmd=None):
334
    hv_base.BaseHypervisor.__init__(self)
335

    
336
    if _cfgdir is None:
337
      self._cfgdir = pathutils.XEN_CONFIG_DIR
338
    else:
339
      self._cfgdir = _cfgdir
340

    
341
    if _run_cmd_fn is None:
342
      self._run_cmd_fn = utils.RunCmd
343
    else:
344
      self._run_cmd_fn = _run_cmd_fn
345

    
346
    self._cmd = _cmd
347

    
348
  def _GetCommand(self):
349
    """Returns Xen command to use.
350

351
    """
352
    if self._cmd is None:
353
      # TODO: Make command a hypervisor parameter
354
      cmd = constants.XEN_CMD
355
    else:
356
      cmd = self._cmd
357

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

    
361
    return cmd
362

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

366
    If a timeout (in seconds) is specified, the command will be terminated after
367
    that number of seconds.
368

369
    @see: L{utils.process.RunCmd}
370

371
    """
372
    cmd = []
373

    
374
    if timeout is not None:
375
      cmd.extend(["timeout", str(timeout)])
376

    
377
    cmd.extend([self._GetCommand()])
378
    cmd.extend(args)
379

    
380
    return self._run_cmd_fn(cmd)
381

    
382
  def _ConfigFileName(self, instance_name):
383
    """Get the config file name for an instance.
384

385
    @param instance_name: instance name
386
    @type instance_name: str
387
    @return: fully qualified path to instance config file
388
    @rtype: str
389

390
    """
391
    return utils.PathJoin(self._cfgdir, instance_name)
392

    
393
  @classmethod
394
  def _GetConfig(cls, instance, startup_memory, block_devices):
395
    """Build Xen configuration for an instance.
396

397
    """
398
    raise NotImplementedError
399

    
400
  def _WriteConfigFile(self, instance_name, data):
401
    """Write the Xen config file for the instance.
402

403
    This version of the function just writes the config file from static data.
404

405
    """
406
    # just in case it exists
407
    utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
408

    
409
    cfg_file = self._ConfigFileName(instance_name)
410
    try:
411
      utils.WriteFile(cfg_file, data=data)
412
    except EnvironmentError, err:
413
      raise errors.HypervisorError("Cannot write Xen instance configuration"
414
                                   " file %s: %s" % (cfg_file, err))
415

    
416
  def _ReadConfigFile(self, instance_name):
417
    """Returns the contents of the instance config file.
418

419
    """
420
    filename = self._ConfigFileName(instance_name)
421

    
422
    try:
423
      file_content = utils.ReadFile(filename)
424
    except EnvironmentError, err:
425
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
426

    
427
    return file_content
428

    
429
  def _RemoveConfigFile(self, instance_name):
430
    """Remove the xen configuration file.
431

432
    """
433
    utils.RemoveFile(self._ConfigFileName(instance_name))
434

    
435
  def _StashConfigFile(self, instance_name):
436
    """Move the Xen config file to the log directory and return its new path.
437

438
    """
439
    old_filename = self._ConfigFileName(instance_name)
440
    base = ("%s-%s" %
441
            (instance_name, utils.TimestampForFilename()))
442
    new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base)
443
    utils.RenameFile(old_filename, new_filename)
444
    return new_filename
445

    
446
  def _GetXmList(self, include_node):
447
    """Wrapper around module level L{_GetXmList}.
448

449
    """
450
    return _GetXmList(lambda: self._RunXen(["list"]), include_node)
451

    
452
  def ListInstances(self):
453
    """Get the list of running instances.
454

455
    """
456
    xm_list = self._GetXmList(False)
457
    names = [info[0] for info in xm_list]
458
    return names
459

    
460
  def GetInstanceInfo(self, instance_name):
461
    """Get instance properties.
462

463
    @param instance_name: the instance name
464

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

467
    """
468
    xm_list = self._GetXmList(instance_name == _DOM0_NAME)
469
    result = None
470
    for data in xm_list:
471
      if data[0] == instance_name:
472
        result = data
473
        break
474
    return result
475

    
476
  def GetAllInstancesInfo(self):
477
    """Get properties of all instances.
478

479
    @return: list of tuples (name, id, memory, vcpus, stat, times)
480

481
    """
482
    xm_list = self._GetXmList(False)
483
    return xm_list
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

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

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

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

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

526
    A soft shutdown can be interrupted. A hard shutdown tries forever.
527

528
    """
529
    assert(timeout is None or force is not None)
530

    
531
    if name is None:
532
      name = instance.name
533

    
534
    return self._StopInstance(name, force, timeout)
535

    
536
  def _ShutdownInstance(self, name, timeout):
537
    """Shutdown an instance if the instance is running.
538

539
    The '-w' flag waits for shutdown to complete which avoids the need
540
    to poll in the case where we want to destroy the domain
541
    immediately after shutdown.
542

543
    @type name: string
544
    @param name: name of the instance to stop
545
    @type timeout: int or None
546
    @param timeout: a timeout after which the shutdown command should be killed,
547
                    or None for no timeout
548

549
    """
550
    instance_info = self.GetInstanceInfo(name)
551

    
552
    if instance_info is None or _IsInstanceShutdown(instance_info[4]):
553
      logging.info("Failed to shutdown instance %s, not running", name)
554
      return None
555

    
556
    return self._RunXen(["shutdown", "-w", name], timeout)
557

    
558
  def _DestroyInstance(self, name):
559
    """Destroy an instance if the instance if the instance exists.
560

561
    @type name: string
562
    @param name: name of the instance to destroy
563

564
    """
565
    instance_info = self.GetInstanceInfo(name)
566

    
567
    if instance_info is None:
568
      logging.info("Failed to destroy instance %s, does not exist", name)
569
      return None
570

    
571
    return self._RunXen(["destroy", name])
572

    
573
  def _StopInstance(self, name, force, timeout):
574
    """Stop an instance.
575

576
    @type name: string
577
    @param name: name of the instance to destroy
578

579
    @type force: boolean
580
    @param force: whether to do a "hard" stop (destroy)
581

582
    @type timeout: int or None
583
    @param timeout: a timeout after which the shutdown command should be killed,
584
                    or None for no timeout
585

586
    """
587
    if force:
588
      result = self._DestroyInstance(name)
589
    else:
590
      self._ShutdownInstance(name, timeout)
591
      result = self._DestroyInstance(name)
592

    
593
    if result is not None and result.failed and \
594
          self.GetInstanceInfo(name) is not None:
595
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
596
                                   (name, result.fail_reason, result.output))
597

    
598
    # Remove configuration file if stopping/starting instance was successful
599
    self._RemoveConfigFile(name)
600

    
601
  def RebootInstance(self, instance):
602
    """Reboot an instance.
603

604
    """
605
    ini_info = self.GetInstanceInfo(instance.name)
606

    
607
    if ini_info is None:
608
      raise errors.HypervisorError("Failed to reboot instance %s,"
609
                                   " not running" % instance.name)
610

    
611
    result = self._RunXen(["reboot", instance.name])
612
    if result.failed:
613
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
614
                                   (instance.name, result.fail_reason,
615
                                    result.output))
616

    
617
    def _CheckInstance():
618
      new_info = self.GetInstanceInfo(instance.name)
619

    
620
      # check if the domain ID has changed or the run time has decreased
621
      if (new_info is not None and
622
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
623
        return
624

    
625
      raise utils.RetryAgain()
626

    
627
    try:
628
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
629
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
630
    except utils.RetryTimeout:
631
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
632
                                   " did not reboot in the expected interval" %
633
                                   (instance.name, ))
634

    
635
  def BalloonInstanceMemory(self, instance, mem):
636
    """Balloon an instance memory to a certain value.
637

638
    @type instance: L{objects.Instance}
639
    @param instance: instance to be accepted
640
    @type mem: int
641
    @param mem: actual memory size to use for instance runtime
642

643
    """
644
    result = self._RunXen(["mem-set", instance.name, mem])
645
    if result.failed:
646
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
647
                                   (instance.name, result.fail_reason,
648
                                    result.output))
649

    
650
    # Update configuration file
651
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
652
    cmd.append(self._ConfigFileName(instance.name))
653

    
654
    result = utils.RunCmd(cmd)
655
    if result.failed:
656
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
657
                                   (instance.name, result.fail_reason,
658
                                    result.output))
659

    
660
  def GetNodeInfo(self):
661
    """Return information about the node.
662

663
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
664

665
    """
666
    result = self._RunXen(["info"])
667
    if result.failed:
668
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
669
                    result.output)
670
      return None
671

    
672
    return _GetNodeInfo(result.stdout, self._GetXmList)
673

    
674
  @classmethod
675
  def GetInstanceConsole(cls, instance, hvparams, beparams):
676
    """Return a command for connecting to the console of an instance.
677

678
    """
679
    return objects.InstanceConsole(instance=instance.name,
680
                                   kind=constants.CONS_SSH,
681
                                   host=instance.primary_node,
682
                                   user=constants.SSH_CONSOLE_USER,
683
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
684
                                            constants.XEN_CMD, instance.name])
685

    
686
  def Verify(self):
687
    """Verify the hypervisor.
688

689
    For Xen, this verifies that the xend process is running.
690

691
    @return: Problem description if something is wrong, C{None} otherwise
692

693
    """
694
    result = self._RunXen(["info"])
695
    if result.failed:
696
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
697

    
698
    return None
699

    
700
  def MigrationInfo(self, instance):
701
    """Get instance information to perform a migration.
702

703
    @type instance: L{objects.Instance}
704
    @param instance: instance to be migrated
705
    @rtype: string
706
    @return: content of the xen config file
707

708
    """
709
    return self._ReadConfigFile(instance.name)
710

    
711
  def AcceptInstance(self, instance, info, target):
712
    """Prepare to accept an instance.
713

714
    @type instance: L{objects.Instance}
715
    @param instance: instance to be accepted
716
    @type info: string
717
    @param info: content of the xen config file on the source node
718
    @type target: string
719
    @param target: target host (usually ip), on this node
720

721
    """
722
    pass
723

    
724
  def FinalizeMigrationDst(self, instance, info, success):
725
    """Finalize an instance migration.
726

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

730
    @type instance: L{objects.Instance}
731
    @param instance: instance whose migration is being finalized
732
    @type info: string
733
    @param info: content of the xen config file on the source node
734
    @type success: boolean
735
    @param success: whether the migration was a success or a failure
736

737
    """
738
    if success:
739
      self._WriteConfigFile(instance.name, info)
740

    
741
  def MigrateInstance(self, instance, target, live):
742
    """Migrate an instance to a target node.
743

744
    The migration will not be attempted if the instance is not
745
    currently running.
746

747
    @type instance: L{objects.Instance}
748
    @param instance: the instance to be migrated
749
    @type target: string
750
    @param target: ip address of the target node
751
    @type live: boolean
752
    @param live: perform a live migration
753

754
    """
755
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
756

    
757
    # TODO: Pass cluster name via RPC
758
    cluster_name = ssconf.SimpleStore().GetClusterName()
759

    
760
    return self._MigrateInstance(cluster_name, instance.name, target, port,
761
                                 live)
762

    
763
  def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
764
                       _ping_fn=netutils.TcpPing):
765
    """Migrate an instance to a target node.
766

767
    @see: L{MigrateInstance} for details
768

769
    """
770
    if self.GetInstanceInfo(instance_name) is None:
771
      raise errors.HypervisorError("Instance not running, cannot migrate")
772

    
773
    cmd = self._GetCommand()
774

    
775
    if (cmd == constants.XEN_CMD_XM and
776
        not _ping_fn(target, port, live_port_needed=True)):
777
      raise errors.HypervisorError("Remote host %s not listening on port"
778
                                   " %s, cannot migrate" % (target, port))
779

    
780
    args = ["migrate"]
781

    
782
    if cmd == constants.XEN_CMD_XM:
783
      args.extend(["-p", "%d" % port])
784
      if live:
785
        args.append("-l")
786

    
787
    elif cmd == constants.XEN_CMD_XL:
788
      args.extend([
789
        "-s", constants.XL_SSH_CMD % cluster_name,
790
        "-C", self._ConfigFileName(instance_name),
791
        ])
792

    
793
    else:
794
      raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
795

    
796
    args.extend([instance_name, target])
797

    
798
    result = self._RunXen(args)
799
    if result.failed:
800
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
801
                                   (instance_name, result.output))
802

    
803
  def FinalizeMigrationSource(self, instance, success, live):
804
    """Finalize the instance migration on the source node.
805

806
    @type instance: L{objects.Instance}
807
    @param instance: the instance that was migrated
808
    @type success: bool
809
    @param success: whether the migration succeeded or not
810
    @type live: bool
811
    @param live: whether the user requested a live migration or not
812

813
    """
814
    # pylint: disable=W0613
815
    if success:
816
      # remove old xen file after migration succeeded
817
      try:
818
        self._RemoveConfigFile(instance.name)
819
      except EnvironmentError:
820
        logging.exception("Failure while removing instance config file")
821

    
822
  def GetMigrationStatus(self, instance):
823
    """Get the migration status
824

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

829
    @type instance: L{objects.Instance}
830
    @param instance: the instance that is being migrated
831
    @rtype: L{objects.MigrationStatus}
832
    @return: the status of the current migration (one of
833
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
834
             progress info that can be retrieved from the hypervisor
835

836
    """
837
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
838

    
839
  @classmethod
840
  def PowercycleNode(cls):
841
    """Xen-specific powercycle.
842

843
    This first does a Linux reboot (which triggers automatically a Xen
844
    reboot), and if that fails it tries to do a Xen reboot. The reason
845
    we don't try a Xen reboot first is that the xen reboot launches an
846
    external command which connects to the Xen hypervisor, and that
847
    won't work in case the root filesystem is broken and/or the xend
848
    daemon is not working.
849

850
    """
851
    try:
852
      cls.LinuxPowercycle()
853
    finally:
854
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
855

    
856

    
857
class XenPvmHypervisor(XenHypervisor):
858
  """Xen PVM hypervisor interface"""
859

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

    
880
  def _GetConfig(self, instance, startup_memory, block_devices):
881
    """Write the Xen config file for the instance.
882

883
    """
884
    hvp = instance.hvparams
885
    config = StringIO()
886
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
887

    
888
    # if bootloader is True, use bootloader instead of kernel and ramdisk
889
    # parameters.
890
    if hvp[constants.HV_USE_BOOTLOADER]:
891
      # bootloader handling
892
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
893
      if bootloader_path:
894
        config.write("bootloader = '%s'\n" % bootloader_path)
895
      else:
896
        raise errors.HypervisorError("Bootloader enabled, but missing"
897
                                     " bootloader path")
898

    
899
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
900
      if bootloader_args:
901
        config.write("bootargs = '%s'\n" % bootloader_args)
902
    else:
903
      # kernel handling
904
      kpath = hvp[constants.HV_KERNEL_PATH]
905
      config.write("kernel = '%s'\n" % kpath)
906

    
907
      # initrd handling
908
      initrd_path = hvp[constants.HV_INITRD_PATH]
909
      if initrd_path:
910
        config.write("ramdisk = '%s'\n" % initrd_path)
911

    
912
    # rest of the settings
913
    config.write("memory = %d\n" % startup_memory)
914
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
915
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
916
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
917
    if cpu_pinning:
918
      config.write("%s\n" % cpu_pinning)
919
    cpu_cap = hvp[constants.HV_CPU_CAP]
920
    if cpu_cap:
921
      config.write("cpu_cap=%d\n" % cpu_cap)
922
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
923
    if cpu_weight:
924
      config.write("cpu_weight=%d\n" % cpu_weight)
925

    
926
    config.write("name = '%s'\n" % instance.name)
927

    
928
    vif_data = []
929
    for nic in instance.nics:
930
      nic_str = "mac=%s" % (nic.mac)
931
      ip = getattr(nic, "ip", None)
932
      if ip is not None:
933
        nic_str += ", ip=%s" % ip
934
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
935
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
936
      vif_data.append("'%s'" % nic_str)
937

    
938
    disk_data = \
939
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
940

    
941
    config.write("vif = [%s]\n" % ",".join(vif_data))
942
    config.write("disk = [%s]\n" % ",".join(disk_data))
943

    
944
    if hvp[constants.HV_ROOT_PATH]:
945
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
946
    config.write("on_poweroff = 'destroy'\n")
947
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
948
      config.write("on_reboot = 'restart'\n")
949
    else:
950
      config.write("on_reboot = 'destroy'\n")
951
    config.write("on_crash = 'restart'\n")
952
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
953

    
954
    return config.getvalue()
955

    
956

    
957
class XenHvmHypervisor(XenHypervisor):
958
  """Xen HVM hypervisor interface"""
959

    
960
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
961
    pathutils.VNC_PASSWORD_FILE,
962
    ]
963
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
964
    pathutils.VNC_PASSWORD_FILE,
965
    ]
966

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

    
1003
  def _GetConfig(self, instance, startup_memory, block_devices):
1004
    """Create a Xen 3.1 HVM config file.
1005

1006
    """
1007
    hvp = instance.hvparams
1008

    
1009
    config = StringIO()
1010

    
1011
    # kernel handling
1012
    kpath = hvp[constants.HV_KERNEL_PATH]
1013
    config.write("kernel = '%s'\n" % kpath)
1014

    
1015
    config.write("builder = 'hvm'\n")
1016
    config.write("memory = %d\n" % startup_memory)
1017
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1018
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1019
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1020
    if cpu_pinning:
1021
      config.write("%s\n" % cpu_pinning)
1022
    cpu_cap = hvp[constants.HV_CPU_CAP]
1023
    if cpu_cap:
1024
      config.write("cpu_cap=%d\n" % cpu_cap)
1025
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1026
    if cpu_weight:
1027
      config.write("cpu_weight=%d\n" % cpu_weight)
1028

    
1029
    config.write("name = '%s'\n" % instance.name)
1030
    if hvp[constants.HV_PAE]:
1031
      config.write("pae = 1\n")
1032
    else:
1033
      config.write("pae = 0\n")
1034
    if hvp[constants.HV_ACPI]:
1035
      config.write("acpi = 1\n")
1036
    else:
1037
      config.write("acpi = 0\n")
1038
    if hvp[constants.HV_VIRIDIAN]:
1039
      config.write("viridian = 1\n")
1040
    else:
1041
      config.write("viridian = 0\n")
1042

    
1043
    config.write("apic = 1\n")
1044
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
1045
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
1046
    config.write("sdl = 0\n")
1047
    config.write("usb = 1\n")
1048
    config.write("usbdevice = 'tablet'\n")
1049
    config.write("vnc = 1\n")
1050
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
1051
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
1052
    else:
1053
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
1054

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

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

    
1070
    config.write("vncpasswd = '%s'\n" % password.rstrip())
1071

    
1072
    config.write("serial = 'pty'\n")
1073
    if hvp[constants.HV_USE_LOCALTIME]:
1074
      config.write("localtime = 1\n")
1075

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

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

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

    
1105
    disk_data = \
1106
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1107

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

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

    
1127
    return config.getvalue()