Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 7bc2c097

History | View | Annotate | Download (34.7 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):
364
    """Wrapper around L{utils.process.RunCmd} to run Xen command.
365

366
    @see: L{utils.process.RunCmd}
367

368
    """
369
    cmd = [self._GetCommand()]
370
    cmd.extend(args)
371

    
372
    return self._run_cmd_fn(cmd)
373

    
374
  def _ConfigFileName(self, instance_name):
375
    """Get the config file name for an instance.
376

377
    @param instance_name: instance name
378
    @type instance_name: str
379
    @return: fully qualified path to instance config file
380
    @rtype: str
381

382
    """
383
    return utils.PathJoin(self._cfgdir, instance_name)
384

    
385
  @classmethod
386
  def _GetConfig(cls, instance, startup_memory, block_devices):
387
    """Build Xen configuration for an instance.
388

389
    """
390
    raise NotImplementedError
391

    
392
  def _WriteConfigFile(self, instance_name, data):
393
    """Write the Xen config file for the instance.
394

395
    This version of the function just writes the config file from static data.
396

397
    """
398
    # just in case it exists
399
    utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
400

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

    
408
  def _ReadConfigFile(self, instance_name):
409
    """Returns the contents of the instance config file.
410

411
    """
412
    filename = self._ConfigFileName(instance_name)
413

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

    
419
    return file_content
420

    
421
  def _RemoveConfigFile(self, instance_name):
422
    """Remove the xen configuration file.
423

424
    """
425
    utils.RemoveFile(self._ConfigFileName(instance_name))
426

    
427
  def _StashConfigFile(self, instance_name):
428
    """Move the Xen config file to the log directory and return its new path.
429

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

    
438
  def _GetXmList(self, include_node):
439
    """Wrapper around module level L{_GetXmList}.
440

441
    """
442
    return _GetXmList(lambda: self._RunXen(["list"]), include_node)
443

    
444
  def ListInstances(self):
445
    """Get the list of running instances.
446

447
    """
448
    xm_list = self._GetXmList(False)
449
    names = [info[0] for info in xm_list]
450
    return names
451

    
452
  def GetInstanceInfo(self, instance_name):
453
    """Get instance properties.
454

455
    @param instance_name: the instance name
456

457
    @return: tuple (name, id, memory, vcpus, stat, times)
458

459
    """
460
    xm_list = self._GetXmList(instance_name == _DOM0_NAME)
461
    result = None
462
    for data in xm_list:
463
      if data[0] == instance_name:
464
        result = data
465
        break
466
    return result
467

    
468
  def GetAllInstancesInfo(self):
469
    """Get properties of all instances.
470

471
    @return: list of tuples (name, id, memory, vcpus, stat, times)
472

473
    """
474
    xm_list = self._GetXmList(False)
475
    return xm_list
476

    
477
  def _MakeConfigFile(self, instance, startup_memory, block_devices):
478
    """Gather configuration details and write to disk.
479

480
    See L{_GetConfig} for arguments.
481

482
    """
483
    buf = StringIO()
484
    buf.write("# Automatically generated by Ganeti. Do not edit!\n")
485
    buf.write("\n")
486
    buf.write(self._GetConfig(instance, startup_memory, block_devices))
487
    buf.write("\n")
488

    
489
    self._WriteConfigFile(instance.name, buf.getvalue())
490

    
491
  def StartInstance(self, instance, block_devices, startup_paused):
492
    """Start an instance.
493

494
    """
495
    startup_memory = self._InstanceStartupMemory(instance)
496

    
497
    self._MakeConfigFile(instance, startup_memory, block_devices)
498

    
499
    cmd = ["create"]
500
    if startup_paused:
501
      cmd.append("-p")
502
    cmd.append(self._ConfigFileName(instance.name))
503

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

    
514
  def StopInstance(self, instance, force=False, retry=False, name=None):
515
    """Stop an instance.
516

517
    """
518
    if name is None:
519
      name = instance.name
520

    
521
    return self._StopInstance(name, force)
522

    
523
  def _ShutdownInstance(self, name):
524
    """Shutdown an instance if the instance is running.
