From 1989c0eeed09a8d8fdae1e0e2a11c5b5da038636 Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Oct 11 2017 15:24:26 +0000 Subject: Issue #77 - Refactor docstrings in rST format - part 1 Decsription: With the first part, refactor docstrings in modules: _mapped_object.py, aci.py, agreement.py, backend.py, changelog.py, mappingTree.py, replica.py, repltools.py Format it properly and put to index.rst Add a script for links to the code in the docs. https://pagure.io/lib389/issue/77 Reviewed by: wibrown (Thanks!) --- diff --git a/src/lib389/doc/source/accesscontrol.rst b/src/lib389/doc/source/accesscontrol.rst new file mode 100644 index 0000000..2c5a882 --- /dev/null +++ b/src/lib389/doc/source/accesscontrol.rst @@ -0,0 +1,6 @@ +Access Control +-------------- + +.. toctree:: + + aci.rst diff --git a/src/lib389/doc/source/aci.rst b/src/lib389/doc/source/aci.rst new file mode 100644 index 0000000..8c7c0f3 --- /dev/null +++ b/src/lib389/doc/source/aci.rst @@ -0,0 +1,66 @@ +ACI +========== + +Usage example +------------------ +:: + + ACI_TARGET = ('(targetfilter ="(ou=groups)")(targetattr ="uniqueMember ' + '|| member")') + ACI_ALLOW = ('(version 3.0; acl "Allow test aci";allow (read, search, ' + 'write)') + ACI_SUBJECT = ('(userdn="ldap:///dc=example,dc=com??sub?(ou=engineering)" ' + 'and userdn="ldap:///dc=example,dc=com??sub?(manager=uid=' + 'wbrown,ou=managers,dc=example,dc=com) || ldap:///dc=examp' + 'le,dc=com??sub?(manager=uid=tbrown,ou=managers,dc=exampl' + 'e,dc=com)" );)') + + # Add some entry with ACI + group_dn = 'cn=testgroup,{}'.format(DEFAULT_SUFFIX) + gentry = Entry(group_dn) + gentry.setValues('objectclass', 'top', 'extensibleobject') + gentry.setValues('cn', 'testgroup') + gentry.setValues('aci', ACI_BODY) + standalone.add_s(gentry) + + # Get and parse ACI + acis = standalone.aci.list() + aci = acis[0] + + assert aci.acidata == { + 'allow': [{'values': ['read', 'search', 'write']}], + 'target': [], 'targetattr': [{'values': ['uniqueMember', 'member'], + 'equal': True}], + 'targattrfilters': [], + 'deny': [], + 'acl': [{'values': ['Allow test aci']}], + 'deny_raw_bindrules': [], + 'targetattrfilters': [], + 'allow_raw_bindrules': [{'values': [( + 'userdn="ldap:///dc=example,dc=com??sub?(ou=engineering)" and' + ' userdn="ldap:///dc=example,dc=com??sub?(manager=uid=wbrown,' + 'ou=managers,dc=example,dc=com) || ldap:///dc=example,dc=com' + '??sub?(manager=uid=tbrown,ou=managers,dc=example,dc=com)" ')]}], + 'targetfilter': [{'values': ['(ou=groups)'], 'equal': True}], + 'targetscope': [], + 'version 3.0;': [], + 'rawaci': complex_aci + } + + # You can get a raw ACI + raw_aci = aci.getRawAci() + +Additional information about ACI +---------------------------------- + +- https://access.redhat.com/documentation/en-US/Red_Hat_Directory_Server/10/html/Administration_Guide/Managing_Access_Control-Bind_Rules.html +- https://access.redhat.com/documentation/en-US/Red_Hat_Directory_Server/10/html/Administration_Guide/Managing_Access_Control-Creating_ACIs_Manually.html + +Module documentation +----------------------- + +.. autoclass:: lib389.aci.Aci + :members: + +.. autoclass:: lib389._entry.EntryAci + :members: diff --git a/src/lib389/doc/source/agreement.rst b/src/lib389/doc/source/agreement.rst new file mode 100644 index 0000000..8d449dc --- /dev/null +++ b/src/lib389/doc/source/agreement.rst @@ -0,0 +1,27 @@ +Agreement +========== + +Usage example +-------------- +:: + + master = topology.ms["master1"] + consumer = topology.ms["consumer1"] + # Create + repl_agreement = master.agreement.create(suffix=DEFAULT_SUFFIX, + host=consumer.host, + port=consumer.port) + # List + ents = master.agreement.list(suffix=DEFAULT_SUFFIX, + consumer_host=consumer.host, + consumer_port=consumer.port) + # Delete + ents = master1.agreement.delete(suffix=DEFAULT_SUFFIX) + + +Module documentation +---------------------- + +.. autoclass:: lib389.agreement.Agreement + :members: + diff --git a/src/lib389/doc/source/backend.rst b/src/lib389/doc/source/backend.rst new file mode 100644 index 0000000..5b4e1f4 --- /dev/null +++ b/src/lib389/doc/source/backend.rst @@ -0,0 +1,49 @@ +Backend +========== + +Usage example +-------------- +:: + + from lib389.backend import Backends + + backends = Backends(standalone) + backend = backends.create(properties={BACKEND_SUFFIX: 'o=new_suffix', # mandatory + BACKEND_NAME: new_backend, # mandatory + BACKEND_SAMPLE_ENTRIES: '001003006'}) + + # Create sample entries + backend.create_sample_entries(version='001003006') + + backend.delete() + +Backend properties +------------------- + +- BACKEND_NAME - 'somename' +- BACKEND_READONLY - 'on' | 'off' +- BACKEND_REQ_INDEX - 'on' | 'off' +- BACKEND_CACHE_ENTRIES - 1 to (2^32 - 1) on 32-bit systems or (2^63 - 1) + on 64-bit systems or -1, which means limitless +- BACKEND_CACHE_SIZE - 500 kilobytes to (2^32 - 1) + on 32-bit systems and to (2^63 - 1) on 64-bit systems +- BACKEND_DNCACHE_SIZE - 500 kilobytes to (2^32 - 1) + on 32-bit systems and to (2^63 - 1) on 64-bit systems +- BACKEND_DIRECTORY - Any valid path to the database instance +- BACKEND_CHAIN_BIND_DN - DN of the multiplexor +- BACKEND_CHAIN_BIND_PW - password of the multiplexor +- BACKEND_CHAIN_URLS - Any valid remote server LDAP URL +- BACKEND_SUFFIX - 'o=somesuffix' +- BACKEND_SAMPLE_ENTRIES - version of confir i.e. '001003006' + + +Module documentation +----------------------- + +.. autoclass:: lib389.backend.Backends + :members: + :inherited-members: + +.. autoclass:: lib389.backend.Backend + :members: + :inherited-members: diff --git a/src/lib389/doc/source/changelog.rst b/src/lib389/doc/source/changelog.rst new file mode 100644 index 0000000..b51dd20 --- /dev/null +++ b/src/lib389/doc/source/changelog.rst @@ -0,0 +1,22 @@ +Changelog +========== + +Usage example +-------------- +:: + + standalone = topology.standalone + # Create + changelog_dn = standalone.changelog.create() + # List + changelog_entries = standalone.changelog.list(changelogdn=changelog_dn) + # Delete + standalone.changelog.delete() + + +Module documentation +---------------------- + +.. autoclass:: lib389.changelog.Changelog + :members: + diff --git a/src/lib389/doc/source/conf.py b/src/lib389/doc/source/conf.py index 62fe1a5..c645db2 100644 --- a/src/lib389/doc/source/conf.py +++ b/src/lib389/doc/source/conf.py @@ -32,6 +32,7 @@ extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.ifconfig', + 'sphinx.ext.linkcode' ] # Add any paths that contain templates here, relative to this directory. @@ -112,7 +113,7 @@ todo_include_todos = False # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'alabaster' +html_theme = 'nature' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -292,3 +293,43 @@ texinfo_documents = [ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'https://docs.python.org/': None} + +import lib389 +import inspect +from os.path import relpath, dirname + +def linkcode_resolve(domain, info): + if domain != 'py': + return None + if not info['module']: + return None + + module_name = info['module'] + fullname = info['fullname'] + file_name = module_name.replace('.', '/') + + # Get the module object + submodule = sys.modules.get(module_name) + if not submodule: + return None + + # Get submodules + next_submodule = submodule + for name in fullname.split('.'): + try: + next_submodule = getattr(next_submodule, name) + except: + return None + + # Get line number for the submodule + try: + _, line = inspect.getsourcelines(next_submodule) + except: + line = None + + if line: + line_fmt = "#_{}".format(line) + else: + line_fmt = "" + + return "https://pagure.io/lib389/blob/master/f/{}.py{}".format(file_name, line_fmt) diff --git a/src/lib389/doc/source/config.rst b/src/lib389/doc/source/config.rst new file mode 100644 index 0000000..8207d42 --- /dev/null +++ b/src/lib389/doc/source/config.rst @@ -0,0 +1,47 @@ +Config +========== + +Usage example +--------------- +:: + + # Set config attribute: + standalone.config.set('passwordStorageScheme', 'SSHA') + + # Reset config attribute (by deleting it): + standalone.config.reset('passwordStorageScheme') + + # Enable/disable logs (error, access, audit): + standalone.config.enable_log('error') + standalone.config.disable_log('access') + + # Set loglevel for errors log. + # If 'update' set to True, it will add the 'vals' to existing values in the loglevel attribute + standalone.config.loglevel(vals=(LOG_DEFAULT,), service='error', update=False) + # You can get log levels from lib389._constants + (LOG_TRACE, + LOG_TRACE_PACKETS, + LOG_TRACE_HEAVY, + LOG_CONNECT, + LOG_PACKET, + LOG_SEARCH_FILTER, + LOG_CONFIG_PARSER, + LOG_ACL, + LOG_ENTRY_PARSER, + LOG_HOUSEKEEPING, + LOG_REPLICA, + LOG_DEFAULT, + LOG_CACHE, + LOG_PLUGIN, + LOG_MICROSECONDS, + LOG_ACL_SUMMARY) = [1 << x for x in (list(range(8)) + list(range(11, 19)))] + + # Set 'nsslapd-accesslog-logbuffering' to 'on' if True, otherwise set it to 'off' + standalone.config.logbuffering(True) + + +Module documentation +----------------------- + +.. autoclass:: lib389.config.Config + :members: diff --git a/src/lib389/doc/source/databases.rst b/src/lib389/doc/source/databases.rst new file mode 100644 index 0000000..3cd1bb1 --- /dev/null +++ b/src/lib389/doc/source/databases.rst @@ -0,0 +1,7 @@ +Configuring Databases +--------------------- + +.. toctree:: + + backend.rst + mappingtree.rst diff --git a/src/lib389/doc/source/dirsrv_log.rst b/src/lib389/doc/source/dirsrv_log.rst new file mode 100644 index 0000000..1d67854 --- /dev/null +++ b/src/lib389/doc/source/dirsrv_log.rst @@ -0,0 +1,30 @@ +DirSrv log +========== + +Usage example +-------------- +:: + + # Get array of all lines (including rotated and compresed logs): + standalone.ds_access_log.readlines_archive() + standalone.ds_error_log.readlines_archive() + # Get array of all lines (without rotated and compresed logs): + standalone.ds_access_log.readlines() + standalone.ds_error_log.readlines() + # Get array of lines that match the regex pattern: + standalone.ds_access_log.match_archive('.*fd=.*') + standalone.ds_error_log.match_archive('.*fd=.*') + standalone.ds_access_log.match('.*fd=.*') + standalone.ds_error_log.match('.*fd=.*') + # Break up the log line into the specific fields: + assert(standalone.ds_error_log.parse_line('[27/Apr/2016:13:46:35.775670167 +1000] slapd started. Listening on All Interfaces port 54321 for LDAP requests') == {'timestamp': '[27/Apr/2016:13:46:35.775670167 +1000]', 'message': 'slapd starte d. Listening on All Interfaces port 54321 for LDAP requests', 'datetime': datetime.datetime(2016, 4, 27, 13, 0, 0, 775670, tzinfo=tzoffset(No ne, 36000))}) + + +Module documentation +----------------------- + +.. autoclass:: lib389.dirsrv_log.DirsrvAccessLog + :members: + +.. autoclass:: lib389.dirsrv_log.DirsrvErrorLog + :members: diff --git a/src/lib389/doc/source/domain.rst b/src/lib389/doc/source/domain.rst new file mode 100644 index 0000000..31040de --- /dev/null +++ b/src/lib389/doc/source/domain.rst @@ -0,0 +1,21 @@ +Domain +========== + +Usage example +-------------- +:: + + # After the creating a backend, sometimes you don't need a lot of entries under the created suffix + # So instead of using BACKEND_SAMPLE_ENTRIES you can create simple domain entry using the next object: + from lib389.idm.domain import Domain + domain = Domain(standalone], 'dc=test,dc=com') + domain.create(properties={'dc': 'test', 'description': 'dc=test,dc=com'}) + + # It will be deleted with the 'backend.delete()' + + +Module documentation +----------------------- + +.. autoclass:: lib389.idm.domain.Domain + :members: diff --git a/src/lib389/doc/source/dseldif.rst b/src/lib389/doc/source/dseldif.rst new file mode 100644 index 0000000..0718232 --- /dev/null +++ b/src/lib389/doc/source/dseldif.rst @@ -0,0 +1,30 @@ +DSE ldif +========== + +Usage example +-------------- +:: + + from lib389.dseldif import DSEldif + + dse_ldif = DSEldif(topo.standalone) + + # Get a list of attribute values under a given entry + config_cn = dse_ldif.get(DN_CONFIG, 'cn') + + # Add an attribute under a given entry + dse_ldif.add(DN_CONFIG, 'someattr', 'someattr_value') + + # Replace attribute values with a new one under a given entry. It will remove all previous 'someattr' values + dse_ldif.replace(DN_CONFIG, 'someattr', 'someattr_value') + + # Delete attributes under a given entry + dse_ldif.delete(DN_CONFIG, 'someattr') + dse_ldif.delete(DN_CONFIG, 'someattr', 'someattr_value') + + +Module documentation +----------------------- + +.. autoclass:: lib389.dseldif.DSEldif + :members: diff --git a/src/lib389/doc/source/group.rst b/src/lib389/doc/source/group.rst new file mode 100644 index 0000000..a916748 --- /dev/null +++ b/src/lib389/doc/source/group.rst @@ -0,0 +1,43 @@ +Group +======== + +Usage example +-------------- +:: + + # group and groups additionaly have 'is_member', 'add_member' and 'remove_member' methods + # posixgroup and posixgroups have 'check_member' and 'add_member' + from lib389.idm.group import Groups + from lib389.idm.posixgroup import PosixGroups + + groups = Groups(standalone, DEFAULT_SUFFIX) + posix_groups = PosixGroups(standalone, DEFAULT_SUFFIX) + group_properties = { + 'cn' : 'group1', + 'description' : 'testgroup' + } + group = groups.create(properties=group_properties) + + # So now you can: + # Check the membership - shouldn't we make it consistent? + assert(not group.is_member(testuser.dn)) + assert(not posix_groups.check_member(testuser.dn)) + + group.add_member(testuser.dn) + posix_groups.add_member(testuser.dn) + + # Remove member - add the method to PosixGroups too? + group.remove_member(testuser.dn) + + group.delete(): + + +Module documentation +----------------------- + +.. autoclass:: lib389.idm.group.Groups + :members: + +.. autoclass:: lib389.idm.group.Group + :members: + diff --git a/src/lib389/doc/source/guidelines.rst b/src/lib389/doc/source/guidelines.rst index daf249b..6d80c11 100644 --- a/src/lib389/doc/source/guidelines.rst +++ b/src/lib389/doc/source/guidelines.rst @@ -8,7 +8,6 @@ For a saving place purposes, I'll replace topology_m2.ms["master1"] with master1 , etc. - Basic workflow ============== @@ -167,11 +166,8 @@ Basic workflow commits into one commit. -A ways to make your code better in a pytest way -=============================================== - Fixtures --------- +========= Basic info about fixtures - http://pytest.org/latest/fixture.html#fixtures @@ -207,7 +203,7 @@ Parametrizing Test cases ----------- +========== Parametrizing ~~~~~~~~~~~~~ @@ -351,11 +347,9 @@ Asserting assert 'maximum recursion' in str(excinfo.value) -lib389 and python-ldap functions -================================ Constants ---------- +========== Basic constants ~~~~~~~~~~~~~~~ @@ -391,7 +385,7 @@ If you need a lot of constants, import with * Add, Modify, and Delete Operations ----------------------------------- +=================================== Please, use these methods for the operations that can't be performed by DSLdapObjects. @@ -415,7 +409,7 @@ by DSLdapObjects. Search and Bind Operations --------------------------- +=================================== + By default when an instance is created and opened, it is already authenticated as the Root DN(Directory Manager). @@ -446,7 +440,7 @@ Search and Bind Operations Basic instance operations -------------------------- +=================================== :: @@ -485,7 +479,7 @@ Basic instance operations Setting up SSL/TLS ------------------- +=================================== :: @@ -520,7 +514,7 @@ Setting up SSL/TLS Certification-based authentication ----------------------------------- +=================================== You need to setup and turn on SSL first (use the previous chapter). @@ -568,11 +562,10 @@ You need to setup and turn on SSL first (use the previous chapter). Replication ------------ +=================================== Basic configuration - + After the instance is created, you can enable it for replication and set up a replication agreement. diff --git a/src/lib389/doc/source/identitymanagement.rst b/src/lib389/doc/source/identitymanagement.rst new file mode 100644 index 0000000..391a691 --- /dev/null +++ b/src/lib389/doc/source/identitymanagement.rst @@ -0,0 +1,10 @@ +Identity Management +------------------- + +.. toctree:: + + user.rst + group.rst + domain.rst + organisationalunit.rst + services.rst diff --git a/src/lib389/doc/source/index.rst b/src/lib389/doc/source/index.rst index 55e10ef..e5445f8 100644 --- a/src/lib389/doc/source/index.rst +++ b/src/lib389/doc/source/index.rst @@ -17,9 +17,20 @@ Contents ======== .. toctree:: - :maxdepth: 3 + :maxdepth: 2 guidelines.rst + Replication + Configuring Databases + Access Control + +Work in progress +----------------- +.. toctree:: + :maxdepth: 2 + + Identity Management + need_to_be_triaged.rst Contact us diff --git a/src/lib389/doc/source/indexes.rst b/src/lib389/doc/source/indexes.rst new file mode 100644 index 0000000..3980028 --- /dev/null +++ b/src/lib389/doc/source/indexes.rst @@ -0,0 +1,42 @@ +Indexes +========== + +Usage example +-------------- +:: + + from lib389.index import Indexes + + indexes = Indexes(standalone) + + # create and delete a default index. + index = indexes.create(properties={ + 'cn': 'modifytimestamp', + 'nsSystemIndex': 'false', + 'nsIndexType': 'eq' + }) + + default_index_list = indexes.list() + found = False + for i in default_index_list: + if i.dn.startswith('cn=modifytimestamp'): + found = True + assert found + index.delete() + + default_index_list = indexes.list() + found = False + for i in default_index_list: + if i.dn.startswith('cn=modifytimestamp'): + found = True + assert not found + + +Module documentation +----------------------- + +.. autoclass:: lib389.index.Index + :members: + +.. autoclass:: lib389.index.Indexes + :members: diff --git a/src/lib389/doc/source/ldclt.rst b/src/lib389/doc/source/ldclt.rst new file mode 100644 index 0000000..046a3b5 --- /dev/null +++ b/src/lib389/doc/source/ldclt.rst @@ -0,0 +1,42 @@ +Config +========== + +Usage example +-------------- +:: + + This class will allow general usage of ldclt. It's not meant to expose all the functions. Just use ldclt for that. + # Creates users as user. Password will be set to password + # This will automatically work with the bind loadtest. + # Template + # objectClass: top + # objectclass: person + # objectClass: organizationalPerson + # objectClass: inetorgperson + # objectClass: posixAccount + # objectClass: shadowAccount + # sn: user[A] + # cn: user[A] + # givenName: user[A] + # description: description [A] + # userPassword: user[A] + # mail: user[A]@example.com + # uidNumber: 1[A] + # gidNumber: 2[A] + # shadowMin: 0 + # shadowMax: 99999 + # shadowInactive: 30 + # shadowWarning: 7 + # homeDirectory: /home/user[A] + # loginShell: /bin/false + topology.instance.ldclt.create_users('ou=People,{}'.format(DEFAULT_SUFFIX), max=1999) + + # Run the load test for a few rounds + topology.instance.ldclt.bind_loadtest('ou=People,{}'.format(DEFAULT_SUFFIX), max=1999) + + +Module documentation +----------------------- + +.. autoclass:: lib389.ldclt.Ldclt + :members: diff --git a/src/lib389/doc/source/mappingtree.rst b/src/lib389/doc/source/mappingtree.rst new file mode 100644 index 0000000..de6843c --- /dev/null +++ b/src/lib389/doc/source/mappingtree.rst @@ -0,0 +1,31 @@ +Mapping Tree +============= + +Usage example +--------------- +:: + + # In the majority of test cases, it is better to use 'Backends' for the operation, + # because it creates the mapping tree for you. Though if you need to create a mapping tree, you can. + # Just work with it as with usual DSLdapObject and DSLdapObjects + # For instance: + from lib389.mappingTree import MappingTrees + mts = MappingTrees(standalone) + mt = mts.create(properties={ + 'cn': ["dc=newexample,dc=com",], + 'nsslapd-state' : 'backend', + 'nsslapd-backend' : 'someRoot', + }) + # It will be deleted with the 'backend.delete()' + + +Module documentation +----------------------- + +.. autoclass:: lib389.mappingTree.MappingTrees + :members: + :inherited-members: + +.. autoclass:: lib389.mappingTree.MappingTree + :members: + :inherited-members: diff --git a/src/lib389/doc/source/monitor.rst b/src/lib389/doc/source/monitor.rst new file mode 100644 index 0000000..5a02510 --- /dev/null +++ b/src/lib389/doc/source/monitor.rst @@ -0,0 +1,19 @@ +Monitor +========== + +Usage example +-------------- +:: + + # Monitor and MonitorLDBM are the simple DSLdapObject things. + # You can use all methods from chapter above to get current server performance detail + version = standalone.monitor.get_attr_val('version') + dbcachehit = standalone.monitorldbm.get_attr_val('dbcachehit') + + +Module documentation +----------------------- + +.. autoclass:: lib389.monitor.Monitor + :members: + diff --git a/src/lib389/doc/source/need_to_be_triaged.rst b/src/lib389/doc/source/need_to_be_triaged.rst new file mode 100644 index 0000000..fbbed03 --- /dev/null +++ b/src/lib389/doc/source/need_to_be_triaged.rst @@ -0,0 +1,20 @@ +Need to be triaged +------------------- + +.. toctree:: + + backend.rst + config.rst + dirsrv_log.rst + dseldif.rst + paths.rst + indexes.rst + ldclt.rst + mappingtree.rst + monitor.rst + passwd.rst + plugin.rst + rootdse.rst + schema.rst + task.rst + utils.rst diff --git a/src/lib389/doc/source/organisationalunit.rst b/src/lib389/doc/source/organisationalunit.rst new file mode 100644 index 0000000..e232f08 --- /dev/null +++ b/src/lib389/doc/source/organisationalunit.rst @@ -0,0 +1,41 @@ +Organisational Unit +==================== + +Usage example +-------------- +:: + + # Don't forget that Services requires created rdn='ou=Services' + # This you can create with OrganisationalUnits + + from lib389.idm.organisationalunit import OrganisationalUnits + from lib389.idm.services import ServiceAccounts + + ous = OrganisationalUnits(standalone, DEFAULT_SUFFIX) + services = ServiceAccounts(standalone, DEFAULT_SUFFIX) + + # Create the OU for them + ous.create(properties={ + 'ou': 'Services', + 'description': 'Computer Service accounts which request DS bind', + }) + + # Now, we can create the services from here. + service = services.create(properties={ + 'cn': 'testbind', + 'userPassword': 'Password1' + }) + + conn = service.bind('Password1') + conn.unbind_s() + + +Module documentation +----------------------- + +.. autoclass:: lib389.idm.organisationalunit.OrganisationalUnits + :members: + +.. autoclass:: lib389.idm.organisationalunit.OrganisationalUnit + :members: + diff --git a/src/lib389/doc/source/passwd.rst b/src/lib389/doc/source/passwd.rst new file mode 100644 index 0000000..5b60d60 --- /dev/null +++ b/src/lib389/doc/source/passwd.rst @@ -0,0 +1,33 @@ +LDCLT +========== + +Usage example +-------------- +:: + + from lib389.passwd import password_hash, password_generate + + bindir = standalone.ds_paths.bin_dir + PWSCHEMES = [ + 'SHA1', + 'SHA256', + 'SHA512', + 'SSHA', + 'SSHA256', + 'SSHA512', + 'PBKDF2_SHA256', + ] + + # Generate password + raw_secure_password = password_generate() + + # Encrypt the password + # default scheme is 'SSHA512' + secure_password = password_hash(raw_secure_password, scheme='SSHA256', bin_dir=bindir) + + +Module documentation +----------------------- + +.. automodule:: lib389.passwd + :members: diff --git a/src/lib389/doc/source/paths.rst b/src/lib389/doc/source/paths.rst new file mode 100644 index 0000000..39680c6 --- /dev/null +++ b/src/lib389/doc/source/paths.rst @@ -0,0 +1,42 @@ +Paths +========== + +Usage example +-------------- +:: + + # You can get any variable from the list bellow. Like this: + product = standalone.ds_paths.product + + variables = [ + 'product', + 'version', + 'user', + 'group', + 'root_dn', + 'prefix', + 'bin_dir', + 'sbin_dir', + 'lib_dir', + 'data_dir', + 'tmp_dir', + 'sysconf_dir', + 'config_dir', + 'schema_dir', + 'cert_dir', + 'local_state_dir', + 'run_dir', + 'lock_dir', + 'log_dir', + 'inst_dir', + 'db_dir', + 'backup_dir', + 'ldif_dir', + 'initconfig_dir', + ] + +Module documentation +----------------------- + +.. autoclass:: lib389.paths.Paths + :members: diff --git a/src/lib389/doc/source/plugin.rst b/src/lib389/doc/source/plugin.rst new file mode 100644 index 0000000..8592291 --- /dev/null +++ b/src/lib389/doc/source/plugin.rst @@ -0,0 +1,35 @@ +Plugin +========== + +You can take plugin constant names here - https://pagure.io/lib389/blob/master/f/lib389/_constants.py#_164 + +Usage example +-------------- +:: + + # Plugin and Plugins additionaly have 'enable', 'disable' and 'status' methods + # Here I show you basic way to work with it. Additional methods of complex plugins will be described in subchapters + + from lib389.plugin import Plugins, ACLPlugin + from lib389._constants import PLUGIN_ACL + + # You can just enable/disable plugins from Plugins interface + plugins = Plugins(standalone) + plugins.enable(PLUGIN_ACL) + + # Or you can first 'get' it and then work with it (make sense if your plugin is a complex one) + aclplugin = ACLPlugin(standalone) + + aclplugin.enable() + + aclplugin.disable() + + # True if nsslapd-pluginEnabled is 'on', False otherwise - change the name? + assert(uniqplugin.status()) + + +Module documentation +----------------------- + +.. automodule:: lib389.plugins + :members: diff --git a/src/lib389/doc/source/replica.rst b/src/lib389/doc/source/replica.rst new file mode 100644 index 0000000..75acdb8 --- /dev/null +++ b/src/lib389/doc/source/replica.rst @@ -0,0 +1,59 @@ +Replica +========== + +Usage example +-------------- +:: + + from lib389.replica import Replicas + + replicas = Replicas(standalone) + # Enable replication + # - changelog will be created + # - replica manager will be with the defaults + # - replica.create() will be executed + replica = replicas.enable(suffix=DEFAULT_SUFFIX, + role=REPLICAROLE_MASTER, + replicaID=REPLICAID_MASTER_1) + # Roles - REPLICAROLE_MASTER, REPLICAROLE_HUB, and REPLICAROLE_CONSUMER + # For masters and hubs you can use the constants REPLICAID_MASTER_X and REPLICAID_HUB_X + # Change X for a number from 1 to 100 - for role REPLICAROLE_MASTER only + + # Disable replication + # - agreements and replica entry will be deleted + # - changelog is not deleted (but should?) + replicas.disable(suffix=DEFAULT_SUFFIX) + + # Get RUV entry + replicas.get_ruv_entry() + + # Get DN + replicas.get_dn(suffix) + + # Promote + replicas.promote(suffix=DEFAULT_SUFFIX, + newrole=REPLICAROLE_MASTER, + binddn=REPL_BINDDN, + rid=REPLICAID_MASTER_1) + # Demote + replicas.demote(suffix=DEFAULT_SUFFIX, + newrole=REPLICAROLE_CONSUMER) + # Test, that replication works + replicas.test(master2) + + # Additional replica object methods + # Get role + replica.get_role() + + replica.deleteAgreements() + +Module documentation +----------------------- + +.. autoclass:: lib389.replica.Replicas + :members: + :inherited-members: + +.. autoclass:: lib389.replica.Replica + :members: + :inherited-members: diff --git a/src/lib389/doc/source/replication.rst b/src/lib389/doc/source/replication.rst new file mode 100644 index 0000000..0a8ab7b --- /dev/null +++ b/src/lib389/doc/source/replication.rst @@ -0,0 +1,9 @@ +Replication +----------- + +.. toctree:: + + agreement.rst + changelog.rst + replica.rst + repltools.rst diff --git a/src/lib389/doc/source/repltools.rst b/src/lib389/doc/source/repltools.rst new file mode 100644 index 0000000..ce28197 --- /dev/null +++ b/src/lib389/doc/source/repltools.rst @@ -0,0 +1,43 @@ +Replication Tools +================== + +Usage example +-------------- +:: + + from lib389.repltools import ReplTools + + # Gather all the CSN strings from the access and verify all of those CSNs exist on all the other replicas. + # dirsrv_replicas - a list of DirSrv objects. The list must begin with master replicas + # ignoreCSNs - an optional string of csns to be ignored + # if the caller knows that some csns can differ eg.: '57e39e72000000020000|vucsn-57e39e76000000030000' + ReplTools.checkCSNs([master1, master2], ignoreCSNs=None) + + # Find and measure the convergence of entries from a replica, and + # print a report on how fast all the "ops" replicated to the other replicas. + # suffix - Replicated suffix + # ops - A list of "operations" to search for in the access logs + # replica - Dirsrv object where the entries originated + # all_replicas - A list of Dirsrv replicas + # It returns - The longest time in seconds for an operation to fully converge + longest_time = ReplTools.replConvReport(DEFAULT_SUFFIX, ops, master1, [master1, master2]) + + # Take a list of DirSrv Objects and check to see if all of the present + # replication agreements are idle for a particular backend + assert(ReplTools.replIdle([master1, master2], suffix=DEFAULT_SUFFIX)) + defaultProperties = { + REPLICATION_BIND_DN: "cn=replrepl,cn=config", + REPLICATION_BIND_PW + + # Create an entry that will be used to bind as replication manager + ReplTools.createReplManager(standalone, + repl_manager_dn=defaultProperties[REPLICATION_BIND_DN], + repl_manager_pw=defaultProperties[REPLICATION_BIND_PW]) + + +Module documentation +----------------------- + +.. autoclass:: lib389.repltools.ReplTools + :members: + diff --git a/src/lib389/doc/source/rootdse.rst b/src/lib389/doc/source/rootdse.rst new file mode 100644 index 0000000..51ec8b8 --- /dev/null +++ b/src/lib389/doc/source/rootdse.rst @@ -0,0 +1,25 @@ +Root DSE +========== + +Usage example +-------------- +:: + + # Get attribute values of 'supportedSASLMechanisms' + standalone.rootdse.supported_sasl() + + # Returns True or False + assert(standalone.rootdse.supports_sasl_gssapi() + assert(standalone.rootdse.supports_sasl_ldapssotoken() + assert(standalone.rootdse.supports_sasl_plain() + assert(standalone.rootdse.supports_sasl_external() + assert(standalone.rootdse.supports_exop_whoami() + assert(standalone.rootdse.supports_exop_ldapssotoken_request() + assert(standalone.rootdse.supports_exop_ldapssotoken_revoke() + + +Module documentation +----------------------- + +.. autoclass:: lib389.rootdse.RootDSE + :members: diff --git a/src/lib389/doc/source/schema.rst b/src/lib389/doc/source/schema.rst new file mode 100644 index 0000000..bcff164 --- /dev/null +++ b/src/lib389/doc/source/schema.rst @@ -0,0 +1,62 @@ +Schema +========== + +Usage example +-------------- +:: + + # Get the schema as an LDAP entry + schema = standalone.schema.get_entry() + + # Get the schema as a python-ldap SubSchema object + subschema = standalone.schema.get_subschema() + + # Get a list of the schema files in the instance schemadir + schema_files = standalone.schema.list_files() + + + # Convert the given schema file name to its python-ldap format suitable for passing to ldap.schema.SubSchema() + parsed = standalone.schema.file_to_ldap('/full/path/to/file.ldif') + + # Convert the given schema file name to its python-ldap format ldap.schema.SubSchema object + parsed = standalone.schema.file_to_subschema('/full/path/to/file.ldif') + + # Add a schema element to the schema + standalone.schema.add_schema(attr, val) + + # Delete a schema element from the schema + standalone.schema.del_schema(attr, val) + + # Add 'attributeTypes' definition to the schema + standalone.schema.add_attribute(attributes) + + # Add 'objectClasses' definition to the schema + standalone.schema.add_objectclass(objectclasses) + + # Get a schema nsSchemaCSN attribute + schema_csn = standalone.schema.get_schema_csn() + + # Get a list of ldap.schema.models.ObjectClass objects for all objectClasses supported by this instance + objectclasses = standalone.schema.get_objectclasses() + + # Get a list of ldap.schema.models.AttributeType objects for all attributeTypes supported by this instance + attributetypes = standalone.schema.get_attributetypes() + + # Get a list of the server defined matching rules + matchingrules = standalone.schema.get_matchingrules() + + # Get a single matching rule instance that matches the mr_name. Returns None if the matching rule doesn't exist + matchingrule = standalone.schema.query_matchingrule(matchingrule_name) + + # Get a single ObjectClass instance that matches objectclassname. Returns None if the objectClass doesn't exist + objectclass = standalone.schema.query_objectclass(objectclass_name) + + # Returns a tuple of the AttributeType, and what objectclasses may or must take this attributeType. Returns None if attributetype doesn't + (attributetype, may, must) = standalone.schema.query_attributetype(attributetype_name) + + +Module documentation +----------------------- + +.. autoclass:: lib389.schema.Schema + :members: diff --git a/src/lib389/doc/source/services.rst b/src/lib389/doc/source/services.rst new file mode 100644 index 0000000..b556e8a --- /dev/null +++ b/src/lib389/doc/source/services.rst @@ -0,0 +1,40 @@ +Services +========== + +Usage example +-------------- +:: + + # Don't forget that Services requires created rdn='ou=Services' + # This you can create with OrganisationalUnits + + from lib389.idm.organisationalunit import OrganisationalUnits + from lib389.idm.services import ServiceAccounts + + ous = OrganisationalUnits(standalone, DEFAULT_SUFFIX) + services = ServiceAccounts(standalone, DEFAULT_SUFFIX) + + # Create the OU for them + ous.create(properties={ + 'ou': 'Services', + 'description': 'Computer Service accounts which request DS bind', + }) + + # Now, we can create the services from here. + service = services.create(properties={ + 'cn': 'testbind', + 'userPassword': 'Password1' + }) + + conn = service.bind('Password1') + conn.unbind_s() + + +Module documentation +----------------------- + +.. autoclass:: lib389.idm.services.ServiceAccounts + :members: + +.. autoclass:: lib389.idm.services.ServiceAccount + :members: diff --git a/src/lib389/doc/source/task.rst b/src/lib389/doc/source/task.rst new file mode 100644 index 0000000..9adf96c --- /dev/null +++ b/src/lib389/doc/source/task.rst @@ -0,0 +1,59 @@ +Indexes +========== + +Besides the predefined tasks (which described in a chapter below) you can create +your own task objects with specifying a DN (https://pagure.io/lib389/blob/master/f/lib389/_constants.py#_134) + +Usage example +-------------- +:: + + from lib389.tasks import Task + + newtask = Task(instance, dn) # Should we create Tasks and put the precious to TasksLegacy? + + newtask.create(rdn, properties, basedn) + + # Check if the task is complete + assert(newtask.is_complete()) + + # Check task's exit code if task is complete, else None + if newtask.is_complete(): + exit_code = newtask.get_exit_code() + + # Wait until task is complete + newtask.wait() + + # If True, waits for the completion of the task before to return + args = {TASK_WAIT: True} + + # Some tasks ca be found only under old object. You can access them with this: + standalone.tasks.importLDIF(DEFAULT_SUFFIX, path_ro_ldif, args) + standalone.tasks.exportLDIF(DEFAULT_SUFFIX, benamebase=None, output_file=path_to_ldif, args) + standalone.tasks.db2bak(backup_dir, args) + standalone.tasks.bak2db(bename=None, backup_dir, args) + standalone.tasks.reindex(suffix=None, benamebase=None, attrname=None, args) + standalone.tasks.fixupMemberOf(suffix=None, benamebase=None, filt=None, args) + standalone.tasks.fixupTombstones(bename=None, args) + standalone.tasks.automemberRebuild(suffix=DEFAULT_SUFFIX, scope='sub', filterstr='objectclass=top', args) + standalone.tasks.automemberExport(suffix=DEFAULT_SUFFIX, scope='sub', fstr='objectclass=top', ldif_out=None, args) + standalone.tasks.automemberMap(ldif_in=None, ldif_out=None, args) + standalone.tasks.fixupLinkedAttrs(linkdn=None, args) + standalone.tasks.schemaReload(schemadir=None, args) + standalone.tasks.fixupWinsyncMembers(suffix=DEFAULT_SUFFIX, fstr='objectclass=top', args) + standalone.tasks.syntaxValidate(suffix=DEFAULT_SUFFIX, fstr='objectclass=top', args) + standalone.tasks.usnTombstoneCleanup(suffix=DEFAULT_SUFFIX, bename=None, maxusn_to_delete=None, args) + standalone.tasks.sysconfigReload(configfile=None, logchanges=None, args) + standalone.tasks.cleanAllRUV(suffix=None, replicaid=None, force=None, args) + standalone.tasks.abortCleanAllRUV(suffix=None, replicaid=None, certify=None, args) + standalone.tasks.upgradeDB(nsArchiveDir=None, nsDatabaseType=None, nsForceToReindex=None, args) + + +Module documentation +----------------------- + +.. autoclass:: lib389.tasks.Tasks + :members: + +.. autoclass:: lib389.tasks.Task + :members: diff --git a/src/lib389/doc/source/user.rst b/src/lib389/doc/source/user.rst new file mode 100644 index 0000000..6d59e91 --- /dev/null +++ b/src/lib389/doc/source/user.rst @@ -0,0 +1,50 @@ +User Accounts +============= + +Usage example +-------------- +:: + + # There is a basic way to work with it + from lib389.idm.user import UserAccounts + users = UserAccounts(standalone, DEFAULT_SUFFIX) + user_properties = { + 'uid': USER_NAME, + 'cn' : USER_NAME, + 'sn' : USER_NAME, + 'userpassword' : USER_PWD, + 'uidNumber' : '1000', + 'gidNumber' : '2000',1 + 'homeDirectory' : '/home/{}'.format(USER_NAME) + } + testuser = users.create(properties=user_properties) + + # After this you can: + # Get the list of them + users.list() + + # Get some user: + testuser = users.get('testuser') + # or + testuser = users.list()[0] # You can loop through 'for user in users:' + + # Set some attribute to the entry + testuser.set('userPassword', 'password') + + # Bind as the user + conn = testuser.bind('password') # It will create a new connection + conn.modify_s() + conn.unbind_s() + + # Delete + testuser.delete() + + +Module documentation +----------------------- + +.. autoclass:: lib389.idm.user.UserAccounts + :members: + +.. autoclass:: lib389.idm.user.UserAccount + :members: diff --git a/src/lib389/doc/source/utils.rst b/src/lib389/doc/source/utils.rst new file mode 100644 index 0000000..9e7865f --- /dev/null +++ b/src/lib389/doc/source/utils.rst @@ -0,0 +1,23 @@ +Utils +========== + +Usage example +-------------- +:: + + standalone.ldif2db(bename, suffixes, excludeSuffixes, encrypt, import_file) + standalone.db2ldif(bename, suffixes, excludeSuffixes, encrypt, repl_data, outputfile) + standalone.bak2db(archive_dir,bename=None) + standalone.db2bak(archive_dir) + standalone.db2index(bename=None, suffixes=None, attrs=None, vlvTag=None) + standalone.dbscan(bename=None, index=None, key=None, width=None, isRaw=False) + + # Generate a simple ldif file using the dbgen.pl script, and set the ownership and permissions to match the user that the server runs as + standalone.buildLDIF(number_of_entries, path_to_ldif, suffix='dc=example,dc=com') + + +Module documentation +----------------------- + +.. automodule:: lib389.utils + :members: diff --git a/src/lib389/lib389/_entry.py b/src/lib389/lib389/_entry.py index b8cb4c4..d13f458 100644 --- a/src/lib389/lib389/_entry.py +++ b/src/lib389/lib389/_entry.py @@ -392,17 +392,22 @@ class Entry(object): class EntryAci(object): + """Breaks down an aci attribute string from 389, into a dictionary + of terms and values. These values can then be manipulated, and + subsequently rebuilt into an aci string. + + :param entry: An entry + :type entry: lib389._entry.Entry + :param rawaci: Aci in a raw form + :type rawaci: str + :param verbose: False by default + :type verbose: bool """ - See https://access.redhat.com/documentation/en-US/Red_Hat_Directory_ - Server/10/html/Administration_Guide/Managing_Access_Control-Bind_Rules. - html - https://access.redhat.com/documentation/en-US/Red_Hat_Directory_Server - /10/html/Administration_Guide/Managing_Access_Control-Creating_ACIs_ - Manually.html - We seperate the keys into 3 groups, and one group that has overlap. - This is so we can not only split the aci, but rebuild it from the - dictionary at a later point in time. - """ + + # We seperate the keys into 3 groups, and one group that has overlap. + # This is so we can not only split the aci, but rebuild it from the + # dictionary at a later point in time. + # These are top level aci comoponent keys _keys = ['targetscope', 'targetattrfilters', @@ -435,9 +440,6 @@ class EntryAci(object): def __init__(self, entry, rawaci, verbose=False): """ - Breaks down an aci attribute string from 389, into a dictionary - of terms and values. These values can then be manipulated, and - subsequently rebuilt into an aci string. """ self.verbose = verbose self.entry = entry @@ -475,13 +477,12 @@ class EntryAci(object): return rawaci def getRawAci(self): - """ - This method will rebuild an aci from the contents of the acidata + """This method will rebuild an aci from the contents of the acidata dict found on the object. - returns an aci attribute string. - + :returns: An aci attribute string. """ + # Rebuild the aci from the .acidata. rawaci = '' # For each key in the outer segment diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py index 6b50f83..fe84cd4 100644 --- a/src/lib389/lib389/_mapped_object.py +++ b/src/lib389/lib389/_mapped_object.py @@ -19,7 +19,6 @@ from lib389.utils import ( ensure_list_int ) - # This function filter and term generation provided thanks to # The University of Adelaide. @@ -64,10 +63,13 @@ def _gen_filter(attrtypes, values, extra=None): class DSLogging(object): - """ - The benefit of this is automatic name detection, and correct application + """The benefit of this is automatic name detection, and correct application of level and verbosity to the object. + + :param verbose: False by default + :type verbose: bool """ + def __init__(self, verbose=False): # Maybe we can think of a way to make this display the instance name or __unicode__? self._log = logging.getLogger(type(self).__name__) @@ -78,11 +80,18 @@ class DSLogging(object): class DSLdapObject(DSLogging): + """A single instance of DSLdapObjects + + :param instance: A instance + :type instance: lib389.DirSrv + :param dn: Entry DN + :type dn: str + :param batch: Not implemented + :type batch: bool + """ # TODO: Automatically create objects when they are requested to have properties added def __init__(self, instance, dn=None, batch=False): - """ - """ self._instance = instance super(DSLdapObject, self).__init__(self._instance.verbose) # This allows some factor objects to be overriden @@ -111,9 +120,19 @@ class DSLdapObject(DSLogging): return self.__unicode__() def raw_entry(self): + """Get an Entry object + + :returns: Entry object + """ + return self._instance.getEntry(self._dn) def exists(self): + """Check if the entry exists + + :returns: True if it exists + """ + try: self._instance.search_s(self._dn, ldap.SCOPE_BASE, attrsonly=1) except ldap.NO_SUCH_OBJECT: @@ -122,10 +141,20 @@ class DSLdapObject(DSLogging): return True def display(self): + """Get an entry but represent it as a string LDIF + + :returns: LDIF formatted string + """ + e = self._instance.getEntry(self._dn) return e.__repr__() def display_attr(self, attr): + """Get all values of given attribute - 'attr: value' + + :returns: Formatted string + """ + out = "" for v in self.get_attr_vals_utf8(attr): out += "%s: %s\n" % (attr, v) @@ -145,14 +174,14 @@ class DSLdapObject(DSLogging): return response def __getattr__(self, name): - """ - This enables a bit of magic to allow us to wrap any function ending with + """This enables a bit of magic to allow us to wrap any function ending with _json to it's form without json, then transformed. It means your function *must* return it's values as a dict of: { attr : [val, val, ...], attr : [], ... } to be supported. """ + if (name.endswith('_json')): int_name = name.replace('_json', '') pfunc = partial(self._jsonify, fn=getattr(self, int_name)) @@ -161,17 +190,34 @@ class DSLdapObject(DSLogging): # We make this a property so that we can over-ride dynamically if needed @property def dn(self): + """Get an object DN + + :returns: DN + """ + return self._dn @property def rdn(self): + """Get an object RDN + + :returns: RDN + """ + # How can we be sure this returns the primary one? return ensure_str(self.get_attr_val(self._rdn_attribute)) def present(self, attr, value=None): + """Assert that some attr, or some attr / value exist on the entry. + + :param attr: an attribute name + :type attr: str + :param value: an attribute value + :type value: str + + :returns: True if attr is present """ - Assert that some attr, or some attr / value exist on the entry. - """ + if self._instance.state != DIRSRV_STATE_ONLINE: raise ValueError("Invalid state. Cannot get presence on instance that is not ONLINE") self._log.debug("%s present(%r) %s" % (self._dn, attr, value)) @@ -183,15 +229,37 @@ class DSLdapObject(DSLogging): return e.hasValue(attr, value) def add(self, key, value): + """Add an attribute with a value + + :param key: an attribute name + :type key: str + :param value: an attribute value + :type value: str + """ + self.set(key, value, action=ldap.MOD_ADD) # Basically what it means; def replace(self, key, value): + """Replace an attribute with a value + + :param key: an attribute name + :type key: str + :param value: an attribute value + :type value: str + """ self.set(key, value, action=ldap.MOD_REPLACE) # This needs to work on key + val, and key def remove(self, key, value): - """Remove a value defined by key""" + """Remove a value defined by key + + :param key: an attribute name + :type key: str + :param value: an attribute value + :type value: str + """ + # Do a mod_delete on the value. self.set(key, value, action=ldap.MOD_DELETE) @@ -200,12 +268,31 @@ class DSLdapObject(DSLogging): If an attribute is multi-valued AND required all values except one will be deleted. + + :param key: an attribute name + :type key: str """ + for val in self.get_attr_vals(key): self.remove(key, val) # maybe this could be renamed? def set(self, key, value, action=ldap.MOD_REPLACE): + """Perform a specified action on a key with value + + :param key: an attribute name + :type key: str + :param value: an attribute value + :type value: str + :param action: - ldap.MOD_REPLACE - by default + - ldap.MOD_ADD + - ldap.MOD_DELETE + :type action: int + + :returns: result of modify_s operation + :raises: ValueError - if instance is not online + """ + self._log.debug("%s set(%r, %r)" % (self._dn, key, value)) if self._instance.state != DIRSRV_STATE_ONLINE: raise ValueError("Invalid state. Cannot set properties on instance that is not ONLINE.") @@ -224,10 +311,11 @@ class DSLdapObject(DSLogging): def apply_mods(self, mods): """Perform modification operation using several mods at once - @param mods - list of tuples: [(action, key, value),] - @raise ValueError - if a provided mod op is invalid - @raise LDAPError + :param mods: [(action, key, value),] + :type mods: list of tuples + :raises: ValueError - if a provided mod op is invalid """ + mod_list = [] for mod in mods: if len(mod) < 2: @@ -256,18 +344,28 @@ class DSLdapObject(DSLogging): @classmethod def compare(cls, obj1, obj2): - """ - Compare if two RDN objects have same attributes and values. + """Compare if two RDN objects have same attributes and values. + This comparison is a loose comparison, not a strict one i.e. "this object *is* this other object" It will just check if the attributes are same. 'nsUniqueId' attribute is not checked intentionally because we want to compare arbitrary objects i.e they may have different 'nsUniqueId' but same attributes. - Example: + + Example:: + cn=user1,ou=a cn=user1,ou=b + Comparision of these two objects should result in same, even though their 'nsUniqueId' attribute differs. - This function returns 'True' if objects have same attributes else returns 'False' + + :param obj1: An entry to check + :type obj1: lib389._mapped_object.DSLdapObject + :param obj2: An entry to check + :type obj2: lib389._mapped_object.DSLdapObject + :returns: True if objects have same attributes else returns False + :raises: ValueError - if obj1 or obj2 don't inherit DSLdapObject """ + # ensuring both the objects are RDN objects if not issubclass(type(obj1), DSLdapObject) or not issubclass(type(obj2), DSLdapObject): raise ValueError("Invalid arguments: Expecting object types that inherits 'DSLdapObject' class") @@ -287,9 +385,10 @@ class DSLdapObject(DSLogging): return True def get_compare_attrs(self): + """Get a dictionary having attributes to be compared + i.e. excluding self._compare_exclude """ - Get a dictionary having attributes to be compared i.e. excluding self._compare_exclude - """ + self._log.debug("%s get_compare_attrs" % (self._dn)) all_attrs_dict = self.get_all_attrs() # removing _compate_exclude attrs from all attrs @@ -298,9 +397,11 @@ class DSLdapObject(DSLogging): return compare_attrs_dict def get_all_attrs(self): + """Get a dictionary having all the attributes of the entry + + :returns: Dict with real attributes and operational attributes """ - Get a dictionary having all the attributes i.e. real attributes + operational attributes - """ + self._log.debug("%s get_all_attrs" % (self._dn)) if self._instance.state != DIRSRV_STATE_ONLINE: raise ValueError("Invalid state. Cannot get properties on instance that is not ONLINE") @@ -320,7 +421,6 @@ class DSLdapObject(DSLogging): return entry.getValuesSet(keys) def get_attr_vals(self, key): - """Get an attribute's values from the dn""" self._log.debug("%s get_attr_vals(%r)" % (self._dn, key)) # We might need to add a state check for NONE dn. if self._instance.state != DIRSRV_STATE_ONLINE: @@ -334,7 +434,6 @@ class DSLdapObject(DSLogging): return entry.getValues(key) def get_attr_val(self, key): - """Get a single attribute value from the dn""" self._log.debug("%s getVal(%r)" % (self._dn, key)) # We might need to add a state check for NONE dn. if self._instance.state != DIRSRV_STATE_ONLINE: @@ -346,21 +445,69 @@ class DSLdapObject(DSLogging): return entry.getValue(key) def get_attr_val_bytes(self, key): + """Get a single attribute value from the entry in bytes type + + :param key: An attribute name + :type key: str + :returns: A single bytes value + :raises: ValueError - if instance is offline + """ + return ensure_bytes(self.get_attr_val(key)) def get_attr_vals_bytes(self, key): + """Get attribute values from the entry in bytes type + + :param key: An attribute name + :type key: str + :returns: A single bytes value + :raises: ValueError - if instance is offline + """ + return ensure_list_bytes(self.get_attr_vals(key)) def get_attr_val_utf8(self, key): + """Get a single attribute value from the entry in utf8 type + + :param key: An attribute name + :type key: str + :returns: A single bytes value + :raises: ValueError - if instance is offline + """ + return ensure_str(self.get_attr_val(key)) def get_attr_vals_utf8(self, key): + """Get attribute values from the entry in utf8 type + + :param key: An attribute name + :type key: str + :returns: A single bytes value + :raises: ValueError - if instance is offline + """ + return ensure_list_str(self.get_attr_vals(key)) def get_attr_val_int(self, key): + """Get a single attribute value from the entry in int type + + :param key: An attribute name + :type key: str + :returns: A single bytes value + :raises: ValueError - if instance is offline + """ + return ensure_int(self.get_attr_val(key)) def get_attr_vals_int(self, key): + """Get attribute values from the entry in int type + + :param key: An attribute name + :type key: str + :returns: A single bytes value + :raises: ValueError - if instance is offline + """ + return ensure_list_int(self.get_attr_vals(key)) # Duplicate, but with many values. IE a dict api. @@ -377,32 +524,40 @@ class DSLdapObject(DSLogging): # If the account can be bound to, this will attempt to do so. We don't check # for exceptions, just pass them back! def bind(self, password=None, *args, **kwargs): + """Open a new connection and bind with the entry. + You can pass arguments that will be passed to openConnection. + + :param password: An entry password + :type password: str + :returns: Connection with a binding as the entry + """ + conn = self._instance.openConnection(*args, **kwargs) conn.simple_bind_s(self.dn, password) return conn def delete(self): - """ - Deletes the object defined by self._dn. + """Deletes the object defined by self._dn. This can be changed with the self._protected flag! """ + self._log.debug("%s delete" % (self._dn)) if not self._protected: # Is there a way to mark this as offline and kill it self._instance.delete_s(self._dn) def _validate(self, rdn, properties, basedn): - """ - Used to validate a create request. + """Used to validate a create request. This way, it can be over-ridden without affecting - the create types + the create types. It also checks that all the values in _must_attribute exist - in some form in the dictionary + in some form in the dictionary. It has the useful trick of returning the dn, so subtypes can use extra properties to create the dn's here for this. """ + if properties is None: raise ldap.UNWILLING_TO_PERFORM('Invalid request to create. Properties cannot be None') if type(properties) != dict: @@ -457,6 +612,18 @@ class DSLdapObject(DSLogging): return (tdn, str_props) def create(self, rdn=None, properties=None, basedn=None): + """Add a new entry + + :param rdn: RDN of the new entry + :type rdn: str + :param properties: Attributes for the new entry + :type properties: dict + :param basedn: Base DN of the new entry + :type rdn: str + + :returns: DSLdapObject of the created entry + """ + assert(len(self._create_objectclasses) > 0) basedn = ensure_str(basedn) self._log.debug('Creating "%s" under %s : %s' % (rdn, basedn, properties)) @@ -476,23 +643,24 @@ class DSLdapObject(DSLogging): return self def lint(self): - """ - Override this to create a linter for a type. This means that we can detect + """Override this to create a linter for a type. This means that we can detect and report common administrative errors in the server from our cli and rest tools. - The structure of a result is: - { - dsle: ''. dsle == ds lint error. Will be a code unique to + The structure of a result is:: + + { + dsle: ''. dsle == ds lint error. Will be a code unique to this module for the error, IE DSBLE0001. - severity: '[HIGH:MEDIUM:LOW]'. severity of the error. - items: '(dn,dn,dn)'. List of affected DNs or names. - detail: 'msg ...'. An explination of the error. - fix: 'msg ...'. Steps to resolve the error. - } + severity: '[HIGH:MEDIUM:LOW]'. severity of the error. + items: '(dn,dn,dn)'. List of affected DNs or names. + detail: 'msg ...'. An explination of the error. + fix: 'msg ...'. Steps to resolve the error. + } - You should return an array of these dicts, on None if there are no errors. + :returns: An array of these dicts, on None if there are no errors. """ + if not self._lint_functions: return None results = [] @@ -505,6 +673,16 @@ class DSLdapObject(DSLogging): # A challenge of this, is how do we manage indexes? They have two naming attribunes.... class DSLdapObjects(DSLogging): + """The object represents the next idea: "Everything is an instance of something + that exists in this way", i.e. we unite LDAP entries by some + set of parameters with the object. + + :param instance: A instance + :type instance: lib389.DirSrv + :param batch: Not implemented + :type batch: bool + """ + def __init__(self, instance, batch=False): self._childobject = DSLdapObject self._instance = instance @@ -530,6 +708,12 @@ class DSLdapObjects(DSLogging): return self._childobject(instance=self._instance, dn=dn, batch=self._batch) def list(self): + """Get a list of children entries (DSLdapObject, Replica, etc.) using a base DN + and objectClasses of our object (DSLdapObjects, Replicas, etc.) + + :returns: A list of children entries + """ + # Filter based on the objectclasses and the basedn insts = None # This will yield and & filter for objectClass with as many terms as needed. @@ -550,6 +734,17 @@ class DSLdapObjects(DSLogging): return insts def get(self, selector=[], dn=None): + """Get a child entry (DSLdapObject, Replica, etc.) with dn or selector + using a base DN and objectClasses of our object (DSLdapObjects, Replicas, etc.) + + :param dn: DN of wanted entry + :type dn: str + :param selector: An additional filter to objectClasses, i.e. 'backend_name' + :type dn: str + + :returns: A child entry + """ + results = [] if dn is not None: results = self._get_dn(dn) @@ -594,9 +789,8 @@ class DSLdapObjects(DSLogging): ) def _validate(self, rdn, properties): - """ - Validate the factory part of the creation - """ + """Validate the factory part of the creation""" + if properties is None: raise ldap.UNWILLING_TO_PERFORM('Invalid request to create. Properties cannot be None') if type(properties) != dict: @@ -618,7 +812,16 @@ class DSLdapObjects(DSLogging): return (rdn, properties) def create(self, rdn=None, properties=None): - # Create the object + """Create an object under base DN of our entry + + :param rdn: RDN of the new entry + :type rdn: str + :param properties: Attributes for the new entry + :type properties: dict + + :returns: DSLdapObject of the created entry + """ + # Should we inject the rdn to properties? # This may not work in all cases, especially when we consider plugins. # diff --git a/src/lib389/lib389/aci.py b/src/lib389/lib389/aci.py index c377486..1f0725a 100644 --- a/src/lib389/lib389/aci.py +++ b/src/lib389/lib389/aci.py @@ -18,9 +18,8 @@ from lib389._constants import * # Helpers to detect common patterns in aci def _aci_any_targetattr_ne(aci): - """ - Returns if any of the targetattr types is a != type - """ + """Returns True if any of the targetattr types is a != type""" + potential = False if 'targetattr' in aci.acidata: for ta in aci.acidata['targetattr']: @@ -31,19 +30,29 @@ def _aci_any_targetattr_ne(aci): class Aci(object): + """An object that helps to work with agreement entry + + :param conn: An instance + :type conn: lib389.DirSrv + """ + def __init__(self, conn): - """ - """ self.conn = conn self.log = conn.log def list(self, basedn, scope=ldap.SCOPE_SUBTREE): - """ - List all acis in the directory server below the basedn confined by + """List all acis in the directory server below the basedn confined by scope. - A set of EntryAcis is returned. + :param basedn: Base DN + :type basedn: str + :param scope: ldap.SCOPE_SUBTREE, ldap.SCOPE_BASE, + ldap.SCOPE_ONELEVEL, ldap.SCOPE_SUBORDINATE + :type scope: int + + :returns: A list of EntryAci objects """ + acis = [] rawacientries = self.conn.search_s(basedn, scope, 'aci=*', ['aci']) for rawacientry in rawacientries: @@ -51,24 +60,30 @@ class Aci(object): return acis def lint(self, basedn, scope=ldap.SCOPE_SUBTREE): - """ - Validate and check for potential aci issues. + """Validate and check for potential aci issues. Given a scope and basedn, this will retrieve all the aci's below. A number of checks are then run on the aci in isolation, and in groups. - returns a tuple of (bool, list( dict )) - - bool represents if the acis pass or fail as a whole. - the list contains a list of warnings about your acis. - the dict is structured as: - { - name: "" # DSALEXXXX - severity: "" # LOW MEDIUM HIGH - detail: "" # explination - } + :param basedn: Base DN + :type basedn: str + :param scope: ldap.SCOPE_SUBTREE, ldap.SCOPE_BASE, + ldap.SCOPE_ONELEVEL, ldap.SCOPE_SUBORDINATE + :type scope: int + + :returns: A tuple of (bool, list( dict )) + - Bool represents if the acis pass or fail as a whole. + - The list contains a list of warnings about your acis. + - The dict is structured as:: + + { + name: "" # DSALEXXXX + severity: "" # LOW MEDIUM HIGH + detail: "" # explination + } """ + result = True # Not thread safe!!! self.warnings = [] @@ -85,9 +100,14 @@ class Aci(object): return (result, self.warnings) def format_lint(self, warnings): + """Takes the array of warnings and returns a formatted string. + + :param warnings: The array of warnings + :type warnings: dict + + :returns: Formatted string or warnings """ - Takes the array of warnings and returns a formatted string. - """ + buf = "-------------------------------------------------------------------------------" for warning in warnings: @@ -113,8 +133,7 @@ Advice: {FIX} # These are the aci lint checks. def _lint_dsale_0001_ne_internal(self, acis): - """ - Check for the presence of "not equals" attributes that will inadvertantly + """Check for the presence of "not equals" attributes that will inadvertantly allow the return / modification of internal attributes. """ @@ -151,8 +170,7 @@ Convert the aci to the form "(targetAttr="x || y || z")". ) def _lint_dsale_0002_ne_mult_subtree(self, acis): - """ - This check will show pairs or more of aci that match the same subtree + """This check will show pairs or more of aci that match the same subtree with a != rute. These can cause the other rule to be invalidated! """ diff --git a/src/lib389/lib389/agreement.py b/src/lib389/lib389/agreement.py index eb7afc7..822f9a3 100644 --- a/src/lib389/lib389/agreement.py +++ b/src/lib389/lib389/agreement.py @@ -19,13 +19,18 @@ from lib389 import Entry, DirSrv, NoSuchEntryError, InvalidArgumentError class Agreement(object): + """An object that helps to work with agreement entry + + :param conn: An instance + :type conn: lib389.DirSrv + """ + ALWAYS = '0000-2359 0123456' NEVER = '2358-2359 0' proxied_methods = 'search_s getEntry'.split() def __init__(self, conn): - """@param conn - a DirSrv instance""" self.conn = conn self.log = conn.log @@ -34,26 +39,33 @@ class Agreement(object): return DirSrv.__getattr__(self.conn, name) def status(self, agreement_dn, just_status=False): - """Return a formatted string with the replica status. Looking like: - Status for meTo_localhost.localdomain:50389 agmt - localhost.localdomain:50389 - Update in progress: TRUE - Last Update Start: 20131121132756Z - Last Update End: 0 - Num. Changes Sent: 1:10/0 - Num. changes Skipped: None - Last update Status: 0 Replica acquired successfully: - Incremental update started - Init in progress: None - Last Init Start: 0 - Last Init End: 0 - Last Init Status: None - Reap Active: 0 - @param agreement_dn - DN of the replication agreement - - @returns string containing the status of the replica agreement - - @raise NoSuchEntryError - if agreement_dn is an unknown entry + """Get a formatted string with the replica status + + :param agreement_dn: DN of the replica agreement + :type agreement_dn: str + :param just_status: If True, returns just status + :type just_status: bool + + :returns: str -- See below + :raises: NoSuchEntryError - if agreement_dn is an unknown entry + + :example: + :: + + Status for meTo_localhost.localdomain:50389 agmt + localhost.localdomain:50389 + Update in progress: TRUE + Last Update Start: 20131121132756Z + Last Update End: 0 + Num. Changes Sent: 1:10/0 + Num. changes Skipped: None + Last update Status: 0 Replica acquired successfully: + Incremental update started + Init in progress: None + Last Init Start: 0 + Last Init End: 0 + Last Init Status: None + Reap Active: 0 """ attrlist = ['cn', 'nsds5BeginReplicaRefresh', 'nsds5ReplicaRoot', @@ -103,19 +115,20 @@ class Agreement(object): return result def _check_interval(self, interval): - ''' - Check the interval for schedule replication is valid: - HH [0..23] - MM [0..59] - DAYS [0-6]{1,7} - - @param interval - interval in the format 'HHMM-HHMM D+' - (D is day number [0-6]) - - @return None + """Check the interval for schedule replication is valid: + HH [0..23] + MM [0..59] + DAYS [0-6]{1,7} + + :param interval: - 'HHMM-HHMM D+' With D=[0123456]+ + - Agreement.ALWAYS + - Agreement.NEVER + :type interval: str + + :returns: None + :raises: ValueError - if the interval is illegal + """ - @raise ValueError - if the inteval is illegal - ''' c = re.compile(re.compile('^([0-9][0-9])([0-9][0-9])-([0-9][0-9])' + '([0-9][0-9]) ([0-6]{1,7})$')) if not c.match(interval): @@ -148,16 +161,17 @@ class Agreement(object): def schedule(self, agmtdn=None, interval=ALWAYS): """Schedule the replication agreement - @param agmtdn - DN of the replica agreement - @param interval - in the form - - Agreement.ALWAYS - - Agreement.NEVER - - or 'HHMM-HHMM D+' With D=[0123456]+ - @return - None + :param agmtdn: DN of the replica agreement + :type agmtdn: str + :param interval: - 'HHMM-HHMM D+' With D=[0123456]+ + - Agreement.ALWAYS + - Agreement.NEVER + :type interval: str - @raise ValueError - if interval is not valid - ldap.NO_SUCH_OBJECT - if agmtdn does not exist + :returns: None + :raises: - ValueError - if interval is not valid; + - ldap.NO_SUCH_OBJECT - if agmtdn does not exist """ if not agmtdn: raise InvalidArgumentError("agreement DN is missing") @@ -178,37 +192,25 @@ class Agreement(object): self.conn.modify_s(agmtdn, mod) def getProperties(self, agmnt_dn=None, properties=None): - ''' - returns a dictionary of the requested properties. - If properties is missing, it returns all the properties. - @param agmtdn - is the replica agreement DN - @param properties - is the list of properties name - Supported properties are - RA_NAME - RA_SUFFIX - RA_BINDDN - RA_BINDPW - RA_METHOD - RA_DESCRIPTION - RA_SCHEDULE - RA_TRANSPORT_PROT - RA_FRAC_EXCLUDE - RA_FRAC_EXCLUDE_TOTAL_UPDATE - RA_FRAC_STRIP - RA_CONSUMER_PORT - RA_CONSUMER_HOST - RA_CONSUMER_TOTAL_INIT - RA_TIMEOUT - RA_CHANGES - - @return - returns a dictionary of the properties - - @raise ValueError - if invalid property name - ldap.NO_SUCH_OBJECT - if agmtdn does not exist - InvalidArgumentError - missing mandatory argument - - - ''' + """Get a dictionary of the requested properties. + If properties parameter is missing, it returns all the properties. + + :param agmtdn: DN of the replica agreement + :type agmtdn: str + :param properties: List of properties name + :type properties: list + + :returns: Returns a dictionary of the properties + :raises: - ValueError - if invalid property name + - ldap.NO_SUCH_OBJECT - if agmtdn does not exist + - InvalidArgumentError - missing mandatory argument + + :supported properties are: + RA_NAME, RA_SUFFIX, RA_BINDDN, RA_BINDPW, RA_METHOD, + RA_DESCRIPTION, RA_SCHEDULE, RA_TRANSPORT_PROT, RA_FRAC_EXCLUDE, + RA_FRAC_EXCLUDE_TOTAL_UPDATE, RA_FRAC_STRIP, RA_CONSUMER_PORT, + RA_CONSUMER_HOST, RA_CONSUMER_TOTAL_INIT, RA_TIMEOUT, RA_CHANGES + """ if not agmnt_dn: raise InvalidArgumentError("agmtdn is a mandatory argument") @@ -248,45 +250,33 @@ class Agreement(object): def setProperties(self, suffix=None, agmnt_dn=None, agmnt_entry=None, properties=None): - ''' - Set the properties of the agreement. If an 'agmnt_entry' (Entry) - is provided, it updates the entry, else it updates the entry on - the server. If the 'agmnt_dn' is provided it retrieves the entry - using it, else it retrieve the agreement using the 'suffix'. - - @param suffix : suffix stored in that agreement (online update) - @param agmnt_dn: DN of the agreement (online update) - @param agmnt_entry: Entry of a agreement (offline update) - @param properties: dictionary of properties - Supported properties are - RA_NAME - RA_SUFFIX - RA_BINDDN - RA_BINDPW - RA_METHOD - RA_DESCRIPTION - RA_SCHEDULE - RA_TRANSPORT_PROT - RA_FRAC_EXCLUDE - RA_FRAC_EXCLUDE_TOTAL_UPDATE - RA_FRAC_STRIP - RA_CONSUMER_PORT - RA_CONSUMER_HOST - RA_CONSUMER_TOTAL_INIT - RA_TIMEOUT - RA_CHANGES - - - @return None - - @raise ValueError: if unknown properties - ValueError: if invalid agreement_entry - ValueError: if agmnt_dn or suffix are not associated to a - replica - InvalidArgumentError: If missing mandatory parameter - - - ''' + """Set the properties of the agreement entry. If an 'agmnt_entry' + is provided, it updates the entry, else it updates the entry on + the server. If the 'agmnt_dn' is provided it retrieves the entry + using it, else it retrieve the agreement using the 'suffix'. + + :param suffix: Suffix stored in that agreement (online update) + :type suffix: str + :param agmnt_dn: DN of the agreement (online update) + :type agmnt_dn: str + :param agmnt_entry: Entry of a agreement (offline update) + :type agmnt_entry: lib389.Entry + :param properties: Dictionary of properties + :type properties: dict + + :returns: None + :raises: - ValueError - if invalid properties + - ValueError - if invalid agreement_entry + - ValueError - if agmnt_dn or suffix are not associated to a replica + - InvalidArgumentError - missing mandatory argument + + :supported properties are: + RA_NAME, RA_SUFFIX, RA_BINDDN, RA_BINDPW, RA_METHOD, + RA_DESCRIPTION, RA_SCHEDULE, RA_TRANSPORT_PROT, RA_FRAC_EXCLUDE, + RA_FRAC_EXCLUDE_TOTAL_UPDATE, RA_FRAC_STRIP, RA_CONSUMER_PORT, + RA_CONSUMER_HOST, RA_CONSUMER_TOTAL_INIT, RA_TIMEOUT, RA_CHANGES + """ + # No properties provided if len(properties) == 0: return @@ -355,34 +345,37 @@ class Agreement(object): def list(self, suffix=None, consumer_host=None, consumer_port=None, agmtdn=None): - ''' - Returns the search result of the replica agreement(s) under the - replica (replicaRoot is 'suffix'). - - Either 'suffix' or 'agmtdn' need to be specfied. - 'consumer_host' and 'consumer_port' are either not specified or - specified both. - - If 'agmtdn' is specified, it returns the search result entry of - that replication agreement. - else if consumer host/port are specified it returns the replica - agreements toward that consumer host:port. - Finally if neither 'agmtdn' nor 'consumser host/port' are - specifies it returns all the replica agreements under the replica - (replicaRoot is 'suffix'). - - @param - suffix is the suffix targeted by the total update - @param - consumer_host hostname of the consumer - @param - consumer_port port of the consumer - @param - agmtdn DN of the replica agreement - - @return - search result of the replica agreements - - @raise - InvalidArgument: if missing mandatory argument - (agmtdn or suffix, then host and port) - - ValueError - if some properties are not valid - - NoSuchEntryError - If no replica defined for the suffix - ''' + """Returns the search result of the replica agreement(s) under the + replica (replicaRoot is 'suffix'). + + Either 'suffix' or 'agmtdn' need to be specfied. + 'consumer_host' and 'consumer_port' are either not specified or + specified both. + + If 'agmtdn' is specified, it returns the search result entry of + that replication agreement, else if consumer host/port are specified + it returns the replica agreements toward that consumer host:port. + + Finally if neither 'agmtdn' nor 'consumser host/port' are + specifies it returns all the replica agreements under the replica + (replicaRoot is 'suffix'). + + :param suffix: The suffix targeted by the total update + :type suffix: str + :param consumer_host: Hostname of the consumer + :type consumer_host: str + :param consumer_port: Port of the consumer + :type consumer_port: int + :param agmtdn: DN of the replica agreement + :type agmtdn: str + + :returns: Search result of the replica agreements + :raises: - InvalidArgument - if missing mandatory argument + (agmtdn or suffix, then host and port) + - ValueError - if some properties are not valid + - NoSuchEntryError - If no replica defined for the suffix + """ + if not suffix and not agmtdn: raise InvalidArgumentError("suffix or agmtdn are required") @@ -427,19 +420,23 @@ class Agreement(object): def create(self, suffix=None, host=None, port=None, properties=None, winsync=False): """Create (and return) a replication agreement from self to consumer. - - self is the supplier, - - @param suffix - Replication Root - @param host - Consumer host - @param port - Consumer port - @param winsync - Identifies the agree as a WinSync agreement - @param properties - Agreement properties - @return dn_agreement - DN of the created agreement - @raise InvalidArgumentError - If the suffix is missing - @raise NoSuchEntryError - if a replica doesn't exist for that - suffix - @raise ldap.LDAPError - ldap error - + Self is the supplier. + + :param suffix: Replication Root + :type suffix: str + :param host: Consumer host + :type host: str + :param port: Consumer port + :type port: int + :param winsync: Identifies the agree as a WinSync agreement + :type winsync: bool + :param properties: Agreement properties + :type properties: dict + + :returns: DN of the created agreement + :raises: - InvalidArgumentError - If the suffix is missing + - NoSuchEntryError - if a replica doesn't exist for that suffix + - ldap.LDAPError - ldap error """ # Check we have a suffix [ mandatory ] @@ -553,14 +550,19 @@ class Agreement(object): agmtdn=None): """Delete a replication agreement - @param suffix - the suffix that the agreement is configured for - @param consumer_host - of the server that the agreement points to - @param consumer_port - of the server that the agreement points to - @param agmtdn - DN of the replica agreement - - @raise ldap.LDAPError - for ldap operation failures - @raise TypeError - if too many agreements were found - @raise NoSuchEntryError - if no agreements were found + :param suffix: The suffix that the agreement is configured for + :type suffix: str + :param consumer_host: Host of the server that the agreement points to + :type consumer_host: str + :param consumer_port: Port of the server that the agreement points to + :type consumer_port: int + :param agmtdn: DN of the replica agreement + :type agmtdn: str + + :returns: None + :raises: - ldap.LDAPError - for ldap operation failures + - TypeError - if too many agreements were found + - NoSuchEntryError - if no agreements were found """ if not (suffix and consumer_host and consumer_port) and not agmtdn: @@ -587,17 +589,21 @@ class Agreement(object): def init(self, suffix=None, consumer_host=None, consumer_port=None): """Trigger a total update of the consumer replica - - self is the supplier, - - consumer is a DirSrv object (consumer can be a master) - - cn_format - use this string to format the agreement name - @param - suffix is the suffix targeted by the total update - [mandatory] - @param - consumer_host hostname of the consumer [mandatory] - @param - consumer_port port of the consumer [mandatory] - - @raise InvalidArgument: if missing mandatory argurment - (suffix/host/port) + - self is the supplier, + - consumer is a DirSrv object (consumer can be a master) + - cn_format - use this string to format the agreement name + + :param suffix: The suffix targeted by the total update [mandatory] + :type suffix: str + :param consumer_host: Hostname of the consumer [mandatory] + :type consumer_host: str + :param consumer_port: Port of the consumer [mandatory] + :type consumer_port: int + + :returns: None + :raises: InvalidArgument - if missing mandatory argument """ + # # check the required parameters are set # @@ -653,17 +659,25 @@ class Agreement(object): def pause(self, agmtdn, interval=NEVER): """Pause this replication agreement. This replication agreement - will send no more changes. Use the resume() method to "unpause". - It tries to disable the replica agreement. If it fails (not - implemented in all version), - It uses the schedule() with interval '2358-2359 0' - @param agmtdn - agreement dn - @param interval - (default NEVER) replication schedule to use + will send no more changes. Use the resume() method to "unpause". + + It tries to disable the replica agreement. If it fails (not + implemented in all version), - @return None + It uses the schedule() with interval '2358-2359 0' - @raise ValueError - if interval is not valid + :param agmtdn: agreement dn + :type agmtdn: str + :param interval: - 'HHMM-HHMM D+' With D=[0123456]+ + - Agreement.ALWAYS + - Agreement.NEVER + - Default is NEVER + :type interval: str + + :returns: None + :raises: ValueError - if interval is not valid """ + self.log.info("Pausing replication %s" % agmtdn) mod = [( ldap.MOD_REPLACE, 'nsds5ReplicaEnabled', ['off'])] @@ -678,19 +692,24 @@ class Agreement(object): time.sleep(5) def resume(self, agmtdn, interval=ALWAYS): - """Resume a paused replication agreement, paused with the "pause" - method. - It tries to enabled the replica agreement. If it fails - (not implemented in all versions), - it uses the schedule() with interval '0000-2359 0123456' - @param agmtdn - agreement dn - @param interval - (default ALWAYS) replication schedule to use - - @return None - - @raise ValueError - if interval is not valid - ldap.NO_SUCH_OBJECT - if agmtdn does not exist + """Resume a paused replication agreement, paused with the "pause" method. + It tries to enabled the replica agreement. If it fails + (not implemented in all versions) + It uses the schedule() with interval '0000-2359 0123456'. + + :param agmtdn: agreement dn + :type agmtdn: str + :param interval: - 'HHMM-HHMM D+' With D=[0123456]+ + - Agreement.ALWAYS + - Agreement.NEVER + - Default is NEVER + :type interval: str + + :returns: None + :raises: - ValueError - if interval is not valid + - ldap.NO_SUCH_OBJECT - if agmtdn does not exist """ + self.log.info("Resuming replication %s" % agmtdn) mod = [( ldap.MOD_REPLACE, 'nsds5ReplicaEnabled', ['on'])] @@ -705,7 +724,16 @@ class Agreement(object): time.sleep(2) def changes(self, agmnt_dn): - """Return a list of changes sent by this agreement.""" + """Get a number of changes sent by this agreement. + + :param agmtdn: agreement dn + :type agmtdn: str + + :returns: Number of changes + :raises: NoSuchEntryError - if agreement entry with changes + attribute is not found + """ + retval = 0 try: ent = self.conn.getEntry(ensure_str(agmnt_dn), ldap.SCOPE_BASE, diff --git a/src/lib389/lib389/backend.py b/src/lib389/lib389/backend.py index 75443b2..6f4c869 100644 --- a/src/lib389/lib389/backend.py +++ b/src/lib389/lib389/backend.py @@ -386,6 +386,18 @@ class BackendLegacy(object): class Backend(DSLdapObject): + """Backend DSLdapObject with: + - must attributes = ['cn, 'nsslapd-suffix'] + - RDN attribute is 'cn' + + :param instance: A instance + :type instance: lib389.DirSrv + :param dn: Entry DN + :type dn: str + :param batch: Not implemented + :type batch: bool + """ + _must_attributes = ['nsslapd-suffix', 'cn'] def __init__(self, instance, dn=None, batch=False): @@ -399,6 +411,12 @@ class Backend(DSLdapObject): self._mts = MappingTrees(self._instance) def create_sample_entries(self, version): + """Creates sample entries under nsslapd-suffix value + + :param version: Sample entries version, i.e. 001003006 + :type version: str + """ + self._log.debug('Creating sample entries at version %s....' % version) # Grab the correct sample entry config centries = get_sample_entries(version) @@ -446,6 +464,19 @@ class Backend(DSLdapObject): return (dn, valid_props) def create(self, dn=None, properties=None, basedn=None): + """Add a new backend entry, create mapping tree, + and, if requested, sample entries + + :param rdn: RDN of the new entry + :type rdn: str + :param properties: Attributes and parameters for the new entry + :type properties: dict + :param basedn: Base DN of the new entry + :type rdn: str + + :returns: DSLdapObject of the created entry + """ + sample_entries = properties.pop(BACKEND_SAMPLE_ENTRIES, False) # Okay, now try to make the backend. super(Backend, self).create(dn, properties, basedn) @@ -460,6 +491,13 @@ class Backend(DSLdapObject): return self def delete(self): + """Deletes the backend, it's mapping tree and all related indices. + This can be changed with the self._protected flag! + + :raises: - UnwillingToPerform - if backend is protected + - UnwillingToPerform - if nsslapd-state is not 'backend' + """ + if self._protected: raise ldap.UNWILLING_TO_PERFORM("This is a protected backend!") # First check if the mapping tree has our suffix still. @@ -476,7 +514,7 @@ class Backend(DSLdapObject): except ldap.NO_SUCH_OBJECT: # Righto, it's already gone! Do nothing ... pass - # Delete all our related indcies + # Delete all our related indices self._instance.index.delete_all(bename) # Now remove our children, this is all ldbm config @@ -485,13 +523,13 @@ class Backend(DSLdapObject): super(Backend, self).delete() def _lint_mappingtree(self): - """ - Backend lint + """Backend lint This should check for: * missing mapping tree entries for the backend - * missing indcies if we are local and have log access? + * missing indices if we are local and have log access? """ + # Check for the missing mapping tree. suffix = self.get_attr_val_utf8('nsslapd-suffix') bename = self.get_attr_val_bytes('cn') @@ -506,18 +544,30 @@ class Backend(DSLdapObject): return None def get_monitor(self): + """Get a MonitorBackend(DSLdapObject) for the backend""" + monitor = MonitorBackend(instance=self._instance, dn= "cn=monitor,%s" % self._dn, batch=self._batch) return monitor def get_indexes(self): + """Get an Indexes(DSLdapObject) for the backend""" + indexes = Indexes(self._instance, basedn="cn=index,%s" % self._dn) return indexes - # Future: add reindex task for this be. -# This only does ldbm backends. Chaining backends are a special case -# of this, so they can be subclassed off. + class Backends(DSLdapObjects): + """DSLdapObjects that represents DN_LDBM base DN + This only does ldbm backends. Chaining backends are a special case + of this, so they can be subclassed off. + + :param instance: A instance + :type instance: lib389.DirSrv + :param batch: Not implemented + :type batch: bool + """ + def __init__(self, instance, batch=False): super(Backends, self).__init__(instance=instance, batch=batch) self._objectclasses = [BACKEND_OBJECTCLASS_VALUE] diff --git a/src/lib389/lib389/changelog.py b/src/lib389/lib389/changelog.py index 5affcee..65f7591 100644 --- a/src/lib389/lib389/changelog.py +++ b/src/lib389/lib389/changelog.py @@ -15,18 +15,35 @@ from lib389 import DirSrv, Entry, InvalidArgumentError class Changelog(object): + """An object that helps to work with changelog entry + + :param conn: An instance + :type conn: lib389.DirSrv + """ + proxied_methods = 'search_s getEntry'.split() def __init__(self, conn): - """@param conn - a DirSrv instance""" self.conn = conn self.log = conn.log + self.changelogdir = os.path.join(os.path.dirname(self.conn.dbdir), DEFAULT_CHANGELOG_DB) def __getattr__(self, name): if name in Changelog.proxied_methods: return DirSrv.__getattr__(self.conn, name) def list(self, suffix=None, changelogdn=DN_CHANGELOG): + """Get a changelog entry using changelogdn parameter + + :param suffix: Not used + :type suffix: str + :param changelogdn: DN of the changelog entry, DN_CHANGELOG by default + :type changelogdn: str + + :returns: Search result of the replica agreements. + Enpty list if nothing was found + """ + base = changelogdn filtr = "(objectclass=extensibleobject)" @@ -42,9 +59,11 @@ class Changelog(object): def create(self, dbname=DEFAULT_CHANGELOG_DB): """Add and return the replication changelog entry. - If dbname starts with "/" then it's considered a full path, - otherwise it's relative to self.dbdir + :param dbname: Database name, it will be used for creating + a changelog dir path + :type dbname: str """ + dn = DN_CHANGELOG attribute, changelog_name = dn.split(",")[0].split("=", 1) dirpath = os.path.join(os.path.dirname(self.conn.dbdir), dbname) @@ -60,13 +79,14 @@ class Changelog(object): self.conn.add_s(entry) except ldap.ALREADY_EXISTS: self.log.warn("entry %s already exists" % dn) - return(dn) + return dn def delete(self): - ''' - Delete the changelog entry - @raise LDAPError - ''' + """Delete the changelog entry + + :raises: LDAPError - failed to delete changelog entry + """ + try: self.conn.delete_s(DN_CHANGELOG) except ldap.LDAPError as e: @@ -74,6 +94,25 @@ class Changelog(object): raise def setProperties(self, changelogdn=None, properties=None): + """Set the properties of the changelog entry. + + :param changelogdn: DN of the changelog + :type changelogdn: str + :param properties: Dictionary of properties + :type properties: dict + + :returns: None + :raises: - ValueError - if invalid properties + - ValueError - if changelog entry is not found + - InvalidArgumentError - changelog DN is missing + + :supported properties are: + CHANGELOG_NAME, CHANGELOG_DIR, CHANGELOG_MAXAGE, + CHANGELOG_MAXENTRIES, CHANGELOG_TRIM_INTERVAL, + CHANGELOG_COMPACT_INTV, CHANGELOG_CONCURRENT_WRITES, + CHANGELOG_ENCRYPT_ALG, CHANGELOG_SYM_KEY + """ + if not changelogdn: raise InvalidArgumentError("changelog DN is missing") @@ -106,4 +145,10 @@ class Changelog(object): self.conn.modify_s(ents[0].dn, mods) def getProperties(self, changelogdn=None, properties=None): + """Get a dictionary of the requested properties. + If properties parameter is missing, it returns all the properties. + + NotImplemented + """ + raise NotImplemented diff --git a/src/lib389/lib389/mappingTree.py b/src/lib389/lib389/mappingTree.py index dcad9b3..f319276 100644 --- a/src/lib389/lib389/mappingTree.py +++ b/src/lib389/lib389/mappingTree.py @@ -377,7 +377,20 @@ class MappingTreeLegacy(object): else: raise InvalidArgumentError("entry or name are mandatory") + class MappingTree(DSLdapObject): + """Mapping tree DSLdapObject with: + - must attributes = ['cn'] + - RDN attribute is 'cn' + + :param instance: A instance + :type instance: lib389.DirSrv + :param dn: Entry DN + :type dn: str + :param batch: Not implemented + :type batch: bool + """ + _must_attributes = ['cn'] def __init__(self, instance, dn=None, batch=False): @@ -389,6 +402,14 @@ class MappingTree(DSLdapObject): class MappingTrees(DSLdapObjects): + """DSLdapObjects that presents Mapping trees + + :param instance: A instance + :type instance: lib389.DirSrv + :param batch: Not implemented + :type batch: bool + """ + def __init__(self, instance, batch=False): super(MappingTrees, self).__init__(instance=instance, batch=batch) self._objectclasses = [MT_OBJECTCLASS_VALUE] diff --git a/src/lib389/lib389/replica.py b/src/lib389/lib389/replica.py index 6d24a7a..cdd0a97 100644 --- a/src/lib389/lib389/replica.py +++ b/src/lib389/lib389/replica.py @@ -791,16 +791,21 @@ class ReplicaLegacy(object): class Replica(DSLdapObject): - """Replica object. There is one "replica" per backend""" + """Replica DSLdapObject with: + - must attributes = ['cn', 'nsDS5ReplicaType', 'nsDS5ReplicaRoot', + 'nsDS5ReplicaBindDN', 'nsDS5ReplicaId'] + - RDN attribute is 'cn' + - There is one "replica" per backend + + :param instance: A instance + :type instance: lib389.DirSrv + :param dn: Entry DN + :type dn: str + :param batch: Not implemented + :type batch: bool + """ def __init__(self, instance, dn=None, batch=False): - """Init the Replica object - - @param instance - a DirSrv object - @param dn - A DN of the replica entry - @param batch - NOT IMPLELMENTED - """ - super(Replica, self).__init__(instance, dn, batch) self._rdn_attribute = 'cn' self._must_attributes = ['cn', REPL_TYPE, @@ -815,8 +820,9 @@ class Replica(DSLdapObject): def _valid_role(role): """Return True if role is valid - @param role - A string containing the "role" name - @return - True if the role is a valid role name, otherwise return False + :param role: MASTER, HUB and CONSUMER + :type role: ReplicaRole + :returns: True if the role is a valid role object, otherwise return False """ if role != ReplicaRole.MASTER and \ @@ -830,9 +836,11 @@ class Replica(DSLdapObject): def _valid_rid(role, rid=None): """Return True if rid is valid for the replica role - @param role - A string containing the role name - @param rid - Only needed if the role is a "master" - @return - True is rid is valid, otherwise return False + :param role: MASTER, HUB and CONSUMER + :type role: ReplicaRole + :param rid: Only needed if the role is a MASTER + :type rid: int + :returns: True is rid is valid, otherwise return False """ if rid is None: @@ -851,13 +859,13 @@ class Replica(DSLdapObject): """Delete a replica related to the provided suffix. If this replica role was ReplicaRole.HUB or ReplicaRole.MASTER, it - also deletes the changelog associated to that replica. If it + also deletes the changelog associated to that replica. If it exists some replication agreement below that replica, they are deleted. - @return None - @raise InvalidArgumentError - if suffix is missing - ldap.LDAPError - for all other update failures + :returns: None + :raises: - InvalidArgumentError - if suffix is missing + - ldap.LDAPError - for all other update failures """ # Get the suffix @@ -884,7 +892,7 @@ class Replica(DSLdapObject): def deleteAgreements(self): """Delete all the agreements for the suffix - @raise LDAPError - If failing to delete or search for agreements + :raises: LDAPError - If failing to delete or search for agreeme :type binddn: strnts """ # Delete the agreements @@ -906,15 +914,15 @@ class Replica(DSLdapObject): def promote(self, newrole, binddn=None, rid=None): """Promote the replica to hub or master - @param newrole - The new replication role for the replica: - ReplicaRole.MASTER - ReplicaRole.HUB - @param binddn - The replication bind dn - only applied to master - @param rid - The replication ID, applies only to promotions to "master" + :param newrole: The new replication role for the replica: MASTER and HUB + :type newrole: ReplicaRole + :param binddn: The replication bind dn - only applied to master + :type binddn: str + :param rid: The replication ID, applies only to promotions to "master" + :type rid: int - @raise ldap.NO_SUCH_OBJECT - If suffix is not replicated - - @raise ValueError + :returns: None + :raises: ValueError - If replica is not promoted """ if not binddn: @@ -979,11 +987,11 @@ class Replica(DSLdapObject): def demote(self, newrole): """Demote a replica to a hub or consumer - @param suffix - The replication suffix - @param newrole - The new replication role of this replica - ReplicaRole.HUB - ReplicaRole.CONSUMER - @raise ValueError + :param newrole: The new replication role for the replica: CONSUMER and HUB + :type newrole: ReplicaRole + + :returns: None + :raises: ValueError - If replica is not demoted """ # Check the role type @@ -1012,9 +1020,9 @@ class Replica(DSLdapObject): raise ValueError('Failed to update replica: ' + str(e)) def get_role(self): - """Return the replica role: + """Return the replica role - @return: 3 for ReplicaRole.MASTER, 2 for ReplicaRole.HUB, 3 for ReplicaRole.CONSUMER + :returns: ReplicaRole.MASTER, ReplicaRole.HUB, ReplicaRole.CONSUMER """ repltype = self.get_attr_val_int(REPL_TYPE) @@ -1034,9 +1042,10 @@ class Replica(DSLdapObject): def check_init(self, agmtdn): """Check that a total update has completed - @returns tuple - first element is done/not done, 2nd is no error/has - error - @param agmtdn - the agreement dn + :param agmtdn: The agreement DN + :type agmtdn: str + + :returns: A tuple - first element is done/not done, 2nd is no error/has error THIS SHOULD BE IN THE NEW AGREEMENT CLASS """ @@ -1083,8 +1092,10 @@ class Replica(DSLdapObject): def wait_init(self, agmtdn): """Initialize replication and wait for completion. - @oaram agmtdn - agreement dn - @return - 0 if the initialization is complete + :param agmtdn: The agreement DN + :type agmtdn: str + + :returns: 0 if the initialization is complete THIS SHOULD BE IN THE NEW AGREEMENT CLASS """ @@ -1113,7 +1124,11 @@ class Replica(DSLdapObject): def start_async(self, agmtdn): """Initialize replication without waiting. - @param agmtdn - agreement dn + + :param agmtdn: The agreement DN + :type agmtdn: str + + :returns: None THIS SHOULD BE IN THE NEW AGREEMENT CLASS """ @@ -1124,9 +1139,10 @@ class Replica(DSLdapObject): def get_ruv_entry(self): """Return the database RUV entry - @return - The database RUV entry - @raise ValeuError - If suffix is not setup for replication - LDAPError - If there is a problem trying to search for the RUV + + :returns: The database RUV entry + :raises: ValeuError - If suffix is not setup for replication + LDAPError - If there is a problem trying to search for the RUV """ try: @@ -1144,10 +1160,12 @@ class Replica(DSLdapObject): """Make a "dummy" update on the the replicated suffix, and check all the provided replicas to see if they received the update. - @param *replica_dirsrvs - DirSrv instance, DirSrv instance, ... - @return True - if all servers have recevioed the update by this - replica, otherwise return False - @raise LDAPError - when failing to update/search database + :param *replica_dirsrvs: DirSrv instance, DirSrv instance, ... + :type *replica_dirsrvs: list of DirSrv + + :returns: True - if all servers have recevioed the update by this + replica, otherwise return False + :raises: LDAPError - when failing to update/search database """ # Generate a unique test value @@ -1187,15 +1205,15 @@ class Replica(DSLdapObject): class Replicas(DSLdapObjects): - """Class of all the Replicas""" + """Replica DSLdapObjects for all replicas - def __init__(self, instance, batch=False): - """Init Replicas - - @param instance - a DirSrv objectc - @param batch - NOT IMPLELMENTED - """ + :param instance: A instance + :type instance: lib389.DirSrv + :param batch: Not implemented + :type batch: bool + """ + def __init__(self, instance, batch=False): super(Replicas, self).__init__(instance=instance, batch=False) self._objectclasses = [REPLICA_OBJECTCLASS_VALUE] self._filterattrs = [REPL_ROOT] @@ -1203,7 +1221,17 @@ class Replicas(DSLdapObjects): self._basedn = DN_MAPPING_TREE def get(self, selector=[], dn=None): - """Wrap Replicas' "get", and set the suffix after we get the replica """ + """Get a child entry (DSLdapObject, Replica, etc.) with dn or selector + using a base DN and objectClasses of our object (DSLdapObjects, Replicas, etc.) + After getting the replica, update the replica._suffix parameter. + + :param dn: DN of wanted entry + :type dn: str + :param selector: An additional filter to objectClasses, i.e. 'backend_name' + :type dn: str + + :returns: A child entry + """ replica = super(Replicas, self).get(selector, dn) if replica: @@ -1214,22 +1242,23 @@ class Replicas(DSLdapObjects): def enable(self, suffix, role, replicaID=None, args=None): """Enable replication for this suffix - @param suffix - The suffix to enable replication for - @param role - ReplicaRole.MASTER, ReplicaRole.HUB or - ReplicaRole.CONSUMER - @param rid - number that identify the supplier replica - (role=ReplicaRole.MASTER) in the topology. For - hub/consumer (role=ReplicaRole.HUB or - ReplicaRole.CONSUMER), rid value is not used. This - parameter is mandatory for supplier. - - @param args - dictionary of additional replica properties - - @return replica DN - - @raise InvalidArgumentError - if missing mandatory arguments - ValueError - argument with invalid value - LDAPError - failed to add replica entry + :param suffix: The suffix to enable replication for + :type suffix: str + :param role: MASTER, HUB and CONSUMER + :type role: ReplicaRole + :param replicaID: number that identify the supplier replica + (role=ReplicaRole.MASTER) in the topology. + For hub/consumer (role=ReplicaRole.HUB or + ReplicaRole.CONSUMER), rid value is not used. + This parameter is mandatory for supplier. + :type replicaID: int + :param args: A dictionary of additional replica properties + :type args: dict + + :returns: Replica DSLdapObject + :raises: - InvalidArgumentError - if missing mandatory arguments + - ValueError - argument with invalid value + - LDAPError - failed to add replica entry """ # Normalize the suffix @@ -1312,8 +1341,11 @@ class Replicas(DSLdapObjects): def disable(self, suffix): """Disable replication on the suffix specified - @param suffix - Replicated suffix to disable - @raise ValueError is suffix is not being replicated + :param suffix: Replicated suffix to disable + :type suffix: str + + :returns: None + :raises: ValueError is suffix is not being replicated """ try: @@ -1332,17 +1364,17 @@ class Replicas(DSLdapObjects): '(%s) LDAP error (%s)' % (suffix, str(e))) def promote(self, suffix, newrole, binddn=None, rid=None): - """Promote the replica speficied by the suffix to the new role - - @param suffix - The replication suffix - @param newrole - The new replication role for the replica: - ReplicaRole.MASTER - ReplicaRole.HUB + """Promote the replica to hub or master - @param binddn - The replication bind dn - only applied to master - @param rid - The replication ID - applies only promotions to "master" + :param newrole: The new replication role for the replica: MASTER and HUB + :type newrole: ReplicaRole + :param binddn: The replication bind dn - only applied to master + :type binddn: str + :param rid: The replication ID, applies only to promotions to "master" + :type rid: int - @raise ldap.NO_SUCH_OBJECT - If suffix is not replicated + :returns: None + :raises: ValueError - If replica is not promoted """ replica = self.get(suffix) @@ -1353,13 +1385,13 @@ class Replicas(DSLdapObjects): replica.promote(newrole, binddn, rid) def demote(self, suffix, newrole): - """Promote the replica speficied by the suffix to the new role + """Demote a replica to a hub or consumer - @param suffix - The replication suffix - @param newrole - The new replication role of this replica - ReplicaRole.HUB - ReplicaRole.CONSUMER - @raise ldap.NO_SUCH_OBJECT - If suffix is not replicated + :param newrole: The new replication role for the replica: CONSUMER and HUB + :type newrole: ReplicaRole + + :returns: None + :raises: ValueError - If replica is not demoted """ replica = self.get(suffix) @@ -1373,9 +1405,12 @@ class Replicas(DSLdapObjects): """Return the DN of the replica from cn=config, this is also known as the mapping tree entry - @param suffix - the replication suffix to get the mapping tree DN - @return - The DN of the replication entry from cn=config + :param suffix: The replication suffix to get the mapping tree DN + :type suffix: str + + :returns: The DN of the replication entry from cn=config """ + try: replica = self.get(suffix) except ldap.NO_SUCH_OBJECT: @@ -1385,9 +1420,9 @@ class Replicas(DSLdapObjects): def get_ruv_entry(self, suffix): """Return the database RUV entry for the provided suffix - @return - The database RUV entry - @raise ValeuError - If suffix is not setup for replication - LDAPError - If there is a problem trying to search for the RUV + :returns: The database RUV entry + :raises: - ValeuError - If suffix is not setup for replication + - LDAPError - If there is a problem trying to search for the RUV """ try: @@ -1400,11 +1435,14 @@ class Replicas(DSLdapObjects): """Make a "dummy" update on the the replicated suffix, and check all the provided replicas to see if they received the update. - @param suffix - the replicated suffix we want to check - @param *replica_dirsrvs - DirSrv instance, DirSrv instance, ... - @return True - if all servers have recevioed the update by this - replica, otherwise return False - @raise LDAPError - when failing to update/search database + :param suffix: The replicated suffix we want to check + :type suffix: str + :param *replica_dirsrvs: DirSrv instance, DirSrv instance, ... + :type *replica_dirsrvs: list of DirSrv + + :returns: True - if all servers have received the update by this + replica, otherwise return False + :raises: LDAPError - when failing to update/search database """ try: diff --git a/src/lib389/lib389/repltools.py b/src/lib389/lib389/repltools.py index 67eb5c3..180a37d 100644 --- a/src/lib389/lib389/repltools.py +++ b/src/lib389/lib389/repltools.py @@ -13,26 +13,32 @@ log = logging.getLogger(__name__) # Helper functions def _alphanum_key(s): - """Turn the string into a list of string and number parts. - """ + """Turn the string into a list of string and number parts""" + return [int(c) if c.isdigit() else c for c in re.split('([0-9]+)', s)] def smart_sort(str_list): """Sort the given list in the way that humans expect. - @param str_list - a list of strings to sort + + :param str_list: A list of strings to sort + :type str_list: list """ + str_list.sort(key=_alphanum_key) def _getCSNTime(inst, csn): """Take a CSN and get the access log timestamp in seconds - @param inst - A DirSrv object to check access log - @param csn - A "csn" string that is used to find when the csn was logged in - the access log, and what time in seconds it was logged. - @return - The time is seconds that the operation was logged. + :param inst: An instance to check access log + :type inst: lib389.DirSrv + :param csn: A "csn" string that is used to find when the csn was logged in + the access log, and what time in seconds it was logged. + :type csn: str + + :returns: The time is seconds that the operation was logged """ op_line = inst.ds_access_log.match('.*csn=%s' % csn) @@ -46,9 +52,12 @@ def _getCSNTime(inst, csn): def _getCSNandTime(inst, line): """Take the line and find the CSN from the inst's access logs - @param inst - A DirSrv object to check access log - @param line - A "RESULT" line from the access log that contains a "csn" - @return - a tuple containing the "csn" value and the time in seconds when + :param inst: An instance to check access log + :type inst: lib389.DirSrv + :param line: A "RESULT" line from the access log that contains a "csn" + :type line: str + + :returns: A tuple containing the "csn" value and the time in seconds when it was logged. """ @@ -72,21 +81,22 @@ def _getCSNandTime(inst, line): class ReplTools(object): - """Replication tools - """ + """Replication tools""" @staticmethod def checkCSNs(dirsrv_replicas, ignoreCSNs=None): """Gather all the CSN strings from the access and verify all of those CSNs exist on all the other replicas. - @param dirsrv_replicas - a list of DirSrv objects. The list must begin - with master replicas - @param ignoreCSNs - an optional string of csns to be ignored if - the caller knows that some csns can differ eg.: - '57e39e72000000020000|vucsn-57e39e76000000030000' + :param dirsrv_replicas: A list of DirSrv objects. The list must begin + with master replicas + :type dirsrv_replicas: list of lib389.DirSrv + :param ignoreCSNs: An optional string of csns to be ignored if + the caller knows that some csns can differ eg.: + '57e39e72000000020000|vucsn-57e39e76000000030000' + :type ignoreCSNs: str - @return - True if all the CSNs are present, otherwise False + :returns: True if all the CSNs are present, otherwise False """ csn_logs = [] @@ -128,12 +138,16 @@ class ReplTools(object): print a report on how fast all the "ops" replicated to the other replicas. - @param suffix - Replicated suffix - @param ops - a list of "operations" to search for in the access logs - @param replica - Dirsrv object where the entries originated - @param all_replicas - A list of Dirsrv replicas: - (suppliers, hubs, consumers) - @return - The longest time in seconds for an operation to fully converge + :param suffix: Replicated suffix + :type suffix: str + :param ops: a list of "operations" to search for in the access logs + :type ops: list + :param replica: Instance where the entries originated + :type replica: lib389.DirSrv + :param all_replicas: Suppliers, hubs, consumers + :type all_replicas: list of lib389.DirSrv + + :returns: The longest time in seconds for an operation to fully converge """ highest_time = 0 total_time = 0 @@ -197,10 +211,13 @@ class ReplTools(object): """Take a list of DirSrv Objects and check to see if all of the present replication agreements are idle for a particular backend - @param replicas - List of DirSrv objects that are using replication - @param suffix - The replicated suffix - @raise LDAPError - if unable to search for the replication agreements - @return - True if all the agreements are idle, otherwise False + :param replicas: Suppliers, hubs, consumers + :type replicas: list of lib389.DirSrv + :param suffix: Replicated suffix + :type suffix: str + + :raises: LDAPError: if unable to search for the replication agreements + :returns: True if all the agreements are idle, otherwise False """ IDLE_MSG = ('Replica acquired successfully: Incremental ' + @@ -231,17 +248,21 @@ class ReplTools(object): @staticmethod def createReplManager(server, repl_manager_dn=None, repl_manager_pw=None): - '''Create an entry that will be used to bind as replication manager. - - @param server - A DirSrv object to connect to - @param repl_manager_dn - DN of the bind entry. If not provided use - the default one - @param repl_manager_pw - Password of the entry. If not provide use - the default one - @return None - @raise KeyError - if can not find valid values of Bind DN and Pwd - LDAPError - if we fail to add the replication manager - ''' + """Create an entry that will be used to bind as replication manager. + + :param server: An instance to connect to + :type server: lib389.DirSrv + :param repl_manager_dn: DN of the bind entry. If not provided use + the default one + :type repl_manager_dn: str + :param repl_manager_pw: Password of the entry. If not provide use + the default one + :type repl_manager_pw: str + + :returns: None + :raises: - KeyError - if can not find valid values of Bind DN and Pwd + - LDAPError - if we fail to add the replication manager + """ # check the DN and PW try: