Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 31da5ab5

History | View | Annotate | Download (31.5 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
    if self._cmd is None:
340
      # TODO: Make command a hypervisor parameter
341
      cmd = constants.XEN_CMD
342
    else:
343
      cmd = self._cmd
344

    
345
    if cmd not in constants.KNOWN_XEN_COMMANDS:
346
      raise errors.ProgrammerError("Unknown Xen command '%s'" % cmd)
347

    
348
    return cmd
349

    
350
  def _RunXen(self, args):
351
    """Wrapper around L{utils.RunCmd} to run Xen command.
352

353
    @see: L{utils.RunCmd}
354

355
    """
356
    cmd = [self._GetCommand()]
357
    cmd.extend(args)
358

    
359
    return self._run_cmd_fn(cmd)
360

    
361
  def _ConfigFileName(self, instance_name):
362
    """Get the config file name for an instance.
363

364
    @param instance_name: instance name
365
    @type instance_name: str
366
    @return: fully qualified path to instance config file
367
    @rtype: str
368

369
    """
370
    return utils.PathJoin(self._cfgdir, instance_name)
371

    
372
  @classmethod
373
  def _GetConfig(cls, instance, startup_memory, block_devices):
374
    """Build Xen configuration for an instance.
375

376
    """
377
    raise NotImplementedError
378

    
379
  def _WriteConfigFile(self, instance_name, data):
380
    """Write the Xen config file for the instance.
381

382
    This version of the function just writes the config file from static data.
383

384
    """
385
    # just in case it exists
386
    utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name))
387

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

    
395
  def _ReadConfigFile(self, instance_name):
396
    """Returns the contents of the instance config file.
397

398
    """
399
    filename = self._ConfigFileName(instance_name)
400

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

    
406
    return file_content
407

    
408
  def _RemoveConfigFile(self, instance_name):
409
    """Remove the xen configuration file.
410

411
    """
412
    utils.RemoveFile(self._ConfigFileName(instance_name))
413

    
414
  def _GetXmList(self, include_node):
415
    """Wrapper around module level L{_GetXmList}.
416

417
    """
418
    return _GetXmList(lambda: self._RunXen(["list"]), include_node)
419

    
420
  def ListInstances(self):
421
    """Get the list of running instances.
422

423
    """
424
    xm_list = self._GetXmList(False)
425
    names = [info[0] for info in xm_list]
426
    return names
427

    
428
  def GetInstanceInfo(self, instance_name):
429
    """Get instance properties.
430

431
    @param instance_name: the instance name
432

433
    @return: tuple (name, id, memory, vcpus, stat, times)
434

435
    """
436
    xm_list = self._GetXmList(instance_name == _DOM0_NAME)
437
    result = None
438
    for data in xm_list:
439
      if data[0] == instance_name:
440
        result = data
441
        break
442
    return result
443

    
444
  def GetAllInstancesInfo(self):
445
    """Get properties of all instances.
446

447
    @return: list of tuples (name, id, memory, vcpus, stat, times)
448

449
    """
450
    xm_list = self._GetXmList(False)
451
    return xm_list
452

    
453
  def _MakeConfigFile(self, instance, startup_memory, block_devices):
454
    """Gather configuration details and write to disk.
455

456
    See L{_GetConfig} for arguments.
457

458
    """
459
    buf = StringIO()
460
    buf.write("# Automatically generated by Ganeti. Do not edit!\n")
461
    buf.write("\n")
462
    buf.write(self._GetConfig(instance, startup_memory, block_devices))
463
    buf.write("\n")
464

    
465
    self._WriteConfigFile(instance.name, buf.getvalue())
466

    
467
  def StartInstance(self, instance, block_devices, startup_paused):
468
    """Start an instance.
469

470
    """
471
    startup_memory = self._InstanceStartupMemory(instance)
472

    
473
    self._MakeConfigFile(instance, startup_memory, block_devices)
474

    
475
    cmd = ["create"]
476
    if startup_paused:
477
      cmd.append("-p")
478
    cmd.append(self._ConfigFileName(instance.name))
479

    
480
    result = self._RunXen(cmd)
