Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_chroot.py @ 14b3f969

History | View | Annotate | Download (9.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

    
31
from ganeti import constants
32
from ganeti import errors # pylint: disable-msg=W0611
33
from ganeti import utils
34
from ganeti.hypervisor import hv_base
35
from ganeti.errors import HypervisorError
36

    
37

    
38
class ChrootManager(hv_base.BaseHypervisor):
39
  """Chroot manager.
40

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

59
  """
60
  _ROOT_DIR = constants.RUN_GANETI_DIR + "/chroot-hypervisor"
61

    
62
  PARAMETERS = {
63
    constants.HV_INIT_SCRIPT: (True, utils.IsNormAbsPath,
64
                               "must be an absolute normalized path",
65
                               None, None),
66
    }
67

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

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

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

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

90
    This function is Linux-specific.
91

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

    
108
  @classmethod
109
  def _InstanceDir(cls, instance_name):
110
    """Return the root directory for an instance.
111

112
    """
113
    return utils.PathJoin(cls._ROOT_DIR, instance_name)
114

    
115
  def ListInstances(self):
116
    """Get the list of running instances.
117

118
    """
119
    return [name for name in os.listdir(self._ROOT_DIR)
120
            if self._IsDirLive(utils.PathJoin(self._ROOT_DIR, name))]
121

    
122
  def GetInstanceInfo(self, instance_name):
123
    """Get instance properties.
124

125
    @type instance_name: string
126
    @param instance_name: the instance name
127

128
    @return: (name, id, memory, vcpus, stat, times)
129

130
    """
131
    dir_name = self._InstanceDir(instance_name)
132
    if not self._IsDirLive(dir_name):
133
      raise HypervisorError("Instance %s is not running" % instance_name)
134
    return (instance_name, 0, 0, 0, 0, 0)
135

    
136
  def GetAllInstancesInfo(self):
137
    """Get properties of all instances.
138

139
    @return: [(name, id, memory, vcpus, stat, times),...]
140

141
    """
142
    data = []
143
    for file_name in os.listdir(self._ROOT_DIR):
144
      path = utils.PathJoin(self._ROOT_DIR, file_name)
145
      if self._IsDirLive(path):
146
        data.append((file_name, 0, 0, 0, 0, 0))
147
    return data
148

    
149
  def StartInstance(self, instance, block_devices):
150
    """Start an instance.
151

152
    For the chroot manager, we try to mount the block device and
153
    execute '/ganeti-chroot start'.
154

155
    """
156
    root_dir = self._InstanceDir(instance.name)
157
    if not os.path.exists(root_dir):
158
      try:
159
        os.mkdir(root_dir)
160
      except IOError, err:
161
        raise HypervisorError("Failed to start instance %s: %s" %
162
                              (instance.name, err))
163
      if not os.path.isdir(root_dir):
164
        raise HypervisorError("Needed path %s is not a directory" % root_dir)
165

    
166
    if not os.path.ismount(root_dir):
167
      if not block_devices:
168
        raise HypervisorError("The chroot manager needs at least one disk")
169

    
170
      sda_dev_path = block_devices[0][1]
171
      result = utils.RunCmd(["mount", sda_dev_path, root_dir])
172
      if result.failed:
173
        raise HypervisorError("Can't mount the chroot dir: %s" % result.output)
174
    init_script = instance.hvparams[constants.HV_INIT_SCRIPT]
175
    result = utils.RunCmd(["chroot", root_dir, init_script, "start"])
176
    if result.failed:
177
      raise HypervisorError("Can't run the chroot start script: %s" %
178
                            result.output)
179

    
180
  def StopInstance(self, instance, force=False, retry=False, name=None):
181
    """Stop an instance.
182

183
    This method has complicated cleanup tests, as we must:
184
      - try to kill all leftover processes
185
      - try to unmount any additional sub-mountpoints
186
      - finally unmount the instance dir
187

188
    """
189
    if name is None:
190
      name = instance.name
191

    
192
    root_dir = self._InstanceDir(name)
193
    if not os.path.exists(root_dir) or not self._IsDirLive(root_dir):
194
      return
195

    
196
    # Run the chroot stop script only once
197
    if not retry and not force:
198
      result = utils.RunCmd(["chroot", root_dir, "/ganeti-chroot", "stop"])
199
      if result.failed:
200
        raise HypervisorError("Can't run the chroot stop script: %s" %
201
                              result.output)
202

    
203
    if not force:
204
      utils.RunCmd(["fuser", "-k", "-TERM", "-m", root_dir])
205
    else:
206
      utils.RunCmd(["fuser", "-k", "-KILL", "-m", root_dir])
207
      # 2 seconds at most should be enough for KILL to take action
208
      time.sleep(2)
209

    
210
    if self._IsDirLive(root_dir):
211
      if force:
212
        raise HypervisorError("Can't stop the processes using the chroot")
213
      return
214

    
215
  def CleanupInstance(self, instance_name):
216
    """Cleanup after a stopped instance
217

218
    """
219
    root_dir = self._InstanceDir(instance_name)
220

    
221
    if not os.path.exists(root_dir):
222
      return
223

    
224
    if self._IsDirLive(root_dir):
225
      raise HypervisorError("Processes are still using the chroot")
226

    
227
    for mpath in self._GetMountSubdirs(root_dir):
228
      utils.RunCmd(["umount", mpath])
229

    
230
    result = utils.RunCmd(["umount", root_dir])
231
    if result.failed:
232
      msg = ("Processes still alive in the chroot: %s" %
233
             utils.RunCmd("fuser -vm %s" % root_dir).output)
234
      logging.error(msg)
235
      raise HypervisorError("Can't umount the chroot dir: %s (%s)" %
236
                            (result.output, msg))
237

    
238
  def RebootInstance(self, instance):
239
    """Reboot an instance.
240

241
    This is not (yet) implemented for the chroot manager.
242

243
    """
244
    raise HypervisorError("The chroot manager doesn't implement the"
245
                          " reboot functionality")
246

    
247
  def GetNodeInfo(self):
248
    """Return information about the node.
249

250
    This is just a wrapper over the base GetLinuxNodeInfo method.
251

252
    @return: a dict with the following keys (values in MiB):
253
          - memory_total: the total memory size on the node
254
          - memory_free: the available memory on the node for instances
255
          - memory_dom0: the memory used by the node itself, if available
256

257
    """
258
    return self.GetLinuxNodeInfo()
259

    
260
  @classmethod
261
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
262
    """Return a command for connecting to the console of an instance.
263

264
    """
265
    root_dir = cls._InstanceDir(instance.name)
266
    if not os.path.ismount(root_dir):
267
      raise HypervisorError("Instance %s is not running" % instance.name)
268

    
269
    return "chroot %s" % root_dir
270

    
271
  def Verify(self):
272
    """Verify the hypervisor.
273

274
    For the chroot manager, it just checks the existence of the base dir.
275

276
    """
277
    if not os.path.exists(self._ROOT_DIR):
278
      return "The required directory '%s' does not exist." % self._ROOT_DIR
279

    
280
  @classmethod
281
  def PowercycleNode(cls):
282
    """Chroot powercycle, just a wrapper over Linux powercycle.
283

284
    """
285
    cls.LinuxPowercycle()
286

    
287
  def MigrateInstance(self, instance, target, live):
288
    """Migrate an instance.
289

290
    @type instance: L{objects.Instance}
291
    @param instance: the instance to be migrated
292
    @type target: string
293
    @param target: hostname (usually ip) of the target node
294
    @type live: boolean
295
    @param live: whether to do a live or non-live migration
296

297
    """
298
    raise HypervisorError("Migration not supported by the chroot hypervisor")