Revision 9d1b963f

b/Makefile.am
214 214
utils_PYTHON = \
215 215
	lib/utils/__init__.py \
216 216
	lib/utils/algo.py \
217
	lib/utils/filelock.py \
217 218
	lib/utils/hash.py \
218 219
	lib/utils/log.py \
219 220
	lib/utils/mlock.py \
......
486 487
	test/ganeti.ssh_unittest.py \
487 488
	test/ganeti.uidpool_unittest.py \
488 489
	test/ganeti.utils.algo_unittest.py \
490
	test/ganeti.utils.filelock_unittest.py \
489 491
	test/ganeti.utils.hash_unittest.py \
490 492
	test/ganeti.utils.mlock_unittest.py \
491 493
	test/ganeti.utils.retry_unittest.py \
b/lib/utils/__init__.py
60 60
from ganeti.utils.log import * # pylint: disable-msg=W0401
61 61
from ganeti.utils.hash import * # pylint: disable-msg=W0401
62 62
from ganeti.utils.wrapper import * # pylint: disable-msg=W0401
63
from ganeti.utils.filelock import * # pylint: disable-msg=W0401
63 64

  
64 65

  
65 66
#: when set to True, L{RunCmd} is disabled
......
2549 2550
  return bool(exitcode)
2550 2551

  
2551 2552

  
2552
def LockFile(fd):
2553
  """Locks a file using POSIX locks.
2554

  
2555
  @type fd: int
2556
  @param fd: the file descriptor we need to lock
2557

  
2558
  """
2559
  try:
2560
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
2561
  except IOError, err:
2562
    if err.errno == errno.EAGAIN:
2563
      raise errors.LockError("File already locked")
2564
    raise
2565

  
2566

  
2567 2553
def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
2568 2554
  """Reads the watcher pause file.
2569 2555

  
......
2658 2644
  WriteFile(filename, mode=0400, data=key_pem + cert_pem)
2659 2645

  
2660 2646

  
2661
class FileLock(object):
2662
  """Utility class for file locks.
2663

  
2664
  """
2665
  def __init__(self, fd, filename):
2666
    """Constructor for FileLock.
2667

  
2668
    @type fd: file
2669
    @param fd: File object
2670
    @type filename: str
2671
    @param filename: Path of the file opened at I{fd}
2672

  
2673
    """
2674
    self.fd = fd
2675
    self.filename = filename
2676

  
2677
  @classmethod
2678
  def Open(cls, filename):
2679
    """Creates and opens a file to be used as a file-based lock.
2680

  
2681
    @type filename: string
2682
    @param filename: path to the file to be locked
2683

  
2684
    """
2685
    # Using "os.open" is necessary to allow both opening existing file
2686
    # read/write and creating if not existing. Vanilla "open" will truncate an
2687
    # existing file -or- allow creating if not existing.
2688
    return cls(os.fdopen(os.open(filename, os.O_RDWR | os.O_CREAT), "w+"),
2689
               filename)
2690

  
2691
  def __del__(self):
2692
    self.Close()
2693

  
2694
  def Close(self):
2695
    """Close the file and release the lock.
2696

  
2697
    """
2698
    if hasattr(self, "fd") and self.fd:
2699
      self.fd.close()
2700
      self.fd = None
2701

  
2702
  def _flock(self, flag, blocking, timeout, errmsg):
2703
    """Wrapper for fcntl.flock.
2704

  
2705
    @type flag: int
2706
    @param flag: operation flag
2707
    @type blocking: bool
2708
    @param blocking: whether the operation should be done in blocking mode.
2709
    @type timeout: None or float
2710
    @param timeout: for how long the operation should be retried (implies
2711
                    non-blocking mode).
2712
    @type errmsg: string
2713
    @param errmsg: error message in case operation fails.
2714

  
2715
    """
2716
    assert self.fd, "Lock was closed"
2717
    assert timeout is None or timeout >= 0, \
2718
      "If specified, timeout must be positive"
2719
    assert not (flag & fcntl.LOCK_NB), "LOCK_NB must not be set"
2720

  
2721
    # When a timeout is used, LOCK_NB must always be set
2722
    if not (timeout is None and blocking):
2723
      flag |= fcntl.LOCK_NB
2724

  
2725
    if timeout is None:
2726
      self._Lock(self.fd, flag, timeout)
2727
    else:
2728
      try:
2729
        Retry(self._Lock, (0.1, 1.2, 1.0), timeout,
2730
              args=(self.fd, flag, timeout))
2731
      except RetryTimeout:
2732
        raise errors.LockError(errmsg)
2733

  
2734
  @staticmethod
2735
  def _Lock(fd, flag, timeout):
2736
    try:
2737
      fcntl.flock(fd, flag)
2738
    except IOError, err:
2739
      if timeout is not None and err.errno == errno.EAGAIN:
2740
        raise RetryAgain()
2741

  
2742
      logging.exception("fcntl.flock failed")
2743
      raise
2744

  
2745
  def Exclusive(self, blocking=False, timeout=None):
2746
    """Locks the file in exclusive mode.