481
    if result.failed:
482
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
483
                                   (instance.name, result.fail_reason,
484
                                    result.output))
485

    
486
  def StopInstance(self, instance, force=False, retry=False, name=None):
487
    """Stop an instance.
488

489
    """
490
    if name is None:
491
      name = instance.name
492

    
493
    return self._StopInstance(name, force)
494

    
495
  def _StopInstance(self, name, force):
496
    """Stop an instance.
497

498
    """
499
    if force:
500
      action = "destroy"
501
    else:
502
      action = "shutdown"
503

    
504
    result = self._RunXen([action, name])
505
    if result.failed:
506
      raise errors.HypervisorError("Failed to stop instance %s: %s, %s" %
507
                                   (name, result.fail_reason, result.output))
508

    
509
    # Remove configuration file if stopping/starting instance was successful
510
    self._RemoveConfigFile(name)
511

    
512
  def RebootInstance(self, instance):
513
    """Reboot an instance.
514

515
    """
516
    ini_info = self.GetInstanceInfo(instance.name)
517

    
518
    if ini_info is None:
519
      raise errors.HypervisorError("Failed to reboot instance %s,"
520
                                   " not running" % instance.name)
521

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

    
528
    def _CheckInstance():
529
      new_info = self.GetInstanceInfo(instance.name)
530

    
531
      # check if the domain ID has changed or the run time has decreased
532
      if (new_info is not None and
533
          (new_info[1] != ini_info[1] or new_info[5] < ini_info[5])):
534
        return
535

    
536
      raise utils.RetryAgain()
537

    
538
    try:
539
      utils.Retry(_CheckInstance, self.REBOOT_RETRY_INTERVAL,
540
                  self.REBOOT_RETRY_INTERVAL * self.REBOOT_RETRY_COUNT)
541
    except utils.RetryTimeout:
542
      raise errors.HypervisorError("Failed to reboot instance %s: instance"
543
                                   " did not reboot in the expected interval" %
544
                                   (instance.name, ))
545

    
546
  def BalloonInstanceMemory(self, instance, mem):
547
    """Balloon an instance memory to a certain value.
548

549
    @type instance: L{objects.Instance}
550
    @param instance: instance to be accepted
551
    @type mem: int
552
    @param mem: actual memory size to use for instance runtime
553

554
    """
555
    result = self._RunXen(["mem-set", instance.name, mem])
556
    if result.failed:
557
      raise errors.HypervisorError("Failed to balloon instance %s: %s (%s)" %
558
                                   (instance.name, result.fail_reason,
559
                                    result.output))
560

    
561
    # Update configuration file
562
    cmd = ["sed", "-ie", "s/^memory.*$/memory = %s/" % mem]
563
    cmd.append(self._ConfigFileName(instance.name))
564

    
565
    result = utils.RunCmd(cmd)
566
    if result.failed:
567
      raise errors.HypervisorError("Failed to update memory for %s: %s (%s)" %
568
                                   (instance.name, result.fail_reason,
569
                                    result.output))
570

    
571
  def GetNodeInfo(self):
572
    """Return information about the node.
573

574
    @see: L{_GetNodeInfo} and L{_ParseNodeInfo}
575

576
    """
577
    result = self._RunXen(["info"])
578
    if result.failed:
579
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
580
                    result.output)
581
      return None
582

    
583
    return _GetNodeInfo(result.stdout, self._GetXmList)
584

    
585
  @classmethod
586
  def GetInstanceConsole(cls, instance, hvparams, beparams):
587
    """Return a command for connecting to the console of an instance.
588

589
    """
590
    return objects.InstanceConsole(instance=instance.name,
591
                                   kind=constants.CONS_SSH,
592
                                   host=instance.primary_node,
593
                                   user=constants.SSH_CONSOLE_USER,
594
                                   command=[pathutils.XEN_CONSOLE_WRAPPER,
595
                                            constants.XEN_CMD, instance.name])
596

    
597
  def Verify(self):
598
    """Verify the hypervisor.
599

600
    For Xen, this verifies that the xend process is running.
601

602
    @return: Problem description if something is wrong, C{None} otherwise
603

604
    """
