def _handle_task_error(self, e, task, error_text="while running task", return_response=False): import traceback trace = traceback.format_exc() self.logger.critical(("(%s) - Exception escaped! %s\nTrace: \n%s") % (timezone.now(), e, trace)) notes = { 'errors': [ "Error: %s(%s) %s. See task itself for details." % (type(e).__name__, e, error_text) ] } create_notification(task, notes, error=True) response_dict = { 'errors': [ "Error: Something went wrong on the server. " "It will be looked into shortly." ] } if return_response: return Response(response_dict, status=500) return response_dict, 500
def post(self, request, format=None): """ Unauthenticated endpoint bound primarily to NewProjectWithUser. This process requires approval, so this will validate incoming data and create a task to be approved later. """ self.logger.info("(%s) - Starting new project task." % timezone.now()) class_conf = settings.TASK_SETTINGS.get(self.task_type, {}) # we need to set the region the resources will be created in: request.data['region'] = class_conf.get('default_region') # parent_id for new project, if null defaults to domain: request.data['parent_id'] = class_conf.get('default_parent_id') processed, status = self.process_actions(request) errors = processed.get('errors', None) if errors: self.logger.info("(%s) - Validation errors with task." % timezone.now()) return Response(errors, status=status) notes = {'notes': ['New task for CreateProject.']} create_notification(processed['task'], notes) self.logger.info("(%s) - Task created." % timezone.now()) response_dict = {'notes': ['task created']} add_task_id_for_roles(request, processed, response_dict, ['admin']) return Response(response_dict, status=status)
def _create_token(self, task): token = create_token(task) try: class_conf = settings.TASK_SETTINGS.get( self.task_type, settings.DEFAULT_TASK_SETTINGS) # will throw a key error if the token template has not # been specified email_conf = class_conf['emails']['token'] send_stage_email(task, email_conf, token) return {'notes': ['created token']}, 200 except KeyError as e: import traceback trace = traceback.format_exc() self.logger.critical( ("(%s) - Exception escaped! %s\nTrace: \n%s") % (timezone.now(), e, trace)) notes = { 'errors': [("Error: '%s' while sending " + "token. See task " + "itself for details.") % e] } create_notification(task, notes, error=True) response_dict = { 'errors': [ "Error: Something went wrong on the " + "server. It will be looked into shortly." ] } return response_dict, 500
def post(self, request): request.data['project_id'] = request.keystone_user['project_id'] self.project_id = request.keystone_user['project_id'] regions = request.data.get('regions', None) if not regions: id_manager = user_store.IdentityManager() regions = [region.id for region in id_manager.list_regions()] request.data['regions'] = regions self.logger.info("(%s) - New UpdateProjectQuotas request." % timezone.now()) processed, status = self.process_actions(request) # check the status errors = processed.get('errors', None) if errors: self.logger.info("(%s) - Validation errors with task." % timezone.now()) return Response({'errors': errors}, status=status) if processed.get('auto_approved', False): response_dict = {'notes': processed['notes']} return Response(response_dict, status=status) task = processed['task'] action_models = task.actions valid = all([act.valid for act in action_models]) if not valid: return Response( { 'errors': [ 'Actions invalid. You may have usage ' 'above the new quota level.' ] }, 400) # Action needs to be manually approved notes = {'notes': ['New task for UpdateProjectQuotas.']} create_notification(processed['task'], notes) self.logger.info("(%s) - Task processed. Awaiting Aprroval" % timezone.now()) response_dict = {'notes': ['Task processed. Awaiting Aprroval.']} return Response(response_dict, status=202)
def post(self, request): """ Update Account Details """ self.logger.info("(%s) - Starting new AccountDetailsManageTask task." % timezone.now()) request.data['project_id'] = request.keystone_user['project_id'] processed, status = self.process_actions(request) errors = processed.get('errors', None) if errors: self.logger.info("(%s) - Validation errors with task." % timezone.now()) return Response(errors, status=status) # cancel any old tasks project_tasks = Task.objects.filter( project_id=request.keystone_user['project_id'], task_type=self.task_type, completed=0, cancelled=0).exclude(uuid=processed['task'].uuid) for task in project_tasks: task.cancelled = True task.save() if processed.get('auto_approved', False): response_dict = {'notes': ['Updated Account Address']} return Response(response_dict, status=status) task = processed['task'] action_models = task.actions valid = all([act.valid for act in action_models]) if not valid: return Response({'errors': ['Actions invalid.']}, 400) # Action needs to be manually approved notes = {'notes': ['New task for AddressManagement.']} create_notification(processed['task'], notes) self.logger.info("(%s) - Task processed. Awaiting Aprroval" % timezone.now()) response_dict = {'notes': ['Task submitted.', 'Awaiting Approval.']} return Response(response_dict, status=202)
def post(self, request, format=None): """ Unauthenticated endpoint bound primarily to NewClientSignUp and NewProjectSignUp. This task requires approval, so this will validate incoming data and create a task to be approved later. """ self.logger.info("(%s) - Starting new OpenStackSignUp task." % timezone.now()) class_conf = settings.TASK_SETTINGS.get(self.task_type, {}) # we need to set the region the resources will be created in: request.data['region'] = class_conf.get('default_region') # Will a default network be setup: request.data['setup_network'] = class_conf.get('setup_network', False) # domain_id for new project: request.data['domain_id'] = class_conf.get( 'default_domain_id', 'default') # parent_id for new project, if null defaults to domain: request.data['parent_id'] = class_conf.get('default_parent_id') processed, status = self.process_actions(request) errors = processed.get('errors', None) if errors: self.logger.info("(%s) - Validation errors with task." % timezone.now()) return Response(errors, status=status) notes = { 'notes': ['New OpenStackSignUp task.'] } create_notification(processed['task'], notes) self.logger.info("(%s) - Task created." % timezone.now()) response_dict = {'notes': ['Sign-up submitted.']} add_task_id_for_roles(request, processed, response_dict, ['admin']) return Response(response_dict, status=status)
def _get_parent_id(self): """Get ID of the owner (company) for the project Expects: - self.project_id - self.odoo_project_id Sets: - self.odoo_owner """ if getattr(self, 'odoo_owner', None): return self.odoo_owner.id odooclient = odoo_client.get_odoo_client() search = [['cloud_tenant', '=', self.odoo_project_id], ['contact_type', '=', 'owner']] all_relations = odooclient.project_relationships.list(search) if len(all_relations) > 1: note = ("WARNING! More than one owner found for '%s'" % self.project_id) self.add_note(note) if not self.get_cache('multi_owner_error'): create_notification(self.action.task, {'errors': [note]}, error=True) self.set_cache('multi_owner_error', True) if len(all_relations) < 1: note = ("WARNING! No owner found for '%s'" % self.project_id) self.add_note(note) if not self.get_cache('no_owner_error'): create_notification(self.action.task, {'errors': [note]}, error=True) self.set_cache('no_owner_error', True) return None self.odoo_owner = all_relations[0].partner_id self.add_note("Found owner: %s" % self.odoo_owner.name) return self.odoo_owner.id
def post(self, request, id, format=None): """ Ensures the required fields are present, will then pass those to the actions via the submit function. """ try: token = Token.objects.get(token=id) if token.expires < timezone.now(): token.delete() token = Token.objects.get(token=id) except Token.DoesNotExist: return Response( {'errors': ['This token does not exist or has expired.']}, status=404) if token.task.completed: return Response( {'errors': ['This task has already been completed.']}, status=400) if token.task.cancelled: return Response({'errors': ['This task has been cancelled.']}, status=400) if token.expires < timezone.now(): token.delete() return Response({'errors': ['This token has expired.']}, status=400) required_fields = set() actions = [] for action in token.task.actions: a = action.get_action() actions.append(a) for field in a.token_fields: required_fields.add(field) errors = {} data = {} for field in required_fields: try: data[field] = request.data[field] except KeyError: errors[field] = [ "This field is required.", ] except TypeError: errors = [ "Improperly formated json. " + "Should be a key-value object.", ] break if errors: return Response({"errors": errors}, status=400) for action in actions: try: action.submit(data) except Exception as e: notes = { 'errors': [("Error: '%s' while submitting task. " + "See task itself for details.") % e], 'task': token.task.uuid } create_notification(token.task, notes) import traceback trace = traceback.format_exc() self.logger.critical( ("(%s) - Exception escaped! %s\n" + "Trace: \n%s") % (timezone.now(), e, trace)) response_dict = { 'errors': [ "Error: Something went wrong on the server. " + "It will be looked into shortly." ] } return Response(response_dict, status=500) token.task.completed = True token.task.completed_on = timezone.now() token.task.save() token.delete() # Sending confirmation email: class_conf = settings.TASK_SETTINGS.get(token.task.task_type, settings.DEFAULT_TASK_SETTINGS) email_conf = class_conf.get('emails', {}).get('completed', None) send_stage_email(token.task, email_conf) return Response({'notes': ["Token submitted successfully."]}, status=200)
def post(self, request, format=None): """ Reissue a token for an approved task. Clears other tokens for it. """ uuid = request.data.get('task', None) if uuid is None: return Response({'task': [ "This field is required.", ]}, status=400) try: if 'admin' in request.keystone_user['roles']: task = Task.objects.get(uuid=uuid) else: task = Task.objects.get( uuid=uuid, project_id=request.keystone_user['project_id']) except Task.DoesNotExist: return Response({'errors': ['No task with this id.']}, status=404) if task.completed: return Response( {'errors': ['This task has already been completed.']}, status=400) if task.cancelled: return Response({'errors': ['This task has been cancelled.']}, status=400) if not task.approved: return Response({'errors': ['This task has not been approved.']}, status=400) for token in task.tokens: token.delete() token = create_token(task) try: class_conf = settings.TASK_SETTINGS.get( task.task_type, settings.DEFAULT_TASK_SETTINGS) # will throw a key error if the token template has not # been specified email_conf = class_conf['emails']['token'] send_stage_email(task, email_conf, token) except KeyError as e: notes = { 'errors': [("Error: '%(error)s' while sending token. " + "See registration itself for details.") % { 'error': e }], 'task': task.uuid } create_notification(task, notes) import traceback trace = traceback.format_exc() self.logger.critical( ("(%s) - Exception escaped!" + " %s\n Trace: \n%s") % (timezone.now(), e, trace)) response_dict = { 'errors': [ "Error: Something went wrong on the " + "server. It will be looked into shortly." ] } return Response(response_dict, status=500) return Response({'notes': ['Token reissued.']}, status=200)
def post(self, request, uuid, format=None): """ Will approve the Task specified, followed by running the post_approve actions and if valid will setup and create a related token. """ try: task = Task.objects.get(uuid=uuid) except Task.DoesNotExist: return Response({'errors': ['No task with this id.']}, status=404) try: if request.data.get('approved') is not True: return Response( {'approved': ["this is a required boolean field."]}, status=400) except ParseError: return Response( {'approved': ["this is a required boolean field."]}, status=400) if task.completed: return Response( {'errors': ['This task has already been completed.']}, status=400) if task.cancelled: return Response({'errors': ['This task has been cancelled.']}, status=400) # we check that the task is valid before approving it: valid = True for action in task.actions: if not action.valid: valid = False if not valid: return Response( { 'errors': [ 'Cannot approve an invalid task. ' + 'Update data and rerun pre_approve.' ] }, status=400) # We approve the task before running actions, # that way if something goes wrong we know if it was approved, # when it was approved, and who approved it last. Subsequent # reapproval attempts overwrite previous approved_by/on. task.approved = True task.approved_by = request.keystone_user task.approved_on = timezone.now() task.save() need_token = False valid = True actions = [] for action in task.actions: act_model = action.get_action() actions.append(act_model) try: act_model.post_approve() except Exception as e: notes = { 'errors': [("Error: '%s' while approving task. " + "See task itself for details.") % e], 'task': task.uuid } create_notification(task, notes) import traceback trace = traceback.format_exc() self.logger.critical( ("(%s) - Exception escaped! %s\n" + "Trace: \n%s") % (timezone.now(), e, trace)) return Response(notes, status=500) if not action.valid: valid = False if action.need_token: need_token = True if valid: if need_token: token = create_token(task) try: class_conf = settings.TASK_SETTINGS.get( task.task_type, settings.DEFAULT_TASK_SETTINGS) # will throw a key error if the token template has not # been specified email_conf = class_conf['emails']['token'] send_stage_email(task, email_conf, token) return Response({'notes': ['created token']}, status=200) except KeyError as e: notes = { 'errors': [("Error: '%s' while sending " + "token. See task " + "itself for details.") % e], 'task': task.uuid } create_notification(task, notes) import traceback trace = traceback.format_exc() self.logger.critical( ("(%s) - Exception escaped!" + " %s\n Trace: \n%s") % (timezone.now(), e, trace)) response_dict = { 'errors': [ "Error: Something went wrong on the " + "server. It will be looked into shortly." ] } return Response(response_dict, status=500) else: for action in actions: try: action.submit({}) except Exception as e: notes = { 'errors': [("Error: '%s' while submitting " + "task. See task " + "itself for details.") % e], 'task': task.uuid } create_notification(task, notes) import traceback trace = traceback.format_exc() self.logger.critical( ("(%s) - Exception escaped!" + " %s\n Trace: \n%s") % (timezone.now(), e, trace)) return Response(notes, status=500) task.completed = True task.completed_on = timezone.now() task.save() # Sending confirmation email: class_conf = settings.TASK_SETTINGS.get( task.task_type, settings.DEFAULT_TASK_SETTINGS) email_conf = class_conf.get('emails', {}).get('completed', None) send_stage_email(task, email_conf) return Response({'notes': ["Task completed successfully."]}, status=200) return Response({'errors': ['actions invalid']}, status=400)
def put(self, request, uuid, format=None): """ Allows the updating of action data and retriggering of the pre_approve step. """ try: task = Task.objects.get(uuid=uuid) except Task.DoesNotExist: return Response({'errors': ['No task with this id.']}, status=404) if task.completed: return Response( {'errors': ['This task has already been completed.']}, status=400) if task.cancelled: # NOTE(adriant): If we can uncancel a task, that should happen # at this endpoint. return Response({'errors': ['This task has been cancelled.']}, status=400) if task.approved: return Response( {'errors': ['This task has already been approved.']}, status=400) act_list = [] valid = True for action in task.actions: action_serializer = settings.ACTION_CLASSES[action.action_name][1] if action_serializer is not None: serializer = action_serializer(data=request.data) else: serializer = None act_list.append({ 'name': action.action_name, 'action': action, 'serializer': serializer }) if serializer is not None and not serializer.is_valid(): valid = False if valid: for act in act_list: if act['serializer'] is not None: data = act['serializer'].validated_data else: data = {} act['action'].action_data = data act['action'].save() try: act['action'].get_action().pre_approve() except Exception as e: notes = { 'errors': [("Error: '%s' while updating task. " + "See task itself for details.") % e], 'task': task.uuid } create_notification(task, notes) import traceback trace = traceback.format_exc() self.logger.critical( ("(%s) - Exception escaped! %s\n" + "Trace: \n%s") % (timezone.now(), e, trace)) response_dict = { 'errors': [ "Error: Something went wrong on the server. " + "It will be looked into shortly." ] } return Response(response_dict, status=500) return Response({'notes': ["Task successfully updated."]}, status=200) else: errors = {} for act in act_list: if act['serializer'] is not None: errors.update(act['serializer'].errors) return Response({'errors': errors}, status=400)
def approve(self, request, task): """ Approves the task and runs the post_approve steps. Will create a token if required, otherwise will run the submit steps. """ # We approve the task before running actions, # that way if something goes wrong we know if it was approved, # when it was approved, and who approved it. task.approved = True task.approved_on = timezone.now() task.approved_by = request.keystone_user task.save() action_models = task.actions actions = [act.get_action() for act in action_models] need_token = False valid = all([act.valid for act in actions]) if not valid: return {'errors': ['actions invalid']}, 400 # post_approve all actions for action in actions: try: action.post_approve() except Exception as e: import traceback trace = traceback.format_exc() self.logger.critical( ("(%s) - Exception escaped! %s\nTrace: \n%s") % (timezone.now(), e, trace)) notes = { 'errors': [("Error: '%s' while approving task. " + "See task itself for details.") % e] } create_notification(task, notes, error=True) response_dict = { 'errors': [ "Error: Something went wrong on the server. " + "It will be looked into shortly." ] } return response_dict, 500 valid = all([act.valid for act in actions]) if not valid: return {'errors': ['actions invalid']}, 400 need_token = any([act.need_token for act in actions]) if need_token: return self._create_token(task) # submit all actions for action in actions: try: action.submit({}) except Exception as e: import traceback trace = traceback.format_exc() self.logger.critical( ("(%s) - Exception escaped! %s\nTrace: \n%s") % (timezone.now(), e, trace)) notes = { 'errors': [("Error: '%s' while submitting " + "task. See task " + "itself for details.") % e] } create_notification(task, notes, error=True) response_dict = { 'errors': [ "Error: Something went wrong on the " + "server. It will be looked into shortly." ] } return response_dict, 500 task.completed = True task.completed_on = timezone.now() task.save() # Sending confirmation email: class_conf = settings.TASK_SETTINGS.get(self.task_type, settings.DEFAULT_TASK_SETTINGS) email_conf = class_conf.get('emails', {}).get('completed', None) send_stage_email(task, email_conf) return {'notes': ["Task completed successfully."]}, 200
def process_actions(self, request): """ Will ensure the request data contains the required data based on the action serializer, and if present will create a Task and the linked actions, attaching notes based on running of the the pre_approve validation function on all the actions. If during the pre_approve step at least one of the actions sets auto_approve to True, and none of them set it to False the approval steps will also be run. """ class_conf = settings.TASK_SETTINGS.get(self.task_type, settings.DEFAULT_TASK_SETTINGS) # Action serializers action_serializer_list = self._instantiate_action_serializers( request, class_conf) if isinstance(action_serializer_list, tuple): return action_serializer_list hash_key = create_task_hash(self.task_type, action_serializer_list) # Handle duplicates duplicate_error = self._handle_duplicates(class_conf, hash_key) if duplicate_error: return duplicate_error # Instantiate Task ip_address = request.META['REMOTE_ADDR'] keystone_user = request.keystone_user try: task = Task.objects.create(ip_address=ip_address, keystone_user=keystone_user, project_id=keystone_user['project_id'], task_type=self.task_type, hash_key=hash_key) except KeyError: task = Task.objects.create(ip_address=ip_address, keystone_user=keystone_user, task_type=self.task_type, hash_key=hash_key) task.save() # Instantiate actions with serializers for i, action in enumerate(action_serializer_list): data = action['serializer'].validated_data # construct the action class action_instance = action['action'](data=data, task=task, order=i) try: action_instance.pre_approve() except Exception as e: import traceback trace = traceback.format_exc() self.logger.critical( ("(%s) - Exception escaped! %s\nTrace: \n%s") % (timezone.now(), e, trace)) notes = { 'errors': [("Error: '%s' while setting up task. " + "See task itself for details.") % e] } create_notification(task, notes, error=True) response_dict = { 'errors': [ "Error: Something went wrong on the server. " + "It will be looked into shortly." ] } return response_dict, 200 # send initial confirmation email: email_conf = class_conf.get('emails', {}).get('initial', None) send_stage_email(task, email_conf) action_models = task.actions approve_list = [act.get_action().auto_approve for act in action_models] # TODO(amelia): It would be nice to explicitly test this, however # currently we don't have the right combinations of # actions to allow for it. if False in approve_list: can_auto_approve = False elif True in approve_list: can_auto_approve = True else: can_auto_approve = False if can_auto_approve: task_name = self.__class__.__name__ self.logger.info("(%s) - AutoApproving %s request." % (timezone.now(), task_name)) approval_data, status = self.approve(request, task) # Additional information that would be otherwise expected approval_data['task'] = task approval_data['auto_approved'] = True return approval_data, status return {'task': task}, 200
def send_email(to_addresses, context, conf, task): """ Function for sending emails from actions """ if not conf.get('template'): return if not to_addresses: return if isinstance(to_addresses, six.string_types): to_addresses = [to_addresses] elif isinstance(to_addresses, set): to_addresses = list(to_addresses) text_template = loader.get_template(conf['template'], using='include_etc_templates') html_template = conf.get('html_template', None) if html_template: html_template = loader.get_template(html_template, using='include_etc_templates') try: message = text_template.render(context) # from_email is the return-path and is distinct from the # message headers from_email = conf.get('from') if not from_email: from_email = conf.get('reply') if not from_email: return elif "%(task_uuid)s" in from_email: from_email = from_email % {'task_uuid': task.uuid} reply_email = conf['reply'] # these are the message headers which will be visible to # the email client. headers = { 'X-Adjutant-Task-UUID': task.uuid, # From needs to be set to be distinct from return-path 'From': reply_email, 'Reply-To': reply_email, } email = EmailMultiAlternatives( conf['subject'], message, from_email, to_addresses, headers=headers, ) if html_template: email.attach_alternative(html_template.render(context), "text/html") email.send(fail_silently=False) return True except SMTPException as e: notes = { 'errors': ("Error: '%s' while sending additional email for task: %s" % (e, task.uuid)) } errors_conf = settings.TASK_SETTINGS.get( task.task_type, settings.DEFAULT_TASK_SETTINGS).get('errors', {}).get("SMTPException", {}) if errors_conf: notification = create_notification(task, notes, error=True, engines=errors_conf.get( 'engines', True)) if errors_conf.get('notification') == "acknowledge": notification.acknowledged = True notification.save() else: create_notification(task, notes, error=True) return False