Check DRBD status on verify-disks
[ganeti-local] / lib / hypervisor / hv_chroot.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2009, 2013 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
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 # pylint: disable=W0611
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
38
39
40 class ChrootManager(hv_base.BaseHypervisor):
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
70   def __init__(self):
71     hv_base.BaseHypervisor.__init__(self)
72     utils.EnsureDirs([(self._ROOT_DIR, constants.RUN_DIRS_MODE)])
73
74   @staticmethod
75   def _IsDirLive(path):
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
85   def _GetMountSubdirs(path):
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
99   def _InstanceDir(cls, instance_name):
100     """Return the root directory for an instance.
101
102     """
103     return utils.PathJoin(cls._ROOT_DIR, instance_name)
104
105   def ListInstances(self, hvparams=None):
106     """Get the list of running instances.
107
108     """
109     return [name for name in os.listdir(self._ROOT_DIR)
110             if self._IsDirLive(utils.PathJoin(self._ROOT_DIR, name))]
111
112   def GetInstanceInfo(self, instance_name, hvparams=None):
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
128   def GetAllInstancesInfo(self, hvparams=None):
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     """Stop an instance.
176
177     This method has complicated cleanup tests, as we must:
178       - try to kill all leftover processes
179       - try to unmount any additional sub-mountpoints
180       - finally unmount the instance dir
181
182     """
183     if name is None:
184       name = instance.name
185
186     root_dir = self._InstanceDir(name)
187     if not os.path.exists(root_dir) or not self._IsDirLive(root_dir):
188       return
189
190     # Run the chroot stop script only once
191     if not retry and not force:
192       result = utils.RunCmd(["chroot", root_dir, "/ganeti-chroot", "stop"])
193       if result.failed:
194         raise HypervisorError("Can't run the chroot stop script: %s" %
195                               result.output)
196
197     if not force:
198       utils.RunCmd(["fuser", "-k", "-TERM", "-m", root_dir])
199     else:
200       utils.RunCmd(["fuser", "-k", "-KILL", "-m", root_dir])
201       # 2 seconds at most should be enough for KILL to take action
202       time.sleep(2)
203
204     if self._IsDirLive(root_dir):
205       if force:
206         raise HypervisorError("Can't stop the processes using the chroot")
207       return
208
209   def CleanupInstance(self, instance_name):
210     """Cleanup after a stopped instance
211
212     """
213     root_dir = self._InstanceDir(instance_name)
214
215     if not os.path.exists(root_dir):
216       return
217
218     if self._IsDirLive(root_dir):
219       raise HypervisorError("Processes are still using the chroot")
220
221     for mpath in self._GetMountSubdirs(root_dir):
222       utils.RunCmd(["umount", mpath])
223
224     result = utils.RunCmd(["umount", root_dir])
225     if result.failed:
226       msg = ("Processes still alive in the chroot: %s" %
227              utils.RunCmd("fuser -vm %s" % root_dir).output)
228       logging.error(msg)
229       raise HypervisorError("Can't umount the chroot dir: %s (%s)" %
230                             (result.output, msg))
231
232   def RebootInstance(self, instance):
233     """Reboot an instance.
234
235     This is not (yet) implemented for the chroot manager.
236
237     """
238     raise HypervisorError("The chroot manager doesn't implement the"
239                           " reboot functionality")
240
241   def BalloonInstanceMemory(self, instance, mem):
242     """Balloon an instance memory to a certain value.
243
244     @type instance: L{objects.Instance}
245     @param instance: instance to be accepted
246     @type mem: int
247     @param mem: actual memory size to use for instance runtime
248
249     """
250     # Currently chroots don't have memory limits
251     pass
252
253   def GetNodeInfo(self, hvparams=None):
254     """Return information about the node.
255
256     See L{BaseHypervisor.GetLinuxNodeInfo}.
257
258     """
259     return self.GetLinuxNodeInfo()
260
261   @classmethod
262   def GetInstanceConsole(cls, instance, primary_node, # pylint: disable=W0221
263                          hvparams, beparams, root_dir=None):
264     """Return information for connecting to the console of an instance.
265
266     """
267     if root_dir is None:
268       root_dir = cls._InstanceDir(instance.name)
269       if not os.path.ismount(root_dir):
270         raise HypervisorError("Instance %s is not running" % instance.name)
271
272     return objects.InstanceConsole(instance=instance.name,
273                                    kind=constants.CONS_SSH,
274                                    host=primary_node.name,
275                                    user=constants.SSH_CONSOLE_USER,
276                                    command=["chroot", root_dir])
277
278   def Verify(self, hvparams=None):
279     """Verify the hypervisor.
280
281     For the chroot manager, it just checks the existence of the base dir.
282
283     @type hvparams: dict of strings
284     @param hvparams: hypervisor parameters to be verified against, not used
285       in for chroot
286
287     @return: Problem description if something is wrong, C{None} otherwise
288
289     """
290     if os.path.exists(self._ROOT_DIR):
291       return None
292     else:
293       return "The required directory '%s' does not exist" % self._ROOT_DIR
294
295   @classmethod
296   def PowercycleNode(cls, hvparams=None):
297     """Chroot powercycle, just a wrapper over Linux powercycle.
298
299     @type hvparams: dict of strings
300     @param hvparams: hypervisor params to be used on this node
301
302     """
303     cls.LinuxPowercycle()
304
305   def MigrateInstance(self, cluster_name, instance, target, live):
306     """Migrate an instance.
307
308     @type cluster_name: string
309     @param cluster_name: name of the cluster
310     @type instance: L{objects.Instance}
311     @param instance: the instance to be migrated
312     @type target: string
313     @param target: hostname (usually ip) of the target node
314     @type live: boolean
315     @param live: whether to do a live or non-live migration
316
317     """
318     raise HypervisorError("Migration not supported by the chroot hypervisor")
319
320   def GetMigrationStatus(self, instance):
321     """Get the migration status
322
323     @type instance: L{objects.Instance}
324     @param instance: the instance that is being migrated
325     @rtype: L{objects.MigrationStatus}
326     @return: the status of the current migration (one of
327              L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
328              progress info that can be retrieved from the hypervisor
329
330     """
331     raise HypervisorError("Migration not supported by the chroot hypervisor")