4 # Copyright (C) 2006, 2007, 2008, 2009 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-msg=W0611
33 from ganeti import utils
34 from ganeti.hypervisor import hv_base
35 from ganeti.errors import HypervisorError
38 class ChrootManager(hv_base.BaseHypervisor):
41 This not-really hypervisor allows ganeti to manage chroots. It has
42 special behaviour and requirements on the OS definition and the node
44 - the start and stop of the chroot environment are done via a
45 script called ganeti-chroot located in the root directory of the
46 first drive, which should be created by the OS definition
47 - this script must accept the start and stop argument and, on
48 shutdown, it should cleanly shutdown the daemons/processes
50 - the daemons run in chroot should only bind to the instance IP
51 (to which the OS create script has access via the instance name)
52 - since some daemons in the node could be listening on the wildcard
53 address, some ports might be unavailable
54 - the instance listing will show no memory usage
55 - on shutdown, the chroot manager will try to find all mountpoints
56 under the root dir of the instance and unmount them
57 - instance alive check is based on whether any process is using the chroot
60 _ROOT_DIR = constants.RUN_GANETI_DIR + "/chroot-hypervisor"
63 constants.HV_INIT_SCRIPT: (True, utils.IsNormAbsPath,
64 "must be an absolute normalized path",
69 hv_base.BaseHypervisor.__init__(self)
70 if not os.path.exists(self._ROOT_DIR):
71 os.mkdir(self._ROOT_DIR)
72 if not os.path.isdir(self._ROOT_DIR):
73 raise HypervisorError("Needed path %s is not a directory" %
78 """Check if a directory looks like a live chroot.
81 if not os.path.ismount(path):
83 result = utils.RunCmd(["fuser", "-m", path])
84 return not result.failed
87 def _GetMountSubdirs(path):
88 """Return the list of mountpoints under a given path.
90 This function is Linux-specific.
93 #TODO(iustin): investigate and document non-linux options
94 #(e.g. via mount output)
96 fh = open("/proc/mounts", "r")
99 _, mountpoint, _ = line.split(" ", 2)
100 if (mountpoint.startswith(path) and
102 data.append(mountpoint)
105 data.sort(key=lambda x: x.count("/"), reverse=True)
109 def _InstanceDir(cls, instance_name):
110 """Return the root directory for an instance.
113 return utils.PathJoin(cls._ROOT_DIR, instance_name)
115 def ListInstances(self):
116 """Get the list of running instances.
119 return [name for name in os.listdir(self._ROOT_DIR)
120 if self._IsDirLive(utils.PathJoin(self._ROOT_DIR, name))]
122 def GetInstanceInfo(self, instance_name):
123 """Get instance properties.
125 @type instance_name: string
126 @param instance_name: the instance name
128 @return: (name, id, memory, vcpus, stat, times)
131 dir_name = self._InstanceDir(instance_name)
132 if not self._IsDirLive(dir_name):
133 raise HypervisorError("Instance %s is not running" % instance_name)
134 return (instance_name, 0, 0, 0, 0, 0)
136 def GetAllInstancesInfo(self):
137 """Get properties of all instances.
139 @return: [(name, id, memory, vcpus, stat, times),...]
143 for file_name in os.listdir(self._ROOT_DIR):
144 path = utils.PathJoin(self._ROOT_DIR, file_name)
145 if self._IsDirLive(path):
146 data.append((file_name, 0, 0, 0, 0, 0))
149 def StartInstance(self, instance, block_devices):
150 """Start an instance.
152 For the chroot manager, we try to mount the block device and
153 execute '/ganeti-chroot start'.
156 root_dir = self._InstanceDir(instance.name)
157 if not os.path.exists(root_dir):
161 raise HypervisorError("Failed to start instance %s: %s" %
162 (instance.name, err))
163 if not os.path.isdir(root_dir):
164 raise HypervisorError("Needed path %s is not a directory" % root_dir)
166 if not os.path.ismount(root_dir):
167 if not block_devices:
168 raise HypervisorError("The chroot manager needs at least one disk")
170 sda_dev_path = block_devices[0][1]
171 result = utils.RunCmd(["mount", sda_dev_path, root_dir])
173 raise HypervisorError("Can't mount the chroot dir: %s" % result.output)
174 init_script = instance.hvparams[constants.HV_INIT_SCRIPT]
175 result = utils.RunCmd(["chroot", root_dir, init_script, "start"])
177 raise HypervisorError("Can't run the chroot start script: %s" %
180 def StopInstance(self, instance, force=False, retry=False):
183 This method has complicated cleanup tests, as we must:
184 - try to kill all leftover processes
185 - try to unmount any additional sub-mountpoints
186 - finally unmount the instance dir
189 root_dir = self._InstanceDir(instance.name)
190 if not os.path.exists(root_dir) or not self._IsDirLive(root_dir):
193 # Run the chroot stop script only once
194 if not retry and not force:
195 result = utils.RunCmd(["chroot", root_dir, "/ganeti-chroot", "stop"])
197 raise HypervisorError("Can't run the chroot stop script: %s" %
201 utils.RunCmd(["fuser", "-k", "-TERM", "-m", root_dir])
203 utils.RunCmd(["fuser", "-k", "-KILL", "-m", root_dir])
204 # 2 seconds at most should be enough for KILL to take action
207 if self._IsDirLive(root_dir):
209 raise HypervisorError("Can't stop the processes using the chroot")
212 for mpath in self._GetMountSubdirs(root_dir):
213 utils.RunCmd(["umount", mpath])
215 result = utils.RunCmd(["umount", root_dir])
216 if result.failed and force:
217 msg = ("Processes still alive in the chroot: %s" %
218 utils.RunCmd("fuser -vm %s" % root_dir).output)
220 raise HypervisorError("Can't umount the chroot dir: %s (%s)" %
221 (result.output, msg))
223 def RebootInstance(self, instance):
224 """Reboot an instance.
226 This is not (yet) implemented for the chroot manager.
229 raise HypervisorError("The chroot manager doesn't implement the"
230 " reboot functionality")
232 def GetNodeInfo(self):
233 """Return information about the node.
235 This is just a wrapper over the base GetLinuxNodeInfo method.
237 @return: a dict with the following keys (values in MiB):
238 - memory_total: the total memory size on the node
239 - memory_free: the available memory on the node for instances
240 - memory_dom0: the memory used by the node itself, if available
243 return self.GetLinuxNodeInfo()
246 def GetShellCommandForConsole(cls, instance, hvparams, beparams):
247 """Return a command for connecting to the console of an instance.
250 root_dir = cls._InstanceDir(instance.name)
251 if not os.path.ismount(root_dir):
252 raise HypervisorError("Instance %s is not running" % instance.name)
254 return "chroot %s" % root_dir
257 """Verify the hypervisor.
259 For the chroot manager, it just checks the existence of the base dir.
262 if not os.path.exists(self._ROOT_DIR):
263 return "The required directory '%s' does not exist." % self._ROOT_DIR
266 def PowercycleNode(cls):
267 """Chroot powercycle, just a wrapper over Linux powercycle.
270 cls.LinuxPowercycle()
272 def MigrateInstance(self, instance, target, live):
273 """Migrate an instance.
275 @type instance: L{objects.Instance}
276 @param instance: the instance to be migrated
278 @param target: hostname (usually ip) of the target node
280 @param live: whether to do a live or non-live migration
283 raise HypervisorError("Migration not supported by the chroot hypervisor")