blob: 7bfc013ea1e2fec67a6cb4d9cedbaeba1910b9a8 [file] [log] [blame]
yunlian8a3bdcb2013-02-19 20:42:48 +00001#!/usr/bin/python
asharifb237bca2013-02-15 20:54:57 +00002#
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
yunlian8a3bdcb2013-02-19 20:42:48 +000013import json
asharifb237bca2013-02-15 20:54:57 +000014import optparse
15import os
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):
yunlian8a3bdcb2013-02-19 20:42:48 +000035 """The description of the lock."""
36
37 def __init__(self, desc):
38 try:
39 self.owner = desc["owner"]
40 self.exclusive = desc["exclusive"]
41 self.counter = desc["counter"]
42 self.time = desc["time"]
43 self.reason = desc["reason"]
44 except (KeyError, TypeError):
45 self.owner = ""
46 self.exclusive = False
47 self.counter = 0
48 self.time = 0
49 self.reason = ""
asharifb237bca2013-02-15 20:54:57 +000050
asharif455157b2013-02-15 21:15:05 +000051 def IsLocked(self):
52 return self.counter or self.exclusive
asharifb237bca2013-02-15 20:54:57 +000053
asharif455157b2013-02-15 21:15:05 +000054 def __str__(self):
55 return " ".join(["Owner: %s" % self.owner,
56 "Exclusive: %s" % self.exclusive,
57 "Counter: %s" % self.counter,
58 "Time: %s" % self.time,
59 "Reason: %s" % self.reason])
asharifb237bca2013-02-15 20:54:57 +000060
61
asharif455157b2013-02-15 21:15:05 +000062class FileLock(object):
yunlian8a3bdcb2013-02-19 20:42:48 +000063 """File lock operation class."""
asharif455157b2013-02-15 21:15:05 +000064
65 def __init__(self, lock_filename):
yunlian52a6c572013-02-19 20:42:07 +000066 self._filepath = lock_filename
67 lock_dir = os.path.dirname(lock_filename)
68 assert os.path.isdir(lock_dir), (
69 "Locks dir: %s doesn't exist!" % lock_dir)
asharif455157b2013-02-15 21:15:05 +000070 self._file = None
71
72 @classmethod
73 def AsString(cls, file_locks):
74 stringify_fmt = "%-30s %-15s %-4s %-4s %-15s %-40s"
75 header = stringify_fmt % ("machine", "owner", "excl", "ctr",
76 "elapsed", "reason")
77 lock_strings = []
78 for file_lock in file_locks:
79
80 elapsed_time = datetime.timedelta(
81 seconds=int(time.time() - file_lock._description.time))
82 elapsed_time = "%s ago" % elapsed_time
83 lock_strings.append(stringify_fmt %
84 (os.path.basename(file_lock._filepath),
85 file_lock._description.owner,
86 file_lock._description.exclusive,
87 file_lock._description.counter,
88 elapsed_time,
89 file_lock._description.reason))
90 table = "\n".join(lock_strings)
91 return "\n".join([header, table])
92
93 @classmethod
yunlian52a6c572013-02-19 20:42:07 +000094 def ListLock(cls, pattern, locks_dir):
95 if not locks_dir:
96 locks_dir = Machine.LOCKS_DIR
97 full_pattern = os.path.join(locks_dir, pattern)
asharif455157b2013-02-15 21:15:05 +000098 file_locks = []
99 for lock_filename in glob.glob(full_pattern):
100 file_lock = FileLock(lock_filename)
101 with file_lock as lock:
102 if lock.IsLocked():
103 file_locks.append(file_lock)
104 logger.GetLogger().LogOutput("\n%s" % cls.AsString(file_locks))
105
106 def __enter__(self):
107 with FileCreationMask(0000):
108 try:
109 self._file = open(self._filepath, "a+")
110 self._file.seek(0, os.SEEK_SET)
111
112 if fcntl.flock(self._file.fileno(), fcntl.LOCK_EX) == -1:
113 raise IOError("flock(%s, LOCK_EX) failed!" % self._filepath)
114
115 try:
yunlian8a3bdcb2013-02-19 20:42:48 +0000116 desc = json.load(self._file)
117 except (EOFError, ValueError):
118 desc = None
119 self._description = LockDescription(desc)
120
asharif455157b2013-02-15 21:15:05 +0000121 return self._description
122 # Check this differently?
123 except IOError as ex:
124 logger.GetLogger().LogError(ex)
125 return None
126
127 def __exit__(self, type, value, traceback):
128 self._file.truncate(0)
yunlian8a3bdcb2013-02-19 20:42:48 +0000129 self._file.write(json.dumps(self._description.__dict__, skipkeys=True))
asharif455157b2013-02-15 21:15:05 +0000130 self._file.close()
131
132 def __str__(self):
133 return self.AsString([self])
134
135
136class Lock(object):
yunlian52a6c572013-02-19 20:42:07 +0000137 def __init__(self, lock_file):
138 self._to_lock = os.path.basename(lock_file)
139 self._lock_file = lock_file
asharif455157b2013-02-15 21:15:05 +0000140 self._logger = logger.GetLogger()
141
142 def NonBlockingLock(self, exclusive, reason=""):
yunlian52a6c572013-02-19 20:42:07 +0000143 with FileLock(self._lock_file) as lock:
asharif455157b2013-02-15 21:15:05 +0000144 if lock.exclusive:
145 self._logger.LogError(
146 "Exclusive lock already acquired by %s. Reason: %s" %
147 (lock.owner, lock.reason))
148 return False
149
150 if exclusive:
151 if lock.counter:
152 self._logger.LogError("Shared lock already acquired")
153 return False
154 lock.exclusive = True
155 lock.reason = reason
asharifda8e93b2013-02-15 22:49:27 +0000156 lock.owner = getpass.getuser()
asharif455157b2013-02-15 21:15:05 +0000157 lock.time = time.time()
158 else:
159 lock.counter += 1
160 self._logger.LogOutput("Successfully locked: %s" % self._to_lock)
161 return True
162
163 def Unlock(self, exclusive, force=False):
yunlian52a6c572013-02-19 20:42:07 +0000164 with FileLock(self._lock_file) as lock:
asharif455157b2013-02-15 21:15:05 +0000165 if not lock.IsLocked():
166 self._logger.LogError("Can't unlock unlocked machine!")
167 return False
168
169 if lock.exclusive != exclusive:
170 self._logger.LogError("shared locks must be unlocked with --shared")
171 return False
172
173 if lock.exclusive:
asharifda8e93b2013-02-15 22:49:27 +0000174 if lock.owner != getpass.getuser() and not force:
asharif455157b2013-02-15 21:15:05 +0000175 self._logger.LogError("%s can't unlock lock owned by: %s" %
asharifda8e93b2013-02-15 22:49:27 +0000176 (getpass.getuser(), lock.owner))
asharif455157b2013-02-15 21:15:05 +0000177 return False
178 lock.exclusive = False
179 lock.reason = ""
180 lock.owner = ""
181 else:
182 lock.counter -= 1
183 return True
184
185
186class Machine(object):
yunlian52a6c572013-02-19 20:42:07 +0000187 LOCKS_DIR = "/home/mobiletc-prebuild/locks"
yunlian8a3bdcb2013-02-19 20:42:48 +0000188
yunlian52a6c572013-02-19 20:42:07 +0000189 def __init__(self, name, locks_dir=LOCKS_DIR):
asharif455157b2013-02-15 21:15:05 +0000190 self._name = name
191 try:
192 self._full_name = socket.gethostbyaddr(name)[0]
193 except socket.error:
194 self._full_name = self._name
yunlian52a6c572013-02-19 20:42:07 +0000195 self._full_name = os.path.join(locks_dir, self._full_name)
asharif455157b2013-02-15 21:15:05 +0000196
197 def Lock(self, exclusive=False, reason=""):
198 lock = Lock(self._full_name)
199 return lock.NonBlockingLock(exclusive, reason)
200
shenhane0a6d8a2013-02-19 20:42:29 +0000201 def TryLock(self, timeout=300, exclusive=False, reason=""):
202 locked = False
yunlian8a3bdcb2013-02-19 20:42:48 +0000203 sleep = timeout / 10
shenhane0a6d8a2013-02-19 20:42:29 +0000204 while True:
205 locked = self.Lock(exclusive, reason)
206 if locked or not timeout >= 0:
207 break
208 print "Lock not acquired for {0}, wait {1} seconds ...".format(
yunlian8a3bdcb2013-02-19 20:42:48 +0000209 self._name, sleep)
shenhane0a6d8a2013-02-19 20:42:29 +0000210 time.sleep(sleep)
211 timeout -= sleep
212 return locked
213
asharif455157b2013-02-15 21:15:05 +0000214 def Unlock(self, exclusive=False, ignore_ownership=False):
215 lock = Lock(self._full_name)
216 return lock.Unlock(exclusive, ignore_ownership)
asharifb237bca2013-02-15 20:54:57 +0000217
218
219def Main(argv):
220 """The main function."""
asharifb237bca2013-02-15 20:54:57 +0000221 parser = optparse.OptionParser()
asharifb237bca2013-02-15 20:54:57 +0000222 parser.add_option("-r",
223 "--reason",
224 dest="reason",
asharif455157b2013-02-15 21:15:05 +0000225 default="",
asharifb237bca2013-02-15 20:54:57 +0000226 help="The lock reason.")
227 parser.add_option("-u",
228 "--unlock",
229 dest="unlock",
230 action="store_true",
231 default=False,
232 help="Use this to unlock.")
233 parser.add_option("-l",
234 "--list_locks",
235 dest="list_locks",
236 action="store_true",
237 default=False,
238 help="Use this to list locks.")
asharif455157b2013-02-15 21:15:05 +0000239 parser.add_option("-f",
240 "--ignore_ownership",
241 dest="ignore_ownership",
242 action="store_true",
243 default=False,
244 help="Use this to force unlock on a lock you don't own.")
245 parser.add_option("-s",
246 "--shared",
247 dest="shared",
248 action="store_true",
249 default=False,
250 help="Use this for a shared (non-exclusive) lock.")
yunlian52a6c572013-02-19 20:42:07 +0000251 parser.add_option("-d",
252 "--dir",
253 dest="locks_dir",
254 action="store",
255 default=Machine.LOCKS_DIR,
256 help="Use this to set different locks_dir")
asharifb237bca2013-02-15 20:54:57 +0000257
asharif455157b2013-02-15 21:15:05 +0000258 options, args = parser.parse_args(argv)
asharifb237bca2013-02-15 20:54:57 +0000259
yunlian52a6c572013-02-19 20:42:07 +0000260 options.locks_dir = os.path.abspath(options.locks_dir)
asharif455157b2013-02-15 21:15:05 +0000261 exclusive = not options.shared
262
263 if not options.list_locks and len(args) != 2:
264 logger.GetLogger().LogError(
265 "Either --list_locks or a machine arg is needed.")
asharifb237bca2013-02-15 20:54:57 +0000266 return 1
267
asharif455157b2013-02-15 21:15:05 +0000268 if len(args) > 1:
yunlian52a6c572013-02-19 20:42:07 +0000269 machine = Machine(args[1], options.locks_dir)
asharif455157b2013-02-15 21:15:05 +0000270 else:
271 machine = None
asharifb237bca2013-02-15 20:54:57 +0000272
273 if options.list_locks:
yunlian52a6c572013-02-19 20:42:07 +0000274 FileLock.ListLock("*", options.locks_dir)
asharif455157b2013-02-15 21:15:05 +0000275 retval = True
276 elif options.unlock:
277 retval = machine.Unlock(exclusive, options.ignore_ownership)
asharifb237bca2013-02-15 20:54:57 +0000278 else:
asharif455157b2013-02-15 21:15:05 +0000279 retval = machine.Lock(exclusive, options.reason)
280
281 if retval:
282 return 0
283 else:
284 return 1
asharifb237bca2013-02-15 20:54:57 +0000285
286if __name__ == "__main__":
asharif455157b2013-02-15 21:15:05 +0000287 sys.exit(Main(sys.argv))