Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ fdf7f055

History | View | Annotate | Download (19.3 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008 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 os
27
import os.path
28
import time
29
import logging
30
from cStringIO import StringIO
31

    
32
from ganeti import constants
33
from ganeti import errors
34
from ganeti import utils
35
from ganeti.hypervisor import hv_base
36

    
37

    
38
class XenHypervisor(hv_base.BaseHypervisor):
39
  """Xen generic hypervisor interface
40

41
  This is the Xen base class used for both Xen PVM and HVM. It contains
42
  all the functionality that is identical for both.
43

44
  """
45

    
46
  @classmethod
47
  def _WriteConfigFile(cls, instance, block_devices, extra_args):
48
    """Write the Xen config file for the instance.
49

50
    """
51
    raise NotImplementedError
52

    
53
  @staticmethod
54
  def _RemoveConfigFile(instance_name):
55
    """Remove the xen configuration file.
56

57
    """
58
    utils.RemoveFile("/etc/xen/%s" % instance_name)
59

    
60
  @staticmethod
61
  def _GetXMList(include_node):
62
    """Return the list of running instances.
63

64
    If the include_node argument is True, then we return information
65
    for dom0 also, otherwise we filter that from the return value.
66

67
    @return: list of (name, id, memory, vcpus, state, time spent)
68

69
    """
70
    for dummy in range(5):
71
      result = utils.RunCmd(["xm", "list"])
72
      if not result.failed:
73
        break
74
      logging.error("xm list failed (%s): %s", result.fail_reason,
75
                    result.output)
76
      time.sleep(1)
77

    
78
    if result.failed:
79
      raise errors.HypervisorError("xm list failed, retries"
80
                                   " exceeded (%s): %s" %
81
                                   (result.fail_reason, result.stderr))
82

    
83
    # skip over the heading
84
    lines = result.stdout.splitlines()[1:]
85
    result = []
86
    for line in lines:
87
      # The format of lines is:
88
      # Name      ID Mem(MiB) VCPUs State  Time(s)
89
      # Domain-0   0  3418     4 r-----    266.2
90
      data = line.split()
91
      if len(data) != 6:
92
        raise errors.HypervisorError("Can't parse output of xm list,"
93
                                     " line: %s" % line)
94
      try:
95
        data[1] = int(data[1])
96
        data[2] = int(data[2])
97
        data[3] = int(data[3])
98
        data[5] = float(data[5])
99
      except ValueError, err:
100
        raise errors.HypervisorError("Can't parse output of xm list,"
101
                                     " line: %s, error: %s" % (line, err))
102

    
103
      # skip the Domain-0 (optional)
104
      if include_node or data[0] != 'Domain-0':
105
        result.append(data)
106

    
107
    return result
108

    
109
  def ListInstances(self):
110
    """Get the list of running instances.
111

112
    """
113
    xm_list = self._GetXMList(False)
114
    names = [info[0] for info in xm_list]
115
    return names
116

    
117
  def GetInstanceInfo(self, instance_name):
118
    """Get instance properties.
119

120
    @param instance_name: the instance name
121

122
    @return: tuple (name, id, memory, vcpus, stat, times)
123

124
    """
125
    xm_list = self._GetXMList(instance_name=="Domain-0")
126
    result = None
127
    for data in xm_list:
128
      if data[0] == instance_name:
129
        result = data
130
        break
131
    return result
132

    
133
  def GetAllInstancesInfo(self):
134
    """Get properties of all instances.
135

136
    @return: list of tuples (name, id, memory, vcpus, stat, times)
137

138
    """
139
    xm_list = self._GetXMList(False)
140
    return xm_list
141

    
142
  def StartInstance(self, instance, block_devices, extra_args):
143
    """Start an instance.
144

145
    """
146
    self._WriteConfigFile(instance, block_devices, extra_args)
147
    result = utils.RunCmd(["xm", "create", instance.name])
148

    
149
    if result.failed:
150
      raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
151
                                   (instance.name, result.fail_reason,
152
                                    result.output))
153

    
154
  def StopInstance(self, instance, force=False):
155
    """Stop an instance.
156

157
    """
158
    self._RemoveConfigFile(instance.name)