605
    result = self._RunXen(["info"])
606
    if result.failed:
607
      return "'xm info' failed: %s, %s" % (result.fail_reason, result.output)
608

    
609
    return None
610

    
611
  def MigrationInfo(self, instance):
612
    """Get instance information to perform a migration.
613

614
    @type instance: L{objects.Instance}
615
    @param instance: instance to be migrated
616
    @rtype: string
617
    @return: content of the xen config file
618

619
    """
620
    return self._ReadConfigFile(instance.name)
621

    
622
  def AcceptInstance(self, instance, info, target):
623
    """Prepare to accept an instance.
624

625
    @type instance: L{objects.Instance}
626
    @param instance: instance to be accepted
627
    @type info: string
628
    @param info: content of the xen config file on the source node
629
    @type target: string
630
    @param target: target host (usually ip), on this node
631

632
    """
633
    pass
634

    
635
  def FinalizeMigrationDst(self, instance, info, success):
636
    """Finalize an instance migration.
637

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

641
    @type instance: L{objects.Instance}
642
    @param instance: instance whose migration is being finalized
643
    @type info: string
644
    @param info: content of the xen config file on the source node
645
    @type success: boolean
646
    @param success: whether the migration was a success or a failure
647

648
    """
649
    if success:
650
      self._WriteConfigFile(instance.name, info)
651

    
652
  def MigrateInstance(self, instance, target, live):
653
    """Migrate an instance to a target node.
654

655
    The migration will not be attempted if the instance is not
656
    currently running.
657

658
    @type instance: L{objects.Instance}
659
    @param instance: the instance to be migrated
660
    @type target: string
661
    @param target: ip address of the target node
662
    @type live: boolean
663
    @param live: perform a live migration
664

665
    """
666
    if self.GetInstanceInfo(instance.name) is None:
667
      raise errors.HypervisorError("Instance not running, cannot migrate")
668

    
669
    port = instance.hvparams[constants.HV_MIGRATION_PORT]
670

    
671
    if (self._cmd == constants.XEN_CMD_XM and
672
        not netutils.TcpPing(target, port, live_port_needed=True)):
673
      raise errors.HypervisorError("Remote host %s not listening on port"
674
                                   " %s, cannot migrate" % (target, port))
675

    
676
    args = ["migrate"]
677

    
678
    if self._cmd == constants.XEN_CMD_XM:
679
      args.extend(["-p", "%d" % port])
680
      if live:
681
        args.append("-l")
682

    
683
    elif self._cmd == constants.XEN_CMD_XL:
684
      cluster_name = ssconf.SimpleStore().GetClusterName()
685
      args.extend(["-s", constants.XL_SSH_CMD % cluster_name])
686
      args.extend(["-C", self._ConfigFileName(instance.name)])
687

    
688
    else:
689
      raise errors.HypervisorError("Unsupported xen command: %s" % self._cmd)
690

    
691
    args.extend([instance.name, target])
692

    
693
    result = self._RunXen(args)
694
    if result.failed:
695
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
696
                                   (instance.name, result.output))
697

    
698
  def FinalizeMigrationSource(self, instance, success, live):
699
    """Finalize the instance migration on the source node.
700

701
    @type instance: L{objects.Instance}
702
    @param instance: the instance that was migrated
703
    @type success: bool
704
    @param success: whether the migration succeeded or not
705
    @type live: bool
706
    @param live: whether the user requested a live migration or not
707

708
    """
709
    # pylint: disable=W0613
710
    if success:
711
      # remove old xen file after migration succeeded
712
      try:
713
        self._RemoveConfigFile(instance.name)
714
      except EnvironmentError:
715
        logging.exception("Failure while removing instance config file")
716

    
717
  def GetMigrationStatus(self, instance):
718
    """Get the migration status
719

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

724
    @type instance: L{objects.Instance}
725
    @param instance: the instance that is being migrated
726
    @rtype: L{objects.MigrationStatus}
727
    @return: the status of the current migration (one of
728
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
729
             progress info that can be retrieved from the hypervisor
730

731
    """
