Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 58e356a9

History | View | Annotate | Download (34.3 kB)

1
#
2
#
3

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

    
21

    
22
"""Xen hypervisors
23

24
"""
25

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

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

    
39

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

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

    
52

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

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

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

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

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

    
84

    
85
def _RunInstanceList(fn, instance_list_errors):
86
  """Helper function for L{_GetInstanceList} to retrieve the list of instances
87
  from xen.
88

89
  @type fn: callable
90
  @param fn: Function to query xen for the list of instances
91
  @type instance_list_errors: list
92
  @param instance_list_errors: Error list
93
  @rtype: list
94

95
  """
96
  result = fn()
97
  if result.failed:
98
    logging.error("Retrieving the instance list from xen failed (%s): %s",
99
                  result.fail_reason, result.output)
100
    instance_list_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 _GetInstanceList(fn, include_node, _timeout=5):
146
  """Return the list of running instances.
147

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

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

    
159
      errmsg = ("listing instances failed, timeout exceeded (%s): %s" %
160
                (instance_list_result.fail_reason, instance_list_result.output))
161
    else:
162
      errmsg = "listing instances failed"
163

    
164
    raise errors.HypervisorError(errmsg)
165

    
166
  return _ParseXmList(lines, include_node)
167

    
168

    
169
def _ParseNodeInfo(info):
170
  """Return information about the node.
171

172
  @return: a dict with the following keys (memory values in MiB):
173
        - memory_total: the total memory size on the node
174
        - memory_free: the available memory on the node for instances
175
        - nr_cpus: total number of CPUs
176
        - nr_nodes: in a NUMA system, the number of domains
177
        - nr_sockets: the number of physical CPU sockets in the node
178
        - hv_version: the hypervisor version in the form (major, minor)
179

180
  """
181
  result = {}
182
  cores_per_socket = threads_per_core = nr_cpus = None
183
  xen_major, xen_minor = None, None
184
  memory_total = None
185
  memory_free = None
186

    
187
  for line in info.splitlines():
188
    fields = line.split(":", 1)
189

    
190
    if len(fields) < 2:
191
      continue
192

    
193
    (key, val) = map(lambda s: s.strip(), fields)
194

    
195
    # Note: in Xen 3, memory has changed to total_memory
196
    if key in ("memory", "total_memory"):
197
      memory_total = int(val)
198
    elif key == "free_memory":
199
      memory_free = int(val)
200
    elif key == "nr_cpus":
201
      nr_cpus = result["cpu_total"] = int(val)
202
    elif key == "nr_nodes":
203
      result["cpu_nodes"] = int(val)
204
    elif key == "cores_per_socket":
205
      cores_per_socket = int(val)
206
    elif key == "threads_per_core":
207
      threads_per_core = int(val)
208
    elif key == "xen_major":
209
      xen_major = int(val)
210
    elif key == "xen_minor":
211
      xen_minor = int(val)
212

    
213
  if None not in [cores_per_socket, threads_per_core, nr_cpus]:
214
    result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
215

    
216
  if memory_free is not None:
217
    result["memory_free"] = memory_free
218

    
219
  if memory_total is not None:
220
    result["memory_total"] = memory_total
221

    
222
  if not (xen_major is None or xen_minor is None):
223
    result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
224

    
225
  return result
226

    
227

    
228
def _MergeInstanceInfo(info, fn):
229
  """Updates node information from L{_ParseNodeInfo} with instance info.
230

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

237
  """
238
  total_instmem = 0
239

    
240
  for (name, _, mem, vcpus, _, _) in fn(True):
241
    if name == _DOM0_NAME:
242
      info["memory_dom0"] = mem
243
      info["dom0_cpus"] = vcpus
244

    
245
    # Include Dom0 in total memory usage
246
    total_instmem += mem
247

    
248
  memory_free = info.get("memory_free")
249
  memory_total = info.get("memory_total")
250

    
251
  # Calculate memory used by hypervisor
252
  if None not in [memory_total, memory_free, total_instmem]:
253
    info["memory_hv"] = memory_total - memory_free - total_instmem
254

    
255
  return info
256

    
257

    
258
def _GetNodeInfo(info, fn):
259
  """Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}.
260

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

    
264

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

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

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

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

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

    
284
  disk_data = []
285

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

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

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

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

    
301
  return disk_data
