Move space reporting constant to constants.py
[ganeti-local] / 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, 0664), "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)