Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 0200a1af

History | View | Annotate | Download (37.3 kB)

1
#
2
#
3

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

    
21

    
22
"""Xen hypervisors
23

24
"""
25

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

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

    
39

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

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

    
52

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

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

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

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

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

    
84

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

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

95
  """
96
  result = fn()
97
  if result.failed:
98
    logging.error("Retrieving the instance list from xen failed (%s): %s",
99
                  result.fail_reason, result.output)
100
    instance_list_errors.append(result)
101
    raise utils.RetryAgain()
102

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

    
106

    
107
def _ParseInstanceList(lines, include_node):
108
  """Parses the output of listing instances by xen.
109

110
  @type lines: list
111
  @param lines: Result of retrieving the instance list from xen
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 instance 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 instance 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{_ParseInstanceList} 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 _ParseInstanceList(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, instance_list):
229
  """Updates node information from L{_ParseNodeInfo} with instance info.
230

231
  @type info: dict
232
  @param info: Result from L{_ParseNodeInfo}
233
  @type instance_list: list of tuples
234
  @param instance_list: list of instance information; one tuple per instance
235
  @rtype: dict
236

237
  """
238
  total_instmem = 0
239

    
240
  for (name, _, mem, vcpus, _, _) in instance_list:
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, instance_list):
259
  """Combines L{_MergeInstanceInfo} and L{_ParseNodeInfo}.
260

261
  @type instance_list: list of tuples
262
  @param instance_list: list of instance information; one tuple per instance
263

264
  """
265
  return _MergeInstanceInfo(_ParseNodeInfo(info), instance_list)
266

    
267

    
268
def _GetConfigFileDiskData(block_devices, blockdev_prefix,
269
                           _letters=_DISK_LETTERS):
270
  """Get disk directives for Xen config file.
271

272
  This method builds the xen config disk directive according to the
273
  given disk_template and block_devices.
274

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

281
  @return: string containing disk directive for xen instance config file
282

283
  """
284
  if len(block_devices) > len(_letters):
285
    raise errors.HypervisorError("Too many disks")
286

    
287
  disk_data = []
288

    
289
  for sd_suffix, (cfdev, dev_path) in zip(_letters, block_devices):
290
    sd_name = blockdev_prefix + sd_suffix
291

    
292
    if cfdev.mode == constants.DISK_RDWR:
293
      mode = "w"
294
    else:
295
      mode = "r"
296

    
297
    if cfdev.dev_type == constants.LD_FILE:
298
      driver = _FILE_DRIVER_MAP[cfdev.physical_id[0]]
299
    else:
300
      driver = "phy"
301

    
302
    disk_data.append("'%s:%s,%s,%s'" % (driver, dev_path, sd_name, mode))
303

    
304
  return disk_data
305

    
306

    
307
class XenHypervisor(hv_base.BaseHypervisor):
308
  """Xen generic hypervisor interface
309

310
  This is the Xen base class used for both Xen PVM and HVM. It contains
311
  all the functionality that is identical for both.
312

313
  """
314
  CAN_MIGRATE = True
315
  REBOOT_RETRY_COUNT = 60
316
  REBOOT_RETRY_INTERVAL = 10
317

    
318
  ANCILLARY_FILES = [
319
    XEND_CONFIG_FILE,
320
    XL_CONFIG_FILE,
321
    VIF_BRIDGE_SCRIPT,
322
    ]
323
  ANCILLARY_FILES_OPT = [
324
    XL_CONFIG_FILE,
325
    ]
326

    
327
  def __init__(self, _cfgdir=None, _run_cmd_fn=None, _cmd=None):
328
    hv_base.BaseHypervisor.__init__(self)
329

    
330
    if _cfgdir is None:
331
      self._cfgdir = pathutils.XEN_CONFIG_DIR
332
    else:
333
      self._cfgdir = _cfgdir
334

    
335
    if _run_cmd_fn is None:
336
      self._run_cmd_fn = utils.RunCmd
