Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_chroot.py @ 46952329

History | View | Annotate | Download (8.5 kB)

1 48297fa2 Iustin Pop
#
2 48297fa2 Iustin Pop
#
3 48297fa2 Iustin Pop
4 48297fa2 Iustin Pop
# Copyright (C) 2006, 2007, 2008, 2009 Google Inc.
5 48297fa2 Iustin Pop
#
6 48297fa2 Iustin Pop
# This program is free software; you can redistribute it and/or modify
7 48297fa2 Iustin Pop
# it under the terms of the GNU General Public License as published by
8 48297fa2 Iustin Pop
# the Free Software Foundation; either version 2 of the License, or
9 48297fa2 Iustin Pop
# (at your option) any later version.
10 48297fa2 Iustin Pop
#
11 48297fa2 Iustin Pop
# This program is distributed in the hope that it will be useful, but
12 48297fa2 Iustin Pop
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 48297fa2 Iustin Pop
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 48297fa2 Iustin Pop
# General Public License for more details.
15 48297fa2 Iustin Pop
#
16 48297fa2 Iustin Pop
# You should have received a copy of the GNU General Public License
17 48297fa2 Iustin Pop
# along with this program; if not, write to the Free Software
18 48297fa2 Iustin Pop
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 48297fa2 Iustin Pop
# 02110-1301, USA.
20 48297fa2 Iustin Pop
21 48297fa2 Iustin Pop
22 48297fa2 Iustin Pop
"""Chroot manager hypervisor
23 48297fa2 Iustin Pop

24 48297fa2 Iustin Pop
"""
25 48297fa2 Iustin Pop
26 48297fa2 Iustin Pop
import os
27 48297fa2 Iustin Pop
import os.path
28 48297fa2 Iustin Pop
import time
29 48297fa2 Iustin Pop
import logging
30 48297fa2 Iustin Pop
from cStringIO import StringIO
31 48297fa2 Iustin Pop
32 48297fa2 Iustin Pop
from ganeti import constants
33 48297fa2 Iustin Pop
from ganeti import errors
34 48297fa2 Iustin Pop
from ganeti import utils
35 48297fa2 Iustin Pop
from ganeti.hypervisor import hv_base
36 48297fa2 Iustin Pop
from ganeti.errors import HypervisorError
37 48297fa2 Iustin Pop
38 48297fa2 Iustin Pop
39 48297fa2 Iustin Pop
class ChrootManager(hv_base.BaseHypervisor):
40 48297fa2 Iustin Pop
  """Chroot manager.
41 48297fa2 Iustin Pop

42 48297fa2 Iustin Pop
  This not-really hypervisor allows ganeti to manage chroots. It has
43 48297fa2 Iustin Pop
  special behaviour and requirements on the OS definition and the node
44 48297fa2 Iustin Pop
  environemnt:
45 48297fa2 Iustin Pop
    - the start and stop of the chroot environment are done via a
46 48297fa2 Iustin Pop
      script called ganeti-chroot located in the root directory of the
47 48297fa2 Iustin Pop
      first drive, which should be created by the OS definition
48 48297fa2 Iustin Pop
    - this script must accept the start and stop argument and, on
49 48297fa2 Iustin Pop
      shutdown, it should cleanly shutdown the daemons/processes
50 48297fa2 Iustin Pop
      using the chroot
51 48297fa2 Iustin Pop
    - the daemons run in chroot should only bind to the instance IP
52 48297fa2 Iustin Pop
      (to which the OS create script has access via the instance name)
53 48297fa2 Iustin Pop
    - since some daemons in the node could be listening on the wildcard
54 48297fa2 Iustin Pop
      address, some ports might be unavailable
55 48297fa2 Iustin Pop
    - the instance listing will show no memory usage
56 48297fa2 Iustin Pop
    - on shutdown, the chroot manager will try to find all mountpoints
57 48297fa2 Iustin Pop
      under the root dir of the instance and unmount them
58 48297fa2 Iustin Pop
    - instance alive check is based on whether any process is using the chroot
59 48297fa2 Iustin Pop

60 48297fa2 Iustin Pop
  """
61 48297fa2 Iustin Pop
  _ROOT_DIR = constants.RUN_GANETI_DIR + "/chroot-hypervisor"
62 48297fa2 Iustin Pop
63 46952329 Guido Trotter
  PARAMETERS = {
64 46952329 Guido Trotter
    constants.HV_INIT_SCRIPT: (True, utils.IsNormAbsPath,
65 46952329 Guido Trotter
                               "must be an absolute normalized path",
66 46952329 Guido Trotter
                               None, None)
67 46952329 Guido Trotter
    }
