Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_chroot.py @ 6bf6870b

History | View | Annotate | Download (10.7 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2013 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, hvparams=None):
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, hvparams=None):
113
    """Get instance properties.
114

115
    @type instance_name: string
116
    @param instance_name: the instance name
117
    @type hvparams: dict of strings
118
    @param hvparams: hvparams to be used with this instance
119

120
    @return: (name, id, memory, vcpus, stat, times)
121

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

    
128
  def GetAllInstancesInfo(self, hvparams=None):
129
    """Get properties of all instances.
130

131
    @type hvparams: dict of strings
132
    @param hvparams: hypervisor parameter
133
    @return: [(name, id, memory, vcpus, stat, times),...]
134

135
    """
136
    data = []
137
    for file_name in os.listdir(self._ROOT_DIR):
138
      path = utils.PathJoin(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, startup_paused):
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 = self._InstanceDir(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, name=None):
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 name is None:
184
      name = instance.name
185

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

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

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

    
204
    if self._IsDirLive(root_dir):
205
      if force:
206
        raise HypervisorError("Can't stop the processes using the chroot")
207
      return
208

    
209
  def CleanupInstance(self, instance_name):
210
    """Cleanup after a stopped instance
211

212
    """
213
    root_dir = self._InstanceDir(instance_name)
214

    
215
    if not os.path.exists(root_dir):
216
      return
217

    
218
    if self._IsDirLive(root_dir):
219
      raise HypervisorError("Processes are still using the chroot")
220

    
221
    for mpath in self._GetMountSubdirs(root_dir):
222
      utils.RunCmd(["umount", mpath])
223

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

    
232
  def RebootInstance(self, instance):
233
    """Reboot an instance.
234

235
    This is not (yet) implemented for the chroot manager.
236

237
    """
238
    raise HypervisorError("The chroot manager doesn't implement the"
239
                          " reboot functionality")
240

    
241
  def BalloonInstanceMemory(self, instance, mem):
242
    """Balloon an instance memory to a certain value.
243

244
    @type instance: L{objects.Instance}
245
    @param instance: instance to be accepted
246
    @type mem: int
247
    @param mem: actual memory size to use for instance runtime
248

249
    """
250
    # Currently chroots don't have memory limits
251
    pass
252

    
253
  def GetNodeInfo(self, hvparams=None):
254
    """Return information about the node.
255

256
    See L{BaseHypervisor.GetLinuxNodeInfo}.
257

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

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

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

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

    
278
  def Verify(self, hvparams=None):
279
    """Verify the hypervisor.
280

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

283
    @type hvparams: dict of strings
284
    @param hvparams: hypervisor parameters to be verified against, not used
285
      in for chroot
286

287
    @return: Problem description if something is wrong, C{None} otherwise
288

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

    
295
  @classmethod
296
  def PowercycleNode(cls, hvparams=None):
297
    """Chroot powercycle, just a wrapper over Linux powercycle.
298

299
    @type hvparams: dict of strings
300
    @param hvparams: hypervisor params to be used on this node
301

302
    """
303
    cls.LinuxPowercycle()
304

    
305
  def MigrateInstance(self, cluster_name, instance, target, live):
306
    """Migrate an instance.
307

308
    @type cluster_name: string
309
    @param cluster_name: name of the cluster
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")