Revision 48297fa2

b/Makefile.am
91 91
hypervisor_PYTHON = \
92 92
	lib/hypervisor/__init__.py \
93 93
	lib/hypervisor/hv_base.py \
94
	lib/hypervisor/hv_chroot.py \
94 95
	lib/hypervisor/hv_fake.py \
95 96
	lib/hypervisor/hv_kvm.py \
96 97
	lib/hypervisor/hv_xen.py
b/lib/constants.py
305 305
HV_ROOT_PATH = "root_path"
306 306
HV_SERIAL_CONSOLE = "serial_console"
307 307
HV_USB_MOUSE = "usb_mouse"
308
HV_INIT_SCRIPT = "init_script"
308 309

  
309 310
HVS_PARAMETER_TYPES = {
310 311
  HV_BOOT_ORDER: VTYPE_STRING,
......
323 324
  HV_ROOT_PATH: VTYPE_STRING,
324 325
  HV_SERIAL_CONSOLE: VTYPE_BOOL,
325 326
  HV_USB_MOUSE: VTYPE_STRING,
327
  HV_INIT_SCRIPT: VTYPE_STRING,
326 328
  }
327 329

  
328 330
HVS_PARAMETERS = frozenset(HVS_PARAMETER_TYPES.keys())
......
348 350
HT_FAKE = "fake"
349 351
HT_XEN_HVM = "xen-hvm"
350 352
HT_KVM = "kvm"
351
HYPER_TYPES = frozenset([HT_XEN_PVM, HT_FAKE, HT_XEN_HVM, HT_KVM])
353
HT_CHROOT = "chroot"
354
HYPER_TYPES = frozenset([HT_XEN_PVM, HT_FAKE, HT_XEN_HVM, HT_KVM, HT_CHROOT])
352 355
HTS_REQ_PORT = frozenset([HT_XEN_HVM, HT_KVM])
353 356
HTS_COPY_VNC_PASSWORD = frozenset([HT_XEN_HVM])
354 357

  
......
508 511
    },
509 512
  HT_FAKE: {
510 513
    },
514
  HT_CHROOT: {
515
    HV_INIT_SCRIPT: "/ganeti-chroot",
516
    },
511 517
  }
512 518

  
513 519
BEC_DEFAULTS = {
b/lib/hypervisor/__init__.py
29 29
from ganeti.hypervisor import hv_fake
30 30
from ganeti.hypervisor import hv_xen
31 31
from ganeti.hypervisor import hv_kvm
32
from ganeti.hypervisor import hv_chroot
32 33

  
33 34

  
34 35
_HYPERVISOR_MAP = {
35
    constants.HT_XEN_PVM: hv_xen.XenPvmHypervisor,
36
    constants.HT_XEN_HVM: hv_xen.XenHvmHypervisor,
37
    constants.HT_FAKE: hv_fake.FakeHypervisor,
38
    constants.HT_KVM: hv_kvm.KVMHypervisor,
39
    }
36
  constants.HT_XEN_PVM: hv_xen.XenPvmHypervisor,
37
  constants.HT_XEN_HVM: hv_xen.XenHvmHypervisor,
38
  constants.HT_FAKE: hv_fake.FakeHypervisor,
39
  constants.HT_KVM: hv_kvm.KVMHypervisor,
40
  constants.HT_CHROOT: hv_chroot.ChrootManager,
41
  }
40 42

  
41 43

  
42 44
def GetHypervisor(ht_kind):
b/lib/hypervisor/hv_chroot.py
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
from cStringIO import StringIO
31

  
32
from ganeti import constants
33
from ganeti import errors
34
from ganeti import utils
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,
65
    ]
66

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

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

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

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

  
89
    This function is Linux-specific.
90

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

  
107
  def ListInstances(self):
108
    """Get the list of running instances.
109

  
110
    """
111
    return [name for name in os.listdir(self._ROOT_DIR)
112
            if self._IsDirLive(os.path.join(self._ROOT_DIR, name))]
113

  
114
  def GetInstanceInfo(self, instance_name):
