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