337
    else:
338
      self._run_cmd_fn = _run_cmd_fn
339

    
340
    self._cmd = _cmd
341

    
342
  def _GetCommand(self, hvparams=None):
343
    """Returns Xen command to use.
344

345
    @type hvparams: dict of strings
346
    @param hvparams: hypervisor parameters
347

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

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

    
362
    return cmd
363

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

367
    @type hvparams: dict of strings
368
    @param hvparams: dictionary of hypervisor params
369
    @see: L{utils.process.RunCmd}
370

371
    """
372
    cmd = [self._GetCommand(hvparams=hvparams)]
373
    cmd.extend(args)
374

    
375
    return self._run_cmd_fn(cmd)
376

    
377
  def _ConfigFileName(self, instance_name):
378
    """Get the config file name for an instance.
379

380
    @param instance_name: instance name
381
    @type instance_name: str
382
    @return: fully qualified path to instance config file
383
    @rtype: str
384

385
    """
386
    return utils.PathJoin(self._cfgdir, instance_name)
387

    
388
  @classmethod
389
  def _GetConfig(cls, instance, startup_memory, block_devices):
390
    """Build Xen configuration for an instance.
391

392
    """
393
    raise NotImplementedError
394

    
395
  def _WriteConfigFile(self, instance_name, data):
396
    """Write the Xen config file for the instance.
397

398
    This version of the function just writes the config file from static data.
399

400
    """
401
    # just in case it exists
402
    utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
403

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

    
411
  def _ReadConfigFile(self, instance_name):
412
    """Returns the contents of the instance config file.
413

414
    """
415
    filename = self._ConfigFileName(instance_name)
416

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

    
422
    return file_content
423

    
424
  def _RemoveConfigFile(self, instance_name):
425
    """Remove the xen configuration file.
426

427
    """
428
    utils.RemoveFile(self._ConfigFileName(instance_name))
429

    
430
  def _StashConfigFile(self, instance_name):
431
    """Move the Xen config file to the log directory and return its new path.
432

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

    
441
  def _GetInstanceList(self, include_node, hvparams=None):
442
    """Wrapper around module level L{_GetInstanceList}.
443

444
    """
445
    return _GetInstanceList(lambda: self._RunXen(["list"], hvparams=hvparams),
446
                            include_node)
447

    
448
  def ListInstances(self, hvparams=None):
449
    """Get the list of running instances.
450

451
    """
452
    instance_list = self._GetInstanceList(False, hvparams=hvparams)
453
    names = [info[0] for info in instance_list]
454
    return names
455

    
456
  def GetInstanceInfo(self, instance_name, hvparams=None):
457
    """Get instance properties.
458

459
    @type instance_name: string
460
    @param instance_name: the instance name
461
    @type hvparams: dict of strings
462
    @param hvparams: the instance's hypervisor params
463

464
    @return: tuple (name, id, memory, vcpus, stat, times)
465

466
    """
467
    instance_list = self._GetInstanceList(instance_name == _DOM0_NAME,
468
                                          hvparams=hvparams)
469
    result = None
470
    for data in instance_list:
471
      if data[0] == instance_name:
472
        result = data
473
        break
474
    return result
475

    
476
  def GetAllInstancesInfo(self, hvparams=None):
477
    """Get properties of all instances.
478

479
    @type hvparams: dict of strings
480
    @param hvparams: hypervisor parameters
481
    @return: list of tuples (name, id, memory, vcpus, stat, times)
482

483
    """
484
    return self._GetInstanceList(False, hvparams=hvparams)
485

    
486
  def _MakeConfigFile(self, instance, startup_memory, block_devices):
487
    """Gather configuration details and write to disk.
488

489
    See L{_GetConfig} for arguments.
490

