Welcome to Syncrepl Client’s documentation!¶
Syncrepl Client requirements¶
The Syncrepl client doesn’t have many direct requires, but those requirements have a large-enough number of requirements that it’s worth listing everything.
This page is broken into two sections: The software you need, and the storage you need.
Software Requirements¶
The Syncrepl client has a number of direct and indirect requirements.
A compatible LDAP server.
“compatible” means that the LDAP server must implement a number of RFCs, including:
- RFC 3909: Lightweight Directory Access Protocol (LDAP) Cancel Operation; and
- RFC 4533: The Lightweight Directory Access Protocol (LDAP) Content Synchronization Operation.
The demo script also requires support for an additional RFC:
- RFC 4532: Lightweight Directory Access Protocol (LDAP) “Who am I?” Operation.
OpenLDAP is known to support these RFCs, although your OpenLDAP server must be configured to support Syncrepl.
Note
For more information, refer to the OpenLDAP Software 2.4 Administrator’s Guide, Section 18.3.1, and to the man page for slapo-syncprov(5).
libldap, the OpenLDAP client library.
libldap is the client library from OpenLDAP. On most Linux distributions, it is packaged separately from the rest of OpenLDAP, so that you can install the client library without installing the server stack.
This module is Pure Python, but other modules (listed below) are not. If you are building other modules, you will also need the libldap headers.
- On Debian (and derivatives, like Ubuntu), the library is part of the libldap-2.4-2 package, and headers are in the libldap2-dev package.
- On Red Hat Enterprise Linux (and derivatives, like CentOS), the library is part of the openldap package, and headers are in the openldap-devel package.
- On SUSE Linux, the library is part of the openldap2-client package, and the headers are part of the openldap2-devel package.
- On Arch Linux, the library and headers are part of the libldap package.
- On Macports, the library and the headers are part of the openldap port.
At the same time, you may also wish to install the ldap-utils, which gives you helpful tools like ldapsearch.
Python 2.7, or Python 3.6.
It is likely that Python 2.6—and older versions of Python 3—will work, but those have not yet been tested. This entry will update once they are!
python-ldap 99 or later (Python 2), or pyldap 2.4.37 or later (Python 3).
python-ldap is an object-oriented wrapper around libldap, and works in Python 2 only. pyldap is a fork of python-ldap, which adds support for Python 3.
pyldap does also work with Python 2, but this software has not been tested with pyldap on Python 2.
Warning
As of this writing, the latest release of python-ldap is 2.4.41. However, it has a bug which affects us:
- python-ldap has a bug in which the ldap.TIMEOUT exception is not properly raised back to the client.
The bug has been reported to the python-ldap project.
The pyasn1 module, at least version 0.2.2, and less than version 0.3.1.
In Syncrepl, messages from the LDAP server are encoded using ASN.1. libldap does not decode these for us, so we use the well-established pyasn1 module to handle the decoding.
Technically, this is a requirement of python-ldap / pyldap. It is an optional dependency for them, and is only used when using
ldap.syncrepl
. That makes it a requirement for us.In version 0.3.1, there were a number of breaking API changes. This causes occasional exceptions to be thrown. Until pyasn1 fixes the issue (assuming it’s a bug), or pyldap / python-ldap change their code, pyasn1 0.3.1 and later may not be used.
Storage Requirements¶
The Syncrepl client keeps an on-disk copy of the LDAP entries it has received, along with additional data. When the connection to the LDAP server is interrupted, this on-disk data is used to speed up the process of re-synchronizing with the LDAP server.
To estimate the storage needs for the Syncrepl Client, convert your LDAP URL into an execution of ldapsearch. Measure the size of the execution’s output, and use that as your estimate.
The storage you use really should be fast, redundant, and battery-backed. For consistency purposes, the Syncrepl client occasionally pauses to ensure that changes are synced to disk; speeding up that process will speed up code execution. A simple RAID-1 of two SSDs would work.
Memory Requirements¶
For the data referenced in the above section, the Syncrepl client keeps a copy of the data in memory, in addition to on disk. This in-memory copy speeds up data store changes, which is important when LDAP updates are coming in.
Beyond whatever memory requirements this code (and your code) has, you should also have enough RAM to match the storage requirments listed above. In other words, if you estimate needing 5 GB of storage, you should also plan on having 5 GB of RAM.
The Syncrepl Client’s Syncrepl Class¶
The Syncrepl
class is the main class that you will be
using. It is responsible for all LDAP operations, and will issue callbacks to
an object you provide (see the BaseCallback
class).
-
class
syncrepl_client.
Syncrepl
(data_path, callback, mode, ldap_url=None, **kwargs)[source]¶ This class implements the Syncrepl client. You should have one instance of this class for each syncrepl search.
Each class requires several items, which will be discussed here:
A data store
The Syncrepl client stores a copy of all LDAP records returned by the LDAP server. This data is stored on disk to speed up synchronization if the client loses connection to the LDAP server (either intentionally or not).
The Syncrepl class writes to several files, so the class will be given a data_path. To come up with the actual file paths, we concatenate data_path and our file name. For that reason, data_path should normally end with a slash (forward or back, depending on OS), to keep our data files in its own directory.
The data store files should be deleted any time you want a completely fresh start. The data store files will also be wiped any time the syncrepl_client software version changes.
Warning
Data store files are also not compatible between Python 2 and Python 3. Attempting to use a data store from Python 2 with Python 3—or vice versa—will likely trigger an exception during instantiation.
A callback class
The callback class is an object (a class, or an instance). The callback class’ methods are called when the Syncrepl client receives updates.
The complete list of callback methods is documented in
BaseCallback
. That class is designed for subclassing, because it defines each callback method but doesn’t actually do anything. You can have your class inherit fromBaseCallback
, and let it handle the callbacks that you don’t care about.For a simple example of a callback in action, see the code for the
LoggingCallback
class.An LDAP URL
The LDAP URL contains all information about how the Syncrepl client should connect, what credentials should be used to connect, and how the search should be performed.
The
LDAPUrl
class is used to parse the LDAP URL. You can also use ldapurl (part of the ldap-utils) to construct a URL. Refer to the class’ documentation for information on the fields available.If a valid data store exists, this field is optional: the URL your provide will be stored in the data store, which will be used in subsequent connections. If you provide both an LDAP URL and a valid data store, your LDAP URL will be used, as long as the search parameters have not changed (the LDAP host and authentication information are OK to change).
syncrepl_client supports the following bind methods, which you control by using particular LDAP URL extensions:
Anonymous bind: Do not set a bind DN or password.
Simple bind: Set the bind DN and password as part of the URL.
The bindname LDAP URL extension is used to hold the bind DN, and the X-BINDPW extension is used to hold the bind password.
Note
For security, it is suggested that you store the LDAP URL without a password, convert the URL into an
ldapurl.LDAPUrl
object at runtime, add the password, and pass the password-laden object to theSyncrepl
constructor.GSSAPI bind: Set the bind DN to GSSAPI, and do not set a password.
Note
You are responsible for ensuring that you have valid Kerberos credentials.
As an extra safety mechanism, when you receive the
bind_complete()
callback, consider doing a “Who am I?” check against the LDAP server, to make sure the bind DN is what you expected. That will help guard against expired or unexpected credentials.
Methods are defined below. Almost all methods are documented, including internal methods.
Warning
Methods whose names start with syncrepl_ are internal methods, which clients must not call. That being said, the methods are still being documented here, for educational purposes.
-
__init__
(data_path, callback, mode, ldap_url=None, **kwargs)[source]¶ Instantiate, connect to an LDAP server, and bind.
Parameters: - data_path (str) – A path to where data files will be stored.
- callback (object) – An object that receives callbacks.
- mode (A member of the
SyncreplMode
enumeration.) – The syncrepl search mode to use. - ldap_url (str or ldapurl.LDAPUrl or None) – A complete LDAP URL string, or an LDAPUrl instance, or
None
.
Returns: A Syncrepl instance.
This is the
Syncrepl
class’s constructor. In addition to basic initialization, it is also responsible for making the initial connection to the LDAP server, binding, and starting the syncrepl search.Note
Many parts of this documentation refers to syncrepl as a “search”. That is because a syncrepl is initiated using an LDAP search operation, to which a syncrepl control is attached.
data_path is used to specify the prefix for the path to data storage.
Syncrepl
will open multiple files, whose names will be appended todata_path
. You are responsible for making sure thatdata_path
is appropriate for your OS.Note
Some basic checks may be performed on the data files. If you use a different version of software, those checks will fail, and the contents will be wiped.
callback
can be anything which can receive method calls, and which is specifically able to handle the calls defined inBaseCallback
.mode
should be one of the values fromSyncreplMode
.REFRESH_ONLY
means that you want the syncrepl search to end once your have been brought in sync with the LDAP server.REFRESH_AND_PERSIST
means that, after being refreshed, you will receive notice whenever a change is made on the LDAP server.ldap_url
is an LDAP URL, containing at least the following information:- The LDAP protocol (ldap, ldaps, or ldapi).
- The base DN to search, and the search scope.
All other LDAP URL fields are recognized. The bindname LDAP URL extension may be used to specify a bind DN (or “GSSAPI” for GSSAPI bind). When using simple bind, the X-BINDPW extension must hold the bind password.
The
bind_complete()
callback will be called at some point during the constructor’s execution.Returns a ready-to-use instance. The next call you should make to the instance is
poll()
. Continue callingpoll()
until it returnsFalse
; then you should callunbind()
. To request safe teardown of the connection, callplease_stop()
.
-
unbind
()[source]¶ Safely save state and disconnect from the LDAP server.
Returns: None If you have instantiated this object on your own, call unbind to ensure that all data files are flushed to disk, and the LDAP server connection is properly closed.
Warning
If you are using the Context Manager protocol, do not call `unbind`; it will be called for you at the appropriate time.
Warning
Not all Python implementations delete objects at the same point in their code. PyPy, in particular, is very different. Do not rely on assumptions about garbage collection!
Once unbound, this instance is no longer usable, even if it hasn’t been deleted yet. To start a new client, make a new instance of
Syncrepl
.
-
please_stop
()[source]¶ Requests the safe termination of a Syncrepl search.
Returns: None. After calling this method, there is a set list of steps your code should take:
- Continue calling
poll()
until it returnsFalse
. - Call
unbind()
(unless you’re using the Context Management protocol). - Stop using this instance.
When running in refresh-only mode, this does nothing: Interrupting a refresh is dangerous, because there is no guarantee that the updates from the LDAP server are being received in any particular order. The refresh will be allowed to complete, and then it is safe to stop.
When running in refresh-and-persist mode, if the refresh phase is still in progress, it will be completed. If in the persist phase, a Cancel request will be sent to the LDAP server. Operations will then continue until the LDAP server confirms the search is cancelled.
This is the only method which is thread-safe.
- Continue calling
-
poll
()[source]¶ Poll the LDAP server for changes.
Returns: True or False. In refresh-only mode, returning
True
indicates that the refresh is still in progress. You must continue callingpoll()
untilFalse
is returned. OnceFalse
is returned, the refresh is complete, and it is safe to callunbind()
.In refresh-and-persist mode, returning
True
only indicates that the connection is still active: Work might or might not be taking place. Therefresh_done()
callback is used to indicate the completion of the refresh phase and the start of the persist phase. During the refresh phase, when the connection is idle,poll()
will returnTrue
every ~3 seconds. This is for single-process applications.Most callbacks will be made during the execution of
poll()
.Warning
Just because
poll()
has returned, does not mean that you are in sync with the LDAP server. You must continue callingpoll()
until it returnsFalse
.To request safe, consistent teardown of the connection, call
please_stop()
.
-
run
()[source]¶ Run
poll()
until it returnsFalse
.Returns: None Runs the
poll()
method continuously, until it returnsFalse
. This is acallable
.In refresh-only mode, this method is good to use, as it saves you from having to write a while loop. Once this method returns, you know that the refresh has completed, and you are clear to call
unbind()
to clean up the instance.In refresh-and-persist mode, this method should only be called when you are running this instance in its own thread. It will call
poll()
effectively forever, until the LDAP server goes away. For that reason, you should use this method as the target to pass to thethreading.Thread
constructor. Doing so allows the Syncrepl search to run while your main thread can get on with other work. In particular, your main thread should catch signals like SIGHUP, SIGINT, and SIGTERM.Note
When a Syncrepl search is actively running, most of the execution time is spent inside OpenLDAP client code, waiting for updates from the LDAP server. If OpenLDAP client code receives a signal, it normally responds by abruptly closing the LDAP connection and raising an exception. That will cause the Syncrepl search to stop in an unsafe manner.
You really should run refresh-and-persist Syncrepl searches in a thread.
When you are running this method in a thread, use
please_stop()
to request the safe shutdown of the Syncrepl search. Once the thread has beenjoin()
-ed, remember to callunbind()
to clean up the instance.Warning
please_stop()
is the only thread-safe method! Once you have spawned your Syncrepl search thread, no other methods (except forplease_stop()
) may be called until the thread has beenjoin()
-ed.
-
sync
(force=False)[source]¶ Sync the data store to storage.
Parameters: force (bool) – Force sync even in refresh mode. Returns: None Note
This is an internal Syncrepl operation. It is documented here for educational purposes, but should not be called by clients.
For performance, the data store is kept in memory, and is only synced to disk in certain cases. Those cases are:
- When an instance is unbound.
- When a change happens in the persist phase of refresh-and-persist mode.
In refresh mode, the data store is not synced to disk until the refresh is complete. This is done because consistency is not guaranteed in the middle of a refresh phase.
You normally never need to call this yourself; it is called for you, typically as soon as your callback completes.
Get Syncrepl cookie from data store.
Returns: bytes or None. Note
This is an internal Syncrepl operation. It is documented here for educational purposes, but should not be called by clients.
Called at the start of the syncrepl operation, to load a cookie from state. If present and valid, the LDAP server will know how far behind we are.
If not present, or invalid (typically because it’s too old), the LDAP server will start us over, as if we were a new client.
Store Syncrepl cookie in data store.
Parameters: cookie (bytes) – An opaque string. Returns: None. Note
This is an internal Syncrepl operation. It is documented here for educational purposes, but should not be called by clients.
Called regularly during syncrepl operations, to store a “cookie” from the LDAP server. This cookie is presented to the LDAP server when we reconnect, so that it knows how far behind we are.
-
syncrepl_refreshdone
()[source]¶ Mark the transition from the refresh phase to the persist phase.
Returns: None. Note
This is an internal Syncrepl operation. It is documented here for educational purposes, but should not be called by clients.
This is called when we moving from the Refresh mode into the Persist mode of refresh-and-persist. This is not called in refresh-only mode.
Triggers a
refresh_done()
callback.
-
syncrepl_delete
(uuids)[source]¶ Report deletion of an LDAP entry.
Parameters: uuids (List of binary.) – List of UUIDs to delete. Returns: None. Note
This is an internal Syncrepl operation. It is documented here for educational purposes, but should not be called by clients.
Called when one or more entries have been deleted, or have changed such that they are no longer in our search results.
The one parameter is a list of UUIDs, which we should already know about.
Triggers a
record_delete()
callback for each UUID.
-
syncrepl_present
(uuids, refreshDeletes=False)[source]¶ Indicate the presence or absence of an LDAP entry.
Parameters: - uuids (List of bytes, or None.) – List of UUIDs present or absent.
- refreshDeletes (boolean) – Indicates presence or absence.
Returns: None.
Note
This is an internal Syncrepl operation. It is documented here for educational purposes, but should not be called by clients.
This function is used in refresh-only syncrepl, and in the refresh phase of refresh-and-persist syncrepl. It is not used in the persist phase.
As part of the syncrepl process, we get a big list of UUIDs and their DNs (plus attributes), from which we build a mapping (see
syncrepl_entry()
, below). The first time a sync takes place (when there is no valid cookie), you are able to assume that every mapping entry received is present in the directory; but in subsequent syncs (using a valid cookie) you can’t be sure which entries are present and which have been deleted. In addition, if you have a cookie that is now too old, there is no way to know which entries in your data store still exist in the directory.The “Present” messages, and the resulting calls, are used to bring us back in sync with the Directory, regardless of our local state.
uuids is either a list of UUIDs, or
None
. refreshDeletes is a boolean. To understand how the two parameters are related, it’s better to look at the latter parameter first.- If refreshDeletes is
False
, and uuids is a list, then uuids contains a list of entries that are currently in the directory. - If refreshDeletes is
False
, but uuids isNone
, then we are almost synced. We now need to go into our mapping, and remove all entries that were not previously mentioned as being in the directory. - If refreshDeletes is
True
, and we have a list, then uuids contains entries that used to be in the directory, but are now gone. - If refreshDeletes is
True
, but uuids isNone
, then we are synced: Our current mapping of UUIDs, minus those previously deleted, represents the current state of the directory.
Here is another way to think about it: The LDAP server needs to work out the most efficient way of relaying changes to us. There are three ways of telling us what has changed:
“The following entries are in the directory; everything else you knew about has been deleted.”
This is the easiest way of informing us of changes and deletions.
In this mode, you will receive:
“The following entries are new, and these other entries have been deleted, but everything else you know about is still in the directory.”
This is the mode that is used when, since your last checkin, there have been alot of additions and deletions.
In this mode, you will receive:
“Everything is up-to-date and there are no changes.”
When things are quiet, this is the mode that is used.
In this mode, you wil receive:
The LDAP server chooses which mode to use when we connect and present a valid cookie. If we don’t have a valid cookie, then the LDAP server falls back to mode A.
-
syncrepl_entry
(dn, attrs, uuid)[source]¶ Report addition or modification of an LDAP entry.
Parameters: Returns: None.
Note
This is an internal Syncrepl operation. It is documented here for educational purposes, but should not be called by clients.
This function is called to add entries to a map of UUIDs to DN/attribute-list pairs. It is also used to change an existing DN: In that case, the UUID matches an existing entry, but the DN is different.
DNs are not static - they can change. That’s a problem when you are trying to track changes over time. To deal with that, the LDAP server assigns each entry a UUID. We then maintain a mapping of UUIDs to DNs, because all future syncrepl-related calls will refernce UUIDs instead of DNs.
In refresh-only sync, and in the refresh phase of refresh-and-persist syncrepl, this method is called multiple times, interspersed with calls to
syncrepl_present()
. If a valid cookie was provided, the server will only send new/changed entries since our last checkin; otherwise, we’ll get a big list of entries—all of which will be present—to seed our mapping.In refresh-and-persist mode, everything from the previous paragraph is true, but when in the persist phase (once the refresh phase has completed) we should expect to be called at random times as the server sends us updates to our mapping.
The set of attributes is the intersection of three sets:
- The populated attributes of a particular entry.
- The attributes you are allowed to see.
- The attributes you requested in your search.
All attribute entries are lists of binary strings. Lists are used throughout because the LDAP client does not know which attributes are multi-valued, and binary strings are used because the LDAP client does not know each attribute’s syntax. The client is responsible for knowing the directory’s schema, and casting/converting values appropriately.
-
class
syncrepl_client.
SyncreplMode
[source]¶ This enumeration is used to specify the operating mode for the Syncrepl client. Once a mode is set it can not be changed. To change the mode, you will have to (safely) shut down your existing search, unbind and destroy the existing instance, and start a new instance in the new mode.
-
REFRESH_ONLY
= 'refreshOnly'¶ In this mode, the syncrepl search will last long enough to bring you in sync with the server. Once you are in sync,
poll()
will returnFalse
.
-
REFRESH_AND_PERSIST
= 'refreshAndPersist'¶ In this mode, you start out doing a refresh. Once the refresh is complete, subsequent calls to
poll()
will be used to receive changes as they happen on the LDAP server. All calls topoll()
will returnTrue
, unless a timeout takes place (that will throwldap.TIMEOUT
), you cancel the search (that will throwldap.CANCELLED
), or something else goes wrong.Note
When running a Syncrepl search in refresh-and-persist mode, it is strongly recommended that you run the actual search operation in a thread, so that you can catch signals which would otherwise cause an unclean termination of the Syncrepl search.
For more information, see the
run()
method, which is what you should use as the thread’s target.
-
Syncrepl Client Callbacks¶
When a Syncrepl
instance wants to tell you about
something, it uses a callback.
The complete list of callbacks, their parameters, and what they mean is defined
in BaseCallback
.
Syncrepl
doesn’t care if your object uses instance
methods, class methods, or static methods, so long as your callback is able to
accept the parameters provided.
In addition to BaseCallback
,
LoggingCallback
is provided as a way to log
the callbacks being received.
Both BaseCallback
and
LoggingCallback
are described below.
BaseCallback¶
-
class
syncrepl_client.callbacks.
BaseCallback
[source]¶ BaseCallback
is a class containing all of the callbacks whichsyncrepl_client.Syncrepl
may call. It is implemented as a new-style class, with class methods (althoughSyncrepl
doesn’t care).This class exists for two reasons:
- It documents each callback, the parameters the callback receives, and what the callback means.
- It gives you a useful base class to sink unwanted callbacks. If you only care about certain callbacks, you can implement them in your class, and let all the other callbacks percolate up to this class, where they are received and ignored.
Another reason for using classes is because they can be stacked. For example, if you want to handle callbacks, but you also want them to be logged, you can have your callback class use
LoggingCallback
as a base class, and then set theLoggingCallback
‘sdest
attribute to the log sink (which could bestdout
, or another file handle).Note
If your class needs any kind of setup or initalization before it can receive callbacks, it is suggested that you implement your callbacks as instance methods, and then use normal class instantiation to do your preparation, before handing off the instance to
Syncrepl
‘s constructor.-
classmethod
bind_complete
(ldap)[source]¶ Called to mark a successful bind to the LDAP server.
Parameters: ldap (ldap.LDAPObject) – The LDAP object. Returns: None - any returned value is ignored. This callback is used to indicate a successful bind, and to give you one opportunity to interact with the LDAP server, before the connection is taken over by the syncrepl search.
For example, if you want to record the bind DN (which you might not know, if you are doing things like a SASL bind), or you want to retrieve the schema stored on the server, this is the time to do it!
This is the very first callback to be called. Once the callback completes, the syncrepl search will begin, and other callbacks will start coming in.
Note
Once the callback has completed, there is no guarantee that ldap will still be a valid reference.
Warning
If you start any asynchronous operations (which includes searches), those operations must be completed before this callback returns.
Warning
Please do not unbind ldap from the LDAP server!
Warning
Once the callback completes, this LDAP connection will be used for the syncrepl search. No other operations will be allowed! If you want to communicate with the LDAP server after this callback completes, you will need to set up a separate LDAP connection.
-
classmethod
refresh_done
(items)[source]¶ Called to mark the end of the refresh phase.
Parameters: items (dict) – The items currently in the directory. Returns: None – any returned value is ignored. When receiving this callback, you know that the refresh phase has completed, and your view of the directory is now consistent with the LDAP server (at least, the part of the directory which matches your search and your access).
items
is a dictionary (or, an object which behaves like a dictionary) where the keys are DNs and the values are dicts of attributes & their values. Every item initems
is present in the directory at the time this callback started.Note
items
is read-only. Any attempt to add, change, or delete and item will cause anAttributeError
to be raised.Warning
The content
items
is guaranteed to be consistent only inside this callback. Once this callback returns, any attempts to accessitems
again will result in undefined behavior.If you need to do any sort of synchronization with anyone else, this is the best time to do it. Once you return from this callback, the persist phase will begin. If you are operating in refresh-only mode, then as soon as this callback completes,
syncrepl_client.Syncrepl.poll()
will returnFalse
. It is then safe to callunbind()
.
-
classmethod
record_add
(dn, attrs)[source]¶ Called to indicate the addition of a new LDAP record.
Parameters: - dn (str) – The DN of the added record.
- attrs (Dict of lists of bytes) – The record’s attributes.
Returns: None - any returned value is ignored.
Warning
attrs
is passed by reference. If you modify the dictionary—or its contents—in any way, you will pay for it later!This callback can happen in all modes, and in all phases, to indicate that an entry has been added to your view of the search results. In refresh-only mode, and the refresh phase of refresh-and-perist mode, the addition may have taken place at any time since your last update. In the persist phase of refresh-and-persist mode, a new entry has just been added—or modified—such that it matches your search.
Attributes which are not present in the record will not present in attrs. Dict keys are attribute names, and dict values are arrays (to support multi-valued attributes).
Note
Just because the dict values are arrays, does not mean that all attributes are multi-valued. The LDAP client does not know which attributes are single- and which are multi-valued, so it assumes that all are multi-valued.
Also, all attribute values will come in as (in Python 2)
str
objects or (in Python 3)bytes
objects.To learn which attributes are single- or multi-valued, and to learn the type (or, in LDAP terms, the syntax) of an attribute, you need to look at the schema, possibly using
ldap.schema
.
-
classmethod
record_delete
(dn)[source]¶ Called to indicate the deletion of an LDAP record.
Parameters: dn (str) – The DN of the deleted record. Returns: None - any returned value is ignored. This callback can happen in all modes, and in all phases, to indicate that an entry has been either been deleted, or that it no longer matches your search. In refresh-only mode, and the refresh phase of refresh-and-perist mode, the deletion may have taken place at any time since your last update. In the persist phase of refresh-and-persist mode, the entry has just disappeared.
-
classmethod
record_rename
(old_dn, new_dn)[source]¶ Called to indicate a change in DN.
Parameters: Returns: None - any returned value is ignored.
This callback happens when an entry’s DN changes.
This callback can happen in all modes, and in all phases, to indicate that an entry’s DN has been changed. In refresh-only mode, and the refresh phase of refresh-and-perist mode, the change may have taken place at any time since your last update. In the persist phase of refresh-and-persist mode, the entry has just changed.
You should expect a call to
record_change()
shortly after this callback completes.
-
classmethod
record_change
(dn, old_attrs, new_attrs)[source]¶ Called to indicate a change in attributes.
Parameters: - dn (str) – The DN of the changed record.
- old_attrs (Dict of lists of bytes) – The old attributes.
- new_attrs (Dict of lists of bytes) – The new attributes.
Returns: None - any returned value is ignored.
This callback happens when an entry has changed.
You are provided with the old attributes, and the new attributes. It is up to you to determine what the changes are (if you care).
Note
If you look back at
record_add()
, see the note about changing attrs, and how it will come back to bite you? Well, here’s where it comes back to bite you!Warning
new_attrs is passed by reference. If you modify the dictionary—or its contents—in any way; you will pay for it later!
This callback can happen in all modes, and in all phases, to indicate that an entry has been changed. In refresh-only mode, and the refresh phase of refresh-and-perist mode, the change may have taken place at any time since your last update. In the persist phase of refresh-and-persist mode, the entry has just changed.
Note
Just because the dict values are arrays, does not mean that all attributes are multi-valued. The LDAP client does not know which attributes are single- and which are multi-valued, so it assumes that all are multi-valued.
Also, all attribute values will come in as (in Python 2)
str
objects or (in Python 3)bytes
objects.To learn which attributes are single- or multi-valued, and to learn the type (or, in LDAP terms, the syntax) of an attribute, you need to look at the schema, possibly using
ldap.schema
.
Called to log a change in Syncrepl cookie.
Parameters: cookie (str) – The new Syncrepl cookie. :return None - any returned value is ignored.
This callback happens any time the LDAP server sends us a new Syncrepl cookie.
The Syncrepl cookie is an opaque string, which we send to the LDAP server at the start of a Syncrepl search. If we do not have one, then the LDAP server knows to send us everything. If we do have one, then the LDAP server can use that to know how far behind we are, and to send us just the changes.
This callback can happen in all modes, and in all phases, as it is up to the LDAP server to give us a new Syncrepl cookie, when it is appropriate to do so.
-
classmethod
debug
(message)[source]¶ Called to log debug messages.
Parameters: message (str) – A message of some sort. Returns: None - any returned value is ignored. This method doesn’t have much of a use. It’s just a way for
Syncrepl
to log debug messages. There’s no guarantee that you’ll get anything meaningful, or anything at all.The safest thing to do is to just pass this method. Or, subclass
BaseCallback
.
LoggingCallback¶
-
class
syncrepl_client.callbacks.
LoggingCallback
[source]¶ LoggingCallback
is a callback class which logs each callback. It is useful for debugging purposes, as the output is not meant to be machine-readable.Each callback will cause messages to be printed to the file set in
dest
. For thebind_complete()
callback, the bind DN is printed. For callbacks containing DNs, the DNs are printed. For callbacks containing attribute dictionaries, each dictionary’s contents are printed.For a list of callbacks, and what they mean, see
BaseCallback
.-
dest
= <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>¶ The log destination.
This can be anything which can be used in
print()
‘s file parameter.Defaults to
sys.stdout
.
-
Syncrepl Client Exceptions¶
When a Syncrepl
instance wants to tell you about
a problem, it raises an exception.
This document covers all of the exceptions thrown directly by
Syncrel
. Note that you should also be prepared to
catch exceptions from ldap
, as those exceptions are allowed to percolate
up to the client, with only a few exceptions (for example, catching CANCELLED
and TIMEOUT exceptions when they are expected).
The exceptions raised by Syncrepl
are as follows:
-
exception
syncrepl_client.exceptions.
ClosedError
[source]¶ Action performed on an unbound instance.
This exception is thrown when a call is made to an instance which has been unbound.
If you wish to reconnect to the server, you must use a new instance.
-
exception
syncrepl_client.exceptions.
LDAPUrlError
[source]¶ Generic LDAP URL-related exception.
All of the exceptions related to LDAP URLs are subclasses of this exception.
-
exception
syncrepl_client.exceptions.
LDAPUrlConflict
(current_url, new_url)[source]¶ Thrown when a URL conflicts with an existing URL.
This exception is thrown when a data store already has a URL, and the client provided a new LDAP URL that conflicts.
The following attributes should be checked:
- The search base DN.
- The search scope.
- The search filter.
- The list of attributes to return.
If any of the above attributes conflict, this exception is thrown.
Attributes:
Index¶
syncrepl_client is a Python module that makes LDAP Syncrepl easy to use.
LDAP Syncrepl allows you to keep up-to-date with an LDAP server, effectively in real time, even without LDAP administrator credentials.
If your LDAP directory is used as the source of truth (or a delegate for the soource of truth), this keeps you informed when something changes. Callbacks—which you write—are triggered by this code when something happens. You can then take appropriate action, such as by inserting into a queue or sending a message over a bus.
What is Syncrepl?¶
Syncrepl (as described in RFC 4533) is a standard which allows an LDAP server to keep clients in sync with itself. The clients keep track of a “cookie”, an opaque string that the server uses to know how far behind the client is. The LDAP server then “refreshes” the client by sending details of new & changed entries, as well as information on which entries have been deleted. After the refresh is complete, the client is able to keep a long-running connection open to the server, and receive notice as soon as a change happens on the server.
Syncrepl is the way that OpenLDAP uses to implement replication (from a master LDAP server to one or more slaves), but the client does not have to be an OpenLDAP server. In fact, because Syncrepl is layered on top of an ordinary LDAP search, regular LDAP clients—even those with limited access—are able to use Syncrepl to effectively be notified as soon as the results of their search has changed. This includes notification on:
- New entries that match your search filter.
- Entries being deleted.
- Entries, which used to match, no longer matching. This is essentially the same as deletion (at least, it is when you are using a search filter).
- Existing entries having their attributes or DN changed.
The entries you see, and the changes made to them, are based on the intersection of four things.
- The entries currently in the directory.
- Your access rights, as determined by your bind DN.
- Your search filter.
- Your list of requested attributes.
Thanks to the Syncrepl protocol, you don’t have to worry about all of the above. The LDAP server handles the work of figuring out what you can see.
Requirements¶
syncrepl_client has four major requirements:
Python 2.7, or Python 3.3+.
If you use Python 2.7 or 3.3, you will also need enum34.
If you plan on doing “refresh and persist” operations (which run for a long time), your Python should support threads.
An appropriate Python LDAP library:
- For Python 2.7, python-ldap 99 or later is needed.
- For Python 3, pyldap 2.4.37 or later is needed.
The pyasn1 module, at least version 0.2.2, and less than version 0.3.1.
Technically, this is a requirement of python-ldap / pyldap. It is an optional dependency for them, and is only used when using
ldap.syncrepl
. That makes it a requirement for us.A fast data store, large enough to store a copy of all the LDAP data received, and a corresponding amount of RAM.
An LDAP server which supports RFC 4533, and which is keeping track of changes.
In the case of OpenLDAP, this means following the instructions in Section 18.3.1 of the Admin Guide.
Lots more details are available on the Requirements page.
How to Use¶
Although you’ll still need to do a fair bit of coding (mainly in Step 1), syncrepl_client is (intentionally) pretty easy to use! Over the life of your code’s execution, you should do these four things:
- Create a class which implements the methods defined in
BaseCallback
This is how you are notified of changes from the LDAP server. - In your main code, import
syncrepl_client
and instantiate a newSyncrepl
object. The instantiation will handle the connection and the search setup. - Call
poll()
until it returnsFalse
. If you’re running single-threaded, set timeout to some positive, non-zero value. Callplease_stop()
when you want to safely shut down, and then resume callingpoll()
until it returnsFalse
. - Call
unbind()
. You’re done!
Lots more details are available in the Requirements page, and see demo.py for a simple example.
Copyright and License¶
The contents of this repository are copywrited according to the contents of the AUTHORS file.
The code is made available under the BSD 3-Clause License.
Other code is made available under the Creative Commons CC0 Public Domain Dedication.
Documentation is made available under the Creative Commons Attribution-ShareAlike 4.0 International Public License (the CC BY-SA License). Code contained within documentation is made available under both the BSD 3-Clause License, and the CC BY-SA License.
To identify the license for any particular file, refer to the contents of the file.
The text of the BSD 3-Clause License is reproduced in the file LICENSE.md. The text of the other licenses may be found in the file LICENSE_others.md. Note that all three licenses are equally important, but are kept in a separate files to aid GitHub’s irepository license-detection mechanisms.