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 self._ListAliveInstances()
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", re.escape(instance_name)]) 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 @classmethod
494 - def _ListAliveInstances(cls):
495 """Return list of alive instances. 496 497 """ 498 result = utils.RunCmd(["lxc-ls", "--running"]) 499 if result.failed: 500 raise HypervisorError("Failed to get running LXC containers list: %s" % 501 result.output) 502 503 return result.stdout.split()
504
505 - def GetInstanceInfo(self, instance_name, hvparams=None):
506 """Get instance properties. 507 508 @type instance_name: string 509 @param instance_name: the instance name 510 @type hvparams: dict of strings 511 @param hvparams: hvparams to be used with this instance 512 @rtype: tuple of strings 513 @return: (name, id, memory, vcpus, stat, times) 514 515 """ 516 if not self._IsInstanceAlive(instance_name): 517 return None 518 519 return self._GetInstanceInfoInner(instance_name)
520
521 - def _GetInstanceInfoInner(self, instance_name):
522 """Get instance properties. 523 524 @type instance_name: string 525 @param instance_name: the instance name 526 @rtype: tuple of strings 527 @return: (name, id, memory, vcpus, stat, times) 528 529 """ 530 531 cpu_list = self._GetCgroupCpuList(instance_name) 532 memory = self._GetCgroupMemoryLimit(instance_name) / (1024 ** 2) 533 cputime = self._GetCgroupCpuUsage(instance_name) 534 return (instance_name, 0, memory, len(cpu_list), 535 hv_base.HvInstanceState.RUNNING, cputime)
536
537 - def GetAllInstancesInfo(self, hvparams=None):
538 """Get properties of all instances. 539 540 @type hvparams: dict of strings 541 @param hvparams: hypervisor parameter 542 @return: [(name, id, memory, vcpus, stat, times),...] 543 544 """ 545 data = [] 546 running_instances = self._ListAliveInstances() 547 filter_fn = lambda x: os.path.isdir(utils.PathJoin(self._INSTANCE_DIR, x)) 548 for dirname in filter(filter_fn, os.listdir(self._INSTANCE_DIR)): 549 if dirname not in running_instances: 550 continue 551 try: 552 info = self._GetInstanceInfoInner(dirname) 553 except errors.HypervisorError: 554 continue 555 if info: 556 data.append(info) 557 return data
558 559 @classmethod
560 - def _GetInstanceDropCapabilities(cls, hvparams):
561 """Get and parse the drop capabilities list from the instance hvparams. 562 563 @type hvparams: dict of strings 564 @param hvparams: instance hvparams 565 @rtype list(string) 566 @return list of drop capabilities 567 568 """ 569 drop_caps = hvparams[constants.HV_LXC_DROP_CAPABILITIES] 570 return drop_caps.split(",")
571
572 - def _CreateConfigFile(self, instance, sda_dev_path):
573 """Create an lxc.conf file for an instance. 574 575 """ 576 out = [] 577 # hostname 578 out.append("lxc.utsname = %s" % instance.name) 579 580 # separate pseudo-TTY instances 581 out.append("lxc.pts = 255") 582 # standard TTYs 583 num_ttys = instance.hvparams[constants.HV_LXC_NUM_TTYS] 584 if num_ttys: # if it is the number greater than 0 585 out.append("lxc.tty = %s" % num_ttys) 586 587 # console log file 588 # After the following patch was applied, we lost the console log file output 589 # until the lxc.console.logfile parameter was introduced in 1.0.6. 590 # https:// 591 # lists.linuxcontainers.org/pipermail/lxc-devel/2014-March/008470.html 592 lxc_version = self._GetLXCVersionFromCmd("lxc-start") 593 if lxc_version >= LXCVersion("1.0.6"): 594 console_log_path = self._InstanceConsoleLogFilePath(instance.name) 595 _CreateBlankFile(console_log_path, constants.SECURE_FILE_MODE) 596 out.append("lxc.console.logfile = %s" % console_log_path) 597 else: 598 logging.warn("Console log file is not supported in LXC version %s," 599 " disabling.", lxc_version) 600 601 # root FS 602 out.append("lxc.rootfs = %s" % sda_dev_path) 603 604 # Necessary file systems 605 out.append("lxc.mount.entry = proc proc proc nodev,noexec,nosuid 0 0") 606 out.append("lxc.mount.entry = sysfs sys sysfs defaults 0 0") 607 608 # CPUs 609 if instance.hvparams[constants.HV_CPU_MASK]: 610 cpu_list = utils.ParseCpuMask(instance.hvparams[constants.HV_CPU_MASK]) 611 cpus_in_mask = len(cpu_list) 612 if cpus_in_mask != instance.beparams["vcpus"]: 613 raise errors.HypervisorError("Number of VCPUs (%d) doesn't match" 614 " the number of CPUs in the" 615 " cpu_mask (%d)" % 616 (instance.beparams["vcpus"], 617 cpus_in_mask)) 618 out.append("lxc.cgroup.cpuset.cpus = %s" % 619 instance.hvparams[constants.HV_CPU_MASK]) 620 621 # Memory 622 out.append("lxc.cgroup.memory.limit_in_bytes = %dM" % 623 instance.beparams[constants.BE_MAXMEM]) 624 if LXCHypervisor._IsCgroupParameterPresent(self._MEMORY_SWAP_PARAMETER, 625 instance.hvparams): 626 out.append("lxc.cgroup.memory.memsw.limit_in_bytes = %dM" % 627 instance.beparams[constants.BE_MAXMEM]) 628 629 # Device control 630 # deny direct device access 631 out.append("lxc.cgroup.devices.deny = a") 632 dev_specs = instance.hvparams[constants.HV_LXC_DEVICES] 633 for dev_spec in dev_specs.split(","): 634 out.append("lxc.cgroup.devices.allow = %s" % dev_spec) 635 636 # Networking 637 for idx, nic in enumerate(instance.nics): 638 out.append("# NIC %d" % idx) 639 mode = nic.nicparams[constants.NIC_MODE] 640 link = nic.nicparams[constants.NIC_LINK] 641 if mode == constants.NIC_MODE_BRIDGED: 642 out.append("lxc.network.type = veth") 643 out.append("lxc.network.link = %s" % link) 644 else: 645 raise errors.HypervisorError("LXC hypervisor only supports" 646 " bridged mode (NIC %d has mode %s)" % 647 (idx, mode)) 648 out.append("lxc.network.hwaddr = %s" % nic.mac) 649 out.append("lxc.network.flags = up") 650 651 # Capabilities 652 for cap in self._GetInstanceDropCapabilities(instance.hvparams): 653 out.append("lxc.cap.drop = %s" % cap) 654 655 # Extra config 656 # TODO: Currently a configuration parameter that includes comma 657 # in its value can't be added via this parameter. 658 # Make this parameter able to read from a file once the 659 # "parameter from a file" feature added. 660 extra_configs = instance.hvparams[constants.HV_LXC_EXTRA_CONFIG] 661 if extra_configs: 662 out.append("# User defined configs") 663 out.extend(extra_configs.split(",")) 664 665 return "\n".join(out) + "\n"
666 667 @classmethod
669 """Return cgroup subsystems list that are enabled in current kernel. 670 671 """ 672 try: 673 subsys_table = utils.ReadFile(cls._PROC_CGROUPS_FILE) 674 except EnvironmentError, err: 675 raise HypervisorError("Failed to read cgroup info from %s: %s" 676 % (cls._PROC_CGROUPS_FILE, err)) 677 return [x.split(None, 1)[0] for x in subsys_table.split("\n") 678 if x and not x.startswith("#")]
679 680 @classmethod
681 - def _EnsureCgroupMounts(cls, hvparams=None):
682 """Ensures all cgroup subsystems required to run LXC container are mounted. 683 684 """ 685 # Check cgroup subsystems required by the Ganeti LXC hypervisor 686 for subsystem in cls._REQUIRED_CGROUP_SUBSYSTEMS: 687 cls._GetOrPrepareCgroupSubsysMountPoint(subsystem) 688 689 # Check cgroup subsystems required by the LXC 690 if hvparams is None or not hvparams[constants.HV_LXC_EXTRA_CGROUPS]: 691 enable_subsystems = cls._GetCgroupEnabledKernelSubsystems() 692 else: 693 enable_subsystems = hvparams[constants.HV_LXC_EXTRA_CGROUPS].split(",") 694 695 for subsystem in enable_subsystems: 696 cls._GetOrPrepareCgroupSubsysMountPoint(subsystem)
697 698 @classmethod
699 - def _PrepareInstanceRootFsBdev(cls, storage_path, stash):
700 """Return mountable path for storage_path. 701 702 This function creates a partition mapping for storage_path and returns the 703 first partition device path as a rootfs partition, and stashes the loopback 704 device path. 705 If storage_path is not a multi-partition block device, just return 706 storage_path. 707 708 """ 709 try: 710 ret = utils.CreateBdevPartitionMapping(storage_path) 711 except errors.CommandError, err: 712 raise HypervisorError("Failed to create partition mapping for %s" 713 ": %s" % (storage_path, err)) 714 715 if ret is None: 716 return storage_path 717 else: 718 loop_dev_path, dm_dev_paths = ret 719 stash[cls._STASH_KEY_ALLOCATED_LOOP_DEV] = loop_dev_path 720 return dm_dev_paths[0]
721 722 @classmethod
723 - def _WaitForInstanceState(cls, instance_name, state, timeout):
724 """Wait for an instance state transition within timeout 725 726 Return True if an instance state changed to the desired state within 727 timeout secs. 728 729 """ 730 result = utils.RunCmd(["lxc-wait", "-n", instance_name, "-s", state], 731 timeout=timeout) 732 if result.failed_by_timeout: 733 return False 734 elif result.failed: 735 raise HypervisorError("Failure while waiting for instance state" 736 " transition: %s" % result.output) 737 else: 738 return True
739
740 - def _SpawnLXC(self, instance, log_file, conf_file):
741 """Execute lxc-start and wait until container health is confirmed. 742 743 """ 744 lxc_start_cmd = [ 745 "lxc-start", 746 "-n", instance.name, 747 "-o", log_file, 748 "-l", "DEBUG", 749 "-f", conf_file, 750 "-d" 751 ] 752 753 result = utils.RunCmd(lxc_start_cmd) 754 if result.failed: 755 raise HypervisorError("Failed to start instance %s : %s" % 756 (instance.name, result.output)) 757 758 lxc_startup_timeout = instance.hvparams[constants.HV_LXC_STARTUP_TIMEOUT] 759 if not self._WaitForInstanceState(instance.name, 760 constants.LXC_STATE_RUNNING, 761 lxc_startup_timeout): 762 raise HypervisorError("Instance %s state didn't change to RUNNING within" 763 " %s secs" % (instance.name, lxc_startup_timeout)) 764 765 # Ensure that the instance is running correctly after being daemonized 766 if not self._IsInstanceAlive(instance.name): 767 raise HypervisorError("Failed to start instance %s :" 768 " lxc process exited after being daemonized" % 769 instance.name)
770 771 @classmethod
772 - def _VerifyDiskRequirements(cls, block_devices):
773 """Insures that the disks provided work with the current implementation. 774 775 """ 776 if len(block_devices) == 0: 777 raise HypervisorError("LXC cannot have diskless instances.") 778 779 if len(block_devices) > 1: 780 raise HypervisorError("At the moment, LXC cannot support more than one" 781 " disk attached to it. Please create this" 782 " instance anew with fewer disks.")
783
784 - def StartInstance(self, instance, block_devices, startup_paused):
785 """Start an instance. 786 787 For LXC, we try to mount the block device and execute 'lxc-start'. 788 We use volatile containers. 789 790 """ 791 LXCHypervisor._VerifyDiskRequirements(block_devices) 792 793 stash = {} 794 795 # Since LXC version >= 1.0.0, the LXC strictly requires all cgroup 796 # subsystems mounted before starting a container. 797 # Try to mount all cgroup subsystems needed to start a LXC container. 798 self._EnsureCgroupMounts(instance.hvparams) 799 800 root_dir = self._InstanceDir(instance.name) 801 try: 802 utils.EnsureDirs([(root_dir, self._DIR_MODE)]) 803 except errors.GenericError, err: 804 raise HypervisorError("Creating instance directory failed: %s", str(err)) 805 806 log_file = self._InstanceLogFilePath(instance) 807 if not os.path.exists(log_file): 808 _CreateBlankFile(log_file, constants.SECURE_FILE_MODE) 809 810 try: 811 sda_dev_path = block_devices[0][1] 812 # LXC needs to use partition mapping devices to access each partition 813 # of the storage 814 sda_dev_path = self._PrepareInstanceRootFsBdev(sda_dev_path, stash) 815 conf_file = self._InstanceConfFilePath(instance.name) 816 conf = self._CreateConfigFile(instance, sda_dev_path) 817 utils.WriteFile(conf_file, data=conf) 818 819 logging.info("Starting LXC container") 820 try: 821 self._SpawnLXC(instance, log_file, conf_file) 822 except: 823 logging.error("Failed to start instance %s. Please take a look at %s to" 824 " see LXC errors.", instance.name, log_file) 825 raise 826 except: 827 # Save the original error 828 exc_info = sys.exc_info() 829 try: 830 self._CleanupInstance(instance.name, stash) 831 except HypervisorError, err: 832 logging.warn("Cleanup for instance %s incomplete: %s", 833 instance.name, err) 834 raise exc_info[0], exc_info[1], exc_info[2] 835 836 self._SaveInstanceStash(instance.name, stash)
837
838 - def StopInstance(self, instance, force=False, retry=False, name=None, 839 timeout=None):
840 """Stop an instance. 841 842 """ 843 assert(timeout is None or force is not None) 844 845 if name is None: 846 name = instance.name 847 848 if self._IsInstanceAlive(instance.name): 849 lxc_stop_cmd = ["lxc-stop", "-n", name] 850 851 if force: 852 lxc_stop_cmd.append("--kill") 853 result = utils.RunCmd(lxc_stop_cmd, timeout=timeout) 854 if result.failed: 855 raise HypervisorError("Failed to kill instance %s: %s" % 856 (name, result.output)) 857 else: 858 # The --timeout=-1 option is needed to prevent lxc-stop performs 859 # hard-stop(kill) for the container after the default timing out. 860 lxc_stop_cmd.extend(["--nokill", "--timeout", "-1"]) 861 result = utils.RunCmd(lxc_stop_cmd, timeout=timeout) 862 if result.failed: 863 logging.error("Failed to stop instance %s: %s", name, result.output)
864
865 - def RebootInstance(self, instance):
866 """Reboot an instance. 867 868 """ 869 if "sys_boot" in self._GetInstanceDropCapabilities(instance.hvparams): 870 raise HypervisorError("The LXC container can't perform a reboot with the" 871 " SYS_BOOT capability dropped.") 872 873 # We can't use the --timeout=-1 approach as same as the StopInstance due to 874 # the following patch was applied in lxc-1.0.5 and we are supporting 875 # LXC >= 1.0.0. 876 # http://lists.linuxcontainers.org/pipermail/lxc-devel/2014-July/009742.html 877 result = utils.RunCmd(["lxc-stop", "-n", instance.name, "--reboot", 878 "--timeout", str(self._REBOOT_TIMEOUT)]) 879 if result.failed: 880 raise HypervisorError("Failed to reboot instance %s: %s" % 881 (instance.name, result.output))
882
883 - def BalloonInstanceMemory(self, instance, mem):
884 """Balloon an instance memory to a certain value. 885 886 @type instance: L{objects.Instance} 887 @param instance: instance to be accepted 888 @type mem: int 889 @param mem: actual memory size to use for instance runtime 890 891 """ 892 mem_in_bytes = mem * 1024 ** 2 893 current_mem_usage = self._GetCgroupMemoryLimit(instance.name) 894 shrinking = mem_in_bytes <= current_mem_usage 895 896 # The memsw.limit_in_bytes parameter might be present depending on kernel 897 # parameters. 898 # If present, it has to be modified at the same time as limit_in_bytes. 899 if LXCHypervisor._IsCgroupParameterPresent(self._MEMORY_SWAP_PARAMETER, 900 instance.hvparams): 901 # memory.memsw.limit_in_bytes is the superlimit of memory.limit_in_bytes 902 # so the order of setting these parameters is quite important. 903 cgparams = [self._MEMORY_SWAP_PARAMETER, self._MEMORY_PARAMETER] 904 else: 905 cgparams = [self._MEMORY_PARAMETER] 906 907 if shrinking: 908 cgparams.reverse() 909 910 for i, cgparam in enumerate(cgparams): 911 try: 912 self._SetCgroupInstanceValue(instance.name, cgparam, str(mem_in_bytes)) 913 except EnvironmentError, err: 914 if shrinking and err.errno == errno.EBUSY: 915 logging.warn("Unable to reclaim memory or swap usage from instance" 916 " %s", instance.name) 917 # Restore changed parameters for an atomicity 918 for restore_param in cgparams[0:i]: 919 try: 920 self._SetCgroupInstanceValue(instance.name, restore_param, 921 str(current_mem_usage)) 922 except EnvironmentError, restore_err: 923 logging.warn("Can't restore the cgroup parameter %s of %s: %s", 924 restore_param, instance.name, restore_err) 925 926 raise HypervisorError("Failed to balloon the memory of %s, can't set" 927 " cgroup parameter %s: %s" % 928 (instance.name, cgparam, err))
929
930 - def GetNodeInfo(self, hvparams=None):
931 """Return information about the node. 932 933 See L{BaseHypervisor.GetLinuxNodeInfo}. 934 935 """ 936 return self.GetLinuxNodeInfo()
937 938 @classmethod
939 - def GetInstanceConsole(cls, instance, primary_node, node_group, 940 hvparams, beparams):
941 """Return a command for connecting to the console of an instance. 942 943 """ 944 ndparams = node_group.FillND(primary_node) 945 return objects.InstanceConsole(instance=instance.name, 946 kind=constants.CONS_SSH, 947 host=primary_node.name, 948 port=ndparams.get(constants.ND_SSH_PORT), 949 user=constants.SSH_CONSOLE_USER, 950 command=["lxc-console", "-n", instance.name])
951 952 @classmethod
953 - def _GetLXCVersionFromCmd(cls, from_cmd):
954 """Return the LXC version currently used in the system. 955 956 Version information will be retrieved by command specified by from_cmd. 957 958 @param from_cmd: the lxc command used to retrieve version information 959 @type from_cmd: string 960 @rtype: L{LXCVersion} 961 @return: a version object which represents the version retrieved from the 962 command 963 964 """ 965 result = utils.RunCmd([from_cmd, "--version"]) 966 if result.failed: 967 raise HypervisorError("Failed to get version info from command %s: %s" % 968 (from_cmd, result.output)) 969 970 try: 971 return LXCVersion(result.stdout.strip()) 972 except ValueError, err: 973 raise HypervisorError("Can't parse LXC version from %s: %s" % 974 (from_cmd, err))
975 976 @classmethod
977 - def _VerifyLXCCommands(cls):
978 """Verify the validity of lxc command line tools. 979 980 @rtype: list(str) 981 @return: list of problem descriptions. the blank list will be returned if 982 there is no problem. 983 984 """ 985 msgs = [] 986 for cmd in cls._LXC_COMMANDS_REQUIRED: 987 try: 988 # lxc-ls needs special checking procedure. 989 # there are two different version of lxc-ls, one is written in python 990 # and the other is written in shell script. 991 # we have to ensure the python version of lxc-ls is installed. 992 if cmd == "lxc-ls": 993 help_string = utils.RunCmd(["lxc-ls", "--help"]).output 994 if "--running" not in help_string: 995 # shell script version has no --running switch 996 msgs.append("The python version of 'lxc-ls' is required." 997 " Maybe lxc was installed without --enable-python") 998 else: 999 try: 1000 version = cls._GetLXCVersionFromCmd(cmd) 1001 except HypervisorError, err: 1002 msgs.append(str(err)) 1003 continue 1004 1005 if version < cls._LXC_MIN_VERSION_REQUIRED: 1006 msgs.append("LXC version >= %s is required but command %s has" 1007 " version %s" % 1008 (cls._LXC_MIN_VERSION_REQUIRED, cmd, version)) 1009 except errors.OpExecError: 1010 msgs.append("Required command %s not found" % cmd) 1011 1012 return msgs
1013
1014 - def Verify(self, hvparams=None):
1015 """Verify the hypervisor. 1016 1017 For the LXC manager, it just checks the existence of the base dir. 1018 1019 @type hvparams: dict of strings 1020 @param hvparams: hypervisor parameters to be verified against; not used here 1021 1022 @return: Problem description if something is wrong, C{None} otherwise 1023 1024 """ 1025 msgs = [] 1026 1027 if not os.path.exists(self._ROOT_DIR): 1028 msgs.append("The required directory '%s' does not exist" % 1029 self._ROOT_DIR) 1030 1031 try: 1032 self._EnsureCgroupMounts(hvparams) 1033 except errors.HypervisorError, err: 1034 msgs.append(str(err)) 1035 1036 msgs.extend(self._VerifyLXCCommands()) 1037 1038 return self._FormatVerifyResults(msgs)
1039 1040 @classmethod
1041 - def PowercycleNode(cls, hvparams=None):
1042 """LXC powercycle, just a wrapper over Linux powercycle. 1043 1044 @type hvparams: dict of strings 1045 @param hvparams: hypervisor params to be used on this node 1046 1047 """ 1048 cls.LinuxPowercycle()
1049
1050 - def MigrateInstance(self, cluster_name, instance, target, live):
1051 """Migrate an instance. 1052 1053 @type cluster_name: string 1054 @param cluster_name: name of the cluster 1055 @type instance: L{objects.Instance} 1056 @param instance: the instance to be migrated 1057 @type target: string 1058 @param target: hostname (usually ip) of the target node 1059 @type live: boolean 1060 @param live: whether to do a live or non-live migration 1061 1062 """ 1063 raise HypervisorError("Migration is not supported by the LXC hypervisor")
1064
1065 - def GetMigrationStatus(self, instance):
1066 """Get the migration status 1067 1068 @type instance: L{objects.Instance} 1069 @param instance: the instance that is being migrated 1070 @rtype: L{objects.MigrationStatus} 1071 @return: the status of the current migration (one of 1072 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional 1073 progress info that can be retrieved from the hypervisor 1074 1075 """ 1076 raise HypervisorError("Migration is not supported by the LXC hypervisor")
1077