Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 73cd67f4

History | View | Annotate | Download (21.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 _WriteConfigFileStatic(instance_name, data):
55
    """Write the Xen config file for the instance.
56

57
    This version of the function just writes the config file from static data.
58

59
    """
60
    utils.WriteFile("/etc/xen/%s" % instance_name, data=data)
61

    
62
  @staticmethod
63
  def _ReadConfigFile(instance_name):
64
    """Returns the contents of the instance config file.
65

66
    """
67
    try:
68
      file_content = utils.ReadFile("/etc/xen/%s" % instance_name)
69
    except EnvironmentError, err:
70
      raise errors.HypervisorError("Failed to load Xen config file: %s" % err)
71
    return file_content
72

    
73
  @staticmethod
74
  def _RemoveConfigFile(instance_name):
75
    """Remove the xen configuration file.
76

77
    """
78
    utils.RemoveFile("/etc/xen/%s" % instance_name)
79

    
80
  @staticmethod
81
  def _GetXMList(include_node):
82
    """Return the list of running instances.
83

84
    If the include_node argument is True, then we return information
85
    for dom0 also, otherwise we filter that from the return value.
86

87
    @return: list of (name, id, memory, vcpus, state, time spent)
88

89
    """
90
    for dummy in range(5):
91
      result = utils.RunCmd(["xm", "list"])
92
      if not result.failed:
93
        break
94
      logging.error("xm list failed (%s): %s", result.fail_reason,
95
                    result.output)
96
      time.sleep(1)
97

    
98
    if result.failed:
99
      raise errors.HypervisorError("xm list failed, retries"
100
                                   " exceeded (%s): %s" %
101
                                   (result.fail_reason, result.stderr))
102

    
103
    # skip over the heading
104
    lines = result.stdout.splitlines()[1:]
105
    result = []
106
    for line in lines:
107
      # The format of lines is:
108
      # Name      ID Mem(MiB) VCPUs State  Time(s)
109
      # Domain-0   0  3418     4 r-----    266.2
110
      data = line.split()
111
      if len(data) != 6:
112
        raise errors.HypervisorError("Can't parse output of xm list,"
113
                                     " line: %s" % line)
114
      try:
115
        data[1] = int(data[1])
116
        data[2] = int(data[2])
117
        data[3] = int(data[3])
118
        data[5] = float(data[5])
119
      except ValueError, err:
120
        raise errors.HypervisorError("Can't parse output of xm list,"
121
                                     " line: %s, error: %s" % (line, err))
122

    
123
      # skip the Domain-0 (optional)
124
      if include_node or data[0] != 'Domain-0':
125
        result.append(data)
126

    
127
    return result
128

    
129
  def ListInstances(self):
130
    """Get the list of running instances.
131

132
    """
133
    xm_list = self._GetXMList(False)
134
    names = [info[0] for info in xm_list]
135
    return names
136

    
137
  def GetInstanceInfo(self, instance_name):
138
    """Get instance properties.
139

140
    @param instance_name: the instance name
141

142
    @return: tuple (name, id, memory, vcpus, stat, times)
143

144
    """
145
    xm_list = self._GetXMList(instance_name=="Domain-0")
146
    result = None
147
    for data in xm_list:
148
      if data[0] == instance_name:
149
        result = data
150
        break
151
    return result
152

    
153
  def GetAllInstancesInfo(self):
154
    """Get properties of all instances.
155

156
    @return: list of tuples (name, id, memory, vcpus, stat, times)
157

158
    """
159
    xm_list = self._GetXMList(False)
160
    return xm_list
161

    
162
  def StartInstance(self, instance, block_devices, extra_args):
163
    """Start an instance.
164

165
    """
166
    self._WriteConfigFile(instance, block_devices, extra_args)
167
    result = utils.RunCmd(["xm", "create", instance.name])
168

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

    
174
  def StopInstance(self, instance, force=False):
175
    """Stop an instance.
176

177
    """
178
    self._RemoveConfigFile(instance.name)
179
    if force:
180
      command = ["xm", "destroy", instance.name]
181
    else:
182
      command = ["xm", "shutdown", instance.name]
183
    result = utils.RunCmd(command)
184

    
185
    if result.failed:
186
      raise errors.HypervisorError("Failed to stop instance %s: %s" %
187
                                   (instance.name, result.fail_reason))
188

    
189
  def RebootInstance(self, instance):
190
    """Reboot an instance.
191

192
    """
193
    result = utils.RunCmd(["xm", "reboot", instance.name])
194

    
195
    if result.failed:
196
      raise errors.HypervisorError("Failed to reboot instance %s: %s" %
197
                                   (instance.name, result.fail_reason))
198

    
199
  def GetNodeInfo(self):
200
    """Return information about the node.
201

202
    @return: a dict with the following keys (values in MiB):
203
          - memory_total: the total memory size on the node
204
          - memory_free: the available memory on the node for instances
205
          - memory_dom0: the memory used by the node itself, if available
206

207
    """
208
    # note: in xen 3, memory has changed to total_memory
209
    result = utils.RunCmd(["xm", "info"])
210
    if result.failed:
211
      logging.error("Can't run 'xm info' (%s): %s", result.fail_reason,
212
                    result.output)
213
      return None
214

    
215
    xmoutput = result.stdout.splitlines()
216
    result = {}
217
    for line in xmoutput:
218
      splitfields = line.split(":", 1)
219

    
220
      if len(splitfields) > 1:
221
        key = splitfields[0].strip()
222
        val = splitfields[1].strip()
223
        if key == 'memory' or key == 'total_memory':
224
          result['memory_total'] = int(val)
225
        elif key == 'free_memory':
226
          result['memory_free'] = int(val)
227
        elif key == 'nr_cpus':
228
          result['cpu_total'] = int(val)
229
    dom0_info = self.GetInstanceInfo("Domain-0")
230
    if dom0_info is not None:
231
      result['memory_dom0'] = dom0_info[2]
232

    
233
    return result
234

    
235
  @classmethod
236
  def GetShellCommandForConsole(cls, instance):
237
    """Return a command for connecting to the console of an instance.
238

239
    """
240
    return "xm console %s" % instance.name
241

    
242

    
243
  def Verify(self):
244
    """Verify the hypervisor.
245

246
    For Xen, this verifies that the xend process is running.
247

248
    """
249
    result = utils.RunCmd(["xm", "info"])
250
    if result.failed:
251
      return "'xm info' failed: %s" % result.fail_reason
252

    
253
  @staticmethod
254
  def _GetConfigFileDiskData(disk_template, block_devices):
255
    """Get disk directive for xen config file.
256

257
    This method builds the xen config disk directive according to the
258
    given disk_template and block_devices.
259

260
    @param disk_template: string containing instance disk template
261
    @param block_devices: list of tuples (cfdev, rldev):
262
        - cfdev: dict containing ganeti config disk part
263
        - rldev: ganeti.bdev.BlockDev object
264

265
    @return: string containing disk directive for xen instance config file
266

267
    """
268
    FILE_DRIVER_MAP = {
269
      constants.FD_LOOP: "file",
270
      constants.FD_BLKTAP: "tap:aio",
271
      }
272
    disk_data = []
273
    if len(block_devices) > 24:
274
      # 'z' - 'a' = 24
275
      raise errors.HypervisorError("Too many disks")
276
    # FIXME: instead of this hardcoding here, each of PVM/HVM should
277
    # directly export their info (currently HVM will just sed this info)
278
    namespace = ["sd" + chr(i + ord('a')) for i in range(24)]
279
    for sd_name, (cfdev, dev_path) in zip(namespace, block_devices):
280
      if cfdev.dev_type == constants.LD_FILE:
281
        line = "'%s:%s,%s,w'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
282
                                 dev_path, sd_name)
283
      else:
284
        line = "'phy:%s,%s,w'" % (dev_path, sd_name)
285
      disk_data.append(line)
286

    
287
    return disk_data
288

    
289
  def MigrationInfo(self, instance):
290
    """Get instance information to perform a migration.
291

292
    @type instance: L{objects.Instance}
293
    @param instance: instance to be migrated
294
    @rtype: string
295
    @return: content of the xen config file
296

297
    """
298
    return self._ReadConfigFile(instance.name)
299

    
300
  def AcceptInstance(self, instance, info, target):
301
    """Prepare to accept an instance.
302

303
    @type instance: L{objects.Instance}
304
    @param instance: instance to be accepted
305
    @type info: string
306
    @param info: content of the xen config file on the source node
307
    @type target: string
308
    @param target: target host (usually ip), on this node
309

310
    """
311
    pass
312

    
313
  def FinalizeMigration(self, instance, info, success):
314
    """Finalize an instance migration.
315

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

319
    @type instance: L{objects.Instance}
320
    @param instance: instance whose migration is being aborted
321
    @type info: string
322
    @param info: content of the xen config file on the source node
323
    @type success: boolean
324
    @param success: whether the migration was a success or a failure
325

326
    """
327
    if success:
328
      self._WriteConfigFileStatic(instance.name, info)
329

    
330
  def MigrateInstance(self, instance, target, live):
331
    """Migrate an instance to a target node.
332

333
    The migration will not be attempted if the instance is not
334
    currently running.
335

336
    @type instance: string
337
    @param instance: instance name
338
    @type target: string
339
    @param target: ip address of the target node
340
    @type live: boolean
341
    @param live: perform a live migration
342

343
    """
344
    if self.GetInstanceInfo(instance) is None:
345
      raise errors.HypervisorError("Instance not running, cannot migrate")
346
    args = ["xm", "migrate"]
347
    if live:
348
      args.append("-l")
349
    args.extend([instance, target])
350
    result = utils.RunCmd(args)
351
    if result.failed:
352
      raise errors.HypervisorError("Failed to migrate instance %s: %s" %
353
                                   (instance, result.output))
354
    # remove old xen file after migration succeeded
355
    try:
356
      self._RemoveConfigFile(instance)
357
    except EnvironmentError:
358
      logging.exception("Failure while removing instance config file")
359

    
360

    
361
class XenPvmHypervisor(XenHypervisor):
362
  """Xen PVM hypervisor interface"""
363

    
364
  PARAMETERS = [
365
    constants.HV_KERNEL_PATH,
366
    constants.HV_INITRD_PATH,
367
    constants.HV_ROOT_PATH,
368
    ]
369

    
370
  @classmethod
371
  def CheckParameterSyntax(cls, hvparams):
372
    """Check the given parameters for validity.
373

374
    For the PVM hypervisor, this only check the existence of the
375
    kernel.
376

377
    @type hvparams:  dict
378
    @param hvparams: dictionary with parameter names/value
379
    @raise errors.HypervisorError: when a parameter is not valid
380

381
    """
382
    super(XenPvmHypervisor, cls).CheckParameterSyntax(hvparams)
383

    
384
    if not hvparams[constants.HV_KERNEL_PATH]:
385
      raise errors.HypervisorError("Need a kernel for the instance")
386

    
387
    if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
388
      raise errors.HypervisorError("The kernel path must be an absolute path")
389

    
390
    if not hvparams[constants.HV_ROOT_PATH]:
391
      raise errors.HypervisorError("Need a root partition for the instance")
392

    
393
    if hvparams[constants.HV_INITRD_PATH]:
394
      if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
395
        raise errors.HypervisorError("The initrd path must be an absolute path"
396
                                     ", if defined")
397

    
398
  def ValidateParameters(self, hvparams):
399
    """Check the given parameters for validity.
400

401
    For the PVM hypervisor, this only check the existence of the
402
    kernel.
403

404
    """
405
    super(XenPvmHypervisor, self).ValidateParameters(hvparams)
406

    
407
    kernel_path = hvparams[constants.HV_KERNEL_PATH]
408
    if not os.path.isfile(kernel_path):
409
      raise errors.HypervisorError("Instance kernel '%s' not found or"
410
                                   " not a file" % kernel_path)
411
    initrd_path = hvparams[constants.HV_INITRD_PATH]
412
    if initrd_path and not os.path.isfile(initrd_path):
413
      raise errors.HypervisorError("Instance initrd '%s' not found or"
414
                                   " not a file" % initrd_path)
415

    
416
  @classmethod
417
  def _WriteConfigFile(cls, instance, block_devices, extra_args):
418
    """Write the Xen config file for the instance.
419

420
    """
421
    config = StringIO()
422
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
423

    
424
    # kernel handling
425
    kpath = instance.hvparams[constants.HV_KERNEL_PATH]
426
    config.write("kernel = '%s'\n" % kpath)
427

    
428
    # initrd handling
429
    initrd_path = instance.hvparams[constants.HV_INITRD_PATH]
430
    if initrd_path:
431
      config.write("ramdisk = '%s'\n" % initrd_path)
432

    
433
    # rest of the settings
434
    config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
435
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
436
    config.write("name = '%s'\n" % instance.name)
437

    
438
    vif_data = []
439
    for nic in instance.nics:
440
      nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
441
      ip = getattr(nic, "ip", None)
442
      if ip is not None:
443
        nic_str += ", ip=%s" % ip
444
      vif_data.append("'%s'" % nic_str)
445

    
446
    config.write("vif = [%s]\n" % ",".join(vif_data))
447
    config.write("disk = [%s]\n" % ",".join(
448
                 cls._GetConfigFileDiskData(instance.disk_template,
449
                                            block_devices)))
450

    
451
    rpath = instance.hvparams[constants.HV_ROOT_PATH]
452
    config.write("root = '%s ro'\n" % rpath)
453
    config.write("on_poweroff = 'destroy'\n")
454
    config.write("on_reboot = 'restart'\n")
455
    config.write("on_crash = 'restart'\n")
456
    if extra_args:
457
      config.write("extra = '%s'\n" % extra_args)
458
    # just in case it exists
459
    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
460
    try:
461
      utils.WriteFile("/etc/xen/%s" % instance.name,
462
                      data=config.getvalue())
463
    except EnvironmentError, err:
464
      raise errors.HypervisorError("Cannot write Xen instance confile"
465
                                   " file /etc/xen/%s: %s" %
466
                                   (instance.name, err))
467

    
468
    return True
469

    
470

    
471
class XenHvmHypervisor(XenHypervisor):
472
  """Xen HVM hypervisor interface"""
473

    
474
  PARAMETERS = [
475
    constants.HV_ACPI,
476
    constants.HV_BOOT_ORDER,
477
    constants.HV_CDROM_IMAGE_PATH,
478
    constants.HV_DISK_TYPE,
479
    constants.HV_NIC_TYPE,
480
    constants.HV_PAE,
481
    constants.HV_VNC_BIND_ADDRESS,
482
    ]
483

    
484
  @classmethod
485
  def CheckParameterSyntax(cls, hvparams):
486
    """Check the given parameter syntax.
487

488
    """
489
    super(XenHvmHypervisor, cls).CheckParameterSyntax(hvparams)
490
    # boot order verification
491
    boot_order = hvparams[constants.HV_BOOT_ORDER]
492
    if len(boot_order.strip("acdn")) != 0:
493
      raise errors.HypervisorError("Invalid boot order '%s' specified,"
494
                                   " must be one or more of [acdn]" %
495
                                   boot_order)
496
    # device type checks
497
    nic_type = hvparams[constants.HV_NIC_TYPE]
498
    if nic_type not in constants.HT_HVM_VALID_NIC_TYPES:
499
      raise errors.HypervisorError("Invalid NIC type %s specified for Xen HVM"
500
                                   " hypervisor" % nic_type)
501
    disk_type = hvparams[constants.HV_DISK_TYPE]
502
    if disk_type not in constants.HT_HVM_VALID_DISK_TYPES:
503
      raise errors.HypervisorError("Invalid disk type %s specified for Xen HVM"
504
                                   " hypervisor" % disk_type)
505
    # vnc_bind_address verification
506
    vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
507
    if vnc_bind_address is not None:
508
      if not utils.IsValidIP(vnc_bind_address):
509
        raise errors.OpPrereqError("given VNC bind address '%s' doesn't look"
510
                                   " like a valid IP address" %
511
                                   vnc_bind_address)
512

    
513
    iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
514
    if iso_path and not os.path.isabs(iso_path):
515
      raise errors.HypervisorError("The path to the HVM CDROM image must"
516
                                   " be an absolute path or None, not %s" %
517
                                   iso_path)
518

    
519
  def ValidateParameters(self, hvparams):
520
    """Check the given parameters for validity.
521

522
    For the PVM hypervisor, this only check the existence of the
523
    kernel.
524

525
    @type hvparams:  dict
526
    @param hvparams: dictionary with parameter names/value
527
    @raise errors.HypervisorError: when a parameter is not valid
528

529
    """
530
    super(XenHvmHypervisor, self).ValidateParameters(hvparams)
531

    
532
    # hvm_cdrom_image_path verification
533
    iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
534
    if iso_path and not os.path.isfile(iso_path):
535
      raise errors.HypervisorError("The HVM CDROM image must either be a"
536
                                   " regular file or a symlink pointing to"
537
                                   " an existing regular file, not %s" %
538
                                   iso_path)
539

    
540
  @classmethod
541
  def _WriteConfigFile(cls, instance, block_devices, extra_args):
542
    """Create a Xen 3.1 HVM config file.
543

544
    """
545
    config = StringIO()
546
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
547
    config.write("kernel = '/usr/lib/xen/boot/hvmloader'\n")
548
    config.write("builder = 'hvm'\n")
549
    config.write("memory = %d\n" % instance.beparams[constants.BE_MEMORY])
550
    config.write("vcpus = %d\n" % instance.beparams[constants.BE_VCPUS])
551
    config.write("name = '%s'\n" % instance.name)
552
    if instance.hvparams[constants.HV_PAE]:
553
      config.write("pae = 1\n")
554
    else:
555
      config.write("pae = 0\n")
556
    if instance.hvparams[constants.HV_ACPI]:
557
      config.write("acpi = 1\n")
558
    else:
559
      config.write("acpi = 0\n")
560
    config.write("apic = 1\n")
561
    arch = os.uname()[4]
562
    if '64' in arch:
563
      config.write("device_model = '/usr/lib64/xen/bin/qemu-dm'\n")
564
    else:
565
      config.write("device_model = '/usr/lib/xen/bin/qemu-dm'\n")
566
    if instance.hvparams[constants.HV_BOOT_ORDER] is None:
567
      config.write("boot = '%s'\n" % constants.HT_HVM_DEFAULT_BOOT_ORDER)
568
    else:
569
      config.write("boot = '%s'\n" % instance.hvparams["boot_order"])
570
    config.write("sdl = 0\n")
571
    config.write("usb = 1\n")
572
    config.write("usbdevice = 'tablet'\n")
573
    config.write("vnc = 1\n")
574
    if instance.hvparams[constants.HV_VNC_BIND_ADDRESS] is None:
575
      config.write("vnclisten = '%s'\n" % constants.VNC_DEFAULT_BIND_ADDRESS)
576
    else:
577
      config.write("vnclisten = '%s'\n" %
578
                   instance.hvparams["vnc_bind_address"])
579

    
580
    if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
581
      display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
582
      config.write("vncdisplay = %s\n" % display)
583
      config.write("vncunused = 0\n")
584
    else:
585
      config.write("# vncdisplay = 1\n")
586
      config.write("vncunused = 1\n")
587

    
588
    try:
589
      password = utils.ReadFile(constants.VNC_PASSWORD_FILE)
590
    except EnvironmentError, err:
591
      raise errors.HypervisorError("Failed to open VNC password file %s: %s" %
592
                                   (constants.VNC_PASSWORD_FILE, err))
593

    
594
    config.write("vncpasswd = '%s'\n" % password.rstrip())
595

    
596
    config.write("serial = 'pty'\n")
597
    config.write("localtime = 1\n")
598

    
599
    vif_data = []
600
    nic_type = instance.hvparams[constants.HV_NIC_TYPE]
601
    if nic_type is None:
602
      # ensure old instances don't change
603
      nic_type_str = ", type=ioemu"
604
    elif nic_type == constants.HT_HVM_DEV_PARAVIRTUAL:
605
      nic_type_str = ", type=paravirtualized"
606
    else:
607
      nic_type_str = ", model=%s, type=ioemu" % nic_type
608
    for nic in instance.nics:
609
      nic_str = "mac=%s, bridge=%s%s" % (nic.mac, nic.bridge, nic_type_str)
610
      ip = getattr(nic, "ip", None)
611
      if ip is not None:
612
        nic_str += ", ip=%s" % ip
613
      vif_data.append("'%s'" % nic_str)
614

    
615
    config.write("vif = [%s]\n" % ",".join(vif_data))
616
    disk_data = cls._GetConfigFileDiskData(instance.disk_template,
617
                                            block_devices)
618
    disk_type = instance.hvparams[constants.HV_DISK_TYPE]
619
    if disk_type in (None, constants.HT_HVM_DEV_IOEMU):
620
      replacement = ",ioemu:hd"
621
    else:
622
      replacement = ",hd"
623
    disk_data = [line.replace(",sd", replacement) for line in disk_data]
624
    iso_path = instance.hvparams[constants.HV_CDROM_IMAGE_PATH]
625
    if iso_path:
626
      iso = "'file:%s,hdc:cdrom,r'" % iso_path
627
      disk_data.append(iso)
628

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

    
631
    config.write("on_poweroff = 'destroy'\n")
632
    config.write("on_reboot = 'restart'\n")
633
    config.write("on_crash = 'restart'\n")
634
    if extra_args:
635
      config.write("extra = '%s'\n" % extra_args)
636
    # just in case it exists
637
    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
638
    try:
639
      utils.WriteFile("/etc/xen/%s" % instance.name,
640
                      data=config.getvalue())
641
    except EnvironmentError, err:
642
      raise errors.HypervisorError("Cannot write Xen instance confile"
643
                                   " file /etc/xen/%s: %s" %
644
                                   (instance.name, err))
645

    
646
    return True