def test_get_error_names_unique(self): '''Tests getting error names without duplicates.''' error_interface_dict = { 'version': '1.0', 'exit_codes': { '0': self.error_1.name, '2': self.error_2.name, '5': self.error_1.name, }, } error_interface = ErrorInterface(error_interface_dict) error_names = error_interface.get_error_names() self.assertSetEqual(error_names, {self.error_1.name, self.error_2.name})
def test_get_error_missing(self): """Tests that general algorithm error is returned when a non-registered name is found in the mapping""" error_interface_dict = { 'version': '1.0', 'exit_codes': { '1': self.error_1.name, '2': self.error_2.name, '3': self.error_3.name, }, } error_interface = ErrorInterface(error_interface_dict) error = error_interface.get_error(4) self.assertIsNotNone(error) self.assertEqual(error.name, 'algorithm-unknown')
def test_get_error_found(self): """ Tests that an error model is returned given an exit_code other than 0""" error_interface_dict = { 'version': '1.0', 'exit_codes': { '1': self.error_1.name, '2': self.error_2.name, '3': self.error_3.name, }, } error_interface = ErrorInterface(error_interface_dict) error = error_interface.get_error(1) self.assertIsNotNone(error) self.assertEqual(error.name, self.error_1.name)
def test_get_error_missing(self): ''' Tests that an unknown error is returned when a non-registered name is found in the mapping''' error_interface_dict = { 'version': '1.0', 'exit_codes': { '1': self.error_1.name, '2': self.error_2.name, '3': self.error_3.name, }, } error_interface = ErrorInterface(error_interface_dict) error = error_interface.get_error(4) self.assertIsNotNone(error) self.assertEqual(error.name, Error.objects.get_unknown_error().name)
def test_get_error_missing(self): '''Tests that general algorithm error is returned when a non-registered name is found in the mapping''' # Clear error cache so test works correctly CACHED_BUILTIN_ERRORS.clear() error_interface_dict = { 'version': '1.0', 'exit_codes': { '1': self.error_1.name, '2': self.error_2.name, '3': self.error_3.name, }, } error_interface = ErrorInterface(error_interface_dict) error = error_interface.get_error(4) self.assertIsNotNone(error) self.assertEqual(error.name, 'algorithm-unknown')
def test_get_error_missing_default(self): """Tests that custom error is returned when a non-registered name is found in the mapping""" # Clear error cache so test works correctly CACHED_BUILTIN_ERRORS.clear() error_interface_dict = { 'version': '1.0', 'exit_codes': { '1': self.error_1.name, '2': self.error_2.name, '3': self.error_3.name, }, } default_error = error_test_utils.create_error() error_interface = ErrorInterface(error_interface_dict) error = error_interface.get_error(4, default_error.name) self.assertIsNotNone(error) self.assertEqual(error.name, default_error.name)
def test_get_error_missing_default(self): """Tests that custom error is returned when a non-registered name is found in the mapping""" error_interface_dict = { 'version': '1.0', 'exit_codes': { '1': self.error_1.name, '2': self.error_2.name, '3': self.error_3.name, }, } default_error = error_test_utils.create_error() default_error.is_builtin = True default_error.save() # Reset error cache so tests work correctly reset_error_cache() error_interface = ErrorInterface(error_interface_dict) error = error_interface.get_error(4, default_error.name) self.assertIsNotNone(error) self.assertEqual(error.name, default_error.name)
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): """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