VirtualBox

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

Last change on this file was 106061, checked in by vboxsync, 4 months ago

Copyright year updates by scm.

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