Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_chroot.py @ 07b49e41

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
    @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
    if retry:
184
      return
185
    root_dir = "%s/%s" % (self._ROOT_DIR, instance.name)
186
    if not os.path.exists(root_dir):
187
      return
188

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

    
221
  def RebootInstance(self, instance):
222
    """Reboot an instance.
223

224
    This is not (yet) implemented for the chroot manager.
225

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

    
230
  def GetNodeInfo(self):
231
    """Return information about the node.
232

233
    This is just a wrapper over the base GetLinuxNodeInfo method.
234

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

240
    """
241
    return self.GetLinuxNodeInfo()
242

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

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

    
252
    return "chroot %s" % root_dir
253

    
254
  def Verify(self):
255
    """Verify the hypervisor.
256

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

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