Beispiel #1
0
    def discover(self):
        self.history = OrderedDict()
        try:
            order = self.device.clean['order']
        except KeyError:
            raise Exception("Key 'order' is missing for device "
                            "'{d}'".format(d=self.device.name))

        # Insert the 'images' value into necessary clean sections
        if self.device.clean['images']:
            initialize_clean_sections(self.image_handler, order)
        # self.device.clean['change_boot_variable'] = {'images': []}

        all_data = {}
        all_schema = {}
        sections = []
        common_data = {}
        self.parameters['common_data'] = common_data
        for section in order:
            try:
                data = self.device.clean[section] or {}
            except KeyError:
                # Cannot find section - raise exception
                raise Exception("Cannot find '{section}' in the provided "
                                "sections even though it was provided in "
                                "the order list '{order}'".\
                                        format(section=section, order=order))

            # Load it up
            # If source isnt provided then check if it is inside the clean json
            if 'source' not in data:
                # Check if that one exists in the json
                task = _get_clean(section, clean_data, self.device)
            else:
                task = load_class(data, self.device)

            # Verify if schema exists for this section
            if hasattr(task, 'schema'):
                # if the stage has schema defined then build the bigger schema
                all_schema[task.__name__] = task.schema
                all_data[task.__name__] = data
                # unwrap to get original method, tmp fix need to handle in genie core infra
                task = unwrap(task)

            func = copy_func(task)
            func.uid = task.__name__
            func.parameters = ParameterDict()
            func.parameters['device'] = self.device
            func.parameters['common_data'] = common_data
            func.source = Source(self, objcls=func.__class__)

            for parameter, value in data.items():
                func.parameters[parameter] = value

            # Bind it and append to the section list
            new_section = func.__get__(self, func.__testcls__)
            self.history[new_section.uid] = new_section

            # Add processor, add parameters to it if any
            if self.device.clean.get('device_recovery'):
                processor = partial(recovery_processor,
                                    **self.device.clean.get('device_recovery'))
                processors.add(new_section,
                               pre=[block_section],
                               post=[processor],
                               exception=[])

            sections.append(new_section)

        recovery_data = self.device.clean.get('device_recovery')
        # if recovery info not provided, don't need to check schema
        if recovery_data:
            recovery_schema = recovery_processor.schema
            all_schema['device_recovery'] = recovery_schema
            all_data['device_recovery'] = recovery_data

        try:
            Schema(all_schema).validate(all_data)
        except SchemaMissingKeyError as e:
            # proto type
            raise ValueError(
                "Clean schema check failed. The following keys are missing from clean yaml file:\n\n{}"
                .format(self._format_missing_key_msg(
                    e.missing_list))) from None
        except SchemaTypeError as e:

            raise TypeError(
                "Clean schema check failed. Incorrect value type was provided for the "
                "following key:\n\n{}\n\nExpected type {} but got type {}".
                format(self._format_missing_key_msg([e.path]), str(e.type),
                       type(e.data))) from None
        except SchemaUnsupportedKeyError as e:
            raise ValueError(
                "Clean schema check failed. The following keys are not supported:\n\n{}"
                .format(self._format_missing_key_msg(
                    e.unsupported_keys))) from None

        return sections
Beispiel #2
0
    def discover(self):
        """ Discovers and loads all clean stages defined within the clean
        yaml file. During loading validate that each stage is a TestSection.
        Once loading is complete a schema validation is executed to ensure
        the data provided is valid.
        """
        clean_schema = {}
        clean_to_validate = {}

        # order is mandatory so verify schema
        clean_schema['order'] = list
        if self.device.clean.get('order'):
            clean_to_validate['order'] = self.device.clean['order']

        # device_recovery is optional so verify schema only if provided
        device_recovery_data = self.device.clean.get('device_recovery')
        if device_recovery_data:
            clean_schema['device_recovery'] = recovery_processor.schema
            clean_to_validate['device_recovery'] = device_recovery_data

            # Setup and save the device recovery processor for later
            # use in __iter__()
            self.device_recovery_processor = partial(
                recovery_processor, **self.device.clean['device_recovery'])

        clean_json = load_clean_json()
        # Verify schema and load each stage
        for stage in self.device.clean:
            if stage in NOT_A_STAGE:
                continue

            if self.image_handler:
                self.image_handler.update_section(stage)

            stage_data = self.device.clean[stage] or {}
            if 'source' not in stage_data:
                # Attempt to load from clean json
                stage_func = get_clean_function(stage, clean_json, self.device)
            else:
                # Attempt to load from the provided source
                stage_func = load_class(stage_data, self.device)

            if hasattr(stage_func, 'schema'):
                clean_schema[stage_func.__name__] = stage_func.schema

                # Add schema that is common to all stages
                clean_schema[stage_func.__name__].update(
                    {Optional('source'): dict})

                clean_to_validate[stage_func.__name__] = stage_data

            # Save for use later in __iter__()
            self.stages[stage] = {
                'func':
                stage_func,
                'change_order_if_pass':
                stage_data.pop('change_order_if_pass', None),
                'change_order_if_fail':
                stage_data.pop('change_order_if_fail', None),
                'stage_reuse_limit':
                stage_data.pop('stage_reuse_limit', None),
                'args':
                stage_data
            }

        try:
            Schema(clean_schema).validate(clean_to_validate)
        except Exception as e:
            raise pretty_schema_exception(e)