525

526
    @type name: string
527
    @param name: name of the instance to stop
528

529
    The '-w' flag waits for shutdown to complete which avoids the need
530
    to poll in the case where we want to destroy the domain
531
    immediately after shutdown.
532

533
    """
534
    instance_info = self.GetInstanceInfo(name)
535

    
536
    if instance_info is None or _IsInstanceShutdown(instance_info[4]):
537
      logging.info("Failed to shutdown instance %s, not running", name)
538
      return None
539

    
540
    return self._RunXen(["shutdown", "-w", name])
541

    
542
  def _DestroyInstance(self, name):
543
    """Destroy an instance if the instance if the instance exists.
544

545
    @type name: string
546
    @param name: name of the instance to destroy
547

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

    
551
    if instance_info is None:
552
      logging.info("Failed to destroy instance %s, does not exist", name)
553
      return None
554

    
555
    return self._RunXen(["destroy", name])
556

    
557
  def _StopInstance(self, name, force):
558
    """Stop an instance.
559

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

563
    @type force: boolean
564
    @param force: whether to do a "hard" stop (destroy)
565

566
    """
567
    if force:
568
      result = self._DestroyInstance(name)
569
    else:
570
      self._ShutdownInstance(name)
571
      result = self._DestroyInstance(name)
572

    
573
    if result is not None and result.failed and \
574
          self.GetInstanceInfo(name) is not None:
575
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
576
                                   (name, result.fail_reason, result.output))
577

    
578
    # Remove configuration file if stopping/starting instance was successful
579
    self._RemoveConfigFile(name)
580

    
581
  def RebootInstance(self, instance):
582
    """Reboot an instance.
583

584
    """
585
    ini_info = self.GetInstanceInfo(instance.name)
586

    
587
    if ini_info is None:
588
      raise errors.HypervisorError("Failed to reboot instance %s,"
589
                                   " not running" % instance.name)
590

    
591
    result = self._RunXen(["reboot", instance.name])
592
    if result.failed:
593
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
594
                                   (instance.name, result.fail_reason,
595
                                    result.output))
596

    
597
    def _CheckInstance():
598
      new_info = self.GetInstanceInfo(instance.name)
599

    
600
      # check if the domain ID has changed or the run time has decreased
601
      if (new_info is not None and
602
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
603
        return
604

    
605
      raise utils.RetryAgain()
606

    
607
    try:
608
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
609
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
610
    except utils.RetryTimeout:
611
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
612
                                   " did not reboot in the expected interval" %
613
                                   (instance.name, ))
614

    
615
  def BalloonInstanceMemory(self, instance, mem):
616
    """Balloon an instance memory to a certain value.
617

618
    @type instance: L{objects.Instance}
619
    @param instance: instance to be accepted
620
    @type mem: int
621
    @param mem: actual memory size to use for instance runtime
622

623
    """
624
    result = self._RunXen(["mem-set", instance.name, mem])
625
    if result.failed:
626
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
627
                                   (instance.name, result.fail_reason,
628
                                    result.output))
629

    
630
    # Update configuration file
631
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
632
    cmd.append(self._ConfigFileName(instance.name))
633

    
634
    result = utils.RunCmd(cmd)
635
    if result.failed:
636
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
637
                                   (instance.name, result.fail_reason,
638
                                    result.output))
639

    
640
  def GetNodeInfo(self):
641
    """Return information about the node.
642

643
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
644

645
    """
646
    result = self._RunXen(["info"])
647
    if result.failed:
648
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
649
                    result.output)
650
      return None
651

    
652
    return _GetNodeInfo(result.stdout, self._GetXmList)
653

    
654
  @classmethod
655
  def GetInstanceConsole(cls, instance, hvparams, beparams):
656
    """Return a command for connecting to the console of an instance.
657

658
    """
659
    return objects.InstanceConsole(instance=instance.name,
660
                                   kind=constants.CONS_SSH,
661
                                   host=instance.primary_node,
662
                                   user=constants.SSH_CONSOLE_USER,
663
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
664
                                            constants.XEN_CMD, instance.name])