302

    
303

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

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

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

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

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

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

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

    
337
    self._cmd = _cmd
338

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

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

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

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

    
359
    return cmd
360

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

364
    @type hvparams: dict of strings
365
    @param hvparams: dictionary of hypervisor params
366
    @see: L{utils.process.RunCmd}
367

368
    """
369
    cmd = [self._GetCommand(hvparams=hvparams)]
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 _GetInstanceList(self, include_node, hvparams=None):
439
    """Wrapper around module level L{_GetInstanceList}.
440

441
    """
442
    return _GetInstanceList(lambda: self._RunXen(["list"], hvparams=hvparams),
443
                            include_node)
444

    
445
  def ListInstances(self, hvparams=None):
446
    """Get the list of running instances.
447

448
    """
449
    instance_list = self._GetInstanceList(False, hvparams=hvparams)
450
    names = [info[0] for info in instance_list]
451
    return names
452

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

456
    @param instance_name: the instance name
457

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

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

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

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

474
    """
475
    return self._GetInstanceList(False)
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, hvparams=instance.hvparams)
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, instance.hvparams)
522

    
523
  def _StopInstance(self, name, force, hvparams):
524
    """Stop an instance.
525

526
    @type name: string
527
    @param name: name of the instance to be shutdown
528
    @type force: boolean
529
    @param force: flag specifying whether shutdown should be forced
530
    @type hvparams: dict of string
531
    @param hvparams: hypervisor parameters of the instance
532

533
    """
534
    if force:
535
      action = "destroy"
536
    else:
537
      action = "shutdown"
538

    
539
    result = self._RunXen([action, name], hvparams=hvparams)
540
    if result.failed:
541
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
542
                                   (name, result.fail_reason, result.output))
543

    
544
    # Remove configuration file if stopping/starting instance was successful
545
    self._RemoveConfigFile(name)
546

    
547
  def RebootInstance(self, instance):
548
    """Reboot an instance.
549

550
    """
551
    ini_info = self.GetInstanceInfo(instance.name)
552

    
553
    if ini_info is None:
554
      raise errors.HypervisorError("Failed to reboot instance %s,"
555
                                   " not running" % instance.name)
556

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

    
563
    def _CheckInstance():
564
      new_info = self.GetInstanceInfo(instance.name)
565

    
566
      # check if the domain ID has changed or the run time has decreased
567
      if (new_info is not None and
568
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
569
        return
570

    
571
      raise utils.RetryAgain()
572

    
573
    try:
574
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
575
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
576
    except utils.RetryTimeout:
577
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
578
                                   " did not reboot in the expected interval" %
579
                                   (instance.name, ))
580

    
581
  def BalloonInstanceMemory(self, instance, mem):
582
    """Balloon an instance memory to a certain value.
583

584
    @type instance: L{objects.Instance}
585
    @param instance: instance to be accepted
586
    @type mem: int
587
    @param mem: actual memory size to use for instance runtime
588

589
    """
590
    result = self._RunXen(["mem-set", instance.name, mem],
591
                          hvparams=instance.hvparams)
592
    if result.failed:
593
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
594
                                   (instance.name, result.fail_reason,
595
                                    result.output))
596

    
597
    # Update configuration file
598
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
599
    cmd.append(self._ConfigFileName(instance.name))
600

    
601
    result = utils.RunCmd(cmd)
602
    if result.failed:
603
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
604
                                   (instance.name, result.fail_reason,
605
                                    result.output))
606

    
607
  def GetNodeInfo(self):
608
    """Return information about the node.
609

610
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
611

612
    """
613
    result = self._RunXen(["info"])
614
    if result.failed:
615
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
616
                    result.output)
617
      return None
618

    
619
    return _GetNodeInfo(result.stdout, self._GetInstanceList)
620

    
621
  @classmethod
622
  def GetInstanceConsole(cls, instance, hvparams, beparams):
623
    """Return a command for connecting to the console of an instance.
624

625
    """
626
    return objects.InstanceConsole(instance=instance.name,
627
                                   kind=constants.CONS_SSH,
628
                                   host=instance.primary_node,
629
                                   user=constants.SSH_CONSOLE_USER,
630
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
631
                                            constants.XEN_CMD, instance.name])
632

    
633
  def Verify(self):