732
    return objects.MigrationStatus(status=constants.HV_MIGRATION_COMPLETED)
733

    
734
  @classmethod
735
  def PowercycleNode(cls):
736
    """Xen-specific powercycle.
737

738
    This first does a Linux reboot (which triggers automatically a Xen
739
    reboot), and if that fails it tries to do a Xen reboot. The reason
740
    we don't try a Xen reboot first is that the xen reboot launches an
741
    external command which connects to the Xen hypervisor, and that
742
    won't work in case the root filesystem is broken and/or the xend
743
    daemon is not working.
744

745
    """
746
    try:
747
      cls.LinuxPowercycle()
748
    finally:
749
      utils.RunCmd([constants.XEN_CMD, "debug", "R"])
750

    
751

    
752
class XenPvmHypervisor(XenHypervisor):
753
  """Xen PVM hypervisor interface"""
754

    
755
  PARAMETERS = {
756
    constants.HV_USE_BOOTLOADER: hv_base.NO_CHECK,
757
    constants.HV_BOOTLOADER_PATH: hv_base.OPT_FILE_CHECK,
758
    constants.HV_BOOTLOADER_ARGS: hv_base.NO_CHECK,
759
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
760
    constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
761
    constants.HV_ROOT_PATH: hv_base.NO_CHECK,
762
    constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
763
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
764
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
765
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
766
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
767
    constants.HV_REBOOT_BEHAVIOR:
768
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
769
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
770
    constants.HV_CPU_CAP: hv_base.OPT_NONNEGATIVE_INT_CHECK,
771
    constants.HV_CPU_WEIGHT:
772
      (False, lambda x: 0 < x < 65536, "invalid weight", None, None),
773
    }
774

    
775
  def _GetConfig(self, instance, startup_memory, block_devices):
776
    """Write the Xen config file for the instance.
777

778
    """
779
    hvp = instance.hvparams
780
    config = StringIO()
781
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
782

    
783
    # if bootloader is True, use bootloader instead of kernel and ramdisk
784
    # parameters.
785
    if hvp[constants.HV_USE_BOOTLOADER]:
786
      # bootloader handling
787
      bootloader_path = hvp[constants.HV_BOOTLOADER_PATH]
788
      if bootloader_path:
789
        config.write("bootloader = '%s'\n" % bootloader_path)
790
      else:
791
        raise errors.HypervisorError("Bootloader enabled, but missing"
792
                                     " bootloader path")
793

    
794
      bootloader_args = hvp[constants.HV_BOOTLOADER_ARGS]
795
      if bootloader_args:
796
        config.write("bootargs = '%s'\n" % bootloader_args)
797
    else:
798
      # kernel handling
799
      kpath = hvp[constants.HV_KERNEL_PATH]
800
      config.write("kernel = '%s'\n" % kpath)
801

    
802
      # initrd handling
803
      initrd_path = hvp[constants.HV_INITRD_PATH]
804
      if initrd_path:
805
        config.write("ramdisk = '%s'\n" % initrd_path)
806

    
807
    # rest of the settings
808
    config.write("memory = %d\n" % startup_memory)
809
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
810
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
811
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
812
    if cpu_pinning:
813
      config.write("%s\n" % cpu_pinning)
814
    cpu_cap = hvp[constants.HV_CPU_CAP]
815
    if cpu_cap:
816
      config.write("cpu_cap=%d\n" % cpu_cap)
817
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
818
    if cpu_weight:
819
      config.write("cpu_weight=%d\n" % cpu_weight)
820

    
821
    config.write("name = '%s'\n" % instance.name)
822

    
823
    vif_data = []
824
    for nic in instance.nics:
825
      nic_str = "mac=%s" % (nic.mac)
826
      ip = getattr(nic, "ip", None)
827
      if ip is not None:
828
        nic_str += ", ip=%s" % ip
829
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
830
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
831
      vif_data.append("'%s'" % nic_str)
832

    
833
    disk_data = \
834
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
835

    
836
    config.write("vif = [%s]\n" % ",".join(vif_data))
