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
30 from cStringIO import StringIO
32 from ganeti import constants
33 from ganeti import errors
34 from ganeti import utils
35 from ganeti.hypervisor import hv_base
36 from ganeti.errors import HypervisorError
39 class ChrootManager(hv_base.BaseHypervisor):
42 This not-really hypervisor allows ganeti to manage chroots. It has
43 special behaviour and requirements on the OS definition and the node
45 - the start and stop of the chroot environment are done via a
46 script called ganeti-chroot located in the root directory of the
47 first drive, which should be created by the OS definition
48 - this script must accept the start and stop argument and, on
49 shutdown, it should cleanly shutdown the daemons/processes
51 - the daemons run in chroot should only bind to the instance IP
52 (to which the OS create script has access via the instance name)
53 - since some daemons in the node could be listening on the wildcard
54 address, some ports might be unavailable
55 - the instance listing will show no memory usage
56 - on shutdown, the chroot manager will try to find all mountpoints
57 under the root dir of the instance and unmount them
58 - instance alive check is based on whether any process is using the chroot
61 _ROOT_DIR = constants.RUN_GANETI_DIR + "/chroot-hypervisor"
64 constants.HV_INIT_SCRIPT: (True, utils.IsNormAbsPath,
65 "must be an absolute normalized path",
70 hv_base.BaseHypervisor.__init__(self)
71 if not os.path.exists(self._ROOT_DIR):
72 os.mkdir(self._ROOT_DIR)
73 if not os.path.isdir(self._ROOT_DIR):
74 raise HypervisorError("Needed path %s is not a directory" %
79 """Check if a directory looks like a live chroot.
82 if not os.path.ismount(path):
84 result = utils.RunCmd(["fuser", "-m", path])
85 return not result.failed
88 def _GetMountSubdirs(path):
89 """Return the list of mountpoints under a given path.
91 This function is Linux-specific.
94 #TODO(iustin): investigate and document non-linux options
95 #(e.g. via mount output)
97 fh = open("/proc/mounts", "r")
100 _, mountpoint, _ = line.split(" ", 2)
101 if (mountpoint.startswith(path) and
103 data.append(mountpoint)
106 data.sort(key=lambda x: x.count("/"), reverse=True)
109 def ListInstances(self):
110 """Get the list of running instances.
113 return [name for name in os.listdir(self._ROOT_DIR)
114 if self._IsDirLive(os.path.join(self._ROOT_DIR, name))]
116 def GetInstanceInfo(self, instance_name):
117 """Get instance properties.
119 @type instance_name: string
120 @param instance_name: the instance name
122 @return: (name, id, memory, vcpus, stat, times)
125 dir_name = "%s/%s" % (self._ROOT_DIR, instance_name)
126 if not self._IsDirLive(dir_name):
127 raise HypervisorError("Instance %s is not running" % instance_name)
128 return (instance_name, 0, 0, 0, 0, 0)
130 def GetAllInstancesInfo(self):
131 """Get properties of all instances.
133 @return: [(name, id, memory, vcpus, stat, times),...]
137 for file_name in os.listdir(self._ROOT_DIR):
138 path = os.path.join(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):
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 = "%s/%s" % (self._ROOT_DIR, 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):
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
183 root_dir = "%s/%s" % (self._ROOT_DIR, instance.name)
184 if not os.path.exists(root_dir) or not self._IsDirLive(root_dir):
187 # Run the chroot stop script only once
188 if not retry and not force:
189 result = utils.RunCmd(["chroot", root_dir, "/ganeti-chroot", "stop"])
191 raise HypervisorError("Can't run the chroot stop script: %s" %
195 utils.RunCmd(["fuser", "-k", "-TERM", "-m", root_dir])
197 utils.RunCmd(["fuser", "-k", "-KILL", "-m", root_dir])
198 # 2 seconds at most should be enough for KILL to take action
201 if self._IsDirLive(root_dir):
203 raise HypervisorError("Can't stop the processes using the chroot")
206 for mpath in self._GetMountSubdirs(root_dir):
207 utils.RunCmd(["umount", mpath])
209 result = utils.RunCmd(["umount", root_dir])
210 if result.failed and force:
211 msg = ("Processes still alive in the chroot: %s" %
212 utils.RunCmd("fuser -vm %s" % root_dir).output)
214 raise HypervisorError("Can't umount the chroot dir: %s (%s)" %
215 (result.output, msg))
217 def RebootInstance(self, instance):
218 """Reboot an instance.
220 This is not (yet) implemented for the chroot manager.
223 raise HypervisorError("The chroot manager doesn't implement the"
224 " reboot functionality")
226 def GetNodeInfo(self):
227 """Return information about the node.
229 This is just a wrapper over the base GetLinuxNodeInfo method.
231 @return: a dict with the following keys (values in MiB):
232 - memory_total: the total memory size on the node
233 - memory_free: the available memory on the node for instances
234 - memory_dom0: the memory used by the node itself, if available
237 return self.GetLinuxNodeInfo()
240 def GetShellCommandForConsole(cls, instance, hvparams, beparams):
241 """Return a command for connecting to the console of an instance.
244 root_dir = "%s/%s" % (cls._ROOT_DIR, instance.name)
245 if not os.path.ismount(root_dir):
246 raise HypervisorError("Instance %s is not running" % instance.name)
248 return "chroot %s" % root_dir
251 """Verify the hypervisor.
253 For the chroot manager, it just checks the existence of the base dir.
256 if not os.path.exists(self._ROOT_DIR):
257 return "The required directory '%s' does not exist." % self._ROOT_DIR