def create(self, request): """Creates a new recipe type and returns a link to the detail URL :param request: the HTTP POST request :type request: :class:`rest_framework.request.Request` :rtype: :class:`rest_framework.response.Response` :returns: the HTTP response to send back to the user """ name = rest_util.parse_string(request, 'name') version = rest_util.parse_string(request, 'version') title = rest_util.parse_string(request, 'title', default_value=name) description = rest_util.parse_string(request, 'description', required=False) definition_dict = rest_util.parse_dict(request, 'definition') # Check for optional trigger rule parameters trigger_rule_dict = rest_util.parse_dict(request, 'trigger_rule', required=False) if (('type' in trigger_rule_dict and 'configuration' not in trigger_rule_dict) or ('type' not in trigger_rule_dict and 'configuration' in trigger_rule_dict)): raise BadParameter('Trigger type and configuration are required together.') is_active = trigger_rule_dict['is_active'] if 'is_active' in trigger_rule_dict else True # Attempt to look up the trigger handler for the type rule_handler = None if trigger_rule_dict and 'type' in trigger_rule_dict: try: rule_handler = trigger_handler.get_trigger_rule_handler(trigger_rule_dict['type']) except InvalidTriggerType as ex: logger.exception('Invalid trigger type for new recipe type: %s', name) raise BadParameter(unicode(ex)) try: with transaction.atomic(): # Validate the recipe definition logger.info(definition_dict) recipe_def = RecipeDefinitionSunset.create(definition_dict) # Attempt to create the trigger rule trigger_rule = None if rule_handler and 'configuration' in trigger_rule_dict: trigger_rule = rule_handler.create_trigger_rule(trigger_rule_dict['configuration'], name, is_active) # Create the recipe type recipe_type = RecipeType.objects.create_recipe_type(name, version, title, description, recipe_def, trigger_rule) except (InvalidDefinition, InvalidTriggerType, InvalidTriggerRule, InvalidRecipeConnection) as ex: logger.exception('Unable to create new recipe type: %s', name) raise BadParameter(unicode(ex)) # Fetch the full recipe type with details try: recipe_type = RecipeType.objects.get_details_v5(recipe_type.id) except RecipeType.DoesNotExist: raise Http404 url = reverse('recipe_type_details_view', args=[recipe_type.id], request=request) if self.request.version == 'v6': serializer = RecipeTypeDetailsSerializerV6(recipe_type) else: serializer = RecipeTypeDetailsSerializerV5(recipe_type) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=dict(location=url))
def post(self, request): """Creates a new recipe type and returns a link to the detail URL :param request: the HTTP POST request :type request: :class:`rest_framework.request.Request` :rtype: :class:`rest_framework.response.Response` :returns: the HTTP response to send back to the user """ name = rest_util.parse_string(request, 'name') version = rest_util.parse_string(request, 'version') title = rest_util.parse_string(request, 'title', default_value=name) description = rest_util.parse_string(request, 'description', required=False) definition_dict = rest_util.parse_dict(request, 'definition') # Check for optional trigger rule parameters trigger_rule_dict = rest_util.parse_dict(request, 'trigger_rule', required=False) if (('type' in trigger_rule_dict and 'configuration' not in trigger_rule_dict) or ('type' not in trigger_rule_dict and 'configuration' in trigger_rule_dict)): raise BadParameter('Trigger type and configuration are required together.') is_active = trigger_rule_dict['is_active'] if 'is_active' in trigger_rule_dict else True # Attempt to look up the trigger handler for the type rule_handler = None if trigger_rule_dict and 'type' in trigger_rule_dict: try: rule_handler = trigger_handler.get_trigger_rule_handler(trigger_rule_dict['type']) except InvalidTriggerType as ex: logger.exception('Invalid trigger type for new recipe type: %s', name) raise BadParameter(unicode(ex)) try: with transaction.atomic(): # Validate the recipe definition recipe_def = RecipeDefinition(definition_dict) # Attempt to create the trigger rule trigger_rule = None if rule_handler and 'configuration' in trigger_rule_dict: trigger_rule = rule_handler.create_trigger_rule(trigger_rule_dict['configuration'], name, is_active) # Create the recipe type recipe_type = RecipeType.objects.create_recipe_type(name, version, title, description, recipe_def, trigger_rule) except (InvalidDefinition, InvalidTriggerType, InvalidTriggerRule, InvalidRecipeConnection) as ex: logger.exception('Unable to create new recipe type: %s', name) raise BadParameter(unicode(ex)) # Fetch the full recipe type with details try: recipe_type = RecipeType.objects.get_details(recipe_type.id) except RecipeType.DoesNotExist: raise Http404 url = urlresolvers.reverse('recipe_type_details_view', args=[recipe_type.id]) serializer = RecipeTypeDetailsSerializer(recipe_type) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=dict(location=url))
def get_configuration(self): """Returns the configuration for this trigger rule :returns: The configuration for this trigger rule :rtype: :class:`trigger.configuration.trigger_rule.TriggerRuleConfiguration` :raises :class:`trigger.configuration.exceptions.InvalidTriggerType`: If the trigger type is invalid """ from trigger.handler import get_trigger_rule_handler handler = get_trigger_rule_handler(self.type) return handler.create_configuration(self.configuration)
def post(self, request): """Validates a new recipe type and returns any warnings discovered :param request: the HTTP POST request :type request: :class:`rest_framework.request.Request` :rtype: :class:`rest_framework.response.Response` :returns: the HTTP response to send back to the user """ name = rest_util.parse_string(request, 'name') version = rest_util.parse_string(request, 'version') title = rest_util.parse_string(request, 'title', default_value=name) description = rest_util.parse_string(request, 'description', required=False) definition_dict = rest_util.parse_dict(request, 'definition') # Check for optional trigger rule parameters trigger_rule_dict = rest_util.parse_dict(request, 'trigger_rule', required=False) if (('type' in trigger_rule_dict and 'configuration' not in trigger_rule_dict) or ('type' not in trigger_rule_dict and 'configuration' in trigger_rule_dict)): raise BadParameter('Trigger type and configuration are required together.') # Attempt to look up the trigger handler for the type rule_handler = None if trigger_rule_dict and 'type' in trigger_rule_dict: try: rule_handler = trigger_handler.get_trigger_rule_handler(trigger_rule_dict['type']) except InvalidTriggerType as ex: logger.exception('Invalid trigger type for recipe validation: %s', name) raise BadParameter(unicode(ex)) # Attempt to look up the trigger rule configuration trigger_config = None if rule_handler and 'configuration' in trigger_rule_dict: try: trigger_config = rule_handler.create_configuration(trigger_rule_dict['configuration']) except InvalidTriggerRule as ex: logger.exception('Invalid trigger rule configuration for recipe validation: %s', name) raise BadParameter(unicode(ex)) # Validate the recipe definition try: recipe_def = RecipeDefinition(definition_dict) warnings = RecipeType.objects.validate_recipe_type(name, title, version, description, recipe_def, trigger_config) except (InvalidDefinition, InvalidTriggerType, InvalidTriggerRule, InvalidRecipeConnection) as ex: logger.exception('Unable to validate new recipe type: %s', name) raise BadParameter(unicode(ex)) results = [{'id': w.key, 'details': w.details} for w in warnings] return Response({'warnings': results})
def post(self, request): """Validates a new recipe type and returns any warnings discovered :param request: the HTTP POST request :type request: :class:`rest_framework.request.Request` :rtype: :class:`rest_framework.response.Response` :returns: the HTTP response to send back to the user """ name = rest_util.parse_string(request, 'name') version = rest_util.parse_string(request, 'version') title = rest_util.parse_string(request, 'title', default_value=name) description = rest_util.parse_string(request, 'description', required=False) definition_dict = rest_util.parse_dict(request, 'definition') # Check for optional trigger rule parameters trigger_rule_dict = rest_util.parse_dict(request, 'trigger_rule', required=False) if (('type' in trigger_rule_dict and 'configuration' not in trigger_rule_dict) or ('type' not in trigger_rule_dict and 'configuration' in trigger_rule_dict)): raise BadParameter('Trigger type and configuration are required together.') # Attempt to look up the trigger handler for the type rule_handler = None if trigger_rule_dict and 'type' in trigger_rule_dict: try: rule_handler = trigger_handler.get_trigger_rule_handler(trigger_rule_dict['type']) except InvalidTriggerType as ex: logger.exception('Invalid trigger type for recipe validation: %s', name) raise BadParameter(unicode(ex)) # Attempt to look up the trigger rule configuration trigger_config = None if rule_handler and 'configuration' in trigger_rule_dict: try: trigger_config = rule_handler.create_configuration(trigger_rule_dict['configuration']) except InvalidTriggerRule as ex: logger.exception('Invalid trigger rule configuration for recipe validation: %s', name) raise BadParameter(unicode(ex)) # Validate the recipe definition try: recipe_def = RecipeDefinitionSunset.create(definition_dict) warnings = RecipeType.objects.validate_recipe_type(name, title, version, description, recipe_def, trigger_config) except (InvalidDefinition, InvalidTriggerType, InvalidTriggerRule, InvalidRecipeConnection) as ex: logger.exception('Unable to validate new recipe type: %s', name) raise BadParameter(unicode(ex)) results = [{'id': w.key, 'details': w.details} for w in warnings] return Response({'warnings': results})
def patch(self, request, job_type_id): """Edits an existing job type and returns the updated details :param request: the HTTP GET request :type request: :class:`rest_framework.request.Request` :param job_type_id: The ID for the job type. :type job_type_id: int encoded as a str :rtype: :class:`rest_framework.response.Response` :returns: the HTTP response to send back to the user """ # Validate the job interface interface_dict = rest_util.parse_dict(request, 'interface', required=False) interface = None try: if interface_dict: interface = JobInterface(interface_dict) except InvalidInterfaceDefinition as ex: raise BadParameter('Job type interface invalid: %s' % unicode(ex)) # Validate the error mapping error_dict = rest_util.parse_dict(request, 'error_mapping', required=False) error_mapping = None try: if error_dict: error_mapping = ErrorInterface(error_dict) error_mapping.validate() except InvalidInterfaceDefinition as ex: raise BadParameter('Job type error mapping invalid: %s' % unicode(ex)) # Check for optional trigger rule parameters trigger_rule_dict = rest_util.parse_dict(request, 'trigger_rule', required=False) if (('type' in trigger_rule_dict and 'configuration' not in trigger_rule_dict) or ('type' not in trigger_rule_dict and 'configuration' in trigger_rule_dict)): raise BadParameter( 'Trigger type and configuration are required together.') is_active = trigger_rule_dict[ 'is_active'] if 'is_active' in trigger_rule_dict else True remove_trigger_rule = rest_util.has_params( request, 'trigger_rule') and not trigger_rule_dict # Fetch the current job type model try: job_type = JobType.objects.select_related('trigger_rule').get( pk=job_type_id) except JobType.DoesNotExist: raise Http404 # Attempt to look up the trigger handler for the type rule_handler = None if trigger_rule_dict and 'type' in trigger_rule_dict: try: rule_handler = trigger_handler.get_trigger_rule_handler( trigger_rule_dict['type']) except InvalidTriggerType as ex: logger.exception('Invalid trigger type for job type: %i', job_type_id) raise BadParameter(unicode(ex)) # Extract the fields that should be updated as keyword arguments extra_fields = {} base_fields = { 'name', 'version', 'interface', 'trigger_rule', 'error_mapping' } for key, value in request.data.iteritems(): if key not in base_fields and key not in JobType.UNEDITABLE_FIELDS: extra_fields[key] = value try: from recipe.configuration.definition.exceptions import InvalidDefinition except: logger.exception( 'Failed to import higher level recipe application.') pass try: with transaction.atomic(): # Attempt to create the trigger rule trigger_rule = None if rule_handler and 'configuration' in trigger_rule_dict: trigger_rule = rule_handler.create_trigger_rule( trigger_rule_dict['configuration'], job_type.name, is_active) # Update the active state separately if that is only given trigger field if not trigger_rule and job_type.trigger_rule and 'is_active' in trigger_rule_dict: job_type.trigger_rule.is_active = is_active job_type.trigger_rule.save() # Edit the job type JobType.objects.edit_job_type(job_type_id, interface, trigger_rule, remove_trigger_rule, error_mapping, **extra_fields) except (InvalidJobField, InvalidTriggerType, InvalidTriggerRule, InvalidConnection, InvalidDefinition, ValueError) as ex: logger.exception('Unable to update job type: %i', job_type_id) raise BadParameter(unicode(ex)) # Fetch the full job type with details try: job_type = JobType.objects.get_details(job_type.id) except JobType.DoesNotExist: raise Http404 serializer = self.get_serializer(job_type) return Response(serializer.data)
def patch(self, request, recipe_type_id): """Edits an existing recipe type and returns the updated details :param request: the HTTP PATCH request :type request: :class:`rest_framework.request.Request` :param recipe_type_id: The ID for the recipe type. :type recipe_type_id: int encoded as a str :rtype: :class:`rest_framework.response.Response` :returns: the HTTP response to send back to the user """ title = rest_util.parse_string(request, 'title', required=False) description = rest_util.parse_string(request, 'description', required=False) definition_dict = rest_util.parse_dict(request, 'definition', required=False) # Check for optional trigger rule parameters trigger_rule_dict = rest_util.parse_dict(request, 'trigger_rule', required=False) if (('type' in trigger_rule_dict and 'configuration' not in trigger_rule_dict) or ('type' not in trigger_rule_dict and 'configuration' in trigger_rule_dict)): raise BadParameter( 'Trigger type and configuration are required together.') is_active = trigger_rule_dict[ 'is_active'] if 'is_active' in trigger_rule_dict else True remove_trigger_rule = rest_util.has_params( request, 'trigger_rule') and not trigger_rule_dict # Fetch the current recipe type model try: recipe_type = RecipeType.objects.select_related( 'trigger_rule').get(pk=recipe_type_id) except RecipeType.DoesNotExist: raise Http404 # Attempt to look up the trigger handler for the type rule_handler = None if trigger_rule_dict and 'type' in trigger_rule_dict: try: rule_handler = trigger_handler.get_trigger_rule_handler( trigger_rule_dict['type']) except InvalidTriggerType as ex: logger.exception('Invalid trigger type for recipe type: %i', recipe_type_id) raise BadParameter(unicode(ex)) try: with transaction.atomic(): # Validate the recipe definition recipe_def = None if definition_dict: recipe_def = RecipeDefinitionSunset.create(definition_dict) # Attempt to create the trigger rule trigger_rule = None if rule_handler and 'configuration' in trigger_rule_dict: trigger_rule = rule_handler.create_trigger_rule( trigger_rule_dict['configuration'], recipe_type.name, is_active) # Update the active state separately if that is only given trigger field if not trigger_rule and recipe_type.trigger_rule and 'is_active' in trigger_rule_dict: recipe_type.trigger_rule.is_active = is_active recipe_type.trigger_rule.save() # Edit the recipe type RecipeType.objects.edit_recipe_type(recipe_type_id, title, description, recipe_def, trigger_rule, remove_trigger_rule) except (InvalidDefinition, InvalidTriggerType, InvalidTriggerRule, InvalidRecipeConnection) as ex: logger.exception('Unable to update recipe type: %i', recipe_type_id) raise BadParameter(unicode(ex)) # Fetch the full recipe type with details try: recipe_type = RecipeType.objects.get_details(recipe_type_id) except RecipeType.DoesNotExist: raise Http404 serializer = self.get_serializer(recipe_type) return Response(serializer.data)
def patch(self, request, recipe_type_id): """Edits an existing recipe type and returns the updated details :param request: the HTTP PATCH request :type request: :class:`rest_framework.request.Request` :param recipe_type_id: The ID for the recipe type. :type recipe_type_id: int encoded as a str :rtype: :class:`rest_framework.response.Response` :returns: the HTTP response to send back to the user """ title = rest_util.parse_string(request, 'title', required=False) description = rest_util.parse_string(request, 'description', required=False) definition_dict = rest_util.parse_dict(request, 'definition', required=False) # Check for optional trigger rule parameters trigger_rule_dict = rest_util.parse_dict(request, 'trigger_rule', required=False) if (('type' in trigger_rule_dict and 'configuration' not in trigger_rule_dict) or ('type' not in trigger_rule_dict and 'configuration' in trigger_rule_dict)): raise BadParameter('Trigger type and configuration are required together.') is_active = trigger_rule_dict['is_active'] if 'is_active' in trigger_rule_dict else True remove_trigger_rule = rest_util.has_params(request, 'trigger_rule') and not trigger_rule_dict # Fetch the current recipe type model try: recipe_type = RecipeType.objects.select_related('trigger_rule').get(pk=recipe_type_id) except RecipeType.DoesNotExist: raise Http404 # Attempt to look up the trigger handler for the type rule_handler = None if trigger_rule_dict and 'type' in trigger_rule_dict: try: rule_handler = trigger_handler.get_trigger_rule_handler(trigger_rule_dict['type']) except InvalidTriggerType as ex: logger.exception('Invalid trigger type for recipe type: %i', recipe_type_id) raise BadParameter(unicode(ex)) try: with transaction.atomic(): # Validate the recipe definition recipe_def = None if definition_dict: recipe_def = RecipeDefinition(definition_dict) # Attempt to create the trigger rule trigger_rule = None if rule_handler and 'configuration' in trigger_rule_dict: trigger_rule = rule_handler.create_trigger_rule(trigger_rule_dict['configuration'], recipe_type.name, is_active) # Update the active state separately if that is only given trigger field if not trigger_rule and recipe_type.trigger_rule and 'is_active' in trigger_rule_dict: recipe_type.trigger_rule.is_active = is_active recipe_type.trigger_rule.save() # Edit the recipe type RecipeType.objects.edit_recipe_type(recipe_type_id, title, description, recipe_def, trigger_rule, remove_trigger_rule) except (InvalidDefinition, InvalidTriggerType, InvalidTriggerRule, InvalidRecipeConnection) as ex: logger.exception('Unable to update recipe type: %i', recipe_type_id) raise BadParameter(unicode(ex)) # Fetch the full recipe type with details try: recipe_type = RecipeType.objects.get_details(recipe_type_id) except RecipeType.DoesNotExist: raise Http404 serializer = self.get_serializer(recipe_type) return Response(serializer.data)
def _import_job_type(job_type_dict, job_type=None): '''Attempts to apply the given job types configuration to the system. Note that proper model locking must be performed before calling this method. :param job_type_dict: A dictionary of job type configuration changes to import. :type job_type_dict: dict :param job_type: The existing job type model to update if applicable. :type job_type: :class:`job.models.JobType` :returns: A list of warnings discovered during import. :rtype: list[:class:`port.schema.ValidationWarning`] :raises :class:`port.schema.InvalidConfiguration`: If any part of the configuration violates the specification. ''' warnings = [] # Parse the JSON content into validated model fields job_type_serializer = serializers.ConfigurationJobTypeSerializer(job_type, data=job_type_dict) if not job_type_serializer.is_valid(): raise InvalidConfiguration('Invalid job type schema: %s -> %s' % (job_type_dict['name'], job_type_serializer.errors)) result = job_type_serializer.validated_data # Importing system-level job types is not allowed if result.get('category') == 'system' or (job_type and job_type.category == 'system'): raise InvalidConfiguration('System job types cannot be imported: %s' % result.get('name')) # Validate the job interface try: interface_dict = None if 'interface' in result: interface_dict = result.get('interface') elif job_type: interface_dict = job_type.interface interface = JobInterface(interface_dict) except InvalidInterfaceDefinition as ex: raise InvalidConfiguration('Job type interface invalid: %s -> %s' % (result.get('name'), unicode(ex))) # Validate the error mapping try: error_mapping_dict = None if 'error_mapping' in result: error_mapping_dict = result.get('error_mapping') elif job_type: error_mapping_dict = job_type.error_mapping error_mapping = ErrorInterface(error_mapping_dict) warnings.extend(error_mapping.validate()) except InvalidInterfaceDefinition as ex: raise InvalidConfiguration('Job type error mapping invalid: %s -> %s' % (result.get('name'), unicode(ex))) # Validate the trigger rule trigger_rule = None if 'trigger_rule' in result and result.get('trigger_rule'): trigger_rule = TriggerRule(**result.get('trigger_rule')) if trigger_rule: trigger_config = trigger_rule.get_configuration() if not isinstance(trigger_config, JobTriggerRuleConfiguration): logger.exception('Job type trigger rule type invalid') raise InvalidConfiguration('Job type trigger type invalid: %s -> %s' % (result.get('name'), trigger_rule.type)) try: warnings.extend(trigger_config.validate_trigger_for_job(interface)) # Create a new rule when the trigger content was provided if job_type_dict.get('trigger_rule'): rule_handler = trigger_handler.get_trigger_rule_handler(trigger_rule.type) trigger_rule = rule_handler.create_trigger_rule(trigger_rule.configuration, trigger_rule.name, trigger_rule.is_active) except (InvalidTriggerType, InvalidTriggerRule, InvalidConnection) as ex: logger.exception('Job type trigger rule invalid') raise InvalidConfiguration('Job type trigger rule invalid: %s -> %s' % (result.get('name'), unicode(ex))) remove_trigger_rule = 'trigger_rule' in job_type_dict and not job_type_dict['trigger_rule'] # Extract the fields that should be updated as keyword arguments extra_fields = {} base_fields = {'name', 'version', 'interface', 'trigger_rule', 'error_mapping'} for key in job_type_dict: if key not in base_fields: if key in JobType.UNEDITABLE_FIELDS: warnings.append(ValidationWarning( 'read-only', 'Job type includes read-only field: %s -> %s' % (result.get('name'), key) )) else: extra_fields[key] = job_type_dict[key] # Edit or create the associated job type model if job_type: try: JobType.objects.edit_job_type(job_type.id, interface, trigger_rule, remove_trigger_rule, error_mapping, **extra_fields) except (InvalidJobField, InvalidTriggerType, InvalidTriggerRule, InvalidConnection, InvalidDefinition) as ex: logger.exception('Job type edit failed') raise InvalidConfiguration('Unable to edit existing job type: %s -> %s' % (result.get('name'), unicode(ex))) else: try: JobType.objects.create_job_type(result.get('name'), result.get('version'), interface, trigger_rule, error_mapping, **extra_fields) except (InvalidJobField, InvalidTriggerType, InvalidTriggerRule, InvalidConnection, InvalidDefinition) as ex: logger.exception('Job type create failed') raise InvalidConfiguration('Unable to create new job type: %s -> %s' % (result.get('name'), unicode(ex))) return warnings
def create(self, request): """Creates a new job type and returns a link to the detail URL :param request: the HTTP POST request :type request: :class:`rest_framework.request.Request` :rtype: :class:`rest_framework.response.Response` :returns: the HTTP response to send back to the user """ name = rest_util.parse_string(request, 'name') version = rest_util.parse_string(request, 'version') # Validate the job interface interface_dict = rest_util.parse_dict(request, 'interface') interface = None try: if interface_dict: interface = JobInterface(interface_dict) except InvalidInterfaceDefinition as ex: raise BadParameter('Job type interface invalid: %s' % unicode(ex)) # Validate the error mapping error_dict = rest_util.parse_dict(request, 'error_mapping', required=False) error_mapping = None try: if error_dict: error_mapping = ErrorInterface(error_dict) error_mapping.validate() except InvalidInterfaceDefinition as ex: raise BadParameter('Job type error mapping invalid: %s' % unicode(ex)) # Check for optional trigger rule parameters trigger_rule_dict = rest_util.parse_dict(request, 'trigger_rule', required=False) if (('type' in trigger_rule_dict and 'configuration' not in trigger_rule_dict) or ('type' not in trigger_rule_dict and 'configuration' in trigger_rule_dict)): raise BadParameter('Trigger type and configuration are required together.') is_active = trigger_rule_dict['is_active'] if 'is_active' in trigger_rule_dict else True # Attempt to look up the trigger handler for the type rule_handler = None if trigger_rule_dict and 'type' in trigger_rule_dict: try: rule_handler = trigger_handler.get_trigger_rule_handler(trigger_rule_dict['type']) except InvalidTriggerType as ex: logger.exception('Invalid trigger type for new job type: %s', name) raise BadParameter(unicode(ex)) # Extract the fields that should be updated as keyword arguments extra_fields = {} base_fields = {'name', 'version', 'interface', 'trigger_rule', 'error_mapping'} for key, value in request.data.iteritems(): if key not in base_fields and key not in JobType.UNEDITABLE_FIELDS: extra_fields[key] = value try: with transaction.atomic(): # Attempt to create the trigger rule trigger_rule = None if rule_handler and 'configuration' in trigger_rule_dict: trigger_rule = rule_handler.create_trigger_rule(trigger_rule_dict['configuration'], name, is_active) # Create the job type job_type = JobType.objects.create_job_type(name, version, interface, trigger_rule, error_mapping, **extra_fields) except (InvalidJobField, InvalidTriggerType, InvalidTriggerRule, InvalidConnection, ValueError) as ex: logger.exception('Unable to create new job type: %s', name) raise BadParameter(unicode(ex)) # Fetch the full job type with details try: job_type = JobType.objects.get_details(job_type.id) except JobType.DoesNotExist: raise Http404 url = urlresolvers.reverse('job_type_details_view', args=[job_type.id]) serializer = JobTypeDetailsSerializer(job_type) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=dict(location=url))
def post(self, request): """Validates a new job type and returns any warnings discovered :param request: the HTTP POST request :type request: :class:`rest_framework.request.Request` :rtype: :class:`rest_framework.response.Response` :returns: the HTTP response to send back to the user """ name = rest_util.parse_string(request, 'name') version = rest_util.parse_string(request, 'version') # Validate the job interface interface_dict = rest_util.parse_dict(request, 'interface') interface = None try: if interface_dict: interface = JobInterface(interface_dict) except InvalidInterfaceDefinition as ex: raise BadParameter('Job type interface invalid: %s' % unicode(ex)) # Validate the error mapping error_dict = rest_util.parse_dict(request, 'error_mapping', required=False) error_mapping = None try: if error_dict: error_mapping = ErrorInterface(error_dict) except InvalidInterfaceDefinition as ex: raise BadParameter('Job type error mapping invalid: %s' % unicode(ex)) # Check for optional trigger rule parameters trigger_rule_dict = rest_util.parse_dict(request, 'trigger_rule', required=False) if (('type' in trigger_rule_dict and 'configuration' not in trigger_rule_dict) or ('type' not in trigger_rule_dict and 'configuration' in trigger_rule_dict)): raise BadParameter('Trigger type and configuration are required together.') # Attempt to look up the trigger handler for the type rule_handler = None if trigger_rule_dict and 'type' in trigger_rule_dict: try: rule_handler = trigger_handler.get_trigger_rule_handler(trigger_rule_dict['type']) except InvalidTriggerType as ex: logger.exception('Invalid trigger type for job validation: %s', name) raise BadParameter(unicode(ex)) # Attempt to look up the trigger rule configuration trigger_config = None if rule_handler and 'configuration' in trigger_rule_dict: try: trigger_config = rule_handler.create_configuration(trigger_rule_dict['configuration']) except InvalidTriggerRule as ex: logger.exception('Invalid trigger rule configuration for job validation: %s', name) raise BadParameter(unicode(ex)) try: from recipe.configuration.definition.exceptions import InvalidDefinition except: logger.exception('Failed to import higher level recipe application.') pass # Validate the job interface try: warnings = JobType.objects.validate_job_type(name, version, interface, error_mapping, trigger_config) except (InvalidDefinition, InvalidTriggerType, InvalidTriggerRule) as ex: logger.exception('Unable to validate new job type: %s', name) raise BadParameter(unicode(ex)) results = [{'id': w.key, 'details': w.details} for w in warnings] return Response({'warnings': results})
def patch(self, request, job_type_id): """Edits an existing job type and returns the updated details :param request: the HTTP GET request :type request: :class:`rest_framework.request.Request` :param job_type_id: The ID for the job type. :type job_type_id: int encoded as a str :rtype: :class:`rest_framework.response.Response` :returns: the HTTP response to send back to the user """ # Validate the job interface interface_dict = rest_util.parse_dict(request, 'interface', required=False) interface = None try: if interface_dict: interface = JobInterface(interface_dict) except InvalidInterfaceDefinition as ex: raise BadParameter('Job type interface invalid: %s' % unicode(ex)) # Validate the error mapping error_dict = rest_util.parse_dict(request, 'error_mapping', required=False) error_mapping = None try: if error_dict: error_mapping = ErrorInterface(error_dict) error_mapping.validate() except InvalidInterfaceDefinition as ex: raise BadParameter('Job type error mapping invalid: %s' % unicode(ex)) # Check for optional trigger rule parameters trigger_rule_dict = rest_util.parse_dict(request, 'trigger_rule', required=False) if (('type' in trigger_rule_dict and 'configuration' not in trigger_rule_dict) or ('type' not in trigger_rule_dict and 'configuration' in trigger_rule_dict)): raise BadParameter('Trigger type and configuration are required together.') is_active = trigger_rule_dict['is_active'] if 'is_active' in trigger_rule_dict else True remove_trigger_rule = rest_util.has_params(request, 'trigger_rule') and not trigger_rule_dict # Fetch the current job type model try: job_type = JobType.objects.select_related('trigger_rule').get(pk=job_type_id) except JobType.DoesNotExist: raise Http404 # Attempt to look up the trigger handler for the type rule_handler = None if trigger_rule_dict and 'type' in trigger_rule_dict: try: rule_handler = trigger_handler.get_trigger_rule_handler(trigger_rule_dict['type']) except InvalidTriggerType as ex: logger.exception('Invalid trigger type for job type: %i', job_type_id) raise BadParameter(unicode(ex)) # Extract the fields that should be updated as keyword arguments extra_fields = {} base_fields = {'name', 'version', 'interface', 'trigger_rule', 'error_mapping'} for key, value in request.data.iteritems(): if key not in base_fields and key not in JobType.UNEDITABLE_FIELDS: extra_fields[key] = value try: from recipe.configuration.definition.exceptions import InvalidDefinition except: logger.exception('Failed to import higher level recipe application.') pass try: with transaction.atomic(): # Attempt to create the trigger rule trigger_rule = None if rule_handler and 'configuration' in trigger_rule_dict: trigger_rule = rule_handler.create_trigger_rule(trigger_rule_dict['configuration'], job_type.name, is_active) # Update the active state separately if that is only given trigger field if not trigger_rule and job_type.trigger_rule and 'is_active' in trigger_rule_dict: job_type.trigger_rule.is_active = is_active job_type.trigger_rule.save() # Edit the job type JobType.objects.edit_job_type(job_type_id, interface, trigger_rule, remove_trigger_rule, error_mapping, **extra_fields) except (InvalidJobField, InvalidTriggerType, InvalidTriggerRule, InvalidConnection, InvalidDefinition, ValueError) as ex: logger.exception('Unable to update job type: %i', job_type_id) raise BadParameter(unicode(ex)) # Fetch the full job type with details try: job_type = JobType.objects.get_details(job_type.id) except JobType.DoesNotExist: raise Http404 serializer = self.get_serializer(job_type) return Response(serializer.data)
def _import_job_type(job_type_dict, job_type=None, validating=False): """Attempts to apply the given job types configuration to the system. Note that proper model locking must be performed before calling this method. :param job_type_dict: A dictionary of job type configuration changes to import. :type job_type_dict: dict :param job_type: The existing job type model to update if applicable. :type job_type: :class:`job.models.JobType` :param validating: Flag to determine if running a validate or commit transaction. :type validating: bool :returns: A list of warnings discovered during import. :rtype: list[:class:`port.schema.ValidationWarning`] :raises :class:`port.schema.InvalidConfiguration`: If any part of the configuration violates the specification. """ warnings = [] # Parse the JSON content into validated model fields job_type_serializer = serializers.ConfigurationJobTypeSerializer(job_type, data=job_type_dict) if not job_type_serializer.is_valid(): raise InvalidConfiguration('Invalid job type schema: %s -> %s' % (job_type_dict['name'], job_type_serializer.errors)) result = job_type_serializer.validated_data # Importing system-level job types is not allowed if result.get('category') == 'system' or (job_type and job_type.category == 'system'): raise InvalidConfiguration('System job types cannot be imported: %s' % result.get('name')) # Validate the job interface try: interface_dict = None if 'interface' in result: interface_dict = result.get('interface') elif job_type: interface_dict = job_type.manifest interface = JobInterface(interface_dict) except InvalidInterfaceDefinition as ex: raise InvalidConfiguration('Job type interface invalid: %s -> %s' % (result.get('name'), unicode(ex))) # Validate the job configuration try: configuration_dict = None secrets = None if 'configuration' in result: configuration_dict = result.get('configuration') elif job_type: configuration_dict = job_type.configuration if interface: configuration = JobConfigurationV2(configuration_dict) if not validating: secrets = configuration.get_secret_settings(interface.get_dict()) warnings.extend(configuration.validate(interface.get_dict())) except InvalidJobConfiguration as ex: raise InvalidConfiguration('Job type configuration invalid: %s -> %s' % (result.get('name'), unicode(ex))) # Validate the error mapping try: error_mapping_dict = None if 'error_mapping' in result: error_mapping_dict = result.get('error_mapping') elif job_type: error_mapping_dict = job_type.error_mapping error_mapping = create_legacy_error_mapping(error_mapping_dict) error_mapping.validate_legacy() except InvalidInterfaceDefinition as ex: raise InvalidConfiguration('Job type error mapping invalid: %s -> %s' % (result.get('name'), unicode(ex))) # Validate the trigger rule trigger_rule = None if 'trigger_rule' in result and result.get('trigger_rule'): trigger_rule = TriggerRule(**result.get('trigger_rule')) if trigger_rule: trigger_config = trigger_rule.get_configuration() if not isinstance(trigger_config, JobTriggerRuleConfiguration): logger.exception('Job type trigger rule type invalid') raise InvalidConfiguration('Job type trigger type invalid: %s -> %s' % (result.get('name'), trigger_rule.type)) try: warnings.extend(trigger_config.validate_trigger_for_job(interface)) # Create a new rule when the trigger content was provided if job_type_dict.get('trigger_rule'): rule_handler = trigger_handler.get_trigger_rule_handler(trigger_rule.type) trigger_rule = rule_handler.create_trigger_rule(trigger_rule.configuration, trigger_rule.name, trigger_rule.is_active) except (InvalidTriggerType, InvalidTriggerRule, InvalidConnection) as ex: logger.exception('Job type trigger rule invalid') raise InvalidConfiguration('Job type trigger rule invalid: %s -> %s' % (result.get('name'), unicode(ex))) remove_trigger_rule = 'trigger_rule' in job_type_dict and not job_type_dict['trigger_rule'] # Extract the fields that should be updated as keyword arguments extra_fields = {} base_fields = {'name', 'version', 'interface', 'trigger_rule', 'error_mapping', 'configuration'} for key in job_type_dict: if key not in base_fields: if key in JobType.UNEDITABLE_FIELDS: warnings.append(ValidationWarning( 'read-only', 'Job type includes read-only field: %s -> %s' % (result.get('name'), key) )) else: extra_fields[key] = job_type_dict[key] # Change mem_required to mem_const_required, TODO: remove once mem_required field is removed from REST API if 'mem_required' in extra_fields: extra_fields['mem_const_required'] = extra_fields['mem_required'] del extra_fields['mem_required'] # Edit or create the associated job type model if job_type: try: JobType.objects.edit_job_type_v5(job_type.id, interface=interface, trigger_rule=trigger_rule, remove_trigger_rule=remove_trigger_rule, error_mapping=error_mapping, configuration=configuration, secrets=secrets, **extra_fields) except (InvalidJobField, InvalidTriggerType, InvalidTriggerRule, InvalidConnection, InvalidDefinition, InvalidSecretsConfiguration) as ex: logger.exception('Job type edit failed') raise InvalidConfiguration( 'Unable to edit existing job type: %s -> %s' % (result.get('name'), unicode(ex))) else: try: JobType.objects.create_job_type_v5(name=result.get('name'), version=result.get('version'), interface=interface, trigger_rule=trigger_rule, error_mapping=error_mapping, configuration=configuration, secrets=secrets, **extra_fields) except (InvalidJobField, InvalidTriggerType, InvalidTriggerRule, InvalidConnection, InvalidDefinition, InvalidSecretsConfiguration) as ex: logger.exception('Job type create failed') raise InvalidConfiguration('Unable to create new job type: %s -> %s' % (result.get('name'), unicode(ex))) return warnings
def _import_recipe_type(recipe_type_dict, recipe_type=None): """Attempts to apply the given recipe types configuration to the system. Note that proper model locking must be performed before calling this method. :param recipe_type_dict: A dictionary of recipe type configuration changes to import. :type recipe_type_dict: dict :param recipe_type: The existing recipe type model to update if applicable. :type recipe_type: :class:`recipe.models.RecipeType` :returns: A list of warnings discovered during import. :rtype: list[:class:`port.schema.ValidationWarning`] :raises :class:`port.schema.InvalidConfiguration`: If any part of the configuration violates the specification. """ warnings = [] # Parse the JSON content into validated model fields recipe_type_serializer = serializers.ConfigurationRecipeTypeSerializer(recipe_type, data=recipe_type_dict) if not recipe_type_serializer.is_valid(): raise InvalidConfiguration('Invalid recipe type schema: %s -> %s' % (recipe_type_dict['name'], recipe_type_serializer.errors)) result = recipe_type_serializer.validated_data # Validate the recipe definition try: definition_dict = None if 'definition' in result: definition_dict = result.get('definition') elif recipe_type: definition_dict = recipe_type.definition definition = RecipeDefinitionSunset.create(definition_dict) warnings.extend(definition.validate_job_interfaces()) except (InvalidDefinition, InvalidRecipeConnection) as ex: raise InvalidConfiguration('Recipe type definition invalid: %s -> %s' % (result.get('name'), unicode(ex))) # Validate the trigger rule trigger_rule = None if 'trigger_rule' in result and result.get('trigger_rule'): trigger_rule = TriggerRule(**result.get('trigger_rule')) if trigger_rule: trigger_config = trigger_rule.get_configuration() if not isinstance(trigger_config, RecipeTriggerRuleConfiguration): logger.exception('Recipe type trigger rule type invalid') raise InvalidConfiguration('Recipe type trigger type invalid: %s -> %s' % (result.get('name'), trigger_rule.type)) try: warnings.extend(trigger_config.validate_trigger_for_recipe(definition)) # Create a new rule when the trigger content was provided if recipe_type_dict.get('trigger_rule'): rule_handler = trigger_handler.get_trigger_rule_handler(trigger_rule.type) trigger_rule = rule_handler.create_trigger_rule(trigger_rule.configuration, trigger_rule.name, trigger_rule.is_active) except (InvalidDefinition, InvalidTriggerType, InvalidTriggerRule, InvalidRecipeConnection) as ex: logger.exception('Recipe type trigger rule invalid') raise InvalidConfiguration('Recipe type trigger rule invalid: %s -> %s' % (result.get('name'), unicode(ex))) remove_trigger_rule = 'trigger_rule' in recipe_type_dict and not recipe_type_dict['trigger_rule'] # Edit or create the associated recipe type model if recipe_type: try: RecipeType.objects.edit_recipe_type(recipe_type.id, result.get('title'), result.get('description'), definition, trigger_rule, remove_trigger_rule) except (InvalidDefinition, InvalidTriggerType, InvalidTriggerRule, InvalidRecipeConnection) as ex: logger.exception('Recipe type edit failed') raise InvalidConfiguration('Unable to edit recipe type: %s -> %s' % (result.get('name'), unicode(ex))) else: try: RecipeType.objects.create_recipe_type(result.get('name'), result.get('version'), result.get('title'), result.get('description'), definition, trigger_rule) except (InvalidDefinition, InvalidTriggerType, InvalidTriggerRule, InvalidRecipeConnection) as ex: logger.exception('Recipe type create failed') raise InvalidConfiguration('Unable to create new recipe type: %s -> %s' % (result.get('name'), unicode(ex))) return warnings
def post(self, request): """Validates a new job type and returns any warnings discovered :param request: the HTTP POST request :type request: :class:`rest_framework.request.Request` :rtype: :class:`rest_framework.response.Response` :returns: the HTTP response to send back to the user """ name = rest_util.parse_string(request, 'name') version = rest_util.parse_string(request, 'version') # Validate the job interface interface_dict = rest_util.parse_dict(request, 'interface') interface = None try: if interface_dict: interface = JobInterface(interface_dict) except InvalidInterfaceDefinition as ex: raise BadParameter('Job type interface invalid: %s' % unicode(ex)) # Validate the error mapping error_dict = rest_util.parse_dict(request, 'error_mapping', required=False) error_mapping = None try: if error_dict: error_mapping = ErrorInterface(error_dict) except InvalidInterfaceDefinition as ex: raise BadParameter('Job type error mapping invalid: %s' % unicode(ex)) # Check for optional trigger rule parameters trigger_rule_dict = rest_util.parse_dict(request, 'trigger_rule', required=False) if (('type' in trigger_rule_dict and 'configuration' not in trigger_rule_dict) or ('type' not in trigger_rule_dict and 'configuration' in trigger_rule_dict)): raise BadParameter( 'Trigger type and configuration are required together.') # Attempt to look up the trigger handler for the type rule_handler = None if trigger_rule_dict and 'type' in trigger_rule_dict: try: rule_handler = trigger_handler.get_trigger_rule_handler( trigger_rule_dict['type']) except InvalidTriggerType as ex: logger.exception('Invalid trigger type for job validation: %s', name) raise BadParameter(unicode(ex)) # Attempt to look up the trigger rule configuration trigger_config = None if rule_handler and 'configuration' in trigger_rule_dict: try: trigger_config = rule_handler.create_configuration( trigger_rule_dict['configuration']) except InvalidTriggerRule as ex: logger.exception( 'Invalid trigger rule configuration for job validation: %s', name) raise BadParameter(unicode(ex)) try: from recipe.configuration.definition.exceptions import InvalidDefinition except: logger.exception( 'Failed to import higher level recipe application.') pass # Validate the job interface try: warnings = JobType.objects.validate_job_type( name, version, interface, error_mapping, trigger_config) except (InvalidDefinition, InvalidTriggerType, InvalidTriggerRule) as ex: logger.exception('Unable to validate new job type: %s', name) raise BadParameter(unicode(ex)) results = [{'id': w.key, 'details': w.details} for w in warnings] return Response({'warnings': results})
def create(self, request): """Creates a new job type and returns a link to the detail URL :param request: the HTTP POST request :type request: :class:`rest_framework.request.Request` :rtype: :class:`rest_framework.response.Response` :returns: the HTTP response to send back to the user """ name = rest_util.parse_string(request, 'name') version = rest_util.parse_string(request, 'version') # Validate the job interface interface_dict = rest_util.parse_dict(request, 'interface') interface = None try: if interface_dict: interface = JobInterface(interface_dict) except InvalidInterfaceDefinition as ex: raise BadParameter('Job type interface invalid: %s' % unicode(ex)) # Validate the error mapping error_dict = rest_util.parse_dict(request, 'error_mapping', required=False) error_mapping = None try: if error_dict: error_mapping = ErrorInterface(error_dict) error_mapping.validate() except InvalidInterfaceDefinition as ex: raise BadParameter('Job type error mapping invalid: %s' % unicode(ex)) # Check for optional trigger rule parameters trigger_rule_dict = rest_util.parse_dict(request, 'trigger_rule', required=False) if (('type' in trigger_rule_dict and 'configuration' not in trigger_rule_dict) or ('type' not in trigger_rule_dict and 'configuration' in trigger_rule_dict)): raise BadParameter( 'Trigger type and configuration are required together.') is_active = trigger_rule_dict[ 'is_active'] if 'is_active' in trigger_rule_dict else True # Attempt to look up the trigger handler for the type rule_handler = None if trigger_rule_dict and 'type' in trigger_rule_dict: try: rule_handler = trigger_handler.get_trigger_rule_handler( trigger_rule_dict['type']) except InvalidTriggerType as ex: logger.exception('Invalid trigger type for new job type: %s', name) raise BadParameter(unicode(ex)) # Extract the fields that should be updated as keyword arguments extra_fields = {} base_fields = { 'name', 'version', 'interface', 'trigger_rule', 'error_mapping' } for key, value in request.data.iteritems(): if key not in base_fields and key not in JobType.UNEDITABLE_FIELDS: extra_fields[key] = value try: with transaction.atomic(): # Attempt to create the trigger rule trigger_rule = None if rule_handler and 'configuration' in trigger_rule_dict: trigger_rule = rule_handler.create_trigger_rule( trigger_rule_dict['configuration'], name, is_active) # Create the job type job_type = JobType.objects.create_job_type( name, version, interface, trigger_rule, error_mapping, **extra_fields) except (InvalidJobField, InvalidTriggerType, InvalidTriggerRule, InvalidConnection, ValueError) as ex: logger.exception('Unable to create new job type: %s', name) raise BadParameter(unicode(ex)) # Fetch the full job type with details try: job_type = JobType.objects.get_details(job_type.id) except JobType.DoesNotExist: raise Http404 url = urlresolvers.reverse('job_type_details_view', args=[job_type.id]) serializer = JobTypeDetailsSerializer(job_type) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=dict(location=url))
def _import_recipe_type(recipe_type_dict, recipe_type=None): '''Attempts to apply the given recipe types configuration to the system. Note that proper model locking must be performed before calling this method. :param recipe_type_dict: A dictionary of recipe type configuration changes to import. :type recipe_type_dict: dict :param recipe_type: The existing recipe type model to update if applicable. :type recipe_type: :class:`recipe.models.RecipeType` :returns: A list of warnings discovered during import. :rtype: list[:class:`port.schema.ValidationWarning`] :raises :class:`port.schema.InvalidConfiguration`: If any part of the configuration violates the specification. ''' warnings = [] # Parse the JSON content into validated model fields recipe_type_serializer = serializers.ConfigurationRecipeTypeSerializer(recipe_type, data=recipe_type_dict) if not recipe_type_serializer.is_valid(): raise InvalidConfiguration('Invalid recipe type schema: %s -> %s' % (recipe_type_dict['name'], recipe_type_serializer.errors)) result = recipe_type_serializer.validated_data # Validate the recipe definition try: definition_dict = None if 'definition' in result: definition_dict = result.get('definition') elif recipe_type: definition_dict = recipe_type.definition definition = RecipeDefinition(definition_dict) warnings.extend(definition.validate_job_interfaces()) except (InvalidDefinition, InvalidRecipeConnection) as ex: raise InvalidConfiguration('Recipe type definition invalid: %s -> %s' % (result.get('name'), unicode(ex))) # Validate the trigger rule trigger_rule = None if 'trigger_rule' in result and result.get('trigger_rule'): trigger_rule = TriggerRule(**result.get('trigger_rule')) if trigger_rule: trigger_config = trigger_rule.get_configuration() if not isinstance(trigger_config, RecipeTriggerRuleConfiguration): logger.exception('Recipe type trigger rule type invalid') raise InvalidConfiguration('Recipe type trigger type invalid: %s -> %s' % (result.get('name'), trigger_rule.type)) try: warnings.extend(trigger_config.validate_trigger_for_recipe(definition)) # Create a new rule when the trigger content was provided if recipe_type_dict.get('trigger_rule'): rule_handler = trigger_handler.get_trigger_rule_handler(trigger_rule.type) trigger_rule = rule_handler.create_trigger_rule(trigger_rule.configuration, trigger_rule.name, trigger_rule.is_active) except (InvalidDefinition, InvalidTriggerType, InvalidTriggerRule, InvalidRecipeConnection) as ex: logger.exception('Recipe type trigger rule invalid') raise InvalidConfiguration('Recipe type trigger rule invalid: %s -> %s' % (result.get('name'), unicode(ex))) remove_trigger_rule = 'trigger_rule' in recipe_type_dict and not recipe_type_dict['trigger_rule'] # Edit or create the associated recipe type model if recipe_type: try: RecipeType.objects.edit_recipe_type(recipe_type.id, result.get('title'), result.get('description'), definition, trigger_rule, remove_trigger_rule) except (InvalidDefinition, InvalidTriggerType, InvalidTriggerRule, InvalidRecipeConnection) as ex: logger.exception('Recipe type edit failed') raise InvalidConfiguration('Unable to edit recipe type: %s -> %s' % (result.get('name'), unicode(ex))) else: try: RecipeType.objects.create_recipe_type(result.get('name'), result.get('version'), result.get('title'), result.get('description'), definition, trigger_rule) except (InvalidDefinition, InvalidTriggerType, InvalidTriggerRule, InvalidRecipeConnection) as ex: logger.exception('Recipe type create failed') raise InvalidConfiguration('Unable to create new recipe type: %s -> %s' % (result.get('name'), unicode(ex))) return warnings