68 48297fa2 Iustin Pop
69 48297fa2 Iustin Pop
  def __init__(self):
70 48297fa2 Iustin Pop
    hv_base.BaseHypervisor.__init__(self)
71 48297fa2 Iustin Pop
    if not os.path.exists(self._ROOT_DIR):
72 48297fa2 Iustin Pop
      os.mkdir(self._ROOT_DIR)
73 48297fa2 Iustin Pop
    if not os.path.isdir(self._ROOT_DIR):
74 48297fa2 Iustin Pop
      raise HypervisorError("Needed path %s is not a directory" %
75 48297fa2 Iustin Pop
                            self._ROOT_DIR)
76 48297fa2 Iustin Pop
77 48297fa2 Iustin Pop
  @staticmethod
78 48297fa2 Iustin Pop
  def _IsDirLive(path):
79 48297fa2 Iustin Pop
    """Check if a directory looks like a live chroot.
80 48297fa2 Iustin Pop

81 48297fa2 Iustin Pop
    """
82 48297fa2 Iustin Pop
    if not os.path.ismount(path):
83 48297fa2 Iustin Pop
      return False
84 48297fa2 Iustin Pop
    result = utils.RunCmd(["fuser", "-m", path])
85 48297fa2 Iustin Pop
    return not result.failed
86 48297fa2 Iustin Pop
87 48297fa2 Iustin Pop
  @staticmethod
88 48297fa2 Iustin Pop
  def _GetMountSubdirs(path):
89 48297fa2 Iustin Pop
    """Return the list of mountpoints under a given path.
90 48297fa2 Iustin Pop

91 48297fa2 Iustin Pop
    This function is Linux-specific.
92 48297fa2 Iustin Pop

93 48297fa2 Iustin Pop
    """
94 48297fa2 Iustin Pop
    #TODO(iustin): investigate and document non-linux options
95 48297fa2 Iustin Pop
    #(e.g. via mount output)
96 48297fa2 Iustin Pop
    data = []
97 48297fa2 Iustin Pop
    fh = open("/proc/mounts", "r")
98 48297fa2 Iustin Pop
    try:
99 48297fa2 Iustin Pop
      for line in fh:
100 48297fa2 Iustin Pop
        fstype, mountpoint, rest = line.split(" ", 2)
101 48297fa2 Iustin Pop
        if (mountpoint.startswith(path) and
102 48297fa2 Iustin Pop
            mountpoint != path):
103 48297fa2 Iustin Pop
          data.append(mountpoint)
104 48297fa2 Iustin Pop
    finally:
105 48297fa2 Iustin Pop
      fh.close()
106 48297fa2 Iustin Pop
    data.sort(key=lambda x: x.count("/"), reverse=True)
107 48297fa2 Iustin Pop
    return data
108 48297fa2 Iustin Pop
109 48297fa2 Iustin Pop
  def ListInstances(self):
110 48297fa2 Iustin Pop
    """Get the list of running instances.
111 48297fa2 Iustin Pop

112 48297fa2 Iustin Pop
    """
113 48297fa2 Iustin Pop
    return [name for name in os.listdir(self._ROOT_DIR)
114 48297fa2 Iustin Pop
            if self._IsDirLive(os.path.join(self._ROOT_DIR, name))]
115 48297fa2 Iustin Pop
116 48297fa2 Iustin Pop
  def GetInstanceInfo(self, instance_name):
117 48297fa2 Iustin Pop
    """Get instance properties.
118 48297fa2 Iustin Pop

119 48297fa2 Iustin Pop
    Args:
120 48297fa2 Iustin Pop
      instance_name: the instance name
121 48297fa2 Iustin Pop

122 48297fa2 Iustin Pop
    Returns:
123 48297fa2 Iustin Pop
      (name, id, memory, vcpus, stat, times)
124 48297fa2 Iustin Pop
    """
125 48297fa2 Iustin Pop
    dir_name = "%s/%s" % (self._ROOT_DIR, instance_name)
126 48297fa2 Iustin Pop
    if not self._IsDirLive(dir_name):
127 48297fa2 Iustin Pop
      raise HypervisorError("Instance %s is not running" % instance_name)
128 48297fa2 Iustin Pop
    return (instance_name, 0, 0, 0, 0, 0)
129 48297fa2 Iustin Pop
130 48297fa2 Iustin Pop
  def GetAllInstancesInfo(self):
131 48297fa2 Iustin Pop
    """Get properties of all instances.
132 48297fa2 Iustin Pop

133 48297fa2 Iustin Pop
    Returns:
134 48297fa2 Iustin Pop
      [(name, id, memory, vcpus, stat, times),...]
135 48297fa2 Iustin Pop
    """
