Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 8b3fd458

History | View | Annotate | Download (18.9 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
from cStringIO import StringIO
30

    
31
from ganeti import constants
32
from ganeti import errors
33
from ganeti import logger
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
      logger.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
      logger.Error("Can't run 'xm info': %s" % result.fail_reason)
188
      return None
189

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

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

    
208
    return result
209

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

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

    
217

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

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

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

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

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

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

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

245
    """
246
    FILE_DRIVER_MAP = {
247
      constants.FD_LOOP: "file",
248
      constants.FD_BLKTAP: "tap:aio",
249
      }
250
    disk_data = []
251
    for cfdev, rldev in block_devices:
252
      if cfdev.dev_type == constants.LD_FILE:
253
        line = "'%s:%s,%s,w'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
254
                                 rldev.dev_path, cfdev.iv_name)
255
      else:
256
        line = "'phy:%s,%s,w'" % (rldev.dev_path, cfdev.iv_name)
257
      disk_data.append(line)
258

    
259
    return disk_data
260

    
261
  def MigrateInstance(self, instance, target, live):
262
    """Migrate an instance to a target node.
263

264
    Arguments:
265
      - instance: the name of the instance
266
      - target: the ip of the target node
267
      - live: whether to do live migration or not
268

269
    Returns: none, errors will be signaled by exception.
270

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

274
    """
275
    if self.GetInstanceInfo(instance) is None:
276
      raise errors.HypervisorError("Instance not running, cannot migrate")
277
    args = ["xm", "migrate"]
278
    if live:
279
      args.append("-l")
280
    args.extend([instance, target])
281
    result = utils.RunCmd(args)
282
    if result.failed:
283
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
284
                                   (instance, result.output))
285

    
286

    
287
class XenPvmHypervisor(XenHypervisor):
288
  """Xen PVM hypervisor interface"""
289

    
290
  PARAMETERS = [
291
    constants.HV_KERNEL_PATH,
292
    constants.HV_INITRD_PATH,
293
    ]
294

    
295
  @classmethod
296
  def CheckParameterSyntax(cls, hvparams):
297
    """Check the given parameters for validity.
298

299
    For the PVM hypervisor, this only check the existence of the
300
    kernel.
301

302
    @type hvparams:  dict
303
    @param hvparams: dictionary with parameter names/value
304
    @raise errors.HypervisorError: when a parameter is not valid
305

306
    """
307
    super(XenPvmHypervisor, cls).CheckParameterSyntax(hvparams)
308

    
309
    if not hvparams[constants.HV_KERNEL_PATH]:
310
      raise errors.HypervisorError("Need a kernel for the instance")
311

    
312
    if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
313
      raise errors.HypervisorError("The kernel path must an absolute path")
314

    
315
    if hvparams[constants.HV_INITRD_PATH]:
316
      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
317
        raise errors.HypervisorError("The initrd path must an absolute path"
318
                                     ", if defined")
319

    
320
  def ValidateParameters(self, hvparams):
321
    """Check the given parameters for validity.
322

323
    For the PVM hypervisor, this only check the existence of the
324
    kernel.
325

326
    """
327
    super(XenPvmHypervisor, self).ValidateParameters(hvparams)
328

    
329
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
330
    if not os.path.isfile(kernel_path):
331
      raise errors.HypervisorError("Instance kernel '%s' not found or"
332
                                   " not a file" % kernel_path)
333
    initrd_path = hvparams[constants.HV_INITRD_PATH]
334
    if initrd_path and not os.path.isfile(initrd_path):
335
      raise errors.HypervisorError("Instance initrd '%s' not found or"
336
                                   " not a file" % initrd_path)
337

    
338
  @classmethod
339
  def _WriteConfigFile(cls, instance, block_devices, extra_args):
340
    """Write the Xen config file for the instance.
341

342
    """
343
    config = StringIO()
344
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
345

    
346
    # kernel handling
347
    kpath = instance.hvparams[constants.HV_KERNEL_PATH]
348
    config.write("kernel = '%s'\n" % kpath)
349

    
350
    # initrd handling
351
    initrd_path = instance.hvparams[constants.HV_INITRD_PATH]
352
    if initrd_path:
353
      config.write("ramdisk = '%s'\n" % initrd_path)
354

    
355
    # rest of the settings
356
    config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
357
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
358
    config.write("name = '%s'\n" % instance.name)
359

    
360
    vif_data = []
361
    for nic in instance.nics:
362
      nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
363
      ip = getattr(nic, "ip", None)
364
      if ip is not None:
365
        nic_str += ", ip=%s" % ip
366
      vif_data.append("'%s'" % nic_str)
367

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

    
391

    
392
class XenHvmHypervisor(XenHypervisor):
393
  """Xen HVM hypervisor interface"""
394

    
395
  PARAMETERS = [
396
    constants.HV_ACPI,
397
    constants.HV_BOOT_ORDER,
398
    constants.HV_CDROM_IMAGE_PATH,
399
    constants.HV_DISK_TYPE,
400
    constants.HV_NIC_TYPE,
401
    constants.HV_PAE,
402
    constants.HV_VNC_BIND_ADDRESS,
403
    ]
404

    
405
  @classmethod
406
  def CheckParameterSyntax(cls, hvparams):
407
    """Check the given parameter syntax.
408

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

    
434
    iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
435
    if iso_path and not os.path.isabs(iso_path):
436
        raise errors.HypervisorError("The path to the HVM CDROM image must"
437
                                     " be an absolute path or None, not %s" %
438
                                     iso_path)
439

    
440
  def ValidateParameters(self, hvparams):
441
    """Check the given parameters for validity.
442

443
    For the PVM hypervisor, this only check the existence of the
444
    kernel.
445

446
    @type hvparams:  dict
447
    @param hvparams: dictionary with parameter names/value
448
    @raise errors.HypervisorError: when a parameter is not valid
449

450
    """
451
    super(XenHvmHypervisor, self).ValidateParameters(hvparams)
452

    
453
    # hvm_cdrom_image_path verification
454
    iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
455
    if iso_path and not os.path.isfile(iso_path):
456
        raise errors.HypervisorError("The HVM CDROM image must either be a"
457
                                     " regular file or a symlink pointing to"
458
                                     " an existing regular file, not %s" %
459
                                     iso_path)
460

    
461
  @classmethod
462
  def _WriteConfigFile(cls, instance, block_devices, extra_args):
463
    """Create a Xen 3.1 HVM config file.
464

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

    
501
    if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
502
      display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
503
      config.write("vncdisplay = %s\n" % display)
504
      config.write("vncunused = 0\n")
505
    else:
506
      config.write("# vncdisplay = 1\n")
507
      config.write("vncunused = 1\n")
508

    
509
    try:
510
      password_file = open(constants.VNC_PASSWORD_FILE, "r")
511
      try:
512
        password = password_file.readline()
513
      finally:
514
        password_file.close()
515
    except IOError:
516
      raise errors.OpExecError("failed to open VNC password file %s " %
517
                               constants.VNC_PASSWORD_FILE)
518

    
519
    config.write("vncpasswd = '%s'\n" % password.rstrip())
520

    
521
    config.write("serial = 'pty'\n")
522
    config.write("localtime = 1\n")
523

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

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

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

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