159
    if force:
160
      command = ["xm", "destroy", instance.name]
161
    else:
162
      command = ["xm", "shutdown", instance.name]
163
    result = utils.RunCmd(command)
164

    
165
    if result.failed:
166
      raise errors.HypervisorError("Failed to stop instance %s: %s" %
167
                                   (instance.name, result.fail_reason))
168

    
169
  def RebootInstance(self, instance):
170
    """Reboot an instance.
171

172
    """
173
    result = utils.RunCmd(["xm", "reboot", instance.name])
174

    
175
    if result.failed:
176
      raise errors.HypervisorError("Failed to reboot instance %s: %s" %
177
                                   (instance.name, result.fail_reason))
178

    
179
  def GetNodeInfo(self):
180
    """Return information about the node.
181

182
    @return: a dict with the following keys (values in MiB):
183
          - memory_total: the total memory size on the node
184
          - memory_free: the available memory on the node for instances
185
          - memory_dom0: the memory used by the node itself, if available
186

187
    """
188
    # note: in xen 3, memory has changed to total_memory
189
    result = utils.RunCmd(["xm", "info"])
190
    if result.failed:
191
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
192
                    result.output)
193
      return None
194

    
195
    xmoutput = result.stdout.splitlines()
196
    result = {}
197
    for line in xmoutput:
198
      splitfields = line.split(":", 1)
199

    
200
      if len(splitfields) > 1:
201
        key = splitfields[0].strip()
202
        val = splitfields[1].strip()
203
        if key == 'memory' or key == 'total_memory':
204
          result['memory_total'] = int(val)
205
        elif key == 'free_memory':
206
          result['memory_free'] = int(val)
207
        elif key == 'nr_cpus':
208
          result['cpu_total'] = int(val)
209
    dom0_info = self.GetInstanceInfo("Domain-0")
210
    if dom0_info is not None:
211
      result['memory_dom0'] = dom0_info[2]
212

    
213
    return result
214

    
215
  @staticmethod
216
  def GetShellCommandForConsole(instance):
217
    """Return a command for connecting to the console of an instance.
218

219
    """
220
    return "xm console %s" % instance.name
221

    
222

    
223
  def Verify(self):
224
    """Verify the hypervisor.
225

226
    For Xen, this verifies that the xend process is running.
227

228
    """
229
    result = utils.RunCmd(["xm", "info"])
230
    if result.failed:
231
      return "'xm info' failed: %s" % result.fail_reason
232

    
233
  @staticmethod
234
  def _GetConfigFileDiskData(disk_template, block_devices):
235
    """Get disk directive for xen config file.
236

237
    This method builds the xen config disk directive according to the
238
    given disk_template and block_devices.
239

240
    @param disk_template: string containing instance disk template
241
    @param block_devices: list of tuples (cfdev, rldev):
242
        - cfdev: dict containing ganeti config disk part
243
        - rldev: ganeti.bdev.BlockDev object
244

245
    @return: string containing disk directive for xen instance config file
246

247
    """
248
    FILE_DRIVER_MAP = {
249
      constants.FD_LOOP: "file",
250
      constants.FD_BLKTAP: "tap:aio",
251
      }
252
    disk_data = []
253
    if len(block_devices) > 24:
254
      # 'z' - 'a' = 24
255
      raise errors.HypervisorError("Too many disks")
256
    # FIXME: instead of this hardcoding here, each of PVM/HVM should
257
    # directly export their info (currently HVM will just sed this info)
258
    namespace = ["sd" + chr(i + ord('a')) for i in range(24)]
259
    for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
260
      if cfdev.dev_type == constants.LD_FILE:
261
        line = "'%s:%s,%s,w'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
262
                                 dev_path, sd_name)
263
      else:
264
        line = "'phy:%s,%s,w'" % (dev_path, sd_name)
265
      disk_data.append(line)
266

    
267
    return disk_data
268

    
269
  def MigrateInstance(self, instance, target, live):
270
    """Migrate an instance to a target node.
271

272
    The migration will not be attempted if the instance is not
273
    currently running.
274

275
    @type instance: string
276
    @param instance: instance name
277
    @type target: string
278
    @param target: ip address of the target node
279
    @type live: boolean
280
    @param live: perform a live migration
281

282
    """
