Package ganeti :: Package hypervisor :: Module hv_lxc
[hide private]
[frames] | no frames]

Source Code for Module ganeti.hypervisor.hv_lxc

  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-msg=W0611 
 33  from ganeti import utils 
 34  from ganeti.hypervisor import hv_base 
 35  from ganeti.errors import HypervisorError 
36 37 38 -class LXCHypervisor(hv_base.BaseHypervisor):
39 """LXC-based virtualization. 40 41 Since current (Spring 2010) distributions are not yet ready for 42 running under a container, the following changes must be done 43 manually: 44 - remove udev 45 - disable the kernel log component of sysklogd/rsyslog/etc., 46 otherwise they will fail to read the log, and at least rsyslog 47 will fill the filesystem with error messages 48 49 TODO: 50 - move hardcoded parameters into hypervisor parameters, once we 51 have the container-parameter support 52 - implement memory limits, but only optionally, depending on host 53 kernel support 54 55 Problems/issues: 56 - LXC is very temperamental; in daemon mode, it succeeds or fails 57 in launching the instance silently, without any error 58 indication, and when failing it can leave network interfaces 59 around, and future successful startups will list the instance 60 twice 61 - shutdown sequence of containers leaves the init 'dead', and the 62 container effectively stopped, but LXC still believes the 63 container to be running; need to investigate using the 64 notify_on_release and release_agent feature of cgroups 65 66 """ 67 _ROOT_DIR = constants.RUN_GANETI_DIR + "/lxc" 68 _DEVS = [ 69 "c 1:3", # /dev/null 70 "c 1:5", # /dev/zero 71 "c 1:7", # /dev/full 72 "c 1:8", # /dev/random 73 "c 1:9", # /dev/urandom 74 "c 1:10", # /dev/aio 75 "c 5:0", # /dev/tty 76 "c 5:1", # /dev/console 77 "c 5:2", # /dev/ptmx 78 "c 136:*", # first block of Unix98 PTY slaves 79 ] 80 _DENIED_CAPABILITIES = [ 81 "mac_override", # Allow MAC configuration or state changes 82 # TODO: remove sys_admin too, for safety 83 #"sys_admin", # Perform a range of system administration operations 84 "sys_boot", # Use reboot(2) and kexec_load(2) 85 "sys_module", # Load and unload kernel modules 86 "sys_time", # Set system clock, set real-time (hardware) clock 87 ] 88 _DIR_MODE = 0755 89 90 PARAMETERS = { 91 constants.HV_CPU_MASK: hv_base.OPT_CPU_MASK_CHECK, 92 } 93
94 - def __init__(self):
97 98 @staticmethod
99 - def _GetMountSubdirs(path):
100 """Return the list of mountpoints under a given path. 101 102 """ 103 result = [] 104 for _, mountpoint, _, _ in utils.GetMounts(): 105 if (mountpoint.startswith(path) and 106 mountpoint != path): 107 result.append(mountpoint) 108 109 result.sort(key=lambda x: x.count("/"), reverse=True) 110 return result
111 112 @classmethod
113 - def _InstanceDir(cls, instance_name):
114 """Return the root directory for an instance. 115 116 """ 117 return utils.PathJoin(cls._ROOT_DIR, instance_name)
118 119 @classmethod
120 - def _InstanceConfFile(cls, instance_name):
121 """Return the configuration file for an instance. 122 123 """ 124 return utils.PathJoin(cls._ROOT_DIR, instance_name + ".conf")
125 126 @classmethod
127 - def _InstanceLogFile(cls, instance_name):
128 """Return the log file for an instance. 129 130 """ 131 return utils.PathJoin(cls._ROOT_DIR, instance_name + ".log")
132 133 @classmethod
134 - def _GetCgroupMountPoint(cls):
135 for _, mountpoint, fstype, _ in utils.GetMounts(): 136 if fstype == "cgroup": 137 return mountpoint 138 raise errors.HypervisorError("The cgroup filesystem is not mounted")
139 140 @classmethod
141 - def _GetCgroupCpuList(cls, instance_name):
142 """Return the list of CPU ids for an instance. 143 144 """ 145 cgroup = cls._GetCgroupMountPoint() 146 try: 147 cpus = utils.ReadFile(utils.PathJoin(cgroup, 148 instance_name, 149 "cpuset.cpus")) 150 except EnvironmentError, err: 151 raise errors.HypervisorError("Getting CPU list for instance" 152 " %s failed: %s" % (instance_name, err)) 153 154 return utils.ParseCpuMask(cpus)
155
156 - def ListInstances(self):
157 """Get the list of running instances. 158 159 """ 160 result = utils.RunCmd(["lxc-ls"]) 161 if result.failed: 162 raise errors.HypervisorError("Running lxc-ls failed: %s" % result.output) 163 return result.stdout.splitlines()
164
165 - def GetInstanceInfo(self, instance_name):
166 """Get instance properties. 167 168 @type instance_name: string 169 @param instance_name: the instance name 170 171 @return: (name, id, memory, vcpus, stat, times) 172 173 """ 174 # TODO: read container info from the cgroup mountpoint 175 176 result = utils.RunCmd(["lxc-info", "-n", instance_name]) 177 if result.failed: 178 raise errors.HypervisorError("Running lxc-info failed: %s" % 179 result.output) 180 # lxc-info output examples: 181 # 'ganeti-lxc-test1' is STOPPED 182 # 'ganeti-lxc-test1' is RUNNING 183 _, state = result.stdout.rsplit(None, 1) 184 if state != "RUNNING": 185 return None 186 187 cpu_list = self._GetCgroupCpuList(instance_name) 188 return (instance_name, 0, 0, len(cpu_list), 0, 0)
189
190 - def GetAllInstancesInfo(self):
191 """Get properties of all instances. 192 193 @return: [(name, id, memory, vcpus, stat, times),...] 194 195 """ 196 data = [] 197 for name in self.ListInstances(): 198 data.append(self.GetInstanceInfo(name)) 199 return data
200
201 - def _CreateConfigFile(self, instance, root_dir):
202 """Create an lxc.conf file for an instance. 203 204 """ 205 out = [] 206 # hostname 207 out.append("lxc.utsname = %s" % instance.name) 208 209 # separate pseudo-TTY instances 210 out.append("lxc.pts = 255") 211 # standard TTYs 212 out.append("lxc.tty = 6") 213 # console log file 214 console_log = utils.PathJoin(self._ROOT_DIR, instance.name + ".console") 215 try: 216 utils.WriteFile(console_log, data="", mode=constants.SECURE_FILE_MODE) 217 except EnvironmentError, err: 218 raise errors.HypervisorError("Creating console log file %s for" 219 " instance %s failed: %s" % 220 (console_log, instance.name, err)) 221 out.append("lxc.console = %s" % console_log) 222 223 # root FS 224 out.append("lxc.rootfs = %s" % root_dir) 225 226 # TODO: additional mounts, if we disable CAP_SYS_ADMIN 227 228 # CPUs 229 if instance.hvparams[constants.HV_CPU_MASK]: 230 cpu_list = utils.ParseCpuMask(instance.hvparams[constants.HV_CPU_MASK]) 231 cpus_in_mask = len(cpu_list) 232 if cpus_in_mask != instance.beparams["vcpus"]: 233 raise errors.HypervisorError("Number of VCPUs (%d) doesn't match" 234 " the number of CPUs in the" 235 " cpu_mask (%d)" % 236 (instance.beparams["vcpus"], 237 cpus_in_mask)) 238 out.append("lxc.cgroup.cpuset.cpus = %s" % 239 instance.hvparams[constants.HV_CPU_MASK]) 240 241 # Device control 242 # deny direct device access 243 out.append("lxc.cgroup.devices.deny = a") 244 for devinfo in self._DEVS: 245 out.append("lxc.cgroup.devices.allow = %s rw" % devinfo) 246 247 # Networking 248 for idx, nic in enumerate(instance.nics): 249 out.append("# NIC %d" % idx) 250 mode = nic.nicparams[constants.NIC_MODE] 251 link = nic.nicparams[constants.NIC_LINK] 252 if mode == constants.NIC_MODE_BRIDGED: 253 out.append("lxc.network.type = veth") 254 out.append("lxc.network.link = %s" % link) 255 else: 256 raise errors.HypervisorError("LXC hypervisor only supports" 257 " bridged mode (NIC %d has mode %s)" % 258 (idx, mode)) 259 out.append("lxc.network.hwaddr = %s" % nic.mac) 260 out.append("lxc.network.flags = up") 261 262 # Capabilities 263 for cap in self._DENIED_CAPABILITIES: 264 out.append("lxc.cap.drop = %s" % cap) 265 266 return "\n".join(out) + "\n"
267
268 - def StartInstance(self, instance, block_devices):
269 """Start an instance. 270 271 For LCX, we try to mount the block device and execute 'lxc-start'. 272 We use volatile containers. 273 274 """ 275 root_dir = self._InstanceDir(instance.name) 276 try: 277 utils.EnsureDirs([(root_dir, self._DIR_MODE)]) 278 except errors.GenericError, err: 279 raise HypervisorError("Creating instance directory failed: %s", str(err)) 280 281 conf_file = self._InstanceConfFile(instance.name) 282 utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir)) 283 284 log_file = self._InstanceLogFile(instance.name) 285 if not os.path.exists(log_file): 286 try: 287 utils.WriteFile(log_file, data="", mode=constants.SECURE_FILE_MODE) 288 except EnvironmentError, err: 289 raise errors.HypervisorError("Creating hypervisor log file %s for" 290 " instance %s failed: %s" % 291 (log_file, instance.name, err)) 292 293 if not os.path.ismount(root_dir): 294 if not block_devices: 295 raise HypervisorError("LXC needs at least one disk") 296 297 sda_dev_path = block_devices[0][1] 298 result = utils.RunCmd(["mount", sda_dev_path, root_dir]) 299 if result.failed: 300 raise HypervisorError("Mounting the root dir of LXC instance %s" 301 " failed: %s" % (instance.name, result.output)) 302 result = utils.RunCmd(["lxc-start", "-n", instance.name, 303 "-o", log_file, 304 "-l", "DEBUG", 305 "-f", conf_file, "-d"]) 306 if result.failed: 307 raise HypervisorError("Running the lxc-start script failed: %s" % 308 result.output)
309
310 - def StopInstance(self, instance, force=False, retry=False, name=None):
311 """Stop an instance. 312 313 This method has complicated cleanup tests, as we must: 314 - try to kill all leftover processes 315 - try to unmount any additional sub-mountpoints 316 - finally unmount the instance dir 317 318 """ 319 if name is None: 320 name = instance.name 321 322 root_dir = self._InstanceDir(name) 323 if not os.path.exists(root_dir): 324 return 325 326 if name in self.ListInstances(): 327 # Signal init to shutdown; this is a hack 328 if not retry and not force: 329 result = utils.RunCmd(["chroot", root_dir, "poweroff"]) 330 if result.failed: 331 raise HypervisorError("Running 'poweroff' on the instance" 332 " failed: %s" % result.output) 333 time.sleep(2) 334 result = utils.RunCmd(["lxc-stop", "-n", name]) 335 if result.failed: 336 logging.warning("Error while doing lxc-stop for %s: %s", name, 337 result.output) 338 339 for mpath in self._GetMountSubdirs(root_dir): 340 result = utils.RunCmd(["umount", mpath]) 341 if result.failed: 342 logging.warning("Error while umounting subpath %s for instance %s: %s", 343 mpath, name, result.output) 344 345 result = utils.RunCmd(["umount", root_dir]) 346 if result.failed and force: 347 msg = ("Processes still alive in the chroot: %s" % 348 utils.RunCmd("fuser -vm %s" % root_dir).output) 349 logging.error(msg) 350 raise HypervisorError("Unmounting the chroot dir failed: %s (%s)" % 351 (result.output, msg))
352
353 - def RebootInstance(self, instance):
354 """Reboot an instance. 355 356 This is not (yet) implemented (in Ganeti) for the LXC hypervisor. 357 358 """ 359 # TODO: implement reboot 360 raise HypervisorError("The LXC hypervisor doesn't implement the" 361 " reboot functionality")
362
363 - def GetNodeInfo(self):
364 """Return information about the node. 365 366 This is just a wrapper over the base GetLinuxNodeInfo method. 367 368 @return: a dict with the following keys (values in MiB): 369 - memory_total: the total memory size on the node 370 - memory_free: the available memory on the node for instances 371 - memory_dom0: the memory used by the node itself, if available 372 373 """ 374 return self.GetLinuxNodeInfo()
375 376 @classmethod
377 - def GetShellCommandForConsole(cls, instance, hvparams, beparams):
378 """Return a command for connecting to the console of an instance. 379 380 """ 381 return "lxc-console -n %s" % instance.name
382
383 - def Verify(self):
384 """Verify the hypervisor. 385 386 For the chroot manager, it just checks the existence of the base dir. 387 388 """ 389 if not os.path.exists(self._ROOT_DIR): 390 return "The required directory '%s' does not exist." % self._ROOT_DIR
391 392 @classmethod
393 - def PowercycleNode(cls):
394 """LXC powercycle, just a wrapper over Linux powercycle. 395 396 """ 397 cls.LinuxPowercycle()
398
399 - def MigrateInstance(self, instance, target, live):
400 """Migrate an instance. 401 402 @type instance: L{objects.Instance} 403 @param instance: the instance to be migrated 404 @type target: string 405 @param target: hostname (usually ip) of the target node 406 @type live: boolean 407 @param live: whether to do a live or non-live migration 408 409 """ 410 raise HypervisorError("Migration is not supported by the LXC hypervisor")
411