Beispiel #3
0
def validate_clean(clean_file, testbed_file, lint=True):
    """ Validates the clean yaml using device abstraction to collect
        the proper schemas

        Args:
            clean_file (str/dict): clean datafile
            testbed_file (str/dict): testbed datafile
            lint (bool, optional): Do yaml linting on the clean_file

        Returns:
            {
                'warnings' ['Warning example', ...],
                'exceptions: [ValueError, ...]
            }
    """
    warnings = []
    exceptions = []
    validation_results = {'warnings': warnings, 'exceptions': exceptions}

    if lint:
        lint_messages = do_lint(clean_file)
        for message in lint_messages:
            # we want to use the str representation not the object
            warnings.append(str(message))

    # these sections are not true stages and therefore cant be loaded
    sections_to_ignore = [
        'images',
        'order'
    ]

    base_schema = {
        Optional('clean_devices'): list,
        'cleaners': {
            Any(): {
                'module': str,
                Optional('devices'): list,
                Optional('platforms'): list,
                Optional('groups'): list,
                Any(): Any()
            }
        },
        'devices': {

        }
    }

    try:
        # Load yaml without parsing markup
        # Mock the use validate to prevent calling functions like
        # translate_host or import_from_name
        with patch.object(PyatsUse, 'validate') as mockvalid:
            # return data on Use.validate
            mockvalid.side_effect = lambda *x, **y: x[1]
            loaded_tb = testbed_loader(testbed_file,
                                       locations={},
                                       markupprocessor=TestbedMarkupProcessor(
                                           reference=True,
                                           callable=False,
                                           env_var=False,
                                           include_file=False,
                                           ask=False,
                                           encode=False))
    except Exception:
        exceptions.append(
            Exception("Could not load the testbed file. Use "
                      "'pyats validate testbed <file>' to validate "
                      "the testbed file.")
        )
        loaded_tb = testbed_loader({})

    loader = Loader(enable_extensions=True,
                    markupprocessor=MarkupProcessor(reference=True,
                                                    callable=False,
                                                    env_var=False,
                                                    include_file=False,
                                                    ask=False,
                                                    encode=False))

    try:
        clean_dict = loader.load(clean_file, locations={})
    except Exception as e:
        exceptions.append(e)
        return validation_results

    loader = Loader(enable_extensions=True,
                    markupprocessor=MarkupProcessor(reference=True,
                                                    callable=False,
                                                    env_var=False,
                                                    include_file=False,
                                                    ask=False,
                                                    encode=False))

    try:
        clean_dict = loader.load(clean_file, locations={})
    except Exception as e:
        exceptions.append(e)
        return validation_results

    try:
        clean_json = load_clean_json()
    except Exception as e:
        exceptions.append(e)

    from genie.libs.clean.recovery import recovery_processor

    for dev in clean_dict.get('devices', {}):
        schema = base_schema.setdefault('devices', {}).setdefault(dev, {})
        schema.update({Optional('order'): list})
        schema.update({Optional('device_recovery'): dict})
        schema.update({Optional('images'): Or(list, dict)})

        clean_data = clean_dict["devices"][dev]

        try:
            dev = loaded_tb.devices[dev]
        except KeyError as e:
            warnings.append(
                "The device {dev} specified in the clean yaml does "
                "not exist in the testbed.".format(dev=e))
            # cant validate schema so allow anything under dev
            schema.update({Any(): Any()})
            continue
        except Exception as e:
            exceptions.append(e)
            schema.update({Any(): Any()})
            continue

        # update stages with image
        if clean_data.get('images'):
            setattr(dev, 'clean', clean_data)
            try:
                # Get abstracted ImageHandler class
                abstract = Lookup.from_device(dev, packages={'clean': clean})
                ImageHandler = abstract.clean.stages.image_handler.ImageHandler
                image_handler = ImageHandler(dev, dev.clean['images'])
                initialize_clean_sections(image_handler, clean_data['order'])
            except Exception as e:
                # If the device does not have custom.abstraction defined
                # then we cannot load the correct stages to test the
                # correct schema. Skip this device.
                exceptions.append(Exception(dev.name+': '+str(e)))
                schema.update({Any(): Any()})
                continue


        for section in clean_data:

            # ignore sections that aren't true stages
            if section in sections_to_ignore:
                continue

            if section == 'device_recovery':
                schema.update({'device_recovery': recovery_processor.schema})
                continue

            clean_data[section].pop('change_order_if_fail', None)
            clean_data[section].pop('change_order_if_pass', None)

            # when no data is provided under stage, change None to dict
            # this is needed for schema validation
            if clean_data[section] is None:
                clean_data[section] = {}

            # Load it up so we can grab the schema from the stage
            # If source isnt provided then check if it is inside the clean json
            try:
                if 'source' not in clean_data:
                    task = get_clean_function(section, clean_json, dev)
                else:
                    task = load_class(clean_data, dev)
            except Exception as e:
                # Stage cannot be found. Allow any schema to prevent schema error
                # and skip this stage
                exceptions.append(str(e))
                schema.update({section: Any()})
                continue


            # Add the stage schema to the base schema
            if hasattr(task, 'schema'):
                schema.update({task.__name__: task.schema})

    try:
        Schema(base_schema).validate(clean_dict)
    except Exception as e:
        exceptions.append(pretty_schema_exception(e))

    return validation_results
