Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_lxc.py @ 2e5a6203

History | View | Annotate | Download (14.2 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
  TODO:
44
    - move hardcoded parameters into hypervisor parameters, once we
45
      have the container-parameter support
46
    - implement memory limits, but only optionally, depending on host
47
      kernel support
48

49
  Problems/issues:
50
    - LXC is very temperamental; in daemon mode, it succeeds or fails
51
      in launching the instance silently, without any error
52
      indication, and when failing it can leave network interfaces
53
      around, and future successful startups will list the instance
54
      twice
55

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

    
80
  PARAMETERS = {
81
    constants.HV_CPU_MASK: hv_base.OPT_CPU_MASK_CHECK,
82
    }
83

    
84
  def __init__(self):
85
    hv_base.BaseHypervisor.__init__(self)
86
    utils.EnsureDirs([(self._ROOT_DIR, self._DIR_MODE)])
87

    
88
  @staticmethod
89
  def _GetMountSubdirs(path):
90
    """Return the list of mountpoints under a given path.
91

92
    """
93
    result = []
94
    for _, mountpoint, _, _ in utils.GetMounts():
95
      if (mountpoint.startswith(path) and
96
          mountpoint != path):
97
        result.append(mountpoint)
98

    
99
    result.sort(key=lambda x: x.count("/"), reverse=True)
100
    return result
101

    
102
  @classmethod
103
  def _InstanceDir(cls, instance_name):
104
    """Return the root directory for an instance.
105

106
    """
107
    return utils.PathJoin(cls._ROOT_DIR, instance_name)
108

    
109
  @classmethod
110
  def _InstanceConfFile(cls, instance_name):
111
    """Return the configuration file for an instance.
112

113
    """
114
    return utils.PathJoin(cls._ROOT_DIR, instance_name + ".conf")
115

    
116
  @classmethod
117
  def _InstanceLogFile(cls, instance_name):
118
    """Return the log file for an instance.
119

120
    """
121
    return utils.PathJoin(cls._ROOT_DIR, instance_name + ".log")
122

    
123
  @classmethod
124
  def _GetCgroupMountPoint(cls):
125
    for _, mountpoint, fstype, _ in utils.GetMounts():
126
      if fstype == "cgroup":
127
        return mountpoint
128
    raise errors.HypervisorError("The cgroup filesystem is not mounted")
129

    
130
  @classmethod
131
  def _GetCgroupCpuList(cls, instance_name):
132
    """Return the list of CPU ids for an instance.
133

134
    """
135
    cgroup = cls._GetCgroupMountPoint()
136
    try:
137
      cpus = utils.ReadFile(utils.PathJoin(cgroup, 'lxc',
138
                                           instance_name,
139
                                           "cpuset.cpus"))
140
    except EnvironmentError, err:
141
      raise errors.HypervisorError("Getting CPU list for instance"
142
                                   " %s failed: %s" % (instance_name, err))
143

    
144
    return utils.ParseCpuMask(cpus)
145

    
146
  def ListInstances(self):
147
    """Get the list of running instances.
148

149
    """
150
    return [ iinfo[0] for iinfo in self.GetAllInstancesInfo() ]
151

    
152
  def GetInstanceInfo(self, instance_name):
153
    """Get instance properties.
154

155
    @type instance_name: string
156
    @param instance_name: the instance name
157

158
    @return: (name, id, memory, vcpus, stat, times)
159

160
    """
161
    # TODO: read container info from the cgroup mountpoint
162

    
163
    result = utils.RunCmd(["lxc-info", "-s", "-n", instance_name])
164
    if result.failed:
165
      raise errors.HypervisorError("Running lxc-info failed: %s" %
166
                                   result.output)
167
    # lxc-info output examples:
168
    # 'state: STOPPED
169
    # 'state: RUNNING
170
    _, state = result.stdout.rsplit(None, 1)
171
    if state != "RUNNING":
172
      return None
173

    
174
    cpu_list = self._GetCgroupCpuList(instance_name)
175
    return (instance_name, 0, 0, len(cpu_list), 0, 0)
176

    
177
  def GetAllInstancesInfo(self):
178
    """Get properties of all instances.
179

180
    @return: [(name, id, memory, vcpus, stat, times),...]
181

182
    """
183
    data = []
184
    for name in os.listdir(self._ROOT_DIR):
185
      try:
186
        info = self.GetInstanceInfo(name)
187
      except errors.HypervisorError:
188
        continue
189
      if info:
190
        data.append(info)
191
    return data
192

    
193
  def _CreateConfigFile(self, instance, root_dir):
194
    """Create an lxc.conf file for an instance.
195

196
    """
197
    out = []
198
    # hostname
199
    out.append("lxc.utsname = %s" % instance.name)
200

    
201
    # separate pseudo-TTY instances
202
    out.append("lxc.pts = 255")
203
    # standard TTYs
204
    out.append("lxc.tty = 6")
205
    # console log file
206
    console_log = utils.PathJoin(self._ROOT_DIR, instance.name + ".console")
207
    try:
208
      utils.WriteFile(console_log, data="", mode=constants.SECURE_FILE_MODE)
209
    except EnvironmentError, err:
210
      raise errors.HypervisorError("Creating console log file %s for"
211
                                   " instance %s failed: %s" %
212
                                   (console_log, instance.name, err))
213
    out.append("lxc.console = %s" % console_log)
214

    
215
    # root FS
216
    out.append("lxc.rootfs = %s" % root_dir)
217

    
218
    # TODO: additional mounts, if we disable CAP_SYS_ADMIN
219

    
220
    # CPUs
221
    if instance.hvparams[constants.HV_CPU_MASK]:
222
      cpu_list = utils.ParseCpuMask(instance.hvparams[constants.HV_CPU_MASK])
223
      cpus_in_mask = len(cpu_list)
224
      if cpus_in_mask != instance.beparams["vcpus"]:
225
        raise errors.HypervisorError("Number of VCPUs (%d) doesn't match"
226
                                     " the number of CPUs in the"
227
                                     " cpu_mask (%d)" %
228
                                     (instance.beparams["vcpus"],
229
                                      cpus_in_mask))
230
      out.append("lxc.cgroup.cpuset.cpus = %s" %
231
                 instance.hvparams[constants.HV_CPU_MASK])
232

    
233
    # Device control
234
    # deny direct device access
235
    out.append("lxc.cgroup.devices.deny = a")
236
    for devinfo in self._DEVS:
237
      out.append("lxc.cgroup.devices.allow = %s rw" % devinfo)
238

    
239
    # Networking
240
    for idx, nic in enumerate(instance.nics):
241
      out.append("# NIC %d" % idx)
242
      mode = nic.nicparams[constants.NIC_MODE]
243
      link = nic.nicparams[constants.NIC_LINK]
244
      if mode == constants.NIC_MODE_BRIDGED:
245
        out.append("lxc.network.type = veth")
246
        out.append("lxc.network.link = %s" % link)
247
      else:
248
        raise errors.HypervisorError("LXC hypervisor only supports"
249
                                     " bridged mode (NIC %d has mode %s)" %
250
                                     (idx, mode))
251
      out.append("lxc.network.hwaddr = %s" % nic.mac)
252
      out.append("lxc.network.flags = up")
253

    
254
    # Capabilities
255
    for cap in self._DENIED_CAPABILITIES:
256
      out.append("lxc.cap.drop = %s" % cap)
257

    
258
    return "\n".join(out) + "\n"
259

    
260
  def StartInstance(self, instance, block_devices, startup_paused):
261
    """Start an instance.
262

263
    For LXC, we try to mount the block device and execute 'lxc-start'.
264
    We use volatile containers.
265

266
    """
267
    root_dir = self._InstanceDir(instance.name)
268
    try:
269
      utils.EnsureDirs([(root_dir, self._DIR_MODE)])
270
    except errors.GenericError, err:
271
      raise HypervisorError("Creating instance directory failed: %s", str(err))
272

    
273
    conf_file = self._InstanceConfFile(instance.name)
274
    utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir))
