1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """Chroot manager hypervisor
23
24 """
25
26 import os
27 import os.path
28 import time
29 import logging
30
31 from ganeti import constants
32 from ganeti import errors
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
41 """Chroot manager.
42
43 This not-really hypervisor allows ganeti to manage chroots. It has
44 special behaviour and requirements on the OS definition and the node
45 environemnt:
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
51 using the chroot
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
60
61 """
62 _ROOT_DIR = pathutils.RUN_DIR + "/chroot-hypervisor"
63
64 PARAMETERS = {
65 constants.HV_INIT_SCRIPT: (True, utils.IsNormAbsPath,
66 "must be an absolute normalized path",
67 None, None),
68 }
69
73
74 @staticmethod
76 """Check if a directory looks like a live chroot.
77
78 """
79 if not os.path.ismount(path):
80 return False
81 result = utils.RunCmd(["fuser", "-m", path])
82 return not result.failed
83
84 @staticmethod
86 """Return the list of mountpoints under a given path.
87
88 """
89 result = []
90 for _, mountpoint, _, _ in utils.GetMounts():
91 if (mountpoint.startswith(path) and
92 mountpoint != path):
93 result.append(mountpoint)
94
95 result.sort(key=lambda x: x.count("/"), reverse=True)
96 return result
97
98 @classmethod
100 """Return the root directory for an instance.
101
102 """
103 return utils.PathJoin(cls._ROOT_DIR, instance_name)
104
111
113 """Get instance properties.
114
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
119
120 @return: (name, id, memory, vcpus, stat, times)
121
122 """
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)
127
129 """Get properties of all instances.
130
131 @type hvparams: dict of strings
132 @param hvparams: hypervisor parameter
133 @return: [(name, id, memory, vcpus, stat, times),...]
134
135 """
136 data = []
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))
141 return data
142
143 - def StartInstance(self, instance, block_devices, startup_paused):
144 """Start an instance.
145
146 For the chroot manager, we try to mount the block device and
147 execute '/ganeti-chroot start'.
148
149 """
150 root_dir = self._InstanceDir(instance.name)
151 if not os.path.exists(root_dir):
152 try:
153 os.mkdir(root_dir)
154 except IOError, err:
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)
159
160 if not os.path.ismount(root_dir):
161 if not block_devices:
162 raise HypervisorError("The chroot manager needs at least one disk")
163
164 sda_dev_path = block_devices[0][1]
165 result = utils.RunCmd(["mount", sda_dev_path, root_dir])
166 if result.failed:
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"])
170 if result.failed:
171 raise HypervisorError("Can't run the chroot start script: %s" %
172 result.output)
173
174 - def StopInstance(self, instance, force=False, retry=False, name=None,
175 timeout=None):
176 """Stop an instance.
177
178 This method has complicated cleanup tests, as we must:
179 - try to kill all leftover processes
180 - try to unmount any additional sub-mountpoints
181 - finally unmount the instance dir
182
183 """
184 assert(timeout is None or force is not None)
185
186 if name is None:
187 name = instance.name
188
189 root_dir = self._InstanceDir(name)
190 if not os.path.exists(root_dir) or not self._IsDirLive(root_dir):
191 return
192
193 timeout_cmd = []
194 if timeout is not None:
195 timeout_cmd.extend(["timeout", str(timeout)])
196
197
198 if not retry and not force:
199 result = utils.RunCmd(timeout_cmd.extend(["chroot", root_dir,
200 "/ganeti-chroot", "stop"]))
201 if result.failed:
202 raise HypervisorError("Can't run the chroot stop script: %s" %
203 result.output)
204
205 if not force:
206 utils.RunCmd(["fuser", "-k", "-TERM", "-m", root_dir])
207 else:
208 utils.RunCmd(["fuser", "-k", "-KILL", "-m", root_dir])
209
210 time.sleep(2)
211
212 if self._IsDirLive(root_dir):
213 if force:
214 raise HypervisorError("Can't stop the processes using the chroot")
215 return
216
218 """Cleanup after a stopped instance
219
220 """
221 root_dir = self._InstanceDir(instance_name)
222
223 if not os.path.exists(root_dir):
224 return
225
226 if self._IsDirLive(root_dir):
227 raise HypervisorError("Processes are still using the chroot")
228
229 for mpath in self._GetMountSubdirs(root_dir):
230 utils.RunCmd(["umount", mpath])
231
232 result = utils.RunCmd(["umount", root_dir])
233 if result.failed:
234 msg = ("Processes still alive in the chroot: %s" %
235 utils.RunCmd("fuser -vm %s" % root_dir).output)
236 logging.error(msg)
237 raise HypervisorError("Can't umount the chroot dir: %s (%s)" %
238 (result.output, msg))
239
241 """Reboot an instance.
242
243 This is not (yet) implemented for the chroot manager.
244
245 """
246 raise HypervisorError("The chroot manager doesn't implement the"
247 " reboot functionality")
248
250 """Balloon an instance memory to a certain value.
251
252 @type instance: L{objects.Instance}
253 @param instance: instance to be accepted
254 @type mem: int
255 @param mem: actual memory size to use for instance runtime
256
257 """
258
259 pass
260
262 """Return information about the node.
263
264 See L{BaseHypervisor.GetLinuxNodeInfo}.
265
266 """
267 return self.GetLinuxNodeInfo()
268
269 @classmethod
270 - def GetInstanceConsole(cls, instance, primary_node,
271 hvparams, beparams, root_dir=None):
285
286 - def Verify(self, hvparams=None):
287 """Verify the hypervisor.
288
289 For the chroot manager, it just checks the existence of the base dir.
290
291 @type hvparams: dict of strings
292 @param hvparams: hypervisor parameters to be verified against, not used
293 in for chroot
294
295 @return: Problem description if something is wrong, C{None} otherwise
296
297 """
298 if os.path.exists(self._ROOT_DIR):
299 return None
300 else:
301 return "The required directory '%s' does not exist" % self._ROOT_DIR
302
303 @classmethod
305 """Chroot powercycle, just a wrapper over Linux powercycle.
306
307 @type hvparams: dict of strings
308 @param hvparams: hypervisor params to be used on this node
309
310 """
311 cls.LinuxPowercycle()
312
314 """Migrate an instance.
315
316 @type cluster_name: string
317 @param cluster_name: name of the cluster
318 @type instance: L{objects.Instance}
319 @param instance: the instance to be migrated
320 @type target: string
321 @param target: hostname (usually ip) of the target node
322 @type live: boolean
323 @param live: whether to do a live or non-live migration
324
325 """
326 raise HypervisorError("Migration not supported by the chroot hypervisor")
327
329 """Get the migration status
330
331 @type instance: L{objects.Instance}
332 @param instance: the instance that is being migrated
333 @rtype: L{objects.MigrationStatus}
334 @return: the status of the current migration (one of
335 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
336 progress info that can be retrieved from the hypervisor
337
338 """
339 raise HypervisorError("Migration not supported by the chroot hypervisor")
340