Remove many 'Unused variable' warnings
[ganeti-local] / lib / hypervisor / hv_chroot.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2009 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 from cStringIO import StringIO
31
32 from ganeti import constants
33 from ganeti import errors
34 from ganeti import utils
35 from ganeti.hypervisor import hv_base
36 from ganeti.errors import HypervisorError
37
38
39 class ChrootManager(hv_base.BaseHypervisor):
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
69   def __init__(self):
70     hv_base.BaseHypervisor.__init__(self)
71     if not os.path.exists(self._ROOT_DIR):
72       os.mkdir(self._ROOT_DIR)
73     if not os.path.isdir(self._ROOT_DIR):
74       raise HypervisorError("Needed path %s is not a directory" %
75                             self._ROOT_DIR)
76
77   @staticmethod
78   def _IsDirLive(path):
79     """Check if a directory looks like a live chroot.
80
81     """
82     if not os.path.ismount(path):
83       return False
84     result = utils.RunCmd(["fuser", "-m", path])
85     return not result.failed
86
87   @staticmethod
88   def _GetMountSubdirs(path):
89     """Return the list of mountpoints under a given path.
90
91     This function is Linux-specific.
92
93     """
94     #TODO(iustin): investigate and document non-linux options
95     #(e.g. via mount output)
96     data = []
97     fh = open("/proc/mounts", "r")
98     try:
99       for line in fh:
100         _, mountpoint, _ = line.split(" ", 2)
101         if (mountpoint.startswith(path) and
102             mountpoint != path):
103           data.append(mountpoint)
104     finally:
105       fh.close()
106     data.sort(key=lambda x: x.count("/"), reverse=True)
107     return data
108
109   def ListInstances(self):
110     """Get the list of running instances.
111
112     """
113     return [name for name in os.listdir(self._ROOT_DIR)
114             if self._IsDirLive(os.path.join(self._ROOT_DIR, name))]
115
116   def GetInstanceInfo(self, instance_name):
117     """Get instance properties.
118
119     @type instance_name: string
120     @param instance_name: the instance name
121
122     @return: (name, id, memory, vcpus, stat, times)
123
124     """
125     dir_name = "%s/%s" % (self._ROOT_DIR, instance_name)
126     if not self._IsDirLive(dir_name):
127       raise HypervisorError("Instance %s is not running" % instance_name)
128     return (instance_name, 0, 0, 0, 0, 0)
129
130   def GetAllInstancesInfo(self):
131     """Get properties of all instances.
132
133     @return: [(name, id, memory, vcpus, stat, times),...]
134
135     """
136     data = []
137     for file_name in os.listdir(self._ROOT_DIR):
138       path = os.path.join(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):
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 = "%s/%s" % (self._ROOT_DIR, 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):
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     root_dir = "%s/%s" % (self._ROOT_DIR, instance.name)
184     if not os.path.exists(root_dir) or not self._IsDirLive(root_dir):
185       return
186
187     # Run the chroot stop script only once
188     if not retry and not force:
189       result = utils.RunCmd(["chroot", root_dir, "/ganeti-chroot", "stop"])
190       if result.failed:
191         raise HypervisorError("Can't run the chroot stop script: %s" %
192                               result.output)
193
194     if not force:
195       utils.RunCmd(["fuser", "-k", "-TERM", "-m", root_dir])
196     else:
197       utils.RunCmd(["fuser", "-k", "-KILL", "-m", root_dir])
198       # 2 seconds at most should be enough for KILL to take action
199       time.sleep(2)
200
201     if self._IsDirLive(root_dir):
202       if force:
203         raise HypervisorError("Can't stop the processes using the chroot")
204       return
205
206     for mpath in self._GetMountSubdirs(root_dir):
207       utils.RunCmd(["umount", mpath])
208
209     result = utils.RunCmd(["umount", root_dir])
210     if result.failed and force:
211       msg = ("Processes still alive in the chroot: %s" %
212              utils.RunCmd("fuser -vm %s" % root_dir).output)
213       logging.error(msg)
214       raise HypervisorError("Can't umount the chroot dir: %s (%s)" %
215                             (result.output, msg))
216
217   def RebootInstance(self, instance):
218     """Reboot an instance.
219
220     This is not (yet) implemented for the chroot manager.
221
222     """
223     raise HypervisorError("The chroot manager doesn't implement the"
224                           " reboot functionality")
225
226   def GetNodeInfo(self):
227     """Return information about the node.
228
229     This is just a wrapper over the base GetLinuxNodeInfo method.
230
231     @return: a dict with the following keys (values in MiB):
232           - memory_total: the total memory size on the node
233           - memory_free: the available memory on the node for instances
234           - memory_dom0: the memory used by the node itself, if available
235
236     """
237     return self.GetLinuxNodeInfo()
238
239   @classmethod
240   def GetShellCommandForConsole(cls, instance, hvparams, beparams):
241     """Return a command for connecting to the console of an instance.
242
243     """
244     root_dir = "%s/%s" % (cls._ROOT_DIR, instance.name)
245     if not os.path.ismount(root_dir):
246       raise HypervisorError("Instance %s is not running" % instance.name)
247
248     return "chroot %s" % root_dir
249
250   def Verify(self):
251     """Verify the hypervisor.
252
253     For the chroot manager, it just checks the existence of the base dir.
254
255     """
256     if not os.path.exists(self._ROOT_DIR):
257       return "The required directory '%s' does not exist." % self._ROOT_DIR