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, 2014, 2015 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 errno 
  36  import os 
  37  import os.path 
  38  import logging 
  39  import sys 
  40  import re 
  41   
  42  from ganeti import constants 
  43  from ganeti import errors # pylint: disable=W0611 
  44  from ganeti import utils 
  45  from ganeti import objects 
  46  from ganeti import pathutils 
  47  from ganeti import serializer 
  48  from ganeti.hypervisor import hv_base 
  49  from ganeti.errors import HypervisorError 
50 51 52 -def _CreateBlankFile(path, mode):
53 """Create blank file. 54 55 Create a blank file for the path with specified mode. 56 An existing file will be overwritten. 57 58 """ 59 try: 60 utils.WriteFile(path, data="", mode=mode) 61 except EnvironmentError, err: 62 raise HypervisorError("Failed to create file %s: %s" % (path, err))
63
64 65 -class LXCVersion(tuple): # pylint: disable=R0924
66 """LXC version class. 67 68 """ 69 # Let beta version following micro version, but don't care about it 70 _VERSION_RE = re.compile(r"^(\d+)\.(\d+)\.(\d+)") 71 72 @classmethod
73 - def _Parse(cls, version_string):
74 """Parse a passed string as an LXC version string. 75 76 @param version_string: a valid LXC version string 77 @type version_string: string 78 @raise ValueError: if version_string is an invalid LXC version string 79 @rtype tuple(int, int, int) 80 @return (major_num, minor_num, micro_num) 81 82 """ 83 match = cls._VERSION_RE.match(version_string) 84 if match: 85 return tuple(map(int, match.groups())) 86 else: 87 raise ValueError("'%s' is not a valid LXC version string" % 88 version_string)
89
90 - def __new__(cls, version_string):
91 version = super(LXCVersion, cls).__new__(cls, cls._Parse(version_string)) 92 version.original_string = version_string 93 return version
94
95 - def __str__(self):
96 return self.original_string
97
98 99 -class LXCHypervisor(hv_base.BaseHypervisor):
100 """LXC-based virtualization. 101 102 """ 103 _ROOT_DIR = pathutils.RUN_DIR + "/lxc" 104 _LOG_DIR = pathutils.LOG_DIR + "/lxc" 105 106 # The instance directory has to be structured in a way that would allow it to 107 # be passed as an argument of the --lxcpath option in lxc- commands. 108 # This means that: 109 # Each LXC instance should have a directory carrying their name under this 110 # directory. 111 # Each instance directory should contain the "config" file that contains the 112 # LXC container configuration of an instance. 113 # 114 # Therefore the structure of the directory tree should be: 115 # 116 # _INSTANCE_DIR 117 # \_ instance1 118 # \_ config 119 # \_ instance2 120 # \_ config 121 # 122 # Other instance specific files can also be placed under an instance 123 # directory. 124 _INSTANCE_DIR = _ROOT_DIR + "/instance" 125 126 _CGROUP_ROOT_DIR = _ROOT_DIR + "/cgroup" 127 _PROC_CGROUPS_FILE = "/proc/cgroups" 128 _PROC_SELF_CGROUP_FILE = "/proc/self/cgroup" 129 130 _LXC_MIN_VERSION_REQUIRED = LXCVersion("1.0.0") 131 _LXC_COMMANDS_REQUIRED = [ 132 "lxc-console", 133 "lxc-ls", 134 "lxc-start", 135 "lxc-stop", 136 "lxc-wait", 137 ] 138 139 _DIR_MODE = 0755 140 _STASH_KEY_ALLOCATED_LOOP_DEV = "allocated_loopdev" 141 142 _MEMORY_PARAMETER = "memory.limit_in_bytes" 143 _MEMORY_SWAP_PARAMETER = "memory.memsw.limit_in_bytes" 144 145 PARAMETERS = { 146 constants.HV_CPU_MASK: hv_base.OPT_CPU_MASK_CHECK, 147 constants.HV_LXC_DEVICES: hv_base.NO_CHECK, 148 constants.HV_LXC_DROP_CAPABILITIES: hv_base.NO_CHECK, 149 constants.HV_LXC_EXTRA_CGROUPS: hv_base.NO_CHECK, 150 constants.HV_LXC_EXTRA_CONFIG: hv_base.NO_CHECK, 151 constants.HV_LXC_NUM_TTYS: hv_base.REQ_NONNEGATIVE_INT_CHECK, 152 constants.HV_LXC_STARTUP_TIMEOUT: hv_base.OPT_NONNEGATIVE_INT_CHECK, 153 } 154 155 _REBOOT_TIMEOUT = 120 # secs 156 _REQUIRED_CGROUP_SUBSYSTEMS = [ 157 "cpuset", 158 "memory", 159 "devices", 160 "cpuacct", 161 ] 162
163 - def __init__(self):
166 167 @classmethod
168 - def _InstanceDir(cls, instance_name):
169 """Return the root directory for an instance. 170 171 """ 172 return utils.PathJoin(cls._INSTANCE_DIR, instance_name)
173 174 @classmethod
175 - def _InstanceConfFilePath(cls, instance_name):
176 """Return the configuration file for an instance. 177 178 """ 179 return utils.PathJoin(cls._InstanceDir(instance_name), "config")
180 181 @classmethod
182 - def _InstanceLogFilePath(cls, instance):
183 """Return the log file for an instance. 184 185 @type instance: L{objects.Instance} 186 187 """ 188 filename = "%s.%s.log" % (instance.name, instance.uuid) 189 return utils.PathJoin(cls._LOG_DIR, filename)
190 191 @classmethod
192 - def _InstanceConsoleLogFilePath(cls, instance_name):
193 """Return the console log file path for an instance. 194 195 """ 196 return utils.PathJoin(cls._InstanceDir(instance_name), "console.log")
197 198 @classmethod
199 - def _InstanceStashFilePath(cls, instance_name):
200 """Return the stash file path for an instance. 201 202 The stash file is used to keep information needed to clean up after the 203 destruction of the instance. 204 205 """ 206 return utils.PathJoin(cls._InstanceDir(instance_name), "stash")
207
208 - def _EnsureDirectoryExistence(self):
209 """Ensures all the directories needed for LXC use exist. 210 211 """ 212 utils.EnsureDirs([ 213 (self._ROOT_DIR, self._DIR_MODE), 214 (self._LOG_DIR, 0750), 215 (self._INSTANCE_DIR, 0750), 216 ])
217
218 - def _SaveInstanceStash(self, instance_name, data):
219 """Save data to the instance stash file in serialized format. 220 221 """ 222 stash_file = self._InstanceStashFilePath(instance_name) 223 serialized = serializer.Dump(data) 224 try: 225 utils.WriteFile(stash_file, data=serialized, 226 mode=constants.SECURE_FILE_MODE) 227 except EnvironmentError, err: 228 raise HypervisorError("Failed to save instance stash file %s : %s" % 229 (stash_file, err))
230
231 - def _LoadInstanceStash(self, instance_name):
232 """Load information stashed in file which was created by 233 L{_SaveInstanceStash}. 234 235 """ 236 stash_file = self._InstanceStashFilePath(instance_name) 237 try: 238 return serializer.Load(utils.ReadFile(stash_file)) 239 except (EnvironmentError, ValueError), err: 240 raise HypervisorError("Failed to load instance stash file %s : %s" % 241 (stash_file, err))
242 243 @classmethod
244 - def _MountCgroupSubsystem(cls, subsystem):
245 """Mount the cgroup subsystem fs under the cgroup root dir. 246 247 @type subsystem: string 248 @param subsystem: cgroup subsystem name to mount 249 @rtype string 250 @return path of subsystem mount point 251 252 """ 253 subsys_dir = utils.PathJoin(cls._GetCgroupMountPoint(), subsystem) 254 if not os.path.isdir(subsys_dir): 255 try: 256 os.makedirs(subsys_dir) 257 except EnvironmentError, err: 258 raise HypervisorError("Failed to create directory %s: %s" % 259 (subsys_dir, err)) 260 261 mount_cmd = ["mount", "-t", "cgroup", "-o", subsystem, subsystem, 262 subsys_dir] 263 result = utils.RunCmd(mount_cmd) 264 if result.failed: 265 raise HypervisorError("Failed to mount cgroup subsystem '%s': %s" % 266 (subsystem, result.output)) 267 268 return subsys_dir
269
270 - def _CleanupInstance(self, instance_name, stash):
271 """Actual implementation of the instance cleanup procedure. 272 273 @type instance_name: string 274 @param instance_name: instance name 275 @type stash: dict(string:any) 276 @param stash: dict that contains desired information for instance cleanup 277 278 """ 279 try: 280 if self._STASH_KEY_ALLOCATED_LOOP_DEV in stash: 281 loop_dev_path = stash[self._STASH_KEY_ALLOCATED_LOOP_DEV] 282 utils.ReleaseBdevPartitionMapping(loop_dev_path) 283 except errors.CommandError, err: 284 raise HypervisorError("Failed to cleanup partition mapping : %s" % err) 285 286 utils.RemoveFile(self._InstanceStashFilePath(instance_name))
287
288 - def CleanupInstance(self, instance_name):
289 """Cleanup after a stopped instance. 290 291 """ 292 stash = self._LoadInstanceStash(instance_name) 293 self._CleanupInstance(instance_name, stash)
294 295 @classmethod
296 - def _GetCgroupMountPoint(cls):
297 """Return the directory that should be the base of cgroup fs. 298 299 """ 300 return cls._CGROUP_ROOT_DIR
301 302 @classmethod
303 - def _GetOrPrepareCgroupSubsysMountPoint(cls, subsystem):
304 """Prepare cgroup subsystem mount point. 305 306 @type subsystem: string 307 @param subsystem: cgroup subsystem name to mount 308 @rtype string 309 @return path of subsystem mount point 310 311 """ 312 for _, mpoint, fstype, options in utils.GetMounts(): 313 if fstype == "cgroup" and subsystem in options.split(","): 314 return mpoint 315 316 return cls._MountCgroupSubsystem(subsystem)
317 318 @classmethod
320 """Return the dict of cgroup subsystem hierarchies this process belongs to. 321 322 The dictionary has the cgroup subsystem as a key and its hierarchy as a 323 value. 324 Information is read from /proc/self/cgroup. 325 326 """ 327 try: 328 cgroup_list = utils.ReadFile(cls._PROC_SELF_CGROUP_FILE) 329 except EnvironmentError, err: 330 raise HypervisorError("Failed to read %s : %s" % 331 (cls._PROC_SELF_CGROUP_FILE, err)) 332 333 cgroups = {} 334 for line in filter(None, cgroup_list.split("\n")): 335 _, subsystems, hierarchy = line.split(":") 336 for subsys in subsystems.split(","): 337 cgroups[subsys] = hierarchy[1:] # discard first '/' 338 339 return cgroups
340 341 @classmethod
342 - def _GetCgroupSubsysDir(cls, subsystem):
343 """Return the directory of the cgroup subsystem we use. 344 345 @type subsystem: string 346 @param subsystem: cgroup subsystem name 347 @rtype: string 348 @return: path of the hierarchy directory for the subsystem 349 350 """ 351 subsys_dir = cls._GetOrPrepareCgroupSubsysMountPoint(subsystem) 352 base_group = cls._GetCurrentCgroupSubsysGroups().get(subsystem, "") 353 354 return utils.PathJoin(subsys_dir, base_group, "lxc")
355 356 @classmethod
357 - def _GetCgroupParamPath(cls, param_name, instance_name=None):
358 """Return the path of the specified cgroup parameter file. 359 360 @type param_name: string 361 @param param_name: cgroup subsystem parameter name 362 @rtype: string 363 @return: path of the cgroup subsystem parameter file 364 365 """ 366 subsystem = param_name.split(".", 1)[0] 367 subsys_dir = cls._GetCgroupSubsysDir(subsystem) 368 if instance_name is not None: 369 return utils.PathJoin(subsys_dir, instance_name, param_name) 370 else: 371 return utils.PathJoin(subsys_dir, param_name)
372 373 @classmethod
374 - def _GetCgroupInstanceValue(cls, instance_name, param_name):
375 """Return the value of the specified cgroup parameter. 376 377 @type instance_name: string 378 @param instance_name: instance name 379 @type param_name: string 380 @param param_name: cgroup subsystem parameter name 381 @rtype string 382 @return value read from cgroup subsystem fs 383 384 """ 385 param_path = cls._GetCgroupParamPath(param_name, 386 instance_name=instance_name) 387 return utils.ReadFile(param_path).rstrip("\n")
388 389 @classmethod
390 - def _SetCgroupInstanceValue(cls, instance_name, param_name, param_value):
391 """Set the value to the specified instance cgroup parameter. 392 393 @type instance_name: string 394 @param instance_name: instance name 395 @type param_name: string 396 @param param_name: cgroup subsystem parameter name 397 @type param_value: string 398 @param param_value: cgroup subsystem parameter value to be set 399 400 """ 401 param_path = cls._GetCgroupParamPath(param_name, 402 instance_name=instance_name) 403 # When interacting with cgroup fs, errno is quite important information 404 # to see what happened when setting a cgroup parameter, so just throw 405 # an error to the upper level. 406 # e.g., we could know that the container can't reclaim its memory by 407 # checking if the errno is EBUSY when setting the 408 # memory.memsw.limit_in_bytes. 409 fd = -1 410 try: 411 fd = os.open(param_path, os.O_WRONLY) 412 os.write(fd, param_value) 413 finally: 414 if fd != -1: 415 os.close(fd)
416 417 @classmethod
418 - def _IsCgroupParameterPresent(cls, parameter, hvparams=None):
419 """Return whether a cgroup parameter can be used. 420 421 This is checked by seeing whether there is a file representation of the 422 parameter in the location where the cgroup is mounted. 423 424 @type parameter: string 425 @param parameter: The name of the parameter. 426 @param hvparams: dict 427 @param hvparams: The hypervisor parameters, optional. 428 @rtype: boolean 429 430 """ 431 cls._EnsureCgroupMounts(hvparams) 432 param_path = cls._GetCgroupParamPath(parameter) 433 434 return os.path.exists(param_path)
435 436 @classmethod
437 - def _GetCgroupCpuList(cls, instance_name):
438 """Return the list of CPU ids for an instance. 439 440 """ 441 try: 442 cpumask = cls._GetCgroupInstanceValue(instance_name, "cpuset.cpus") 443 except EnvironmentError, err: 444 raise errors.HypervisorError("Getting CPU list for instance" 445 " %s failed: %s" % (instance_name, err)) 446 447 return utils.ParseCpuMask(cpumask)
448 449 @classmethod
450 - def _GetCgroupCpuUsage(cls, instance_name):
451 """Return the CPU usage of an instance. 452 453 """ 454 try: 455 cputime_ns = cls._GetCgroupInstanceValue(instance_name, "cpuacct.usage") 456 except EnvironmentError, err: 457 raise HypervisorError("Failed to get the cpu usage of %s: %s" % 458 (instance_name, err)) 459 460 return float(cputime_ns) / 10 ** 9 # nano secs to float secs
461 462 @classmethod
463 - def _GetCgroupMemoryLimit(cls, instance_name):
464 """Return the memory limit for an instance 465 466 """ 467 try: 468 mem_limit = cls._GetCgroupInstanceValue(instance_name, 469 "memory.limit_in_bytes") 470 return int(mem_limit) 471 except EnvironmentError, err: 472 raise HypervisorError("Can't get instance memory limit of %s: %s" % 473 (instance_name, err))
474
475 - def ListInstances(self, hvparams=None):
476 """Get the list of running instances. 477 478 """ 479 return [iinfo[0] for iinfo in self.GetAllInstancesInfo()]
480 481 @classmethod
482 - def _IsInstanceAlive(cls, instance_name):
483 """Return True if instance is alive. 484 485 """ 486 result = utils.RunCmd(["lxc-ls", "--running"]) 487 if result.failed: 488 raise HypervisorError("Failed to get running LXC containers list: %s" % 489 result.output) 490 491 return instance_name in result.stdout.split()
492
493 - def GetInstanceInfo(self, instance_name, hvparams=None):
494 """Get instance properties. 495 496 @type instance_name: string 497 @param instance_name: the instance name 498 @type hvparams: dict of strings 499 @param hvparams: hvparams to be used with this instance 500 @rtype: tuple of strings 501 @return: (name, id, memory, vcpus, stat, times) 502 503 """ 504 if not self._IsInstanceAlive(instance_name): 505 return None 506 507 cpu_list = self._GetCgroupCpuList(instance_name) 508 memory = self._GetCgroupMemoryLimit(instance_name) / (1024 ** 2) 509 cputime = self._GetCgroupCpuUsage(instance_name) 510 return (instance_name, 0, memory, len(cpu_list), 511 hv_base.HvInstanceState.RUNNING, cputime)
512
513 - def GetAllInstancesInfo(self, hvparams=None):
514 """Get properties of all instances. 515 516 @type hvparams: dict of strings 517 @param hvparams: hypervisor parameter 518 @return: [(name, id, memory, vcpus, stat, times),...] 519 520 """ 521 data = [] 522 filter_fn = lambda x: os.path.isdir(utils.PathJoin(self._INSTANCE_DIR, x)) 523 for dirname in filter(filter_fn, os.listdir(self._INSTANCE_DIR)): 524 try: 525 info = self.GetInstanceInfo(dirname) 526 except errors.HypervisorError: 527 continue 528 if info: 529 data.append(info) 530 return data
531 532 @classmethod
533 - def _GetInstanceDropCapabilities(cls, hvparams):
534 """Get and parse the drop capabilities list from the instance hvparams. 535 536 @type hvparams: dict of strings 537 @param hvparams: instance hvparams 538 @rtype list(string) 539 @return list of drop capabilities 540 541 """ 542 drop_caps = hvparams[constants.HV_LXC_DROP_CAPABILITIES] 543 return drop_caps.split(",")
544
545 - def _CreateConfigFile(self, instance, sda_dev_path):
546 """Create an lxc.conf file for an instance. 547 548 """ 549 out = [] 550 # hostname 551 out.append("lxc.utsname = %s" % instance.name) 552 553 # separate pseudo-TTY instances 554 out.append("lxc.pts = 255") 555 # standard TTYs 556 num_ttys = instance.hvparams[constants.HV_LXC_NUM_TTYS] 557 if num_ttys: # if it is the number greater than 0 558 out.append("lxc.tty = %s" % num_ttys) 559 560 # console log file 561 # After the following patch was applied, we lost the console log file output 562 # until the lxc.console.logfile parameter was introduced in 1.0.6. 563 # https:// 564 # lists.linuxcontainers.org/pipermail/lxc-devel/2014-March/008470.html 565 lxc_version = self._GetLXCVersionFromCmd("lxc-start") 566 if lxc_version >= LXCVersion("1.0.6"): 567 console_log_path = self._InstanceConsoleLogFilePath(instance.name) 568 _CreateBlankFile(console_log_path, constants.SECURE_FILE_MODE) 569 out.append("lxc.console.logfile = %s" % console_log_path) 570 else: 571 logging.warn("Console log file is not supported in LXC version %s," 572 " disabling.", lxc_version) 573 574 # root FS 575 out.append("lxc.rootfs = %s" % sda_dev_path) 576 577 # Necessary file systems 578 out.append("lxc.mount.entry = proc proc proc nodev,noexec,nosuid 0 0") 579 out.append("lxc.mount.entry = sysfs sys sysfs defaults 0 0") 580 581 # CPUs 582 if instance.hvparams[constants.HV_CPU_MASK]: 583 cpu_list = utils.ParseCpuMask(instance.hvparams[constants.HV_CPU_MASK]) 584 cpus_in_mask = len(cpu_list) 585 if cpus_in_mask != instance.beparams["vcpus"]: 586 raise errors.HypervisorError("Number of VCPUs (%d) doesn't match" 587 " the number of CPUs in the" 588 " cpu_mask (%d)" % 589 (instance.beparams["vcpus"], 590 cpus_in_mask)) 591 out.append("lxc.cgroup.cpuset.cpus = %s" % 592 instance.hvparams[constants.HV_CPU_MASK]) 593 594 # Memory 595 out.append("lxc.cgroup.memory.limit_in_bytes = %dM" % 596 instance.beparams[constants.BE_MAXMEM]) 597 if LXCHypervisor._IsCgroupParameterPresent(self._MEMORY_SWAP_PARAMETER, 598 instance.hvparams): 599 out.append("lxc.cgroup.memory.memsw.limit_in_bytes = %dM" % 600 instance.beparams[constants.BE_MAXMEM]) 601 602 # Device control 603 # deny direct device access 604 out.append("lxc.cgroup.devices.deny = a") 605 dev_specs = instance.hvparams[constants.HV_LXC_DEVICES] 606 for dev_spec in dev_specs.split(","): 607 out.append("lxc.cgroup.devices.allow = %s" % dev_spec) 608 609 # Networking 610 for idx, nic in enumerate(instance.nics): 611 out.append("# NIC %d" % idx) 612 mode = nic.nicparams[constants.NIC_MODE] 613 link = nic.nicparams[constants.NIC_LINK] 614 if mode == constants.NIC_MODE_BRIDGED: 615 out.append("lxc.network.type = veth") 616 out.append("lxc.network.link = %s" % link) 617 else: 618 raise errors.HypervisorError("LXC hypervisor only supports" 619 " bridged mode (NIC %d has mode %s)" % 620 (idx, mode)) 621 out.append("lxc.network.hwaddr = %s" % nic.mac) 622 out.append("lxc.network.flags = up") 623 624 # Capabilities 625 for cap in self._GetInstanceDropCapabilities(instance.hvparams): 626 out.append("lxc.cap.drop = %s" % cap) 627 628 # Extra config 629 # TODO: Currently a configuration parameter that includes comma 630 # in its value can't be added via this parameter. 631 # Make this parameter able to read from a file once the 632 # "parameter from a file" feature added. 633 extra_configs = instance.hvparams[constants.HV_LXC_EXTRA_CONFIG] 634 if extra_configs: 635 out.append("# User defined configs") 636 out.extend(extra_configs.split(",")) 637 638 return "\n".join(out) + "\n"
639 640 @classmethod
642 """Return cgroup subsystems list that are enabled in current kernel. 643 644 """ 645 try: 646 subsys_table = utils.ReadFile(cls._PROC_CGROUPS_FILE) 647 except EnvironmentError, err: 648 raise HypervisorError("Failed to read cgroup info from %s: %s" 649 % (cls._PROC_CGROUPS_FILE, err)) 650 return [x.split(None, 1)[0] for x in subsys_table.split("\n") 651 if x and not x.startswith("#")]
652 653 @classmethod
654 - def _EnsureCgroupMounts(cls, hvparams=None):
655 """Ensures all cgroup subsystems required to run LXC container are mounted. 656 657 """ 658 # Check cgroup subsystems required by the Ganeti LXC hypervisor 659 for subsystem in cls._REQUIRED_CGROUP_SUBSYSTEMS: 660 cls._GetOrPrepareCgroupSubsysMountPoint(subsystem) 661 662 # Check cgroup subsystems required by the LXC 663 if hvparams is None or not hvparams[constants.HV_LXC_EXTRA_CGROUPS]: 664 enable_subsystems = cls._GetCgroupEnabledKernelSubsystems() 665 else: 666 enable_subsystems = hvparams[constants.HV_LXC_EXTRA_CGROUPS].split(",") 667 668 for subsystem in enable_subsystems: 669 cls._GetOrPrepareCgroupSubsysMountPoint(subsystem)
670 671 @classmethod
672 - def _PrepareInstanceRootFsBdev(cls, storage_path, stash):
673 """Return mountable path for storage_path. 674 675 This function creates a partition mapping for storage_path and returns the 676 first partition device path as a rootfs partition, and stashes the loopback 677 device path. 678 If storage_path is not a multi-partition block device, just return 679 storage_path. 680 681 """ 682 try: 683 ret = utils.CreateBdevPartitionMapping(storage_path) 684 except errors.CommandError, err: 685 raise HypervisorError("Failed to create partition mapping for %s" 686 ": %s" % (storage_path, err)) 687 688 if ret is None: 689 return storage_path 690 else: 691 loop_dev_path, dm_dev_paths = ret 692 stash[cls._STASH_KEY_ALLOCATED_LOOP_DEV] = loop_dev_path 693 return dm_dev_paths[0]
694 695 @classmethod
696 - def _WaitForInstanceState(cls, instance_name, state, timeout):
697 """Wait for an instance state transition within timeout 698 699 Return True if an instance state changed to the desired state within 700 timeout secs. 701 702 """ 703 result = utils.RunCmd(["lxc-wait", "-n", instance_name, "-s", state], 704 timeout=timeout) 705 if result.failed_by_timeout: 706 return False 707 elif result.failed: 708 raise HypervisorError("Failure while waiting for instance state" 709 " transition: %s" % result.output) 710 else: 711 return True
712
713 - def _SpawnLXC(self, instance, log_file, conf_file):
714 """Execute lxc-start and wait until container health is confirmed. 715 716 """ 717 lxc_start_cmd = [ 718 "lxc-start", 719 "-n", instance.name, 720 "-o", log_file, 721 "-l", "DEBUG", 722 "-f", conf_file, 723 "-d" 724 ] 725 726 result = utils.RunCmd(lxc_start_cmd) 727 if result.failed: 728 raise HypervisorError("Failed to start instance %s : %s" % 729 (instance.name, result.output)) 730 731 lxc_startup_timeout = instance.hvparams[constants.HV_LXC_STARTUP_TIMEOUT] 732 if not self._WaitForInstanceState(instance.name, 733 constants.LXC_STATE_RUNNING, 734 lxc_startup_timeout): 735 raise HypervisorError("Instance %s state didn't change to RUNNING within" 736 " %s secs" % (instance.name, lxc_startup_timeout)) 737 738 # Ensure that the instance is running correctly after being daemonized 739 if not self._IsInstanceAlive(instance.name): 740 raise HypervisorError("Failed to start instance %s :" 741 " lxc process exited after being daemonized" % 742 instance.name)
743 744 @classmethod
745 - def _VerifyDiskRequirements(cls, block_devices):
746 """Insures that the disks provided work with the current implementation. 747 748 """ 749 if len(block_devices) == 0: 750 raise HypervisorError("LXC cannot have diskless instances.") 751 752 if len(block_devices) > 1: 753 raise HypervisorError("At the moment, LXC cannot support more than one" 754 " disk attached to it. Please create this" 755 " instance anew with fewer disks.")
756
757 - def StartInstance(self, instance, block_devices, startup_paused):
758 """Start an instance. 759 760 For LXC, we try to mount the block device and execute 'lxc-start'. 761 We use volatile containers. 762 763 """ 764 LXCHypervisor._VerifyDiskRequirements(block_devices) 765 766 stash = {} 767 768 # Since LXC version >= 1.0.0, the LXC strictly requires all cgroup 769 # subsystems mounted before starting a container. 770 # Try to mount all cgroup subsystems needed to start a LXC container. 771 self._EnsureCgroupMounts(instance.hvparams) 772 773 root_dir = self._InstanceDir(instance.name) 774 try: 775 utils.EnsureDirs([(root_dir, self._DIR_MODE)]) 776 except errors.GenericError, err: 777 raise HypervisorError("Creating instance directory failed: %s", str(err)) 778 779 log_file = self._InstanceLogFilePath(instance) 780 if not os.path.exists(log_file): 781 _CreateBlankFile(log_file, constants.SECURE_FILE_MODE) 782 783 try: 784 sda_dev_path = block_devices[0][1] 785 # LXC needs to use partition mapping devices to access each partition 786 # of the storage 787 sda_dev_path = self._PrepareInstanceRootFsBdev(sda_dev_path, stash) 788 conf_file = self._InstanceConfFilePath(instance.name) 789 conf = self._CreateConfigFile(instance, sda_dev_path) 790 utils.WriteFile(conf_file, data=conf) 791 792 logging.info("Starting LXC container") 793 try: 794 self._SpawnLXC(instance, log_file, conf_file) 795 except: 796 logging.error("Failed to start instance %s. Please take a look at %s to" 797 " see LXC errors.", instance.name, log_file) 798 raise 799 except: 800 # Save the original error 801 exc_info = sys.exc_info() 802 try: 803 self._CleanupInstance(instance.name, stash) 804 except HypervisorError, err: 805 logging.warn("Cleanup for instance %s incomplete: %s", 806 instance.name, err) 807 raise exc_info[0], exc_info[1], exc_info[2] 808 809 self._SaveInstanceStash(instance.name, stash)
810
811 - def StopInstance(self, instance, force=False, retry=False, name=None, 812 timeout=None):
813 """Stop an instance. 814 815 """ 816 assert(timeout is None or force is not None) 817 818 if name is None: 819 name = instance.name 820 821 if self._IsInstanceAlive(instance.name): 822 lxc_stop_cmd = ["lxc-stop", "-n", name] 823 824 if force: 825 lxc_stop_cmd.append("--kill") 826 result = utils.RunCmd(lxc_stop_cmd, timeout=timeout) 827 if result.failed: 828 raise HypervisorError("Failed to kill instance %s: %s" % 829 (name, result.output)) 830 else: 831 # The --timeout=-1 option is needed to prevent lxc-stop performs 832 # hard-stop(kill) for the container after the default timing out. 833 lxc_stop_cmd.extend(["--nokill", "--timeout", "-1"]) 834 result = utils.RunCmd(lxc_stop_cmd, timeout=timeout) 835 if result.failed: 836 logging.error("Failed to stop instance %s: %s", name, result.output)
837
838 - def RebootInstance(self, instance):
839 """Reboot an instance. 840 841 """ 842 if "sys_boot" in self._GetInstanceDropCapabilities(instance.hvparams): 843 raise HypervisorError("The LXC container can't perform a reboot with the" 844 " SYS_BOOT capability dropped.") 845 846 # We can't use the --timeout=-1 approach as same as the StopInstance due to 847 # the following patch was applied in lxc-1.0.5 and we are supporting 848 # LXC >= 1.0.0. 849 # http://lists.linuxcontainers.org/pipermail/lxc-devel/2014-July/009742.html 850 result = utils.RunCmd(["lxc-stop", "-n", instance.name, "--reboot", 851 "--timeout", str(self._REBOOT_TIMEOUT)]) 852 if result.failed: 853 raise HypervisorError("Failed to reboot instance %s: %s" % 854 (instance.name, result.output))
855
856 - def BalloonInstanceMemory(self, instance, mem):
857 """Balloon an instance memory to a certain value. 858 859 @type instance: L{objects.Instance} 860 @param instance: instance to be accepted 861 @type mem: int 862 @param mem: actual memory size to use for instance runtime 863 864 """ 865 mem_in_bytes = mem * 1024 ** 2 866 current_mem_usage = self._GetCgroupMemoryLimit(instance.name) 867 shrinking = mem_in_bytes <= current_mem_usage 868 869 # The memsw.limit_in_bytes parameter might be present depending on kernel 870 # parameters. 871 # If present, it has to be modified at the same time as limit_in_bytes. 872 if LXCHypervisor._IsCgroupParameterPresent(self._MEMORY_SWAP_PARAMETER, 873 instance.hvparams): 874 # memory.memsw.limit_in_bytes is the superlimit of memory.limit_in_bytes 875 # so the order of setting these parameters is quite important. 876 cgparams = [self._MEMORY_SWAP_PARAMETER, self._MEMORY_PARAMETER] 877 else: 878 cgparams = [self._MEMORY_PARAMETER] 879 880 if shrinking: 881 cgparams.reverse() 882 883 for i, cgparam in enumerate(cgparams): 884 try: 885 self._SetCgroupInstanceValue(instance.name, cgparam, str(mem_in_bytes)) 886 except EnvironmentError, err: 887 if shrinking and err.errno == errno.EBUSY: 888 logging.warn("Unable to reclaim memory or swap usage from instance" 889 " %s", instance.name) 890 # Restore changed parameters for an atomicity 891 for restore_param in cgparams[0:i]: 892 try: 893 self._SetCgroupInstanceValue(instance.name, restore_param, 894 str(current_mem_usage)) 895 except EnvironmentError, restore_err: 896 logging.warn("Can't restore the cgroup parameter %s of %s: %s", 897 restore_param, instance.name, restore_err) 898 899 raise HypervisorError("Failed to balloon the memory of %s, can't set" 900 " cgroup parameter %s: %s" % 901 (instance.name, cgparam, err))
902
903 - def GetNodeInfo(self, hvparams=None):
904 """Return information about the node. 905 906 See L{BaseHypervisor.GetLinuxNodeInfo}. 907 908 """ 909 return self.GetLinuxNodeInfo()
910 911 @classmethod
912 - def GetInstanceConsole(cls, instance, primary_node, node_group, 913 hvparams, beparams):
914 """Return a command for connecting to the console of an instance. 915 916 """ 917 ndparams = node_group.FillND(primary_node) 918 return objects.InstanceConsole(instance=instance.name, 919 kind=constants.CONS_SSH, 920 host=primary_node.name, 921 port=ndparams.get(constants.ND_SSH_PORT), 922 user=constants.SSH_CONSOLE_USER, 923 command=["lxc-console", "-n", instance.name])
924 925 @classmethod
926 - def _GetLXCVersionFromCmd(cls, from_cmd):
927 """Return the LXC version currently used in the system. 928 929 Version information will be retrieved by command specified by from_cmd. 930 931 @param from_cmd: the lxc command used to retrieve version information 932 @type from_cmd: string 933 @rtype: L{LXCVersion} 934 @return: a version object which represents the version retrieved from the 935 command 936 937 """ 938 result = utils.RunCmd([from_cmd, "--version"]) 939 if result.failed: 940 raise HypervisorError("Failed to get version info from command %s: %s" % 941 (from_cmd, result.output)) 942 943 try: 944 return LXCVersion(result.stdout.strip()) 945 except ValueError, err: 946 raise HypervisorError("Can't parse LXC version from %s: %s" % 947 (from_cmd, err))
948 949 @classmethod
950 - def _VerifyLXCCommands(cls):
951 """Verify the validity of lxc command line tools. 952 953 @rtype: list(str) 954 @return: list of problem descriptions. the blank list will be returned if 955 there is no problem. 956 957 """ 958 msgs = [] 959 for cmd in cls._LXC_COMMANDS_REQUIRED: 960 try: 961 # lxc-ls needs special checking procedure. 962 # there are two different version of lxc-ls, one is written in python 963 # and the other is written in shell script. 964 # we have to ensure the python version of lxc-ls is installed. 965 if cmd == "lxc-ls": 966 help_string = utils.RunCmd(["lxc-ls", "--help"]).output 967 if "--running" not in help_string: 968 # shell script version has no --running switch 969 msgs.append("The python version of 'lxc-ls' is required." 970 " Maybe lxc was installed without --enable-python") 971 else: 972 try: 973 version = cls._GetLXCVersionFromCmd(cmd) 974 except HypervisorError, err: 975 msgs.append(str(err)) 976 continue 977 978 if version < cls._LXC_MIN_VERSION_REQUIRED: 979 msgs.append("LXC version >= %s is required but command %s has" 980 " version %s" % 981 (cls._LXC_MIN_VERSION_REQUIRED, cmd, version)) 982 except errors.OpExecError: 983 msgs.append("Required command %s not found" % cmd) 984 985 return msgs
986
987 - def Verify(self, hvparams=None):
988 """Verify the hypervisor. 989 990 For the LXC manager, it just checks the existence of the base dir. 991 992 @type hvparams: dict of strings 993 @param hvparams: hypervisor parameters to be verified against; not used here 994 995 @return: Problem description if something is wrong, C{None} otherwise 996 997 """ 998 msgs = [] 999 1000 if not os.path.exists(self._ROOT_DIR): 1001 msgs.append("The required directory '%s' does not exist" % 1002 self._ROOT_DIR) 1003 1004 try: 1005 self._EnsureCgroupMounts(hvparams) 1006 except errors.HypervisorError, err: 1007 msgs.append(str(err)) 1008 1009 msgs.extend(self._VerifyLXCCommands()) 1010 1011 return self._FormatVerifyResults(msgs)
1012 1013 @classmethod
1014 - def PowercycleNode(cls, hvparams=None):
1015 """LXC powercycle, just a wrapper over Linux powercycle. 1016 1017 @type hvparams: dict of strings 1018 @param hvparams: hypervisor params to be used on this node 1019 1020 """ 1021 cls.LinuxPowercycle()
1022
1023 - def MigrateInstance(self, cluster_name, instance, target, live):
1024 """Migrate an instance. 1025 1026 @type cluster_name: string 1027 @param cluster_name: name of the cluster 1028 @type instance: L{objects.Instance} 1029 @param instance: the instance to be migrated 1030 @type target: string 1031 @param target: hostname (usually ip) of the target node 1032 @type live: boolean 1033 @param live: whether to do a live or non-live migration 1034 1035 """ 1036 raise HypervisorError("Migration is not supported by the LXC hypervisor")
1037
1038 - def GetMigrationStatus(self, instance):
1039 """Get the migration status 1040 1041 @type instance: L{objects.Instance} 1042 @param instance: the instance that is being migrated 1043 @rtype: L{objects.MigrationStatus} 1044 @return: the status of the current migration (one of 1045 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional 1046 progress info that can be retrieved from the hypervisor 1047 1048 """ 1049 raise HypervisorError("Migration is not supported by the LXC hypervisor")
1050