blob: a938971e40bd3783929427e5b2a157877d82cdaa [file] [log] [blame]
Neels Hofmeyr3531a192017-03-28 14:30:28 +02001# osmo_gsm_tester: trial: directory of binaries to be tested
2#
3# Copyright (C) 2016-2017 by sysmocom - s.f.m.c. GmbH
4#
5# Author: Neels Hofmeyr <neels@hofmeyr.de>
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20import os
21import time
22import shutil
23import tarfile
24
25from . import log, util
26
27FILE_MARK_TAKEN = 'taken'
28FILE_CHECKSUMS = 'checksums.md5'
29TIMESTAMP_FMT = '%Y-%m-%d_%H-%M-%S'
30FILE_LAST_RUN = 'last_run'
31
32class Trial(log.Origin):
33 path = None
34 dir = None
35 _run_dir = None
36 bin_tars = None
37
38 @staticmethod
39 def next(trials_dir):
40
41 with trials_dir.lock('Trial.next'):
42 trials = [e for e in trials_dir.children()
43 if trials_dir.isdir(e) and not trials_dir.exists(e, FILE_MARK_TAKEN)]
44 if not trials:
45 return None
46 # sort by time to get the one that waited longest
47 trials.sort(key=lambda e: os.path.getmtime(trials_dir.child(e)))
48 next_trial = trials[0]
49 return Trial(trials_dir.child(next_trial)).take()
50
51 def __init__(self, trial_dir):
52 self.path = trial_dir
53 self.set_name(self.path)
54 self.set_log_category(log.C_TST)
55 self.dir = util.Dir(self.path)
56 self.inst_dir = util.Dir(self.dir.child('inst'))
57 self.bin_tars = []
58
59 def __repr__(self):
60 return self.name()
61
62 def __enter__(self):
63 self.log('Trial start')
64 super().__enter__()
65
66 def __exit__(self, *exc_info):
67 super().__exit__(*exc_info)
68 self.log('Trial end')
69
70 def take(self):
71 self.dir.touch(FILE_MARK_TAKEN)
72 return self
73
74 def get_run_dir(self):
75 if self._run_dir is not None:
76 return self._run_dir
77 self._run_dir = util.Dir(self.dir.new_child('run.%s' % time.strftime(TIMESTAMP_FMT)))
78 self._run_dir.mkdir()
79
80 last_run = self.dir.child(FILE_LAST_RUN)
81 if os.path.islink(last_run):
82 os.remove(last_run)
83 if not os.path.exists(last_run):
84 os.symlink(self.dir.rel_path(self._run_dir.path), last_run)
85 return self._run_dir
86
87 def verify(self):
88 "verify checksums"
89
90 if not self.dir.exists():
91 raise RuntimeError('Trial dir does not exist: %r' % self.dir)
92 if not self.dir.isdir():
93 raise RuntimeError('Trial dir is not a dir: %r' % self.dir)
94
95 checksums = self.dir.child(FILE_CHECKSUMS)
96 if not self.dir.isfile(FILE_CHECKSUMS):
97 raise RuntimeError('No checksums file in trial dir: %r', checksums)
98
99 with open(checksums, 'r') as f:
100 line_nr = 0
101 for line in [l.strip() for l in f.readlines()]:
102 line_nr += 1
103 if not line:
104 continue
105 md5, filename = line.split(' ')
106 file_path = self.dir.child(filename)
107
108 if not self.dir.isfile(filename):
109 raise RuntimeError('File listed in checksums file but missing in trials dir:'
110 ' %r vs. %r line %d' % (file_path, checksums, line_nr))
111
112 if md5 != util.md5_of_file(file_path):
113 raise RuntimeError('Checksum mismatch for %r vs. %r line %d'
114 % (file_path, checksums, line_nr))
115
116 if filename.endswith('.tgz'):
117 self.bin_tars.append(filename)
118
119 def has_bin_tar(self, bin_name):
120 bin_tar_start = '%s.' % bin_name
121 matches = [t for t in self.bin_tars if t.startswith(bin_tar_start)]
122 self.dbg(bin_name=bin_name, matches=matches)
123 if not matches:
124 return None
125 if len(matches) > 1:
126 raise RuntimeError('More than one match for bin name %r: %r' % (bin_name, matches))
127 bin_tar = matches[0]
128 bin_tar_path = self.dir.child(bin_tar)
129 if not os.path.isfile(bin_tar_path):
130 raise RuntimeError('Not a file or missing: %r' % bin_tar_path)
131 return bin_tar_path
132
133 def get_inst(self, bin_name):
134 bin_tar = self.has_bin_tar(bin_name)
135 if not bin_tar:
136 return None
137 inst_dir = self.inst_dir.child(bin_name)
138
139 if os.path.isdir(inst_dir):
140 # already unpacked
141 return inst_dir
142
143 t = None
144 try:
145 os.makedirs(inst_dir)
146 t = tarfile.open(bin_tar)
147 t.extractall(inst_dir)
148 return inst_dir
149
150 except:
151 shutil.rmtree(inst_dir)
152 raise
153 finally:
154 if t:
155 try:
156 t.close()
157 except:
158 pass
159
160# vim: expandtab tabstop=4 shiftwidth=4