837
    config.write("disk = [%s]\n" % ",".join(disk_data))
838

    
839
    if hvp[constants.HV_ROOT_PATH]:
840
      config.write("root = '%s'\n" % hvp[constants.HV_ROOT_PATH])
841
    config.write("on_poweroff = 'destroy'\n")
842
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
843
      config.write("on_reboot = 'restart'\n")
844
    else:
845
      config.write("on_reboot = 'destroy'\n")
846
    config.write("on_crash = 'restart'\n")
847
    config.write("extra = '%s'\n" % hvp[constants.HV_KERNEL_ARGS])
848

    
849
    return config.getvalue()
850

    
851

    
852
class XenHvmHypervisor(XenHypervisor):
853
  """Xen HVM hypervisor interface"""
854

    
855
  ANCILLARY_FILES = XenHypervisor.ANCILLARY_FILES + [
856
    pathutils.VNC_PASSWORD_FILE,
857
    ]
858
  ANCILLARY_FILES_OPT = XenHypervisor.ANCILLARY_FILES_OPT + [
859
    pathutils.VNC_PASSWORD_FILE,
860
    ]
861

    
862
  PARAMETERS = {
863
    constants.HV_ACPI: hv_base.NO_CHECK,
864
    constants.HV_BOOT_ORDER: (True, ) +
865
      (lambda x: x and len(x.strip("acdn")) == 0,
866
       "Invalid boot order specified, must be one or more of [acdn]",
867
       None, None),
868
    constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
869
    constants.HV_DISK_TYPE:
870
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
871
    constants.HV_NIC_TYPE:
872
      hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
873
    constants.HV_PAE: hv_base.NO_CHECK,
874
    constants.HV_VNC_BIND_ADDRESS:
875
      (False, netutils.IP4Address.IsValid,
876
       "VNC bind address is not a valid IP address", None, None),
877
    constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
878
    constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
879
    constants.HV_VNC_PASSWORD_FILE: hv_base.REQ_FILE_CHECK,
880
    constants.HV_MIGRATION_PORT: hv_base.REQ_NET_PORT_CHECK,
881
    constants.HV_MIGRATION_MODE: hv_base.MIGRATION_MODE_CHECK,
882
    constants.HV_USE_LOCALTIME: hv_base.NO_CHECK,
883
    # TODO: Add a check for the blockdev prefix (matching [a-z:] or similar).
884
    constants.HV_BLOCKDEV_PREFIX: hv_base.NO_CHECK,
885
    # Add PCI passthrough
886
    constants.HV_PASSTHROUGH: hv_base.NO_CHECK,
887
    constants.HV_REBOOT_BEHAVIOR:
888
      hv_base.ParamInSet(True, constants.REBOOT_BEHAVIORS),
889
    constants.HV_CPU_MASK: hv_base.OPT_MULTI_CPU_MASK_CHECK,
890
    constants.HV_CPU_CAP: hv_base.NO_CHECK,
891
    constants.HV_CPU_WEIGHT:
892
      (False, lambda x: 0 < x < 65535, "invalid weight", None, None),
893
    }
894

    
895
  def _GetConfig(self, instance, startup_memory, block_devices):
896
    """Create a Xen 3.1 HVM config file.
897

898
    """
899
    hvp = instance.hvparams
900

    
901
    config = StringIO()
902

    
903
    # kernel handling
904
    kpath = hvp[constants.HV_KERNEL_PATH]
905
    config.write("kernel = '%s'\n" % kpath)
906

    
907
    config.write("builder = 'hvm'\n")
908
    config.write("memory = %d\n" % startup_memory)
909
    config.write("maxmem = %d\n" % instance.beparams[constants.BE_MAXMEM])
910
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
911
    cpu_pinning = _CreateConfigCpus(hvp[constants.HV_CPU_MASK])
912
    if cpu_pinning:
913
      config.write("%s\n" % cpu_pinning)
914
    cpu_cap = hvp[constants.HV_CPU_CAP]
915
    if cpu_cap:
916
      config.write("cpu_cap=%d\n" % cpu_cap)
