Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 7066724d

History | View | Annotate | Download (34.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
  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
    """Stop an instance.
524

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

    
529
    return self._StopInstance(name, force)
530

    
531
  def _ShutdownInstance(self, name):
532
    """Shutdown an instance if the instance is running.
533

534
    The '-w' flag waits for shutdown to complete which avoids the need
535
    to poll in the case where we want to destroy the domain
536
    immediately after shutdown.
537

538
    @type name: string
539
    @param name: name of the instance to stop
540

541
    """
542
    instance_info = self.GetInstanceInfo(name)
543

    
544
    if instance_info is None or _IsInstanceShutdown(instance_info[4]):
545
      logging.info("Failed to shutdown instance %s, not running", name)
546
      return None
547

    
548
    return self._RunXen(["shutdown", "-w", name])
549

    
550
  def _DestroyInstance(self, name):
551
    """Destroy an instance if the instance if the instance exists.
552

553
    @type name: string
554
    @param name: name of the instance to destroy
555

556
    """
557
    instance_info = self.GetInstanceInfo(name)
558

    
559
    if instance_info is None:
560
      logging.info("Failed to destroy instance %s, does not exist", name)
561
      return None
562

    
563
    return self._RunXen(["destroy", name])
564

    
565
  def _StopInstance(self, name, force):
566
    """Stop an instance.
567

568
    @type name: string
569
    @param name: name of the instance to destroy
570

571
    @type force: boolean
572
    @param force: whether to do a "hard" stop (destroy)
573

574
    """
575
    if force:
576
      result = self._DestroyInstance(name)
577
    else:
578
      self._ShutdownInstance(name)
579
      result = self._DestroyInstance(name)
580

    
581
    if result is not None and result.failed and \
582
          self.GetInstanceInfo(name) is not None:
583
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
584
                                   (name, result.fail_reason, result.output))
585

    
586
    # Remove configuration file if stopping/starting instance was successful
587
    self._RemoveConfigFile(name)
588

    
589
  def RebootInstance(self, instance):
590
    """Reboot an instance.
591

592
    """
593
    ini_info = self.GetInstanceInfo(instance.name)
594

    
595
    if ini_info is None:
596
      raise errors.HypervisorError("Failed to reboot instance %s,"
597
                                   " not running" % instance.name)
598

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

    
605
    def _CheckInstance():
606
      new_info = self.GetInstanceInfo(instance.name)
607

    
608
      # check if the domain ID has changed or the run time has decreased
609
      if (new_info is not None and
610
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
611
        return
612

    
613
      raise utils.RetryAgain()
614

    
615
    try:
616
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
617
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
618
    except utils.RetryTimeout:
619
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
620
                                   " did not reboot in the expected interval" %
621
                                   (instance.name, ))
622

    
623
  def BalloonInstanceMemory(self, instance, mem):
624
    """Balloon an instance memory to a certain value.
625

626
    @type instance: L{objects.Instance}
627
    @param instance: instance to be accepted
628
    @type mem: int
629
    @param mem: actual memory size to use for instance runtime
630

631
    """
632
    result = self._RunXen(["mem-set", instance.name, mem])
633
    if result.failed:
634
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
635
                                   (instance.name, result.fail_reason,
636
                                    result.output))
637

    
638
    # Update configuration file
639
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
640
    cmd.append(self._ConfigFileName(instance.name))
641

    
642
    result = utils.RunCmd(cmd)
643
    if result.failed:
644
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
645
                                   (instance.name, result.fail_reason,
646
                                    result.output))
647

    
648
  def GetNodeInfo(self):
649
    """Return information about the node.
650

651
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
652

653
    """
654
    result = self._RunXen(["info"])
655
    if result.failed:
656
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
657
                    result.output)
658
      return None
659

    
660
    return _GetNodeInfo(result.stdout, self._GetXmList)
661

    
662
  @classmethod
663
  def GetInstanceConsole(cls, instance, hvparams, beparams):
664
    """Return a command for connecting to the console of an instance.
665

666
    """
667
    return objects.InstanceConsole(instance=instance.name,
668
                                   kind=constants.CONS_SSH,
669
                                   host=instance.primary_node,
670
                                   user=constants.SSH_CONSOLE_USER,
671
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
672
                                            constants.XEN_CMD, instance.name])
673

    
674
  def Verify(self):
675
    """Verify the hypervisor.
676

677
    For Xen, this verifies that the xend process is running.
678

679
    @return: Problem description if something is wrong, C{None} otherwise
680

681
    """
682
    result = self._RunXen(["info"])
683
    if result.failed:
684
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
685

    
686
    return None
687

    
688
  def MigrationInfo(self, instance):
689
    """Get instance information to perform a migration.
690

691
    @type instance: L{objects.Instance}
692
    @param instance: instance to be migrated
693
    @rtype: string
694
    @return: content of the xen config file
695

696
    """
697
    return self._ReadConfigFile(instance.name)
698

    
699
  def AcceptInstance(self, instance, info, target):
700
    """Prepare to accept an instance.
701

702
    @type instance: L{objects.Instance}
703
    @param instance: instance to be accepted
704
    @type info: string
705
    @param info: content of the xen config file on the source node
706
    @type target: string
707
    @param target: target host (usually ip), on this node
708

709
    """
710
    pass
711

    
712
  def FinalizeMigrationDst(self, instance, info, success):
713
    """Finalize an instance migration.
714

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

718
    @type instance: L{objects.Instance}
719
    @param instance: instance whose migration is being finalized
720
    @type info: string
721
    @param info: content of the xen config file on the source node
722
    @type success: boolean
723
    @param success: whether the migration was a success or a failure
724

725
    """
726
    if success:
727
      self._WriteConfigFile(instance.name, info)
728

    
729
  def MigrateInstance(self, instance, target, live):
730
    """Migrate an instance to a target node.
731

732
    The migration will not be attempted if the instance is not
733
    currently running.
734

735
    @type instance: L{objects.Instance}
736
    @param instance: the instance to be migrated
737
    @type target: string
738
    @param target: ip address of the target node
739
    @type live: boolean
740
    @param live: perform a live migration
741

742
    """
743
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
744

    
745
    # TODO: Pass cluster name via RPC
746
    cluster_name = ssconf.SimpleStore().GetClusterName()
747

    
748
    return self._MigrateInstance(cluster_name, instance.name, target, port,
749
                                 live)
750

    
751
  def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
752
                       _ping_fn=netutils.TcpPing):
753
    """Migrate an instance to a target node.
754

755
    @see: L{MigrateInstance} for details
756

757
    """
758
    if self.GetInstanceInfo(instance_name) is None:
759
      raise errors.HypervisorError("Instance not running, cannot migrate")
760

    
761
    cmd = self._GetCommand()
762

    
763
    if (cmd == constants.XEN_CMD_XM and
764
        not _ping_fn(target, port, live_port_needed=True)):
765
      raise errors.HypervisorError("Remote host %s not listening on port"
766
                                   " %s, cannot migrate" % (target, port))
767

    
768
    args = ["migrate"]
769

    
770
    if cmd == constants.XEN_CMD_XM:
771
      args.extend(["-p", "%d" % port])
772
      if live:
773
        args.append("-l")
774

    
775
    elif cmd == constants.XEN_CMD_XL:
776
      args.extend([
777
        "-s", constants.XL_SSH_CMD % cluster_name,
778
        "-C", self._ConfigFileName(instance_name),
779
        ])
780

    
781
    else:
782
      raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
783

    
784
    args.extend([instance_name, target])
785

    
786
    result = self._RunXen(args)
787
    if result.failed:
788
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
789
                                   (instance_name, result.output))
790

    
791
  def FinalizeMigrationSource(self, instance, success, live):
792
    """Finalize the instance migration on the source node.
793

794
    @type instance: L{objects.Instance}
795
    @param instance: the instance that was migrated
796
    @type success: bool
797
    @param success: whether the migration succeeded or not
798
    @type live: bool
799
    @param live: whether the user requested a live migration or not
800

801
    """
802
    # pylint: disable=W0613
803
    if success:
804
      # remove old xen file after migration succeeded
805
      try:
806
        self._RemoveConfigFile(instance.name)
807
      except EnvironmentError:
808
        logging.exception("Failure while removing instance config file")
809

    
810
  def GetMigrationStatus(self, instance):
811
    """Get the migration status
812

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

817
    @type instance: L{objects.Instance}
818
    @param instance: the instance that is being migrated
819
    @rtype: L{objects.MigrationStatus}
820
    @return: the status of the current migration (one of
821
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
822
             progress info that can be retrieved from the hypervisor
823

824
    """
825
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
826

    
827
  @classmethod
828
  def PowercycleNode(cls):
829
    """Xen-specific powercycle.
830

831
    This first does a Linux reboot (which triggers automatically a Xen
832
    reboot), and if that fails it tries to do a Xen reboot. The reason
833
    we don't try a Xen reboot first is that the xen reboot launches an
834
    external command which connects to the Xen hypervisor, and that
835
    won't work in case the root filesystem is broken and/or the xend
836
    daemon is not working.
837

838
    """
839
    try:
840
      cls.LinuxPowercycle()
841
    finally:
842
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
843

    
844

    
845
class XenPvmHypervisor(XenHypervisor):
846
  """Xen PVM hypervisor interface"""
847

    
848
  PARAMETERS = {
849
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
850
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
851
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
852
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
853
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
854
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
855
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
856
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
857
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
858
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
859
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
860
    constants.HV_REBOOT_BEHAVIOR:
861
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
862
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
863
    constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
864
    constants.HV_CPU_WEIGHT:
865
      (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
866
    }
867

    
868
  def _GetConfig(self, instance, startup_memory, block_devices):
869
    """Write the Xen config file for the instance.
870

871
    """
872
    hvp = instance.hvparams
873
    config = StringIO()
874
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
875

    
876
    # if bootloader is True, use bootloader instead of kernel and ramdisk
877
    # parameters.
878
    if hvp[constants.HV_USE_BOOTLOADER]:
879
      # bootloader handling
880
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
881
      if bootloader_path:
882
        config.write("bootloader = '%s'\n" % bootloader_path)
883
      else:
884
        raise errors.HypervisorError("Bootloader enabled, but missing"
885
                                     " bootloader path")
886

    
887
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
888
      if bootloader_args:
889
        config.write("bootargs = '%s'\n" % bootloader_args)
890
    else:
891
      # kernel handling
892
      kpath = hvp[constants.HV_KERNEL_PATH]
893
      config.write("kernel = '%s'\n" % kpath)
894

    
895
      # initrd handling
896
      initrd_path = hvp[constants.HV_INITRD_PATH]
897
      if initrd_path:
898
        config.write("ramdisk = '%s'\n" % initrd_path)
899

    
900
    # rest of the settings
901
    config.write("memory = %d\n" % startup_memory)
902
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
903
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
904
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
905
    if cpu_pinning:
906
      config.write("%s\n" % cpu_pinning)
907
    cpu_cap = hvp[constants.HV_CPU_CAP]
908
    if cpu_cap:
909
      config.write("cpu_cap=%d\n" % cpu_cap)
910
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
911
    if cpu_weight:
912
      config.write("cpu_weight=%d\n" % cpu_weight)
913

    
914
    config.write("name = '%s'\n" % instance.name)
915

    
916
    vif_data = []
917
    for nic in instance.nics:
918
      nic_str = "mac=%s" % (nic.mac)
919
      ip = getattr(nic, "ip", None)
920
      if ip is not None:
921
        nic_str += ", ip=%s" % ip
922
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
923
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
924
      vif_data.append("'%s'" % nic_str)
925

    
926
    disk_data = \
927
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
928

    
929
    config.write("vif = [%s]\n" % ",".join(vif_data))
930
    config.write("disk = [%s]\n" % ",".join(disk_data))
931

    
932
    if hvp[constants.HV_ROOT_PATH]:
933
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
934
    config.write("on_poweroff = 'destroy'\n")
935
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
936
      config.write("on_reboot = 'restart'\n")
937
    else:
938
      config.write("on_reboot = 'destroy'\n")
939
    config.write("on_crash = 'restart'\n")
940
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
941

    
942
    return config.getvalue()
943

    
944

    
945
class XenHvmHypervisor(XenHypervisor):
946
  """Xen HVM hypervisor interface"""
947

    
948
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
949
    pathutils.VNC_PASSWORD_FILE,
950
    ]
951
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
952
    pathutils.VNC_PASSWORD_FILE,
953
    ]
954

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

    
991
  def _GetConfig(self, instance, startup_memory, block_devices):
992
    """Create a Xen 3.1 HVM config file.
993

994
    """
995
    hvp = instance.hvparams
996

    
997
    config = StringIO()
998

    
999
    # kernel handling
1000
    kpath = hvp[constants.HV_KERNEL_PATH]
1001
    config.write("kernel = '%s'\n" % kpath)
1002

    
1003
    config.write("builder = 'hvm'\n")
1004
    config.write("memory = %d\n" % startup_memory)
1005
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1006
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1007
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1008
    if cpu_pinning:
1009
      config.write("%s\n" % cpu_pinning)
1010
    cpu_cap = hvp[constants.HV_CPU_CAP]
1011
    if cpu_cap:
1012
      config.write("cpu_cap=%d\n" % cpu_cap)
1013
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1014
    if cpu_weight:
1015
      config.write("cpu_weight=%d\n" % cpu_weight)
1016

    
1017
    config.write("name = '%s'\n" % instance.name)
1018
    if hvp[constants.HV_PAE]:
1019
      config.write("pae = 1\n")
1020
    else:
1021
      config.write("pae = 0\n")
1022
    if hvp[constants.HV_ACPI]:
1023
      config.write("acpi = 1\n")
1024
    else:
1025
      config.write("acpi = 0\n")
1026
    if hvp[constants.HV_VIRIDIAN]:
1027
      config.write("viridian = 1\n")
1028
    else:
1029
      config.write("viridian = 0\n")
1030

    
1031
    config.write("apic = 1\n")
1032
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
1033
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
1034
    config.write("sdl = 0\n")
1035
    config.write("usb = 1\n")
1036
    config.write("usbdevice = 'tablet'\n")
1037
    config.write("vnc = 1\n")
1038
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
1039
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
1040
    else:
1041
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
1042

    
1043
    if instance.network_port > constants.VNC_BASE_PORT:
1044
      display = instance.network_port - constants.VNC_BASE_PORT
1045
      config.write("vncdisplay = %s\n" % display)
1046
      config.write("vncunused = 0\n")
1047
    else:
1048
      config.write("# vncdisplay = 1\n")
1049
      config.write("vncunused = 1\n")
1050

    
1051
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
1052
    try:
1053
      password = utils.ReadFile(vnc_pwd_file)
1054
    except EnvironmentError, err:
1055
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1056
                                   (vnc_pwd_file, err))
1057

    
1058
    config.write("vncpasswd = '%s'\n" % password.rstrip())
1059

    
1060
    config.write("serial = 'pty'\n")
1061
    if hvp[constants.HV_USE_LOCALTIME]:
1062
      config.write("localtime = 1\n")
1063

    
1064
    vif_data = []
1065
    # Note: what is called 'nic_type' here, is used as value for the xen nic
1066
    # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1067
    # the 'vif_type' to avoid a clash of notation.
1068
    nic_type = hvp[constants.HV_NIC_TYPE]
1069

    
1070
    if nic_type is None:
1071
      vif_type_str = ""
1072
      if hvp[constants.HV_VIF_TYPE]:
1073
        vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
1074
      # ensure old instances don't change
1075
      nic_type_str = vif_type_str
1076
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
1077
      nic_type_str = ", type=paravirtualized"
1078
    else:
1079
      # parameter 'model' is only valid with type 'ioemu'
1080
      nic_type_str = ", model=%s, type=%s" % \
1081
        (nic_type, constants.HT_HVM_VIF_IOEMU)
1082
    for nic in instance.nics:
1083
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1084
      ip = getattr(nic, "ip", None)
1085
      if ip is not None:
1086
        nic_str += ", ip=%s" % ip
1087
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1088
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1089
      vif_data.append("'%s'" % nic_str)
1090

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

    
1093
    disk_data = \
1094
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1095

    
1096
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1097
    if iso_path:
1098
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
1099
      disk_data.append(iso)
1100

    
1101
    config.write("disk = [%s]\n" % (",".join(disk_data)))
1102
    # Add PCI passthrough
1103
    pci_pass_arr = []
1104
    pci_pass = hvp[constants.HV_PASSTHROUGH]
1105
    if pci_pass:
1106
      pci_pass_arr = pci_pass.split(";")
1107
      config.write("pci = %s\n" % pci_pass_arr)
1108
    config.write("on_poweroff = 'destroy'\n")
1109
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1110
      config.write("on_reboot = 'restart'\n")
1111
    else:
1112
      config.write("on_reboot = 'destroy'\n")
1113
    config.write("on_crash = 'restart'\n")
1114

    
1115
    return config.getvalue()