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
118 @return: (name, id, memory, vcpus, stat, times)
119
120 """
121 dir_name = self._InstanceDir(instance_name)
122 if not self._IsDirLive(dir_name):
123 raise HypervisorError("Instance %s is not running" % instance_name)
124 return (instance_name, 0, 0, 0, 0, 0)
125
127 """Get properties of all instances.
128
129 @return: [(name, id, memory, vcpus, stat, times),...]
130
131 """
132 data = []
133 for file_name in os.listdir(self._ROOT_DIR):
134 path = utils.PathJoin(self._ROOT_DIR, file_name)
135 if self._IsDirLive(path):
136 data.append((file_name, 0, 0, 0, 0, 0))
137 return data
138
139 - def StartInstance(self, instance, block_devices, startup_paused):
140 """Start an instance.
141
142 For the chroot manager, we try to mount the block device and
143 execute '/ganeti-chroot start'.
144
145 """
146 root_dir = self._InstanceDir(instance.name)
147 if not os.path.exists(root_dir):
148 try:
149 os.mkdir(root_dir)
150 except IOError, err:
151 raise HypervisorError("Failed to start instance %s: %s" %
152 (instance.name, err))
153 if not os.path.isdir(root_dir):
154 raise HypervisorError("Needed path %s is not a directory" % root_dir)
155
156 if not os.path.ismount(root_dir):
157 if not block_devices:
158 raise HypervisorError("The chroot manager needs at least one disk")
159
160 sda_dev_path = block_devices[0][1]
161 result = utils.RunCmd(["mount", sda_dev_path, root_dir])
162 if result.failed:
163 raise HypervisorError("Can't mount the chroot dir: %s" % result.output)
164 init_script = instance.hvparams[constants.HV_INIT_SCRIPT]
165 result = utils.RunCmd(["chroot", root_dir, init_script, "start"])
166 if result.failed:
167 raise HypervisorError("Can't run the chroot start script: %s" %
168 result.output)
169
170 - def StopInstance(self, instance, force=False, retry=False, name=None):
171 """Stop an instance.
172
173 This method has complicated cleanup tests, as we must:
174 - try to kill all leftover processes
175 - try to unmount any additional sub-mountpoints
176 - finally unmount the instance dir
177
178 """
179 if name is None:
180 name = instance.name
181
182 root_dir = self._InstanceDir(name)
183 if not os.path.exists(root_dir) or not self._IsDirLive(root_dir):
184 return
185
186
187 if not retry and not force:
188 result = utils.RunCmd(["chroot", root_dir, "/ganeti-chroot", "stop"])
189 if result.failed:
190 raise HypervisorError("Can't run the chroot stop script: %s" %
191 result.output)
192
193 if not force:
194 utils.RunCmd(["fuser", "-k", "-TERM", "-m", root_dir])
195 else:
196 utils.RunCmd(["fuser", "-k", "-KILL", "-m", root_dir])
197
198 time.sleep(2)
199
200 if self._IsDirLive(root_dir):
201 if force:
202 raise HypervisorError("Can't stop the processes using the chroot")
203 return
204
206 """Cleanup after a stopped instance
207
208 """
209 root_dir = self._InstanceDir(instance_name)
210
211 if not os.path.exists(root_dir):
212 return
213
214 if self._IsDirLive(root_dir):
215 raise HypervisorError("Processes are still using the chroot")
216
217 for mpath in self._GetMountSubdirs(root_dir):
218 utils.RunCmd(["umount", mpath])
219
220 result = utils.RunCmd(["umount", root_dir])
221 if result.failed:
222 msg = ("Processes still alive in the chroot: %s" %
223 utils.RunCmd("fuser -vm %s" % root_dir).output)
224 logging.error(msg)
225 raise HypervisorError("Can't umount the chroot dir: %s (%s)" %
226 (result.output, msg))
227
229 """Reboot an instance.
230
231 This is not (yet) implemented for the chroot manager.
232
233 """
234 raise HypervisorError("The chroot manager doesn't implement the"
235 " reboot functionality")
236
238 """Balloon an instance memory to a certain value.
239
240 @type instance: L{objects.Instance}
241 @param instance: instance to be accepted
242 @type mem: int
243 @param mem: actual memory size to use for instance runtime
244
245 """
246
247 pass
248
250 """Return information about the node.
251
252 This is just a wrapper over the base GetLinuxNodeInfo method.
253
254 @return: a dict with the following keys (values in MiB):
255 - memory_total: the total memory size on the node
256 - memory_free: the available memory on the node for instances
257 - memory_dom0: the memory used by the node itself, if available
258
259 """
260 return self.GetLinuxNodeInfo()
261
262 @classmethod
278
280 """Verify the hypervisor.
281
282 For the chroot manager, it just checks the existence of the base dir.
283
284 @return: Problem description if something is wrong, C{None} otherwise
285
286 """
287 if os.path.exists(self._ROOT_DIR):
288 return None
289 else:
290 return "The required directory '%s' does not exist" % self._ROOT_DIR
291
292 @classmethod
294 """Chroot powercycle, just a wrapper over Linux powercycle.
295
296 """
297 cls.LinuxPowercycle()
298
300 """Migrate an instance.
301
302 @type instance: L{objects.Instance}
303 @param instance: the instance to be migrated
304 @type target: string
305 @param target: hostname (usually ip) of the target node
306 @type live: boolean
307 @param live: whether to do a live or non-live migration
308
309 """
310 raise HypervisorError("Migration not supported by the chroot hypervisor")
311
313 """Get the migration status
314
315 @type instance: L{objects.Instance}
316 @param instance: the instance that is being migrated
317 @rtype: L{objects.MigrationStatus}
318 @return: the status of the current migration (one of
319 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
320 progress info that can be retrieved from the hypervisor
321
322 """
323 raise HypervisorError("Migration not supported by the chroot hypervisor")
324