2747

  
2748
    @type blocking: boolean
2749
    @param blocking: whether to block and wait until we
2750
        can lock the file or return immediately
2751
    @type timeout: int or None
2752
    @param timeout: if not None, the duration to wait for the lock
2753
        (in blocking mode)
2754

  
2755
    """
2756
    self._flock(fcntl.LOCK_EX, blocking, timeout,
2757
                "Failed to lock %s in exclusive mode" % self.filename)
2758

  
2759
  def Shared(self, blocking=False, timeout=None):
2760
    """Locks the file in shared mode.
2761

  
2762
    @type blocking: boolean
2763
    @param blocking: whether to block and wait until we
2764
        can lock the file or return immediately
2765
    @type timeout: int or None
2766
    @param timeout: if not None, the duration to wait for the lock
2767
        (in blocking mode)
2768

  
2769
    """
2770
    self._flock(fcntl.LOCK_SH, blocking, timeout,
2771
                "Failed to lock %s in shared mode" % self.filename)
2772

  
2773
  def Unlock(self, blocking=True, timeout=None):
2774
    """Unlocks the file.
2775

  
2776
    According to C{flock(2)}, unlocking can also be a nonblocking
2777
    operation::
2778

  
2779
      To make a non-blocking request, include LOCK_NB with any of the above
2780
      operations.
2781

  
2782
    @type blocking: boolean
2783
    @param blocking: whether to block and wait until we
2784
        can lock the file or return immediately
2785
    @type timeout: int or None
2786
    @param timeout: if not None, the duration to wait for the lock
2787
        (in blocking mode)
2788

  
2789
    """
2790
    self._flock(fcntl.LOCK_UN, blocking, timeout,
2791
                "Failed to unlock %s" % self.filename)
2792

  
2793

  
2794 2647
def SignalHandled(signums):
2795 2648
  """Signal Handled decoration.
2796 2649

  
b/lib/utils/filelock.py
1
#
2
#
3

  
4
# Copyright (C) 2006, 2007, 2010, 2011 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
"""Utility functions for file-based locks.
22

  
23
"""
24

  
25
import fcntl
26
import errno
27
import os
28
import logging
29

  
30
from ganeti import errors
31
from ganeti.utils import retry
32

  
33

  
34
def LockFile(fd):
35
  """Locks a file using POSIX locks.
36

  
37
  @type fd: int
38
  @param fd: the file descriptor we need to lock
39

  
40
  """
41
  try:
42
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
43
  except IOError, err:
44
    if err.errno == errno.EAGAIN:
45
      raise errors.LockError("File already locked")
46
    raise
47

  
48

  
49
class FileLock(object):
50
  """Utility class for file locks.
51

  
52
  """
53
  def __init__(self, fd, filename):
54
    """Constructor for FileLock.
55

  
56
    @type fd: file
57
    @param fd: File object
58
    @type filename: str
59
    @param filename: Path of the file opened at I{fd}
60

  
61
    """
62
    self.fd = fd
63
    self.filename = filename
64

  
65
  @classmethod
66
  def Open(cls, filename):
67
    """Creates and opens a file to be used as a file-based lock.
68

  
69
    @type filename: string
70
    @param filename: path to the file to be locked
71

  
72
    """
73
    # Using "os.open" is necessary to allow both opening existing file
74
    # read/write and creating if not existing. Vanilla "open" will truncate an
75
    # existing file -or- allow creating if not existing.
76
    return cls(os.fdopen(os.open(filename, os.O_RDWR | os.O_CREAT), "w+"),
77
               filename)
78

  
79
  def __del__(self):
80
    self.Close()
81

  
82
  def Close(self):
83
    """Close the file and release the lock.
84

  
85
    """
86
    if hasattr(self, "fd") and self.fd:
87
      self.fd.close()
88
      self.fd = None
89

  
90
  def _flock(self, flag, blocking, timeout, errmsg):
