Title: [258355] trunk/Tools
Revision
258355
Author
[email protected]
Date
2020-03-12 14:06:33 -0700 (Thu, 12 Mar 2020)

Log Message

resultsdpy: Add script to run local instance
https://bugs.webkit.org/show_bug.cgi?id=208746

Rubber-stamped by Aakash Jain.

* resultsdbpy/resultsdbpy/example: Added.
* resultsdbpy/resultsdbpy/example/__init__.py: Added.
* resultsdbpy/resultsdbpy/example/environment.py: Added.
(Environment): Organize environment variables used by results database.
(ModelFromEnvironment): Construct the database model from the environment.
(main): Create and drop tables, if required.
* resultsdbpy/resultsdbpy/example/main.py: Added.
(health): Web-server health check.
(handle_errors):
(main): Run results database web-server.
* resultsdbpy/resultsdbpy/example/worker.py: Added.
(main): Asynchronously process uploaded results.
* resultsdbpy/resultsdbpy/run: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/Tools/ChangeLog (258354 => 258355)


--- trunk/Tools/ChangeLog	2020-03-12 21:01:46 UTC (rev 258354)
+++ trunk/Tools/ChangeLog	2020-03-12 21:06:33 UTC (rev 258355)
@@ -1,3 +1,24 @@
+2020-03-12  Jonathan Bedard  <[email protected]>
+
+        resultsdpy: Add script to run local instance
+        https://bugs.webkit.org/show_bug.cgi?id=208746
+
+        Rubber-stamped by Aakash Jain.
+
+        * resultsdbpy/resultsdbpy/example: Added.
+        * resultsdbpy/resultsdbpy/example/__init__.py: Added.
+        * resultsdbpy/resultsdbpy/example/environment.py: Added.
+        (Environment): Organize environment variables used by results database.
+        (ModelFromEnvironment): Construct the database model from the environment.
+        (main): Create and drop tables, if required.
+        * resultsdbpy/resultsdbpy/example/main.py: Added.
+        (health): Web-server health check.
+        (handle_errors):
+        (main): Run results database web-server.
+        * resultsdbpy/resultsdbpy/example/worker.py: Added.
+        (main): Asynchronously process uploaded results.
+        * resultsdbpy/resultsdbpy/run: Added.
+
 2020-03-12  Wenson Hsieh  <[email protected]>
 
         run-webkit-tests --use-gpu-process should enable GPU Process for canvas

Added: trunk/Tools/resultsdbpy/resultsdbpy/example/__init__.py (0 => 258355)


--- trunk/Tools/resultsdbpy/resultsdbpy/example/__init__.py	                        (rev 0)
+++ trunk/Tools/resultsdbpy/resultsdbpy/example/__init__.py	2020-03-12 21:06:33 UTC (rev 258355)
@@ -0,0 +1,21 @@
+# Copyright (C) 2020 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1.  Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+# 2.  Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Added: trunk/Tools/resultsdbpy/resultsdbpy/example/environment.py (0 => 258355)


