VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/batch/filearchiver.py@ 98523

Last change on this file since 98523 was 98103, checked in by vboxsync, 22 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: 10.4 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# $Id: filearchiver.py 98103 2023-01-17 14:15:46Z vboxsync $
4# pylint: disable=line-too-long
5
6"""
7A cronjob that compresses logs and other files, moving them to the
8g_ksZipFileAreaRootDir storage area.
9"""
10
11from __future__ import print_function;
12
13__copyright__ = \
14"""
15Copyright (C) 2012-2023 Oracle and/or its affiliates.
16
17This file is part of VirtualBox base platform packages, as
18available from https://www.virtualbox.org.
19
20This program is free software; you can redistribute it and/or
21modify it under the terms of the GNU General Public License
22as published by the Free Software Foundation, in version 3 of the
23License.
24
25This program is distributed in the hope that it will be useful, but
26WITHOUT ANY WARRANTY; without even the implied warranty of
27MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
28General Public License for more details.
29
30You should have received a copy of the GNU General Public License
31along with this program; if not, see <https://www.gnu.org/licenses>.
32
33The contents of this file may alternatively be used under the terms
34of the Common Development and Distribution License Version 1.0
35(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
36in the VirtualBox distribution, in which case the provisions of the
37CDDL are applicable instead of those of the GPL.
38
39You may elect to license modified versions of this file under the
40terms and conditions of either the GPL or the CDDL or both.
41
42SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
43"""
44__version__ = "$Revision: 98103 $"
45
46# Standard python imports
47import sys
48import os
49from optparse import OptionParser; # pylint: disable=deprecated-module
50import time;
51import zipfile;
52
53# Add Test Manager's modules path
54g_ksTestManagerDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
55sys.path.append(g_ksTestManagerDir)
56
57# Test Manager imports
58from common import utils;
59from testmanager import config;
60from testmanager.core.db import TMDatabaseConnection;
61from testmanager.core.testset import TestSetData, TestSetLogic;
62
63
64
65class FileArchiverBatchJob(object): # pylint: disable=too-few-public-methods
66 """
67 Log+files comp
68 """
69
70 def __init__(self, oOptions):
71 """
72 Parse command line
73 """
74 self.fVerbose = oOptions.fVerbose;
75 self.sSrcDir = config.g_ksFileAreaRootDir;
76 self.sDstDir = config.g_ksZipFileAreaRootDir;
77 #self.oTestSetLogic = TestSetLogic(TMDatabaseConnection(self.dprint if self.fVerbose else None));
78 self.oTestSetLogic = TestSetLogic(TMDatabaseConnection(None));
79 self.fDryRun = oOptions.fDryRun;
80
81 def dprint(self, sText):
82 """ Verbose output. """
83 if self.fVerbose:
84 print(sText);
85 return True;
86
87 def warning(self, sText):
88 """Prints a warning."""
89 print(sText);
90 return True;
91
92 def _processTestSet(self, idTestSet, asFiles, sCurDir):
93 """
94 Worker for processDir.
95 Same return codes as processDir.
96 """
97
98 sBaseFilename = os.path.join(sCurDir, 'TestSet-%d' % (idTestSet,));
99 if sBaseFilename[0:2] == ('.' + os.path.sep):
100 sBaseFilename = sBaseFilename[2:];
101 sSrcFileBase = os.path.join(self.sSrcDir, sBaseFilename + '-');
102
103 #
104 # Skip the file if the test set is still running.
105 # But delete them if the testset is not found.
106 #
107 oTestSet = self.oTestSetLogic.tryFetch(idTestSet);
108 if oTestSet is not None and sBaseFilename != oTestSet.sBaseFilename:
109 self.warning('TestSet %d: Deleting because sBaseFilename differs: "%s" (disk) vs "%s" (db)' \
110 % (idTestSet, sBaseFilename, oTestSet.sBaseFilename,));
111 oTestSet = None;
112
113 if oTestSet is not None:
114 if oTestSet.enmStatus == TestSetData.ksTestStatus_Running:
115 self.dprint('Skipping test set #%d, still running' % (idTestSet,));
116 return True;
117
118 #
119 # If we have a zip file already, don't try recreate it as we might
120 # have had trouble removing the source files.
121 #
122 sDstDirPath = os.path.join(self.sDstDir, sCurDir);
123 sZipFileNm = os.path.join(sDstDirPath, 'TestSet-%d.zip' % (idTestSet,));
124 if not os.path.exists(sZipFileNm):
125 #
126 # Create zip file with all testset files as members.
127 #
128 self.dprint('TestSet %d: Creating %s...' % (idTestSet, sZipFileNm,));
129 if not self.fDryRun:
130
131 if not os.path.exists(sDstDirPath):
132 os.makedirs(sDstDirPath, 0o755);
133
134 utils.noxcptDeleteFile(sZipFileNm + '.tmp');
135 with zipfile.ZipFile(sZipFileNm + '.tmp', 'w', zipfile.ZIP_DEFLATED, allowZip64 = True) as oZipFile:
136 for sFile in asFiles:
137 sSuff = os.path.splitext(sFile)[1];
138 if sSuff in [ '.png', '.webm', '.gz', '.bz2', '.zip', '.mov', '.avi', '.mpg', '.gif', '.jpg' ]:
139 ## @todo Consider storing these files outside the zip if they are a little largish.
140 self.dprint('TestSet %d: Storing %s...' % (idTestSet, sFile));
141 oZipFile.write(sSrcFileBase + sFile, sFile, zipfile.ZIP_STORED);
142 else:
143 self.dprint('TestSet %d: Deflating %s...' % (idTestSet, sFile));
144 oZipFile.write(sSrcFileBase + sFile, sFile, zipfile.ZIP_DEFLATED);
145
146 #
147 # .zip.tmp -> .zip.
148 #
149 utils.noxcptDeleteFile(sZipFileNm);
150 os.rename(sZipFileNm + '.tmp', sZipFileNm);
151
152 #else: Dry run.
153 else:
154 self.dprint('TestSet %d: zip file exists already (%s)' % (idTestSet, sZipFileNm,));
155
156 #
157 # Delete the files.
158 #
159 fRc = True;
160 if self.fVerbose:
161 self.dprint('TestSet %d: deleting file: %s' % (idTestSet, asFiles));
162 if not self.fDryRun:
163 for sFile in asFiles:
164 if utils.noxcptDeleteFile(sSrcFileBase + sFile) is False:
165 self.warning('TestSet %d: Failed to delete "%s" (%s)' % (idTestSet, sFile, sSrcFileBase + sFile,));
166 fRc = False;
167
168 return fRc;
169
170
171 def processDir(self, sCurDir):
172 """
173 Process the given directory (relative to sSrcDir and sDstDir).
174 Returns success indicator.
175 """
176 if self.fVerbose:
177 self.dprint('processDir: %s' % (sCurDir,));
178
179 #
180 # Sift thought the directory content, collecting subdirectories and
181 # sort relevant files by test set.
182 # Generally there will either be subdirs or there will be files.
183 #
184 asSubDirs = [];
185 dTestSets = {};
186 sCurPath = os.path.abspath(os.path.join(self.sSrcDir, sCurDir));
187 for sFile in os.listdir(sCurPath):
188 if os.path.isdir(os.path.join(sCurPath, sFile)):
189 if sFile not in [ '.', '..' ]:
190 asSubDirs.append(sFile);
191 elif sFile.startswith('TestSet-'):
192 # Parse the file name. ASSUMES 'TestSet-%d-filename' format.
193 iSlash1 = sFile.find('-');
194 iSlash2 = sFile.find('-', iSlash1 + 1);
195 if iSlash2 <= iSlash1:
196 self.warning('Bad filename (1): "%s"' % (sFile,));
197 continue;
198
199 try: idTestSet = int(sFile[(iSlash1 + 1):iSlash2]);
200 except:
201 self.warning('Bad filename (2): "%s"' % (sFile,));
202 if self.fVerbose:
203 self.dprint('\n'.join(utils.getXcptInfo(4)));
204 continue;
205
206 if idTestSet <= 0:
207 self.warning('Bad filename (3): "%s"' % (sFile,));
208 continue;
209
210 if iSlash2 + 2 >= len(sFile):
211 self.warning('Bad filename (4): "%s"' % (sFile,));
212 continue;
213 sName = sFile[(iSlash2 + 1):];
214
215 # Add it.
216 if idTestSet not in dTestSets:
217 dTestSets[idTestSet] = [];
218 asTestSet = dTestSets[idTestSet];
219 asTestSet.append(sName);
220
221 #
222 # Test sets.
223 #
224 fRc = True;
225 for idTestSet, oTestSet in dTestSets.items():
226 try:
227 if self._processTestSet(idTestSet, oTestSet, sCurDir) is not True:
228 fRc = False;
229 except:
230 self.warning('TestSet %d: Exception in _processTestSet:\n%s' % (idTestSet, '\n'.join(utils.getXcptInfo()),));
231 fRc = False;
232
233 #
234 # Sub dirs.
235 #
236 for sSubDir in asSubDirs:
237 if self.processDir(os.path.join(sCurDir, sSubDir)) is not True:
238 fRc = False;
239
240 #
241 # Try Remove the directory iff it's not '.' and it's been unmodified
242 # for the last 24h (race protection).
243 #
244 if sCurDir != '.':
245 try:
246 fpModTime = float(os.path.getmtime(sCurPath));
247 if fpModTime + (24*3600) <= time.time():
248 if utils.noxcptRmDir(sCurPath) is True:
249 self.dprint('Removed "%s".' % (sCurPath,));
250 except:
251 pass;
252
253 return fRc;
254
255 @staticmethod
256 def main():
257 """ C-style main(). """
258 #
259 # Parse options.
260 #
261 oParser = OptionParser();
262 oParser.add_option('-v', '--verbose', dest = 'fVerbose', action = 'store_true', default = False,
263 help = 'Verbose output.');
264 oParser.add_option('-q', '--quiet', dest = 'fVerbose', action = 'store_false', default = False,
265 help = 'Quiet operation.');
266 oParser.add_option('-d', '--dry-run', dest = 'fDryRun', action = 'store_true', default = False,
267 help = 'Dry run, do not make any changes.');
268 (oOptions, asArgs) = oParser.parse_args()
269 if asArgs != []:
270 oParser.print_help();
271 return 1;
272
273 #
274 # Do the work.
275 #
276 oBatchJob = FileArchiverBatchJob(oOptions);
277 fRc = oBatchJob.processDir('.');
278 return 0 if fRc is True else 1;
279
280if __name__ == '__main__':
281 sys.exit(FileArchiverBatchJob.main());
282
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