Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 47387b1e

History | View | Annotate | Download (19.2 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
  @staticmethod
47
  def _WriteConfigFile(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):
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
    The return value is a 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
    Args:
121
      instance_name: the instance name
122

123
    Returns:
124
      (name, id, memory, vcpus, stat, times)
125
    """
126
    xm_list = self._GetXMList(instance_name=="Domain-0")
127
    result = None
128
    for data in xm_list:
129
      if data[0] == instance_name:
130
        result = data
131
        break
132
    return result
133

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

137
    Returns:
138
      [(name, id, memory, vcpus, stat, times),...]
139
    """
140
    xm_list = self._GetXMList(False)
141
    return xm_list
142

    
143
  def StartInstance(self, instance, block_devices, extra_args):
144
    """Start an instance."""
145
    self._WriteConfigFile(instance, block_devices, extra_args)
146
    result = utils.RunCmd(["xm", "create", instance.name])
147

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

    
153
  def StopInstance(self, instance, force=False):
154
    """Stop an instance."""
155
    self._RemoveConfigFile(instance)
156
    if force:
157
      command = ["xm", "destroy", instance.name]
158
    else:
159
      command = ["xm", "shutdown", instance.name]
160
    result = utils.RunCmd(command)
161

    
162
    if result.failed:
163
      raise errors.HypervisorError("Failed to stop instance %s: %s" %
164
                                   (instance.name, result.fail_reason))
165

    
166
  def RebootInstance(self, instance):
167
    """Reboot an instance."""
168
    result = utils.RunCmd(["xm", "reboot", instance.name])
169

    
170
    if result.failed:
171
      raise errors.HypervisorError("Failed to reboot instance %s: %s" %
172
                                   (instance.name, result.fail_reason))
173

    
174
  def GetNodeInfo(self):
175
    """Return information about the node.
176

177
    The return value is a dict, which has to have the following items:
178
      (all values in MiB)
179
      - memory_total: the total memory size on the node
180
      - memory_free: the available memory on the node for instances
181
      - memory_dom0: the memory used by the node itself, if available
182

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

    
191
    xmoutput = result.stdout.splitlines()
192
    result = {}
193
    for line in xmoutput:
194
      splitfields = line.split(":", 1)
195

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

    
209
    return result
210

    
211
  @staticmethod
212
  def GetShellCommandForConsole(instance):
213
    """Return a command for connecting to the console of an instance.
214

215
    """
216
    return "xm console %s" % instance.name
217

    
218

    
219
  def Verify(self):
220
    """Verify the hypervisor.
221

222
    For Xen, this verifies that the xend process is running.
223

224
    """
225
    result = utils.RunCmd(["xm", "info"])
226
    if result.failed:
227
      return "'xm info' failed: %s" % result.fail_reason
228

    
229
  @staticmethod
230
  def _GetConfigFileDiskData(disk_template, block_devices):
231
    """Get disk directive for xen config file.
232

233
    This method builds the xen config disk directive according to the
234
    given disk_template and block_devices.
235

236
    Args:
237
      disk_template: String containing instance disk template
238
      block_devices: List[tuple1,tuple2,...]
239
        tuple: (cfdev, rldev)
240
          cfdev: dict containing ganeti config disk part
241
          rldev: ganeti.bdev.BlockDev object
242

243
    Returns:
244
      String containing disk directive for xen instance config file
245

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

    
266
    return disk_data
267

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

271
    Arguments:
272
      - instance: the name of the instance
273
      - target: the ip of the target node
274
      - live: whether to do live migration or not
275

276
    Returns: none, errors will be signaled by exception.
277

278
    The migration will not be attempted if the instance is not
279
    currently running.
280

281
    """
282
    if self.GetInstanceInfo(instance) is None:
283
      raise errors.HypervisorError("Instance not running, cannot migrate")
284
    args = ["xm", "migrate"]
285
    if live:
286
      args.append("-l")
287
    args.extend([instance, target])
288
    result = utils.RunCmd(args)
289
    if result.failed:
290
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
291
                                   (instance, result.output))
292

    
293

    
294
class XenPvmHypervisor(XenHypervisor):
295
  """Xen PVM hypervisor interface"""
296

    
297
  PARAMETERS = [
298
    constants.HV_KERNEL_PATH,
299
    constants.HV_INITRD_PATH,
300
    ]
301

    
302
  @classmethod
303
  def CheckParameterSyntax(cls, hvparams):
304
    """Check the given parameters for validity.
305

306
    For the PVM hypervisor, this only check the existence of the
307
    kernel.
308

309
    @type hvparams:  dict
310
    @param hvparams: dictionary with parameter names/value
311
    @raise errors.HypervisorError: when a parameter is not valid
312

313
    """
314
    super(XenPvmHypervisor, cls).CheckParameterSyntax(hvparams)
315

    
316
    if not hvparams[constants.HV_KERNEL_PATH]:
317
      raise errors.HypervisorError("Need a kernel for the instance")
318

    
319
    if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
320
      raise errors.HypervisorError("The kernel path must an absolute path")
321

    
322
    if hvparams[constants.HV_INITRD_PATH]:
323
      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
324
        raise errors.HypervisorError("The initrd path must an absolute path"
325
                                     ", if defined")
326

    
327
  def ValidateParameters(self, hvparams):
328
    """Check the given parameters for validity.
329

330
    For the PVM hypervisor, this only check the existence of the
331
    kernel.
332

333
    """
334
    super(XenPvmHypervisor, self).ValidateParameters(hvparams)
335

    
336
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
337
    if not os.path.isfile(kernel_path):
338
      raise errors.HypervisorError("Instance kernel '%s' not found or"
339
                                   " not a file" % kernel_path)
340
    initrd_path = hvparams[constants.HV_INITRD_PATH]
341
    if initrd_path and not os.path.isfile(initrd_path):
342
      raise errors.HypervisorError("Instance initrd '%s' not found or"
343
                                   " not a file" % initrd_path)
344

    
345
  @classmethod
346
  def _WriteConfigFile(cls, instance, block_devices, extra_args):
347
    """Write the Xen config file for the instance.
348

349
    """
350
    config = StringIO()
351
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
352

    
353
    # kernel handling
354
    kpath = instance.hvparams[constants.HV_KERNEL_PATH]
355
    config.write("kernel = '%s'\n" % kpath)
356

    
357
    # initrd handling
358
    initrd_path = instance.hvparams[constants.HV_INITRD_PATH]
359
    if initrd_path:
360
      config.write("ramdisk = '%s'\n" % initrd_path)
361

    
362
    # rest of the settings
363
    config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
364
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
365
    config.write("name = '%s'\n" % instance.name)
366

    
367
    vif_data = []
368
    for nic in instance.nics:
369
      nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
370
      ip = getattr(nic, "ip", None)
371
      if ip is not None:
372
        nic_str += ", ip=%s" % ip
373
      vif_data.append("'%s'" % nic_str)
374

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

    
398

    
399
class XenHvmHypervisor(XenHypervisor):
400
  """Xen HVM hypervisor interface"""
401

    
402
  PARAMETERS = [
403
    constants.HV_ACPI,
404
    constants.HV_BOOT_ORDER,
405
    constants.HV_CDROM_IMAGE_PATH,
406
    constants.HV_DISK_TYPE,
407
    constants.HV_NIC_TYPE,
408
    constants.HV_PAE,
409
    constants.HV_VNC_BIND_ADDRESS,
410
    ]
411

    
412
  @classmethod
413
  def CheckParameterSyntax(cls, hvparams):
414
    """Check the given parameter syntax.
415

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

    
441
    iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
442
    if iso_path and not os.path.isabs(iso_path):
443
        raise errors.HypervisorError("The path to the HVM CDROM image must"
444
                                     " be an absolute path or None, not %s" %
445
                                     iso_path)
446

    
447
  def ValidateParameters(self, hvparams):
448
    """Check the given parameters for validity.
449

450
    For the PVM hypervisor, this only check the existence of the
451
    kernel.
452

453
    @type hvparams:  dict
454
    @param hvparams: dictionary with parameter names/value
455
    @raise errors.HypervisorError: when a parameter is not valid
456

457
    """
458
    super(XenHvmHypervisor, self).ValidateParameters(hvparams)
459

    
460
    # hvm_cdrom_image_path verification
461
    iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
462
    if iso_path and not os.path.isfile(iso_path):
463
        raise errors.HypervisorError("The HVM CDROM image must either be a"
464
                                     " regular file or a symlink pointing to"
465
                                     " an existing regular file, not %s" %
466
                                     iso_path)
467

    
468
  @classmethod
469
  def _WriteConfigFile(cls, instance, block_devices, extra_args):
470
    """Create a Xen 3.1 HVM config file.
471

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

    
508
    if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
509
      display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
510
      config.write("vncdisplay = %s\n" % display)
511
      config.write("vncunused = 0\n")
512
    else:
513
      config.write("# vncdisplay = 1\n")
514
      config.write("vncunused = 1\n")
515

    
516
    try:
517
      password_file = open(constants.VNC_PASSWORD_FILE, "r")
518
      try:
519
        password = password_file.readline()
520
      finally:
521
        password_file.close()
522
    except IOError:
523
      raise errors.OpExecError("failed to open VNC password file %s " %
524
                               constants.VNC_PASSWORD_FILE)
525

    
526
    config.write("vncpasswd = '%s'\n" % password.rstrip())
527

    
528
    config.write("serial = 'pty'\n")
529
    config.write("localtime = 1\n")
530

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

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

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

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