1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 import functools
18 import logging
19 import sys
20 import traceback
21 from datetime import timedelta
22
23 try:
24 import json
25 except ImportError:
26 import simplejson as json
27
28 import pymongo.json_util
29 import web
30
31 from pulp.server.tasking.task import Task, task_complete_states
32 from pulp.server.webservices import auth
33 from pulp.server.webservices import http
34 from pulp.server.webservices.queues import fifo
35
36 log = logging.getLogger(__name__)
39 """
40 Base controller class with convenience methods for JSON serialization
41 """
42 @staticmethod
44 """
45 Static controller method wrapper that catches internal errors and
46 reports them as JSON serialized trace back strings
47 """
48 @functools.wraps(method)
49 def report_error(self, *args, **kwargs):
50 try:
51 return method(self, *args, **kwargs)
52 except Exception:
53 exc_info = sys.exc_info()
54 tb_msg = ''.join(traceback.format_exception(*exc_info))
55 log.error("%s" % (traceback.format_exc()))
56 return self.internal_server_error(tb_msg)
57 return report_error
58
59 @staticmethod
61 """
62 Static Controller method to check user permissions on web service calls
63 """
64 def _user_auth_required(method):
65 @functools.wraps(method)
66 def check_user_auth(self, *args, **kwargs):
67 if not auth.check_roles(roles):
68 return self.unauthorized('You do not have permission for this URI')
69 return method(self, *args, **kwargs)
70 return check_user_auth
71 return _user_auth_required
72
73
74
76 """
77 JSON decode the objects in the requests body and return them
78 @return: dict of parameters passed in through the body
79 """
80 data = web.data()
81 if not data:
82 return {}
83 return json.loads(data)
84
86 """
87 Fetch any parameters passed on the url
88 @type valid: list of str's
89 @param valid: list of expected query parameters
90 @return: dict of param: [value(s)] of uri query parameters
91 """
92 return http.query_parameters(valid)
93
95 """
96 @deprecated: use mongo.filters_to_re_spec and pass the result into pulp's api instead
97 @type results: iterable of pulp model instances
98 @param results: results from a db query
99 @type filters: dict of str: list
100 @param filters: result filters passed in, in the uri
101 @return: list of model instances that meat the criteria in the filters
102 """
103 if not filters:
104 return results
105 new_results = []
106 for result in results:
107 is_good = True
108 for filter, criteria in filters.items():
109 if result[filter] not in criteria:
110 is_good = False
111 break
112 if is_good:
113 new_results.append(result)
114 return new_results
115
116
117
119 """
120 JSON encode the response and set the appropriate headers
121 """
122 http.header('Content-Type', 'application/json')
123 return json.dumps(data, default=pymongo.json_util.default)
124
125 - def ok(self, data):
126 """
127 Return an ok response.
128 @type data: mapping type
129 @param data: data to be returned in the body of the response
130 @return: JSON encoded response
131 """
132 http.status_ok()
133 return self._output(data)
134
135 - def created(self, location, data):
136 """
137 Return a created response.
138 @type location: str
139 @param location: URL of the created resource
140 @type data: mapping type
141 @param data: data to be returned in the body of the response
142 @return: JSON encoded response
143 """
144 http.status_created()
145 http.header('Location', location)
146 return self._output(data)
147
148 - def no_content(self):
149 """
150 """
151 return
152
154 """
155 Return a not found error.
156 @type msg: str
157 @param msg: optional error message
158 @return: JSON encoded response
159 """
160 http.status_bad_request()
161 return self._output(msg)
162
168
170 """
171 Return a not found error.
172 @type msg: str
173 @param msg: optional error message
174 @return: JSON encoded response
175 """
176 http.status_not_found()
177 return self._output(msg)
178
180 """
181 Return a method not allowed error.
182 @type msg: str
183 @param msg: optional error message
184 @return: JSON encoded response
185 """
186 http.status_method_not_allowed()
187 return None
188
190 """
191 Return a not acceptable error.
192 @type msg: str
193 @param msg: optional error message
194 @return: JSON encoded response
195 """
196 http.status_not_acceptable()
197 return self._output(msg)
198
200 """
201 Return a conflict error.
202 @type msg: str
203 @param msg: optional error message
204 @return: JSON encoded response
205 """
206 http.status_conflict()
207 return self._output(msg)
208
210 """
211 Return an internal server error.
212 @type msg: str
213 @param msg: optional error message
214 @return: JSON encoded response
215 """
216 http.status_internal_server_error()
217 return self._output(msg)
218
221 """
222 Base controller class with convenience methods for executing asynchronous
223 tasks.
224 """
226 """
227 Convert a task to a dictionary (non-destructive) while retaining the
228 pertinent information for a status check but in a more convenient form
229 for JSON serialization.
230 @type task: Task instance
231 @param task: task to convert
232 @return dict representing task
233 """
234 fields = ('id', 'method_name', 'state', 'start_time', 'finish_time',
235 'result', 'exception', 'traceback', 'progress')
236 return dict((f, getattr(task, f)) for f in fields)
237
239 """
240 Construct a URL path that can be used to poll a task's status
241 A status path is constructed as follows:
242 /<collection>/<object id>/<action>/<action id>/
243 A GET request sent to this path will get a JSON encoded status object
244 """
245 parts = web.ctx.path.split('/')
246 if parts[-2] == id:
247 return http.uri_path()
248 return http.extend_uri_path(id)
249
250 - def start_task(self,
251 func,
252 args=[],
253 kwargs={},
254 timeout=None,
255 unique=False):
256 """
257 Execute the function and its arguments as an asynchronous task.
258 @param func: python callable
259 @param args: positional arguments for func
260 @param kwargs: key word arguments for func
261 @return: dict representing the task
262 """
263 task = Task(func, args, kwargs, timeout)
264 if not fifo.enqueue(task, unique=unique):
265 return None
266 return task
267
269 """
270 Cancel the passed in task
271 @type task: Task instance
272 @param task: task to cancel
273 @return: True if the task was successfully canceled, False otherwise
274 """
275 if task is None or task.state in task_complete_states:
276 return False
277 fifo.cancel(task)
278 return True
279
292
294 """
295 Find and return a task with the given id
296 @type id: str
297 @param id: id of task to find
298 @return: Task instance if a task with the id exists, None otherwise
299 """
300 return fifo.find(id=id)
301
303 """
304 Parse any timeout values out of the passed in data
305 @type data: dict
306 @param data: values passed in via a request body
307 @return: datetime.timedelta instance corresponding to a properly
308 formatted timeout value if found in data, None otherwise
309 """
310 if 'timeout' not in data or data['timeout'] is None:
311 return None
312 timeouts = {
313 'days': lambda x: timedelta(days=x),
314 'seconds': lambda x: timedelta(seconds=x),
315 'microseconds': lambda x: timedelta(microseconds=x),
316 'milliseconds': lambda x: timedelta(milliseconds=x),
317 'minutes': lambda x: timedelta(minutes=x),
318 'hours': lambda x: timedelta(hours=x),
319 'weeks': lambda x: timedelta(weeks=x)
320 }
321 timeout = data['timeout']
322 if timeout.find(':') < 0:
323 return None
324 units, length = timeout.split(':', 1)
325 units = units.strip().lower()
326 if units not in timeouts:
327 return None
328 try:
329 length = int(length.strip())
330 except ValueError:
331 return None
332 return timeouts[units](length)
333
342