Beispiel #4
0
def validate_clean(clean_dict, testbed_dict):
    """ Validates the clean yaml using device abstraction to collect
        the proper schemas

        Args:
            clean_dict (dict): clean datafile
            testbed_dict (dict): testbed datafile

        Returns:
            {
                'warnings' ['Warning example', ...],
                'exceptions: [ValueError, ...]
            }
    """
    warnings = []
    exceptions = []
    validation_results = {'warnings': warnings, 'exceptions': exceptions}

    # these sections are not true stages and therefore cant be loaded
    sections_to_ignore = [
        'images',
        'order'
    ]

    base_schema = {
        'cleaners': {
            Any(): {
                'module': str,
                Optional('devices'): list,
                Optional('platforms'): list,
                Optional('groups'): list,
                Any(): Any()
            }
        },
        'devices': {

        }
    }

    try:
        loaded_tb = testbed_loader(testbed_dict)
    except Exception:
        exceptions.append(
            Exception("Could not load the testbed file. Use "
                      "'pyats validate testbed <file>' to validate "
                      "the testbed file.")
        )

    clean_json = load_clean_json()
    from genie.libs.clean.stages.recovery import recovery_processor

    for dev in clean_dict.get('devices', {}):
        schema = base_schema.setdefault('devices', {}).setdefault(dev, {})
        schema.update({Optional('order'): list})
        schema.update({Optional('device_recovery'): dict})
        schema.update({Optional('images'): Or(list, dict)})

        clean_data = clean_dict["devices"][dev]

        try:
            dev = loaded_tb.devices[dev]
        except KeyError as e:
            warnings.append(
                "The device {dev} specified in the clean yaml does "
                "not exist in the testbed.".format(dev=e))
            # cant validate schema so allow anything under dev
            schema.update({Any(): Any()})
            continue

        # update stages with image
        if clean_data.get('images'):
            setattr(dev, 'clean', clean_data)
            # Get abstracted ImageHandler class
            abstract = Lookup.from_device(dev, packages={'clean': clean})
            ImageHandler = abstract.clean.stages.image_handler.ImageHandler

            # Image handler
            image_handler = ImageHandler(dev, dev.clean['images'])
            initialize_clean_sections(image_handler, clean_data['order'])

        for section in clean_data:
            # ignore sections that aren't true stages
            if section in sections_to_ignore:
                continue

            if section == 'device_recovery':
                schema.update({'device_recovery': recovery_processor.schema})
                continue

            # when no data is provided under stage, change None to dict
            # this is needed for schema validation
            if clean_data[section] is None:
                clean_data[section] = {}

            # Load it up so we can grab the schema from the stage
            # If source isnt provided then check if it is inside the clean json
            if 'source' not in clean_data:
                task = get_clean_function(section, clean_json, dev)
            else:
                task = load_class(clean_data, dev)

            # Add the stage schema to the base schema
            if hasattr(task, 'schema'):
                schema.update({task.__name__: task.schema})

    try:
        Schema(base_schema).validate(clean_dict)
    except Exception as e:
        exceptions.append(pretty_schema_exception(e))

    return validation_results