lock: provide command to place a multi-command hold on a project.
This commit is contained in:
parent
77f79f9175
commit
53a79f2c05
@ -157,6 +157,19 @@ def do_staging(self, subcmd, opts, *args):
|
|||||||
|
|
||||||
"list" will list/supersede requests for ring packages or all if no rings.
|
"list" will list/supersede requests for ring packages or all if no rings.
|
||||||
|
|
||||||
|
"lock" acquire a hold on the project in order to execute multiple commands
|
||||||
|
and prevent others from interrupting. An example:
|
||||||
|
|
||||||
|
lock -m "checkin round"
|
||||||
|
|
||||||
|
list --supersede
|
||||||
|
adi
|
||||||
|
accept A B C D E
|
||||||
|
|
||||||
|
unlock
|
||||||
|
|
||||||
|
Each command will update the lock to keep it up-to-date.
|
||||||
|
|
||||||
"repair" will attempt to repair the state of a request that has been
|
"repair" will attempt to repair the state of a request that has been
|
||||||
corrupted.
|
corrupted.
|
||||||
|
|
||||||
@ -234,7 +247,10 @@ def do_staging(self, subcmd, opts, *args):
|
|||||||
|
|
||||||
Use the --cleanup flag to include all obsolete requests.
|
Use the --cleanup flag to include all obsolete requests.
|
||||||
|
|
||||||
"unlock" will remove the staging lock in case it gets stuck
|
"unlock" will remove the staging lock in case it gets stuck or a manual hold
|
||||||
|
If a command lock gets stuck while a hold is placed on a project the
|
||||||
|
unlock command will need to be run twice since there are two layers of
|
||||||
|
locks.
|
||||||
|
|
||||||
"rebuild" will rebuild broken packages in the given stagings or all
|
"rebuild" will rebuild broken packages in the given stagings or all
|
||||||
The rebuild command will only trigger builds for packages with less than
|
The rebuild command will only trigger builds for packages with less than
|
||||||
@ -257,6 +273,7 @@ def do_staging(self, subcmd, opts, *args):
|
|||||||
osc staging ignore [-m MESSAGE] REQUEST...
|
osc staging ignore [-m MESSAGE] REQUEST...
|
||||||
osc staging unignore [--cleanup] [REQUEST...|all]
|
osc staging unignore [--cleanup] [REQUEST...|all]
|
||||||
osc staging list [--supersede]
|
osc staging list [--supersede]
|
||||||
|
osc staging lock [-m MESSAGE]
|
||||||
osc staging select [--no-freeze] [--move [--from STAGING]]
|
osc staging select [--no-freeze] [--move [--from STAGING]]
|
||||||
[--add PACKAGE]
|
[--add PACKAGE]
|
||||||
STAGING REQUEST...
|
STAGING REQUEST...
|
||||||
@ -302,6 +319,8 @@ def do_staging(self, subcmd, opts, *args):
|
|||||||
min_args, max_args = 0, None
|
min_args, max_args = 0, None
|
||||||
elif cmd in ('cleanup_rings', 'acheck'):
|
elif cmd in ('cleanup_rings', 'acheck'):
|
||||||
min_args, max_args = 0, 0
|
min_args, max_args = 0, 0
|
||||||
|
elif cmd == 'lock':
|
||||||
|
min_args, max_args = 0, 0
|
||||||
elif cmd == 'unlock':
|
elif cmd == 'unlock':
|
||||||
min_args, max_args = 0, 0
|
min_args, max_args = 0, 0
|
||||||
elif cmd == 'rebuild':
|
elif cmd == 'rebuild':
|
||||||
@ -336,7 +355,7 @@ def do_staging(self, subcmd, opts, *args):
|
|||||||
|
|
||||||
lock = OBSLock(opts.apiurl, opts.project, reason=cmd)
|
lock = OBSLock(opts.apiurl, opts.project, reason=cmd)
|
||||||
if cmd == 'unlock':
|
if cmd == 'unlock':
|
||||||
lock.release()
|
lock.release(force=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
with lock:
|
with lock:
|
||||||
@ -543,6 +562,8 @@ def do_staging(self, subcmd, opts, *args):
|
|||||||
UnignoreCommand(api).perform(args[1:], opts.cleanup)
|
UnignoreCommand(api).perform(args[1:], opts.cleanup)
|
||||||
elif cmd == 'list':
|
elif cmd == 'list':
|
||||||
ListCommand(api).perform(supersede=opts.supersede)
|
ListCommand(api).perform(supersede=opts.supersede)
|
||||||
|
elif cmd == 'lock':
|
||||||
|
lock.hold(opts.message)
|
||||||
elif cmd == 'adi':
|
elif cmd == 'adi':
|
||||||
AdiCommand(api).perform(args[1:], move=opts.move, by_dp=opts.by_develproject, split=opts.split)
|
AdiCommand(api).perform(args[1:], move=opts.move, by_dp=opts.by_develproject, split=opts.split)
|
||||||
elif cmd == 'rebuild':
|
elif cmd == 'rebuild':
|
||||||
|
@ -37,23 +37,30 @@ class OBSLock(object):
|
|||||||
self.ttl = ttl
|
self.ttl = ttl
|
||||||
self.user = conf.config['api_host_options'][apiurl]['user']
|
self.user = conf.config['api_host_options'][apiurl]['user']
|
||||||
self.reason = reason
|
self.reason = reason
|
||||||
|
self.reason_sub = None
|
||||||
self.locked = False
|
self.locked = False
|
||||||
|
|
||||||
def _signature(self):
|
def _signature(self):
|
||||||
"""Create a signature with a timestamp."""
|
"""Create a signature with a timestamp."""
|
||||||
reason = str(self.reason).replace('@', 'at').replace('#', 'hash')
|
reason = str(self.reason)
|
||||||
|
if self.reason_sub:
|
||||||
|
reason += ' ({})'.format(self.reason_sub)
|
||||||
|
reason = reason.replace('@', 'at').replace('#', 'hash')
|
||||||
return '%s#%s@%s' % (self.user, reason, datetime.isoformat(datetime.utcnow()))
|
return '%s#%s@%s' % (self.user, reason, datetime.isoformat(datetime.utcnow()))
|
||||||
|
|
||||||
def _parse(self, signature):
|
def _parse(self, signature):
|
||||||
"""Parse a signature into an user and a timestamp."""
|
"""Parse a signature into an user and a timestamp."""
|
||||||
user, reason, ts = None, None, None
|
user, reason, reason_sub, ts = None, None, None, None
|
||||||
try:
|
try:
|
||||||
rest, ts_str = signature.split('@')
|
rest, ts_str = signature.split('@')
|
||||||
user, reason = rest.split('#')
|
user, reason = rest.split('#')
|
||||||
|
if ' (hold' in reason:
|
||||||
|
reason, reason_sub = reason.split(' (', 1)
|
||||||
|
reason_sub = reason_sub.rstrip(')')
|
||||||
ts = datetime.strptime(ts_str, '%Y-%m-%dT%H:%M:%S.%f')
|
ts = datetime.strptime(ts_str, '%Y-%m-%dT%H:%M:%S.%f')
|
||||||
except (AttributeError, ValueError):
|
except (AttributeError, ValueError):
|
||||||
pass
|
pass
|
||||||
return user, reason, ts
|
return user, reason, reason_sub, ts
|
||||||
|
|
||||||
def _read(self):
|
def _read(self):
|
||||||
url = makeurl(self.apiurl, ['source', self.lock, '_attribute', '%s:LockedBy' % self.ns])
|
url = makeurl(self.apiurl, ['source', self.lock, '_attribute', '%s:LockedBy' % self.ns])
|
||||||
@ -82,35 +89,49 @@ class OBSLock(object):
|
|||||||
warnings.warn('Locking attribute is not found. Create one to avoid race conditions.')
|
warnings.warn('Locking attribute is not found. Create one to avoid race conditions.')
|
||||||
return self
|
return self
|
||||||
|
|
||||||
user, reason, ts = self._parse(self._read())
|
user, reason, reason_sub, ts = self._parse(self._read())
|
||||||
if user and ts:
|
if user and ts:
|
||||||
now = datetime.utcnow()
|
now = datetime.utcnow()
|
||||||
if now < ts:
|
if now < ts:
|
||||||
raise Exception('Lock acquired from the future [%s] by [%s]. Try later.' % (ts, user))
|
raise Exception('Lock acquired from the future [%s] by [%s]. Try later.' % (ts, user))
|
||||||
delta = now - ts
|
delta = now - ts
|
||||||
if delta.seconds < self.ttl:
|
if delta.seconds < self.ttl and not(
|
||||||
|
user == self.user and (reason == 'lock' or reason.startswith('hold'))):
|
||||||
print 'Lock acquired by [%s] %s ago, reason <%s>. Try later.' % (user, delta, reason)
|
print 'Lock acquired by [%s] %s ago, reason <%s>. Try later.' % (user, delta, reason)
|
||||||
exit(-1)
|
exit(-1)
|
||||||
# raise Exception('Lock acquired by [%s]. Try later.' % user)
|
# raise Exception('Lock acquired by [%s]. Try later.' % user)
|
||||||
|
if reason and reason != 'lock':
|
||||||
|
self.reason_sub = reason
|
||||||
self._write(self._signature())
|
self._write(self._signature())
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
user, _, _ = self._parse(self._read())
|
user, _, _, _ = self._parse(self._read())
|
||||||
if user != self.user:
|
if user != self.user:
|
||||||
raise Exception('Race condition, [%s] wins. Try later.' % user)
|
raise Exception('Race condition, [%s] wins. Try later.' % user)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def release(self):
|
def release(self, force=False):
|
||||||
# If the project do not have locks configured, simply ignore
|
# If the project do not have locks configured, simply ignore
|
||||||
# the operation.
|
# the operation.
|
||||||
if not self.lock:
|
if not self.lock:
|
||||||
return
|
return
|
||||||
|
|
||||||
user, reason, _ = self._parse(self._read())
|
user, reason, reason_sub, _ = self._parse(self._read())
|
||||||
if user == self.user:
|
if user == self.user:
|
||||||
|
if reason_sub:
|
||||||
|
self.reason = reason_sub
|
||||||
|
self.reason_sub = None
|
||||||
|
self._write(self._signature())
|
||||||
|
elif not reason.startswith('hold') or force:
|
||||||
self._write('')
|
self._write('')
|
||||||
|
|
||||||
|
def hold(self, message=None):
|
||||||
|
self.reason = 'hold'
|
||||||
|
if message:
|
||||||
|
self.reason += ': ' + message
|
||||||
|
self.acquire()
|
||||||
|
|
||||||
__enter__ = acquire
|
__enter__ = acquire
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user