91
    """Wrapper for fcntl.flock.
92

  
93
    @type flag: int
94
    @param flag: operation flag
95
    @type blocking: bool
96
    @param blocking: whether the operation should be done in blocking mode.
97
    @type timeout: None or float
98
    @param timeout: for how long the operation should be retried (implies
99
                    non-blocking mode).
100
    @type errmsg: string
101
    @param errmsg: error message in case operation fails.
102

  
103
    """
104
    assert self.fd, "Lock was closed"
105
    assert timeout is None or timeout >= 0, \
106
      "If specified, timeout must be positive"
107
    assert not (flag & fcntl.LOCK_NB), "LOCK_NB must not be set"
108

  
109
    # When a timeout is used, LOCK_NB must always be set
110
    if not (timeout is None and blocking):
111
      flag |= fcntl.LOCK_NB
112

  
113
    if timeout is None:
114
      self._Lock(self.fd, flag, timeout)
115
    else:
116
      try:
117
        retry.Retry(self._Lock, (0.1, 1.2, 1.0), timeout,
118
                    args=(self.fd, flag, timeout))
119
      except retry.RetryTimeout:
120
        raise errors.LockError(errmsg)
121

  
122
  @staticmethod
123
  def _Lock(fd, flag, timeout):
124
    try:
125
      fcntl.flock(fd, flag)
126
    except IOError, err:
127
      if timeout is not None and err.errno == errno.EAGAIN:
128
        raise retry.RetryAgain()
129

  
130
      logging.exception("fcntl.flock failed")
131
      raise
132

  
133
  def Exclusive(self, blocking=False, timeout=None):
134
    """Locks the file in exclusive mode.
135

  
136
    @type blocking: boolean
137
    @param blocking: whether to block and wait until we
138
        can lock the file or return immediately
139
    @type timeout: int or None
140
    @param timeout: if not None, the duration to wait for the lock
141
        (in blocking mode)
142

  
143
    """
144
    self._flock(fcntl.LOCK_EX, blocking, timeout,
145
                "Failed to lock %s in exclusive mode" % self.filename)
146

  
147
  def Shared(self, blocking=False, timeout=None):
148
    """Locks the file in shared mode.
149

  
150
    @type blocking: boolean
151
    @param blocking: whether to block and wait until we
152
        can lock the file or return immediately
153
    @type timeout: int or None
154
    @param timeout: if not None, the duration to wait for the lock
155
        (in blocking mode)
156

  
157
    """
158
    self._flock(fcntl.LOCK_SH, blocking, timeout,
159
                "Failed to lock %s in shared mode" % self.filename)
160

  
161
  def Unlock(self, blocking=True, timeout=None):
162
    """Unlocks the file.
163

  
164
    According to C{flock(2)}, unlocking can also be a nonblocking
165
    operation::
166

  
167
      To make a non-blocking request, include LOCK_NB with any of the above
168
      operations.
169

  
170
    @type blocking: boolean
171
    @param blocking: whether to block and wait until we
172
        can lock the file or return immediately
173
    @type timeout: int or None
174
    @param timeout: if not None, the duration to wait for the lock
175
        (in blocking mode)
176

  
177
    """
178
    self._flock(fcntl.LOCK_UN, blocking, timeout,
179
                "Failed to unlock %s" % self.filename)
b/test/ganeti.utils.filelock_unittest.py
1
#!/usr/bin/python
2
#
3

  
4
# Copyright (C) 2006, 2007, 2010, 2011 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
"""Script for testing ganeti.utils.filelock"""
23

  
24
import os
25
import tempfile
26
import unittest
27

  
28
from ganeti import constants
29
from ganeti import utils
30
from ganeti import errors
31

  
32
import testutils
33

  
34

  
35
class _BaseFileLockTest:
36
  """Test case for the FileLock class"""
37

  
38
  def testSharedNonblocking(self):
39
    self.lock.Shared(blocking=False)
40
    self.lock.Close()
41

  
42
  def testExclusiveNonblocking(self):
43
    self.lock.Exclusive(blocking=False)
44
    self.lock.Close()
45

  
46
  def testUnlockNonblocking(self):
47
    self.lock.Unlock(blocking=False)
48
    self.lock.Close()
49

  
50
  def testSharedBlocking(self):
51
    self.lock.Shared(blocking=True)
52
    self.lock.Close()
53

  
54
  def testExclusiveBlocking(self):
55
    self.lock.Exclusive(blocking=True)
56
    self.lock.Close()
