This repository has been archived by the owner on Dec 7, 2022. It is now read-only.
/
history.py
232 lines (180 loc) · 8.23 KB
/
history.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# -*- coding: utf-8 -*-
#
# Copyright © 2012 Red Hat, Inc.
#
# This software is licensed to you under the GNU General Public
# License as published by the Free Software Foundation; either version
# 2 of the License (GPLv2) or (at your option) any later version.
# There is NO WARRANTY for this software, express or implied,
# including the implied warranties of MERCHANTABILITY,
# NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
# have received a copy of GPLv2 along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
"""
Contains manager class and exceptions for operations for recording and retrieving
consumer history events.
"""
import logging
import datetime
import pymongo
import isodate
from pulp.common import dateutils
from pulp.server import config
from pulp.server.auth.principal import get_principal
from pulp.server.db.model.consumer import Consumer, ConsumerHistoryEvent
from pulp.server.exceptions import InvalidValue, MissingResource
# -- constants ----------------------------------------------------------------
# Event Types
TYPE_CONSUMER_REGISTERED = 'consumer_registered'
TYPE_CONSUMER_UNREGISTERED = 'consumer_unregistered'
TYPE_REPO_BOUND = 'repo_bound'
TYPE_REPO_UNBOUND = 'repo_unbound'
TYPE_CONTENT_UNIT_INSTALLED = 'content_unit_installed'
TYPE_CONTENT_UNIT_UNINSTALLED = 'content_unit_uninstalled'
TYPE_UNIT_PROFILE_CHANGED = 'unit_profile_changed'
TYPE_ADDED_TO_GROUP = 'added_to_group'
TYPE_REMOVED_FROM_GROUP = 'removed_from_group'
TYPES = (TYPE_CONSUMER_REGISTERED, TYPE_CONSUMER_UNREGISTERED, TYPE_REPO_BOUND,
TYPE_REPO_UNBOUND, TYPE_CONTENT_UNIT_INSTALLED, TYPE_CONTENT_UNIT_UNINSTALLED,
TYPE_UNIT_PROFILE_CHANGED, TYPE_ADDED_TO_GROUP, TYPE_REMOVED_FROM_GROUP)
# Maps user entered query sort parameters to the pymongo representation
SORT_ASCENDING = 'ascending'
SORT_DESCENDING = 'descending'
SORT_DIRECTION = {
SORT_ASCENDING : pymongo.ASCENDING,
SORT_DESCENDING : pymongo.DESCENDING,
}
_LOG = logging.getLogger(__name__)
# -- manager ------------------------------------------------------------------
class ConsumerHistoryManager(object):
"""
Performs consumer related CRUD operations
"""
# -- internal ----------------------------------------
def _originator(self):
'''
Returns the value to use as the originator of the consumer event (either the
consumer itself or an admin user).
@return: login of the originator value to use in the event
@rtype: string
'''
return get_principal()['login']
def record_event(self, consumer_id, event_type, event_details=None):
"""
@ivar consumer_id: identifies the consumer
@type id: str
@param type: event type
@type type: str
@param details: event details
@type details: dict
@raises MissingResource: if the given consumer does not exist
@raises InvalidValue: if any of the fields is unacceptable
"""
# Check that consumer exists for all except registration event
existing_consumer = Consumer.get_collection().find_one({'id' : consumer_id})
if not existing_consumer and event_type != TYPE_CONSUMER_UNREGISTERED:
raise MissingResource(consumer=consumer_id)
invalid_values = []
if event_type not in TYPES:
invalid_values.append('event_type')
if event_details is not None and not isinstance(event_details, dict):
invalid_values.append('event_details')
if invalid_values:
raise InvalidValue(invalid_values)
event = ConsumerHistoryEvent(consumer_id, self._originator(), event_type, event_details)
ConsumerHistoryEvent.get_collection().save(event, safe=True)
def query(self, consumer_id=None, event_type=None, limit=None, sort='descending',
start_date=None, end_date=None):
'''
Queries the consumer history storage.
@param consumer_id: if specified, events will only be returned for the the
consumer referenced
@type consumer_id: string or number
@param event_type: if specified, only events of the given type are returned
@type event_type: string (enumeration found in TYPES)
@param limit: if specified, the query will only return up to this amount of
entries; default is to not limit the entries returned
@type limit: number greater than zero
@param sort: indicates the sort direction of the results; results are sorted
by timestamp
@type sort: string; valid values are 'ascending' and 'descending'
@param start_date: if specified, no events prior to this date will be returned
@type start_date: datetime.datetime
@param end_date: if specified, no events after this date will be returned
@type end_date: datetime.datetime
@return: list of consumer history entries that match the given parameters;
empty list (not None) if no matching entries are found
@rtype: list of ConsumerHistoryEvent instances
@raises MissingResource: if the given consumer does not exist
@raises InvalidValue: if any of the fields is unacceptable
'''
invalid_values = []
if event_type is not None and event_type not in TYPES:
invalid_values.append('event_type')
# Verify the limit makes sense
if limit is not None and limit < 1:
invalid_values.append('limit')
# Verify the sort direction is valid
if not sort in SORT_DIRECTION:
invalid_values.append('sort')
# Verify that start_date and end_date is valid
if start_date is not None:
try:
dateutils.parse_iso8601_date(start_date)
except (ValueError, isodate.ISO8601Error):
invalid_values.append('start_date')
if end_date is not None:
try:
dateutils.parse_iso8601_date(end_date)
except (ValueError, isodate.ISO8601Error):
invalid_values.append('end_date')
if invalid_values:
raise InvalidValue(invalid_values)
# Assemble the mongo search parameters
search_params = {}
if consumer_id:
search_params['consumer_id'] = consumer_id
if event_type:
search_params['type'] = event_type
# Add in date range limits if specified
date_range = {}
if start_date:
date_range['$gte'] = start_date
if end_date:
date_range['$lte'] = end_date
if len(date_range) > 0:
search_params['timestamp'] = date_range
# Determine the correct mongo cursor to retrieve
if len(search_params) == 0:
cursor = ConsumerHistoryEvent.get_collection().find()
else:
cursor = ConsumerHistoryEvent.get_collection().find(search_params)
# Sort by most recent entry first
cursor.sort('timestamp', direction=SORT_DIRECTION[sort])
# If a limit was specified, add it to the cursor
if limit:
cursor.limit(limit)
# Finally convert to a list before returning
return list(cursor)
def event_types(self):
return TYPES
def cull_history(self, lifetime):
'''
Deletes all consumer history entries that are older than the given lifetime.
@param lifetime: length in days; history entries older than this many days old
are deleted in this call
@type lifetime: L{datetime.timedelta}
'''
now = datetime.datetime.now(dateutils.local_tz())
limit = dateutils.format_iso8601_datetime(now - lifetime)
spec = {'timestamp': {'$lt': limit}}
self.collection.remove(spec, safe=False)
def _get_lifetime(self):
'''
Returns the configured maximum lifetime for consumer history entries.
@return: time in days
@rtype: L{datetime.timedelta}
'''
days = config.config.getint('consumer_history', 'lifetime')
return datetime.timedelta(days=days)
# -- functions ----------------------------------------------------------------