665

    
666
  def Verify(self):
667
    """Verify the hypervisor.
668

669
    For Xen, this verifies that the xend process is running.
670

671
    @return: Problem description if something is wrong, C{None} otherwise
672

673
    """
674
    result = self._RunXen(["info"])
675
    if result.failed:
676
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
677

    
678
    return None
679

    
680
  def MigrationInfo(self, instance):
681
    """Get instance information to perform a migration.
682

683
    @type instance: L{objects.Instance}
684
    @param instance: instance to be migrated
685
    @rtype: string
686
    @return: content of the xen config file
687

688
    """
689
    return self._ReadConfigFile(instance.name)
690

    
691
  def AcceptInstance(self, instance, info, target):
692
    """Prepare to accept an instance.
693

694
    @type instance: L{objects.Instance}
695
    @param instance: instance to be accepted
696
    @type info: string
697
    @param info: content of the xen config file on the source node
698
    @type target: string
699
    @param target: target host (usually ip), on this node
700

701
    """
702
    pass
703

    
704
  def FinalizeMigrationDst(self, instance, info, success):
705
    """Finalize an instance migration.
706

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

710
    @type instance: L{objects.Instance}
711
    @param instance: instance whose migration is being finalized
712
    @type info: string
713
    @param info: content of the xen config file on the source node
714
    @type success: boolean
715
    @param success: whether the migration was a success or a failure
716

717
    """
718
    if success:
719
      self._WriteConfigFile(instance.name, info)
720

    
721
  def MigrateInstance(self, instance, target, live):
722
    """Migrate an instance to a target node.
723

724
    The migration will not be attempted if the instance is not
725
    currently running.
726

727
    @type instance: L{objects.Instance}
728
    @param instance: the instance to be migrated
729
    @type target: string
730
    @param target: ip address of the target node
731
    @type live: boolean
732
    @param live: perform a live migration
733

734
    """
735
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
736

    
737
    # TODO: Pass cluster name via RPC
738
    cluster_name = ssconf.SimpleStore().GetClusterName()
739

    
740
    return self._MigrateInstance(cluster_name, instance.name, target, port,
741
                                 live)
742

    
743
  def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
744
                       _ping_fn=netutils.TcpPing):
745
    """Migrate an instance to a target node.
746

747
    @see: L{MigrateInstance} for details
748

749
    """
750
    if self.GetInstanceInfo(instance_name) is None:
751
      raise errors.HypervisorError("Instance not running, cannot migrate")
752

    
753
    cmd = self._GetCommand()
754

    
755
    if (cmd == constants.XEN_CMD_XM and
756
        not _ping_fn(target, port, live_port_needed=True)):
757
      raise errors.HypervisorError("Remote host %s not listening on port"
758
                                   " %s, cannot migrate" % (target, port))
759

    
760
    args = ["migrate"]
761

    
762
    if cmd == constants.XEN_CMD_XM:
763
      args.extend(["-p", "%d" % port])
764
      if live:
765
        args.append("-l")
766

    
767
    elif cmd == constants.XEN_CMD_XL:
768
      args.extend([
769
        "-s", constants.XL_SSH_CMD % cluster_name,
770
        "-C", self._ConfigFileName(instance_name),
771
        ])
772

    
773
    else:
774
      raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
775

    
776
    args.extend([instance_name, target])
777

    
778
    result = self._RunXen(args)
779
    if result.failed:
780
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
781
                                   (instance_name, result.output))
782

    
783
  def FinalizeMigrationSource(self, instance, success, live):
784
    """Finalize the instance migration on the source node.
785

786
    @type instance: L{objects.Instance}
787
    @param instance: the instance that was migrated
788
    @type success: bool
789
    @param success: whether the migration succeeded or not
790
    @type live: bool
791
    @param live: whether the user requested a live migration or not
792

793
    """
794
    # pylint: disable=W0613
795
    if success:
796
      # remove old xen file after migration succeeded
797
      try:
798
        self._RemoveConfigFile(instance.name)
799
      except EnvironmentError:
800
        logging.exception("Failure while removing instance config file")
801

    
802
  def GetMigrationStatus(self, instance):
803
    """Get the migration status
804

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

809
    @type instance: L{objects.Instance}
810
    @param instance: the instance that is being migrated
811
    @rtype: L{objects.MigrationStatus}
812
    @return: the status of the current migration (one of
813
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
814
             progress info that can be retrieved from the hypervisor
815

816
    """
817
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
818

    
819
  @classmethod
820
  def PowercycleNode(cls):
821
    """Xen-specific powercycle.
822

823
    This first does a Linux reboot (which triggers automatically a Xen
824
    reboot), and if that fails it tries to do a Xen reboot. The reason
825
    we don't try a Xen reboot first is that the xen reboot launches an
826
    external command which connects to the Xen hypervisor, and that
827
    won't work in case the root filesystem is broken and/or the xend
828
    daemon is not working.
829

830
    """
831
    try:
832
      cls.LinuxPowercycle()
833
    finally:
834
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
835

    
836

    
837
class XenPvmHypervisor(XenHypervisor):
838
  """Xen PVM hypervisor interface"""
839

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

    
860
  def _GetConfig(self, instance, startup_memory, block_devices):
861
    """Write the Xen config file for the instance.
862

863
    """
864
    hvp = instance.hvparams
865
    config = StringIO()
866
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
867

    
868
    # if bootloader is True, use bootloader instead of kernel and ramdisk
869
    # parameters.
870
    if hvp[constants.HV_USE_BOOTLOADER]:
871
      # bootloader handling
872
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
873
      if bootloader_path:
874
        config.write("bootloader = '%s'\n" % bootloader_path)
875
      else:
876
        raise errors.HypervisorError("Bootloader enabled, but missing"
877
                                     " bootloader path")
878

    
879
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
880
      if bootloader_args:
881
        config.write("bootargs = '%s'\n" % bootloader_args)
882
    else:
883
      # kernel handling
884
      kpath = hvp[constants.HV_KERNEL_PATH]
885
      config.write("kernel = '%s'\n" % kpath)
886

    
887
      # initrd handling
888
      initrd_path = hvp[constants.HV_INITRD_PATH]
889
      if initrd_path:
890
        config.write("ramdisk = '%s'\n" % initrd_path)
891

    
892
    # rest of the settings
893
    config.write("memory = %d\n" % startup_memory)
894
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
895
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
896
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
897
    if cpu_pinning:
898
      config.write("%s\n" % cpu_pinning)
899
    cpu_cap = hvp[constants.HV_CPU_CAP]
900
    if cpu_cap:
901
      config.write("cpu_cap=%d\n" % cpu_cap)
902
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
903
    if cpu_weight:
904
      config.write("cpu_weight=%d\n" % cpu_weight)
905

    
906
    config.write("name = '%s'\n" % instance.name)
907

    
908
    vif_data = []
909
    for nic in instance.nics:
910
      nic_str = "mac=%s" % (nic.mac)
911
      ip = getattr(nic, "ip", None)
912
      if ip is not None:
913
        nic_str += ", ip=%s" % ip
914
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
915
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
916
      vif_data.append("'%s'" % nic_str)
917

    
918
    disk_data = \
919
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
920

    
921
    config.write("vif = [%s]\n" % ",".join(vif_data))
922
    config.write("disk = [%s]\n" % ",".join(disk_data))
923

    
924
    if hvp[constants.HV_ROOT_PATH]:
925
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
926
    config.write("on_poweroff = 'destroy'\n")
927
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
928
      config.write("on_reboot = 'restart'\n")
929
    else:
930
      config.write("on_reboot = 'destroy'\n")
931
    config.write("on_crash = 'restart'\n")
932
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
933

    
934
    return config.getvalue()
935

    
936

    
937
class XenHvmHypervisor(XenHypervisor):
938
  """Xen HVM hypervisor interface"""