283
    if self.GetInstanceInfo(instance) is None:
284
      raise errors.HypervisorError("Instance not running, cannot migrate")
285
    args = ["xm", "migrate"]
286
    if live:
287
      args.append("-l")
288
    args.extend([instance, target])
289
    result = utils.RunCmd(args)
290
    if result.failed:
291
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
292
                                   (instance, result.output))
293
    # remove old xen file after migration succeeded
294
    try:
295
      self._RemoveConfigFile(instance)
296
    except EnvironmentError, err:
297
      logger.Error("Failure while removing instance config file: %s" %
298
                   str(err))
299

    
300

    
301
class XenPvmHypervisor(XenHypervisor):
302
  """Xen PVM hypervisor interface"""
303

    
304
  PARAMETERS = [
305
    constants.HV_KERNEL_PATH,
306
    constants.HV_INITRD_PATH,
307
    ]
308

    
309
  @classmethod
310
  def CheckParameterSyntax(cls, hvparams):
311
    """Check the given parameters for validity.
312

313
    For the PVM hypervisor, this only check the existence of the
314
    kernel.
315

316
    @type hvparams:  dict
317
    @param hvparams: dictionary with parameter names/value
318
    @raise errors.HypervisorError: when a parameter is not valid
319

320
    """
321
    super(XenPvmHypervisor, cls).CheckParameterSyntax(hvparams)
322

    
323
    if not hvparams[constants.HV_KERNEL_PATH]:
324
      raise errors.HypervisorError("Need a kernel for the instance")
325

    
326
    if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
327
      raise errors.HypervisorError("The kernel path must an absolute path")
328

    
329
    if hvparams[constants.HV_INITRD_PATH]:
330
      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
331
        raise errors.HypervisorError("The initrd path must an absolute path"
332
                                     ", if defined")
333

    
334
  def ValidateParameters(self, hvparams):
335
    """Check the given parameters for validity.
336

337
    For the PVM hypervisor, this only check the existence of the
338
    kernel.
339

340
    """
341
    super(XenPvmHypervisor, self).ValidateParameters(hvparams)
342

    
343
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
344
    if not os.path.isfile(kernel_path):
345
      raise errors.HypervisorError("Instance kernel '%s' not found or"
346
                                   " not a file" % kernel_path)
347
    initrd_path = hvparams[constants.HV_INITRD_PATH]
348
    if initrd_path and not os.path.isfile(initrd_path):
349
      raise errors.HypervisorError("Instance initrd '%s' not found or"
350
                                   " not a file" % initrd_path)
351

    
352
  @classmethod
353
  def _WriteConfigFile(cls, instance, block_devices, extra_args):
354
    """Write the Xen config file for the instance.
355

356
    """
357
    config = StringIO()
358
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
359

    
360
    # kernel handling
361
    kpath = instance.hvparams[constants.HV_KERNEL_PATH]
362
    config.write("kernel = '%s'\n" % kpath)
363

    
364
    # initrd handling
365
    initrd_path = instance.hvparams[constants.HV_INITRD_PATH]
366
    if initrd_path:
367
      config.write("ramdisk = '%s'\n" % initrd_path)
368

    
369
    # rest of the settings
370
    config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
371
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
372
    config.write("name = '%s'\n" % instance.name)
373

    
374
    vif_data = []
375
    for nic in instance.nics:
376
      nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
377
      ip = getattr(nic, "ip", None)
378
      if ip is not None:
379
        nic_str += ", ip=%s" % ip
380
      vif_data.append("'%s'" % nic_str)
381

    
382
    config.write("vif = [%s]\n" % ",".join(vif_data))
383
    config.write("disk = [%s]\n" % ",".join(
384
                 cls._GetConfigFileDiskData(instance.disk_template,
385
                                            block_devices)))
386
    config.write("root = '/dev/sda ro'\n")
387
    config.write("on_poweroff = 'destroy'\n")
388
    config.write("on_reboot = 'restart'\n")
389
    config.write("on_crash = 'restart'\n")
390
    if extra_args:
391
      config.write("extra = '%s'\n" % extra_args)
392
    # just in case it exists
393
    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
394
    try:
395
      f = open("/etc/xen/%s" % instance.name, "w")
396
      try:
397
        f.write(config.getvalue())
398
      finally:
399
        f.close()
400
    except IOError, err:
401
      raise errors.OpExecError("Cannot write Xen instance confile"
402
                               " file /etc/xen/%s: %s" % (instance.name, err))
403
    return True
404

    
405

    
406
class XenHvmHypervisor(XenHypervisor):
407
  """Xen HVM hypervisor interface"""
408

    
409
  PARAMETERS = [
410
    constants.HV_ACPI,
411
    constants.HV_BOOT_ORDER,
412
    constants.HV_CDROM_IMAGE_PATH,
413
    constants.HV_DISK_TYPE,
414
    constants.HV_NIC_TYPE,
415
    constants.HV_PAE,
416
    constants.HV_VNC_BIND_ADDRESS,
417
    ]
418

    
419
  @classmethod
420
  def CheckParameterSyntax(cls, hvparams):
421
    """Check the given parameter syntax.
422

423
    """
424
    super(XenHvmHypervisor, cls).CheckParameterSyntax(hvparams)
425
    # boot order verification
426
    boot_order = hvparams[constants.HV_BOOT_ORDER]
427
    if len(boot_order.strip("acdn")) != 0:
428
      raise errors.HypervisorError("Invalid boot order '%s' specified,"
429
                                   " must be one or more of [acdn]" %
430
                                   boot_order)
431
    # device type checks
432
    nic_type = hvparams[constants.HV_NIC_TYPE]
433
    if nic_type not in constants.HT_HVM_VALID_NIC_TYPES:
434
      raise errors.HypervisorError("Invalid NIC type %s specified for Xen HVM"
435
                                   " hypervisor" % nic_type)
436
    disk_type = hvparams[constants.HV_DISK_TYPE]
437
    if disk_type not in constants.HT_HVM_VALID_DISK_TYPES:
438
      raise errors.HypervisorError("Invalid disk type %s specified for Xen HVM"
439
                                   " hypervisor" % disk_type)
440
    # vnc_bind_address verification
441
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
442
    if vnc_bind_address is not None:
443
      if not utils.IsValidIP(vnc_bind_address):
444
        raise errors.OpPrereqError("given VNC bind address '%s' doesn't look"
445
                                   " like a valid IP address" %
446
                                   vnc_bind_address)
447

    
448
    iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
449
    if iso_path and not os.path.isabs(iso_path):
450
      raise errors.HypervisorError("The path to the HVM CDROM image must"
451
                                   " be an absolute path or None, not %s" %
452
                                   iso_path)
453

    
454
  def ValidateParameters(self, hvparams):
455
    """Check the given parameters for validity.
456

457
    For the PVM hypervisor, this only check the existence of the
458
    kernel.
459

460
    @type hvparams:  dict
461
    @param hvparams: dictionary with parameter names/value
462
    @raise errors.HypervisorError: when a parameter is not valid
463

464
    """
465
    super(XenHvmHypervisor, self).ValidateParameters(hvparams)
466

    
467
    # hvm_cdrom_image_path verification
468
    iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
469
    if iso_path and not os.path.isfile(iso_path):
470
      raise errors.HypervisorError("The HVM CDROM image must either be a"
471
                                   " regular file or a symlink pointing to"
472
                                   " an existing regular file, not %s" %
473
                                   iso_path)
474

    
475
  @classmethod
476
  def _WriteConfigFile(cls, instance, block_devices, extra_args):
477
    """Create a Xen 3.1 HVM config file.
478

479
    """
480
    config = StringIO()
481
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
482
    config.write("kernel = '/usr/lib/xen/boot/hvmloader'\n")
483
    config.write("builder = 'hvm'\n")
484
    config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
485
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
486
    config.write("name = '%s'\n" % instance.name)
487
    if instance.hvparams[constants.HV_PAE]:
488
      config.write("pae = 1\n")
489
    else:
490
      config.write("pae = 0\n")
491
    if instance.hvparams[constants.HV_ACPI]:
492
      config.write("acpi = 1\n")
493
    else:
494
      config.write("acpi = 0\n")
495
    config.write("apic = 1\n")
496
    arch = os.uname()[4]
497
    if '64' in arch:
498
      config.write("device_model = '/usr/lib64/xen/bin/qemu-dm'\n")
499
    else:
500
      config.write("device_model = '/usr/lib/xen/bin/qemu-dm'\n")
501
    if instance.hvparams[constants.HV_BOOT_ORDER] is None:
502
      config.write("boot = '%s'\n" % constants.HT_HVM_DEFAULT_BOOT_ORDER)
503
    else:
504
      config.write("boot = '%s'\n" % instance.hvparams["boot_order"])
505
    config.write("sdl = 0\n")
506
    config.write("usb = 1\n")
507
    config.write("usbdevice = 'tablet'\n")
508
    config.write("vnc = 1\n")
509
    if instance.hvparams[constants.HV_VNC_BIND_ADDRESS] is None:
510
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
511
    else:
512
      config.write("vnclisten = '%s'\n" %
513
                   instance.hvparams["vnc_bind_address"])
514

    
515
    if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
516
      display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
517
      config.write("vncdisplay = %s\n" % display)
518
      config.write("vncunused = 0\n")
519
    else:
520
      config.write("# vncdisplay = 1\n")
521
      config.write("vncunused = 1\n")
522

    
523
    try:
524
      password_file = open(constants.VNC_PASSWORD_FILE, "r")
525
      try:
526
        password = password_file.readline()
527
      finally:
528
        password_file.close()
529
    except IOError:
530
      raise errors.OpExecError("failed to open VNC password file %s " %
531
                               constants.VNC_PASSWORD_FILE)
532

    
533
    config.write("vncpasswd = '%s'\n" % password.rstrip())
534

    
535
    config.write("serial = 'pty'\n")
536
    config.write("localtime = 1\n")
537

    
538
    vif_data = []
539
    nic_type = instance.hvparams[constants.HV_NIC_TYPE]
540
    if nic_type is None:
541
      # ensure old instances don't change
542
      nic_type_str = ", type=ioemu"
543
    elif nic_type == constants.HT_HVM_DEV_PARAVIRTUAL:
544
      nic_type_str = ", type=paravirtualized"
545
    else:
546
      nic_type_str = ", model=%s, type=ioemu" % nic_type
547
    for nic in instance.nics:
548
      nic_str = "mac=%s, bridge=%s%s" % (nic.mac, nic.bridge, nic_type_str)
549
      ip = getattr(nic, "ip", None)
550
      if ip is not None:
551
        nic_str += ", ip=%s" % ip
552
      vif_data.append("'%s'" % nic_str)
553

    
554
    config.write("vif = [%s]\n" % ",".join(vif_data))
555
    disk_data = cls._GetConfigFileDiskData(instance.disk_template,
556
                                            block_devices)
557
    disk_type = instance.hvparams[constants.HV_DISK_TYPE]
558
    if disk_type in (None, constants.HT_HVM_DEV_IOEMU):
559
      replacement = ",ioemu:hd"
560
    else:
561
      replacement = ",hd"
562
    disk_data = [line.replace(",sd", replacement) for line in disk_data]
563
    iso_path = instance.hvparams[constants.HV_CDROM_IMAGE_PATH]
564
    if iso_path:
565
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
566
      disk_data.append(iso)
567

    
568
    config.write("disk = [%s]\n" % (",".join(disk_data)))
569

    
570
    config.write("on_poweroff = 'destroy'\n")
571
    config.write("on_reboot = 'restart'\n")
572
    config.write("on_crash = 'restart'\n")
573
    if extra_args:
574
      config.write("extra = '%s'\n" % extra_args)
575
    # just in case it exists
576
    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
577
    try:
578
      f = open("/etc/xen/%s" % instance.name, "w")
579
      try:
580
        f.write(config.getvalue())
581
      finally:
582
        f.close()
583
    except IOError, err:
584
      raise errors.OpExecError("Cannot write Xen instance confile"
585
                               " file /etc/xen/%s: %s" % (instance.name, err))
586
    return True