115
    """Get instance properties.
116

  
117
    Args:
118
      instance_name: the instance name
119

  
120
    Returns:
121
      (name, id, memory, vcpus, stat, times)
122
    """
123
    dir_name = "%s/%s" % (self._ROOT_DIR, 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):
129
    """Get properties of all instances.
130

  
131
    Returns:
132
      [(name, id, memory, vcpus, stat, times),...]
133
    """
134
    data = []
135
    for file_name in os.listdir(self._ROOT_DIR):
136
      path = os.path.join(self._ROOT_DIR, file_name)
137
      if self._IsDirLive(path):
138
        data.append((file_name, 0, 0, 0, 0, 0))
139
    return data
140

  
141
  def StartInstance(self, instance, block_devices):
142
    """Start an instance.
143

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

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

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

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

  
172
  def StopInstance(self, instance, force=False):
173
    """Stop an instance.
174

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

  
180
    """
181
    root_dir = "%s/%s" % (self._ROOT_DIR, instance.name)
182
    if not os.path.exists(root_dir):
183
      return
184

  
185
    if self._IsDirLive(root_dir):
186
      result = utils.RunCmd(["chroot", root_dir, "/ganeti-chroot", "stop"])
187
      if result.failed:
188
        raise HypervisorError("Can't run the chroot stop script: %s" %
189
                              result.output)
190
      retry = 20
191
      while not force and self._IsDirLive(root_dir) and retry > 0:
192
        time.sleep(1)
193
        retry -= 1
194
        if retry < 10:
195
          result = utils.RunCmd(["fuser", "-k", "-TERM", "-m", root_dir])
196
      retry = 5
197
      while force and self._IsDirLive(root_dir) and retry > 0:
198
        time.sleep(1)
199
        retry -= 1
200
        utils.RunCmd(["fuser", "-k", "-KILL", "-m", root_dir])
201
      if self._IsDirLive(root_dir):
202
        raise HypervisorError("Can't stop the processes using the chroot")
203
    for mpath in self._GetMountSubdirs(root_dir):
204
      utils.RunCmd(["umount", mpath])
205
    retry = 10
206
    while retry > 0:
207
      result = utils.RunCmd(["umount", root_dir])
208
      if not result.failed:
209
        break
210
      retry -= 1
211
      time.sleep(1)
212
    if result.failed:
213
      logging.error("Processes still alive in the chroot: %s",
214
                    utils.RunCmd("fuser -vm %s" % root_dir).output)
215
      raise HypervisorError("Can't umount the chroot dir: %s" % result.output)
216

  
217
  def RebootInstance(self, instance):
218
    """Reboot an instance.
219

  
220
    This is not (yet) implemented for the chroot manager.
221

  
222
    """
223
    raise HypervisorError("The chroot manager doesn't implement the"
224
                          " reboot functionality")
225

  
226
  def GetNodeInfo(self):
227
    """Return information about the node.
228

  
229
    This is just a wrapper over the base GetLinuxNodeInfo method.
230

  
231
    @return: a dict with the following keys (values in MiB):
232
          - memory_total: the total memory size on the node
233
          - memory_free: the available memory on the node for instances
234
          - memory_dom0: the memory used by the node itself, if available
235

  
236
    """
237
    return self.GetLinuxNodeInfo()
238

  
239
  @classmethod
240
  def GetShellCommandForConsole(cls, instance, hvparams, beparams):
241
    """Return a command for connecting to the console of an instance.
242

  
243
    """
244
    root_dir = "%s/%s" % (cls._ROOT_DIR, instance.name)
245
    if not os.path.ismount(root_dir):
246
      raise HypervisorError("Instance %s is not running" % instance.name)
247

  
248
    return "chroot %s" % root_dir
249

  
250
  def Verify(self):
251
    """Verify the hypervisor.
252

  
253
    For the chroot manager, it just checks the existence of the base
254
    dir.
255

  
256
    """
257
    if not os.path.exists(self._ROOT_DIR):
258
      return "The required directory '%s' does not exist." % self._ROOT_DIR

Also available in: Unified diff