4 # Copyright (C) 2006, 2007, 2008, 2009, 2013 Google Inc.
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.
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.
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
22 """Chroot manager hypervisor
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
40 class ChrootManager(hv_base.BaseHypervisor):
43 This not-really hypervisor allows ganeti to manage chroots. It has
44 special behaviour and requirements on the OS definition and the node
46 - the start and stop of the chroot environment are done via a
47 script called ganeti-chroot located in the root directory of the
48 first drive, which should be created by the OS definition
49 - this script must accept the start and stop argument and, on
50 shutdown, it should cleanly shutdown the daemons/processes
52 - the daemons run in chroot should only bind to the instance IP
53 (to which the OS create script has access via the instance name)
54 - since some daemons in the node could be listening on the wildcard
55 address, some ports might be unavailable
56 - the instance listing will show no memory usage
57 - on shutdown, the chroot manager will try to find all mountpoints
58 under the root dir of the instance and unmount them
59 - instance alive check is based on whether any process is using the chroot
62 _ROOT_DIR = pathutils.RUN_DIR + "/chroot-hypervisor"
65 constants.HV_INIT_SCRIPT: (True, utils.IsNormAbsPath,
66 "must be an absolute normalized path",
71 hv_base.BaseHypervisor.__init__(self)
72 utils.EnsureDirs([(self._ROOT_DIR, constants.RUN_DIRS_MODE)])
76 """Check if a directory looks like a live chroot.
79 if not os.path.ismount(path):
81 result = utils.RunCmd(["fuser", "-m", path])
82 return not result.failed
85 def _GetMountSubdirs(path):
86 """Return the list of mountpoints under a given path.
90 for _, mountpoint, _, _ in utils.GetMounts():
91 if (mountpoint.startswith(path) and
93 result.append(mountpoint)
95 result.sort(key=lambda x: x.count("/"), reverse=True)
99 def _InstanceDir(cls, instance_name):
100 """Return the root directory for an instance.
103 return utils.PathJoin(cls._ROOT_DIR, instance_name)
105 def ListInstances(self, hvparams=None):
106 """Get the list of running instances.
109 return [name for name in os.listdir(self._ROOT_DIR)
110 if self._IsDirLive(utils.PathJoin(self._ROOT_DIR, name))]
112 def GetInstanceInfo(self, instance_name, hvparams=None):
113 """Get instance properties.
115 @type instance_name: string
116 @param instance_name: the instance name
117 @type hvparams: dict of strings
118 @param hvparams: hvparams to be used with this instance
120 @return: (name, id, memory, vcpus, stat, times)
123 dir_name = self._InstanceDir(instance_name)
124 if not self._IsDirLive(dir_name):
125 raise HypervisorError("Instance %s is not running" % instance_name)
126 return (instance_name, 0, 0, 0, 0, 0)
128 def GetAllInstancesInfo(self, hvparams=None):
129 """Get properties of all instances.
131 @type hvparams: dict of strings
132 @param hvparams: hypervisor parameter
133 @return: [(name, id, memory, vcpus, stat, times),...]
137 for file_name in os.listdir(self._ROOT_DIR):
138 path = utils.PathJoin(self._ROOT_DIR, file_name)
139 if self._IsDirLive(path):
140 data.append((file_name, 0, 0, 0, 0, 0))
143 def StartInstance(self, instance, block_devices, startup_paused):
144 """Start an instance.
146 For the chroot manager, we try to mount the block device and
147 execute '/ganeti-chroot start'.
150 root_dir = self._InstanceDir(instance.name)
151 if not os.path.exists(root_dir):
155 raise HypervisorError("Failed to start instance %s: %s" %
156 (instance.name, err))
157 if not os.path.isdir(root_dir):
158 raise HypervisorError("Needed path %s is not a directory" % root_dir)
160 if not os.path.ismount(root_dir):
161 if not block_devices:
162 raise HypervisorError("The chroot manager needs at least one disk")
164 sda_dev_path = block_devices[0][1]
165 result = utils.RunCmd(["mount", sda_dev_path, root_dir])
167 raise HypervisorError("Can't mount the chroot dir: %s" % result.output)
168 init_script = instance.hvparams[constants.HV_INIT_SCRIPT]
169 result = utils.RunCmd(["chroot", root_dir, init_script, "start"])
171 raise HypervisorError("Can't run the chroot start script: %s" %
174 def StopInstance(self, instance, force=False, retry=False, name=None):
177 This method has complicated cleanup tests, as we must:
178 - try to kill all leftover processes
179 - try to unmount any additional sub-mountpoints
180 - finally unmount the instance dir
186 root_dir = self._InstanceDir(name)
187 if not os.path.exists(root_dir) or not self._IsDirLive(root_dir):
190 # Run the chroot stop script only once
191 if not retry and not force:
192 result = utils.RunCmd(["chroot", root_dir, "/ganeti-chroot", "stop"])
194 raise HypervisorError("Can't run the chroot stop script: %s" %
198 utils.RunCmd(["fuser", "-k", "-TERM", "-m", root_dir])
200 utils.RunCmd(["fuser", "-k", "-KILL", "-m", root_dir])
201 # 2 seconds at most should be enough for KILL to take action
204 if self._IsDirLive(root_dir):
206 raise HypervisorError("Can't stop the processes using the chroot")
209 def CleanupInstance(self, instance_name):
210 """Cleanup after a stopped instance
213 root_dir = self._InstanceDir(instance_name)
215 if not os.path.exists(root_dir):
218 if self._IsDirLive(root_dir):
219 raise HypervisorError("Processes are still using the chroot")
221 for mpath in self._GetMountSubdirs(root_dir):
222 utils.RunCmd(["umount", mpath])
224 result = utils.RunCmd(["umount", root_dir])
226 msg = ("Processes still alive in the chroot: %s" %
227 utils.RunCmd("fuser -vm %s" % root_dir).output)
229 raise HypervisorError("Can't umount the chroot dir: %s (%s)" %
230 (result.output, msg))
232 def RebootInstance(self, instance):
233 """Reboot an instance.
235 This is not (yet) implemented for the chroot manager.
238 raise HypervisorError("The chroot manager doesn't implement the"
239 " reboot functionality")
241 def BalloonInstanceMemory(self, instance, mem):
242 """Balloon an instance memory to a certain value.
244 @type instance: L{objects.Instance}
245 @param instance: instance to be accepted
247 @param mem: actual memory size to use for instance runtime
250 # Currently chroots don't have memory limits
253 def GetNodeInfo(self, hvparams=None):
254 """Return information about the node.
256 See L{BaseHypervisor.GetLinuxNodeInfo}.
259 return self.GetLinuxNodeInfo()
262 def GetInstanceConsole(cls, instance, primary_node, # pylint: disable=W0221
263 hvparams, beparams, root_dir=None):
264 """Return information for connecting to the console of an instance.
268 root_dir = cls._InstanceDir(instance.name)
269 if not os.path.ismount(root_dir):
270 raise HypervisorError("Instance %s is not running" % instance.name)
272 return objects.InstanceConsole(instance=instance.name,
273 kind=constants.CONS_SSH,
274 host=primary_node.name,
275 user=constants.SSH_CONSOLE_USER,
276 command=["chroot", root_dir])
278 def Verify(self, hvparams=None):
279 """Verify the hypervisor.
281 For the chroot manager, it just checks the existence of the base dir.
283 @type hvparams: dict of strings
284 @param hvparams: hypervisor parameters to be verified against, not used
287 @return: Problem description if something is wrong, C{None} otherwise
290 if os.path.exists(self._ROOT_DIR):
293 return "The required directory '%s' does not exist" % self._ROOT_DIR
296 def PowercycleNode(cls, hvparams=None):
297 """Chroot powercycle, just a wrapper over Linux powercycle.
299 @type hvparams: dict of strings
300 @param hvparams: hypervisor params to be used on this node
303 cls.LinuxPowercycle()
305 def MigrateInstance(self, cluster_name, instance, target, live):
306 """Migrate an instance.
308 @type cluster_name: string
309 @param cluster_name: name of the cluster
310 @type instance: L{objects.Instance}
311 @param instance: the instance to be migrated
313 @param target: hostname (usually ip) of the target node
315 @param live: whether to do a live or non-live migration
318 raise HypervisorError("Migration not supported by the chroot hypervisor")
320 def GetMigrationStatus(self, instance):
321 """Get the migration status
323 @type instance: L{objects.Instance}
324 @param instance: the instance that is being migrated
325 @rtype: L{objects.MigrationStatus}
326 @return: the status of the current migration (one of
327 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
328 progress info that can be retrieved from the hypervisor
331 raise HypervisorError("Migration not supported by the chroot hypervisor")