Statistics
| Branch: | Tag: | Revision:

root / lib / hypervisor / hv_chroot.py @ b459a848

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=W0611
33
from ganeti import utils
34
from ganeti import objects
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
    utils.EnsureDirs([(self._ROOT_DIR, constants.RUN_DIRS_MODE)])
72

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
236
  def GetNodeInfo(self):
237
    """Return information about the node.
238

239
    This is just a wrapper over the base GetLinuxNodeInfo method.
240

241
    @return: a dict with the following keys (values in MiB):
242
          - memory_total: the total memory size on the node
243
          - memory_free: the available memory on the node for instances
244
          - memory_dom0: the memory used by the node itself, if available
245

246
    """
247
    return self.GetLinuxNodeInfo()
248

    
249
  @classmethod
250
  def GetInstanceConsole(cls, instance, # pylint: disable=W0221
251
                         hvparams, beparams, root_dir=None):
252
    """Return information for connecting to the console of an instance.
253

254
    """
255
    if root_dir is None:
256
      root_dir = cls._InstanceDir(instance.name)
257
      if not os.path.ismount(root_dir):
258
        raise HypervisorError("Instance %s is not running" % instance.name)
259

    
260
    return objects.InstanceConsole(instance=instance.name,
261
                                   kind=constants.CONS_SSH,
262
                                   host=instance.primary_node,
263
                                   user=constants.GANETI_RUNAS,
264
                                   command=["chroot", root_dir])
265

    
266
  def Verify(self):
267
    """Verify the hypervisor.
268

269
    For the chroot manager, it just checks the existence of the base dir.
270

271
    """
272
    if not os.path.exists(self._ROOT_DIR):
273
      return "The required directory '%s' does not exist." % self._ROOT_DIR
274

    
275
  @classmethod
276
  def PowercycleNode(cls):
277
    """Chroot powercycle, just a wrapper over Linux powercycle.
278

279
    """
280
    cls.LinuxPowercycle()
281

    
282
  def MigrateInstance(self, instance, target, live):
283
    """Migrate an instance.
284

285
    @type instance: L{objects.Instance}
286
    @param instance: the instance to be migrated
287
    @type target: string
288
    @param target: hostname (usually ip) of the target node
289
    @type live: boolean
290
    @param live: whether to do a live or non-live migration
291

292
    """
293
    raise HypervisorError("Migration not supported by the chroot hypervisor")