From dbf5e54307d734c14673f524739bf6fa9501bfcb Mon Sep 17 00:00:00 2001 From: Josh Durgin Date: Tue, 5 Jul 2011 17:16:08 -0700 Subject: [PATCH] Add simple lock server HTTP interface. --- teuthology/locker/api.py | 103 ++++++++++++++++++++++++++++++++++++ teuthology/locker/config.py | 23 ++++++++ teuthology/locker/locker.py | 15 ++++++ 3 files changed, 141 insertions(+) create mode 100644 teuthology/locker/api.py create mode 100644 teuthology/locker/config.py create mode 100755 teuthology/locker/locker.py diff --git a/teuthology/locker/api.py b/teuthology/locker/api.py new file mode 100644 index 0000000000..5266b1a759 --- /dev/null +++ b/teuthology/locker/api.py @@ -0,0 +1,103 @@ +import json +import web + +from config import DB + +def load_machine(name): + results = list(DB.select('machine', what='*', + where='name = $name', + vars=dict(name=name))) + if not results: + raise web.NotFound() + return results[0] + +class MachineLock: + def GET(self, name): + row = load_machine(name) + row.locked_since = row.locked_since.isoformat() + web.header('Content-type', 'text/json') + return json.dumps(row) + + def DELETE(self, name): + user = web.input('user')['user'] + machine = load_machine(name) + if not machine.locked: + raise web.BadRequest() + if machine.locked_by != user: + raise web.Forbidden() + + res = DB.update('machine', + where='locked = true AND name = $name AND locked_by = $user', + vars=dict(name=name, user=user), + locked=False, locked_by=None) + assert res == 1, 'Failed to unlock machine {name}'.format(name=name) + + def POST(self, name): + user = web.input('user')['user'] + machine = load_machine(name) + if machine.locked: + raise web.Forbidden() + res = DB.update('machine', where='name = $name AND locked = false', + vars=dict(name=name), + locked=True, + locked_by=user, + locked_since=web.db.SQLLiteral('NOW()')) + assert res == 1, 'Failed to lock machine {name}'.format(name=name) + + def PUT(self, name): + desc = web.input(desc=None)['desc'] + status = web.input(status=None)['status'] + + updated = {} + if desc is not None: + updated['description'] = desc + if status is not None: + updated['up'] = (status == 'up') + + if not updated: + raise web.BadRequest() + DB.update('machine', where='name = $name', + vars=dict(name=name), **updated) + +class Lock: + def GET(self): + rows = list(DB.select('machine', what='*')) + if not rows: + raise web.NotFound() + for row in rows: + row.locked_since = row.locked_since.isoformat() + web.header('Content-type', 'text/json') + return json.dumps(rows) + + def POST(self): + user = web.input('user')['user'] + num = int(web.input('num')['num']) + + if num < 1: + raise web.BadRequest() + + tries = 0 + while True: + try: + with DB.transaction(): + results = list(DB.select('machine', what='name', + where='locked = false and up = true', + limit=num)) + if len(results) < num: + raise web.HTTPError(status='503 Service Unavailable') + names = [row.name for row in results] + num_locked = DB.update('machine', where=web.db.sqlors('name = ', names), + locked=True, + locked_by=user, + locked_since=web.db.SQLLiteral('NOW()')) + assert num_locked == num, 'Failed to lock machines' + except: + tries += 1 + if tries < 10: + continue + raise + else: + break + + web.header('Content-type', 'text/json') + return json.dumps(names) diff --git a/teuthology/locker/config.py b/teuthology/locker/config.py new file mode 100644 index 0000000000..f2bbe1b251 --- /dev/null +++ b/teuthology/locker/config.py @@ -0,0 +1,23 @@ +""" +This file contains database configuration. + +The schema can be created with:: + + CREATE TABLE machine ( + name varchar(255), + up boolean NOT NULL, + locked boolean NOT NULL, + locked_since timestamp NOT NULL DEFAULT '0000-00-00T00:00:00', + locked_by varchar(32), + description TEXT, + PRIMARY KEY (name), + INDEX (locked), + INDEX (up)); + +If using MySQL, be sure to use an engine that supports +transactions, like InnoDB. +""" +import web + +# Change these values to the connection info for your database. +DB = web.database(dbn='dbms', db='db', user='user', pw='password', host='host') diff --git a/teuthology/locker/locker.py b/teuthology/locker/locker.py new file mode 100755 index 0000000000..93067cf84e --- /dev/null +++ b/teuthology/locker/locker.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +import web + +from api import Lock, MachineLock + +urls = ( + '/lock', 'Lock', + '/lock/(.*)', 'MachineLock', + ) + +app = web.application(urls, globals()) + +if __name__ == "__main__": + app.run() -- 2.39.5