Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ ff19ac20

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

364
    @see: L{utils.process.RunCmd}
365

366
    """
367
    cmd = [self._GetCommand()]
368
    cmd.extend(args)
369

    
370
    return self._run_cmd_fn(cmd)
371

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

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

380
    """
381
    return utils.PathJoin(self._cfgdir, instance_name)
382

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

387
    """
388
    raise NotImplementedError
389

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

393
    This version of the function just writes the config file from static data.
394

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

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

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

409
    """
410
    filename = self._ConfigFileName(instance_name)
411

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

    
417
    return file_content
418

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

422
    """
423
    utils.RemoveFile(self._ConfigFileName(instance_name))
424

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

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

    
436
  def _GetInstanceList(self, include_node):
437
    """Wrapper around module level L{_GetInstanceList}.
438

439
    """
440
    return _GetInstanceList(lambda: self._RunXen(["list"]), include_node)
441

    
442
  def ListInstances(self):
443
    """Get the list of running instances.
444

445
    """
446
    instance_list = self._GetInstanceList(False)
447
    names = [info[0] for info in instance_list]
448
    return names
449

    
450
  def GetInstanceInfo(self, instance_name):
451
    """Get instance properties.
452

453
    @param instance_name: the instance name
454

455
    @return: tuple (name, id, memory, vcpus, stat, times)
456

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

    
466
  def GetAllInstancesInfo(self):
467
    """Get properties of all instances.
468

469
    @return: list of tuples (name, id, memory, vcpus, stat, times)
470

471
    """
472
    return self._GetInstanceList(False)
473

    
474
  def _MakeConfigFile(self, instance, startup_memory, block_devices):
475
    """Gather configuration details and write to disk.
476

477
    See L{_GetConfig} for arguments.
478

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

    
486
    self._WriteConfigFile(instance.name, buf.getvalue())
487

    
488
  def StartInstance(self, instance, block_devices, startup_paused):
489
    """Start an instance.
490

491
    """
492
    startup_memory = self._InstanceStartupMemory(instance)
493

    
494
    self._MakeConfigFile(instance, startup_memory, block_devices)
495

    
496
    cmd = ["create"]
497
    if startup_paused:
498
      cmd.append("-p")
499
    cmd.append(self._ConfigFileName(instance.name))
500

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

    
511
  def StopInstance(self, instance, force=False, retry=False, name=None):
512
    """Stop an instance.
513

514
    """
515
    if name is None:
516
      name = instance.name
517

    
518
    return self._StopInstance(name, force)
519

    
520
  def _StopInstance(self, name, force):
521
    """Stop an instance.
522

523
    """
524
    if force:
525
      action = "destroy"
526
    else:
527
      action = "shutdown"
528

    
529
    result = self._RunXen([action, name])
530
    if result.failed:
531
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
532
                                   (name, result.fail_reason, result.output))
533

    
534
    # Remove configuration file if stopping/starting instance was successful
535
    self._RemoveConfigFile(name)
536

    
537
  def RebootInstance(self, instance):
538
    """Reboot an instance.
539

540
    """
541
    ini_info = self.GetInstanceInfo(instance.name)
542

    
543
    if ini_info is None:
544
      raise errors.HypervisorError("Failed to reboot instance %s,"
545
                                   " not running" % instance.name)
546

    
547
    result = self._RunXen(["reboot", instance.name])
548
    if result.failed:
549
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
550
                                   (instance.name, result.fail_reason,
551
                                    result.output))
552

    
553
    def _CheckInstance():
554
      new_info = self.GetInstanceInfo(instance.name)
555

    
556
      # check if the domain ID has changed or the run time has decreased
557
      if (new_info is not None and
558
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
559
        return
560

    
561
      raise utils.RetryAgain()
562

    
563
    try:
564
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
565
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
566
    except utils.RetryTimeout:
567
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
568
                                   " did not reboot in the expected interval" %
569
                                   (instance.name, ))
570

    
571
  def BalloonInstanceMemory(self, instance, mem):
572
    """Balloon an instance memory to a certain value.
573

574
    @type instance: L{objects.Instance}
575
    @param instance: instance to be accepted
576
    @type mem: int
577
    @param mem: actual memory size to use for instance runtime
578

579
    """
580
    result = self._RunXen(["mem-set", instance.name, mem])
581
    if result.failed:
582
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
583
                                   (instance.name, result.fail_reason,
584
                                    result.output))
585

    
586
    # Update configuration file
587
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
588
    cmd.append(self._ConfigFileName(instance.name))
589

    
590
    result = utils.RunCmd(cmd)
591
    if result.failed:
592
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
593
                                   (instance.name, result.fail_reason,
594
                                    result.output))
595

    
596
  def GetNodeInfo(self):
597
    """Return information about the node.
