Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_chroot.py @ 48297fa2

History | View | Annotate | Download (8.3 kB)

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,
65
    ]
66

    
67
  def __init__(self):
68
    hv_base.BaseHypervisor.__init__(self)
69
    if not os.path.exists(self._ROOT_DIR):
70
      os.mkdir(self._ROOT_DIR)
71
    if not os.path.isdir(self._ROOT_DIR):
72
      raise HypervisorError("Needed path %s is not a directory" %
73
                            self._ROOT_DIR)
74

    
75
  @staticmethod
76
  def _IsDirLive(path):
77
    """Check if a directory looks like a live chroot.
78

79
    """
80
    if not os.path.ismount(path):
81
      return False
82
    result = utils.RunCmd(["fuser", "-m", path])
83
    return not result.failed
84

    
85
  @staticmethod
86
  def _GetMountSubdirs(path):
87
    """Return the list of mountpoints under a given path.
88

89
    This function is Linux-specific.
90

91
    """
92
    #TODO(iustin): investigate and document non-linux options
93
    #(e.g. via mount output)
94
    data = []
95
    fh = open("/proc/mounts", "r")
96
    try:
97
      for line in fh:
98
        fstype, mountpoint, rest = line.split(" ", 2)
99
        if (mountpoint.startswith(path) and
100
            mountpoint != path):
101
          data.append(mountpoint)
102
    finally:
103
      fh.close()
104
    data.sort(key=lambda x: x.count("/"), reverse=True)
105
    return data
106

    
107
  def ListInstances(self):
108
    """Get the list of running instances.
109

110
    """
111
    return [name for name in os.listdir(self._ROOT_DIR)
112
            if self._IsDirLive(os.path.join(self._ROOT_DIR, name))]
113

    
114
  def GetInstanceInfo(self, instance_name):
115
    """Get instance properties.
116

117
    Args:
118
      instance_name: the instance name
119

120
    Returns:
121
      (name, id, memory, vcpus, stat, times)
122
    """
123
    dir_name = "%s/%s" % (self._ROOT_DIR, 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):
129
    """Get properties of all instances.
130

131
    Returns:
132
      [(name, id, memory, vcpus, stat, times),...]
133
    """
134
    data = []
135
    for file_name in os.listdir(self._ROOT_DIR):
136
      path = os.path.join(self._ROOT_DIR, file_name)
137
      if self._IsDirLive(path):
138
        data.append((file_name, 0, 0, 0, 0, 0))
139
    return data
140

    
141
  def StartInstance(self, instance, block_devices):
142
    """Start an instance.
143

144
    For the chroot manager, we try to mount the block device and
145
    execute '/ganeti-chroot start'.
146

147
    """
148
    root_dir = "%s/%s" % (self._ROOT_DIR, instance.name)
149
    if not os.path.exists(root_dir):
150
      try:
151
        os.mkdir(root_dir)
152
      except IOError, err:
153
        raise HypervisorError("Failed to start instance %s: %s" %
154
                              (instance.name, err))
155
      if not os.path.isdir(root_dir):
156
        raise HypervisorError("Needed path %s is not a directory" % root_dir)
157

    
158
    if not os.path.ismount(root_dir):
159
      if not block_devices:
160
        raise HypervisorError("The chroot manager needs at least one disk")
161

    
162
      sda_dev_path = block_devices[0][1]
163
      result = utils.RunCmd(["mount", sda_dev_path, root_dir])
164
      if result.failed:
165
        raise HypervisorError("Can't mount the chroot dir: %s" % result.output)
166
    init_script = instance.hvparams[constants.HV_INIT_SCRIPT]
167
    result = utils.RunCmd(["chroot", root_dir, init_script, "start"])
168
    if result.failed:
169
      raise HypervisorError("Can't run the chroot start script: %s" %
170
                            result.output)
171

    
172
  def StopInstance(self, instance, force=False):
173
    """Stop an instance.
174

175
    This method has complicated cleanup tests, as we must:
176
      - try to kill all leftover processes
177
      - try to unmount any additional sub-mountpoints
178
      - finally unmount the instance dir
179

180
    """
181
    root_dir = "%s/%s" % (self._ROOT_DIR, instance.name)
182
    if not os.path.exists(root_dir):
183
      return
184

    
185
    if self._IsDirLive(root_dir):
186
      result = utils.RunCmd(["chroot", root_dir, "/ganeti-chroot", "stop"])
187
      if result.failed:
188
        raise HypervisorError("Can't run the chroot stop script: %s" %
189
                              result.output)
190
      retry = 20
191
      while not force and self._IsDirLive(root_dir) and retry > 0:
192
        time.sleep(1)
193
        retry -= 1
194
        if retry < 10:
195
          result = utils.RunCmd(["fuser", "-k", "-TERM", "-m", root_dir])
196
      retry = 5
197
      while force and self._IsDirLive(root_dir) and retry > 0:
198
        time.sleep(1)
199
        retry -= 1
200
        utils.RunCmd(["fuser", "-k", "-KILL", "-m", root_dir])
201
      if self._IsDirLive(root_dir):
202
        raise HypervisorError("Can't stop the processes using the chroot")
203
    for mpath in self._GetMountSubdirs(root_dir):
204
      utils.RunCmd(["umount", mpath])
205
    retry = 10
206
    while retry > 0:
207
      result = utils.RunCmd(["umount", root_dir])
208
      if not result.failed:
209
        break
210
      retry -= 1
211
      time.sleep(1)
212
    if result.failed:
213
      logging.error("Processes still alive in the chroot: %s",
214
                    utils.RunCmd("fuser -vm %s" % root_dir).output)
215
      raise HypervisorError("Can't umount the chroot dir: %s" % result.output)
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
254
    dir.
255

256
    """
257
    if not os.path.exists(self._ROOT_DIR):
258
      return "The required directory '%s' does not exist." % self._ROOT_DIR