57

  
58
  def testUnlockBlocking(self):
59
    self.lock.Unlock(blocking=True)
60
    self.lock.Close()
61

  
62
  def testSharedExclusiveUnlock(self):
63
    self.lock.Shared(blocking=False)
64
    self.lock.Exclusive(blocking=False)
65
    self.lock.Unlock(blocking=False)
66
    self.lock.Close()
67

  
68
  def testExclusiveSharedUnlock(self):
69
    self.lock.Exclusive(blocking=False)
70
    self.lock.Shared(blocking=False)
71
    self.lock.Unlock(blocking=False)
72
    self.lock.Close()
73

  
74
  def testSimpleTimeout(self):
75
    # These will succeed on the first attempt, hence a short timeout
76
    self.lock.Shared(blocking=True, timeout=10.0)
77
    self.lock.Exclusive(blocking=False, timeout=10.0)
78
    self.lock.Unlock(blocking=True, timeout=10.0)
79
    self.lock.Close()
80

  
81
  @staticmethod
82
  def _TryLockInner(filename, shared, blocking):
83
    lock = utils.FileLock.Open(filename)
84

  
85
    if shared:
86
      fn = lock.Shared
87
    else:
88
      fn = lock.Exclusive
89

  
90
    try:
91
      # The timeout doesn't really matter as the parent process waits for us to
92
      # finish anyway.
93
      fn(blocking=blocking, timeout=0.01)
94
    except errors.LockError, err:
95
      return False
96

  
97
    return True
98

  
99
  def _TryLock(self, *args):
100
    return utils.RunInSeparateProcess(self._TryLockInner, self.tmpfile.name,
101
                                      *args)
102

  
103
  def testTimeout(self):
104
    for blocking in [True, False]:
105
      self.lock.Exclusive(blocking=True)
106
      self.failIf(self._TryLock(False, blocking))
107
      self.failIf(self._TryLock(True, blocking))
108

  
109
      self.lock.Shared(blocking=True)
110
      self.assert_(self._TryLock(True, blocking))
111
      self.failIf(self._TryLock(False, blocking))
112

  
113
  def testCloseShared(self):
114
    self.lock.Close()
115
    self.assertRaises(AssertionError, self.lock.Shared, blocking=False)
116

  
117
  def testCloseExclusive(self):
118
    self.lock.Close()
119
    self.assertRaises(AssertionError, self.lock.Exclusive, blocking=False)
120

  
121
  def testCloseUnlock(self):
122
    self.lock.Close()
123
    self.assertRaises(AssertionError, self.lock.Unlock, blocking=False)
124

  
125

  
126
class TestFileLockWithFilename(testutils.GanetiTestCase, _BaseFileLockTest):
127
  TESTDATA = "Hello World\n" * 10
128

  
129
  def setUp(self):
130
    testutils.GanetiTestCase.setUp(self)
131

  
132
    self.tmpfile = tempfile.NamedTemporaryFile()
133
    utils.WriteFile(self.tmpfile.name, data=self.TESTDATA)
134
    self.lock = utils.FileLock.Open(self.tmpfile.name)
135

  
136
    # Ensure "Open" didn't truncate file
137
    self.assertFileContent(self.tmpfile.name, self.TESTDATA)
138

  
139
  def tearDown(self):
140
    self.assertFileContent(self.tmpfile.name, self.TESTDATA)
141

  
142
    testutils.GanetiTestCase.tearDown(self)
143

  
144

  
145
class TestFileLockWithFileObject(unittest.TestCase, _BaseFileLockTest):
146
  def setUp(self):
147
    self.tmpfile = tempfile.NamedTemporaryFile()
148
    self.lock = utils.FileLock(open(self.tmpfile.name, "w"), self.tmpfile.name)
149

  
150

  
151
if __name__ == "__main__":
152
  testutils.GanetiTestProgram()
b/test/ganeti.utils_unittest.py
1165 1165
      self.failUnlessEqual(TailFile(fname, lines=i), data[-i:])
1166 1166

  
1167 1167

  
1168
class _BaseFileLockTest:
1169
  """Test case for the FileLock class"""
1170

  
1171
  def testSharedNonblocking(self):
1172
    self.lock.Shared(blocking=False)
1173
    self.lock.Close()
1174

  
1175
  def testExclusiveNonblocking(self):
1176
    self.lock.Exclusive(blocking=False)
1177
    self.lock.Close()
1178

  
1179
  def testUnlockNonblocking(self):
1180
    self.lock.Unlock(blocking=False)
