github.com/yrj2011/jx-test-infra@v0.0.0-20190529031832-7a2065ee98eb/kettle/model.py (about) 1 # Copyright 2017 The Kubernetes Authors. 2 # 3 # Licensed under the Apache License, Version 2.0 (the "License"); 4 # you may not use this file except in compliance with the License. 5 # You may obtain a copy of the License at 6 # 7 # http://www.apache.org/licenses/LICENSE-2.0 8 # 9 # Unless required by applicable law or agreed to in writing, software 10 # distributed under the License is distributed on an "AS IS" BASIS, 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 # See the License for the specific language governing permissions and 13 # limitations under the License. 14 15 16 import json 17 import os 18 import sqlite3 19 import time 20 import zlib 21 22 23 class Database(object): 24 """ 25 Store build and test result information, and support incremental updates to results. 26 """ 27 28 DEFAULT_INCREMENTAL_TABLE = 'build_emitted' 29 30 def __init__(self, path=None): 31 if path is None: 32 path = os.getenv('KETTLE_DB') or 'build.db' 33 self.db = sqlite3.connect(path) 34 self.db.executescript(''' 35 create table if not exists build(gcs_path primary key, started_json, finished_json, finished_time); 36 create table if not exists file(path string primary key, data); 37 create table if not exists build_junit_missing(build_id integer primary key); 38 create index if not exists build_finished_time_idx on build(finished_time) 39 ''') 40 41 def commit(self): 42 self.db.commit() 43 44 def get_existing_builds(self, jobs_dir): 45 """ 46 Return a set of (job, number) tuples indicating already present builds. 47 48 A build is already present if it has a finished.json, or if it's older than 49 five days with no finished.json. 50 """ 51 builds_have_paths = self.db.execute( 52 'select gcs_path from build' 53 ' where gcs_path between ? and ?' 54 ' and finished_json IS NOT NULL' 55 , 56 (jobs_dir + '\x00', jobs_dir + '\x7f')).fetchall() 57 path_tuple = lambda path: tuple(path[len(jobs_dir):].split('/')[-2:]) 58 builds_have = {path_tuple(path) for (path,) in builds_have_paths} 59 for path, started_json in self.db.execute( 60 'select gcs_path, started_json from build' 61 ' where gcs_path between ? and ?' 62 ' and started_json IS NOT NULL and finished_json IS NULL', 63 (jobs_dir + '\x00', jobs_dir + '\x7f')): 64 started = json.loads(started_json) 65 if int(started['timestamp']) < time.time() - 60*60*24*5: 66 # over 5 days old, no need to try looking for finished any more. 67 builds_have.add(path_tuple(path)) 68 return builds_have 69 70 ### make_db 71 72 def insert_build(self, build_dir, started, finished): 73 """ 74 Add a build with optional started and finished dictionaries to the database. 75 """ 76 started_json = started and json.dumps(started, sort_keys=True) 77 finished_json = finished and json.dumps(finished, sort_keys=True) 78 if not self.db.execute( 79 'select 1 from build where gcs_path=? ' 80 'and started_json=? and finished_json=?', 81 (build_dir, started_json, finished_json)).fetchone(): 82 rowid = self.db.execute( 83 'insert or replace into build values(?,?,?,?)', 84 (build_dir, started_json, finished_json, 85 finished and finished.get('timestamp', None))).lastrowid 86 self.db.execute('insert into build_junit_missing values(?)', (rowid,)) 87 return True 88 return False 89 90 def get_builds_missing_junit(self): 91 """ 92 Return (rowid, path) for each build that hasn't enumerated junit files. 93 """ 94 # cleanup 95 self.db.execute('delete from build_junit_missing' 96 ' where build_id not in (select rowid from build)') 97 return self.db.execute( 98 'select rowid, gcs_path from build' 99 ' where rowid in (select build_id from build_junit_missing)' 100 ).fetchall() 101 102 def insert_build_junits(self, build_id, junits): 103 """ 104 Insert a junit dictionary {gcs_path: contents} for a given build's rowid. 105 """ 106 for path, data in junits.iteritems(): 107 self.db.execute('replace into file values(?,?)', 108 (path, buffer(zlib.compress(data, 9)))) 109 self.db.execute('delete from build_junit_missing where build_id=?', (build_id,)) 110 111 ### make_json 112 113 def _init_incremental(self, table): 114 """ 115 Create tables necessary for storing incremental emission state. 116 """ 117 self.db.execute('create table if not exists %s(build_id integer primary key, gen)' % table) 118 119 @staticmethod 120 def _get_builds(results): 121 for rowid, path, started, finished in results: 122 started = started and json.loads(started) 123 finished = finished and json.loads(finished) 124 yield rowid, path, started, finished 125 126 def get_builds(self, path='', min_started=None, incremental_table=DEFAULT_INCREMENTAL_TABLE): 127 """ 128 Iterate through (buildid, gcs_path, started, finished) for each build under 129 the given path that has not already been emitted. 130 """ 131 self._init_incremental(incremental_table) 132 results = self.db.execute( 133 'select rowid, gcs_path, started_json, finished_json from build ' 134 'where gcs_path like ?' 135 ' and finished_time >= ?' + 136 ' and rowid not in (select build_id from %s)' 137 ' order by finished_time' % incremental_table 138 , (path + '%', min_started or 0)).fetchall() 139 return self._get_builds(results) 140 141 def get_builds_from_paths(self, paths, incremental_table=DEFAULT_INCREMENTAL_TABLE): 142 self._init_incremental(incremental_table) 143 results = self.db.execute( 144 'select rowid, gcs_path, started_json, finished_json from build ' 145 'where gcs_path in (%s)' 146 ' and rowid not in (select build_id from %s)' 147 ' order by finished_time' % (','.join(['?'] * len(paths)), incremental_table) 148 , paths).fetchall() 149 return self._get_builds(results) 150 151 def test_results_for_build(self, path): 152 """ 153 Return a list of file data under the given path. Intended for JUnit artifacts. 154 """ 155 results = [] 156 for dataz, in self.db.execute( 157 'select data from file where path between ? and ?', 158 (path, path + '\x7F')): 159 data = zlib.decompress(dataz) 160 if data: 161 results.append(data) 162 return results 163 164 def get_oldest_emitted(self, incremental_table): 165 return self.db.execute('select min(finished_time) from build ' 166 'where rowid in (select build_id from %s)' 167 % incremental_table).fetchone()[0] 168 169 def reset_emitted(self, incremental_table=DEFAULT_INCREMENTAL_TABLE): 170 self.db.execute('drop table if exists %s' % incremental_table) 171 172 def insert_emitted(self, rows_emitted, incremental_table=DEFAULT_INCREMENTAL_TABLE): 173 self._init_incremental(incremental_table) 174 gen, = self.db.execute('select max(gen)+1 from %s' % incremental_table).fetchone() 175 if not gen: 176 gen = 0 177 self.db.executemany( 178 'insert into %s values(?,?)' % incremental_table, 179 ((row, gen) for row in rows_emitted)) 180 self.db.commit() 181 return gen