Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_chroot.py @ 98c98ab9

History | View | Annotate | Download (9.2 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
31 48297fa2 Iustin Pop
from ganeti import constants
32 30e4e741 Iustin Pop
from ganeti import errors # pylint: disable-msg=W0611
33 48297fa2 Iustin Pop
from ganeti import utils
34 48297fa2 Iustin Pop
from ganeti.hypervisor import hv_base
35 48297fa2 Iustin Pop
from ganeti.errors import HypervisorError
36 48297fa2 Iustin Pop
37 48297fa2 Iustin Pop
38 48297fa2 Iustin Pop
class ChrootManager(hv_base.BaseHypervisor):
39 48297fa2 Iustin Pop
  """Chroot manager.
40 48297fa2 Iustin Pop

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

59 48297fa2 Iustin Pop
  """
60 48297fa2 Iustin Pop
  _ROOT_DIR = constants.RUN_GANETI_DIR + "/chroot-hypervisor"
61 48297fa2 Iustin Pop
62 46952329 Guido Trotter
  PARAMETERS = {
63 46952329 Guido Trotter
    constants.HV_INIT_SCRIPT: (True, utils.IsNormAbsPath,
64 46952329 Guido Trotter
                               "must be an absolute normalized path",
65 d73ef63f Michael Hanselmann
                               None, None),
66 46952329 Guido Trotter
    }
67 48297fa2 Iustin Pop
68 48297fa2 Iustin Pop
  def __init__(self):
69 48297fa2 Iustin Pop
    hv_base.BaseHypervisor.__init__(self)
70 98c98ab9 Guido Trotter
    utils.EnsureDirs([(self._ROOT_DIR, constants.RUN_DIRS_MODE)])
71 48297fa2 Iustin Pop
72 48297fa2 Iustin Pop
  @staticmethod
73 48297fa2 Iustin Pop
  def _IsDirLive(path):
74 48297fa2 Iustin Pop
    """Check if a directory looks like a live chroot.
75 48297fa2 Iustin Pop

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

86 48297fa2 Iustin Pop
    This function is Linux-specific.
87 48297fa2 Iustin Pop

88 48297fa2 Iustin Pop
    """
89 48297fa2 Iustin Pop
    #TODO(iustin): investigate and document non-linux options
90 48297fa2 Iustin Pop
    #(e.g. via mount output)
91 48297fa2 Iustin Pop
    data = []
92 48297fa2 Iustin Pop
    fh = open("/proc/mounts", "r")
93 48297fa2 Iustin Pop
    try:
94 48297fa2 Iustin Pop
      for line in fh:
95 1122eb25 Iustin Pop
        _, mountpoint, _ = line.split(" ", 2)
96 48297fa2 Iustin Pop
        if (mountpoint.startswith(path) and
97 48297fa2 Iustin Pop
            mountpoint != path):
98 48297fa2 Iustin Pop
          data.append(mountpoint)
99 48297fa2 Iustin Pop
    finally:
100 48297fa2 Iustin Pop
      fh.close()
101 48297fa2 Iustin Pop
    data.sort(key=lambda x: x.count("/"), reverse=True)
102 48297fa2 Iustin Pop
    return data
103 48297fa2 Iustin Pop
104 6b0391b3 Iustin Pop
  @classmethod
105 6b0391b3 Iustin Pop
  def _InstanceDir(cls, instance_name):
106 6b0391b3 Iustin Pop
    """Return the root directory for an instance.
107 6b0391b3 Iustin Pop

108 6b0391b3 Iustin Pop
    """
109 6b0391b3 Iustin Pop
    return utils.PathJoin(cls._ROOT_DIR, instance_name)
110 6b0391b3 Iustin Pop
111 48297fa2 Iustin Pop
  def ListInstances(self):
112 48297fa2 Iustin Pop
    """Get the list of running instances.
113 48297fa2 Iustin Pop

114 48297fa2 Iustin Pop
    """
115 48297fa2 Iustin Pop
    return [name for name in os.listdir(self._ROOT_DIR)
116 c4feafe8 Iustin Pop
            if self._IsDirLive(utils.PathJoin(self._ROOT_DIR, name))]
117 48297fa2 Iustin Pop
118 48297fa2 Iustin Pop
  def GetInstanceInfo(self, instance_name):
119 48297fa2 Iustin Pop
    """Get instance properties.
120 48297fa2 Iustin Pop

121 d73ef63f Michael Hanselmann
    @type instance_name: string
122 d73ef63f Michael Hanselmann
    @param instance_name: the instance name
123 d73ef63f Michael Hanselmann

124 d73ef63f Michael Hanselmann
    @return: (name, id, memory, vcpus, stat, times)
125 48297fa2 Iustin Pop

126 48297fa2 Iustin Pop
    """
127 6b0391b3 Iustin Pop
    dir_name = self._InstanceDir(instance_name)
128 48297fa2 Iustin Pop
    if not self._IsDirLive(dir_name):
129 48297fa2 Iustin Pop
      raise HypervisorError("Instance %s is not running" % instance_name)
130 48297fa2 Iustin Pop
    return (instance_name, 0, 0, 0, 0, 0)
131 48297fa2 Iustin Pop
132 48297fa2 Iustin Pop
  def GetAllInstancesInfo(self):
133 48297fa2 Iustin Pop
    """Get properties of all instances.
134 48297fa2 Iustin Pop

135 d73ef63f Michael Hanselmann
    @return: [(name, id, memory, vcpus, stat, times),...]
136 d73ef63f Michael Hanselmann

137 48297fa2 Iustin Pop
    """
138 48297fa2 Iustin Pop
    data = []
139 48297fa2 Iustin Pop
    for file_name in os.listdir(self._ROOT_DIR):
140 c4feafe8 Iustin Pop
      path = utils.PathJoin(self._ROOT_DIR, file_name)
141 48297fa2 Iustin Pop
      if self._IsDirLive(path):
142 48297fa2 Iustin Pop
        data.append((file_name, 0, 0, 0, 0, 0))
143 48297fa2 Iustin Pop
    return data
144 48297fa2 Iustin Pop
145 48297fa2 Iustin Pop
  def StartInstance(self, instance, block_devices):
146 48297fa2 Iustin Pop
    """Start an instance.
147 48297fa2 Iustin Pop

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

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

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

184 48297fa2 Iustin Pop
    """
185 bbcf7ad0 Iustin Pop
    if name is None:
186 bbcf7ad0 Iustin Pop
      name = instance.name
187 bbcf7ad0 Iustin Pop
188 bbcf7ad0 Iustin Pop
    root_dir = self._InstanceDir(name)
189 a2771c83 Guido Trotter
    if not os.path.exists(root_dir) or not self._IsDirLive(root_dir):
190 48297fa2 Iustin Pop
      return
191 48297fa2 Iustin Pop
192 a2771c83 Guido Trotter
    # Run the chroot stop script only once
193 a2771c83 Guido Trotter
    if not retry and not force:
194 48297fa2 Iustin Pop
      result = utils.RunCmd(["chroot", root_dir, "/ganeti-chroot", "stop"])
195 48297fa2 Iustin Pop
      if result.failed:
196 48297fa2 Iustin Pop
        raise HypervisorError("Can't run the chroot stop script: %s" %
197 48297fa2 Iustin Pop
                              result.output)
198 a2771c83 Guido Trotter
199 a2771c83 Guido Trotter
    if not force:
200 a2771c83 Guido Trotter
      utils.RunCmd(["fuser", "-k", "-TERM", "-m", root_dir])
201 a2771c83 Guido Trotter
    else:
202 a2771c83 Guido Trotter
      utils.RunCmd(["fuser", "-k", "-KILL", "-m", root_dir])
203 a2771c83 Guido Trotter
      # 2 seconds at most should be enough for KILL to take action
204 a2771c83 Guido Trotter
      time.sleep(2)
205 a2771c83 Guido Trotter
206 a2771c83 Guido Trotter
    if self._IsDirLive(root_dir):
207 a2771c83 Guido Trotter
      if force:
208 48297fa2 Iustin Pop
        raise HypervisorError("Can't stop the processes using the chroot")
209 a2771c83 Guido Trotter
      return
210 a2771c83 Guido Trotter
211 9e302a8c Iustin Pop
  def CleanupInstance(self, instance_name):
212 9e302a8c Iustin Pop
    """Cleanup after a stopped instance
213 9e302a8c Iustin Pop

214 9e302a8c Iustin Pop
    """
215 14b3f969 Iustin Pop
    root_dir = self._InstanceDir(instance_name)
216 14b3f969 Iustin Pop
217 14b3f969 Iustin Pop
    if not os.path.exists(root_dir):
218 14b3f969 Iustin Pop
      return
219 14b3f969 Iustin Pop
220 9e302a8c Iustin Pop
    if self._IsDirLive(root_dir):
221 9e302a8c Iustin Pop
      raise HypervisorError("Processes are still using the chroot")
222 9e302a8c Iustin Pop
223 48297fa2 Iustin Pop
    for mpath in self._GetMountSubdirs(root_dir):
224 48297fa2 Iustin Pop
      utils.RunCmd(["umount", mpath])
225 a2771c83 Guido Trotter
226 a2771c83 Guido Trotter
    result = utils.RunCmd(["umount", root_dir])
227 14b3f969 Iustin Pop
    if result.failed:
228 a2771c83 Guido Trotter
      msg = ("Processes still alive in the chroot: %s" %
229 a2771c83 Guido Trotter
             utils.RunCmd("fuser -vm %s" % root_dir).output)
230 a2771c83 Guido Trotter
      logging.error(msg)
231 a2771c83 Guido Trotter
      raise HypervisorError("Can't umount the chroot dir: %s (%s)" %
232 a2771c83 Guido Trotter
                            (result.output, msg))
233 48297fa2 Iustin Pop
234 48297fa2 Iustin Pop
  def RebootInstance(self, instance):
235 48297fa2 Iustin Pop
    """Reboot an instance.
236 48297fa2 Iustin Pop

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

239 48297fa2 Iustin Pop
    """
240 48297fa2 Iustin Pop
    raise HypervisorError("The chroot manager doesn't implement the"
241 48297fa2 Iustin Pop
                          " reboot functionality")
242 48297fa2 Iustin Pop
243 48297fa2 Iustin Pop
  def GetNodeInfo(self):
244 48297fa2 Iustin Pop
    """Return information about the node.
245 48297fa2 Iustin Pop

246 48297fa2 Iustin Pop
    This is just a wrapper over the base GetLinuxNodeInfo method.
247 48297fa2 Iustin Pop

248 48297fa2 Iustin Pop
    @return: a dict with the following keys (values in MiB):
249 48297fa2 Iustin Pop
          - memory_total: the total memory size on the node
250 48297fa2 Iustin Pop
          - memory_free: the available memory on the node for instances
251 48297fa2 Iustin Pop
          - memory_dom0: the memory used by the node itself, if available
252 48297fa2 Iustin Pop

253 48297fa2 Iustin Pop
    """
254 48297fa2 Iustin Pop
    return self.GetLinuxNodeInfo()
255 48297fa2 Iustin Pop
256 48297fa2 Iustin Pop
  @classmethod
257 48297fa2 Iustin Pop
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
258 48297fa2 Iustin Pop
    """Return a command for connecting to the console of an instance.
259 48297fa2 Iustin Pop

260 48297fa2 Iustin Pop
    """
261 6b0391b3 Iustin Pop
    root_dir = cls._InstanceDir(instance.name)
262 48297fa2 Iustin Pop
    if not os.path.ismount(root_dir):
263 48297fa2 Iustin Pop
      raise HypervisorError("Instance %s is not running" % instance.name)
264 48297fa2 Iustin Pop
265 48297fa2 Iustin Pop
    return "chroot %s" % root_dir
266 48297fa2 Iustin Pop
267 48297fa2 Iustin Pop
  def Verify(self):
268 48297fa2 Iustin Pop
    """Verify the hypervisor.
269 48297fa2 Iustin Pop

270 d73ef63f Michael Hanselmann
    For the chroot manager, it just checks the existence of the base dir.
271 48297fa2 Iustin Pop

272 48297fa2 Iustin Pop
    """
273 48297fa2 Iustin Pop
    if not os.path.exists(self._ROOT_DIR):
274 48297fa2 Iustin Pop
      return "The required directory '%s' does not exist." % self._ROOT_DIR
275 94fed7da Iustin Pop
276 94fed7da Iustin Pop
  @classmethod
277 94fed7da Iustin Pop
  def PowercycleNode(cls):
278 94fed7da Iustin Pop
    """Chroot powercycle, just a wrapper over Linux powercycle.
279 94fed7da Iustin Pop

280 94fed7da Iustin Pop
    """
281 94fed7da Iustin Pop
    cls.LinuxPowercycle()
282 94fed7da Iustin Pop
283 94fed7da Iustin Pop
  def MigrateInstance(self, instance, target, live):
284 94fed7da Iustin Pop
    """Migrate an instance.
285 94fed7da Iustin Pop

286 3a488770 Iustin Pop
    @type instance: L{objects.Instance}
287 94fed7da Iustin Pop
    @param instance: the instance to be migrated
288 94fed7da Iustin Pop
    @type target: string
289 94fed7da Iustin Pop
    @param target: hostname (usually ip) of the target node
290 94fed7da Iustin Pop
    @type live: boolean
291 94fed7da Iustin Pop
    @param live: whether to do a live or non-live migration
292 94fed7da Iustin Pop

293 94fed7da Iustin Pop
    """
294 94fed7da Iustin Pop
    raise HypervisorError("Migration not supported by the chroot hypervisor")