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