Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 364c350f

History | View | Annotate | Download (34.6 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 _IsInstanceRunning(instance_info):
169
  return instance_info == "r-----" \
170
      or instance_info == "-b----"
171

    
172

    
173
def _IsInstanceShutdown(instance_info):
174
  return instance_info == "---s--"
175

    
176

    
177
def _ParseNodeInfo(info):
178
  """Return information about the node.
179

180
  @return: a dict with the following keys (memory values in MiB):
181
        - memory_total: the total memory size on the node
182
        - memory_free: the available memory on the node for instances
183
        - nr_cpus: total number of CPUs
184
        - nr_nodes: in a NUMA system, the number of domains
185
        - nr_sockets: the number of physical CPU sockets in the node
186
        - hv_version: the hypervisor version in the form (major, minor)
187

188
  """
189
  result = {}
190
  cores_per_socket = threads_per_core = nr_cpus = None
191
  xen_major, xen_minor = None, None
192
  memory_total = None
193
  memory_free = None
194

    
195
  for line in info.splitlines():
196
    fields = line.split(":", 1)
197

    
198
    if len(fields) < 2:
199
      continue
200

    
201
    (key, val) = map(lambda s: s.strip(), fields)
202

    
203
    # Note: in Xen 3, memory has changed to total_memory
204
    if key in ("memory", "total_memory"):
205
      memory_total = int(val)
206
    elif key == "free_memory":
207
      memory_free = int(val)
208
    elif key == "nr_cpus":
209
      nr_cpus = result["cpu_total"] = int(val)
210
    elif key == "nr_nodes":
211
      result["cpu_nodes"] = int(val)
212
    elif key == "cores_per_socket":
213
      cores_per_socket = int(val)
214
    elif key == "threads_per_core":
215
      threads_per_core = int(val)
216
    elif key == "xen_major":
217
      xen_major = int(val)
218
    elif key == "xen_minor":
219
      xen_minor = int(val)
220

    
221
  if None not in [cores_per_socket, threads_per_core, nr_cpus]:
222
    result["cpu_sockets"] = nr_cpus / (cores_per_socket * threads_per_core)
223

    
224
  if memory_free is not None:
225
    result["memory_free"] = memory_free
226

    
227
  if memory_total is not None:
228
    result["memory_total"] = memory_total
229

    
230
  if not (xen_major is None or xen_minor is None):
231
    result[constants.HV_NODEINFO_KEY_VERSION] = (xen_major, xen_minor)
232

    
233
  return result
234

    
235

    
236
def _MergeInstanceInfo(info, fn):
237
  """Updates node information from L{_ParseNodeInfo} with instance info.
238

239
  @type info: dict
240
  @param info: Result from L{_ParseNodeInfo}
241
  @type fn: callable
242
  @param fn: Function returning result of running C{xm list}
243
  @rtype: dict
244

245
  """
246
  total_instmem = 0
247

    
248
  for (name, _, mem, vcpus, _, _) in fn(True):
249
    if name == _DOM0_NAME:
250
      info["memory_dom0"] = mem
251
      info["dom0_cpus"] = vcpus
252

    
253
    # Include Dom0 in total memory usage
254
    total_instmem += mem
255

    
256
  memory_free = info.get("memory_free")
257
  memory_total = info.get("memory_total")
258

    
259
  # Calculate memory used by hypervisor
260
  if None not in [memory_total, memory_free, total_instmem]:
261
    info["memory_hv"] = memory_total - memory_free - total_instmem
262

    
263
  return info
264

    
265

    
266
def _GetNodeInfo(info, fn):
267
  """Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}.
268

269
  """
270
  return _MergeInstanceInfo(_ParseNodeInfo(info), fn)
271

    
272

    
273
def _GetConfigFileDiskData(block_devices, blockdev_prefix,
274
                           _letters=_DISK_LETTERS):
275
  """Get disk directives for Xen config file.
276

277
  This method builds the xen config disk directive according to the
278
  given disk_template and block_devices.
279

280
  @param block_devices: list of tuples (cfdev, rldev):
281
      - cfdev: dict containing ganeti config disk part
282
      - rldev: ganeti.bdev.BlockDev object
283
  @param blockdev_prefix: a string containing blockdevice prefix,
284
                          e.g. "sd" for /dev/sda
285

286
  @return: string containing disk directive for xen instance config file
287

288
  """
289
  if len(block_devices) > len(_letters):
290
    raise errors.HypervisorError("Too many disks")
291

    
292
  disk_data = []
293

    
294
  for sd_suffix, (cfdev, dev_path) in zip(_letters, block_devices):
295
    sd_name = blockdev_prefix + sd_suffix
296

    
297
    if cfdev.mode == constants.DISK_RDWR:
298
      mode = "w"
299
    else:
300
      mode = "r"
301

    
302
    if cfdev.dev_type == constants.LD_FILE:
303
      driver = _FILE_DRIVER_MAP[cfdev.physical_id[0]]
304
    else:
305
      driver = "phy"
306

    
307
    disk_data.append("'%s:%s,%s,%s'" % (driver, dev_path, sd_name, mode))
308

    
309
  return disk_data
310

    
311

    
312
class XenHypervisor(hv_base.BaseHypervisor):
313
  """Xen generic hypervisor interface
314

315
  This is the Xen base class used for both Xen PVM and HVM. It contains
316
  all the functionality that is identical for both.
317

318
  """
319
  CAN_MIGRATE = True
320
  REBOOT_RETRY_COUNT = 60
321
  REBOOT_RETRY_INTERVAL = 10
322

    
323
  ANCILLARY_FILES = [
324
    XEND_CONFIG_FILE,
325
    XL_CONFIG_FILE,
326
    VIF_BRIDGE_SCRIPT,
327
    ]
328
  ANCILLARY_FILES_OPT = [
329
    XL_CONFIG_FILE,
330
    ]
331

    
332
  def __init__(self, _cfgdir=None, _run_cmd_fn=None, _cmd=None):
333
    hv_base.BaseHypervisor.__init__(self)
334

    
335
    if _cfgdir is None:
336
      self._cfgdir = pathutils.XEN_CONFIG_DIR
337
    else:
338
      self._cfgdir = _cfgdir
339

    
340
    if _run_cmd_fn is None:
341
      self._run_cmd_fn = utils.RunCmd
342
    else:
343
      self._run_cmd_fn = _run_cmd_fn
344

    
345
    self._cmd = _cmd
346

    
347
  def _GetCommand(self):
348
    """Returns Xen command to use.
349

350
    """
351
    if self._cmd is None:
352
      # TODO: Make command a hypervisor parameter
353
      cmd = constants.XEN_CMD
354
    else:
355
      cmd = self._cmd
356

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

    
360
    return cmd
361

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

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

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

    
371
    return self._run_cmd_fn(cmd)
372

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

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

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

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

388
    """
389
    raise NotImplementedError
390

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

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

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

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

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

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

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

    
418
    return file_content
419

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

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

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

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

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

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

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

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

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

454
    @param instance_name: the instance name
455

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

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

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

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

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

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

479
    See L{_GetConfig} for arguments.
480

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

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

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

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

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

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

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

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

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

    
520
    return self._StopInstance(name, force)
521

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
604
      raise utils.RetryAgain()
605

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
677
    return None
678

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

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

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

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

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

700
    """
701
    pass
702

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

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

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

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

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

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

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

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

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

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

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

746
    @see: L{MigrateInstance} for details
747

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

    
752
    cmd = self._GetCommand()
753

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

    
759
    args = ["migrate"]
760

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
835

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

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

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

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

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

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

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

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

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

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

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

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

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

    
933
    return config.getvalue()
934

    
935

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

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

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

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

985
    """
986
    hvp = instance.hvparams
987

    
988
    config = StringIO()
989

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1106
    return config.getvalue()