Revision 649bcdd8 lib/uidpool.py
b/lib/uidpool.py | ||
---|---|---|
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)) |
Also available in: Unified diff