--- trunk/Tools/resultsdbpy/resultsdbpy/example/environment.py	                        (rev 0)
+++ trunk/Tools/resultsdbpy/resultsdbpy/example/environment.py	2020-03-12 21:06:33 UTC (rev 258355)
@@ -0,0 +1,159 @@
+# Copyright (C) 2020 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1.  Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+# 2.  Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import os
+import sys
+
+from cassandra.auth import PlainTextAuthProvider
+from fakeredis import FakeStrictRedis
+from redis import StrictRedis
+
+sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
+
+from resultsdbpy.model.cassandra_context import CassandraContext
+from resultsdbpy.model.casserole import CasseroleNodes, CasseroleRedis
+from resultsdbpy.model.ci_context import BuildbotURLFactory, BuildbotEightURLFactory
+from resultsdbpy.model.mock_cassandra_context import MockCassandraContext
+from resultsdbpy.model.model import Model
+from resultsdbpy.model.repository import WebKitRepository
+from resultsdbpy.model.partitioned_redis import PartitionedRedis
+
+MOCK_CASSANDRA = 'mock'
+KEYSPACE = 'results_database_example'
+
+
+class Environment(object):
+    REDIS_PORT = 6379
+
+    def __init__(self, cqleng_allow_schema_management=False):
+        self.port = int(os.environ.get('PORT', 5000))
+
+        # The base environment provides defaults for database information, although that can be explicitly overridden
+        self.cassandra_server = os.environ.get('CASSANDRA_SERVER', 'localhost')
+        self.keyspace = os.environ.get('KEYSPACE', KEYSPACE)
+
+        self.redis_host = os.environ.get('REDIS_HOST', 'localhost')
+        self.redis_port = os.environ.get('REDIS_PORT', self.REDIS_PORT)
+
+        self.drop_keyspace = cqleng_allow_schema_management and bool(int(os.environ.get('DROP_KEYSPACE', 0)))
+        self.create_keyspace = cqleng_allow_schema_management and bool(int(os.environ.get('CREATE_KEYSPACE', 1)))
+        self.cqleng_allow_schema_management = cqleng_allow_schema_management
+
+        os.environ['CQLENG_ALLOW_SCHEMA_MANAGEMENT'] = '1' if self.cqleng_allow_schema_management else '0'
+
+        for key, value in self.__dict__.items():
+            if value is None:
+                raise EnvironmentError(f'{key.upper()} is not defined in the environment')
+
+        self.redis_url = os.environ.get('REDIS_URL', None)
+        self.redis_password = os.environ.get('REDIS_PASSWORD', None)
+
+        self.cassandra_username = None
+        self.cassandra_password = None
+
+        if not self.cqleng_allow_schema_management:
+            self.cassandra_username = os.environ.get('CASSANDRA_USER')
+            self.cassandra_password = os.environ.get('CASSANDRA_USER_PASSWORD')
+
+        if not self.cassandra_username or not self.cassandra_password:
+            self.cassandra_username = os.environ.get('CASSANDRA_ADMIN')
+            self.cassandra_password = os.environ.get('CASSANDRA_ADMIN_PASSWORD')
+
+    @staticmethod
+    def obfuscate_password(password):
+        if password:
+            return '*' * len(password)
+        return '-'
+
+    def __str__(self):
+        result = ''
+        order = [
+            'port',
+            'cassandra_server', 'cqleng_allow_schema_management',
+            'keyspace', 'drop_keyspace', 'create_keyspace',
+            'cassandra_username', 'cassandra_password',
+            'redis_url',
+            'redis_host', 'redis_port', 'redis_password',
+        ]
+        for key in self.__dict__.keys():
+            if key not in order:
+                order.append(key)
+        for key in order:
+            value = self.__dict__.get(key)
+            if value and ('password' in key or 'token' in key or key.endswith('key')):
+                value = self.obfuscate_password(value)
+            result += f"    {key.upper()}: {value}\n"
+        return result[:-1]
+
+
+class ModelFromEnvironment(Model):
+    TTL_SECONDS = Model.TTL_YEAR * 5
+
+    def __init__(self, environment):
+        nodes = ['localhost']
+        cassandra_class = CassandraContext
+        if environment.cassandra_server == MOCK_CASSANDRA:
+            cassandra_class = MockCassandraContext
+
+        auth_provider = None
+        if environment.cassandra_username and environment.cassandra_password:
+            auth_provider = PlainTextAuthProvider(environment.cassandra_username, environment.cassandra_password)
+
+        if environment.drop_keyspace:
+            cassandra_class.drop_keyspace(nodes=nodes, keyspace=environment.keyspace, auth_provider=auth_provider)
+
+        cassandra = cassandra_class(
+            nodes=nodes,
+            keyspace=environment.keyspace,
+            auth_provider=auth_provider,
+            create_keyspace=environment.create_keyspace,
+            replication_map=None,
+        )
+        if environment.redis_host == 'mock':
+            redis = FakeStrictRedis()
+        elif environment.redis_url:
+            redis = StrictRedis.from_url(environment.redis_url)
+        else:
+            redis = StrictRedis(
+                port=environment.redis_port,
+                host=environment.redis_host,
+                password=environment.redis_password,
+            )
+        redis.ping()  # Force a connection with the redis database
+
+        webkit = WebKitRepository()
+        super(ModelFromEnvironment, self).__init__(
+            redis=redis, cassandra=cassandra,
+            repositories=[webkit],
+            default_ttl_seconds=self.TTL_SECONDS,
+            async_processing=True,
+        )
+
+
+def main():
+    environment = Environment(cqleng_allow_schema_management=True)
+    print(f'Environment for setup:\n{environment}')
+    ModelFromEnvironment(environment)
+
+
+if __name__ == '__main__':
+    main()