275

    
276
    log_file = self._InstanceLogFile(instance.name)
277
    if not os.path.exists(log_file):
278
      try:
279
        utils.WriteFile(log_file, data="", mode=constants.SECURE_FILE_MODE)
280
      except EnvironmentError, err:
281
        raise errors.HypervisorError("Creating hypervisor log file %s for"
282
                                     " instance %s failed: %s" %
283
                                     (log_file, instance.name, err))
284

    
285
    if not os.path.ismount(root_dir):
286
      if not block_devices:
287
        raise HypervisorError("LXC needs at least one disk")
288

    
289
      sda_dev_path = block_devices[0][1]
290
      result = utils.RunCmd(["mount", sda_dev_path, root_dir])
291
      if result.failed:
292
        raise HypervisorError("Mounting the root dir of LXC instance %s"
293
                              " failed: %s" % (instance.name, result.output))
294
    result = utils.RunCmd(["lxc-start", "-n", instance.name,
295
                           "-o", log_file,
296
                           "-l", "DEBUG",
297
                           "-f", conf_file, "-d"])
298
    if result.failed:
299
      raise HypervisorError("Running the lxc-start script failed: %s" %
300
                            result.output)
301

    
302
  def StopInstance(self, instance, force=False, retry=False, name=None):
303
    """Stop an instance.
304

305
    This method has complicated cleanup tests, as we must:
306
      - try to kill all leftover processes
307
      - try to unmount any additional sub-mountpoints
308
      - finally unmount the instance dir
309

310
    """
