Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_chroot.py @ 3ae003d8

History | View | Annotate | Download (10.8 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
                   timeout=None):
172
    """Stop an instance.
173

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

179
    """
180
    assert(timeout is None or force is not None)
181

    
182
    if name is None:
183
      name = instance.name
184

    
185
    root_dir = self._InstanceDir(name)
186
    if not os.path.exists(root_dir) or not self._IsDirLive(root_dir):
187
      return
188

    
189
    timeout_cmd = []
190
    if timeout is not None:
191
      timeout_cmd.extend(["timeout", str(timeout)])
192

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

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

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

    
213
  def CleanupInstance(self, instance_name):
214
    """Cleanup after a stopped instance
215

216
    """
217
    root_dir = self._InstanceDir(instance_name)
218

    
219
    if not os.path.exists(root_dir):
220
      return
221

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

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

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

    
236
  def RebootInstance(self, instance):
237
    """Reboot an instance.
238

239
    This is not (yet) implemented for the chroot manager.
240

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

    
245
  def BalloonInstanceMemory(self, instance, mem):
246
    """Balloon an instance memory to a certain value.
247

248
    @type instance: L{objects.Instance}
249
    @param instance: instance to be accepted
250
    @type mem: int
251
    @param mem: actual memory size to use for instance runtime
252

253
    """
254
    # Currently chroots don't have memory limits
255
    pass
256

    
257
  def GetNodeInfo(self):
258
    """Return information about the node.
259

260
    This is just a wrapper over the base GetLinuxNodeInfo method.
261

262
    @return: a dict with the following keys (values in MiB):
263
          - memory_total: the total memory size on the node
264
          - memory_free: the available memory on the node for instances
265
          - memory_dom0: the memory used by the node itself, if available
266

267
    """
268
    return self.GetLinuxNodeInfo()
269

    
270
  @classmethod
271
  def GetInstanceConsole(cls, instance, # pylint: disable=W0221
272
                         hvparams, beparams, root_dir=None):
273
    """Return information for connecting to the console of an instance.
274

275
    """
276
    if root_dir is None:
277
      root_dir = cls._InstanceDir(instance.name)
278
      if not os.path.ismount(root_dir):
279
        raise HypervisorError("Instance %s is not running" % instance.name)
280

    
281
    return objects.InstanceConsole(instance=instance.name,
282
                                   kind=constants.CONS_SSH,
283
                                   host=instance.primary_node,
284
                                   user=constants.SSH_CONSOLE_USER,
285
                                   command=["chroot", root_dir])
286

    
287
  def Verify(self):
288
    """Verify the hypervisor.
289

290
    For the chroot manager, it just checks the existence of the base dir.
291

292
    @return: Problem description if something is wrong, C{None} otherwise
293

294
    """
295
    if os.path.exists(self._ROOT_DIR):
296
      return None
297
    else:
298
      return "The required directory '%s' does not exist" % self._ROOT_DIR
299

    
300
  @classmethod
301
  def PowercycleNode(cls):
302
    """Chroot powercycle, just a wrapper over Linux powercycle.
303

304
    """
305
    cls.LinuxPowercycle()
306

    
307
  def MigrateInstance(self, instance, target, live):
308
    """Migrate an instance.
309

310
    @type instance: L{objects.Instance}
311
    @param instance: the instance to be migrated
312
    @type target: string
313
    @param target: hostname (usually ip) of the target node
314
    @type live: boolean
315
    @param live: whether to do a live or non-live migration
316

317
    """
318
    raise HypervisorError("Migration not supported by the chroot hypervisor")
319

    
320
  def GetMigrationStatus(self, instance):
321
    """Get the migration status
322

323
    @type instance: L{objects.Instance}
324
    @param instance: the instance that is being migrated
325
    @rtype: L{objects.MigrationStatus}
326
    @return: the status of the current migration (one of
327
             L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
328
             progress info that can be retrieved from the hypervisor
329

330
    """
331
    raise HypervisorError("Migration not supported by the chroot hypervisor")
332

    
333
  def HotplugSupported(self, instance, action, dev_type):
334
    """Whether hotplug is supported.
335

336
    """
337
    raise HypervisorError("Hotplug not supported by the chroot hypervisor")