From: Joshua Watt <jpewhacker@gmail.com>
To: bitbake-devel@lists.openembedded.org,
openembedded-core@lists.openembedded.org
Subject: [RFC 8/9] hashserver: Add initial reference server
Date: Mon, 16 Jul 2018 15:37:27 -0500 [thread overview]
Message-ID: <20180716203728.23078-9-JPEWhacker@gmail.com> (raw)
In-Reply-To: <20180716203728.23078-1-JPEWhacker@gmail.com>
Adds an initial reference implementation of the hash server.
NOTE: This is my first dive into HTTP & REST technologies. Feedback is
appreciated. Also, I don't think it will be necessary for this reference
implementation to live in bitbake, and it can be moved to it's own
independent project if necessary?
Also, this server has some concurrency issues that I haven't tracked
down and will occasionally fail to record a new POST'd task with an
error indicating the database is locked. Based on some reading, I
believe this is because the server is using a sqlite backend, and it
would go away with a more production worthy backend. Anyway, it is good
enough for some preliminary testing.
Starting the server is simple and only requires pipenv to be installed:
$ pipenv shell
$ ./app.py
Signed-off-by: Joshua Watt <JPEWhacker@gmail.com>
---
bitbake/contrib/hashserver/.gitignore | 2 +
bitbake/contrib/hashserver/Pipfile | 15 ++
bitbake/contrib/hashserver/app.py | 212 ++++++++++++++++++++++++++
3 files changed, 229 insertions(+)
create mode 100644 bitbake/contrib/hashserver/.gitignore
create mode 100644 bitbake/contrib/hashserver/Pipfile
create mode 100755 bitbake/contrib/hashserver/app.py
diff --git a/bitbake/contrib/hashserver/.gitignore b/bitbake/contrib/hashserver/.gitignore
new file mode 100644
index 00000000000..030640a2b21
--- /dev/null
+++ b/bitbake/contrib/hashserver/.gitignore
@@ -0,0 +1,2 @@
+hashes.db
+Pipfile.lock
diff --git a/bitbake/contrib/hashserver/Pipfile b/bitbake/contrib/hashserver/Pipfile
new file mode 100644
index 00000000000..29cfb41a907
--- /dev/null
+++ b/bitbake/contrib/hashserver/Pipfile
@@ -0,0 +1,15 @@
+[[source]]
+url = "https://pypi.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+flask = "*"
+flask-sqlalchemy = "*"
+marshmallow-sqlalchemy = "*"
+flask-marshmallow = "*"
+
+[dev-packages]
+
+[requires]
+python_version = "3.6"
diff --git a/bitbake/contrib/hashserver/app.py b/bitbake/contrib/hashserver/app.py
new file mode 100755
index 00000000000..4fd2070fe92
--- /dev/null
+++ b/bitbake/contrib/hashserver/app.py
@@ -0,0 +1,212 @@
+#! /usr/bin/env python3
+#
+# BitBake Hash Server Reference Implementation
+#
+# Copyright (C) 2018 Garmin International
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from flask import Flask, request, jsonify
+from flask_sqlalchemy import SQLAlchemy
+from flask_marshmallow import Marshmallow
+from sqlalchemy import desc, case, func
+import sqlalchemy
+import sqlite3
+import hashlib
+import datetime
+
+app = Flask(__name__)
+app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///hashes.db'
+app.config['SQLALCHEMY_TIMEOUT'] = 15
+
+# Order matters: Initialize SQLAlchemy before Marshmallow
+db = SQLAlchemy(app)
+ma = Marshmallow(app)
+
+@sqlalchemy.event.listens_for(sqlalchemy.engine.Engine, "connect")
+def set_sqlite_pragma(dbapi_connection, connection_record):
+ cursor = dbapi_connection.cursor()
+ cursor.execute("PRAGMA journal_mode=WAL")
+ cursor.close()
+
+class TaskModel(db.Model):
+ __tablename__ = 'tasks'
+
+ id = db.Column(db.Integer, primary_key=True)
+ taskhash = db.Column(db.String(), nullable=False)
+ method = db.Column(db.String(), nullable=False)
+ outhash = db.Column(db.String(), nullable=False)
+ depid = db.Column(db.String(), nullable=False)
+ owner = db.Column(db.String())
+ created = db.Column(db.DateTime)
+ PN = db.Column(db.String())
+ PV = db.Column(db.String())
+ PR = db.Column(db.String())
+ task = db.Column(db.String())
+ outhash_siginfo = db.Column(db.Text())
+
+ __table_args__ = (
+ db.UniqueConstraint('taskhash', 'method', 'outhash', name='unique_task'),
+ # Make an index on taskhash and method for fast lookup
+ db.Index('lookup_index', 'taskhash', 'method'),
+ )
+
+ def __init__(self, taskhash, method, outhash, depid, owner=None):
+ self.taskhash = taskhash
+ self.method = method
+ self.outhash = outhash
+ self.depid = depid
+ self.owner = owner
+ self.created = datetime.datetime.utcnow()
+ self.task = None
+ self.outhash_siginfo = None
+
+schemas = {}
+
+class TaskFullSchema(ma.ModelSchema):
+ class Meta:
+ model = TaskModel
+
+task_full_schema = TaskFullSchema()
+tasks_full_schema = TaskFullSchema(many=True)
+schemas['full'] = tasks_full_schema
+
+class TaskSchema(ma.ModelSchema):
+ class Meta:
+ fields = ('id', 'taskhash', 'method', 'outhash', 'depid', 'owner', 'created', 'PN', 'PV', 'PR', 'task')
+
+task_schema = TaskSchema()
+tasks_schema = TaskSchema(many=True)
+schemas['default'] = tasks_schema
+
+class DepIDSchema(ma.ModelSchema):
+ class Meta:
+ fields = ('taskhash', 'method', 'depid')
+
+depid_schema = DepIDSchema()
+depids_schema = DepIDSchema(many=True)
+schemas['depid'] = depids_schema
+
+def get_tasks_schema():
+ return schemas.get(request.args.get('output', 'default'), tasks_schema)
+
+def get_count_column(column, min_count):
+ count_column = func.count(column).label('count')
+ query = (db.session.query(TaskModel)
+ .with_entities(column, count_column)
+ .group_by(column))
+
+ if min_count > 1:
+ query = query.having(count_column >= min_count)
+
+ col_name = column.name
+
+ result = [{'count': data.count, col_name: getattr(data, col_name)} for data in query.all()]
+
+ return jsonify(result)
+
+def filter_query_from_request(query, request):
+ for key in request.args:
+ if hasattr(TaskModel, key):
+ vals = request.args.getlist(key)
+ query = (query
+ .filter(getattr(TaskModel, key).in_(vals))
+ .order_by(TaskModel.created.asc()))
+ return query
+
+@app.route("/v1/count/outhashes", methods=["GET"])
+def outhashes():
+ return get_count_column(TaskModel.outhash, int(request.args.get('min', 1)))
+
+@app.route("/v1/count/taskhashes", methods=["GET"])
+def taskhashes():
+ return get_count_column(TaskModel.taskhash, int(request.args.get('min', 1)))
+
+@app.route("/v1/equivalent", methods=["GET", "POST"])
+def equivalent():
+ if request.method == 'GET':
+ task = (db.session.query(TaskModel)
+ .filter(TaskModel.method == request.args['method'])
+ .filter(TaskModel.taskhash == request.args['taskhash'])
+ .order_by(
+ # If there are multiple matching task hashes, return the oldest
+ # one
+ TaskModel.created.asc())
+ .limit(1)
+ .one_or_none())
+
+ return depid_schema.jsonify(task)
+
+ # TODO: Handle authentication
+
+ data = request.get_json()
+ # TODO handle when data is None. Currently breaks
+
+ # Find an appropriate task.
+ new_task = (db.session.query(TaskModel)
+ .filter(
+ # The output hash and method must match exactly
+ TaskModel.method == data['method'],
+ TaskModel.outhash == data['outhash'])
+ .order_by(
+ # If there is a matching taskhash, it will be sorted first, and
+ # thus the only row returned.
+ case([(TaskModel.taskhash == data['taskhash'], 1)], else_=2))
+ .order_by(
+ # Sort by date, oldest first. This only really matters if there
+ # isn't an exact match on the taskhash
+ TaskModel.created.asc())
+ # Only return one row
+ .limit(1)
+ .one_or_none())
+
+ # If no task was found that exactly matches this taskhash, create a new one
+ if not new_task or new_task.taskhash != data['taskhash']:
+ # Capture the dependency ID. If a matching task was found, then change
+ # this tasks dependency ID to match.
+ depid = data['depid']
+ if new_task:
+ depid = new_task.depid
+
+ new_task = TaskModel(data['taskhash'], data['method'], data['outhash'], depid)
+ db.session.add(new_task)
+
+ if new_task.taskhash == data['taskhash']:
+ # Add or update optional attributes
+ for o in ('outhash_siginfo', 'owner', 'task', 'PN', 'PV', 'PR'):
+ v = getattr(new_task, o, None)
+ if v is None:
+ setattr(new_task, o, data.get(o, None))
+
+ db.session.commit()
+
+ return depid_schema.jsonify(new_task)
+
+# TODO: Handle errors. Currently, everything is a 500 error
+@app.route("/v1/tasks", methods=["GET"])
+def tasks():
+ query = db.session.query(TaskModel)
+ query = filter_query_from_request(query, request)
+ return get_tasks_schema().jsonify(query.all())
+
+@app.route("/v1/tasks/overridden", methods=["GET"])
+def overridden():
+ query = db.session.query(TaskModel).filter(TaskModel.depid != TaskModel.taskhash)
+ query = filter_query_from_request(query, request)
+ return get_tasks_schema().jsonify(query.all())
+
+if __name__ == '__main__':
+ db.create_all()
+ app.run(debug=True)
+
--
2.17.1
next prev parent reply other threads:[~2018-07-16 20:38 UTC|newest]
Thread overview: 95+ messages / expand[flat|nested] mbox.gz Atom feed top
2018-07-16 20:37 [RFC 0/9] Hash Equivalency Server Joshua Watt
2018-07-16 20:37 ` [RFC 1/9] bitbake-worker: Pass taskhash as runtask parameter Joshua Watt
2018-07-16 20:37 ` [RFC 2/9] siggen: Split out stampfile hash fetch Joshua Watt
2018-07-16 20:37 ` [RFC 3/9] siggen: Split out task depend ID Joshua Watt
2018-07-16 20:37 ` [RFC 4/9] runqueue: Track task dependency ID Joshua Watt
2018-07-16 20:37 ` [RFC 5/9] runqueue: Pass dependency ID to task Joshua Watt
2018-07-16 20:37 ` [RFC 6/9] runqueue: Pass dependency ID to hash validate Joshua Watt
2018-07-16 20:37 ` [RFC 7/9] classes/sstate: Handle depid in hash check Joshua Watt
2018-07-16 20:37 ` Joshua Watt [this message]
2018-07-17 12:11 ` [bitbake-devel] [RFC 8/9] hashserver: Add initial reference server Richard Purdie
2018-07-17 13:44 ` Joshua Watt
2018-07-18 13:53 ` Joshua Watt
2018-07-16 20:37 ` [RFC 9/9] sstate: Implement hash equivalence sstate Joshua Watt
2018-08-09 22:08 ` [RFC v2 00/16] Hash Equivalency Server Joshua Watt
2018-08-09 22:08 ` [RFC v2 01/16] bitbake: fork: Add os.fork() wrappers Joshua Watt
2018-08-09 22:08 ` [RFC v2 02/16] bitbake: persist_data: Fix leaking cursors causing deadlock Joshua Watt
2018-08-09 22:08 ` [RFC v2 03/16] bitbake: persist_data: Add key constraints Joshua Watt
2018-08-09 22:08 ` [RFC v2 04/16] bitbake: persist_data: Enable Write Ahead Log Joshua Watt
2018-08-09 22:08 ` [RFC v2 05/16] bitbake: persist_data: Disable enable_shared_cache Joshua Watt
2018-08-09 22:08 ` [RFC v2 06/16] bitbake: persist_data: Close databases across fork Joshua Watt
2018-08-09 22:08 ` [RFC v2 07/16] bitbake: tests/persist_data: Add tests Joshua Watt
2018-08-09 22:08 ` [RFC v2 08/16] bitbake: bitbake-worker: Pass taskhash as runtask parameter Joshua Watt
2018-08-09 22:08 ` [RFC v2 09/16] bitbake: siggen: Split out stampfile hash fetch Joshua Watt
2018-08-09 22:08 ` [RFC v2 10/16] bitbake: siggen: Split out task depend ID Joshua Watt
2018-08-09 22:08 ` [RFC v2 11/16] bitbake: runqueue: Track task dependency ID Joshua Watt
2018-08-09 22:08 ` [RFC v2 12/16] bitbake: runqueue: Pass dependency ID to task Joshua Watt
2018-08-09 22:08 ` [RFC v2 13/16] bitbake: runqueue: Pass dependency ID to hash validate Joshua Watt
2018-08-09 22:08 ` [RFC v2 14/16] classes/sstate: Handle depid in hash check Joshua Watt
2018-08-09 22:08 ` [RFC v2 15/16] bitbake: hashserv: Add hash equivalence reference server Joshua Watt
2018-08-09 22:08 ` [RFC v2 16/16] sstate: Implement hash equivalence sstate Joshua Watt
2018-12-04 3:42 ` [PATCH v3 00/17] Hash Equivalency Server Joshua Watt
2018-12-04 3:42 ` [PATCH v3 01/17] bitbake: fork: Add os.fork() wrappers Joshua Watt
2018-12-04 3:42 ` [PATCH v3 02/17] bitbake: persist_data: Fix leaking cursors causing deadlock Joshua Watt
2018-12-04 3:42 ` [PATCH v3 03/17] bitbake: persist_data: Add key constraints Joshua Watt
2018-12-04 3:42 ` [PATCH v3 04/17] bitbake: persist_data: Enable Write Ahead Log Joshua Watt
2018-12-04 3:42 ` [PATCH v3 05/17] bitbake: persist_data: Disable enable_shared_cache Joshua Watt
2018-12-04 3:42 ` [PATCH v3 06/17] bitbake: persist_data: Close databases across fork Joshua Watt
2018-12-04 3:42 ` [PATCH v3 07/17] bitbake: tests/persist_data: Add tests Joshua Watt
2018-12-04 3:42 ` [PATCH v3 08/17] bitbake: bitbake-worker: Pass taskhash as runtask parameter Joshua Watt
2018-12-04 3:42 ` [PATCH v3 09/17] bitbake: siggen: Split out stampfile hash fetch Joshua Watt
2018-12-04 3:42 ` [PATCH v3 10/17] bitbake: siggen: Split out task depend ID Joshua Watt
2018-12-05 22:50 ` [bitbake-devel] " Richard Purdie
2018-12-06 14:58 ` Joshua Watt
2018-12-04 3:42 ` [PATCH v3 11/17] bitbake: runqueue: Track task dependency ID Joshua Watt
2018-12-04 3:42 ` [PATCH v3 12/17] bitbake: runqueue: Pass dependency ID to task Joshua Watt
2018-12-04 3:42 ` [PATCH v3 13/17] bitbake: runqueue: Pass dependency ID to hash validate Joshua Watt
2018-12-05 22:52 ` [bitbake-devel] " Richard Purdie
2018-12-04 3:42 ` [PATCH v3 14/17] classes/sstate: Handle depid in hash check Joshua Watt
2018-12-04 3:42 ` [PATCH v3 15/17] bitbake: hashserv: Add hash equivalence reference server Joshua Watt
2018-12-04 3:42 ` [PATCH v3 16/17] sstate: Implement hash equivalence sstate Joshua Watt
2018-12-04 3:42 ` [PATCH v3 17/17] classes/image-buildinfo: Remove unused argument Joshua Watt
2018-12-18 15:30 ` [PATCH v4 00/10] Hash Equivalency Server Joshua Watt
2018-12-18 15:30 ` [PATCH v4 01/10] bitbake: fork: Add os.fork() wrappers Joshua Watt
2018-12-18 15:30 ` [PATCH v4 02/10] bitbake: persist_data: Close databases across fork Joshua Watt
2018-12-18 15:30 ` [PATCH v4 03/10] bitbake: tests/persist_data: Add tests Joshua Watt
2018-12-18 15:30 ` [PATCH v4 04/10] bitbake: siggen: Split out task unique hash Joshua Watt
2018-12-18 15:30 ` [PATCH v4 05/10] bitbake: runqueue: Track " Joshua Watt
2018-12-18 15:30 ` [PATCH v4 06/10] bitbake: runqueue: Pass unique hash to task Joshua Watt
2018-12-18 15:30 ` [PATCH v4 07/10] bitbake: runqueue: Pass unique hash to hash validate Joshua Watt
2018-12-18 16:24 ` Richard Purdie
2018-12-18 16:31 ` Joshua Watt
2018-12-18 15:30 ` [PATCH v4 08/10] classes/sstate: Handle unihash in hash check Joshua Watt
2018-12-18 15:31 ` [PATCH v4 09/10] bitbake: hashserv: Add hash equivalence reference server Joshua Watt
2018-12-18 15:31 ` [PATCH v4 10/10] sstate: Implement hash equivalence sstate Joshua Watt
2018-12-19 3:10 ` [PATCH v5 0/8] Hash Equivalency Server Joshua Watt
2018-12-19 3:10 ` [PATCH v5 1/8] bitbake: tests/persist_data: Add tests Joshua Watt
2018-12-19 3:10 ` [PATCH v5 2/8] bitbake: siggen: Split out task unique hash Joshua Watt
2018-12-19 3:10 ` [PATCH v5 3/8] bitbake: runqueue: Track " Joshua Watt
2019-01-05 7:49 ` Alejandro Hernandez
2019-01-06 3:09 ` Joshua Watt
2019-01-07 6:52 ` Alejandro Hernandez
2019-01-07 16:16 ` akuster808
2019-01-07 16:40 ` Joshua Watt
2018-12-19 3:10 ` [PATCH v5 4/8] bitbake: runqueue: Pass unique hash to task Joshua Watt
2018-12-19 3:10 ` [PATCH v5 5/8] bitbake: runqueue: Pass unique hash to hash validate Joshua Watt
2018-12-19 3:10 ` [PATCH v5 6/8] classes/sstate: Handle unihash in hash check Joshua Watt
2018-12-19 3:10 ` [PATCH v5 7/8] bitbake: hashserv: Add hash equivalence reference server Joshua Watt
2018-12-19 3:10 ` [PATCH v5 8/8] sstate: Implement hash equivalence sstate Joshua Watt
2018-12-19 3:33 ` ✗ patchtest: failure for Hash Equivalency Server (rev3) Patchwork
2019-01-04 2:42 ` [PATCH v6 0/3] Hash Equivalency Server Joshua Watt
2019-01-04 2:42 ` [PATCH v6 1/3] classes/sstate: Handle unihash in hash check Joshua Watt
2019-01-04 7:01 ` [bitbake-devel] " Richard Purdie
2019-01-04 2:42 ` [PATCH v6 2/3] bitbake: hashserv: Add hash equivalence reference server Joshua Watt
2019-01-04 2:42 ` [PATCH v6 3/3] sstate: Implement hash equivalence sstate Joshua Watt
2019-01-04 16:20 ` [PATCH v7 0/3] Hash Equivalency Server Joshua Watt
2019-01-04 16:20 ` [PATCH v7 1/3] classes/sstate: Handle unihash in hash check Joshua Watt
2019-01-04 16:20 ` [PATCH v7 2/3] bitbake: hashserv: Add hash equivalence reference server Joshua Watt
2019-01-04 16:20 ` [PATCH v7 3/3] sstate: Implement hash equivalence sstate Joshua Watt
2019-01-08 6:29 ` [bitbake-devel] " Jacob Kroon
2019-01-09 17:09 ` Joshua Watt
2019-01-11 20:39 ` Peter Kjellerstedt
2019-01-04 16:33 ` ✗ patchtest: failure for Hash Equivalency Server (rev5) Patchwork
2019-01-04 3:03 ` ✗ patchtest: failure for Hash Equivalency Server (rev4) Patchwork
2018-12-18 16:03 ` ✗ patchtest: failure for Hash Equivalency Server (rev2) Patchwork
2018-12-04 4:05 ` ✗ patchtest: failure for Hash Equivalency Server Patchwork
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20180716203728.23078-9-JPEWhacker@gmail.com \
--to=jpewhacker@gmail.com \
--cc=bitbake-devel@lists.openembedded.org \
--cc=openembedded-core@lists.openembedded.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox