Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_lxc.py @ 9d9bded1

History | View | Annotate | Download (14.6 kB)

1
#
2
#
3

    
4
# Copyright (C) 2010 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
"""LXC hypervisor
23

24
"""
25

    
26
import os
27
import os.path
28
import time
29
import logging
30

    
31
from ganeti import constants
32
from ganeti import errors # pylint: disable=W0611
33
from ganeti import utils
34
from ganeti import objects
35
from ganeti import pathutils
36
from ganeti.hypervisor import hv_base
37
from ganeti.errors import HypervisorError
38

    
39

    
40
class LXCHypervisor(hv_base.BaseHypervisor):
41
  """LXC-based virtualization.
42

43
  Since current (Spring 2010) distributions are not yet ready for
44
  running under a container, the following changes must be done
45
  manually:
46
    - remove udev
47
    - disable the kernel log component of sysklogd/rsyslog/etc.,
48
      otherwise they will fail to read the log, and at least rsyslog
49
      will fill the filesystem with error messages
50

51
  TODO:
52
    - move hardcoded parameters into hypervisor parameters, once we
53
      have the container-parameter support
54
    - implement memory limits, but only optionally, depending on host
55
      kernel support
56

57
  Problems/issues:
58
    - LXC is very temperamental; in daemon mode, it succeeds or fails
59
      in launching the instance silently, without any error
60
      indication, and when failing it can leave network interfaces
61
      around, and future successful startups will list the instance
62
      twice
63
    - shutdown sequence of containers leaves the init 'dead', and the
64
      container effectively stopped, but LXC still believes the
65
      container to be running; need to investigate using the
66
      notify_on_release and release_agent feature of cgroups
67

68
  """
69
  _ROOT_DIR = pathutils.RUN_DIR + "/lxc"
70
  _DEVS = [
71
    "c 1:3",   # /dev/null
72
    "c 1:5",   # /dev/zero
73
    "c 1:7",   # /dev/full
74
    "c 1:8",   # /dev/random
75
    "c 1:9",   # /dev/urandom
76
    "c 1:10",  # /dev/aio
77
    "c 5:0",   # /dev/tty
78
    "c 5:1",   # /dev/console
79
    "c 5:2",   # /dev/ptmx
80
    "c 136:*", # first block of Unix98 PTY slaves
81
    ]
82
  _DENIED_CAPABILITIES = [
83
    "mac_override",    # Allow MAC configuration or state changes
84
    # TODO: remove sys_admin too, for safety
85
    #"sys_admin",       # Perform  a range of system administration operations
86
    "sys_boot",        # Use reboot(2) and kexec_load(2)
87
    "sys_module",      # Load  and  unload kernel modules
88
    "sys_time",        # Set  system  clock, set real-time (hardware) clock
89
    ]
90
  _DIR_MODE = 0755
91

    
92
  PARAMETERS = {
93
    constants.HV_CPU_MASK: hv_base.OPT_CPU_MASK_CHECK,
94
    }
95

    
96
  def __init__(self):
97
    hv_base.BaseHypervisor.__init__(self)
98
    utils.EnsureDirs([(self._ROOT_DIR, self._DIR_MODE)])
99

    
100
  @staticmethod
101
  def _GetMountSubdirs(path):
102
    """Return the list of mountpoints under a given path.
103

104
    """
105
    result = []
106
    for _, mountpoint, _, _ in utils.GetMounts():
107
      if (mountpoint.startswith(path) and
108
          mountpoint != path):
109
        result.append(mountpoint)
110

    
111
    result.sort(key=lambda x: x.count("/"), reverse=True)
112
    return result
113

    
114
  @classmethod
115
  def _InstanceDir(cls, instance_name):
116
    """Return the root directory for an instance.
117

118
    """
119
    return utils.PathJoin(cls._ROOT_DIR, instance_name)
120

    
121
  @classmethod
122
  def _InstanceConfFile(cls, instance_name):
123
    """Return the configuration file for an instance.
124

125
    """
126
    return utils.PathJoin(cls._ROOT_DIR, instance_name + ".conf")
127

    
128
  @classmethod
129
  def _InstanceLogFile(cls, instance_name):
130
    """Return the log file for an instance.
131

132
    """
133
    return utils.PathJoin(cls._ROOT_DIR, instance_name + ".log")
134

    
135
  @classmethod
136
  def _GetCgroupMountPoint(cls):
137
    for _, mountpoint, fstype, _ in utils.GetMounts():
138
      if fstype == "cgroup":
139
        return mountpoint
140
    raise errors.HypervisorError("The cgroup filesystem is not mounted")
141

    
142
  @classmethod
143
  def _GetCgroupCpuList(cls, instance_name):
144
    """Return the list of CPU ids for an instance.
145

146
    """
147
    cgroup = cls._GetCgroupMountPoint()
148
    try:
149
      cpus = utils.ReadFile(utils.PathJoin(cgroup,
150
                                           instance_name,
151
                                           "cpuset.cpus"))
152
    except EnvironmentError, err:
153
      raise errors.HypervisorError("Getting CPU list for instance"
154
                                   " %s failed: %s" % (instance_name, err))
155

    
156
    return utils.ParseCpuMask(cpus)
157

    
158
  def ListInstances(self):
159
    """Get the list of running instances.
160

161
    """
162
    result = utils.RunCmd(["lxc-ls"])
163
    if result.failed:
164
      raise errors.HypervisorError("Running lxc-ls failed: %s" % result.output)
165
    return result.stdout.splitlines()
166

    
167
  def GetInstanceInfo(self, instance_name):
168
    """Get instance properties.
169

170
    @type instance_name: string
171
    @param instance_name: the instance name
172

173
    @return: (name, id, memory, vcpus, stat, times)
174

175
    """
176
    # TODO: read container info from the cgroup mountpoint
177

    
178
    result = utils.RunCmd(["lxc-info", "-n", instance_name])
179
    if result.failed:
180
      raise errors.HypervisorError("Running lxc-info failed: %s" %
181
                                   result.output)
182
    # lxc-info output examples:
183
    # 'ganeti-lxc-test1' is STOPPED
184
    # 'ganeti-lxc-test1' is RUNNING
185
    _, state = result.stdout.rsplit(None, 1)
186
    if state != "RUNNING":
187
      return None
188

    
189
    cpu_list = self._GetCgroupCpuList(instance_name)
190
    return (instance_name, 0, 0, len(cpu_list), 0, 0)
191

    
192
  def GetAllInstancesInfo(self):
193
    """Get properties of all instances.
194

195
    @return: [(name, id, memory, vcpus, stat, times),...]
196

197
    """
198
    data = []
199
    for name in self.ListInstances():
200
      data.append(self.GetInstanceInfo(name))
201
    return data
202

    
203
  def _CreateConfigFile(self, instance, root_dir):
204
    """Create an lxc.conf file for an instance.
205

206
    """
207
    out = []
208
    # hostname
209
    out.append("lxc.utsname = %s" % instance.name)
210

    
211
    # separate pseudo-TTY instances
212
    out.append("lxc.pts = 255")
213
    # standard TTYs
214
    out.append("lxc.tty = 6")
215
    # console log file
216
    console_log = utils.PathJoin(self._ROOT_DIR, instance.name + ".console")
217
    try:
218
      utils.WriteFile(console_log, data="", mode=constants.SECURE_FILE_MODE)
219
    except EnvironmentError, err:
220
      raise errors.HypervisorError("Creating console log file %s for"
221
                                   " instance %s failed: %s" %
222
                                   (console_log, instance.name, err))
223
    out.append("lxc.console = %s" % console_log)
224

    
225
    # root FS
226
    out.append("lxc.rootfs = %s" % root_dir)
227

    
228
    # TODO: additional mounts, if we disable CAP_SYS_ADMIN
229

    
230
    # CPUs
231
    if instance.hvparams[constants.HV_CPU_MASK]:
232
      cpu_list = utils.ParseCpuMask(instance.hvparams[constants.HV_CPU_MASK])
233
      cpus_in_mask = len(cpu_list)