491
    """
492
    buf = StringIO()
493
    buf.write("# Automatically generated by Ganeti. Do not edit!\n")
494
    buf.write("\n")
495
    buf.write(self._GetConfig(instance, startup_memory, block_devices))
496
    buf.write("\n")
497

    
498
    self._WriteConfigFile(instance.name, buf.getvalue())
499

    
500
  def StartInstance(self, instance, block_devices, startup_paused):
501
    """Start an instance.
502

503
    """
504
    startup_memory = self._InstanceStartupMemory(instance,
505
                                                 hvparams=instance.hvparams)
506

    
507
    self._MakeConfigFile(instance, startup_memory, block_devices)
508

    
509
    cmd = ["create"]
510
    if startup_paused:
511
      cmd.append("-p")
512
    cmd.append(self._ConfigFileName(instance.name))
513

    
514
    result = self._RunXen(cmd, hvparams=instance.hvparams)
515
    if result.failed:
516
      # Move the Xen configuration file to the log directory to avoid
517
      # leaving a stale config file behind.
518
      stashed_config = self._StashConfigFile(instance.name)
519
      raise errors.HypervisorError("Failed to start instance %s: %s (%s). Moved"
520
                                   " config file to %s" %
521
                                   (instance.name, result.fail_reason,
522
                                    result.output, stashed_config))
523

    
524
  def StopInstance(self, instance, force=False, retry=False, name=None):
525
    """Stop an instance.
526

527
    """
528
    if name is None:
529
      name = instance.name
530

    
531
    return self._StopInstance(name, force, instance.hvparams)
532

    
533
  def _StopInstance(self, name, force, hvparams):
534
    """Stop an instance.
535

536
    @type name: string
537
    @param name: name of the instance to be shutdown
538
    @type force: boolean
539
    @param force: flag specifying whether shutdown should be forced
540
    @type hvparams: dict of string
541
    @param hvparams: hypervisor parameters of the instance
542

543
    """
544
    if force:
545
      action = "destroy"
546
    else:
547
      action = "shutdown"
548

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

    
554
    # Remove configuration file if stopping/starting instance was successful
555
    self._RemoveConfigFile(name)
556

    
557
  def RebootInstance(self, instance):
558
    """Reboot an instance.
559

560
    """
561
    ini_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
562

    
563
    if ini_info is None:
564
      raise errors.HypervisorError("Failed to reboot instance %s,"
565
                                   " not running" % instance.name)
566

    
567
    result = self._RunXen(["reboot", instance.name], hvparams=instance.hvparams)
568
    if result.failed:
569
      raise errors.HypervisorError("Failed to reboot instance %s: %s, %s" %
570
                                   (instance.name, result.fail_reason,
571
                                    result.output))
572

    
573
    def _CheckInstance():
574
      new_info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams)
575

    
576
      # check if the domain ID has changed or the run time has decreased
577
      if (new_info is not None and
578
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
579
        return
580

    
581
      raise utils.RetryAgain()
582

    
583
    try:
584
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
585
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
586
    except utils.RetryTimeout:
587
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
588
                                   " did not reboot in the expected interval" %
589
                                   (instance.name, ))
590

    
591
  def BalloonInstanceMemory(self, instance, mem):
592
    """Balloon an instance memory to a certain value.
593

594
    @type instance: L{objects.Instance}
595
    @param instance: instance to be accepted
596
    @type mem: int
597
    @param mem: actual memory size to use for instance runtime
598

599
    """
600
    result = self._RunXen(["mem-set", instance.name, mem],
601
                          hvparams=instance.hvparams)
602
    if result.failed:
603
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
604
                                   (instance.name, result.fail_reason,
605
                                    result.output))
606

    
607
    # Update configuration file
608
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
609
    cmd.append(self._ConfigFileName(instance.name))
610

    
611
    result = utils.RunCmd(cmd)
612
    if result.failed:
613
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
614
                                   (instance.name, result.fail_reason,
615
                                    result.output))
616

    
617
  def GetNodeInfo(self, hvparams=None):
618
    """Return information about the node.
619

620
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
621

