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), 202 hv_base.HvInstanceState.RUNNING, 0)
203
204 - def GetAllInstancesInfo(self, hvparams=None):
205 """Get properties of all instances. 206 207 @type hvparams: dict of strings 208 @param hvparams: hypervisor parameter 209 @return: [(name, id, memory, vcpus, stat, times),...] 210 211 """ 212 data = [] 213 for name in os.listdir(self._ROOT_DIR): 214 try: 215 info = self.GetInstanceInfo(name) 216 except errors.HypervisorError: 217 continue 218 if info: 219 data.append(info) 220 return data
221
222 - def _CreateConfigFile(self, instance, root_dir):
223 """Create an lxc.conf file for an instance. 224 225 """ 226 out = [] 227 # hostname 228 out.append("lxc.utsname = %s" % instance.name) 229 230 # separate pseudo-TTY instances 231 out.append("lxc.pts = 255") 232 # standard TTYs 233 out.append("lxc.tty = 6") 234 # console log file 235 console_log = utils.PathJoin(self._ROOT_DIR, instance.name + ".console") 236 try: 237 utils.WriteFile(console_log, data="", mode=constants.SECURE_FILE_MODE) 238 except EnvironmentError, err: 239 raise errors.HypervisorError("Creating console log file %s for" 240 " instance %s failed: %s" % 241 (console_log, instance.name, err)) 242 out.append("lxc.console = %s" % console_log) 243 244 # root FS 245 out.append("lxc.rootfs = %s" % root_dir) 246 247 # TODO: additional mounts, if we disable CAP_SYS_ADMIN 248 249 # CPUs 250 if instance.hvparams[constants.HV_CPU_MASK]: 251 cpu_list = utils.ParseCpuMask(instance.hvparams[constants.HV_CPU_MASK]) 252 cpus_in_mask = len(cpu_list) 253 if cpus_in_mask != instance.beparams["vcpus"]: 254 raise errors.HypervisorError("Number of VCPUs (%d) doesn't match" 255 " the number of CPUs in the" 256 " cpu_mask (%d)" % 257 (instance.beparams["vcpus"], 258 cpus_in_mask)) 259 out.append("lxc.cgroup.cpuset.cpus = %s" % 260 instance.hvparams[constants.HV_CPU_MASK]) 261 262 # Memory 263 # Conditionally enable, memory resource controller might be disabled 264 cgroup = self._GetCgroupMountPoint() 265 if os.path.exists(utils.PathJoin(cgroup, 'memory.limit_in_bytes')): 266 out.append("lxc.cgroup.memory.limit_in_bytes = %dM" % 267 instance.beparams[constants.BE_MAXMEM]) 268 269 if os.path.exists(utils.PathJoin(cgroup, 'memory.memsw.limit_in_bytes')): 270 out.append("lxc.cgroup.memory.memsw.limit_in_bytes = %dM" % 271 instance.beparams[constants.BE_MAXMEM]) 272 273 # Device control 274 # deny direct device access 275 out.append("lxc.cgroup.devices.deny = a") 276 for devinfo in self._DEVS: 277 out.append("lxc.cgroup.devices.allow = %s rw" % devinfo) 278 279 # Networking 280 for idx, nic in enumerate(instance.nics): 281 out.append("# NIC %d" % idx) 282 mode = nic.nicparams[constants.NIC_MODE] 283 link = nic.nicparams[constants.NIC_LINK] 284 if mode == constants.NIC_MODE_BRIDGED: 285 out.append("lxc.network.type = veth") 286 out.append("lxc.network.link = %s" % link) 287 else: 288 raise errors.HypervisorError("LXC hypervisor only supports" 289 " bridged mode (NIC %d has mode %s)" % 290 (idx, mode)) 291 out.append("lxc.network.hwaddr = %s" % nic.mac) 292 out.append("lxc.network.flags = up") 293 294 # Capabilities 295 for cap in self._DENIED_CAPABILITIES: 296 out.append("lxc.cap.drop = %s" % cap) 297 298 return "\n".join(out) + "\n"
299
300 - def StartInstance(self, instance, block_devices, startup_paused):
301 """Start an instance. 302 303 For LXC, we try to mount the block device and execute 'lxc-start'. 304 We use volatile containers. 305 306 """ 307 root_dir = self._InstanceDir(instance.name) 308 try: 309 utils.EnsureDirs([(root_dir, self._DIR_MODE)]) 310 except errors.GenericError, err: 311 raise HypervisorError("Creating instance directory failed: %s", str(err)) 312 313 conf_file = self._InstanceConfFile(instance.name) 314 utils.WriteFile(conf_file, data=self._CreateConfigFile(instance, root_dir)) 315 316 log_file = self._InstanceLogFile(instance.name) 317 if not os.path.exists(log_file): 318 try: 319 utils.WriteFile(log_file, data="", mode=constants.SECURE_FILE_MODE) 320 except EnvironmentError, err: 321 raise errors.HypervisorError("Creating hypervisor log file %s for" 322 " instance %s failed: %s" % 323 (log_file, instance.name, err)) 324 325 if not os.path.ismount(root_dir): 326 if not block_devices: 327 raise HypervisorError("LXC needs at least one disk") 328 329 sda_dev_path = block_devices[0][1] 330 result = utils.RunCmd(["mount", sda_dev_path, root_dir]) 331 if result.failed: 332 raise HypervisorError("Mounting the root dir of LXC instance %s" 333 " failed: %s" % (instance.name, result.output)) 334 result = utils.RunCmd(["lxc-start", "-n", instance.name, 335 "-o", log_file, 336 "-l", "DEBUG", 337 "-f", conf_file, "-d"]) 338 if result.failed: 339 raise HypervisorError("Running the lxc-start script failed: %s" % 340 result.output)
341
342 - def StopInstance(self, instance, force=False, retry=False, name=None, 343 timeout=None):
344 """Stop an instance. 345 346 This method has complicated cleanup tests, as we must: 347 - try to kill all leftover processes 348 - try to unmount any additional sub-mountpoints 349 - finally unmount the instance dir 350 351 """ 352 assert(timeout is None or force is not None) 353 354 if name is None: 355 name = instance.name 356 357 timeout_cmd = [] 358 if timeout is not None: 359 timeout_cmd.extend(["timeout", str(timeout)]) 360 361 root_dir = self._InstanceDir(name) 362 if not os.path.exists(root_dir): 363 return 364 365 if name in self.ListInstances(): 366 # Signal init to shutdown; this is a hack 367 if not retry and not force: 368 result = utils.RunCmd(["chroot", root_dir, "poweroff"]) 369 if result.failed: 370 raise HypervisorError("Running 'poweroff' on the instance" 371 " failed: %s" % result.output) 372 time.sleep(2) 373 result = utils.RunCmd(timeout_cmd.extend(["lxc-stop", "-n", name])) 374 if result.failed: 375 logging.warning("Error while doing lxc-stop for %s: %s", name, 376 result.output) 377 378 if not os.path.ismount(root_dir): 379 return 380 381 for mpath in self._GetMountSubdirs(root_dir): 382 result = utils.RunCmd(timeout_cmd.extend(["umount", mpath])) 383 if result.failed: 384 logging.warning("Error while umounting subpath %s for instance %s: %s", 385 mpath, name, result.output) 386 387 result = utils.RunCmd(timeout_cmd.extend(["umount", root_dir])) 388 if result.failed and force: 389 msg = ("Processes still alive in the chroot: %s" % 390 utils.RunCmd("fuser -vm %s" % root_dir).output) 391 logging.error(msg) 392 raise HypervisorError("Unmounting the chroot dir failed: %s (%s)" % 393 (result.output, msg))
394
395 - def RebootInstance(self, instance):
396 """Reboot an instance. 397 398 This is not (yet) implemented (in Ganeti) for the LXC hypervisor. 399 400 """ 401 # TODO: implement reboot 402 raise HypervisorError("The LXC hypervisor doesn't implement the" 403 " reboot functionality")
404
405 - def BalloonInstanceMemory(self, instance, mem):
406 """Balloon an instance memory to a certain value. 407 408 @type instance: L{objects.Instance} 409 @param instance: instance to be accepted 410 @type mem: int 411 @param mem: actual memory size to use for instance runtime 412 413 """ 414 # Currently lxc instances don't have memory limits 415 pass
416
417 - def GetNodeInfo(self, hvparams=None):
418 """Return information about the node. 419 420 See L{BaseHypervisor.GetLinuxNodeInfo}. 421 422 """ 423 return self.GetLinuxNodeInfo()
424 425 @classmethod
426 - def GetInstanceConsole(cls, instance, primary_node, node_group, 427 hvparams, beparams):
428 """Return a command for connecting to the console of an instance. 429 430 """ 431 ndparams = node_group.FillND(primary_node) 432 return objects.InstanceConsole(instance=instance.name, 433 kind=constants.CONS_SSH, 434 host=primary_node.name, 435 port=ndparams.get(constants.ND_SSH_PORT), 436 user=constants.SSH_CONSOLE_USER, 437 command=["lxc-console", "-n", instance.name])
438
439 - def Verify(self, hvparams=None):
440 """Verify the hypervisor. 441 442 For the LXC manager, it just checks the existence of the base dir. 443 444 @type hvparams: dict of strings 445 @param hvparams: hypervisor parameters to be verified against; not used here 446 447 @return: Problem description if something is wrong, C{None} otherwise 448 449 """ 450 msgs = [] 451 452 if not os.path.exists(self._ROOT_DIR): 453 msgs.append("The required directory '%s' does not exist" % 454 self._ROOT_DIR) 455 456 try: 457 self._GetCgroupMountPoint() 458 except errors.HypervisorError, err: 459 msgs.append(str(err)) 460 461 return self._FormatVerifyResults(msgs)
462 463 @classmethod
464 - def PowercycleNode(cls, hvparams=None):
465 """LXC powercycle, just a wrapper over Linux powercycle. 466 467 @type hvparams: dict of strings 468 @param hvparams: hypervisor params to be used on this node 469 470 """ 471 cls.LinuxPowercycle()
472
473 - def MigrateInstance(self, cluster_name, instance, target, live):
474 """Migrate an instance. 475 476 @type cluster_name: string 477 @param cluster_name: name of the cluster 478 @type instance: L{objects.Instance} 479 @param instance: the instance to be migrated 480 @type target: string 481 @param target: hostname (usually ip) of the target node 482 @type live: boolean 483 @param live: whether to do a live or non-live migration 484 485 """ 486 raise HypervisorError("Migration is not supported by the LXC hypervisor")
487
488 - def GetMigrationStatus(self, instance):
489 """Get the migration status 490 491 @type instance: L{objects.Instance} 492 @param instance: the instance that is being migrated 493 @rtype: L{objects.MigrationStatus} 494 @return: the status of the current migration (one of 495 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional 496 progress info that can be retrieved from the hypervisor 497 498 """ 499 raise HypervisorError("Migration is not supported by the LXC hypervisor")
500