311
    if name is None:
312
      name = instance.name
313

    
314
    root_dir = self._InstanceDir(name)
315
    if not os.path.exists(root_dir):
316
      return
317

    
318
    if name in self.ListInstances():
319
      # Signal init to shutdown; this is a hack
320
      if not retry and not force:
321
        result = utils.RunCmd(["chroot", root_dir, "poweroff"])
322
        if result.failed:
323
          raise HypervisorError("Running 'poweroff' on the instance"
324
                                " failed: %s" % result.output)
325
      time.sleep(2)
326
      result = utils.RunCmd(["lxc-stop", "-n", name])
327
      if result.failed:
328
        logging.warning("Error while doing lxc-stop for %s: %s", name,
329
                        result.output)
330

    
331
    if not os.path.ismount(root_dir):
332
        return
333

    
334
    for mpath in self._GetMountSubdirs(root_dir):
335
      result = utils.RunCmd(["umount", mpath])
336
      if result.failed:
337
        logging.warning("Error while umounting subpath %s for instance %s: %s",
338
                        mpath, name, result.output)
339

    
340
    result = utils.RunCmd(["umount", root_dir])
341
    if result.failed and force:
342
      msg = ("Processes still alive in the chroot: %s" %
343
             utils.RunCmd("fuser -vm %s" % root_dir).output)
344
      logging.error(msg)
345
      raise HypervisorError("Unmounting the chroot dir failed: %s (%s)" %
346
                            (result.output, msg))
347

    
348
  def RebootInstance(self, instance):
349
    """Reboot an instance.
350

351
    This is not (yet) implemented (in Ganeti) for the LXC hypervisor.
352

353
    """
354
    # TODO: implement reboot
355
    raise HypervisorError("The LXC hypervisor doesn't implement the"
356
                          " reboot functionality")
357

    
358
  def BalloonInstanceMemory(self, instance, mem):
359
    """Balloon an instance memory to a certain value.
360

361
    @type instance: L{objects.Instance}
362
    @param instance: instance to be accepted
363
    @type mem: int
364
    @param mem: actual memory size to use for instance runtime
365

366
    """
367
    # Currently lxc instances don't have memory limits
368
    pass
369

    
370
  def GetNodeInfo(self):
371
    """Return information about the node.
372

373
    This is just a wrapper over the base GetLinuxNodeInfo method.
374

375
    @return: a dict with the following keys (values in MiB):
376
          - memory_total: the total memory size on the node
377
          - memory_free: the available memory on the node for instances
378
          - memory_dom0: the memory used by the node itself, if available
379

380
    """
381
    return self.GetLinuxNodeInfo()
382

    
383
  @classmethod
384
  def GetInstanceConsole(cls, instance, hvparams, beparams):
385
    """Return a command for connecting to the console of an instance.
386

387
    """
388
    return objects.InstanceConsole(instance=instance.name,
389
                                   kind=constants.CONS_SSH,
390
                                   host=instance.primary_node,
391
                                   user=constants.SSH_CONSOLE_USER,
392
                                   command=["lxc-console", "-n", instance.name])
393

    
394
  def Verify(self):
395
    """Verify the hypervisor.
396

397
    For the LXC manager, it just checks the existence of the base dir.
398

399
    @return: Problem description if something is wrong, C{None} otherwise
400

401
    """
402
    if os.path.exists(self._ROOT_DIR):
403
      return None
404
    else:
405
      return "The required directory '%s' does not exist" % self._ROOT_DIR
406

    
407
  @classmethod
408
  def PowercycleNode(cls):
409
    """LXC powercycle, just a wrapper over Linux powercycle.
410

411
    """
412
    cls.LinuxPowercycle()
413

    
414
  def MigrateInstance(self, instance, target, live):
415
    """Migrate an instance.
416

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

424
    """
425
    raise HypervisorError("Migration is not supported by the LXC hypervisor")
426

    
427
  def GetMigrationStatus(self, instance):
428
    """Get the migration status
429

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

437
    """
438
    raise HypervisorError("Migration is not supported by the LXC hypervisor")