634
    """Verify the hypervisor.
635

636
    For Xen, this verifies that the xend process is running.
637

638
    @return: Problem description if something is wrong, C{None} otherwise
639

640
    """
641
    result = self._RunXen(["info"])
642
    if result.failed:
643
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
644

    
645
    return None
646

    
647
  def MigrationInfo(self, instance):
648
    """Get instance information to perform a migration.
649

650
    @type instance: L{objects.Instance}
651
    @param instance: instance to be migrated
652
    @rtype: string
653
    @return: content of the xen config file
654

655
    """
656
    return self._ReadConfigFile(instance.name)
657

    
658
  def AcceptInstance(self, instance, info, target):
659
    """Prepare to accept an instance.
660

661
    @type instance: L{objects.Instance}
662
    @param instance: instance to be accepted
663
    @type info: string
664
    @param info: content of the xen config file on the source node
665
    @type target: string
666
    @param target: target host (usually ip), on this node
667

668
    """
669
    pass
670

    
671
  def FinalizeMigrationDst(self, instance, info, success):
672
    """Finalize an instance migration.
673

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

677
    @type instance: L{objects.Instance}
678
    @param instance: instance whose migration is being finalized
679
    @type info: string
680
    @param info: content of the xen config file on the source node
681
    @type success: boolean
682
    @param success: whether the migration was a success or a failure
683

684
    """
685
    if success:
686
      self._WriteConfigFile(instance.name, info)
687

    
688
  def MigrateInstance(self, instance, target, live):
689
    """Migrate an instance to a target node.
690

691
    The migration will not be attempted if the instance is not
692
    currently running.
693

694
    @type instance: L{objects.Instance}
695
    @param instance: the instance to be migrated
696
    @type target: string
697
    @param target: ip address of the target node
698
    @type live: boolean
699
    @param live: perform a live migration
700

701
    """
702
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
703

    
704
    # TODO: Pass cluster name via RPC
705
    cluster_name = ssconf.SimpleStore().GetClusterName()
706

    
707
    return self._MigrateInstance(cluster_name, instance.name, target, port,
708
                                 live)
709

    
710
  def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
711
                       _ping_fn=netutils.TcpPing):
712
    """Migrate an instance to a target node.
713

714
    @see: L{MigrateInstance} for details
715

716
    """
717
    if self.GetInstanceInfo(instance_name) is None:
718
      raise errors.HypervisorError("Instance not running, cannot migrate")
719

    
720
    cmd = self._GetCommand()
721

    
722
    if (cmd == constants.XEN_CMD_XM and
723
        not _ping_fn(target, port, live_port_needed=True)):
724
      raise errors.HypervisorError("Remote host %s not listening on port"
725
                                   " %s, cannot migrate" % (target, port))
726

    
727
    args = ["migrate"]
728

    
729
    if cmd == constants.XEN_CMD_XM:
730
      args.extend(["-p", "%d" % port])
731
      if live:
732
        args.append("-l")
733

    
734
    elif cmd == constants.XEN_CMD_XL:
735
      args.extend([
736
        "-s", constants.XL_SSH_CMD % cluster_name,
737
        "-C", self._ConfigFileName(instance_name),
738
        ])
739

    
740
    else:
741
      raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
742

    
743
    args.extend([instance_name, target])
744

    
745
    result = self._RunXen(args, hvparams=hvparams)
746
    if result.failed:
747
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
748
                                   (instance_name, result.output))
749

    
750
  def FinalizeMigrationSource(self, instance, success, live):
751
    """Finalize the instance migration on the source node.
752

753
    @type instance: L{objects.Instance}
754
    @param instance: the instance that was migrated
755
    @type success: bool
756
    @param success: whether the migration succeeded or not
757
    @type live: bool
758
    @param live: whether the user requested a live migration or not
759

760
    """
761
    # pylint: disable=W0613
762
    if success:
763
      # remove old xen file after migration succeeded
764
      try:
765
        self._RemoveConfigFile(instance.name)
766
      except EnvironmentError:
767
        logging.exception("Failure while removing instance config file")
768

    
769
  def GetMigrationStatus(self, instance):
770
    """Get the migration status
771

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

776
    @type instance: L{objects.Instance}
777
    @param instance: the instance that is being migrated
778
    @rtype: L{objects.MigrationStatus}
779
    @return: the status of the current migration (one of
780
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
781
             progress info that can be retrieved from the hypervisor
782

783
    """
