Revision d4fa5c23

b/daemons/ganeti-masterd
393 393

  
394 394
  logger.SetupDaemon(constants.LOG_MASTERDAEMON, debug=options.debug)
395 395

  
396
  logger.Info("ganeti master daemon startup")
396
  logging.info("ganeti master daemon startup")
397 397

  
398
  master.setup_queue()
398 399
  try:
399
    utils.Lock('cmd', debug=options.debug)
400
  except errors.LockError, err:
401
    print >> sys.stderr, str(err)
402
    master.server_cleanup()
403
    return
404

  
405
  try:
406
    master.setup_queue()
407
    try:
408
      master.serve_forever()
409
    finally:
410
      master.server_cleanup()
400
    master.serve_forever()
411 401
  finally:
412
    utils.Unlock('cmd')
413
    utils.LockCleanup()
402
    master.server_cleanup()
414 403

  
415 404

  
416 405
if __name__ == "__main__":
b/lib/cmdlib.py
1093 1093
    if done or oneshot:
1094 1094
      break
1095 1095

  
1096
    if unlock:
1097
      #utils.Unlock('cmd')
1098
      pass
1099
    try:
1100
      time.sleep(min(60, max_time))
1101
    finally:
1102
      if unlock:
1103
        #utils.Lock('cmd')
1104
        pass
1096
    time.sleep(min(60, max_time))
1105 1097

  
1106 1098
  if done:
1107 1099
    proc.LogInfo("Instance %s's disks are in sync." % instance.name)
b/lib/utils.py
101 101
  output = property(_GetOutput, None, None, "Return full output")
102 102

  
103 103

  
104
def _GetLockFile(subsystem):
105
  """Compute the file name for a given lock name."""
106
  return "%s/ganeti_lock_%s" % (constants.LOCK_DIR, subsystem)
107

  
108

  
109
def Lock(name, max_retries=None, debug=False):
110
  """Lock a given subsystem.
111

  
112
  In case the lock is already held by an alive process, the function
113
  will sleep indefintely and poll with a one second interval.
114

  
115
  When the optional integer argument 'max_retries' is passed with a
116
  non-zero value, the function will sleep only for this number of
117
  times, and then it will will raise a LockError if the lock can't be
118
  acquired. Passing in a negative number will cause only one try to
119
  get the lock. Passing a positive number will make the function retry
120
  for approximately that number of seconds.
121

  
122
  """
123
  lockfile = _GetLockFile(name)
124

  
125
  if name in _locksheld:
126
    raise errors.LockError('Lock "%s" already held!' % (name,))
127

  
128
  errcount = 0
129

  
130
  retries = 0
131
  while True:
132
    try:
133
      fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR | os.O_SYNC)
134
      break
135
    except OSError, creat_err:
136
      if creat_err.errno != errno.EEXIST:
137
        raise errors.LockError("Can't create the lock file. Error '%s'." %
138
                               str(creat_err))
139

  
140
      try:
141
        pf = open(lockfile, 'r')
142
      except IOError, open_err:
143
        errcount += 1
144
        if errcount >= 5:
145
          raise errors.LockError("Lock file exists but cannot be opened."
146
                                 " Error: '%s'." % str(open_err))
147
        time.sleep(1)
148
        continue
149

  
150
      try:
151
        pid = int(pf.read())
152
      except ValueError:
153
        raise errors.LockError("Invalid pid string in %s" %
154
                               (lockfile,))
155

  
156
      if not IsProcessAlive(pid):
157
        raise errors.LockError("Stale lockfile %s for pid %d?" %
158
                               (lockfile, pid))
159

  
160
      if max_retries and max_retries <= retries:
161
        raise errors.LockError("Can't acquire lock during the specified"
162
                               " time, aborting.")
163
      if retries == 5 and (debug or sys.stdin.isatty()):
164
        logger.ToStderr("Waiting for '%s' lock from pid %d..." % (name, pid))
