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 timeout=None):
330 """Stop an instance. 331 332 This method has complicated cleanup tests, as we must: 333 - try to kill all leftover processes 334 - try to unmount any additional sub-mountpoints 335 - finally unmount the instance dir 336 337 """ 338 assert(timeout is None or force is not None) 339 340 if name is None: 341 name = instance.name 342 343 timeout_cmd = [] 344 if timeout is not None: 345 timeout_cmd.extend(["timeout", str(timeout)]) 346 347 root_dir = self._InstanceDir(name) 348 if not os.path.exists(root_dir): 349 return 350 351 if name in self.ListInstances(): 352 # Signal init to shutdown; this is a hack 353 if not retry and not force: 354 result = utils.RunCmd(["chroot", root_dir, "poweroff"]) 355 if result.failed: 356 raise HypervisorError("Running 'poweroff' on the instance" 357 " failed: %s" % result.output) 358 time.sleep(2) 359 result = utils.RunCmd(timeout_cmd.extend(["lxc-stop", "-n", name])) 360 if result.failed: 361 logging.warning("Error while doing lxc-stop for %s: %s", name, 362 result.output) 363 364 if not os.path.ismount(root_dir): 365 return 366 367 for mpath in self._GetMountSubdirs(root_dir): 368 result = utils.RunCmd(timeout_cmd.extend(["umount", mpath])) 369 if result.failed: 370 logging.warning("Error while umounting subpath %s for instance %s: %s", 371 mpath, name, result.output) 372 373 result = utils.RunCmd(timeout_cmd.extend(["umount", root_dir])) 374 if result.failed and force: 375 msg = ("Processes still alive in the chroot: %s" % 376 utils.RunCmd("fuser -vm %s" % root_dir).output) 377 logging.error(msg) 378 raise HypervisorError("Unmounting the chroot dir failed: %s (%s)" % 379 (result.output, msg))
380
381 - def RebootInstance(self, instance):
382 """Reboot an instance. 383 384 This is not (yet) implemented (in Ganeti) for the LXC hypervisor. 385 386 """ 387 # TODO: implement reboot 388 raise HypervisorError("The LXC hypervisor doesn't implement the" 389 " reboot functionality")
390
391 - def BalloonInstanceMemory(self, instance, mem):
392 """Balloon an instance memory to a certain value. 393 394 @type instance: L{objects.Instance} 395 @param instance: instance to be accepted 396 @type mem: int 397 @param mem: actual memory size to use for instance runtime 398 399 """ 400 # Currently lxc instances don't have memory limits 401 pass
402
403 - def GetNodeInfo(self):
404 """Return information about the node. 405 406 This is just a wrapper over the base GetLinuxNodeInfo method. 407 408 @return: a dict with the following keys (values in MiB): 409 - memory_total: the total memory size on the node 410 - memory_free: the available memory on the node for instances 411 - memory_dom0: the memory used by the node itself, if available 412 413 """ 414 return self.GetLinuxNodeInfo()
415 416 @classmethod
417 - def GetInstanceConsole(cls, instance, hvparams, beparams):
418 """Return a command for connecting to the console of an instance. 419 420 """ 421 return objects.InstanceConsole(instance=instance.name, 422 kind=constants.CONS_SSH, 423 host=instance.primary_node, 424 user=constants.SSH_CONSOLE_USER, 425 command=["lxc-console", "-n", instance.name])
426
427 - def Verify(self):
428 """Verify the hypervisor. 429 430 For the LXC manager, it just checks the existence of the base dir. 431 432 @return: Problem description if something is wrong, C{None} otherwise 433 434 """ 435 msgs = [] 436 437 if not os.path.exists(self._ROOT_DIR): 438 msgs.append("The required directory '%s' does not exist" % 439 self._ROOT_DIR) 440 441 try: 442 self._GetCgroupMountPoint() 443 except errors.HypervisorError, err: 444 msgs.append(str(err)) 445 446 return self._FormatVerifyResults(msgs)
447 448 @classmethod
449 - def PowercycleNode(cls):
450 """LXC powercycle, just a wrapper over Linux powercycle. 451 452 """ 453 cls.LinuxPowercycle()
454
455 - def MigrateInstance(self, instance, target, live):
456 """Migrate an instance. 457 458 @type instance: L{objects.Instance} 459 @param instance: the instance to be migrated 460 @type target: string 461 @param target: hostname (usually ip) of the target node 462 @type live: boolean 463 @param live: whether to do a live or non-live migration 464 465 """ 466 raise HypervisorError("Migration is not supported by the LXC hypervisor")
467
468 - def GetMigrationStatus(self, instance):
469 """Get the migration status 470 471 @type instance: L{objects.Instance} 472 @param instance: the instance that is being migrated 473 @rtype: L{objects.MigrationStatus} 474 @return: the status of the current migration (one of 475 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional 476 progress info that can be retrieved from the hypervisor 477 478 """ 479 raise HypervisorError("Migration is not supported by the LXC hypervisor")
480