917
    cpu_weight = hvp[constants.HV_CPU_WEIGHT]
918
    if cpu_weight:
919
      config.write("cpu_weight=%d\n" % cpu_weight)
920

    
921
    config.write("name = '%s'\n" % instance.name)
922
    if hvp[constants.HV_PAE]:
923
      config.write("pae = 1\n")
924
    else:
925
      config.write("pae = 0\n")
926
    if hvp[constants.HV_ACPI]:
927
      config.write("acpi = 1\n")
928
    else:
929
      config.write("acpi = 0\n")
930
    config.write("apic = 1\n")
931
    config.write("device_model = '%s'\n" % hvp[constants.HV_DEVICE_MODEL])
932
    config.write("boot = '%s'\n" % hvp[constants.HV_BOOT_ORDER])
933
    config.write("sdl = 0\n")
934
    config.write("usb = 1\n")
935
    config.write("usbdevice = 'tablet'\n")
936
    config.write("vnc = 1\n")
937
    if hvp[constants.HV_VNC_BIND_ADDRESS] is None:
938
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
939
    else:
940
      config.write("vnclisten = '%s'\n" % hvp[constants.HV_VNC_BIND_ADDRESS])
941

    
942
    if instance.network_port > constants.VNC_BASE_PORT:
943
      display = instance.network_port - constants.VNC_BASE_PORT
944
      config.write("vncdisplay = %s\n" % display)
945
      config.write("vncunused = 0\n")
946
    else:
947
      config.write("# vncdisplay = 1\n")
948
      config.write("vncunused = 1\n")
949

    
950
    vnc_pwd_file = hvp[constants.HV_VNC_PASSWORD_FILE]
951
    try:
952
      password = utils.ReadFile(vnc_pwd_file)
953
    except EnvironmentError, err:
954
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
955
                                   (vnc_pwd_file, err))
956

    
957
    config.write("vncpasswd = '%s'\n" % password.rstrip())
958

    
959
    config.write("serial = 'pty'\n")
960
    if hvp[constants.HV_USE_LOCALTIME]:
961
      config.write("localtime = 1\n")
962

    
963
    vif_data = []
964
    nic_type = hvp[constants.HV_NIC_TYPE]
965
    if nic_type is None:
966
      # ensure old instances don't change
967
      nic_type_str = ", type=ioemu"
968
    elif nic_type == constants.HT_NIC_PARAVIRTUAL:
969
      nic_type_str = ", type=paravirtualized"
970
    else:
971
      nic_type_str = ", model=%s, type=ioemu" % nic_type
972
    for nic in instance.nics:
973
      nic_str = "mac=%s%s" % (nic.mac, nic_type_str)
974
      ip = getattr(nic, "ip", None)
975
      if ip is not None:
976
        nic_str += ", ip=%s" % ip
977
      if nic.nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
978
        nic_str += ", bridge=%s" % nic.nicparams[constants.NIC_LINK]
979
      vif_data.append("'%s'" % nic_str)
980

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

    
983
    disk_data = \
984
      _GetConfigFileDiskData(block_devices, hvp[constants.HV_BLOCKDEV_PREFIX])
985

    
986
    iso_path = hvp[constants.HV_CDROM_IMAGE_PATH]
987
    if iso_path:
988
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
989
      disk_data.append(iso)
990

    
991
    config.write("disk = [%s]\n" % (",".join(disk_data)))
992
    # Add PCI passthrough
993
    pci_pass_arr = []
994
    pci_pass = hvp[constants.HV_PASSTHROUGH]
995
    if pci_pass:
996
      pci_pass_arr = pci_pass.split(";")
997
      config.write("pci = %s\n" % pci_pass_arr)
998
    config.write("on_poweroff = 'destroy'\n")
999
    if hvp[constants.HV_REBOOT_BEHAVIOR] == constants.INSTANCE_REBOOT_ALLOWED:
1000
      config.write("on_reboot = 'restart'\n")
1001
    else:
1002
      config.write("on_reboot = 'destroy'\n")
1003
    config.write("on_crash = 'restart'\n")
1004

    
1005
    return config.getvalue()