1
# Copyright 2005-2011 Canonical Ltd. All rights reserved.
3
# This program is free software: you can redistribute it and/or modify
4
# it under the terms of the GNU Affero General Public License as published by
5
# the Free Software Foundation, either version 3 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU Affero General Public License for more details.
13
# You should have received a copy of the GNU Affero General Public License
14
# along with this program. If not, see <http://www.gnu.org/licenses/>.
24
from oopstools.oops.models import Oops, OopsReadError, oops_re
27
# the section of the OOPS ID before the instance identifier is the
28
# days since the epoch, which is defined as the start of 2006.
29
epoch = datetime.datetime(2006, 01, 01, 00, 00, 00)
32
class OopsNotFound(LookupError):
37
"""A collection of OOPS reports"""
39
def __init__(self, oopsdir):
40
"""Create an OOPS store.
42
The oopsdir parameter is the top directory in the directory tree
43
containing oops reports. Each directory in the dir tree should
44
contain subdirectories of the form YYYY-mm-dd, which in turn contain
48
date_pat = re.compile('\d{4}-\d{2}-\d{2}')
50
# This is an optimization to find the directories.
51
# os.walk() was previously used here but it was taking too much time.
54
if not os.path.isdir(dir):
58
# Filter out filenames. We only want directories.
59
subdirs = [d for d in os.listdir(dir) if os.path.isdir(
60
os.path.join(dir, d))]
62
# Can't list dir, permission denied. Ignore it.
65
# dir is empty. Ignore it.
68
# Sort subdirs so date directories, if any, will show up as
70
sorted_subdirs = sorted(subdirs)
71
if date_pat.match(sorted_subdirs[0]):
72
self.oopsdirs.add(os.getcwd())
77
# XXX matsubara: remove the if clause when refactoring this code
78
# to use a single oops directory.
79
if type(oopsdir) == str:
81
# XXX matsubara: find_dirs changes directories to do its job and
82
# before calling it, record the original directory the script is
83
# in and after it finishes building
84
# self.oopsdirs set, the script returns to the original place.
85
original_working_dir = os.getcwd()
87
os.chdir(original_working_dir)
89
def find_oops(self, oopsid):
90
"""Get an OOPS report by oopsid."""
91
date, oops = self.canonical_name(oopsid).split('/', 1)
92
oops_prefix = oops_re.match(oops).group('oopsprefix')
94
for oopsdir in self.oopsdirs:
95
datedir = os.path.join(oopsdir, date)
96
if not os.path.isdir(datedir):
98
for filename in sorted(os.listdir(datedir)):
99
original_filename = filename
101
filename = filename.upper()
102
if (oops_prefix in filename and
103
(filename.endswith('.' + oops) or
104
filename.endswith('.' + oops + '.BZ2') or
105
filename.endswith('.' + oops + '.GZ') or
106
# U1/ISD oops reports use different filename scheme.
107
filename.endswith(oops + '.OOPS') or
108
filename.endswith(oops + '.OOPS.BZ2'))):
109
return Oops.from_pathname(
110
os.path.join(datedir, original_filename))
112
raise OopsNotFound("Could not find OOPS")
114
def canonical_name(self, name):
115
"""Canonicalise an OOPS report name."""
116
match = oops_re.match(name.upper())
118
raise OopsNotFound("Invalid OOPS name")
120
now = datetime.datetime.utcnow()
122
date = match.group('date')
123
dse = match.group('dse')
124
oops = match.group('oopsprefix') + match.group('id')
126
pass # got enough data
128
# dse is the number of days since the epoch
129
day = epoch + datetime.timedelta(days=int(dse) - 1)
130
date = '%04d-%02d-%02d' % (day.year, day.month, day.day)
132
# Assume the OOPS is from today
133
date = '%04d-%02d-%02d' % (now.year, now.month, now.day)
135
return '%s/%s' % (date, oops)
137
def _get_oops_prefix(self, filename):
138
"""Return oops prefix for a given filename."""
139
if filename.upper().endswith((".OOPS", ".OOPS.BZ2")):
140
# Got a U1/ISD oops report.
141
oops_prefix = filename.split(".", 1)[0]
142
oops_prefix = re.split("\d+", oops_prefix)[2]
144
#XXX: matsubara 2009-11-06 bug=353945 There's a disparity
145
# between the OOPS filename and the OOPS id, that's why
146
# there're two different regex to identify those.
147
oops_prefix = filename.split(".")[1]
148
oops_prefix = re.sub("\d+$", "", oops_prefix)
149
# Normalize oops prefix
150
return oops_prefix.upper()
152
def _search(self, startdate, enddate, prefixes=None):
153
"""Yield each OOPS report between the given two dates."""
155
while date <= enddate:
156
datestr = date.strftime('%Y-%m-%d')
157
for oopsdir in self.oopsdirs:
158
datedir = os.path.join(oopsdir, datestr)
159
if not os.path.isdir(datedir):
161
for filename in sorted(os.listdir(datedir)):
162
oops_prefix = self._get_oops_prefix(filename)
164
if oops_prefix not in prefixes:
165
# Didn't match my prefix, get outta here
168
yield Oops.from_pathname(
169
os.path.join(datedir, filename))
170
except OopsReadError:
171
# ignore non-OOPS reports
173
date += datetime.timedelta(days=1)
175
def search(self, startdatetime, enddatetime, prefixes=None):
176
"""Return a generator with OOPSes between the given two datetimes."""
177
oops_results = self._search(startdatetime, enddatetime, prefixes)
178
if not startdatetime.time() and not enddatetime.time():
179
# Faster to just return the generator from _search() when there's
180
# only date and no time.
183
return itertools.ifilter(
184
lambda oops: startdatetime <= oops.date <= enddatetime,