Package pulp :: Package server :: Package webservices :: Package controllers :: Module base
[hide private]
[frames] | no frames]

Source Code for Module pulp.server.webservices.controllers.base

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3  # 
  4  # Copyright © 2010 Red Hat, Inc. 
  5  # 
  6  # This software is licensed to you under the GNU General Public License, 
  7  # version 2 (GPLv2). There is NO WARRANTY for this software, express or 
  8  # implied, including the implied warranties of MERCHANTABILITY or FITNESS 
  9  # FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 
 10  # along with this software; if not, see 
 11  # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. 
 12  # 
 13  # Red Hat trademarks are not licensed under GPLv2. No permission is 
 14  # granted to use or replicate Red Hat trademarks that are incorporated 
 15  # in this software or its documentation. 
 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__) 
37 38 -class JSONController(object):
39 """ 40 Base controller class with convenience methods for JSON serialization 41 """ 42 @staticmethod
43 - def error_handler(method):
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
60 - def user_auth_required(roles=()):
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 # input methods ----------------------------------------------------------- 74
75 - def params(self):
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
85 - def filters(self, valid):
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
94 - def filter_results(self, results, filters):
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 # response methods -------------------------------------------------------- 117
118 - def _output(self, data):
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
153 - def bad_request(self, msg=None):
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
163 - def unauthorized(self, msg=None):
164 """ 165 """ 166 http.status_unauthorized() 167 return self._output(msg)
168
169 - def not_found(self, msg=None):
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
179 - def method_not_allowed(self, msg=None):
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
189 - def not_acceptable(self, msg=None):
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
199 - def conflict(self, msg=None):
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
209 - def internal_server_error(self, msg=None):
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
219 220 -class AsyncController(JSONController):
221 """ 222 Base controller class with convenience methods for executing asynchronous 223 tasks. 224 """
225 - def _task_to_dict(self, task):
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
238 - def _status_path(self, id):
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
268 - def cancel_task(self, task):
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
280 - def task_status(self, id):
281 """ 282 Get the current status of an asynchronous task. 283 @param id: task id 284 @return: TaskModel instance 285 """ 286 task = fifo.find(id=id) 287 status = None 288 if task is not None: 289 status = self._task_to_dict(task) 290 status.update({'status_path': self._status_path(id)}) 291 return status
292
293 - def find_task(self, id):
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
302 - def timeout(self, data):
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
334 - def accepted(self, status):
335 """ 336 Return an accepted response with status information in the body. 337 @return: JSON encoded response 338 """ 339 http.status_accepted() 340 status.update({'status_path': self._status_path(status['id'])}) 341 return self._output(status)
342