From 4a84f843dd04d996fc91e14ec8d99168824855c5 Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Sep 09 2019 07:10:31 +0000 Subject: Issue 50173 - Add the validate-syntax task to the dsconf schema Description: Perl scripts will be removed. And we should have the replacement for syntax-validate.pl. We should add the CLI option to dsconf schema. Add validate-syntax task subcommand for 'dsconf schema'. Add a test for syntax validate task https://pagure.io/389-ds-base/issue/50173 https://pagure.io/389-ds-base/issue/50545 Reviewed by: firstyear (Thanks!) --- diff --git a/dirsrvtests/tests/suites/syntax/acceptance_test.py b/dirsrvtests/tests/suites/syntax/acceptance_test.py new file mode 100644 index 0000000..db8f63c --- /dev/null +++ b/dirsrvtests/tests/suites/syntax/acceptance_test.py @@ -0,0 +1,112 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2019 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- + +import logging +import pytest +import os +from lib389.schema import Schema +from lib389.config import Config +from lib389.idm.user import UserAccounts +from lib389._constants import DEFAULT_SUFFIX +from lib389.topologies import log, topology_st as topo + +pytestmark = pytest.mark.tier0 + +log = log.getChild(__name__) + + +@pytest.fixture(scope="function") +def validate_syntax_off(topo, request): + config = Config(topo.standalone) + config.replace("nsslapd-syntaxcheck", "off") + + def fin(): + config.replace("nsslapd-syntaxcheck", "on") + request.addfinalizer(fin) + + +def test_valid(topo, validate_syntax_off): + """Test syntax-validate task with valid entries + + :id: ec402a5b-bfb1-494d-b751-71b0d31a4d83 + :setup: Standalone instance + :steps: + 1. Set nsslapd-syntaxcheck to off + 2. Clean error log + 3. Run syntax validate task + 4. Assert that there are no errors in the error log + 5. Set nsslapd-syntaxcheck to on + :expectedresults: + 1. It should succeed + 2. It should succeed + 3. It should succeed + 4. It should succeed + 5. It should succeed + """ + + inst = topo.standalone + + log.info('Clean the error log') + inst.deleteErrorLogs() + + schema = Schema(inst) + log.info('Attempting to add task entry...') + validate_task = schema.validate_syntax(DEFAULT_SUFFIX) + validate_task.wait() + exitcode = validate_task.get_exit_code() + assert exitcode == 0 + error_lines = inst.ds_error_log.match('.*Found 0 invalid entries.*') + assert (len(error_lines) == 1) + log.info('Found 0 invalid entries - Success') + + +def test_invalid_uidnumber(topo, validate_syntax_off): + """Test syntax-validate task with invalid uidNumber attribute value + + :id: 30fdcae6-ffa6-4ec4-8da9-6fb138fc1828 + :setup: Standalone instance + :steps: + 1. Set nsslapd-syntaxcheck to off + 2. Clean error log + 3. Add a user with uidNumber attribute set to an invalid value (string) + 4. Run syntax validate task + 5. Assert that there is corresponding error in the error log + 6. Set nsslapd-syntaxcheck to on + :expectedresults: + 1. It should succeed + 2. It should succeed + 3. It should succeed + 4. It should succeed + 5. It should succeed + 6. It should succeed + """ + + inst = topo.standalone + + log.info('Clean the error log') + inst.deleteErrorLogs() + + users = UserAccounts(inst, DEFAULT_SUFFIX) + users.create_test_user(uid="invalid_value") + + schema = Schema(inst) + log.info('Attempting to add task entry...') + validate_task = schema.validate_syntax(DEFAULT_SUFFIX) + validate_task.wait() + exitcode = validate_task.get_exit_code() + assert exitcode == 0 + error_lines = inst.ds_error_log.match('.*uidNumber: value #0 invalid per syntax.*') + assert (len(error_lines) == 1) + log.info('Found an invalid entry with wrong uidNumber - Success') + + +if __name__ == '__main__': + # Run isolated + # -s for DEBUG mode + CURRENT_FILE = os.path.realpath(__file__) + pytest.main("-s %s" % CURRENT_FILE) diff --git a/src/lib389/lib389/cli_conf/schema.py b/src/lib389/lib389/cli_conf/schema.py index 7764356..61ae662 100644 --- a/src/lib389/lib389/cli_conf/schema.py +++ b/src/lib389/lib389/cli_conf/schema.py @@ -198,6 +198,18 @@ def reload_schema(inst, basedn, log, args): print("To verify that the schema reload operation was successful, please check the error logs.") +def validate_syntax(inst, basedn, log, args): + schema = Schema(inst) + log.info('Attempting to add task entry...') + validate_task = schema.validate_syntax(args.DN, args.filter) + validate_task.wait() + exitcode = validate_task.get_exit_code() + if exitcode != 0: + log.error(f'Validate syntax task for {args.DN} has failed. Please, check logs') + else: + log.info('Successfully added task entry') + + def get_syntaxes(inst, basedn, log, args): log = log.getChild('get_syntaxes') schema = Schema(inst) @@ -364,4 +376,10 @@ def create_parser(subparsers): reload_parser.add_argument('-d', '--schemadir', help="directory where schema files are located") reload_parser.add_argument('--wait', action='store_true', default=False, help="Wait for the reload task to complete") - + validate_parser = schema_subcommands.add_parser('validate-syntax', + help='Run a task to check every modification to attributes to make sure ' + 'that the new value has the required syntax for that attribute type') + validate_parser.set_defaults(func=validate_syntax) + validate_parser.add_argument('DN', help="Base DN that contains entries to validate") + validate_parser.add_argument('-f', '--filter', help='Filter for entries to validate.\n' + 'If omitted, all entries with filter "(objectclass=*)" are validated') diff --git a/src/lib389/lib389/schema.py b/src/lib389/lib389/schema.py index 929e673..2a29a01 100755 --- a/src/lib389/lib389/schema.py +++ b/src/lib389/lib389/schema.py @@ -22,7 +22,7 @@ from lib389._constants import * from lib389._constants import DN_SCHEMA from lib389.utils import ds_is_newer from lib389._mapped_object import DSLdapObject -from lib389.tasks import SchemaReloadTask +from lib389.tasks import SchemaReloadTask, SyntaxValidateTask # Count should start with 0 because of the python-ldap API ObjectclassKind = Enum("Objectclass kind", @@ -497,6 +497,25 @@ class Schema(DSLdapObject): else: return str(attributetype), may, must + def validate_syntax(self, basedn, _filter=None): + """Create a validate syntax task + + :param basedn: Basedn to validate + :type basedn: str + :param _filter: a filter for entries to validate + :type _filter: str + + :returns: an instance of Task(DSLdapObject) + """ + + task = SyntaxValidateTask(self._instance) + task_properties = {'basedn': basedn} + if _filter is not None: + task_properties['filter'] = _filter + task.create(properties=task_properties) + + return task + class SchemaLegacy(object): diff --git a/src/lib389/lib389/tasks.py b/src/lib389/lib389/tasks.py index af97634..81de4a5 100644 --- a/src/lib389/lib389/tasks.py +++ b/src/lib389/lib389/tasks.py @@ -203,6 +203,21 @@ class SchemaReloadTask(Task): super(SchemaReloadTask, self).__init__(instance, dn) +class SyntaxValidateTask(Task): + """A single instance of schema reload task entry + + :param instance: An instance + :type instance: lib389.DirSrv + """ + + def __init__(self, instance, dn=None): + self.cn = 'syntax_validate_' + Task._get_task_date() + dn = f"cn={self.cn},cn=syntax validate,cn=tasks,cn=config" + + super(SyntaxValidateTask, self).__init__(instance, dn) + self._must_attributes.extend(['basedn']) + + class AbortCleanAllRUVTask(Task): """Abort the Clean All Ruv task on all masters. You should call this from "CleanAllRUVTask.abort()" instead to provide