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