622
    """
623
    result = self._RunXen(["info"], hvparams=hvparams)
624
    if result.failed:
625
      logging.error("Can't retrieve xen hypervisor information (%s): %s",
626
                    result.fail_reason, result.output)
627
      return None
628

    
629
    instance_list = self._GetInstanceList(True, hvparams=hvparams)
630
    return _GetNodeInfo(result.stdout, instance_list)
631

    
632
  @classmethod
633
  def GetInstanceConsole(cls, instance, hvparams, beparams):
634
    """Return a command for connecting to the console of an instance.
635

636
    """
637
    return objects.InstanceConsole(instance=instance.name,
638
                                   kind=constants.CONS_SSH,
639
                                   host=instance.primary_node,
640
                                   user=constants.SSH_CONSOLE_USER,
641
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
642
                                            constants.XEN_CMD, instance.name])
643

    
644
  def Verify(self, hvparams=None):
645
    """Verify the hypervisor.
646

647
    For Xen, this verifies that the xend process is running.
648

649
    @type hvparams: dict of strings
650
    @param hvparams: hypervisor parameters to be verified against
651

652
    @return: Problem description if something is wrong, C{None} otherwise
653

654
    """
655
    if hvparams is None:
656
      return "Could not verify the hypervisor, because no hvparams were" \
657
             " provided."
658

    
659
    if constants.HV_XEN_CMD in hvparams:
660
      xen_cmd = hvparams[constants.HV_XEN_CMD]
661
      try:
662
        self._CheckToolstack(xen_cmd)
663
      except errors.HypervisorError:
664
        return "The configured xen toolstack '%s' is not available on this" \
665
               " node." % xen_cmd
666

    
667
    result = self._RunXen(["info"], hvparams=hvparams)
668
    if result.failed:
669
      return "Retrieving information from xen failed: %s, %s" % \
670
        (result.fail_reason, result.output)
671

    
672
    return None
673

    
674
  def MigrationInfo(self, instance):
675
    """Get instance information to perform a migration.
676

677
    @type instance: L{objects.Instance}
678
    @param instance: instance to be migrated
679
    @rtype: string
680
    @return: content of the xen config file
681

682
    """
683
    return self._ReadConfigFile(instance.name)
684

    
685
  def AcceptInstance(self, instance, info, target):
686
    """Prepare to accept an instance.
687

688
    @type instance: L{objects.Instance}
689
    @param instance: instance to be accepted
690
    @type info: string
691
    @param info: content of the xen config file on the source node
692
    @type target: string
693
    @param target: target host (usually ip), on this node
694

695
    """
696
    pass
697

    
698
  def FinalizeMigrationDst(self, instance, info, success):
699
    """Finalize an instance migration.
700

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

704
    @type instance: L{objects.Instance}
705
    @param instance: instance whose migration is being finalized
706
    @type info: string
707
    @param info: content of the xen config file on the source node
708
    @type success: boolean
709
    @param success: whether the migration was a success or a failure
710

711
    """
712
    if success:
713
      self._WriteConfigFile(instance.name, info)
714

    
715
  def MigrateInstance(self, instance, target, live):
716
    """Migrate an instance to a target node.
717

718
    The migration will not be attempted if the instance is not
719
    currently running.
720

721
    @type instance: L{objects.Instance}
722
    @param instance: the instance to be migrated
723
    @type target: string
724
    @param target: ip address of the target node
725
    @type live: boolean
726
    @param live: perform a live migration
727

728
    """
729
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
730

    
731
    # TODO: Pass cluster name via RPC
732
    cluster_name = ssconf.SimpleStore().GetClusterName()
733

    
734
    return self._MigrateInstance(cluster_name, instance.name, target, port,
735
                                 live, instance.hvparams)
736

    
737
  def _MigrateInstance(self, cluster_name, instance_name, target, port, live,
738
                       hvparams, _ping_fn=netutils.TcpPing):
739
    """Migrate an instance to a target node.
740

741
    @see: L{MigrateInstance} for details
742

