blob: a0079a8fc726281b6de6e740b26f343f76528fad [file] [log] [blame]
asharifb237bca2013-02-15 20:54:57 +00001#!/usr/bin/python2.6
2#
3# Copyright 2010 Google Inc. All Rights Reserved.
4
5"""Script to lock/unlock machines.
6
7"""
8
9__author__ = "asharif@google.com (Ahmad Sharif)"
10
11import datetime
asharif455157b2013-02-15 21:15:05 +000012import fcntl
asharifb237bca2013-02-15 20:54:57 +000013import glob
14import optparse
15import os
asharif455157b2013-02-15 21:15:05 +000016import pickle
asharifb237bca2013-02-15 20:54:57 +000017import socket
asharif455157b2013-02-15 21:15:05 +000018import sys
19import time
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):
54 LOCKS_DIR = "/home/mobiletc-prebuild/locks"
55
56 def __init__(self, lock_filename):
57 assert os.path.isdir(self.LOCKS_DIR), (
58 "Locks dir: %s doesn't exist!" % self.LOCKS_DIR)
59 self._filepath = os.path.join(self.LOCKS_DIR, lock_filename)
60 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
84 def ListLock(cls, pattern):
85 full_pattern = os.path.join(cls.LOCKS_DIR, pattern)
86 file_locks = []
87 for lock_filename in glob.glob(full_pattern):
88 file_lock = FileLock(lock_filename)
89 with file_lock as lock:
90 if lock.IsLocked():
91 file_locks.append(file_lock)
92 logger.GetLogger().LogOutput("\n%s" % cls.AsString(file_locks))
93
94 def __enter__(self):
95 with FileCreationMask(0000):
96 try:
97 self._file = open(self._filepath, "a+")
98 self._file.seek(0, os.SEEK_SET)
99
100 if fcntl.flock(self._file.fileno(), fcntl.LOCK_EX) == -1:
101 raise IOError("flock(%s, LOCK_EX) failed!" % self._filepath)
102
103 try:
104 self._description = pickle.load(self._file)
105 except (EOFError, pickle.PickleError):
106 self._description = LockDescription()
107 return self._description
108 # Check this differently?
109 except IOError as ex:
110 logger.GetLogger().LogError(ex)
111 return None
112
113 def __exit__(self, type, value, traceback):
114 self._file.truncate(0)
115 self._file.write(pickle.dumps(self._description))
116 self._file.close()
117
118 def __str__(self):
119 return self.AsString([self])
120
121
122class Lock(object):
123 def __init__(self, to_lock):
124 self._to_lock = to_lock
125 self._logger = logger.GetLogger()
126
127 def NonBlockingLock(self, exclusive, reason=""):
128 with FileLock(self._to_lock) as lock:
129 if lock.exclusive:
130 self._logger.LogError(
131 "Exclusive lock already acquired by %s. Reason: %s" %
132 (lock.owner, lock.reason))
133 return False
134
135 if exclusive:
136 if lock.counter:
137 self._logger.LogError("Shared lock already acquired")
138 return False
139 lock.exclusive = True
140 lock.reason = reason
141 lock.owner = os.getlogin()
142 lock.time = time.time()
143 else:
144 lock.counter += 1
145 self._logger.LogOutput("Successfully locked: %s" % self._to_lock)
146 return True
147
148 def Unlock(self, exclusive, force=False):
149 with FileLock(self._to_lock) as lock:
150 if not lock.IsLocked():
151 self._logger.LogError("Can't unlock unlocked machine!")
152 return False
153
154 if lock.exclusive != exclusive:
155 self._logger.LogError("shared locks must be unlocked with --shared")
156 return False
157
158 if lock.exclusive:
159 if lock.owner != os.getlogin() and not force:
160 self._logger.LogError("%s can't unlock lock owned by: %s" %
161 (os.getlogin(), lock.owner))
162 return False
163 lock.exclusive = False
164 lock.reason = ""
165 lock.owner = ""
166 else:
167 lock.counter -= 1
168 return True
169
170
171class Machine(object):
172 def __init__(self, name):
173 self._name = name
174 try:
175 self._full_name = socket.gethostbyaddr(name)[0]
176 except socket.error:
177 self._full_name = self._name
178
179 def Lock(self, exclusive=False, reason=""):
180 lock = Lock(self._full_name)
181 return lock.NonBlockingLock(exclusive, reason)
182
183 def Unlock(self, exclusive=False, ignore_ownership=False):
184 lock = Lock(self._full_name)
185 return lock.Unlock(exclusive, ignore_ownership)
asharifb237bca2013-02-15 20:54:57 +0000186
187
188def Main(argv):
189 """The main function."""
asharifb237bca2013-02-15 20:54:57 +0000190 parser = optparse.OptionParser()
asharifb237bca2013-02-15 20:54:57 +0000191 parser.add_option("-r",
192 "--reason",
193 dest="reason",
asharif455157b2013-02-15 21:15:05 +0000194 default="",
asharifb237bca2013-02-15 20:54:57 +0000195 help="The lock reason.")
196 parser.add_option("-u",
197 "--unlock",
198 dest="unlock",
199 action="store_true",
200 default=False,
201 help="Use this to unlock.")
202 parser.add_option("-l",
203 "--list_locks",
204 dest="list_locks",
205 action="store_true",
206 default=False,
207 help="Use this to list locks.")
asharif455157b2013-02-15 21:15:05 +0000208 parser.add_option("-f",
209 "--ignore_ownership",
210 dest="ignore_ownership",
211 action="store_true",
212 default=False,
213 help="Use this to force unlock on a lock you don't own.")
214 parser.add_option("-s",
215 "--shared",
216 dest="shared",
217 action="store_true",
218 default=False,
219 help="Use this for a shared (non-exclusive) lock.")
asharifb237bca2013-02-15 20:54:57 +0000220
asharif455157b2013-02-15 21:15:05 +0000221 options, args = parser.parse_args(argv)
asharifb237bca2013-02-15 20:54:57 +0000222
asharif455157b2013-02-15 21:15:05 +0000223 exclusive = not options.shared
224
225 if not options.list_locks and len(args) != 2:
226 logger.GetLogger().LogError(
227 "Either --list_locks or a machine arg is needed.")
asharifb237bca2013-02-15 20:54:57 +0000228 return 1
229
asharif455157b2013-02-15 21:15:05 +0000230 if len(args) > 1:
231 machine = Machine(args[1])
232 else:
233 machine = None
asharifb237bca2013-02-15 20:54:57 +0000234
235 if options.list_locks:
asharif455157b2013-02-15 21:15:05 +0000236 FileLock.ListLock("*")
237 retval = True
238 elif options.unlock:
239 retval = machine.Unlock(exclusive, options.ignore_ownership)
asharifb237bca2013-02-15 20:54:57 +0000240 else:
asharif455157b2013-02-15 21:15:05 +0000241 retval = machine.Lock(exclusive, options.reason)
242
243 if retval:
244 return 0
245 else:
246 return 1
asharifb237bca2013-02-15 20:54:57 +0000247
248if __name__ == "__main__":
asharif455157b2013-02-15 21:15:05 +0000249 sys.exit(Main(sys.argv))