VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/db/partial-db-dump.py@ 69448

Last change on this file since 69448 was 69111, checked in by vboxsync, 7 years ago

(C) year

  • Property svn:eol-style set to LF
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 14.2 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# $Id: partial-db-dump.py 69111 2017-10-17 14:26:02Z vboxsync $
4# pylint: disable=C0301
5
6"""
7Utility for dumping the last X days of data.
8"""
9
10__copyright__ = \
11"""
12Copyright (C) 2012-2017 Oracle Corporation
13
14This file is part of VirtualBox Open Source Edition (OSE), as
15available from http://www.virtualbox.org. This file is free software;
16you can redistribute it and/or modify it under the terms of the GNU
17General Public License (GPL) as published by the Free Software
18Foundation, in version 2 as it comes in the "COPYING" file of the
19VirtualBox OSE distribution. VirtualBox OSE is distributed in the
20hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
21
22The contents of this file may alternatively be used under the terms
23of the Common Development and Distribution License Version 1.0
24(CDDL) only, as it comes in the "COPYING.CDDL" file of the
25VirtualBox OSE distribution, in which case the provisions of the
26CDDL are applicable instead of those of the GPL.
27
28You may elect to license modified versions of this file under the
29terms and conditions of either the GPL or the CDDL or both.
30"""
31__version__ = "$Revision: 69111 $"
32
33# Standard python imports
34import sys;
35import os;
36import zipfile;
37from optparse import OptionParser;
38import xml.etree.ElementTree as ET;
39
40# Add Test Manager's modules path
41g_ksTestManagerDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))));
42sys.path.append(g_ksTestManagerDir);
43
44# Test Manager imports
45from testmanager.core.db import TMDatabaseConnection;
46from common import utils;
47
48
49class PartialDbDump(object): # pylint: disable=R0903
50 """
51 Dumps or loads the last X days of database data.
52
53 This is a useful tool when hacking on the test manager locally. You can get
54 a small sample from the last few days from the production test manager server
55 without spending hours dumping, downloading, and loading the whole database
56 (because it is gigantic).
57
58 """
59
60 def __init__(self):
61 """
62 Parse command line.
63 """
64
65 oParser = OptionParser()
66 oParser.add_option('-q', '--quiet', dest = 'fQuiet', action = 'store_true',
67 help = 'Quiet execution');
68 oParser.add_option('-f', '--filename', dest = 'sFilename', metavar = '<filename>',
69 default = 'partial-db-dump.zip', help = 'The name of the partial database zip file to write/load.');
70
71 oParser.add_option('-t', '--tmp-file', dest = 'sTempFile', metavar = '<temp-file>',
72 default = '/tmp/tm-partial-db-dump.pgtxt',
73 help = 'Name of temporary file for duping tables. Must be absolute');
74 oParser.add_option('--days-to-dump', dest = 'cDays', metavar = '<days>', type = 'int', default = 14,
75 help = 'How many days to dump (counting backward from current date).');
76 oParser.add_option('--load-dump-into-database', dest = 'fLoadDumpIntoDatabase', action = 'store_true',
77 default = False, help = 'For loading instead of dumping.');
78
79 (self.oConfig, _) = oParser.parse_args();
80
81
82 ##
83 # Tables dumped in full because they're either needed in full or they normally
84 # aren't large enough to bother reducing.
85 kasTablesToDumpInFull = [
86 'Users',
87 'BuildBlacklist',
88 'BuildCategories',
89 'BuildSources',
90 'FailureCategories',
91 'FailureReasons',
92 'GlobalResources',
93 'TestBoxStrTab',
94 'Testcases',
95 'TestcaseArgs',
96 'TestcaseDeps',
97 'TestcaseGlobalRsrcDeps',
98 'TestGroups',
99 'TestGroupMembers',
100 'SchedGroups',
101 'SchedGroupMembers', # ?
102 'SchedQueues',
103 'Builds', # ??
104 'VcsRevisions', # ?
105 'TestResultStrTab', # 36K rows, never mind complicated then.
106 ];
107
108 ##
109 # Tables where we only dump partial info (the TestResult* tables are rather
110 # gigantic).
111 kasTablesToPartiallyDump = [
112 'TestBoxes', # 2016-05-25: ca. 641 MB
113 'TestSets', # 2016-05-25: ca. 525 MB
114 'TestResults', # 2016-05-25: ca. 13 GB
115 'TestResultFiles', # 2016-05-25: ca. 87 MB
116 'TestResultMsgs', # 2016-05-25: ca. 29 MB
117 'TestResultValues', # 2016-05-25: ca. 3728 MB
118 'TestResultFailures',
119 'SystemLog',
120 ];
121
122 def _doCopyTo(self, sTable, oZipFile, oDb, sSql, aoArgs = None):
123 """ Does one COPY TO job. """
124 print 'Dumping %s...' % (sTable,);
125
126 if aoArgs is not None:
127 sSql = oDb.formatBindArgs(sSql, aoArgs);
128
129 oFile = open(self.oConfig.sTempFile, 'w');
130 oDb.copyExpert(sSql, oFile);
131 cRows = oDb.getRowCount();
132 oFile.close();
133 print '... %s rows.' % (cRows,);
134
135 oZipFile.write(self.oConfig.sTempFile, sTable);
136 return True;
137
138 def _doDump(self, oDb):
139 """ Does the dumping of the database. """
140
141 oZipFile = zipfile.ZipFile(self.oConfig.sFilename, 'w', zipfile.ZIP_DEFLATED);
142
143 oDb.begin();
144
145 # Dumping full tables is simple.
146 for sTable in self.kasTablesToDumpInFull:
147 self._doCopyTo(sTable, oZipFile, oDb, 'COPY ' + sTable + ' TO STDOUT WITH (FORMAT TEXT)');
148
149 # Figure out how far back we need to go.
150 oDb.execute('SELECT CURRENT_TIMESTAMP - INTERVAL \'%s days\'' % (self.oConfig.cDays,));
151 tsEffective = oDb.fetchOne()[0];
152 oDb.execute('SELECT CURRENT_TIMESTAMP - INTERVAL \'%s days\'' % (self.oConfig.cDays + 2,));
153 tsEffectiveSafe = oDb.fetchOne()[0];
154 print 'Going back to: %s (safe: %s)' % (tsEffective, tsEffectiveSafe);
155
156 # We dump test boxes back to the safe timestamp because the test sets may
157 # use slightly dated test box references and we don't wish to have dangling
158 # references when loading.
159 for sTable in [ 'TestBoxes', ]:
160 self._doCopyTo(sTable, oZipFile, oDb,
161 'COPY (SELECT * FROM ' + sTable + ' WHERE tsExpire >= %s) TO STDOUT WITH (FORMAT TEXT)',
162 (tsEffectiveSafe,));
163
164 # The test results needs to start with test sets and then dump everything
165 # releated to them. So, figure the lowest (oldest) test set ID we'll be
166 # dumping first.
167 oDb.execute('SELECT idTestSet FROM TestSets WHERE tsCreated >= %s', (tsEffective, ));
168 idFirstTestSet = 0;
169 if oDb.getRowCount() > 0:
170 idFirstTestSet = oDb.fetchOne()[0];
171 print 'First test set ID: %s' % (idFirstTestSet,);
172
173 oDb.execute('SELECT MAX(idTestSet) FROM TestSets WHERE tsCreated >= %s', (tsEffective, ));
174 idLastTestSet = 0;
175 if oDb.getRowCount() > 0:
176 idLastTestSet = oDb.fetchOne()[0];
177 print 'Last test set ID: %s' % (idLastTestSet,);
178
179 oDb.execute('SELECT MAX(idTestResult) FROM TestResults WHERE tsCreated >= %s', (tsEffective, ));
180 idLastTestResult = 0;
181 if oDb.getRowCount() > 0:
182 idLastTestResult = oDb.fetchOne()[0];
183 print 'Last test result ID: %s' % (idLastTestResult,);
184
185
186 # Tables with idTestSet member.
187 for sTable in [ 'TestSets', 'TestResults', 'TestResultValues' ]:
188 self._doCopyTo(sTable, oZipFile, oDb,
189 'COPY (SELECT *\n'
190 ' FROM ' + sTable + '\n'
191 ' WHERE idTestSet >= %s\n'
192 ' AND idTestSet <= %s\n'
193 ' AND idTestResult <= %s\n'
194 ') TO STDOUT WITH (FORMAT TEXT)'
195 , ( idFirstTestSet, idLastTestSet, idLastTestResult,));
196
197 # Tables where we have to go via TestResult.
198 for sTable in [ 'TestResultFiles', 'TestResultMsgs', 'TestResultFailures' ]:
199 self._doCopyTo(sTable, oZipFile, oDb,
200 'COPY (SELECT it.*\n'
201 ' FROM ' + sTable + ' it, TestResults tr\n'
202 ' WHERE tr.idTestSet >= %s\n'
203 ' AND tr.idTestSet <= %s\n'
204 ' AND tr.idTestResult <= %s\n'
205 ' AND tr.tsCreated >= %s\n' # performance hack.
206 ' AND it.idTestResult = tr.idTestResult\n'
207 ') TO STDOUT WITH (FORMAT TEXT)'
208 , ( idFirstTestSet, idLastTestSet, idLastTestResult, tsEffective,));
209
210 # Tables which goes exclusively by tsCreated.
211 for sTable in [ 'SystemLog', ]:
212 self._doCopyTo(sTable, oZipFile, oDb,
213 'COPY (SELECT * FROM ' + sTable + ' WHERE tsCreated >= %s) TO STDOUT WITH (FORMAT TEXT)',
214 (tsEffective,));
215
216 oZipFile.close();
217 print "Done!";
218 return 0;
219
220 def _doLoad(self, oDb):
221 """ Does the loading of the dumped data into the database. """
222
223 oZipFile = zipfile.ZipFile(self.oConfig.sFilename, 'r');
224
225 asTablesInLoadOrder = [
226 'Users',
227 'BuildBlacklist',
228 'BuildCategories',
229 'BuildSources',
230 'FailureCategories',
231 'FailureReasons',
232 'GlobalResources',
233 'Testcases',
234 'TestcaseArgs',
235 'TestcaseDeps',
236 'TestcaseGlobalRsrcDeps',
237 'TestGroups',
238 'TestGroupMembers',
239 'SchedGroups',
240 'TestBoxStrTab',
241 'TestBoxes',
242 'SchedGroupMembers',
243 'SchedQueues',
244 'Builds',
245 'SystemLog',
246 'VcsRevisions',
247 'TestResultStrTab',
248 'TestSets',
249 'TestResults',
250 'TestResultFiles',
251 'TestResultMsgs',
252 'TestResultValues',
253 'TestResultFailures',
254 ];
255 assert len(asTablesInLoadOrder) == len(self.kasTablesToDumpInFull) + len(self.kasTablesToPartiallyDump);
256
257 oDb.begin();
258 oDb.execute('SET CONSTRAINTS ALL DEFERRED;');
259
260 print 'Checking if the database looks empty...\n'
261 for sTable in asTablesInLoadOrder + [ 'TestBoxStatuses', 'GlobalResourceStatuses' ]:
262 oDb.execute('SELECT COUNT(*) FROM ' + sTable);
263 cRows = oDb.fetchOne()[0];
264 cMaxRows = 0;
265 if sTable in [ 'SchedGroups', 'TestBoxStrTab', 'TestResultStrTab', 'Users' ]: cMaxRows = 1;
266 if cRows > cMaxRows:
267 print 'error: Table %s has %u rows which is more than %u - refusing to delete and load.' \
268 % (sTable, cRows, cMaxRows,);
269 print 'info: Please drop and recreate the database before loading!'
270 return 1;
271
272 print 'Dropping default table content...\n'
273 for sTable in [ 'SchedGroups', 'TestBoxStrTab', 'TestResultStrTab', 'Users']:
274 oDb.execute('DELETE FROM ' + sTable);
275
276 oDb.execute('ALTER TABLE TestSets DROP CONSTRAINT IF EXISTS TestSets_idTestResult_fkey');
277
278 for sTable in asTablesInLoadOrder:
279 print 'Loading %s...' % (sTable,);
280 oFile = oZipFile.open(sTable);
281 oDb.copyExpert('COPY ' + sTable + ' FROM STDIN WITH (FORMAT TEXT)', oFile);
282 cRows = oDb.getRowCount();
283 print '... %s rows.' % (cRows,);
284
285 oDb.execute('ALTER TABLE TestSets ADD FOREIGN KEY (idTestResult) REFERENCES TestResults(idTestResult)');
286 oDb.commit();
287
288 # Correct sequences.
289 atSequences = [
290 ( 'UserIdSeq', 'Users', 'uid' ),
291 ( 'GlobalResourceIdSeq', 'GlobalResources', 'idGlobalRsrc' ),
292 ( 'BuildSourceIdSeq', 'BuildSources', 'idBuildSrc' ),
293 ( 'TestCaseIdSeq', 'TestCases', 'idTestCase' ),
294 ( 'TestCaseGenIdSeq', 'TestCases', 'idGenTestCase' ),
295 ( 'TestCaseArgsIdSeq', 'TestCaseArgs', 'idTestCaseArgs' ),
296 ( 'TestCaseArgsGenIdSeq', 'TestCaseArgs', 'idGenTestCaseArgs' ),
297 ( 'TestGroupIdSeq', 'TestGroups', 'idTestGroup' ),
298 ( 'SchedGroupIdSeq', 'SchedGroups', 'idSchedGroup' ),
299 ( 'TestBoxStrTabIdSeq', 'TestBoxStrTab', 'idStr' ),
300 ( 'TestBoxIdSeq', 'TestBoxes', 'idTestBox' ),
301 ( 'TestBoxGenIdSeq', 'TestBoxes', 'idGenTestBox' ),
302 ( 'FailureCategoryIdSeq', 'FailureCategories', 'idFailureCategory' ),
303 ( 'FailureReasonIdSeq', 'FailureReasons', 'idFailureReason' ),
304 ( 'BuildBlacklistIdSeq', 'BuildBlacklist', 'idBlacklisting' ),
305 ( 'BuildCategoryIdSeq', 'BuildCategories', 'idBuildCategory' ),
306 ( 'BuildIdSeq', 'Builds', 'idBuild' ),
307 ( 'TestResultStrTabIdSeq', 'TestResultStrTab', 'idStr' ),
308 ( 'TestResultIdSeq', 'TestResults', 'idTestResult' ),
309 ( 'TestResultValueIdSeq', 'TestResultValues', 'idTestResultValue' ),
310 ( 'TestResultFileId', 'TestResultFiles', 'idTestResultFile' ),
311 ( 'TestResultMsgIdSeq', 'TestResultMsgs', 'idTestResultMsg' ),
312 ( 'TestSetIdSeq', 'TestSets', 'idTestSet' ),
313 ( 'SchedQueueItemIdSeq', 'SchedQueues', 'idItem' ),
314 ];
315 for (sSeq, sTab, sCol) in atSequences:
316 oDb.execute('SELECT MAX(%s) FROM %s' % (sCol, sTab,));
317 idMax = oDb.fetchOne()[0];
318 print '%s: idMax=%s' % (sSeq, idMax);
319 if idMax is not None:
320 oDb.execute('SELECT setval(\'%s\', %s)' % (sSeq, idMax));
321
322 # Last step.
323 print 'Analyzing...'
324 oDb.execute('ANALYZE');
325 oDb.commit();
326
327 print 'Done!'
328 return 0;
329
330 def main(self):
331 """
332 Main function.
333 """
334 oDb = TMDatabaseConnection();
335
336 if self.oConfig.fLoadDumpIntoDatabase is not True:
337 rc = self._doDump(oDb);
338 else:
339 rc = self._doLoad(oDb);
340
341 oDb.close();
342 return 0;
343
344if __name__ == '__main__':
345 sys.exit(PartialDbDump().main());
346
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette