Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_xen.py @ 97efde45

History | View | Annotate | Download (15.4 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
    raise NotImplementedError
216

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

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

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

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

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

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

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

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

    
258
    return disk_data
259

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

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

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

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

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

    
285

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

    
289
  @classmethod
290
  def _WriteConfigFile(cls, instance, block_devices, extra_args):
291
    """Write the Xen config file for the instance.
292

293
    """
294
    config = StringIO()
295
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
296

    
297
    # kernel handling
298
    if instance.kernel_path in (None, constants.VALUE_DEFAULT):
299
      kpath = constants.XEN_KERNEL
300
    else:
301
      if not os.path.exists(instance.kernel_path):
302
        raise errors.HypervisorError("The kernel %s for instance %s is"
303
                                     " missing" % (instance.kernel_path,
304
                                                   instance.name))
305
      kpath = instance.kernel_path
306
    config.write("kernel = '%s'\n" % kpath)
307

    
308
    # initrd handling
309
    if instance.initrd_path in (None, constants.VALUE_DEFAULT):
310
      if os.path.exists(constants.XEN_INITRD):
311
        initrd_path = constants.XEN_INITRD
312
      else:
313
        initrd_path = None
314
    elif instance.initrd_path == constants.VALUE_NONE:
315
      initrd_path = None
316
    else:
317
      if not os.path.exists(instance.initrd_path):
318
        raise errors.HypervisorError("The initrd %s for instance %s is"
319
                                     " missing" % (instance.initrd_path,
320
                                                   instance.name))
321
      initrd_path = instance.initrd_path
322

    
323
    if initrd_path:
324
      config.write("ramdisk = '%s'\n" % initrd_path)
325

    
326
    # rest of the settings
327
    config.write("memory = %d\n" % instance.memory)
328
    config.write("vcpus = %d\n" % instance.vcpus)
329
    config.write("name = '%s'\n" % instance.name)
330

    
331
    vif_data = []
332
    for nic in instance.nics:
333
      nic_str = "mac=%s, bridge=%s" % (nic.mac, nic.bridge)
334
      ip = getattr(nic, "ip", None)
335
      if ip is not None:
336
        nic_str += ", ip=%s" % ip
337
      vif_data.append("'%s'" % nic_str)
338

    
339
    config.write("vif = [%s]\n" % ",".join(vif_data))
340
    config.write("disk = [%s]\n" % ",".join(
341
                 cls._GetConfigFileDiskData(instance.disk_template,
342
                                            block_devices)))
343
    config.write("root = '/dev/sda ro'\n")
344
    config.write("on_poweroff = 'destroy'\n")
345
    config.write("on_reboot = 'restart'\n")
346
    config.write("on_crash = 'restart'\n")
347
    if extra_args:
348
      config.write("extra = '%s'\n" % extra_args)
349
    # just in case it exists
350
    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
351
    try:
352
      f = open("/etc/xen/%s" % instance.name, "w")
353
      try:
354
        f.write(config.getvalue())
355
      finally:
356
        f.close()
357
    except IOError, err:
358
      raise errors.OpExecError("Cannot write Xen instance confile"
359
                               " file /etc/xen/%s: %s" % (instance.name, err))
360
    return True
361

    
362
  @staticmethod
363
  def GetShellCommandForConsole(instance):
364
    """Return a command for connecting to the console of an instance.
365

366
    """
367
    return "xm console %s" % instance.name
368

    
369

    
370
class XenHvmHypervisor(XenHypervisor):
371
  """Xen HVM hypervisor interface"""
372

    
373
  @classmethod
374
  def _WriteConfigFile(cls, instance, block_devices, extra_args):
375
    """Create a Xen 3.1 HVM config file.
376

377
    """
378
    config = StringIO()
379
    config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
380
    config.write("kernel = '/usr/lib/xen/boot/hvmloader'\n")
381
    config.write("builder = 'hvm'\n")
382
    config.write("memory = %d\n" % instance.memory)
383
    config.write("vcpus = %d\n" % instance.vcpus)
384
    config.write("name = '%s'\n" % instance.name)
385
    if instance.hvm_pae:
386
      config.write("pae = 1\n")
387
    else:
388
      config.write("pae = 0\n")
389
    if instance.hvm_acpi:
390
      config.write("acpi = 1\n")
391
    else:
392
      config.write("acpi = 0\n")
393
    config.write("apic = 1\n")
394
    arch = os.uname()[4]
395
    if '64' in arch:
396
      config.write("device_model = '/usr/lib64/xen/bin/qemu-dm'\n")
397
    else:
398
      config.write("device_model = '/usr/lib/xen/bin/qemu-dm'\n")
399
    if instance.hvm_boot_order is None:
400
      config.write("boot = '%s'\n" % constants.HT_HVM_DEFAULT_BOOT_ORDER)
401
    else:
402
      config.write("boot = '%s'\n" % instance.hvm_boot_order)
403
    config.write("sdl = 0\n")
404
    config.write("usb = 1\n")
405
    config.write("usbdevice = 'tablet'\n")
406
    config.write("vnc = 1\n")
407
    config.write("vnclisten = '%s'\n" % instance.vnc_bind_address)
408

    
409
    if instance.network_port > constants.HT_HVM_VNC_BASE_PORT:
410
      display = instance.network_port - constants.HT_HVM_VNC_BASE_PORT
411
      config.write("vncdisplay = %s\n" % display)
412
      config.write("vncunused = 0\n")
413
    else:
414
      config.write("# vncdisplay = 1\n")
415
      config.write("vncunused = 1\n")
416

    
417
    try:
418
      password_file = open(constants.VNC_PASSWORD_FILE, "r")
419
      try:
420
        password = password_file.readline()
421
      finally:
422
        password_file.close()
423
    except IOError:
424
      raise errors.OpExecError("failed to open VNC password file %s " %
425
                               constants.VNC_PASSWORD_FILE)
426

    
427
    config.write("vncpasswd = '%s'\n" % password.rstrip())
428

    
429
    config.write("serial = 'pty'\n")
430
    config.write("localtime = 1\n")
431

    
432
    vif_data = []
433
    for nic in instance.nics:
434
      nic_str = "mac=%s, bridge=%s, type=ioemu" % (nic.mac, nic.bridge)
435
      ip = getattr(nic, "ip", None)
436
      if ip is not None:
437
        nic_str += ", ip=%s" % ip
438
      vif_data.append("'%s'" % nic_str)
439

    
440
    config.write("vif = [%s]\n" % ",".join(vif_data))
441
    disk_data = cls._GetConfigFileDiskData(instance.disk_template,
442
                                            block_devices)
443
    disk_data = [line.replace(",sd", ",ioemu:hd") for line in disk_data]
444
    if instance.hvm_cdrom_image_path is not None:
445
      iso = "'file:%s,hdc:cdrom,r'" % (instance.hvm_cdrom_image_path)
446
      disk_data.append(iso)
447

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

    
450
    config.write("on_poweroff = 'destroy'\n")
451
    config.write("on_reboot = 'restart'\n")
452
    config.write("on_crash = 'restart'\n")
453
    if extra_args:
454
      config.write("extra = '%s'\n" % extra_args)
455
    # just in case it exists
456
    utils.RemoveFile("/etc/xen/auto/%s" % instance.name)
457
    try:
458
      f = open("/etc/xen/%s" % instance.name, "w")
459
      try:
460
        f.write(config.getvalue())
461
      finally:
462
        f.close()
463
    except IOError, err:
464
      raise errors.OpExecError("Cannot write Xen instance confile"
465
                               " file /etc/xen/%s: %s" % (instance.name, err))
466
    return True
467

    
468
  @staticmethod
469
  def GetShellCommandForConsole(instance):
470
    """Return a command for connecting to the console of an instance.
471

472
    """
473
    if instance.network_port is None:
474
      raise errors.OpExecError("no console port defined for %s"
475
                               % instance.name)
476
    elif instance.vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
477
      raise errors.OpExecError("no PTY console, connect to %s:%s via VNC"
478
                               % (instance.primary_node,
479
                                  instance.network_port))
480
    else:
481
      raise errors.OpExecError("no PTY console, connect to %s:%s via VNC"
482
                               % (instance.vnc_bind_address,
483
                                  instance.network_port))