784
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
785

    
786
  @classmethod
787
  def PowercycleNode(cls):
788
    """Xen-specific powercycle.
789

790
    This first does a Linux reboot (which triggers automatically a Xen
791
    reboot), and if that fails it tries to do a Xen reboot. The reason
792
    we don't try a Xen reboot first is that the xen reboot launches an
793
    external command which connects to the Xen hypervisor, and that
794
    won't work in case the root filesystem is broken and/or the xend
795
    daemon is not working.
796

797
    """
798
    try:
799
      cls.LinuxPowercycle()
800
    finally:
801
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
802

    
803

    
804
class XenPvmHypervisor(XenHypervisor):
805
  """Xen PVM hypervisor interface"""
806

    
807
  PARAMETERS = {
808
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
809
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
810
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
811
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
812
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
813
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
814
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
815
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
816
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
817
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
818
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
819
    constants.HV_REBOOT_BEHAVIOR:
820
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
821
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
822
    constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
823
    constants.HV_CPU_WEIGHT:
824
      (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
825
    constants.HV_XEN_CMD:
826
      hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
827
    }
828

    
829
  def _GetConfig(self, instance, startup_memory, block_devices):
830
    """Write the Xen config file for the instance.
831

832
    """
833
    hvp = instance.hvparams
834
    config = StringIO()
835
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
836

    
837
    # if bootloader is True, use bootloader instead of kernel and ramdisk
838
    # parameters.
839
    if hvp[constants.HV_USE_BOOTLOADER]:
840
      # bootloader handling
841
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
842
      if bootloader_path:
843
        config.write("bootloader = '%s'\n" % bootloader_path)
844
      else:
845
        raise errors.HypervisorError("Bootloader enabled, but missing"
846
                                     " bootloader path")
847

    
848
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
849
      if bootloader_args:
850
        config.write("bootargs = '%s'\n" % bootloader_args)
851
    else:
852
      # kernel handling
853
      kpath = hvp[constants.HV_KERNEL_PATH]
854
      config.write("kernel = '%s'\n" % kpath)
855

    
856
      # initrd handling
857
      initrd_path = hvp[constants.HV_INITRD_PATH]
858
      if initrd_path:
859
        config.write("ramdisk = '%s'\n" % initrd_path)
860

    
861
    # rest of the settings
862
    config.write("memory = %d\n" % startup_memory)
863
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
864
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
865
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
866
    if cpu_pinning:
867
      config.write("%s\n" % cpu_pinning)
868
    cpu_cap = hvp[constants.HV_CPU_CAP]
869
    if cpu_cap:
870
      config.write("cpu_cap=%d\n" % cpu_cap)
871
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
872
    if cpu_weight:
873
      config.write("cpu_weight=%d\n" % cpu_weight)
874

    
875
    config.write("name = '%s'\n" % instance.name)
876

    
877
    vif_data = []
878
    for nic in instance.nics:
879
      nic_str = "mac=%s" % (nic.mac)
880
      ip = getattr(nic, "ip", None)
881
      if ip is not None:
882
        nic_str += ", ip=%s" % ip
883
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
884
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
885
      vif_data.append("'%s'" % nic_str)
886

    
887
    disk_data = \
888
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
889

    
890
    config.write("vif = [%s]\n" % ",".join(vif_data))
891
    config.write("disk = [%s]\n" % ",".join(disk_data))
892

    
893
    if hvp[constants.HV_ROOT_PATH]:
894
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
895
    config.write("on_poweroff = 'destroy'\n")
896
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
897
      config.write("on_reboot = 'restart'\n")
898
    else:
899
      config.write("on_reboot = 'destroy'\n")
900
    config.write("on_crash = 'restart'\n")
901
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
902

    
903
    return config.getvalue()
904

    
905

    
906
class XenHvmHypervisor(XenHypervisor):
907
  """Xen HVM hypervisor interface"""
908

    
909
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
910
    pathutils.VNC_PASSWORD_FILE,
911
    ]
912
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
913
    pathutils.VNC_PASSWORD_FILE,
914
    ]
915

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

    
953
  def _GetConfig(self, instance, startup_memory, block_devices):
