Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_chroot.py @ 1122eb25

History | View | Annotate | Download (8.4 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
        _, 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