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, 2013 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 47 Problems/issues: 48 - LXC is very temperamental; in daemon mode, it succeeds or fails 49 in launching the instance silently, without any error 50 indication, and when failing it can leave network interfaces 51 around, and future successful startups will list the instance 52 twice 53 54 """ 55 _ROOT_DIR = pathutils.RUN_DIR + "/lxc" 56 _DEVS = [ 57 "c 1:3", # /dev/null 58 "c 1:5", # /dev/zero 59 "c 1:7", # /dev/full 60 "c 1:8", # /dev/random 61 "c 1:9", # /dev/urandom 62 "c 1:10", # /dev/aio 63 "c 5:0", # /dev/tty 64 "c 5:1", # /dev/console 65 "c 5:2", # /dev/ptmx 66 "c 136:*", # first block of Unix98 PTY slaves 67 ] 68 _DENIED_CAPABILITIES = [ 69 "mac_override", # Allow MAC configuration or state changes 70 # TODO: remove sys_admin too, for safety 71 #"sys_admin", # Perform a range of system administration operations 72 "sys_boot", # Use reboot(2) and kexec_load(2) 73 "sys_module", # Load and unload kernel modules 74 "sys_time", # Set system clock, set real-time (hardware) clock 75 ] 76 _DIR_MODE = 0755 77 78 PARAMETERS = { 79 constants.HV_CPU_MASK: hv_base.OPT_CPU_MASK_CHECK, 80 } 81
82 - def __init__(self):
83 hv_base.BaseHypervisor.__init__(self) 84 utils.EnsureDirs([(self._ROOT_DIR, self._DIR_MODE)])
85 86 @staticmethod
87 - def _GetMountSubdirs(path):
88 """Return the list of mountpoints under a given path. 89 90 """ 91 result = [] 92 for _, mountpoint, _, _ in utils.GetMounts(): 93 if (mountpoint.startswith(path) and 94 mountpoint != path): 95 result.append(mountpoint) 96 97 result.sort(key=lambda x: x.count("/"), reverse=True) 98 return result
99 100 @classmethod
101 - def _InstanceDir(cls, instance_name):
102 """Return the root directory for an instance. 103 104 """ 105 return utils.PathJoin(cls._ROOT_DIR, instance_name)
106 107 @classmethod
108 - def _InstanceConfFile(cls, instance_name):
109 """Return the configuration file for an instance. 110 111 """ 112 return utils.PathJoin(cls._ROOT_DIR, instance_name + ".conf")
113 114 @classmethod
115 - def _InstanceLogFile(cls, instance_name):
116 """Return the log file for an instance. 117 118 """ 119 return utils.PathJoin(cls._ROOT_DIR, instance_name + ".log")
120 121 @classmethod
122 - def _GetCgroupMountPoint(cls):
123 for _, mountpoint, fstype, _ in utils.GetMounts(): 124 if fstype == "cgroup": 125 return mountpoint 126 raise errors.HypervisorError("The cgroup filesystem is not mounted")
127 128 @classmethod
129 - def _GetCgroupCpuList(cls, instance_name):
130 """Return the list of CPU ids for an instance. 131 132 """ 133 cgroup = cls._GetCgroupMountPoint() 134 try: 135 cpus = utils.ReadFile(utils.PathJoin(cgroup, 'lxc', 136 instance_name, 137 "cpuset.cpus")) 138 except EnvironmentError, err: 139 raise errors.HypervisorError("Getting CPU list for instance" 140 " %s failed: %s" % (instance_name, err)) 141 142 return utils.ParseCpuMask(cpus)
143 144 @classmethod
145 - def _GetCgroupMemoryLimit(cls, instance_name):
146 """Return the memory limit for an instance 147 148 """ 149 cgroup = cls._GetCgroupMountPoint() 150 try: 151 memory = int(utils.ReadFile(utils.PathJoin(cgroup, 'lxc', 152 instance_name, 153 "memory.limit_in_bytes"))) 154 except EnvironmentError: 155 # memory resource controller may be disabled, ignore 156 memory = 0 157 158 return memory
159
160 - def ListInstances(self):
161 """Get the list of running instances. 162 163 """ 164 return [iinfo[0] for iinfo in self.GetAllInstancesInfo()]
165
166 - def GetInstanceInfo(self, instance_name):
167 """Get instance properties. 168 169 @type instance_name: string 170 @param instance_name: the instance name 171 @rtype: tuple of strings 172 @return: (name, id, memory, vcpus, stat, times) 173 174 """ 175 # TODO: read container info from the cgroup mountpoint 176 177 result = utils.RunCmd(["lxc-info", "-s", "-n", instance_name]) 178 if result.failed: 179 raise errors.HypervisorError("Running lxc-info failed: %s" % 180 result.output) 181 # lxc-info output examples: 182 # 'state: STOPPED 183 # 'state: RUNNING 184 _, state = result.stdout.rsplit(None, 1) 185 if state != "RUNNING": 186 return None 187 188 cpu_list = self._GetCgroupCpuList(instance_name) 189 memory = self._GetCgroupMemoryLimit(instance_name) / (1024 ** 2) 190 return (instance_name, 0, memory, 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 os.listdir(self._ROOT_DIR): 200 try: 201 info = self.GetInstanceInfo(name) 202 except errors.HypervisorError: 203 continue 204 if info: 205 data.append(info) 206 return data
207
208 - def _CreateConfigFile(self, instance, root_dir):
209 """Create an lxc.conf file for an instance. 210 211 """ 212 out = [] 213 # hostname 214 out.append("lxc.utsname = %s" % instance.name) 215 216 # separate pseudo-TTY instances 217 out.append("lxc.pts = 255") 218 # standard TTYs 219 out.append("lxc.tty = 6") 220 # console log file 221 console_log = utils.PathJoin(self._ROOT_DIR, instance.name + ".console") 222 try: 223 utils.WriteFile(console_log, data="", mode=constants.SECURE_FILE_MODE) 224 except EnvironmentError, err: 225 raise errors.HypervisorError("Creating console log file %s for" 226 " instance %s failed: %s" % 227 (console_log, instance.name, err)) 228 out.append("lxc.console = %s" % console_log) 229 230 # root FS 231 out.append("lxc.rootfs = %s" % root_dir) 232 233 # TODO: additional mounts, if we disable CAP_SYS_ADMIN 234 235 # CPUs 236 if instance.hvparams[constants.HV_CPU_MASK]: 237 cpu_list = utils.ParseCpuMask(instance.hvparams[constants.HV_CPU_MASK]) 238 cpus_in_mask = len(cpu_list) 239 if cpus_in_mask != instance.beparams["vcpus"]: 240 raise errors.HypervisorError("Number of VCPUs (%d) doesn't match" 241 " the number of CPUs in the" 242 " cpu_mask (%d)" % 243 (instance.beparams["vcpus"], 244 cpus_in_mask)) 245 out.append("lxc.cgroup.cpuset.cpus = %s" % 246 instance.hvparams[constants.HV_CPU_MASK]) 247 248 # Memory 249 # Conditionally enable, memory resource controller might be disabled 250 cgroup = self._GetCgroupMountPoint() 251 if os.path.exists(utils.PathJoin(cgroup, 'memory.limit_in_bytes')): 252 out.append("lxc.cgroup.memory.limit_in_bytes = %dM" % 253 instance.beparams[constants.BE_MAXMEM]) 254 255 if os.path.exists(utils.PathJoin(cgroup, 'memory.memsw.limit_in_bytes')): 256 out.append("lxc.cgroup.memory.memsw.limit_in_bytes = %dM" % 257 instance.beparams[constants.BE_MAXMEM]) 258 259 # Device control 260 # deny direct device access 261 out.append("lxc.cgroup.devices.deny = a") 262 for devinfo in self._DEVS: 263 out.append("lxc.cgroup.devices.allow = %s rw" % devinfo) 264 265 # Networking 266 for idx, nic in enumerate(instance.nics): 267 out.append("# NIC %d" % idx) 268 mode = nic.nicparams[constants.NIC_MODE] 269 link = nic.nicparams[constants.NIC_LINK] 270 if mode == constants.NIC_MODE_BRIDGED: 271 out.append("lxc.network.type = veth") 272 out.append("lxc.network.link = %s" % link) 273 else: 274 raise errors.HypervisorError("LXC hypervisor only supports" 275 " bridged mode (NIC %d has mode %s)" % 276 (idx, mode)) 277 out.append("lxc.network.hwaddr = %s" % nic.mac) 278 out.append("lxc.network.flags = up") 279 280 # Capabilities 281 for cap in self._DENIED_CAPABILITIES: 282 out.append("lxc.cap.drop = %s" % cap) 283 284 return "\n".join(out) + "\n"
285
286 - def StartInstance(self, instance, block_devices, startup_paused):
287 """Start an instance. 288 289 For LXC, we try to mount the block device and execute 'lxc-start'. 290 We use volatile containers. 291 292 """ 293 root_dir = self._InstanceDir(instance.name) 294 try: 295 utils.EnsureDirs([(root_dir, self._DIR_MODE)]) 296 except errors.GenericError, err: 297 raise HypervisorError("Creating instance directory failed: %s", str(err)) 298 299 conf_file = self._InstanceConfFile(instance.name) 300 utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir)) 301 302 log_file = self._InstanceLogFile(instance.name) 303 if not os.path.exists(log_file): 304 try: 305 utils.WriteFile(log_file, data="", mode=constants.SECURE_FILE_MODE) 306 except EnvironmentError, err: 307 raise errors.HypervisorError("Creating hypervisor log file %s for" 308 " instance %s failed: %s" % 309 (log_file, instance.name, err)) 310 311 if not os.path.ismount(root_dir): 312 if not block_devices: 313 raise HypervisorError("LXC needs at least one disk") 314 315 sda_dev_path = block_devices[0][1] 316 result = utils.RunCmd(["mount", sda_dev_path, root_dir]) 317 if result.failed: 318 raise HypervisorError("Mounting the root dir of LXC instance %s" 319 " failed: %s" % (instance.name, result.output)) 320 result = utils.RunCmd(["lxc-start", "-n", instance.name, 321 "-o", log_file, 322 "-l", "DEBUG", 323 "-f", conf_file, "-d"]) 324 if result.failed: 325 raise HypervisorError("Running the lxc-start script failed: %s" % 326 result.output)
327
328 - def StopInstance(self, instance, force=False, retry=False, name=None):
329 """Stop an instance. 330 331 This method has complicated cleanup tests, as we must: 332 - try to kill all leftover processes 333 - try to unmount any additional sub-mountpoints 334 - finally unmount the instance dir 335 336 """ 337 if name is None: 338 name = instance.name 339 340 root_dir = self._InstanceDir(name) 341 if not os.path.exists(root_dir): 342 return 343 344 if name in self.ListInstances(): 345 # Signal init to shutdown; this is a hack 346 if not retry and not force: 347 result = utils.RunCmd(["chroot", root_dir, "poweroff"]) 348 if result.failed: 349 raise HypervisorError("Running 'poweroff' on the instance" 350 " failed: %s" % result.output) 351 time.sleep(2) 352 result = utils.RunCmd(["lxc-stop", "-n", name]) 353 if result.failed: 354 logging.warning("Error while doing lxc-stop for %s: %s", name, 355 result.output) 356 357 if not os.path.ismount(root_dir): 358 return 359 360 for mpath in self._GetMountSubdirs(root_dir): 361 result = utils.RunCmd(["umount", mpath]) 362 if result.failed: 363 logging.warning("Error while umounting subpath %s for instance %s: %s", 364 mpath, name, result.output) 365 366 result = utils.RunCmd(["umount", root_dir]) 367 if result.failed and force: 368 msg = ("Processes still alive in the chroot: %s" % 369 utils.RunCmd("fuser -vm %s" % root_dir).output) 370 logging.error(msg) 371 raise HypervisorError("Unmounting the chroot dir failed: %s (%s)" % 372 (result.output, msg))
373
374 - def RebootInstance(self, instance):
375 """Reboot an instance. 376 377 This is not (yet) implemented (in Ganeti) for the LXC hypervisor. 378 379 """ 380 # TODO: implement reboot 381 raise HypervisorError("The LXC hypervisor doesn't implement the" 382 " reboot functionality")
383
384 - def BalloonInstanceMemory(self, instance, mem):
385 """Balloon an instance memory to a certain value. 386 387 @type instance: L{objects.Instance} 388 @param instance: instance to be accepted 389 @type mem: int 390 @param mem: actual memory size to use for instance runtime 391 392 """ 393 # Currently lxc instances don't have memory limits 394 pass
395
396 - def GetNodeInfo(self):
397 """Return information about the node. 398 399 This is just a wrapper over the base GetLinuxNodeInfo method. 400 401 @return: a dict with the following keys (values in MiB): 402 - memory_total: the total memory size on the node 403 - memory_free: the available memory on the node for instances 404 - memory_dom0: the memory used by the node itself, if available 405 406 """ 407 return self.GetLinuxNodeInfo()
408 409 @classmethod
410 - def GetInstanceConsole(cls, instance, hvparams, beparams):
411 """Return a command for connecting to the console of an instance. 412 413 """ 414 return objects.InstanceConsole(instance=instance.name, 415 kind=constants.CONS_SSH, 416 host=instance.primary_node, 417 user=constants.SSH_CONSOLE_USER, 418 command=["lxc-console", "-n", instance.name])
419
420 - def Verify(self):
421 """Verify the hypervisor. 422 423 For the LXC manager, it just checks the existence of the base dir. 424 425 @return: Problem description if something is wrong, C{None} otherwise 426 427 """ 428 msgs = [] 429 430 if not os.path.exists(self._ROOT_DIR): 431 msgs.append("The required directory '%s' does not exist" % 432 self._ROOT_DIR) 433 434 try: 435 self._GetCgroupMountPoint() 436 except errors.HypervisorError, err: 437 msgs.append(str(err)) 438 439 return self._FormatVerifyResults(msgs)
440 441 @classmethod
442 - def PowercycleNode(cls):
443 """LXC powercycle, just a wrapper over Linux powercycle. 444 445 """ 446 cls.LinuxPowercycle()
447
448 - def MigrateInstance(self, instance, target, live):
449 """Migrate an instance. 450 451 @type instance: L{objects.Instance} 452 @param instance: the instance to be migrated 453 @type target: string 454 @param target: hostname (usually ip) of the target node 455 @type live: boolean 456 @param live: whether to do a live or non-live migration 457 458 """ 459 raise HypervisorError("Migration is not supported by the LXC hypervisor")
460
461 - def GetMigrationStatus(self, instance):
462 """Get the migration status 463 464 @type instance: L{objects.Instance} 465 @param instance: the instance that is being migrated 466 @rtype: L{objects.MigrationStatus} 467 @return: the status of the current migration (one of 468 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional 469 progress info that can be retrieved from the hypervisor 470 471 """ 472 raise HypervisorError("Migration is not supported by the LXC hypervisor")
473