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