743
    """
744
    if hvparams is None:
745
      raise errors.HypervisorError("No hvparams provided.")
746

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

    
750
    cmd = self._GetCommand(hvparams=hvparams)
751

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

    
757
    args = ["migrate"]
758

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

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

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

    
773
    args.extend([instance_name, target])
774

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

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

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

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

    
799
  def GetMigrationStatus(self, instance):
800
    """Get the migration status
801

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

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

813
    """
814
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
815

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

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

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

    
833
  def _CheckToolstack(self, xen_cmd):
834
    """Check whether the given toolstack is available on the node.
835

836
    @type xen_cmd: string
837
    @param xen_cmd: xen command (e.g. 'xm' or 'xl')
838

839
    """
840
    binary_found = self._CheckToolstackBinary(xen_cmd)
841
    if not binary_found:
842
      raise errors.HypervisorError("No '%s' binary found on node." % xen_cmd)
843
    elif xen_cmd == constants.XEN_CMD_XL:
844
      if not self._CheckToolstackXlConfigured():
845
        raise errors.HypervisorError("Toolstack '%s' is not enabled on this"
846
                                     "node." % xen_cmd)
847

    
848
  def _CheckToolstackBinary(self, xen_cmd):
849
    """Checks whether the xen command's binary is found on the machine.
850

851
    """
852
    if xen_cmd not in constants.KNOWN_XEN_COMMANDS:
853
      raise errors.HypervisorError("Unknown xen command '%s'." % xen_cmd)
854
    result = self._run_cmd_fn(["which", xen_cmd])
855
    return not result.failed
856

    
857
  def _CheckToolstackXlConfigured(self):
858
    """Checks whether xl is enabled on an xl-capable node.
859

860
    @rtype: bool
861
    @returns: C{True} if 'xl' is enabled, C{False} otherwise
862

863
    """
864
    result = self._run_cmd_fn([constants.XEN_CMD_XL, "help"])
865
    if not result.failed:
866
      return True
867
    elif result.failed:
868
      if "toolstack" in result.stderr:
869
        return False
870
      # xl fails for some other reason than the toolstack
871
      else:
872
        raise errors.HypervisorError("Cannot run xen ('%s'). Error: %s."
873
                                     % (constants.XEN_CMD_XL, result.stderr))
874

    
875

    
876
class XenPvmHypervisor(XenHypervisor):
877
  """Xen PVM hypervisor interface"""
878

    
879
  PARAMETERS = {
880
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
881
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
882
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
883
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
884
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
885
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
886
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
887
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
888
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
889
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
890
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
891
    constants.HV_REBOOT_BEHAVIOR:
892
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
893
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
894
    constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
895
    constants.HV_CPU_WEIGHT:
896
      (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
897
    constants.HV_XEN_CMD:
898
      hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
899
    }
900

    
901
  def _GetConfig(self, instance, startup_memory, block_devices):
902
    """Write the Xen config file for the instance.
903