939

    
940
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
941
    pathutils.VNC_PASSWORD_FILE,
942
    ]
943
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
944
    pathutils.VNC_PASSWORD_FILE,
945
    ]
946

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

    
983
  def _GetConfig(self, instance, startup_memory, block_devices):
984
    """Create a Xen 3.1 HVM config file.
985

986
    """
987
    hvp = instance.hvparams
988

    
989
    config = StringIO()
990

    
991
    # kernel handling
992
    kpath = hvp[constants.HV_KERNEL_PATH]
993
    config.write("kernel = '%s'\n" % kpath)
994

    
995
    config.write("builder = 'hvm'\n")
996
    config.write("memory = %d\n" % startup_memory)
997
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
998
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
999
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1000
    if cpu_pinning:
1001
      config.write("%s\n" % cpu_pinning)
1002
    cpu_cap = hvp[constants.HV_CPU_CAP]
1003
    if cpu_cap:
1004
      config.write("cpu_cap=%d\n" % cpu_cap)
1005
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1006
    if cpu_weight:
1007
      config.write("cpu_weight=%d\n" % cpu_weight)
1008

    
1009
    config.write("name = '%s'\n" % instance.name)
1010
    if hvp[constants.HV_PAE]:
1011
      config.write("pae = 1\n")
1012
    else:
1013
      config.write("pae = 0\n")
1014
    if hvp[constants.HV_ACPI]:
1015
      config.write("acpi = 1\n")
1016
    else:
1017
      config.write("acpi = 0\n")
1018
    if hvp[constants.HV_VIRIDIAN]:
1019
      config.write("viridian = 1\n")
1020
    else:
1021
      config.write("viridian = 0\n")
1022

    
1023
    config.write("apic = 1\n")
1024
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
1025
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
1026
    config.write("sdl = 0\n")
1027
    config.write("usb = 1\n")
1028
    config.write("usbdevice = 'tablet'\n")
1029
    config.write("vnc = 1\n")
1030
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
1031
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
1032
    else:
1033
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
1034

    
1035
    if instance.network_port > constants.VNC_BASE_PORT:
1036
      display = instance.network_port - constants.VNC_BASE_PORT
1037
      config.write("vncdisplay = %s\n" % display)
1038
      config.write("vncunused = 0\n")
1039
    else:
1040
      config.write("# vncdisplay = 1\n")
1041
      config.write("vncunused = 1\n")
1042

    
1043
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
1044
    try:
1045
      password = utils.ReadFile(vnc_pwd_file)
1046
    except EnvironmentError, err:
1047
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1048
                                   (vnc_pwd_file, err))
1049

    
1050
    config.write("vncpasswd = '%s'\n" % password.rstrip())
1051

    
1052
    config.write("serial = 'pty'\n")
1053
    if hvp[constants.HV_USE_LOCALTIME]:
1054
      config.write("localtime = 1\n")
1055

    
1056
    vif_data = []
1057
    # Note: what is called 'nic_type' here, is used as value for the xen nic
1058
    # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1059
    # the 'vif_type' to avoid a clash of notation.
1060
    nic_type = hvp[constants.HV_NIC_TYPE]
1061

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

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

    
1085
    disk_data = \
1086
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1087

    
1088
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1089
    if iso_path:
1090
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
1091
      disk_data.append(iso)
1092

    
1093
    config.write("disk = [%s]\n" % (",".join(disk_data)))
1094
    # Add PCI passthrough
1095
    pci_pass_arr = []
1096
    pci_pass = hvp[constants.HV_PASSTHROUGH]
1097
    if pci_pass:
1098
      pci_pass_arr = pci_pass.split(";")
1099
      config.write("pci = %s\n" % pci_pass_arr)
1100
    config.write("on_poweroff = 'destroy'\n")
1101
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1102
      config.write("on_reboot = 'restart'\n")
1103
    else:
1104
      config.write("on_reboot = 'destroy'\n")
1105
    config.write("on_crash = 'restart'\n")
1106

    
1107
    return config.getvalue()