|
| 1 | +# -*- coding: utf-8; -*- |
| 2 | +# |
| 3 | +# Licensed to CRATE Technology GmbH ("Crate") under one or more contributor |
| 4 | +# license agreements. See the NOTICE file distributed with this work for |
| 5 | +# additional information regarding copyright ownership. Crate licenses |
| 6 | +# this file to you under the Apache License, Version 2.0 (the "License"); |
| 7 | +# you may not use this file except in compliance with the License. You may |
| 8 | +# obtain a copy of the License at |
| 9 | +# |
| 10 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | +# |
| 12 | +# Unless required by applicable law or agreed to in writing, software |
| 13 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| 14 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| 15 | +# License for the specific language governing permissions and limitations |
| 16 | +# under the License. |
| 17 | +# |
| 18 | +# However, if you have executed another commercial license agreement |
| 19 | +# with Crate these terms will supersede the license and you may use the |
| 20 | +# software solely pursuant to the terms of the relevant commercial agreement. |
| 21 | + |
| 22 | +from __future__ import absolute_import |
| 23 | + |
| 24 | +import json |
| 25 | +import os |
| 26 | +import socket |
| 27 | +import unittest |
| 28 | +from pprint import pprint |
| 29 | +from http.server import HTTPServer, BaseHTTPRequestHandler |
| 30 | +import ssl |
| 31 | +import time |
| 32 | +import threading |
| 33 | +import logging |
| 34 | + |
| 35 | +import stopit |
| 36 | + |
| 37 | +from crate.testing.layer import CrateLayer |
| 38 | +from crate.testing.settings import \ |
| 39 | + crate_host, crate_path, crate_port, \ |
| 40 | + crate_transport_port, docs_path, localhost |
| 41 | +from crate.client import connect |
| 42 | + |
| 43 | + |
| 44 | +makeSuite = unittest.TestLoader().loadTestsFromTestCase |
| 45 | + |
| 46 | +log = logging.getLogger('crate.testing.layer') |
| 47 | +ch = logging.StreamHandler() |
| 48 | +ch.setLevel(logging.ERROR) |
| 49 | +log.addHandler(ch) |
| 50 | + |
| 51 | + |
| 52 | +def cprint(s): |
| 53 | + if isinstance(s, bytes): |
| 54 | + s = s.decode('utf-8') |
| 55 | + print(s) |
| 56 | + |
| 57 | + |
| 58 | +settings = { |
| 59 | + 'udc.enabled': 'false', |
| 60 | + 'lang.js.enabled': 'true', |
| 61 | + 'auth.host_based.enabled': 'true', |
| 62 | + 'auth.host_based.config.0.user': 'crate', |
| 63 | + 'auth.host_based.config.0.method': 'trust', |
| 64 | + 'auth.host_based.config.98.user': 'trusted_me', |
| 65 | + 'auth.host_based.config.98.method': 'trust', |
| 66 | + 'auth.host_based.config.99.user': 'me', |
| 67 | + 'auth.host_based.config.99.method': 'password', |
| 68 | +} |
| 69 | +crate_layer = None |
| 70 | + |
| 71 | + |
| 72 | +def ensure_cratedb_layer(): |
| 73 | + """ |
| 74 | + In order to skip individual tests by manually disabling them within |
| 75 | + `def test_suite()`, it is crucial make the test layer not run on each |
| 76 | + and every occasion. So, things like this will be possible:: |
| 77 | +
|
| 78 | + ./bin/test -vvvv --ignore_dir=testing |
| 79 | +
|
| 80 | + TODO: Through a subsequent patch, the possibility to individually |
| 81 | + unselect specific tests might be added to `def test_suite()` |
| 82 | + on behalf of environment variables. |
| 83 | + A blueprint for this kind of logic can be found at |
| 84 | + https://github.com/crate/crate/commit/414cd833. |
| 85 | + """ |
| 86 | + global crate_layer |
| 87 | + |
| 88 | + if crate_layer is None: |
| 89 | + crate_layer = CrateLayer('crate', |
| 90 | + crate_home=crate_path(), |
| 91 | + port=crate_port, |
| 92 | + host=localhost, |
| 93 | + transport_port=crate_transport_port, |
| 94 | + settings=settings) |
| 95 | + return crate_layer |
| 96 | + |
| 97 | + |
| 98 | +def setUpCrateLayerBaseline(test): |
| 99 | + if hasattr(test, "globs"): |
| 100 | + test.globs['crate_host'] = crate_host |
| 101 | + test.globs['pprint'] = pprint |
| 102 | + test.globs['print'] = cprint |
| 103 | + |
| 104 | + with connect(crate_host) as conn: |
| 105 | + cursor = conn.cursor() |
| 106 | + |
| 107 | + with open(docs_path('testing/testdata/mappings/locations.sql')) as s: |
| 108 | + stmt = s.read() |
| 109 | + cursor.execute(stmt) |
| 110 | + stmt = ("select count(*) from information_schema.tables " |
| 111 | + "where table_name = 'locations'") |
| 112 | + cursor.execute(stmt) |
| 113 | + assert cursor.fetchall()[0][0] == 1 |
| 114 | + |
| 115 | + data_path = docs_path('testing/testdata/data/test_a.json') |
| 116 | + # load testing data into crate |
| 117 | + cursor.execute("copy locations from ?", (data_path,)) |
| 118 | + # refresh location table so imported data is visible immediately |
| 119 | + cursor.execute("refresh table locations") |
| 120 | + # create blob table |
| 121 | + cursor.execute("create blob table myfiles clustered into 1 shards " + |
| 122 | + "with (number_of_replicas=0)") |
| 123 | + |
| 124 | + # create users |
| 125 | + cursor.execute("CREATE USER me WITH (password = 'my_secret_pw')") |
| 126 | + cursor.execute("CREATE USER trusted_me") |
| 127 | + |
| 128 | + cursor.close() |
| 129 | + |
| 130 | + |
| 131 | +def tearDownDropEntitiesBaseline(test): |
| 132 | + """ |
| 133 | + Drop all tables, views, and users created by `setUpWithCrateLayer*`. |
| 134 | + """ |
| 135 | + ddl_statements = [ |
| 136 | + "DROP TABLE foobar", |
| 137 | + "DROP TABLE locations", |
| 138 | + "DROP BLOB TABLE myfiles", |
| 139 | + "DROP USER me", |
| 140 | + "DROP USER trusted_me", |
| 141 | + ] |
| 142 | + _execute_statements(ddl_statements) |
| 143 | + |
| 144 | + |
| 145 | +class HttpsTestServerLayer: |
| 146 | + PORT = 65534 |
| 147 | + HOST = "localhost" |
| 148 | + CERT_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__), |
| 149 | + "pki/server_valid.pem")) |
| 150 | + CACERT_FILE = os.path.abspath(os.path.join(os.path.dirname(__file__), |
| 151 | + "pki/cacert_valid.pem")) |
| 152 | + |
| 153 | + __name__ = "httpsserver" |
| 154 | + __bases__ = tuple() |
| 155 | + |
| 156 | + class HttpsServer(HTTPServer): |
| 157 | + def get_request(self): |
| 158 | + |
| 159 | + # Prepare SSL context. |
| 160 | + context = ssl._create_unverified_context( |
| 161 | + protocol=ssl.PROTOCOL_TLS_SERVER, |
| 162 | + cert_reqs=ssl.CERT_OPTIONAL, |
| 163 | + check_hostname=False, |
| 164 | + purpose=ssl.Purpose.CLIENT_AUTH, |
| 165 | + certfile=HttpsTestServerLayer.CERT_FILE, |
| 166 | + keyfile=HttpsTestServerLayer.CERT_FILE, |
| 167 | + cafile=HttpsTestServerLayer.CACERT_FILE) |
| 168 | + |
| 169 | + # Set minimum protocol version, TLSv1 and TLSv1.1 are unsafe. |
| 170 | + context.minimum_version = ssl.TLSVersion.TLSv1_2 |
| 171 | + |
| 172 | + # Wrap TLS encryption around socket. |
| 173 | + socket, client_address = HTTPServer.get_request(self) |
| 174 | + socket = context.wrap_socket(socket, server_side=True) |
| 175 | + |
| 176 | + return socket, client_address |
| 177 | + |
| 178 | + class HttpsHandler(BaseHTTPRequestHandler): |
| 179 | + |
| 180 | + payload = json.dumps({"name": "test", "status": 200, }) |
| 181 | + |
| 182 | + def do_GET(self): |
| 183 | + self.send_response(200) |
| 184 | + payload = self.payload.encode('UTF-8') |
| 185 | + self.send_header("Content-Length", len(payload)) |
| 186 | + self.send_header("Content-Type", "application/json; charset=UTF-8") |
| 187 | + self.end_headers() |
| 188 | + self.wfile.write(payload) |
| 189 | + |
| 190 | + def setUp(self): |
| 191 | + self.server = self.HttpsServer( |
| 192 | + (self.HOST, self.PORT), |
| 193 | + self.HttpsHandler |
| 194 | + ) |
| 195 | + thread = threading.Thread(target=self.serve_forever) |
| 196 | + thread.daemon = True # quit interpreter when only thread exists |
| 197 | + thread.start() |
| 198 | + self.waitForServer() |
| 199 | + |
| 200 | + def serve_forever(self): |
| 201 | + print("listening on", self.HOST, self.PORT) |
| 202 | + self.server.serve_forever() |
| 203 | + print("server stopped.") |
| 204 | + |
| 205 | + def tearDown(self): |
| 206 | + self.server.shutdown() |
| 207 | + self.server.server_close() |
| 208 | + |
| 209 | + def isUp(self): |
| 210 | + """ |
| 211 | + Test if a host is up. |
| 212 | + """ |
| 213 | + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| 214 | + ex = s.connect_ex((self.HOST, self.PORT)) |
| 215 | + s.close() |
| 216 | + return ex == 0 |
| 217 | + |
| 218 | + def waitForServer(self, timeout=5): |
| 219 | + """ |
| 220 | + Wait for the host to be available. |
| 221 | + """ |
| 222 | + with stopit.ThreadingTimeout(timeout) as to_ctx_mgr: |
| 223 | + while True: |
| 224 | + if self.isUp(): |
| 225 | + break |
| 226 | + time.sleep(0.001) |
| 227 | + |
| 228 | + if not to_ctx_mgr: |
| 229 | + raise TimeoutError("Could not properly start embedded webserver " |
| 230 | + "within {} seconds".format(timeout)) |
| 231 | + |
| 232 | + |
| 233 | +def setUpWithHttps(test): |
| 234 | + test.globs['crate_host'] = "https://{0}:{1}".format( |
| 235 | + HttpsTestServerLayer.HOST, HttpsTestServerLayer.PORT |
| 236 | + ) |
| 237 | + test.globs['pprint'] = pprint |
| 238 | + test.globs['print'] = cprint |
| 239 | + |
| 240 | + test.globs['cacert_valid'] = os.path.abspath( |
| 241 | + os.path.join(os.path.dirname(__file__), "pki/cacert_valid.pem") |
| 242 | + ) |
| 243 | + test.globs['cacert_invalid'] = os.path.abspath( |
| 244 | + os.path.join(os.path.dirname(__file__), "pki/cacert_invalid.pem") |
| 245 | + ) |
| 246 | + test.globs['clientcert_valid'] = os.path.abspath( |
| 247 | + os.path.join(os.path.dirname(__file__), "pki/client_valid.pem") |
| 248 | + ) |
| 249 | + test.globs['clientcert_invalid'] = os.path.abspath( |
| 250 | + os.path.join(os.path.dirname(__file__), "pki/client_invalid.pem") |
| 251 | + ) |
| 252 | + |
| 253 | + |
| 254 | +def _execute_statements(statements, on_error="ignore"): |
| 255 | + with connect(crate_host) as conn: |
| 256 | + cursor = conn.cursor() |
| 257 | + for stmt in statements: |
| 258 | + _execute_statement(cursor, stmt, on_error=on_error) |
| 259 | + cursor.close() |
| 260 | + |
| 261 | + |
| 262 | +def _execute_statement(cursor, stmt, on_error="ignore"): |
| 263 | + try: |
| 264 | + cursor.execute(stmt) |
| 265 | + except Exception: # pragma: no cover |
| 266 | + # FIXME: Why does this croak on statements like ``DROP TABLE cities``? |
| 267 | + # Note: When needing to debug the test environment, you may want to |
| 268 | + # enable this logger statement. |
| 269 | + # log.exception("Executing SQL statement failed") |
| 270 | + if on_error == "ignore": |
| 271 | + pass |
| 272 | + elif on_error == "raise": |
| 273 | + raise |
0 commit comments