Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ ffc27116

History | View | Annotate | Download (33.2 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 _RunXmList(fn, xmllist_errors):
86
  """Helper function for L{_GetXmList} to run "xm list".
87

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

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

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

    
105

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

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

116
  """
117
  result = []
118

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

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

    
141
  return result
142

    
143

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

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

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

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

    
163
    raise errors.HypervisorError(errmsg)
164

    
165
  return _ParseXmList(lines, include_node)
166

    
167

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

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

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

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

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

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

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

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

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

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

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

    
224
  return result
225

    
226

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

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

236
  """
237
  total_instmem = 0
238

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

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

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

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

    
254
  return info
255

    
256

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

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

    
263

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

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

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

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

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

    
283
  disk_data = []
284

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

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

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

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

    
300
  return disk_data
301

    
302

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

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

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

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

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

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

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

    
336
    self._cmd = _cmd
337

    
338
  def _GetCommand(self):
339
    """Returns Xen command to use.
340

341
    """
342
    if self._cmd is None:
343
      # TODO: Make command a hypervisor parameter
344
      cmd = constants.XEN_CMD
345
    else:
346
      cmd = self._cmd
347

    
348
    if cmd not in constants.KNOWN_XEN_COMMANDS:
349
      raise errors.ProgrammerError("Unknown Xen command '%s'" % cmd)
350

    
351
    return cmd
352

    
353
  def _RunXen(self, args):
354
    """Wrapper around L{utils.process.RunCmd} to run Xen command.
355

356
    @see: L{utils.process.RunCmd}
357

358
    """
359
    cmd = [self._GetCommand()]
360
    cmd.extend(args)
361

    
362
    return self._run_cmd_fn(cmd)
363

    
364
  def _ConfigFileName(self, instance_name):
365
    """Get the config file name for an instance.
366

367
    @param instance_name: instance name
368
    @type instance_name: str
369
    @return: fully qualified path to instance config file
370
    @rtype: str
371

372
    """
373
    return utils.PathJoin(self._cfgdir, instance_name)
374

    
375
  @classmethod
376
  def _GetConfig(cls, instance, startup_memory, block_devices):
377
    """Build Xen configuration for an instance.
378

379
    """
380
    raise NotImplementedError
381

    
382
  def _WriteConfigFile(self, instance_name, data):
383
    """Write the Xen config file for the instance.
384

385
    This version of the function just writes the config file from static data.
386

387
    """
388
    # just in case it exists
389
    utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
390

    
391
    cfg_file = self._ConfigFileName(instance_name)
392
    try:
393
      utils.WriteFile(cfg_file, data=data)
394
    except EnvironmentError, err:
395
      raise errors.HypervisorError("Cannot write Xen instance configuration"
396
                                   " file %s: %s" % (cfg_file, err))
397

    
398
  def _ReadConfigFile(self, instance_name):
399
    """Returns the contents of the instance config file.
400

401
    """
402
    filename = self._ConfigFileName(instance_name)
403

    
404
    try:
405
      file_content = utils.ReadFile(filename)
406
    except EnvironmentError, err:
407
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
408

    
409
    return file_content
410

    
411
  def _RemoveConfigFile(self, instance_name):
412
    """Remove the xen configuration file.
413

414
    """
415
    utils.RemoveFile(self._ConfigFileName(instance_name))
416

    
417
  def _StashConfigFile(self, instance_name):
418
    """Move the Xen config file to the log directory and return its new path.
419

420
    """
421
    old_filename = self._ConfigFileName(instance_name)
422
    base = ("%s-%s" %
423
            (instance_name, utils.TimestampForFilename()))
424
    new_filename = utils.PathJoin(pathutils.LOG_XEN_DIR, base)
425
    utils.RenameFile(old_filename, new_filename)
426
    return new_filename
427

    
428
  def _GetXmList(self, include_node):
429
    """Wrapper around module level L{_GetXmList}.
430

431
    """
432
    return _GetXmList(lambda: self._RunXen(["list"]), include_node)
433

    
434
  def ListInstances(self):
435
    """Get the list of running instances.
436

437
    """
438
    xm_list = self._GetXmList(False)
439
    names = [info[0] for info in xm_list]
440
    return names
441

    
442
  def GetInstanceInfo(self, instance_name):
443
    """Get instance properties.
444

445
    @param instance_name: the instance name
446

447
    @return: tuple (name, id, memory, vcpus, stat, times)
448

449
    """
450
    xm_list = self._GetXmList(instance_name == _DOM0_NAME)
451
    result = None
452
    for data in xm_list:
453
      if data[0] == instance_name:
454
        result = data
455
        break
456
    return result
457

    
458
  def GetAllInstancesInfo(self):
459
    """Get properties of all instances.
460

461
    @return: list of tuples (name, id, memory, vcpus, stat, times)
462

463
    """
464
    xm_list = self._GetXmList(False)
465
    return xm_list
466

    
467
  def _MakeConfigFile(self, instance, startup_memory, block_devices):
468
    """Gather configuration details and write to disk.
469

470
    See L{_GetConfig} for arguments.
471

472
    """
473
    buf = StringIO()
474
    buf.write("# Automatically generated by Ganeti. Do not edit!\n")
475
    buf.write("\n")
476
    buf.write(self._GetConfig(instance, startup_memory, block_devices))
477
    buf.write("\n")
478

    
479
    self._WriteConfigFile(instance.name, buf.getvalue())
480

    
481
  def StartInstance(self, instance, block_devices, startup_paused):
482
    """Start an instance.
483

484
    """
485
    startup_memory = self._InstanceStartupMemory(instance)
486

    
487
    self._MakeConfigFile(instance, startup_memory, block_devices)
488

    
489
    cmd = ["create"]
490
    if startup_paused:
491
      cmd.append("-p")
492
    cmd.append(self._ConfigFileName(instance.name))
493

    
494
    result = self._RunXen(cmd)
495
    if result.failed:
496
      # Move the Xen configuration file to the log directory to avoid
497
      # leaving a stale config file behind.
498
      stashed_config = self._StashConfigFile(instance.name)
499
      raise errors.HypervisorError("Failed to start instance %s: %s (%s). Moved"
500
                                   " config file to %s" %
501
                                   (instance.name, result.fail_reason,
502
                                    result.output, stashed_config))
503

    
504
  def StopInstance(self, instance, force=False, retry=False, name=None):
505
    """Stop an instance.
506

507
    """
508
    if name is None:
509
      name = instance.name
510

    
511
    return self._StopInstance(name, force)
512

    
513
  def _StopInstance(self, name, force):
514
    """Stop an instance.
515

516
    """
517
    if force:
518
      action = "destroy"
519
    else:
520
      action = "shutdown"
521

    
522
    result = self._RunXen([action, name])
523
    if result.failed:
524
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
525
                                   (name, result.fail_reason, result.output))
526

    
527
    # Remove configuration file if stopping/starting instance was successful
528
    self._RemoveConfigFile(name)
529

    
530
  def RebootInstance(self, instance):
531
    """Reboot an instance.
532

533
    """
534
    ini_info = self.GetInstanceInfo(instance.name)
535

    
536
    if ini_info is None:
537
      raise errors.HypervisorError("Failed to reboot instance %s,"
538
                                   " not running" % instance.name)
539

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

    
546
    def _CheckInstance():
547
      new_info = self.GetInstanceInfo(instance.name)
548

    
549
      # check if the domain ID has changed or the run time has decreased
550
      if (new_info is not None and
551
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
552
        return
553

    
554
      raise utils.RetryAgain()
555

    
556
    try:
557
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
558
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
559
    except utils.RetryTimeout:
560
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
561
                                   " did not reboot in the expected interval" %
562
                                   (instance.name, ))
563

    
564
  def BalloonInstanceMemory(self, instance, mem):
565
    """Balloon an instance memory to a certain value.
566

567
    @type instance: L{objects.Instance}
568
    @param instance: instance to be accepted
569
    @type mem: int
570
    @param mem: actual memory size to use for instance runtime
571

572
    """
573
    result = self._RunXen(["mem-set", instance.name, mem])
574
    if result.failed:
575
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
576
                                   (instance.name, result.fail_reason,
577
                                    result.output))
578

    
579
    # Update configuration file
580
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
581
    cmd.append(self._ConfigFileName(instance.name))
582

    
583
    result = utils.RunCmd(cmd)
584
    if result.failed:
585
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
586
                                   (instance.name, result.fail_reason,
587
                                    result.output))
588

    
589
  def GetNodeInfo(self):
590
    """Return information about the node.
591

592
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
593

594
    """
595
    result = self._RunXen(["info"])
596
    if result.failed:
597
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
598
                    result.output)
599
      return None
600

    
601
    return _GetNodeInfo(result.stdout, self._GetXmList)
602

    
603
  @classmethod
604
  def GetInstanceConsole(cls, instance, hvparams, beparams):
605
    """Return a command for connecting to the console of an instance.
606

607
    """
608
    return objects.InstanceConsole(instance=instance.name,
609
                                   kind=constants.CONS_SSH,
610
                                   host=instance.primary_node,
611
                                   user=constants.SSH_CONSOLE_USER,
612
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
613
                                            constants.XEN_CMD, instance.name])
614

    
615
  def Verify(self):
616
    """Verify the hypervisor.
617

618
    For Xen, this verifies that the xend process is running.
619

620
    @return: Problem description if something is wrong, C{None} otherwise
621

622
    """
623
    result = self._RunXen(["info"])
624
    if result.failed:
625
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
626

    
627
    return None
628

    
629
  def MigrationInfo(self, instance):
630
    """Get instance information to perform a migration.
631

632
    @type instance: L{objects.Instance}
633
    @param instance: instance to be migrated
634
    @rtype: string
635
    @return: content of the xen config file
636

637
    """
638
    return self._ReadConfigFile(instance.name)
639

    
640
  def AcceptInstance(self, instance, info, target):
641
    """Prepare to accept an instance.
642

643
    @type instance: L{objects.Instance}
644
    @param instance: instance to be accepted
645
    @type info: string
646
    @param info: content of the xen config file on the source node
647
    @type target: string
648
    @param target: target host (usually ip), on this node
649

650
    """
651
    pass
652

    
653
  def FinalizeMigrationDst(self, instance, info, success):
654
    """Finalize an instance migration.
655

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

659
    @type instance: L{objects.Instance}
660
    @param instance: instance whose migration is being finalized
661
    @type info: string
662
    @param info: content of the xen config file on the source node
663
    @type success: boolean
664
    @param success: whether the migration was a success or a failure
665

666
    """
667
    if success:
668
      self._WriteConfigFile(instance.name, info)
669

    
670
  def MigrateInstance(self, instance, target, live):
671
    """Migrate an instance to a target node.
672

673
    The migration will not be attempted if the instance is not
674
    currently running.
675

676
    @type instance: L{objects.Instance}
677
    @param instance: the instance to be migrated
678
    @type target: string
679
    @param target: ip address of the target node
680
    @type live: boolean
681
    @param live: perform a live migration
682

683
    """
684
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
685

    
686
    # TODO: Pass cluster name via RPC
687
    cluster_name = ssconf.SimpleStore().GetClusterName()
688

    
689
    return self._MigrateInstance(cluster_name, instance.name, target, port,
690
                                 live)
691

    
692
  def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
693
                       _ping_fn=netutils.TcpPing):
694
    """Migrate an instance to a target node.
695

696
    @see: L{MigrateInstance} for details
697

698
    """
699
    if self.GetInstanceInfo(instance_name) is None:
700
      raise errors.HypervisorError("Instance not running, cannot migrate")
701

    
702
    cmd = self._GetCommand()
703

    
704
    if (cmd == constants.XEN_CMD_XM and
705
        not _ping_fn(target, port, live_port_needed=True)):
706
      raise errors.HypervisorError("Remote host %s not listening on port"
707
                                   " %s, cannot migrate" % (target, port))
708

    
709
    args = ["migrate"]
710

    
711
    if cmd == constants.XEN_CMD_XM:
712
      args.extend(["-p", "%d" % port])
713
      if live:
714
        args.append("-l")
715

    
716
    elif cmd == constants.XEN_CMD_XL:
717
      args.extend([
718
        "-s", constants.XL_SSH_CMD % cluster_name,
719
        "-C", self._ConfigFileName(instance_name),
720
        ])
721

    
722
    else:
723
      raise errors.HypervisorError("Unsupported Xen command: %s" % self._cmd)
724

    
725
    args.extend([instance_name, target])
726

    
727
    result = self._RunXen(args)
728
    if result.failed:
729
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
730
                                   (instance_name, result.output))
731

    
732
  def FinalizeMigrationSource(self, instance, success, live):
733
    """Finalize the instance migration on the source node.
734

735
    @type instance: L{objects.Instance}
736
    @param instance: the instance that was migrated
737
    @type success: bool
738
    @param success: whether the migration succeeded or not
739
    @type live: bool
740
    @param live: whether the user requested a live migration or not
741

742
    """
743
    # pylint: disable=W0613
744
    if success:
745
      # remove old xen file after migration succeeded
746
      try:
747
        self._RemoveConfigFile(instance.name)
748
      except EnvironmentError:
749
        logging.exception("Failure while removing instance config file")
750

    
751
  def GetMigrationStatus(self, instance):
752
    """Get the migration status
753

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

758
    @type instance: L{objects.Instance}
759
    @param instance: the instance that is being migrated
760
    @rtype: L{objects.MigrationStatus}
761
    @return: the status of the current migration (one of
762
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
763
             progress info that can be retrieved from the hypervisor
764

765
    """
766
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
767

    
768
  @classmethod
769
  def PowercycleNode(cls):
770
    """Xen-specific powercycle.
771

772
    This first does a Linux reboot (which triggers automatically a Xen
773
    reboot), and if that fails it tries to do a Xen reboot. The reason
774
    we don't try a Xen reboot first is that the xen reboot launches an
775
    external command which connects to the Xen hypervisor, and that
776
    won't work in case the root filesystem is broken and/or the xend
777
    daemon is not working.
778

779
    """
780
    try:
781
      cls.LinuxPowercycle()
782
    finally:
783
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
784

    
785

    
786
class XenPvmHypervisor(XenHypervisor):
787
  """Xen PVM hypervisor interface"""
788

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

    
809
  def _GetConfig(self, instance, startup_memory, block_devices):
810
    """Write the Xen config file for the instance.
811

812
    """
813
    hvp = instance.hvparams
814
    config = StringIO()
815
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
816

    
817
    # if bootloader is True, use bootloader instead of kernel and ramdisk
818
    # parameters.
819
    if hvp[constants.HV_USE_BOOTLOADER]:
820
      # bootloader handling
821
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
822
      if bootloader_path:
823
        config.write("bootloader = '%s'\n" % bootloader_path)
824
      else:
825
        raise errors.HypervisorError("Bootloader enabled, but missing"
826
                                     " bootloader path")
827

    
828
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
829
      if bootloader_args:
830
        config.write("bootargs = '%s'\n" % bootloader_args)
831
    else:
832
      # kernel handling
833
      kpath = hvp[constants.HV_KERNEL_PATH]
834
      config.write("kernel = '%s'\n" % kpath)
835

    
836
      # initrd handling
837
      initrd_path = hvp[constants.HV_INITRD_PATH]
838
      if initrd_path:
839
        config.write("ramdisk = '%s'\n" % initrd_path)
840

    
841
    # rest of the settings
842
    config.write("memory = %d\n" % startup_memory)
843
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
844
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
845
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
846
    if cpu_pinning:
847
      config.write("%s\n" % cpu_pinning)
848
    cpu_cap = hvp[constants.HV_CPU_CAP]
849
    if cpu_cap:
850
      config.write("cpu_cap=%d\n" % cpu_cap)
851
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
852
    if cpu_weight:
853
      config.write("cpu_weight=%d\n" % cpu_weight)
854

    
855
    config.write("name = '%s'\n" % instance.name)
856

    
857
    vif_data = []
858
    for nic in instance.nics:
859
      nic_str = "mac=%s" % (nic.mac)
860
      ip = getattr(nic, "ip", None)
861
      if ip is not None:
862
        nic_str += ", ip=%s" % ip
863
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
864
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
865
      vif_data.append("'%s'" % nic_str)
866

    
867
    disk_data = \
868
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
869

    
870
    config.write("vif = [%s]\n" % ",".join(vif_data))
871
    config.write("disk = [%s]\n" % ",".join(disk_data))
872

    
873
    if hvp[constants.HV_ROOT_PATH]:
874
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
875
    config.write("on_poweroff = 'destroy'\n")
876
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
877
      config.write("on_reboot = 'restart'\n")
878
    else:
879
      config.write("on_reboot = 'destroy'\n")
880
    config.write("on_crash = 'restart'\n")
881
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
882

    
883
    return config.getvalue()
884

    
885

    
886
class XenHvmHypervisor(XenHypervisor):
887
  """Xen HVM hypervisor interface"""
888

    
889
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
890
    pathutils.VNC_PASSWORD_FILE,
891
    ]
892
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
893
    pathutils.VNC_PASSWORD_FILE,
894
    ]
895

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

    
932
  def _GetConfig(self, instance, startup_memory, block_devices):
933
    """Create a Xen 3.1 HVM config file.
934

935
    """
936
    hvp = instance.hvparams
937

    
938
    config = StringIO()
939

    
940
    # kernel handling
941
    kpath = hvp[constants.HV_KERNEL_PATH]
942
    config.write("kernel = '%s'\n" % kpath)
943

    
944
    config.write("builder = 'hvm'\n")
945
    config.write("memory = %d\n" % startup_memory)
946
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
947
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
948
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
949
    if cpu_pinning:
950
      config.write("%s\n" % cpu_pinning)
951
    cpu_cap = hvp[constants.HV_CPU_CAP]
952
    if cpu_cap:
953
      config.write("cpu_cap=%d\n" % cpu_cap)
954
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
955
    if cpu_weight:
956
      config.write("cpu_weight=%d\n" % cpu_weight)
957

    
958
    config.write("name = '%s'\n" % instance.name)
959
    if hvp[constants.HV_PAE]:
960
      config.write("pae = 1\n")
961
    else:
962
      config.write("pae = 0\n")
963
    if hvp[constants.HV_ACPI]:
964
      config.write("acpi = 1\n")
965
    else:
966
      config.write("acpi = 0\n")
967
    if hvp[constants.HV_VIRIDIAN]:
968
      config.write("viridian = 1\n")
969
    else:
970
      config.write("viridian = 0\n")
971

    
972
    config.write("apic = 1\n")
973
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
974
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
975
    config.write("sdl = 0\n")
976
    config.write("usb = 1\n")
977
    config.write("usbdevice = 'tablet'\n")
978
    config.write("vnc = 1\n")
979
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
980
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
981
    else:
982
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
983

    
984
    if instance.network_port > constants.VNC_BASE_PORT:
985
      display = instance.network_port - constants.VNC_BASE_PORT
986
      config.write("vncdisplay = %s\n" % display)
987
      config.write("vncunused = 0\n")
988
    else:
989
      config.write("# vncdisplay = 1\n")
990
      config.write("vncunused = 1\n")
991

    
992
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
993
    try:
994
      password = utils.ReadFile(vnc_pwd_file)
995
    except EnvironmentError, err:
996
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
997
                                   (vnc_pwd_file, err))
998

    
999
    config.write("vncpasswd = '%s'\n" % password.rstrip())
1000

    
1001
    config.write("serial = 'pty'\n")
1002
    if hvp[constants.HV_USE_LOCALTIME]:
1003
      config.write("localtime = 1\n")
1004

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

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

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

    
1034
    disk_data = \
1035
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1036

    
1037
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1038
    if iso_path:
1039
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
1040
      disk_data.append(iso)
1041

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

    
1056
    return config.getvalue()