598

599
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
600

601
    """
602
    result = self._RunXen(["info"])
603
    if result.failed:
604
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
605
                    result.output)
606
      return None
607

    
608
    return _GetNodeInfo(result.stdout, self._GetInstanceList)
609

    
610
  @classmethod
611
  def GetInstanceConsole(cls, instance, hvparams, beparams):
612
    """Return a command for connecting to the console of an instance.
613

614
    """
615
    return objects.InstanceConsole(instance=instance.name,
616
                                   kind=constants.CONS_SSH,
617
                                   host=instance.primary_node,
618
                                   user=constants.SSH_CONSOLE_USER,
619
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
620
                                            constants.XEN_CMD, instance.name])
621

    
622
  def Verify(self):
623
    """Verify the hypervisor.
624

625
    For Xen, this verifies that the xend process is running.
626

627
    @return: Problem description if something is wrong, C{None} otherwise
628

629
    """
630
    result = self._RunXen(["info"])
631
    if result.failed:
632
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
633

    
634
    return None
635

    
636
  def MigrationInfo(self, instance):
637
    """Get instance information to perform a migration.
638

639
    @type instance: L{objects.Instance}
640
    @param instance: instance to be migrated
641
    @rtype: string
642
    @return: content of the xen config file
643

644
    """
645
    return self._ReadConfigFile(instance.name)
646

    
647
  def AcceptInstance(self, instance, info, target):
648
    """Prepare to accept an instance.
649

650
    @type instance: L{objects.Instance}
651
    @param instance: instance to be accepted
652
    @type info: string
653
    @param info: content of the xen config file on the source node
654
    @type target: string
655
    @param target: target host (usually ip), on this node
656

657
    """
658
    pass
659

    
660
  def FinalizeMigrationDst(self, instance, info, success):
661
    """Finalize an instance migration.
662

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

666
    @type instance: L{objects.Instance}
667
    @param instance: instance whose migration is being finalized
668
    @type info: string
669
    @param info: content of the xen config file on the source node
670
    @type success: boolean
671
    @param success: whether the migration was a success or a failure
672

673
    """
674
    if success:
675
      self._WriteConfigFile(instance.name, info)
676

    
677
  def MigrateInstance(self, instance, target, live):
678
    """Migrate an instance to a target node.
679

680
    The migration will not be attempted if the instance is not
681
    currently running.
682

683
    @type instance: L{objects.Instance}
684
    @param instance: the instance to be migrated
685
    @type target: string
686
    @param target: ip address of the target node
687
    @type live: boolean
688
    @param live: perform a live migration
689

690
    """
691
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
692

    
693
    # TODO: Pass cluster name via RPC
694
    cluster_name = ssconf.SimpleStore().GetClusterName()
695

    
696
    return self._MigrateInstance(cluster_name, instance.name, target, port,
697
                                 live)
698

    
699
  def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
700
                       _ping_fn=netutils.TcpPing):
701
    """Migrate an instance to a target node.
702

703
    @see: L{MigrateInstance} for details
704

705
    """
706
    if self.GetInstanceInfo(instance_name) is None:
707
      raise errors.HypervisorError("Instance not running, cannot migrate")
708

    
709
    cmd = self._GetCommand()
710

    
711
    if (cmd == constants.XEN_CMD_XM and
712
        not _ping_fn(target, port, live_port_needed=True)):
713
      raise errors.HypervisorError("Remote host %s not listening on port"
714
                                   " %s, cannot migrate" % (target, port))
715

    
716
    args = ["migrate"]
717

    
718
    if cmd == constants.XEN_CMD_XM:
719
      args.extend(["-p", "%d" % port])
720
      if live:
721
        args.append("-l")
722

    
723
    elif cmd == constants.XEN_CMD_XL:
724
      args.extend([
725
        "-s", constants.XL_SSH_CMD % cluster_name,
726
        "-C", self._ConfigFileName(instance_name),
727
        ])
728

    
729
    else:
730
      raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
731

    
732
    args.extend([instance_name, target])
733

    
734
    result = self._RunXen(args)
735
    if result.failed:
736
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
737
                                   (instance_name, result.output))
738

    
739
  def FinalizeMigrationSource(self, instance, success, live):
740
    """Finalize the instance migration on the source node.
741

742
    @type instance: L{objects.Instance}
743
    @param instance: the instance that was migrated
744
    @type success: bool