165

  
166
      time.sleep(1)
167
      retries += 1
168
      continue
169

  
170
  os.write(fd, '%d\n' % (os.getpid(),))
171
  os.close(fd)
172

  
173
  _locksheld.append(name)
174

  
175

  
176
def Unlock(name):
177
  """Unlock a given subsystem.
178

  
179
  """
180
  lockfile = _GetLockFile(name)
181

  
182
  try:
183
    fd = os.open(lockfile, os.O_RDONLY)
184
  except OSError:
185
    raise errors.LockError('Lock "%s" not held.' % (name,))
186

  
187
  f = os.fdopen(fd, 'r')
188
  pid_str = f.read()
189

  
190
  try:
191
    pid = int(pid_str)
192
  except ValueError:
193
    raise errors.LockError('Unable to determine PID of locking process.')
194

  
195
  if pid != os.getpid():
196
    raise errors.LockError('Lock not held by me (%d != %d)' %
197
                           (os.getpid(), pid,))
198

  
199
  os.unlink(lockfile)
200
  _locksheld.remove(name)
201

  
202

  
203
def LockCleanup():
204
  """Remove all locks.
205

  
206
  """
207
  for lock in _locksheld:
208
    Unlock(lock)
209

  
210

  
211 104
def RunCmd(cmd):
212 105
  """Execute a (shell) command.
213 106

  
......
281 174
  return RunResult(exitcode, signal, out, err, strcmd)
282 175

  
283 176

  
284
def RunCmdUnlocked(cmd):
285
  """Execute a shell command without the 'cmd' lock.
286

  
287
  This variant of `RunCmd()` drops the 'cmd' lock before running the
288
  command and re-aquires it afterwards, thus it can be used to call
289
  other ganeti commands.
290

  
291
  The argument and return values are the same as for the `RunCmd()`
292
  function.
293

  
294
  Args:
295
    cmd - command to run. (str)
296

  
297
  Returns:
298
    `RunResult`
299

  
300
  """
301
  Unlock('cmd')
302
  ret = RunCmd(cmd)
303
  Lock('cmd')
304

  
305
  return ret
306

  
307

  
308 177
def RemoveFile(filename):
309 178
  """Remove a file ignoring some errors.
310 179

  
b/test/ganeti.utils_unittest.py
99 99
                 "noexisting process detected")
100 100

  
101 101

  
102
class TestLocking(unittest.TestCase):
103
  """Testing case for the Lock/Unlock functions"""
104

  
105
  def setUp(self):
106
    lock_dir = tempfile.mkdtemp(prefix="ganeti.unittest.",
107
                                suffix=".locking")
108
    self.old_lock_dir = constants.LOCK_DIR
109
    constants.LOCK_DIR = lock_dir
110

  
111
  def tearDown(self):
112
    try:
113
      ganeti.utils.Unlock("unittest")
114
    except LockError:
115
      pass
116
    shutil.rmtree(constants.LOCK_DIR, ignore_errors=True)
117
    constants.LOCK_DIR = self.old_lock_dir
118

  
119
  def clean_lock(self, name):
120
    try:
121
      ganeti.utils.Unlock("unittest")
122
    except LockError:
123
      pass
124

  
125

  
126
  def testLock(self):
127
    self.clean_lock("unittest")
128
    self.assertEqual(None, Lock("unittest"))
129

  
130

  
131
  def testUnlock(self):
132
    self.clean_lock("unittest")
133
    ganeti.utils.Lock("unittest")
134
    self.assertEqual(None, Unlock("unittest"))
135

  
136
  def testDoubleLock(self):
137
    self.clean_lock("unittest")
138
    ganeti.utils.Lock("unittest")
139
    self.assertRaises(LockError, Lock, "unittest")
140

  
141

  
142 102
class TestRunCmd(unittest.TestCase):
143 103
  """Testing case for the RunCmd function"""
144 104

  

Also available in: Unified diff