234
      if cpus_in_mask != instance.beparams["vcpus"]:
235
        raise errors.HypervisorError("Number of VCPUs (%d) doesn't match"
236
                                     " the number of CPUs in the"
237
                                     " cpu_mask (%d)" %
238
                                     (instance.beparams["vcpus"],
239
                                      cpus_in_mask))
240
      out.append("lxc.cgroup.cpuset.cpus = %s" %
241
                 instance.hvparams[constants.HV_CPU_MASK])
242

    
243
    # Device control
244
    # deny direct device access
245
    out.append("lxc.cgroup.devices.deny = a")
246
    for devinfo in self._DEVS:
247
      out.append("lxc.cgroup.devices.allow = %s rw" % devinfo)
248

    
249
    # Networking
250
    for idx, nic in enumerate(instance.nics):
251
      out.append("# NIC %d" % idx)
252
      mode = nic.nicparams[constants.NIC_MODE]
253
      link = nic.nicparams[constants.NIC_LINK]
254
      if mode == constants.NIC_MODE_BRIDGED:
255
        out.append("lxc.network.type = veth")
256
        out.append("lxc.network.link = %s" % link)
257
      else:
258
        raise errors.HypervisorError("LXC hypervisor only supports"
259
                                     " bridged mode (NIC %d has mode %s)" %
260
                                     (idx, mode))
261
      out.append("lxc.network.hwaddr = %s" % nic.mac)
262
      out.append("lxc.network.flags = up")
263

    
264
    # Capabilities
265
    for cap in self._DENIED_CAPABILITIES:
266
      out.append("lxc.cap.drop = %s" % cap)
267

    
268
    return "\n".join(out) + "\n"
269

    
270
  def StartInstance(self, instance, block_devices, startup_paused):
271
    """Start an instance.
272

273
    For LCX, we try to mount the block device and execute 'lxc-start'.
274
    We use volatile containers.
275

276
    """
277
    root_dir = self._InstanceDir(instance.name)
278
    try:
279
      utils.EnsureDirs([(root_dir, self._DIR_MODE)])
280
    except errors.GenericError, err:
281
      raise HypervisorError("Creating instance directory failed: %s", str(err))
282

    
283
    conf_file = self._InstanceConfFile(instance.name)
284
    utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir))
285

    
286
    log_file = self._InstanceLogFile(instance.name)
287
    if not os.path.exists(log_file):
288
      try:
289
        utils.WriteFile(log_file, data="", mode=constants.SECURE_FILE_MODE)
290
      except EnvironmentError, err:
291
        raise errors.HypervisorError("Creating hypervisor log file %s for"
292
                                     " instance %s failed: %s" %
293
                                     (log_file, instance.name, err))
294

    
295
    if not os.path.ismount(root_dir):
296
      if not block_devices:
297
        raise HypervisorError("LXC needs at least one disk")
298

    
299
      sda_dev_path = block_devices[0][1]
300
      result = utils.RunCmd(["mount", sda_dev_path, root_dir])
301
      if result.failed:
302
        raise HypervisorError("Mounting the root dir of LXC instance %s"
303
                              " failed: %s" % (instance.name, result.output))
304
    result = utils.RunCmd(["lxc-start", "-n", instance.name,
305
                           "-o", log_file,
306
                           "-l", "DEBUG",
307
                           "-f", conf_file, "-d"])
308
    if result.failed:
309
      raise HypervisorError("Running the lxc-start script failed: %s" %
310
                            result.output)
311

    
312
  def StopInstance(self, instance, force=False, retry=False, name=None):
313
    """Stop an instance.
314

315
    This method has complicated cleanup tests, as we must:
316
      - try to kill all leftover processes
317
      - try to unmount any additional sub-mountpoints
318
      - finally unmount the instance dir
319

320
    """
321
    if name is None:
322
      name = instance.name
323

    
324
    root_dir = self._InstanceDir(name)
325
    if not os.path.exists(root_dir):
326
      return
327

    
328
    if name in self.ListInstances():
329
      # Signal init to shutdown; this is a hack
330
      if not retry and not force:
331
        result = utils.RunCmd(["chroot", root_dir, "poweroff"])
332
        if result.failed:
333
          raise HypervisorError("Running 'poweroff' on the instance"
334
                                " failed: %s" % result.output)
335
      time.sleep(2)
336
      result = utils.RunCmd(["lxc-stop", "-n", name])
337
      if result.failed:
338
        logging.warning("Error while doing lxc-stop for %s: %s", name,
339
                        result.output)
340

    
341
    for mpath in self._GetMountSubdirs(root_dir):
342
      result = utils.RunCmd(["umount", mpath])
343
      if result.failed:
344
        logging.warning("Error while umounting subpath %s for instance %s: %s",
345
                        mpath, name, result.output)
346

    
347
    result = utils.RunCmd(["umount", root_dir])
348
    if result.failed and force:
349
      msg = ("Processes still alive in the chroot: %s" %
350
             utils.RunCmd("fuser -vm %s" % root_dir).output)
351
      logging.error(msg)
352
      raise HypervisorError("Unmounting the chroot dir failed: %s (%s)" %
353
                            (result.output, msg))
354

    
355
  def RebootInstance(self, instance):
356
    """Reboot an instance.
357

358
    This is not (yet) implemented (in Ganeti) for the LXC hypervisor.
359

360
    """
361
    # TODO: implement reboot
362
    raise HypervisorError("The LXC hypervisor doesn't implement the"
363
                          " reboot functionality")
364

    
365
  def BalloonInstanceMemory(self, instance, mem):
366
    """Balloon an instance memory to a certain value.
367

368
    @type instance: L{objects.Instance}
369
    @param instance: instance to be accepted
370
    @type mem: int
371
    @param mem: actual memory size to use for instance runtime
372

373
    """
374
    # Currently lxc instances don't have memory limits
375
    pass
376

    
377
  def GetNodeInfo(self):
378
    """Return information about the node.
379

380
    This is just a wrapper over the base GetLinuxNodeInfo method.
381

382
    @return: a dict with the following keys (values in MiB):
383
          - memory_total: the total memory size on the node
384
          - memory_free: the available memory on the node for instances
385
          - memory_dom0: the memory used by the node itself, if available
386

387
    """
388
    return self.GetLinuxNodeInfo()
389

    
390
  @classmethod
391
  def GetInstanceConsole(cls, instance, hvparams, beparams):
392
    """Return a command for connecting to the console of an instance.
393

394
    """
395
    return objects.InstanceConsole(instance=instance.name,
396
                                   kind=constants.CONS_SSH,
397
                                   host=instance.primary_node,
398
                                   user=constants.GANETI_RUNAS,
399
                                   command=["lxc-console", "-n", instance.name])
400

    
401
  def Verify(self):
402
    """Verify the hypervisor.
403

404
    For the chroot manager, it just checks the existence of the base dir.
405

406
    """
407
    if not os.path.exists(self._ROOT_DIR):
408
      return "The required directory '%s' does not exist." % self._ROOT_DIR
409

    
410
  @classmethod
411
  def PowercycleNode(cls):
412
    """LXC powercycle, just a wrapper over Linux powercycle.
413

414
    """
415
    cls.LinuxPowercycle()
416

    
417
  def MigrateInstance(self, instance, target, live):
418
    """Migrate an instance.
419

420
    @type instance: L{objects.Instance}
421
    @param instance: the instance to be migrated
422
    @type target: string
423
    @param target: hostname (usually ip) of the target node
424
    @type live: boolean
425
    @param live: whether to do a live or non-live migration
426

427
    """
428
    raise HypervisorError("Migration is not supported by the LXC hypervisor")
429

    
430
  def GetMigrationStatus(self, instance):
431
    """Get the migration status
432

433
    @type instance: L{objects.Instance}
434
    @param instance: the instance that is being migrated
435
    @rtype: L{objects.MigrationStatus}
436
    @return: the status of the current migration (one of
437
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
438
             progress info that can be retrieved from the hypervisor
439

440
    """
441
    raise HypervisorError("Migration is not supported by the LXC hypervisor")