136 48297fa2 Iustin Pop
    data = []
137 48297fa2 Iustin Pop
    for file_name in os.listdir(self._ROOT_DIR):
138 48297fa2 Iustin Pop
      path = os.path.join(self._ROOT_DIR, file_name)
139 48297fa2 Iustin Pop
      if self._IsDirLive(path):
140 48297fa2 Iustin Pop
        data.append((file_name, 0, 0, 0, 0, 0))
141 48297fa2 Iustin Pop
    return data
142 48297fa2 Iustin Pop
143 48297fa2 Iustin Pop
  def StartInstance(self, instance, block_devices):
144 48297fa2 Iustin Pop
    """Start an instance.
145 48297fa2 Iustin Pop

146 48297fa2 Iustin Pop
    For the chroot manager, we try to mount the block device and
147 48297fa2 Iustin Pop
    execute '/ganeti-chroot start'.
148 48297fa2 Iustin Pop

149 48297fa2 Iustin Pop
    """
150 48297fa2 Iustin Pop
    root_dir = "%s/%s" % (self._ROOT_DIR, instance.name)
151 48297fa2 Iustin Pop
    if not os.path.exists(root_dir):
152 48297fa2 Iustin Pop
      try:
153 48297fa2 Iustin Pop
        os.mkdir(root_dir)
154 48297fa2 Iustin Pop
      except IOError, err:
155 48297fa2 Iustin Pop
        raise HypervisorError("Failed to start instance %s: %s" %
156 48297fa2 Iustin Pop
                              (instance.name, err))
157 48297fa2 Iustin Pop
      if not os.path.isdir(root_dir):
158 48297fa2 Iustin Pop
        raise HypervisorError("Needed path %s is not a directory" % root_dir)
159 48297fa2 Iustin Pop
160 48297fa2 Iustin Pop
    if not os.path.ismount(root_dir):
161 48297fa2 Iustin Pop
      if not block_devices:
162 48297fa2 Iustin Pop
        raise HypervisorError("The chroot manager needs at least one disk")
163 48297fa2 Iustin Pop
164 48297fa2 Iustin Pop
      sda_dev_path = block_devices[0][1]
165 48297fa2 Iustin Pop
      result = utils.RunCmd(["mount", sda_dev_path, root_dir])
166 48297fa2 Iustin Pop
      if result.failed:
167 48297fa2 Iustin Pop
        raise HypervisorError("Can't mount the chroot dir: %s" % result.output)
168 48297fa2 Iustin Pop
    init_script = instance.hvparams[constants.HV_INIT_SCRIPT]
169 48297fa2 Iustin Pop
    result = utils.RunCmd(["chroot", root_dir, init_script, "start"])
170 48297fa2 Iustin Pop
    if result.failed:
171 48297fa2 Iustin Pop
      raise HypervisorError("Can't run the chroot start script: %s" %
172 48297fa2 Iustin Pop
                            result.output)
173 48297fa2 Iustin Pop
174 48297fa2 Iustin Pop
  def StopInstance(self, instance, force=False):
175 48297fa2 Iustin Pop
    """Stop an instance.
176 48297fa2 Iustin Pop

177 48297fa2 Iustin Pop
    This method has complicated cleanup tests, as we must:
178 48297fa2 Iustin Pop
      - try to kill all leftover processes
179 48297fa2 Iustin Pop
      - try to unmount any additional sub-mountpoints
180 48297fa2 Iustin Pop
      - finally unmount the instance dir
181 48297fa2 Iustin Pop

182 48297fa2 Iustin Pop
    """
183 48297fa2 Iustin Pop
    root_dir = "%s/%s" % (self._ROOT_DIR, instance.name)
184 48297fa2 Iustin Pop
    if not os.path.exists(root_dir):
185 48297fa2 Iustin Pop
      return
186 48297fa2 Iustin Pop
187 48297fa2 Iustin Pop
    if self._IsDirLive(root_dir):
188 48297fa2 Iustin Pop
      result = utils.RunCmd(["chroot", root_dir, "/ganeti-chroot", "stop"])
189 48297fa2 Iustin Pop
      if result.failed:
190 48297fa2 Iustin Pop
        raise HypervisorError("Can't run the chroot stop script: %s" %
191 48297fa2 Iustin Pop
                              result.output)
192 48297fa2 Iustin Pop
      retry = 20
193 48297fa2 Iustin Pop
      while not force and self._IsDirLive(root_dir) and retry > 0:
194 48297fa2 Iustin Pop
        time.sleep(1)
195 48297fa2 Iustin Pop
        retry -= 1
196 48297fa2 Iustin Pop
        if retry < 10:
