29 |
29 |
|
30 |
30 |
"""
|
31 |
31 |
|
|
32 |
import errno
|
|
33 |
import logging
|
|
34 |
import os
|
|
35 |
import random
|
|
36 |
|
32 |
37 |
from ganeti import errors
|
33 |
38 |
from ganeti import constants
|
34 |
39 |
from ganeti import utils
|
... | ... | |
172 |
177 |
for lower, higher in uid_pool:
|
173 |
178 |
uids.update(range(lower, higher + 1))
|
174 |
179 |
return list(uids)
|
|
180 |
|
|
181 |
|
|
182 |
def _IsUidUsed(uid):
|
|
183 |
"""Check if there is any process in the system running with the given user-id
|
|
184 |
|
|
185 |
"""
|
|
186 |
pgrep_command = [constants.PGREP, "-u", uid]
|
|
187 |
result = utils.RunCmd(pgrep_command)
|
|
188 |
|
|
189 |
if result.exit_code == 0:
|
|
190 |
return True
|
|
191 |
elif result.exit_code == 1:
|
|
192 |
return False
|
|
193 |
else:
|
|
194 |
raise errors.CommandError("Running pgrep failed. exit code: %s"
|
|
195 |
% result.exit_code)
|
|
196 |
|
|
197 |
|
|
198 |
class LockedUid(object):
|
|
199 |
"""Class representing a locked user-id in the uid-pool.
|
|
200 |
|
|
201 |
This binds together a userid and a lock.
|
|
202 |
|
|
203 |
"""
|
|
204 |
def __init__(self, uid, lock):
|
|
205 |
"""Constructor
|
|
206 |
|
|
207 |
@param uid: a user-id
|
|
208 |
@param lock: a utils.FileLock object
|
|
209 |
|
|
210 |
"""
|
|
211 |
self._uid = uid
|
|
212 |
self._lock = lock
|
|
213 |
|
|
214 |
def Unlock(self):
|
|
215 |
# Release the exclusive lock and close the filedescriptor
|
|
216 |
self._lock.Close()
|
|
217 |
|
|
218 |
def __str__(self):
|
|
219 |
return "%s" % self._uid
|
|
220 |
|
|
221 |
|
|
222 |
def RequestUnusedUid(all_uids):
|
|
223 |
"""Tries to find an unused uid from the uid-pool, locks it and returns it.
|
|
224 |
|
|
225 |
Usage pattern:
|
|
226 |
|
|
227 |
1) When starting a process
|
|
228 |
|
|
229 |
from ganeti import ssconf
|
|
230 |
from ganeti import uidpool
|
|
231 |
|
|
232 |
# Get list of all user-ids in the uid-pool from ssconf
|
|
233 |
ss = ssconf.SimpleStore()
|
|
234 |
uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\n")
|
|
235 |
all_uids = set(uidpool.ExpandUidPool(uid_pool))
|
|
236 |
|
|
237 |
uid = uidpool.RequestUnusedUid(all_uids)
|
|
238 |
try:
|
|
239 |
<start a process with the UID>
|
|
240 |
# Once the process is started, we can release the file lock
|
|
241 |
uid.Unlock()
|
|
242 |
except ..., err:
|
|
243 |
# Return the UID to the pool
|
|
244 |
uidpool.ReleaseUid(uid)
|
|
245 |
|
|
246 |
2) Stopping a process
|
|
247 |
|
|
248 |
from ganeti import uidpool
|
|
249 |
|
|
250 |
uid = <get the UID the process is running under>
|
|
251 |
<stop the process>
|
|
252 |
uidpool.ReleaseUid(uid)
|
|
253 |
|
|
254 |
@param all_uids: a set containing all the user-ids in the user-id pool
|
|
255 |
@return: a LockedUid object representing the unused uid. It's the caller's
|
|
256 |
responsibility to unlock the uid once an instance is started with
|
|
257 |
this uid.
|
|
258 |
|
|
259 |
"""
|
|
260 |
# Create the lock dir if it's not yet present
|
|
261 |
try:
|
|
262 |
utils.EnsureDirs([(constants.UIDPOOL_LOCKDIR, 0755)])
|
|
263 |
except errors.GenericError, err:
|
|
264 |
raise errors.LockError("Failed to create user-id pool lock dir: %s" % err)
|
|
265 |
|
|
266 |
# Get list of currently used uids from the filesystem
|
|
267 |
try:
|
|
268 |
taken_uids = set(os.listdir(constants.UIDPOOL_LOCKDIR))
|
|
269 |
# Filter out spurious entries from the directory listing
|
|
270 |
taken_uids = all_uids.intersection(taken_uids)
|
|
271 |
except OSError, err:
|
|
272 |
raise errors.LockError("Failed to get list of used user-ids: %s" % err)
|
|
273 |
|
|
274 |
# Remove the list of used uids from the list of all uids
|
|
275 |
unused_uids = list(all_uids - taken_uids)
|
|
276 |
if not unused_uids:
|
|
277 |
logging.info("All user-ids in the uid-pool are marked 'taken'")
|
|
278 |
|
|
279 |
# Randomize the order of the unused user-id list
|
|
280 |
random.shuffle(unused_uids)
|
|
281 |
|
|
282 |
# Randomize the order of the unused user-id list
|
|
283 |
taken_uids = list(taken_uids)
|
|
284 |
random.shuffle(taken_uids)
|
|
285 |
|
|
286 |
for uid in (unused_uids + taken_uids):
|
|
287 |
try:
|
|
288 |
# Create the lock file
|
|
289 |
# Note: we don't care if it exists. Only the fact that we can
|
|
290 |
# (or can't) lock it later is what matters.
|
|
291 |
uid_path = utils.PathJoin(constants.UIDPOOL_LOCKDIR, str(uid))
|
|
292 |
lock = utils.FileLock.Open(uid_path)
|
|
293 |
except OSError, err:
|
|
294 |
raise errors.LockError("Failed to create lockfile for user-id %s: %s"
|
|
295 |
% (uid, err))
|
|
296 |
try:
|
|
297 |
# Try acquiring an exclusive lock on the lock file
|
|
298 |
lock.Exclusive()
|
|
299 |
# Check if there is any process running with this user-id
|
|
300 |
if _IsUidUsed(uid):
|
|
301 |
logging.debug("There is already a process running under"
|
|
302 |
" user-id %s", uid)
|
|
303 |
lock.Unlock()
|
|
304 |
continue
|
|
305 |
return LockedUid(uid, lock)
|
|
306 |
except IOError, err:
|
|
307 |
if err.errno == errno.EAGAIN:
|
|
308 |
# The file is already locked, let's skip it and try another unused uid
|
|
309 |
logging.debug("Lockfile for user-id is already locked %s: %s", uid, err)
|
|
310 |
continue
|
|
311 |
except errors.LockError, err:
|
|
312 |
# There was an unexpected error while trying to lock the file
|
|
313 |
logging.error("Failed to lock the lockfile for user-id %s: %s", uid, err)
|
|
314 |
raise
|
|
315 |
|
|
316 |
raise errors.LockError("Failed to find an unused user-id")
|
|
317 |
|
|
318 |
|
|
319 |
def ReleaseUid(uid):
|
|
320 |
"""This should be called when the given user-id is no longer in use.
|
|
321 |
|
|
322 |
"""
|
|
323 |
# Make sure we release the exclusive lock, if there is any
|
|
324 |
uid.Unlock()
|
|
325 |
try:
|
|
326 |
uid_path = utils.PathJoin(constants.UIDPOOL_LOCKDIR, str(uid))
|
|
327 |
os.remove(uid_path)
|
|
328 |
except OSError, err:
|
|
329 |
raise errors.LockError("Failed to remove user-id lockfile"
|
|
330 |
" for user-id %s: %s" % (uid, err))
|