954
    """Create a Xen 3.1 HVM config file.
955

956
    """
957
    hvp = instance.hvparams
958

    
959
    config = StringIO()
960

    
961
    # kernel handling
962
    kpath = hvp[constants.HV_KERNEL_PATH]
963
    config.write("kernel = '%s'\n" % kpath)
964

    
965
    config.write("builder = 'hvm'\n")
966
    config.write("memory = %d\n" % startup_memory)
967
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
968
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
969
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
970
    if cpu_pinning:
971
      config.write("%s\n" % cpu_pinning)
972
    cpu_cap = hvp[constants.HV_CPU_CAP]
973
    if cpu_cap:
974
      config.write("cpu_cap=%d\n" % cpu_cap)
975
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
976
    if cpu_weight:
977
      config.write("cpu_weight=%d\n" % cpu_weight)
978

    
979
    config.write("name = '%s'\n" % instance.name)
980
    if hvp[constants.HV_PAE]:
981
      config.write("pae = 1\n")
982
    else:
983
      config.write("pae = 0\n")
984
    if hvp[constants.HV_ACPI]:
985
      config.write("acpi = 1\n")
986
    else:
987
      config.write("acpi = 0\n")
988
    config.write("apic = 1\n")
989
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
990
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
991
    config.write("sdl = 0\n")
992
    config.write("usb = 1\n")
993
    config.write("usbdevice = 'tablet'\n")
994
    config.write("vnc = 1\n")
995
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
996
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
997
    else:
998
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
999

    
1000
    if instance.network_port > constants.VNC_BASE_PORT:
1001
      display = instance.network_port - constants.VNC_BASE_PORT
1002
      config.write("vncdisplay = %s\n" % display)
1003
      config.write("vncunused = 0\n")
1004
    else:
1005
      config.write("# vncdisplay = 1\n")
1006
      config.write("vncunused = 1\n")
1007

    
1008
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
1009
    try:
1010
      password = utils.ReadFile(vnc_pwd_file)
1011
    except EnvironmentError, err:
1012
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1013
                                   (vnc_pwd_file, err))
1014

    
1015
    config.write("vncpasswd = '%s'\n" % password.rstrip())
1016

    
1017
    config.write("serial = 'pty'\n")
1018
    if hvp[constants.HV_USE_LOCALTIME]:
1019
      config.write("localtime = 1\n")
1020

    
1021
    vif_data = []
1022
    # Note: what is called 'nic_type' here, is used as value for the xen nic
1023
    # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1024
    # the 'vif_type' to avoid a clash of notation.
1025
    nic_type = hvp[constants.HV_NIC_TYPE]
1026

    
1027
    if nic_type is None:
1028
      vif_type_str = ""
1029
      if hvp[constants.HV_VIF_TYPE]:
1030
        vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
1031
      # ensure old instances don't change
1032
      nic_type_str = vif_type_str
1033
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
1034
      nic_type_str = ", type=paravirtualized"
1035
    else:
1036
      # parameter 'model' is only valid with type 'ioemu'
1037
      nic_type_str = ", model=%s, type=%s" % \
1038
        (nic_type, constants.HT_HVM_VIF_IOEMU)
1039
    for nic in instance.nics:
1040
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1041
      ip = getattr(nic, "ip", None)
1042
      if ip is not None:
1043
        nic_str += ", ip=%s" % ip
1044
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1045
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1046
      vif_data.append("'%s'" % nic_str)
1047

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

    
1050
    disk_data = \
1051
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1052

    
1053
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1054
    if iso_path:
1055
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
1056
      disk_data.append(iso)
1057

    
1058
    config.write("disk = [%s]\n" % (",".join(disk_data)))
1059
    # Add PCI passthrough
1060
    pci_pass_arr = []
1061
    pci_pass = hvp[constants.HV_PASSTHROUGH]
1062
    if pci_pass:
1063
      pci_pass_arr = pci_pass.split(";")
1064
      config.write("pci = %s\n" % pci_pass_arr)
1065
    config.write("on_poweroff = 'destroy'\n")
1066
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1067
      config.write("on_reboot = 'restart'\n")
1068
    else:
1069
      config.write("on_reboot = 'destroy'\n")
1070
    config.write("on_crash = 'restart'\n")
1071

    
1072
    return config.getvalue()