745
    @param success: whether the migration succeeded or not
746
    @type live: bool
747
    @param live: whether the user requested a live migration or not
748

749
    """
750
    # pylint: disable=W0613
751
    if success:
752
      # remove old xen file after migration succeeded
753
      try:
754
        self._RemoveConfigFile(instance.name)
755
      except EnvironmentError:
756
        logging.exception("Failure while removing instance config file")
757

    
758
  def GetMigrationStatus(self, instance):
759
    """Get the migration status
760

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

765
    @type instance: L{objects.Instance}
766
    @param instance: the instance that is being migrated
767
    @rtype: L{objects.MigrationStatus}
768
    @return: the status of the current migration (one of
769
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
770
             progress info that can be retrieved from the hypervisor
771

772
    """
773
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
774

    
775
  @classmethod
776
  def PowercycleNode(cls):
777
    """Xen-specific powercycle.
778

779
    This first does a Linux reboot (which triggers automatically a Xen
780
    reboot), and if that fails it tries to do a Xen reboot. The reason
781
    we don't try a Xen reboot first is that the xen reboot launches an
782
    external command which connects to the Xen hypervisor, and that
783
    won't work in case the root filesystem is broken and/or the xend
784
    daemon is not working.
785

786
    """
787
    try:
788
      cls.LinuxPowercycle()
789
    finally:
790
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
791

    
792

    
793
class XenPvmHypervisor(XenHypervisor):
794
  """Xen PVM hypervisor interface"""
795

    
796
  PARAMETERS = {
797
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
798
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
799
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
800
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
801
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
802
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
803
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
804
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
805
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
806
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
807
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
808
    constants.HV_REBOOT_BEHAVIOR:
809
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
810
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
811
    constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
812
    constants.HV_CPU_WEIGHT:
813
      (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
814
    constants.HV_XEN_CMD:
815
      hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
816
    }
817

    
818
  def _GetConfig(self, instance, startup_memory, block_devices):
819
    """Write the Xen config file for the instance.
820

821
    """
822
    hvp = instance.hvparams
823
    config = StringIO()
824
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
825

    
826
    # if bootloader is True, use bootloader instead of kernel and ramdisk
827
    # parameters.
828
    if hvp[constants.HV_USE_BOOTLOADER]:
829
      # bootloader handling
830
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
831
      if bootloader_path:
832
        config.write("bootloader = '%s'\n" % bootloader_path)
833
      else:
834
        raise errors.HypervisorError("Bootloader enabled, but missing"
835
                                     " bootloader path")
836

    
837
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
838
      if bootloader_args:
839
        config.write("bootargs = '%s'\n" % bootloader_args)
840
    else:
841
      # kernel handling
842
      kpath = hvp[constants.HV_KERNEL_PATH]
843
      config.write("kernel = '%s'\n" % kpath)
844

    
845
      # initrd handling
846
      initrd_path = hvp[constants.HV_INITRD_PATH]
847
      if initrd_path:
848
        config.write("ramdisk = '%s'\n" % initrd_path)
849

    
850
    # rest of the settings
851
    config.write("memory = %d\n" % startup_memory)
852
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
853
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
854
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
855
    if cpu_pinning:
856
      config.write("%s\n" % cpu_pinning)
857
    cpu_cap = hvp[constants.HV_CPU_CAP]
858
    if cpu_cap:
859
      config.write("cpu_cap=%d\n" % cpu_cap)
860
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
861
    if cpu_weight:
862
      config.write("cpu_weight=%d\n" % cpu_weight)
863

    
864
    config.write("name = '%s'\n" % instance.name)
865

    
866
    vif_data = []
867
    for nic in instance.nics:
868
      nic_str = "mac=%s" % (nic.mac)
869
      ip = getattr(nic, "ip", None)
870
      if ip is not None:
871
        nic_str += ", ip=%s" % ip
872
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
873
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
874
      vif_data.append("'%s'" % nic_str)
875

    
876
    disk_data = \
877
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
878

    
879
    config.write("vif = [%s]\n" % ",".join(vif_data))
880
    config.write("disk = [%s]\n" % ",".join(disk_data))
881

    
882
    if hvp[constants.HV_ROOT_PATH]:
883
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
884
    config.write("on_poweroff = 'destroy'\n")
885
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
886
      config.write("on_reboot = 'restart'\n")
887
    else:
888
      config.write("on_reboot = 'destroy'\n")
889
    config.write("on_crash = 'restart'\n")
890
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
891

    
892
    return config.getvalue()
893

    
894

    
895
class XenHvmHypervisor(XenHypervisor):
896
  """Xen HVM hypervisor interface"""
897

    
898
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
899
    pathutils.VNC_PASSWORD_FILE,
900
    ]
901
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
902
    pathutils.VNC_PASSWORD_FILE,
903
    ]
904

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

    
942
  def _GetConfig(self, instance, startup_memory, block_devices):
943
    """Create a Xen 3.1 HVM config file.
944

945
    """
946
    hvp = instance.hvparams
947

    
948
    config = StringIO()
949

    
950
    # kernel handling
951
    kpath = hvp[constants.HV_KERNEL_PATH]
952
    config.write("kernel = '%s'\n" % kpath)
953

    
954
    config.write("builder = 'hvm'\n")
955
    config.write("memory = %d\n" % startup_memory)
956
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
957
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
958
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
959
    if cpu_pinning:
960
      config.write("%s\n" % cpu_pinning)
961
    cpu_cap = hvp[constants.HV_CPU_CAP]
962
    if cpu_cap:
963
      config.write("cpu_cap=%d\n" % cpu_cap)
964
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
965
    if cpu_weight:
966
      config.write("cpu_weight=%d\n" % cpu_weight)
967

    
968
    config.write("name = '%s'\n" % instance.name)
969
    if hvp[constants.HV_PAE]:
970
      config.write("pae = 1\n")
971
    else:
972
      config.write("pae = 0\n")
973
    if hvp[constants.HV_ACPI]:
974
      config.write("acpi = 1\n")
975
    else:
976
      config.write("acpi = 0\n")
977
    config.write("apic = 1\n")
978
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
979
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
980
    config.write("sdl = 0\n")
981
    config.write("usb = 1\n")
982
    config.write("usbdevice = 'tablet'\n")
983
    config.write("vnc = 1\n")
984
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
985
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
986
    else:
987
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
988

    
989
    if instance.network_port > constants.VNC_BASE_PORT:
990
      display = instance.network_port - constants.VNC_BASE_PORT
991
      config.write("vncdisplay = %s\n" % display)
992
      config.write("vncunused = 0\n")
993
    else:
994
      config.write("# vncdisplay = 1\n")
995
      config.write("vncunused = 1\n")
996

    
997
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
998
    try:
999
      password = utils.ReadFile(vnc_pwd_file)
1000
    except EnvironmentError, err:
1001
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1002
                                   (vnc_pwd_file, err))
1003

    
1004
    config.write("vncpasswd = '%s'\n" % password.rstrip())
1005

    
1006
    config.write("serial = 'pty'\n")
1007
    if hvp[constants.HV_USE_LOCALTIME]:
1008
      config.write("localtime = 1\n")
1009

    
1010
    vif_data = []
1011
    # Note: what is called 'nic_type' here, is used as value for the xen nic
1012
    # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1013
    # the 'vif_type' to avoid a clash of notation.
1014
    nic_type = hvp[constants.HV_NIC_TYPE]
1015

    
1016
    if nic_type is None:
1017
      vif_type_str = ""
1018
      if hvp[constants.HV_VIF_TYPE]:
1019
        vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
1020
      # ensure old instances don't change
1021
      nic_type_str = vif_type_str
1022
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
1023
      nic_type_str = ", type=paravirtualized"
1024
    else:
1025
      # parameter 'model' is only valid with type 'ioemu'
1026
      nic_type_str = ", model=%s, type=%s" % \
1027
        (nic_type, constants.HT_HVM_VIF_IOEMU)
1028
    for nic in instance.nics:
1029
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1030
      ip = getattr(nic, "ip", None)
1031
      if ip is not None:
1032
        nic_str += ", ip=%s" % ip
1033
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1034
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1035
      vif_data.append("'%s'" % nic_str)
1036

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

    
1039
    disk_data = \
1040
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1041

    
1042
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1043
    if iso_path:
1044
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
1045
      disk_data.append(iso)
1046

    
1047
    config.write("disk = [%s]\n" % (",".join(disk_data)))
1048
    # Add PCI passthrough
1049
    pci_pass_arr = []
1050
    pci_pass = hvp[constants.HV_PASSTHROUGH]
1051
    if pci_pass:
1052
      pci_pass_arr = pci_pass.split(";")
1053
      config.write("pci = %s\n" % pci_pass_arr)
1054
    config.write("on_poweroff = 'destroy'\n")
1055
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1056
      config.write("on_reboot = 'restart'\n")
1057
    else:
1058
      config.write("on_reboot = 'destroy'\n")
1059
    config.write("on_crash = 'restart'\n")
1060

    
1061
    return config.getvalue()