Package ganeti :: Package hypervisor :: Module hv_chroot
[hide private]
[frames] | no frames]

Source Code for Module ganeti.hypervisor.hv_chroot

  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):
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 timeout=None):
176 """Stop an instance. 177 178 This method has complicated cleanup tests, as we must: 179 - try to kill all leftover processes 180 - try to unmount any additional sub-mountpoints 181 - finally unmount the instance dir 182 183 """ 184 assert(timeout is None or force is not None) 185 186 if name is None: 187 name = instance.name 188 189 root_dir = self._InstanceDir(name) 190 if not os.path.exists(root_dir) or not self._IsDirLive(root_dir): 191 return 192 193 timeout_cmd = [] 194 if timeout is not None: 195 timeout_cmd.extend(["timeout", str(timeout)]) 196 197 # Run the chroot stop script only once 198 if not retry and not force: 199 result = utils.RunCmd(timeout_cmd.extend(["chroot", root_dir, 200 "/ganeti-chroot", "stop"])) 201 if result.failed: 202 raise HypervisorError("Can't run the chroot stop script: %s" % 203 result.output) 204 205 if not force: 206 utils.RunCmd(["fuser", "-k", "-TERM", "-m", root_dir]) 207 else: 208 utils.RunCmd(["fuser", "-k", "-KILL", "-m", root_dir]) 209 # 2 seconds at most should be enough for KILL to take action 210 time.sleep(2) 211 212 if self._IsDirLive(root_dir): 213 if force: 214 raise HypervisorError("Can't stop the processes using the chroot") 215 return
216
217 - def CleanupInstance(self, instance_name):
218 """Cleanup after a stopped instance 219 220 """ 221 root_dir = self._InstanceDir(instance_name) 222 223 if not os.path.exists(root_dir): 224 return 225 226 if self._IsDirLive(root_dir): 227 raise HypervisorError("Processes are still using the chroot") 228 229 for mpath in self._GetMountSubdirs(root_dir): 230 utils.RunCmd(["umount", mpath]) 231 232 result = utils.RunCmd(["umount", root_dir]) 233 if result.failed: 234 msg = ("Processes still alive in the chroot: %s" % 235 utils.RunCmd("fuser -vm %s" % root_dir).output) 236 logging.error(msg) 237 raise HypervisorError("Can't umount the chroot dir: %s (%s)" % 238 (result.output, msg))
239
240 - def RebootInstance(self, instance):
241 """Reboot an instance. 242 243 This is not (yet) implemented for the chroot manager. 244 245 """ 246 raise HypervisorError("The chroot manager doesn't implement the" 247 " reboot functionality")
248
249 - def BalloonInstanceMemory(self, instance, mem):
250 """Balloon an instance memory to a certain value. 251 252 @type instance: L{objects.Instance} 253 @param instance: instance to be accepted 254 @type mem: int 255 @param mem: actual memory size to use for instance runtime 256 257 """ 258 # Currently chroots don't have memory limits 259 pass
260
261 - def GetNodeInfo(self, hvparams=None):
262 """Return information about the node. 263 264 See L{BaseHypervisor.GetLinuxNodeInfo}. 265 266 """ 267 return self.GetLinuxNodeInfo()
268 269 @classmethod
270 - def GetInstanceConsole(cls, instance, primary_node, # pylint: disable=W0221 271 hvparams, beparams, root_dir=None):
272 """Return information for connecting to the console of an instance. 273 274 """ 275 if root_dir is None: 276 root_dir = cls._InstanceDir(instance.name) 277 if not os.path.ismount(root_dir): 278 raise HypervisorError("Instance %s is not running" % instance.name) 279 280 return objects.InstanceConsole(instance=instance.name, 281 kind=constants.CONS_SSH, 282 host=primary_node.name, 283 user=constants.SSH_CONSOLE_USER, 284 command=["chroot", root_dir])
285
286 - def Verify(self, hvparams=None):
287 """Verify the hypervisor. 288 289 For the chroot manager, it just checks the existence of the base dir. 290 291 @type hvparams: dict of strings 292 @param hvparams: hypervisor parameters to be verified against, not used 293 in for chroot 294 295 @return: Problem description if something is wrong, C{None} otherwise 296 297 """ 298 if os.path.exists(self._ROOT_DIR): 299 return None 300 else: 301 return "The required directory '%s' does not exist" % self._ROOT_DIR
302 303 @classmethod
304 - def PowercycleNode(cls, hvparams=None):
305 """Chroot powercycle, just a wrapper over Linux powercycle. 306 307 @type hvparams: dict of strings 308 @param hvparams: hypervisor params to be used on this node 309 310 """ 311 cls.LinuxPowercycle()
312
313 - def MigrateInstance(self, cluster_name, instance, target, live):
314 """Migrate an instance. 315 316 @type cluster_name: string 317 @param cluster_name: name of the cluster 318 @type instance: L{objects.Instance} 319 @param instance: the instance to be migrated 320 @type target: string 321 @param target: hostname (usually ip) of the target node 322 @type live: boolean 323 @param live: whether to do a live or non-live migration 324 325 """ 326 raise HypervisorError("Migration not supported by the chroot hypervisor")
327
328 - def GetMigrationStatus(self, instance):
329 """Get the migration status 330 331 @type instance: L{objects.Instance} 332 @param instance: the instance that is being migrated 333 @rtype: L{objects.MigrationStatus} 334 @return: the status of the current migration (one of 335 L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional 336 progress info that can be retrieved from the hypervisor 337 338 """ 339 raise HypervisorError("Migration not supported by the chroot hypervisor")
340