Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_chroot.py @ 46952329

History | View | Annotate | Download (8.5 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: (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
        fstype, mountpoint, rest = 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
    Args:
120
      instance_name: the instance name
121

122
    Returns:
123
      (name, id, memory, vcpus, stat, times)
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
    Returns:
134
      [(name, id, memory, vcpus, stat, times),...]
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):
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):
185
      return
186

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

    
219
  def RebootInstance(self, instance):
220
    """Reboot an instance.
221

222
    This is not (yet) implemented for the chroot manager.
223

224
    """
225
    raise HypervisorError("The chroot manager doesn't implement the"
226
                          " reboot functionality")
227

    
228
  def GetNodeInfo(self):
229
    """Return information about the node.
230

231
    This is just a wrapper over the base GetLinuxNodeInfo method.
232

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

238
    """
239
    return self.GetLinuxNodeInfo()
240

    
241
  @classmethod
242
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
243
    """Return a command for connecting to the console of an instance.
244

245
    """
246
    root_dir = "%s/%s" % (cls._ROOT_DIR, instance.name)
247
    if not os.path.ismount(root_dir):
248
      raise HypervisorError("Instance %s is not running" % instance.name)
249

    
250
    return "chroot %s" % root_dir
251

    
252
  def Verify(self):
253
    """Verify the hypervisor.
254

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

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