Added: trunk/Tools/resultsdbpy/resultsdbpy/example/main.py (0 => 258355)


--- trunk/Tools/resultsdbpy/resultsdbpy/example/main.py	                        (rev 0)
+++ trunk/Tools/resultsdbpy/resultsdbpy/example/main.py	2020-03-12 21:06:33 UTC (rev 258355)
@@ -0,0 +1,65 @@
+# Copyright (C) 2020 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1.  Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+# 2.  Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import hashlib
+import json
+
+from example.environment import Environment, ModelFromEnvironment
+from flask import abort, Flask, request
+from resultsdbpy.controller.api_routes import APIRoutes
+from resultsdbpy.view.view_routes import ViewRoutes
+
+
+environment = Environment()
+print(f'Environment for web-app:\n{environment}')
+
+model = ModelFromEnvironment(environment)
+app = Flask(__name__)
+
+
+api_routes = APIRoutes(model=model, import_name=__name__)
+view_routes = ViewRoutes(
+    title='Example Results Database',
+    model=model, controller=api_routes, import_name=__name__,
+)
+
+
[email protected]('/__health', methods=('GET',))
+def health():
+    return 'ok'
+
+
[email protected](401)
[email protected](404)
[email protected](405)
+def handle_errors(error):
+    if request.path.startswith('/api/'):
+        return api_routes.error_response(error)
+    return view_routes.error(error=error)
+
+
+app.register_blueprint(api_routes)
+app.register_blueprint(view_routes)
+
+
+def main():
+    app.run(host='0.0.0.0', port=environment.port)

Added: trunk/Tools/resultsdbpy/resultsdbpy/example/worker.py (0 => 258355)


--- trunk/Tools/resultsdbpy/resultsdbpy/example/worker.py	                        (rev 0)
+++ trunk/Tools/resultsdbpy/resultsdbpy/example/worker.py	2020-03-12 21:06:33 UTC (rev 258355)
@@ -0,0 +1,42 @@
+# Copyright (C) 2020 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1.  Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+# 2.  Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import argparse
+import random
+import time
+from example.environment import Environment, ModelFromEnvironment
+
+
+def main():
+    environment = Environment()
+    print(f'Environment for worker:\n{environment}')
+
+    model = ModelFromEnvironment(environment)
+
+    random.seed()
+    while True:
+        model.do_work()
+        time.sleep(random.randint(15, 45))  # Stagger workers.
+
+
+if __name__ == '__main__':
+    main()

Added: trunk/Tools/resultsdbpy/resultsdbpy/run (0 => 258355)