904
    """
905
    hvp = instance.hvparams
906
    config = StringIO()
907
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
908

    
909
    # if bootloader is True, use bootloader instead of kernel and ramdisk
910
    # parameters.
911
    if hvp[constants.HV_USE_BOOTLOADER]:
912
      # bootloader handling
913
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
914
      if bootloader_path:
915
        config.write("bootloader = '%s'\n" % bootloader_path)
916
      else:
917
        raise errors.HypervisorError("Bootloader enabled, but missing"
918
                                     " bootloader path")
919

    
920
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
921
      if bootloader_args:
922
        config.write("bootargs = '%s'\n" % bootloader_args)
923
    else:
924
      # kernel handling
925
      kpath = hvp[constants.HV_KERNEL_PATH]
926
      config.write("kernel = '%s'\n" % kpath)
927

    
928
      # initrd handling
929
      initrd_path = hvp[constants.HV_INITRD_PATH]
930
      if initrd_path:
931
        config.write("ramdisk = '%s'\n" % initrd_path)
932

    
933
    # rest of the settings
934
    config.write("memory = %d\n" % startup_memory)
935
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
936
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
937
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
938
    if cpu_pinning:
939
      config.write("%s\n" % cpu_pinning)
940
    cpu_cap = hvp[constants.HV_CPU_CAP]
941
    if cpu_cap:
942
      config.write("cpu_cap=%d\n" % cpu_cap)
943
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
944
    if cpu_weight:
945
      config.write("cpu_weight=%d\n" % cpu_weight)
946

    
947
    config.write("name = '%s'\n" % instance.name)
948

    
949
    vif_data = []
950
    for nic in instance.nics:
951
      nic_str = "mac=%s" % (nic.mac)
952
      ip = getattr(nic, "ip", None)
953
      if ip is not None:
954
        nic_str += ", ip=%s" % ip
955
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
956
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
957
      vif_data.append("'%s'" % nic_str)
958

    
959
    disk_data = \
960
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
961

    
962
    config.write("vif = [%s]\n" % ",".join(vif_data))
963
    config.write("disk = [%s]\n" % ",".join(disk_data))
964

    
965
    if hvp[constants.HV_ROOT_PATH]:
966
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
967
    config.write("on_poweroff = 'destroy'\n")
968
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
969
      config.write("on_reboot = 'restart'\n")
970
    else:
971
      config.write("on_reboot = 'destroy'\n")
972
    config.write("on_crash = 'restart'\n")
973
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
974

    
975
    return config.getvalue()
976

    
977

    
978
class XenHvmHypervisor(XenHypervisor):
979
  """Xen HVM hypervisor interface"""
980

    
981
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
982
    pathutils.VNC_PASSWORD_FILE,
983
    ]
984
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
985
    pathutils.VNC_PASSWORD_FILE,
986
    ]
987

    
988
  PARAMETERS = {
989
    constants.HV_ACPI: hv_base.NO_CHECK,
990
    constants.HV_BOOT_ORDER: (True, ) +
991
      (lambda x: x and len(x.strip("acdn")) == 0,
992
       "Invalid boot order specified, must be one or more of [acdn]",
993
       None, None),
994
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
995
    constants.HV_DISK_TYPE:
996
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
997
    constants.HV_NIC_TYPE:
998
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
999
    constants.HV_PAE: hv_base.NO_CHECK,
1000
    constants.HV_VNC_BIND_ADDRESS:
1001
      (False, netutils.IP4Address.IsValid,
1002
       "VNC bind address is not a valid IP address", None, None),
1003
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
1004
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
1005
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
1006
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
1007
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
1008
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
1009
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
1010
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
1011
    # Add PCI passthrough
1012
    constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
1013
    constants.HV_REBOOT_BEHAVIOR:
1014
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
1015
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
1016
    constants.HV_CPU_CAP: hv_base.NO_CHECK,
1017
    constants.HV_CPU_WEIGHT:
1018
      (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
1019
    constants.HV_VIF_TYPE:
1020
      hv_base.ParamInSet(False, constants.HT_HVM_VALID_VIF_TYPES),
1021
    constants.HV_XEN_CMD:
1022
      hv_base.ParamInSet(True, constants.KNOWN_XEN_COMMANDS),
1023
    }
1024

    
1025
  def _GetConfig(self, instance, startup_memory, block_devices):
1026
    """Create a Xen 3.1 HVM config file.
1027

1028
    """
1029
    hvp = instance.hvparams
1030

    
1031
    config = StringIO()
1032

    
1033
    # kernel handling
1034
    kpath = hvp[constants.HV_KERNEL_PATH]
1035
    config.write("kernel = '%s'\n" % kpath)
1036

    
1037
    config.write("builder = 'hvm'\n")
1038
    config.write("memory = %d\n" % startup_memory)
1039
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
1040
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
1041
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
1042
    if cpu_pinning:
1043
      config.write("%s\n" % cpu_pinning)
1044
    cpu_cap = hvp[constants.HV_CPU_CAP]
1045
    if cpu_cap:
1046
      config.write("cpu_cap=%d\n" % cpu_cap)
1047
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
1048
    if cpu_weight:
1049
      config.write("cpu_weight=%d\n" % cpu_weight)
1050

    
1051
    config.write("name = '%s'\n" % instance.name)
1052
    if hvp[constants.HV_PAE]:
1053
      config.write("pae = 1\n")
1054
    else:
1055
      config.write("pae = 0\n")
1056
    if hvp[constants.HV_ACPI]:
1057
      config.write("acpi = 1\n")
1058
    else:
1059
      config.write("acpi = 0\n")
1060
    config.write("apic = 1\n")
1061
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
1062
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
1063
    config.write("sdl = 0\n")
1064
    config.write("usb = 1\n")
1065
    config.write("usbdevice = 'tablet'\n")
1066
    config.write("vnc = 1\n")
1067
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
1068
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
1069
    else:
1070
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
1071

    
1072
    if instance.network_port > constants.VNC_BASE_PORT:
1073
      display = instance.network_port - constants.VNC_BASE_PORT
1074
      config.write("vncdisplay = %s\n" % display)
1075
      config.write("vncunused = 0\n")
1076
    else:
1077
      config.write("# vncdisplay = 1\n")
1078
      config.write("vncunused = 1\n")
1079

    
1080
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
1081
    try:
1082
      password = utils.ReadFile(vnc_pwd_file)
1083
    except EnvironmentError, err:
1084
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
1085
                                   (vnc_pwd_file, err))
1086

    
1087
    config.write("vncpasswd = '%s'\n" % password.rstrip())
1088

    
1089
    config.write("serial = 'pty'\n")
1090
    if hvp[constants.HV_USE_LOCALTIME]:
1091
      config.write("localtime = 1\n")
1092

    
1093
    vif_data = []
1094
    # Note: what is called 'nic_type' here, is used as value for the xen nic
1095
    # vif config parameter 'model'. For the xen nic vif parameter 'type', we use
1096
    # the 'vif_type' to avoid a clash of notation.
1097
    nic_type = hvp[constants.HV_NIC_TYPE]
1098

    
1099
    if nic_type is None:
1100
      vif_type_str = ""
1101
      if hvp[constants.HV_VIF_TYPE]:
1102
        vif_type_str = ", type=%s" % hvp[constants.HV_VIF_TYPE]
1103
      # ensure old instances don't change
1104
      nic_type_str = vif_type_str
1105
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
1106
      nic_type_str = ", type=paravirtualized"
1107
    else:
1108
      # parameter 'model' is only valid with type 'ioemu'
1109
      nic_type_str = ", model=%s, type=%s" % \
1110
        (nic_type, constants.HT_HVM_VIF_IOEMU)
1111
    for nic in instance.nics:
1112
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
1113
      ip = getattr(nic, "ip", None)
1114
      if ip is not None:
1115
        nic_str += ", ip=%s" % ip
1116
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
1117
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
1118
      vif_data.append("'%s'" % nic_str)
1119

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

    
1122
    disk_data = \
1123
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
1124

    
1125
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
1126
    if iso_path:
1127
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
1128
      disk_data.append(iso)
1129

    
1130
    config.write("disk = [%s]\n" % (",".join(disk_data)))
1131
    # Add PCI passthrough
1132
    pci_pass_arr = []
1133
    pci_pass = hvp[constants.HV_PASSTHROUGH]
1134
    if pci_pass:
1135
      pci_pass_arr = pci_pass.split(";")
1136
      config.write("pci = %s\n" % pci_pass_arr)
1137
    config.write("on_poweroff = 'destroy'\n")
1138
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1139
      config.write("on_reboot = 'restart'\n")
1140
    else:
1141
      config.write("on_reboot = 'destroy'\n")
1142
    config.write("on_crash = 'restart'\n")
1143

    
1144
    return config.getvalue()