Esempio n. 1
0
    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))
Esempio n. 2
0
    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))
Esempio n. 3
0
    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)
Esempio n. 4
0
    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)
Esempio n. 5
0
    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})
Esempio n. 6
0
    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})
Esempio n. 7
0
    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)
Esempio n. 8
0
    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)
Esempio n. 9
0
    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)
Esempio n. 10
0
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
Esempio n. 11
0
    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))
Esempio n. 12
0
    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})
Esempio n. 13
0
    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)
Esempio n. 14
0
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
Esempio n. 15
0
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
Esempio n. 16
0
    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})
Esempio n. 17
0
    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))
Esempio n. 18
0
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