1181
    self.lock.Close()
1182

  
1183
  def testSharedBlocking(self):
1184
    self.lock.Shared(blocking=True)
1185
    self.lock.Close()
1186

  
1187
  def testExclusiveBlocking(self):
1188
    self.lock.Exclusive(blocking=True)
1189
    self.lock.Close()
1190

  
1191
  def testUnlockBlocking(self):
1192
    self.lock.Unlock(blocking=True)
1193
    self.lock.Close()
1194

  
1195
  def testSharedExclusiveUnlock(self):
1196
    self.lock.Shared(blocking=False)
1197
    self.lock.Exclusive(blocking=False)
1198
    self.lock.Unlock(blocking=False)
1199
    self.lock.Close()
1200

  
1201
  def testExclusiveSharedUnlock(self):
1202
    self.lock.Exclusive(blocking=False)
1203
    self.lock.Shared(blocking=False)
1204
    self.lock.Unlock(blocking=False)
1205
    self.lock.Close()
1206

  
1207
  def testSimpleTimeout(self):
1208
    # These will succeed on the first attempt, hence a short timeout
1209
    self.lock.Shared(blocking=True, timeout=10.0)
1210
    self.lock.Exclusive(blocking=False, timeout=10.0)
1211
    self.lock.Unlock(blocking=True, timeout=10.0)
1212
    self.lock.Close()
1213

  
1214
  @staticmethod
1215
  def _TryLockInner(filename, shared, blocking):
1216
    lock = utils.FileLock.Open(filename)
1217

  
1218
    if shared:
1219
      fn = lock.Shared
1220
    else:
1221
      fn = lock.Exclusive
1222

  
1223
    try:
1224
      # The timeout doesn't really matter as the parent process waits for us to
1225
      # finish anyway.
1226
      fn(blocking=blocking, timeout=0.01)
1227
    except errors.LockError, err:
1228
      return False
1229

  
1230
    return True
1231

  
1232
  def _TryLock(self, *args):
1233
    return utils.RunInSeparateProcess(self._TryLockInner, self.tmpfile.name,
1234
                                      *args)
1235

  
1236
  def testTimeout(self):
1237
    for blocking in [True, False]:
1238
      self.lock.Exclusive(blocking=True)
1239
      self.failIf(self._TryLock(False, blocking))
1240
      self.failIf(self._TryLock(True, blocking))
1241

  
1242
      self.lock.Shared(blocking=True)
1243
      self.assert_(self._TryLock(True, blocking))
1244
      self.failIf(self._TryLock(False, blocking))
1245

  
1246
  def testCloseShared(self):
1247
    self.lock.Close()
1248
    self.assertRaises(AssertionError, self.lock.Shared, blocking=False)
1249

  
1250
  def testCloseExclusive(self):
1251
    self.lock.Close()
1252
    self.assertRaises(AssertionError, self.lock.Exclusive, blocking=False)
1253

  
1254
  def testCloseUnlock(self):
1255
    self.lock.Close()
1256
    self.assertRaises(AssertionError, self.lock.Unlock, blocking=False)
1257

  
1258

  
1259
class TestFileLockWithFilename(testutils.GanetiTestCase, _BaseFileLockTest):
1260
  TESTDATA = "Hello World\n" * 10
1261

  
1262
  def setUp(self):
1263
    testutils.GanetiTestCase.setUp(self)
1264

  
1265
    self.tmpfile = tempfile.NamedTemporaryFile()
1266
    utils.WriteFile(self.tmpfile.name, data=self.TESTDATA)
1267
    self.lock = utils.FileLock.Open(self.tmpfile.name)
1268

  
1269
    # Ensure "Open" didn't truncate file
1270
    self.assertFileContent(self.tmpfile.name, self.TESTDATA)
1271

  
1272
  def tearDown(self):
1273
    self.assertFileContent(self.tmpfile.name, self.TESTDATA)
1274

  
1275
    testutils.GanetiTestCase.tearDown(self)
1276

  
1277

  
1278
class TestFileLockWithFileObject(unittest.TestCase, _BaseFileLockTest):
1279
  def setUp(self):
1280
    self.tmpfile = tempfile.NamedTemporaryFile()
1281
    self.lock = utils.FileLock(open(self.tmpfile.name, "w"), self.tmpfile.name)
1282

  
1283

  
1284 1168
class TestTimeFunctions(unittest.TestCase):
1285 1169
  """Test case for time functions"""
1286 1170

  

Also available in: Unified diff