blob: 3852bf212eba5bed0fb7ffe73cdf4722a9f5ef07 [file] [log] [blame]
asharifb237bca2013-02-15 20:54:57 +00001#!/usr/bin/python2.6
2#
3# Copyright 2010 Google Inc. All Rights Reserved.
4
yunlian52a6c572013-02-19 20:42:07 +00005"""Script to lock/unlock machines."""
asharifb237bca2013-02-15 20:54:57 +00006
7__author__ = "asharif@google.com (Ahmad Sharif)"
8
9import datetime
asharif455157b2013-02-15 21:15:05 +000010import fcntl
asharifda8e93b2013-02-15 22:49:27 +000011import getpass
asharifb237bca2013-02-15 20:54:57 +000012import glob
13import optparse
14import os
asharif455157b2013-02-15 21:15:05 +000015import pickle
asharifb237bca2013-02-15 20:54:57 +000016import socket
asharif455157b2013-02-15 21:15:05 +000017import sys
18import time
yunlian52a6c572013-02-19 20:42:07 +000019
asharifb237bca2013-02-15 20:54:57 +000020from utils import logger
21
22
asharif455157b2013-02-15 21:15:05 +000023class FileCreationMask(object):
24 def __init__(self, mask):
25 self._mask = mask
26
27 def __enter__(self):
28 self._old_mask = os.umask(self._mask)
29
30 def __exit__(self, type, value, traceback):
31 os.umask(self._old_mask)
asharifb237bca2013-02-15 20:54:57 +000032
33
asharif455157b2013-02-15 21:15:05 +000034class LockDescription(object):
35 def __init__(self):
36 self.owner = ""
37 self.exclusive = False
38 self.counter = 0
39 self.time = 0
40 self.reason = ""
asharifb237bca2013-02-15 20:54:57 +000041
asharif455157b2013-02-15 21:15:05 +000042 def IsLocked(self):
43 return self.counter or self.exclusive
asharifb237bca2013-02-15 20:54:57 +000044
asharif455157b2013-02-15 21:15:05 +000045 def __str__(self):
46 return " ".join(["Owner: %s" % self.owner,
47 "Exclusive: %s" % self.exclusive,
48 "Counter: %s" % self.counter,
49 "Time: %s" % self.time,
50 "Reason: %s" % self.reason])
asharifb237bca2013-02-15 20:54:57 +000051
52
asharif455157b2013-02-15 21:15:05 +000053class FileLock(object):
asharif455157b2013-02-15 21:15:05 +000054
55 def __init__(self, lock_filename):
yunlian52a6c572013-02-19 20:42:07 +000056 self._filepath = lock_filename
57 lock_dir = os.path.dirname(lock_filename)
58 assert os.path.isdir(lock_dir), (
59 "Locks dir: %s doesn't exist!" % lock_dir)
asharif455157b2013-02-15 21:15:05 +000060 self._file = None
61
62 @classmethod
63 def AsString(cls, file_locks):
64 stringify_fmt = "%-30s %-15s %-4s %-4s %-15s %-40s"
65 header = stringify_fmt % ("machine", "owner", "excl", "ctr",
66 "elapsed", "reason")
67 lock_strings = []
68 for file_lock in file_locks:
69
70 elapsed_time = datetime.timedelta(
71 seconds=int(time.time() - file_lock._description.time))
72 elapsed_time = "%s ago" % elapsed_time
73 lock_strings.append(stringify_fmt %
74 (os.path.basename(file_lock._filepath),
75 file_lock._description.owner,
76 file_lock._description.exclusive,
77 file_lock._description.counter,
78 elapsed_time,
79 file_lock._description.reason))
80 table = "\n".join(lock_strings)
81 return "\n".join([header, table])
82
83 @classmethod
yunlian52a6c572013-02-19 20:42:07 +000084 def ListLock(cls, pattern, locks_dir):
85 if not locks_dir:
86 locks_dir = Machine.LOCKS_DIR
87 full_pattern = os.path.join(locks_dir, pattern)
asharif455157b2013-02-15 21:15:05 +000088 file_locks = []
89 for lock_filename in glob.glob(full_pattern):
90 file_lock = FileLock(lock_filename)
91 with file_lock as lock:
92 if lock.IsLocked():
93 file_locks.append(file_lock)
94 logger.GetLogger().LogOutput("\n%s" % cls.AsString(file_locks))
95
96 def __enter__(self):
97 with FileCreationMask(0000):
98 try:
99 self._file = open(self._filepath, "a+")
100 self._file.seek(0, os.SEEK_SET)
101
102 if fcntl.flock(self._file.fileno(), fcntl.LOCK_EX) == -1:
103 raise IOError("flock(%s, LOCK_EX) failed!" % self._filepath)
104
105 try:
106 self._description = pickle.load(self._file)
107 except (EOFError, pickle.PickleError):
108 self._description = LockDescription()
109 return self._description
110 # Check this differently?
111 except IOError as ex:
112 logger.GetLogger().LogError(ex)
113 return None
114
115 def __exit__(self, type, value, traceback):
116 self._file.truncate(0)
117 self._file.write(pickle.dumps(self._description))
118 self._file.close()
119
120 def __str__(self):
121 return self.AsString([self])
122
123
124class Lock(object):
yunlian52a6c572013-02-19 20:42:07 +0000125 def __init__(self, lock_file):
126 self._to_lock = os.path.basename(lock_file)
127 self._lock_file = lock_file
asharif455157b2013-02-15 21:15:05 +0000128 self._logger = logger.GetLogger()
129
130 def NonBlockingLock(self, exclusive, reason=""):
yunlian52a6c572013-02-19 20:42:07 +0000131 with FileLock(self._lock_file) as lock:
asharif455157b2013-02-15 21:15:05 +0000132 if lock.exclusive:
133 self._logger.LogError(
134 "Exclusive lock already acquired by %s. Reason: %s" %
135 (lock.owner, lock.reason))
136 return False
137
138 if exclusive:
139 if lock.counter:
140 self._logger.LogError("Shared lock already acquired")
141 return False
142 lock.exclusive = True
143 lock.reason = reason
asharifda8e93b2013-02-15 22:49:27 +0000144 lock.owner = getpass.getuser()
asharif455157b2013-02-15 21:15:05 +0000145 lock.time = time.time()
146 else:
147 lock.counter += 1
148 self._logger.LogOutput("Successfully locked: %s" % self._to_lock)
149 return True
150
151 def Unlock(self, exclusive, force=False):
yunlian52a6c572013-02-19 20:42:07 +0000152 with FileLock(self._lock_file) as lock:
asharif455157b2013-02-15 21:15:05 +0000153 if not lock.IsLocked():
154 self._logger.LogError("Can't unlock unlocked machine!")
155 return False
156
157 if lock.exclusive != exclusive:
158 self._logger.LogError("shared locks must be unlocked with --shared")
159 return False
160
161 if lock.exclusive:
asharifda8e93b2013-02-15 22:49:27 +0000162 if lock.owner != getpass.getuser() and not force:
asharif455157b2013-02-15 21:15:05 +0000163 self._logger.LogError("%s can't unlock lock owned by: %s" %
asharifda8e93b2013-02-15 22:49:27 +0000164 (getpass.getuser(), lock.owner))
asharif455157b2013-02-15 21:15:05 +0000165 return False
166 lock.exclusive = False
167 lock.reason = ""
168 lock.owner = ""
169 else:
170 lock.counter -= 1
171 return True
172
173
174class Machine(object):
yunlian52a6c572013-02-19 20:42:07 +0000175 LOCKS_DIR = "/home/mobiletc-prebuild/locks"
176 def __init__(self, name, locks_dir=LOCKS_DIR):
asharif455157b2013-02-15 21:15:05 +0000177 self._name = name
178 try:
179 self._full_name = socket.gethostbyaddr(name)[0]
180 except socket.error:
181 self._full_name = self._name
yunlian52a6c572013-02-19 20:42:07 +0000182 self._full_name = os.path.join(locks_dir, self._full_name)
asharif455157b2013-02-15 21:15:05 +0000183
184 def Lock(self, exclusive=False, reason=""):
185 lock = Lock(self._full_name)
186 return lock.NonBlockingLock(exclusive, reason)
187
188 def Unlock(self, exclusive=False, ignore_ownership=False):
189 lock = Lock(self._full_name)
190 return lock.Unlock(exclusive, ignore_ownership)
asharifb237bca2013-02-15 20:54:57 +0000191
192
193def Main(argv):
194 """The main function."""
asharifb237bca2013-02-15 20:54:57 +0000195 parser = optparse.OptionParser()
asharifb237bca2013-02-15 20:54:57 +0000196 parser.add_option("-r",
197 "--reason",
198 dest="reason",
asharif455157b2013-02-15 21:15:05 +0000199 default="",
asharifb237bca2013-02-15 20:54:57 +0000200 help="The lock reason.")
201 parser.add_option("-u",
202 "--unlock",
203 dest="unlock",
204 action="store_true",
205 default=False,
206 help="Use this to unlock.")
207 parser.add_option("-l",
208 "--list_locks",
209 dest="list_locks",
210 action="store_true",
211 default=False,
212 help="Use this to list locks.")
asharif455157b2013-02-15 21:15:05 +0000213 parser.add_option("-f",
214 "--ignore_ownership",
215 dest="ignore_ownership",
216 action="store_true",
217 default=False,
218 help="Use this to force unlock on a lock you don't own.")
219 parser.add_option("-s",
220 "--shared",
221 dest="shared",
222 action="store_true",
223 default=False,
224 help="Use this for a shared (non-exclusive) lock.")
yunlian52a6c572013-02-19 20:42:07 +0000225 parser.add_option("-d",
226 "--dir",
227 dest="locks_dir",
228 action="store",
229 default=Machine.LOCKS_DIR,
230 help="Use this to set different locks_dir")
asharifb237bca2013-02-15 20:54:57 +0000231
asharif455157b2013-02-15 21:15:05 +0000232 options, args = parser.parse_args(argv)
asharifb237bca2013-02-15 20:54:57 +0000233
yunlian52a6c572013-02-19 20:42:07 +0000234 options.locks_dir = os.path.abspath(options.locks_dir)
asharif455157b2013-02-15 21:15:05 +0000235 exclusive = not options.shared
236
237 if not options.list_locks and len(args) != 2:
238 logger.GetLogger().LogError(
239 "Either --list_locks or a machine arg is needed.")
asharifb237bca2013-02-15 20:54:57 +0000240 return 1
241
asharif455157b2013-02-15 21:15:05 +0000242 if len(args) > 1:
yunlian52a6c572013-02-19 20:42:07 +0000243 machine = Machine(args[1], options.locks_dir)
asharif455157b2013-02-15 21:15:05 +0000244 else:
245 machine = None
asharifb237bca2013-02-15 20:54:57 +0000246
247 if options.list_locks:
yunlian52a6c572013-02-19 20:42:07 +0000248 FileLock.ListLock("*", options.locks_dir)
asharif455157b2013-02-15 21:15:05 +0000249 retval = True
250 elif options.unlock:
251 retval = machine.Unlock(exclusive, options.ignore_ownership)
asharifb237bca2013-02-15 20:54:57 +0000252 else:
asharif455157b2013-02-15 21:15:05 +0000253 retval = machine.Lock(exclusive, options.reason)
254
255 if retval:
256 return 0
257 else:
258 return 1
asharifb237bca2013-02-15 20:54:57 +0000259
260if __name__ == "__main__":
asharif455157b2013-02-15 21:15:05 +0000261 sys.exit(Main(sys.argv))