--- trunk/Tools/resultsdbpy/resultsdbpy/run	                        (rev 0)
+++ trunk/Tools/resultsdbpy/resultsdbpy/run	2020-03-12 21:06:33 UTC (rev 258355)
@@ -0,0 +1,173 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2020 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1.  Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+# 2.  Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import argparse
+import os
+import subprocess
+import sys
+import time
+import threading
+
+sys.dont_write_bytecode = True
+
+from model.docker import Docker
+from multiprocessing import Process
+from redis import StrictRedis
+
+
+class DockerIfNeededContext(object):
+
+    def __init__(self, args):
+        self.docker = Docker.instance() if 'localhost' in [args.cassandra_server, args.redis_server] else None
+
+    def __enter__(self):
+        if self.docker:
+            self.docker.__enter__()
+
+    def __exit__(self, *args, **kwargs):
+        if self.docker:
+            self.docker.__exit__(*args, **kwargs)
+
+
+def has_gunicorn():
+    return not subprocess.run(['python3', '/usr/local/bin/gunicorns', '--version'], capture_output=True).returncode
+
+
+def parse_arguments():
+    parser = argparse.ArgumentParser(description='Run a simple instance of the results database for local development')
+    parser.add_argument(
+        '--local-cassandra', help='Use a local Cassandra instance',
+        dest='cassandra_server', action='', const='localhost',
+    )
+    parser.add_argument(
+        '--mock-cassandra', help='Use a mock Cassandra instance',
+        dest='cassandra_server', action='', const='mock',
+    )
+    parser.set_defaults(cassandra_server='localhost' if Docker.installed() else 'mock')
+
+    parser.add_argument(
+        '--local-redis', help='Use a local Redis instance',
+        dest='redis_server', action='', const='localhost',
+    )
+    parser.add_argument(
+        '--mock-redis', help='Use a mock Redis instance',
+        dest='redis_server', action='', const='mock',
+    )
+    parser.set_defaults(redis_server='localhost' if Docker.installed() else 'mock')
+
+    parser.add_argument(
+        '--drop-keyspace', help='Drop the existing keyspace before running the program',
+        dest='drop_keyspace', action='',
+    )
+    parser.add_argument(
+        '--no-drop-keyspace', help='Do NOT drop the existing keyspace before running the program',
+        dest='drop_keyspace', action='',
+    )
+    parser.set_defaults(drop_keyspace=False)
+
+    parser.add_argument(
+        '--clear-redis', help='Clear redis before running the application',
+        dest='clear_redis', action='',
+    )
+    parser.add_argument(
+        '--no-clear-redis',
+        help='Do not clear redis before running the application',
+        dest='clear_redis', action='',
+    )
+    parser.set_defaults(clear_redis=True)
+
+    parser.add_argument(
+        '--gunicorn', help='Use the gunicorn webserver',
+        dest='gunicorn', action='',
+    )
+    parser.add_argument(
+        '--no-gunicorn',
+        help='Do not use the gunicorn webserver',
+        dest='gunicorn', action='',
+    )
+    parser.set_defaults(gunicorn=has_gunicorn())
+
+    return parser.parse_args()
+
+
+def do_work():
+    from example.worker import main
+    main()
+
+
+def web_app():
+    from example.main import main
+    return main()
+
+
+def gunicorn():
+    if not has_gunicorn():
+        print('gunicorn is not installed, please run `pip3 install gunicorn`')
+        return 1
+
+    # Slight delay so that the worker is started before the webserver
+    time.sleep(2)
+    return subprocess.run([
+        'python3', '/usr/local/bin/gunicorn',
+        'example.main:app',
+        '--log-file=-', '--reload',
+    ]).returncode
+
+
+def main():
+    args = parse_arguments()
+    with DockerIfNeededContext(args):
+        os.environ['CASSANDRA_SERVER'] = args.cassandra_server
+        os.environ['REDIS_HOST'] = args.redis_server
+        os.environ['DROP_KEYSPACE'] = '1' if args.drop_keyspace else '0'
+        os.environ['CREATE_KEYSPACE'] = '1'  # Unconditionally create keyspace for testing
+
+        if args.clear_redis and args.redis_server != 'mock':
+            StrictRedis(host=args.redis_server).flushdb()
+
+        if args.gunicorn:
+            if 'mock' in [args.redis_server, args.cassandra_server]:
+                raise RuntimeError('Cannot run with gunicorn and mock databases, asynchronous workers rely on the real databases')
+
+        from example.environment import main as environment_main
+        environment_main()
+
+        if not args.gunicorn:
+            worker = threading.Thread(target=do_work)
+            worker.daemon = True
+            worker.start()
+
+            return web_app()
+
+        worker = Process(target=do_work)
+        worker.start()
+
+        try:
+            return gunicorn()
+        finally:
+            worker.terminate()
+            worker.join()
+
+if __name__ == '__main__':
+    sys.exit(main())
Property changes on: trunk/Tools/resultsdbpy/resultsdbpy/run
___________________________________________________________________

Added: svn:executable

+* \ No newline at end of property
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to