Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_chroot.py @ 860bf930

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

    
31
from ganeti import constants
32
from ganeti import errors # pylint: disable=W0611
33
from ganeti import utils
34
from ganeti import objects
35
from ganeti import pathutils
36
from ganeti.hypervisor import hv_base
37
from ganeti.errors import HypervisorError
38

    
39

    
40
class ChrootManager(hv_base.BaseHypervisor):
41
  """Chroot manager.
42

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

61
  """
62
  _ROOT_DIR = pathutils.RUN_DIR + "/chroot-hypervisor"
63

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

    
70
  def __init__(self):
71
    hv_base.BaseHypervisor.__init__(self)
72
    utils.EnsureDirs([(self._ROOT_DIR, constants.RUN_DIRS_MODE)])
73

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

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

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

88
    """
89
    result = []
90
    for _, mountpoint, _, _ in utils.GetMounts():
91
      if (mountpoint.startswith(path) and
92
          mountpoint != path):
93
        result.append(mountpoint)
94

    
95
    result.sort(key=lambda x: x.count("/"), reverse=True)
96
    return result
97

    
98
  @classmethod
99
  def _InstanceDir(cls, instance_name):
100
    """Return the root directory for an instance.
101

102
    """
103
    return utils.PathJoin(cls._ROOT_DIR, instance_name)
104

    
105
  def ListInstances(self):
106
    """Get the list of running instances.
107

108
    """
109
    return [name for name in os.listdir(self._ROOT_DIR)
110
            if self._IsDirLive(utils.PathJoin(self._ROOT_DIR, name))]
111

    
112
  def GetInstanceInfo(self, instance_name):
113
    """Get instance properties.
114

115
    @type instance_name: string
116
    @param instance_name: the instance name
117

118
    @return: (name, id, memory, vcpus, stat, times)
119

120
    """
121
    dir_name = self._InstanceDir(instance_name)
122
    if not self._IsDirLive(dir_name):
123
      raise HypervisorError("Instance %s is not running" % instance_name)
124
    return (instance_name, 0, 0, 0, 0, 0)
125

    
126
  def GetAllInstancesInfo(self):
127
    """Get properties of all instances.
128

129
    @return: [(name, id, memory, vcpus, stat, times),...]
130

131
    """
132
    data = []
133
    for file_name in os.listdir(self._ROOT_DIR):
134
      path = utils.PathJoin(self._ROOT_DIR, file_name)
135
      if self._IsDirLive(path):
136
        data.append((file_name, 0, 0, 0, 0, 0))
137
    return data
138

    
139
  def StartInstance(self, instance, block_devices, startup_paused):
140
    """Start an instance.
141

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

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

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

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

    
170
  def StopInstance(self, instance, force=False, retry=False, name=None):
171
    """Stop an instance.
172

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

178
    """
179
    if name is None:
180
      name = instance.name
181

    
182
    root_dir = self._InstanceDir(name)
183
    if not os.path.exists(root_dir) or not self._IsDirLive(root_dir):
184
      return
185

    
186
    # Run the chroot stop script only once
187
    if not retry and not force:
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

    
193
    if not force:
194
      utils.RunCmd(["fuser", "-k", "-TERM", "-m", root_dir])
195
    else:
196
      utils.RunCmd(["fuser", "-k", "-KILL", "-m", root_dir])
197
      # 2 seconds at most should be enough for KILL to take action
198
      time.sleep(2)
199

    
200
    if self._IsDirLive(root_dir):
201
      if force:
202
        raise HypervisorError("Can't stop the processes using the chroot")
203
      return
204

    
205
  def CleanupInstance(self, instance_name):
206
    """Cleanup after a stopped instance
207

208
    """
209
    root_dir = self._InstanceDir(instance_name)
210

    
211
    if not os.path.exists(root_dir):
212
      return
213

    
214
    if self._IsDirLive(root_dir):
215
      raise HypervisorError("Processes are still using the chroot")
216

    
217
    for mpath in self._GetMountSubdirs(root_dir):
218
      utils.RunCmd(["umount", mpath])
219

    
220
    result = utils.RunCmd(["umount", root_dir])
221
    if result.failed:
222
      msg = ("Processes still alive in the chroot: %s" %
223
             utils.RunCmd("fuser -vm %s" % root_dir).output)
224
      logging.error(msg)
225
      raise HypervisorError("Can't umount the chroot dir: %s (%s)" %
226
                            (result.output, msg))
227

    
228
  def RebootInstance(self, instance):
229
    """Reboot an instance.
230

231
    This is not (yet) implemented for the chroot manager.
232

233
    """
234
    raise HypervisorError("The chroot manager doesn't implement the"
235
                          " reboot functionality")
236

    
237
  def BalloonInstanceMemory(self, instance, mem):
238
    """Balloon an instance memory to a certain value.
239

240
    @type instance: L{objects.Instance}
241
    @param instance: instance to be accepted
242
    @type mem: int
243
    @param mem: actual memory size to use for instance runtime
244

245
    """
246
    # Currently chroots don't have memory limits
247
    pass
248

    
249
  def GetNodeInfo(self):
250
    """Return information about the node.
251

252
    This is just a wrapper over the base GetLinuxNodeInfo method.
253

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

259
    """
260
    return self.GetLinuxNodeInfo()
261

    
262
  @classmethod
263
  def GetInstanceConsole(cls, instance, # pylint: disable=W0221
264
                         hvparams, beparams, root_dir=None):
265
    """Return information for connecting to the console of an instance.
266

267
    """
268
    if root_dir is None:
269
      root_dir = cls._InstanceDir(instance.name)
270
      if not os.path.ismount(root_dir):
271
        raise HypervisorError("Instance %s is not running" % instance.name)
272

    
273
    return objects.InstanceConsole(instance=instance.name,
274
                                   kind=constants.CONS_SSH,
275
                                   host=instance.primary_node,
276
                                   user=constants.SSH_CONSOLE_USER,
277
                                   command=["chroot", root_dir])
278

    
279
  def Verify(self):
280
    """Verify the hypervisor.
281

282
    For the chroot manager, it just checks the existence of the base dir.
283

284
    @return: Problem description if something is wrong, C{None} otherwise
285

286
    """
287
    if os.path.exists(self._ROOT_DIR):
288
      return None
289
    else:
290
      return "The required directory '%s' does not exist" % self._ROOT_DIR
291

    
292
  @classmethod
293
  def PowercycleNode(cls):
294
    """Chroot powercycle, just a wrapper over Linux powercycle.
295

296
    """
297
    cls.LinuxPowercycle()
298

    
299
  def MigrateInstance(self, instance, target, live):
300
    """Migrate an instance.
301

302
    @type instance: L{objects.Instance}
303
    @param instance: the instance to be migrated
304
    @type target: string
305
    @param target: hostname (usually ip) of the target node
306
    @type live: boolean
307
    @param live: whether to do a live or non-live migration
308

309
    """
310
    raise HypervisorError("Migration not supported by the chroot hypervisor")
311

    
312
  def GetMigrationStatus(self, instance):
313
    """Get the migration status
314

315
    @type instance: L{objects.Instance}
316
    @param instance: the instance that is being migrated
317
    @rtype: L{objects.MigrationStatus}
318
    @return: the status of the current migration (one of
319
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
320
             progress info that can be retrieved from the hypervisor
321

322
    """
323
    raise HypervisorError("Migration not supported by the chroot hypervisor")
324

    
325
  def HotplugSupported(self, instance, action, dev_type):
326
    """Whether hotplug is supported.
327

328
    """
329
    raise HypervisorError("Hotplug not supported by the chroot hypervisor")