Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ b48909c8

History | View | Annotate | Download (18.8 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
    for cfdev, rldev in block_devices:
253
      if cfdev.dev_type == constants.LD_FILE:
254
        line = "'%s:%s,%s,w'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
255
                                 rldev.dev_path, cfdev.iv_name)
256
      else:
257
        line = "'phy:%s,%s,w'" % (rldev.dev_path, cfdev.iv_name)
258
      disk_data.append(line)
259

    
260
    return disk_data
261

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

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

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

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

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

    
287

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
392

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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