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.hypervisor import hv_base
35 from ganeti.errors import HypervisorError
39 """Chroot manager.
40
41 This not-really hypervisor allows ganeti to manage chroots. It has
42 special behaviour and requirements on the OS definition and the node
43 environemnt:
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
49 using the chroot
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
58
59 """
60 _ROOT_DIR = constants.RUN_GANETI_DIR + "/chroot-hypervisor"
61
62 PARAMETERS = {
63 constants.HV_INIT_SCRIPT: (True, utils.IsNormAbsPath,
64 "must be an absolute normalized path",
65 None, None),
66 }
67
71
72 @staticmethod
74 """Check if a directory looks like a live chroot.
75
76 """
77 if not os.path.ismount(path):
78 return False
79 result = utils.RunCmd(["fuser", "-m", path])
80 return not result.failed
81
82 @staticmethod
84 """Return the list of mountpoints under a given path.
85
86 """
87 result = []
88 for _, mountpoint, _, _ in utils.GetMounts():
89 if (mountpoint.startswith(path) and
90 mountpoint != path):
91 result.append(mountpoint)
92
93 result.sort(key=lambda x: x.count("/"), reverse=True)
94 return result
95
96 @classmethod
98 """Return the root directory for an instance.
99
100 """
101 return utils.PathJoin(cls._ROOT_DIR, instance_name)
102
109
111 """Get instance properties.
112
113 @type instance_name: string
114 @param instance_name: the instance name
115
116 @return: (name, id, memory, vcpus, stat, times)
117
118 """
119 dir_name = self._InstanceDir(instance_name)
120 if not self._IsDirLive(dir_name):
121 raise HypervisorError("Instance %s is not running" % instance_name)
122 return (instance_name, 0, 0, 0, 0, 0)
123
125 """Get properties of all instances.
126
127 @return: [(name, id, memory, vcpus, stat, times),...]
128
129 """
130 data = []
131 for file_name in os.listdir(self._ROOT_DIR):
132 path = utils.PathJoin(self._ROOT_DIR, file_name)
133 if self._IsDirLive(path):
134 data.append((file_name, 0, 0, 0, 0, 0))
135 return data
136
138 """Start an instance.
139
140 For the chroot manager, we try to mount the block device and
141 execute '/ganeti-chroot start'.
142
143 """
144 root_dir = self._InstanceDir(instance.name)
145 if not os.path.exists(root_dir):
146 try:
147 os.mkdir(root_dir)
148 except IOError, err:
149 raise HypervisorError("Failed to start instance %s: %s" %
150 (instance.name, err))
151 if not os.path.isdir(root_dir):
152 raise HypervisorError("Needed path %s is not a directory" % root_dir)
153
154 if not os.path.ismount(root_dir):
155 if not block_devices:
156 raise HypervisorError("The chroot manager needs at least one disk")
157
158 sda_dev_path = block_devices[0][1]
159 result = utils.RunCmd(["mount", sda_dev_path, root_dir])
160 if result.failed:
161 raise HypervisorError("Can't mount the chroot dir: %s" % result.output)
162 init_script = instance.hvparams[constants.HV_INIT_SCRIPT]
163 result = utils.RunCmd(["chroot", root_dir, init_script, "start"])
164 if result.failed:
165 raise HypervisorError("Can't run the chroot start script: %s" %
166 result.output)
167
168 - def StopInstance(self, instance, force=False, retry=False, name=None):
169 """Stop an instance.
170
171 This method has complicated cleanup tests, as we must:
172 - try to kill all leftover processes
173 - try to unmount any additional sub-mountpoints
174 - finally unmount the instance dir
175
176 """
177 if name is None:
178 name = instance.name
179
180 root_dir = self._InstanceDir(name)
181 if not os.path.exists(root_dir) or not self._IsDirLive(root_dir):
182 return
183
184
185 if not retry and not force:
186 result = utils.RunCmd(["chroot", root_dir, "/ganeti-chroot", "stop"])
187 if result.failed:
188 raise HypervisorError("Can't run the chroot stop script: %s" %
189 result.output)
190
191 if not force:
192 utils.RunCmd(["fuser", "-k", "-TERM", "-m", root_dir])
193 else:
194 utils.RunCmd(["fuser", "-k", "-KILL", "-m", root_dir])
195
196 time.sleep(2)
197
198 if self._IsDirLive(root_dir):
199 if force:
200 raise HypervisorError("Can't stop the processes using the chroot")
201 return
202
204 """Cleanup after a stopped instance
205
206 """
207 root_dir = self._InstanceDir(instance_name)
208
209 if not os.path.exists(root_dir):
210 return
211
212 if self._IsDirLive(root_dir):
213 raise HypervisorError("Processes are still using the chroot")
214
215 for mpath in self._GetMountSubdirs(root_dir):
216 utils.RunCmd(["umount", mpath])
217
218 result = utils.RunCmd(["umount", root_dir])
219 if result.failed:
220 msg = ("Processes still alive in the chroot: %s" %
221 utils.RunCmd("fuser -vm %s" % root_dir).output)
222 logging.error(msg)
223 raise HypervisorError("Can't umount the chroot dir: %s (%s)" %
224 (result.output, msg))
225
227 """Reboot an instance.
228
229 This is not (yet) implemented for the chroot manager.
230
231 """
232 raise HypervisorError("The chroot manager doesn't implement the"
233 " reboot functionality")
234
236 """Return information about the node.
237
238 This is just a wrapper over the base GetLinuxNodeInfo method.
239
240 @return: a dict with the following keys (values in MiB):
241 - memory_total: the total memory size on the node
242 - memory_free: the available memory on the node for instances
243 - memory_dom0: the memory used by the node itself, if available
244
245 """
246 return self.GetLinuxNodeInfo()
247
248 @classmethod
250 """Return a command for connecting to the console of an instance.
251
252 """
253 root_dir = cls._InstanceDir(instance.name)
254 if not os.path.ismount(root_dir):
255 raise HypervisorError("Instance %s is not running" % instance.name)
256
257 return "chroot %s" % root_dir
258
260 """Verify the hypervisor.
261
262 For the chroot manager, it just checks the existence of the base dir.
263
264 """
265 if not os.path.exists(self._ROOT_DIR):
266 return "The required directory '%s' does not exist." % self._ROOT_DIR
267
268 @classmethod
270 """Chroot powercycle, just a wrapper over Linux powercycle.
271
272 """
273 cls.LinuxPowercycle()
274
276 """Migrate an instance.
277
278 @type instance: L{objects.Instance}
279 @param instance: the instance to be migrated
280 @type target: string
281 @param target: hostname (usually ip) of the target node
282 @type live: boolean
283 @param live: whether to do a live or non-live migration
284
285 """
286 raise HypervisorError("Migration not supported by the chroot hypervisor")
287