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