1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 """Chroot manager hypervisor
32
33 """
34
35 import os
36 import os.path
37 import time
38 import logging
39
40 from ganeti import constants
41 from ganeti import errors
42 from ganeti import utils
43 from ganeti import objects
44 from ganeti import pathutils
45 from ganeti.hypervisor import hv_base
46 from ganeti.errors import HypervisorError
50 """Chroot manager.
51
52 This not-really hypervisor allows ganeti to manage chroots. It has
53 special behaviour and requirements on the OS definition and the node
54 environemnt:
55 - the start and stop of the chroot environment are done via a
56 script called ganeti-chroot located in the root directory of the
57 first drive, which should be created by the OS definition
58 - this script must accept the start and stop argument and, on
59 shutdown, it should cleanly shutdown the daemons/processes
60 using the chroot
61 - the daemons run in chroot should only bind to the instance IP
62 (to which the OS create script has access via the instance name)
63 - since some daemons in the node could be listening on the wildcard
64 address, some ports might be unavailable
65 - the instance listing will show no memory usage
66 - on shutdown, the chroot manager will try to find all mountpoints
67 under the root dir of the instance and unmount them
68 - instance alive check is based on whether any process is using the chroot
69
70 """
71 _ROOT_DIR = pathutils.RUN_DIR + "/chroot-hypervisor"
72
73 PARAMETERS = {
74 constants.HV_INIT_SCRIPT: (True, utils.IsNormAbsPath,
75 "must be an absolute normalized path",
76 None, None),
77 }
78
82
83 @staticmethod
85 """Check if a directory looks like a live chroot.
86
87 """
88 if not os.path.ismount(path):
89 return False
90 result = utils.RunCmd(["fuser", "-m", path])
91 return not result.failed
92
93 @staticmethod
95 """Return the list of mountpoints under a given path.
96
97 """
98 result = []
99 for _, mountpoint, _, _ in utils.GetMounts():
100 if (mountpoint.startswith(path) and
101 mountpoint != path):
102 result.append(mountpoint)
103
104 result.sort(key=lambda x: x.count("/"), reverse=True)
105 return result
106
107 @classmethod
109 """Return the root directory for an instance.
110
111 """
112 return utils.PathJoin(cls._ROOT_DIR, instance_name)
113
120
122 """Get instance properties.
123
124 @type instance_name: string
125 @param instance_name: the instance name
126 @type hvparams: dict of strings
127 @param hvparams: hvparams to be used with this instance
128
129 @return: (name, id, memory, vcpus, stat, times)
130
131 """
132 dir_name = self._InstanceDir(instance_name)
133 if not self._IsDirLive(dir_name):
134 raise HypervisorError("Instance %s is not running" % instance_name)
135 return (instance_name, 0, 0, 0, hv_base.HvInstanceState.RUNNING, 0)
136
138 """Get properties of all instances.
139
140 @type hvparams: dict of strings
141 @param hvparams: hypervisor parameter
142 @return: [(name, id, memory, vcpus, stat, times),...]
143
144 """
145 data = []
146 for file_name in os.listdir(self._ROOT_DIR):
147 path = utils.PathJoin(self._ROOT_DIR, file_name)
148 if self._IsDirLive(path):
149 data.append((file_name, 0, 0, 0, 0, 0))
150 return data
151
152 - def StartInstance(self, instance, block_devices, startup_paused):
153 """Start an instance.
154
155 For the chroot manager, we try to mount the block device and
156 execute '/ganeti-chroot start'.
157
158 """
159 root_dir = self._InstanceDir(instance.name)
160 if not os.path.exists(root_dir):
161 try:
162 os.mkdir(root_dir)
163 except IOError, err:
164 raise HypervisorError("Failed to start instance %s: %s" %
165 (instance.name, err))
166 if not os.path.isdir(root_dir):
167 raise HypervisorError("Needed path %s is not a directory" % root_dir)
168
169 if not os.path.ismount(root_dir):
170 if not block_devices:
171 raise HypervisorError("The chroot manager needs at least one disk")
172
173 sda_dev_path = block_devices[0][1]
174 result = utils.RunCmd(["mount", sda_dev_path, root_dir])
175 if result.failed:
176 raise HypervisorError("Can't mount the chroot dir: %s" % result.output)
177 init_script = instance.hvparams[constants.HV_INIT_SCRIPT]
178 result = utils.RunCmd(["chroot", root_dir, init_script, "start"])
179 if result.failed:
180 raise HypervisorError("Can't run the chroot start script: %s" %
181 result.output)
182
183 - def StopInstance(self, instance, force=False, retry=False, name=None,
184 timeout=None):
185 """Stop an instance.
186
187 This method has complicated cleanup tests, as we must:
188 - try to kill all leftover processes
189 - try to unmount any additional sub-mountpoints
190 - finally unmount the instance dir
191
192 """
193 assert(timeout is None or force is not None)
194
195 if name is None:
196 name = instance.name
197
198 root_dir = self._InstanceDir(name)
199 if not os.path.exists(root_dir) or not self._IsDirLive(root_dir):
200 return
201
202 timeout_cmd = []
203 if timeout is not None:
204 timeout_cmd.extend(["timeout", str(timeout)])
205
206
207 if not retry and not force:
208 result = utils.RunCmd(timeout_cmd.extend(["chroot", root_dir,
209 "/ganeti-chroot", "stop"]))
210 if result.failed:
211 raise HypervisorError("Can't run the chroot stop script: %s" %
212 result.output)
213
214 if not force:
215 utils.RunCmd(["fuser", "-k", "-TERM", "-m", root_dir])
216 else:
217 utils.RunCmd(["fuser", "-k", "-KILL", "-m", root_dir])
218
219 time.sleep(2)
220
221 if self._IsDirLive(root_dir):
222 if force:
223 raise HypervisorError("Can't stop the processes using the chroot")
224 return
225
227 """Cleanup after a stopped instance
228
229 """
230 root_dir = self._InstanceDir(instance_name)
231
232 if not os.path.exists(root_dir):
233 return
234
235 if self._IsDirLive(root_dir):
236 raise HypervisorError("Processes are still using the chroot")
237
238 for mpath in self._GetMountSubdirs(root_dir):
239 utils.RunCmd(["umount", mpath])
240
241 result = utils.RunCmd(["umount", root_dir])
242 if result.failed:
243 msg = ("Processes still alive in the chroot: %s" %
244 utils.RunCmd("fuser -vm %s" % root_dir).output)
245 logging.error(msg)
246 raise HypervisorError("Can't umount the chroot dir: %s (%s)" %
247 (result.output, msg))
248
250 """Reboot an instance.
251
252 This is not (yet) implemented for the chroot manager.
253
254 """
255 raise HypervisorError("The chroot manager doesn't implement the"
256 " reboot functionality")
257
259 """Balloon an instance memory to a certain value.
260
261 @type instance: L{objects.Instance}
262 @param instance: instance to be accepted
263 @type mem: int
264 @param mem: actual memory size to use for instance runtime
265
266 """
267
268 pass
269
271 """Return information about the node.
272
273 See L{BaseHypervisor.GetLinuxNodeInfo}.
274
275 """
276 return self.GetLinuxNodeInfo()
277
278 @classmethod
279 - def GetInstanceConsole(cls, instance, primary_node,
280 node_group, hvparams, beparams, root_dir=None):
296
297 - def Verify(self, hvparams=None):
298 """Verify the hypervisor.
299
300 For the chroot manager, it just checks the existence of the base dir.
301
302 @type hvparams: dict of strings
303 @param hvparams: hypervisor parameters to be verified against, not used
304 in for chroot
305
306 @return: Problem description if something is wrong, C{None} otherwise
307
308 """
309 if os.path.exists(self._ROOT_DIR):
310 return None
311 else:
312 return "The required directory '%s' does not exist" % self._ROOT_DIR
313
314 @classmethod
316 """Chroot powercycle, just a wrapper over Linux powercycle.
317
318 @type hvparams: dict of strings
319 @param hvparams: hypervisor params to be used on this node
320
321 """
322 cls.LinuxPowercycle()
323
325 """Migrate an instance.
326
327 @type cluster_name: string
328 @param cluster_name: name of the cluster
329 @type instance: L{objects.Instance}
330 @param instance: the instance to be migrated
331 @type target: string
332 @param target: hostname (usually ip) of the target node
333 @type live: boolean
334 @param live: whether to do a live or non-live migration
335
336 """
337 raise HypervisorError("Migration not supported by the chroot hypervisor")
338
340 """Get the migration status
341
342 @type instance: L{objects.Instance}
343 @param instance: the instance that is being migrated
344 @rtype: L{objects.MigrationStatus}
345 @return: the status of the current migration (one of
346 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
347 progress info that can be retrieved from the hypervisor
348
349 """
350 raise HypervisorError("Migration not supported by the chroot hypervisor")
351