197 48297fa2 Iustin Pop
          result = utils.RunCmd(["fuser", "-k", "-TERM", "-m", root_dir])
198 48297fa2 Iustin Pop
      retry = 5
199 48297fa2 Iustin Pop
      while force and self._IsDirLive(root_dir) and retry > 0:
200 48297fa2 Iustin Pop
        time.sleep(1)
201 48297fa2 Iustin Pop
        retry -= 1
202 48297fa2 Iustin Pop
        utils.RunCmd(["fuser", "-k", "-KILL", "-m", root_dir])
203 48297fa2 Iustin Pop
      if self._IsDirLive(root_dir):
204 48297fa2 Iustin Pop
        raise HypervisorError("Can't stop the processes using the chroot")
205 48297fa2 Iustin Pop
    for mpath in self._GetMountSubdirs(root_dir):
206 48297fa2 Iustin Pop
      utils.RunCmd(["umount", mpath])
207 48297fa2 Iustin Pop
    retry = 10
208 48297fa2 Iustin Pop
    while retry > 0:
209 48297fa2 Iustin Pop
      result = utils.RunCmd(["umount", root_dir])
210 48297fa2 Iustin Pop
      if not result.failed:
211 48297fa2 Iustin Pop
        break
212 48297fa2 Iustin Pop
      retry -= 1
213 48297fa2 Iustin Pop
      time.sleep(1)
214 48297fa2 Iustin Pop
    if result.failed:
215 48297fa2 Iustin Pop
      logging.error("Processes still alive in the chroot: %s",
216 48297fa2 Iustin Pop
                    utils.RunCmd("fuser -vm %s" % root_dir).output)
217 48297fa2 Iustin Pop
      raise HypervisorError("Can't umount the chroot dir: %s" % result.output)
218 48297fa2 Iustin Pop
219 48297fa2 Iustin Pop
  def RebootInstance(self, instance):
220 48297fa2 Iustin Pop
    """Reboot an instance.
221 48297fa2 Iustin Pop

222 48297fa2 Iustin Pop
    This is not (yet) implemented for the chroot manager.
223 48297fa2 Iustin Pop

224 48297fa2 Iustin Pop
    """
225 48297fa2 Iustin Pop
    raise HypervisorError("The chroot manager doesn't implement the"
226 48297fa2 Iustin Pop
                          " reboot functionality")
227 48297fa2 Iustin Pop
228 48297fa2 Iustin Pop
  def GetNodeInfo(self):
229 48297fa2 Iustin Pop
    """Return information about the node.
230 48297fa2 Iustin Pop

231 48297fa2 Iustin Pop
    This is just a wrapper over the base GetLinuxNodeInfo method.
232 48297fa2 Iustin Pop

233 48297fa2 Iustin Pop
    @return: a dict with the following keys (values in MiB):
234 48297fa2 Iustin Pop
          - memory_total: the total memory size on the node
235 48297fa2 Iustin Pop
          - memory_free: the available memory on the node for instances
236 48297fa2 Iustin Pop
          - memory_dom0: the memory used by the node itself, if available
237 48297fa2 Iustin Pop

238 48297fa2 Iustin Pop
    """
239 48297fa2 Iustin Pop
    return self.GetLinuxNodeInfo()
240 48297fa2 Iustin Pop
241 48297fa2 Iustin Pop
  @classmethod
242 48297fa2 Iustin Pop
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
243 48297fa2 Iustin Pop
    """Return a command for connecting to the console of an instance.
244 48297fa2 Iustin Pop

245 48297fa2 Iustin Pop
    """
246 48297fa2 Iustin Pop
    root_dir = "%s/%s" % (cls._ROOT_DIR, instance.name)
247 48297fa2 Iustin Pop
    if not os.path.ismount(root_dir):
248 48297fa2 Iustin Pop
      raise HypervisorError("Instance %s is not running" % instance.name)
249 48297fa2 Iustin Pop
250 48297fa2 Iustin Pop
    return "chroot %s" % root_dir
251 48297fa2 Iustin Pop
252 48297fa2 Iustin Pop
  def Verify(self):
253 48297fa2 Iustin Pop
    """Verify the hypervisor.
254 48297fa2 Iustin Pop

255 48297fa2 Iustin Pop
    For the chroot manager, it just checks the existence of the base
256 48297fa2 Iustin Pop
    dir.
257 48297fa2 Iustin Pop

258 48297fa2 Iustin Pop
    """
259 48297fa2 Iustin Pop
    if not os.path.exists(self._ROOT_DIR):
260 48297fa2 Iustin Pop
      return "The required directory '%s' does not exist." % self._ROOT_DIR