New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactoring: LDAP Connection Management #145
Conversation
| @@ -265,12 +261,14 @@ def __common_setup(self, enable_ssl=False): | |||
| self.step("creating directory server instance", self.__create_instance) | |||
| self.step("updating configuration in dse.ldif", self.__update_dse_ldif) | |||
| self.step("restarting directory server", self.__restart_instance) | |||
| self.step("enabling ldapi", self.__enable_ldapi) | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello,
Isn't it possible to enable ldapi+autobind before the 'line 263' restart ?
I would save a DS restart
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, I'd like to avoid additional DS restart as much as possible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tried to do that, but I haven't been able to connect to LDAP before the first restart happens.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok. Sorry I thought that all steps were done through ldap connection but they were done by editing dse.ldif. So there was no already established connection.
I wonder if after __create_instance it wouldn't be possible to open a connection (LDAPClient(ldap://fqdn:389)) and bind using 'directory manager' password. So that enable ldapi, etc.. could be done via ldap:389 so that only one restart would be possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, it is possible to connect to ldap right after the instance is created. I didn't notice the service is stopped before updating the dse. Thanks, fixed in next synchronization.
|
Please fix PEP8 |
| @@ -1639,48 +1636,25 @@ def __init__(self, host='', port=389, cacert=None, debug=None, ldapi=False, | |||
| def __str__(self): | |||
| return self.host + ":" + str(self.port) | |||
|
|
|||
| def __wait_for_connection(self, timeout): | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
https://fedorahosted.org/freeipa/ticket/2175
Upgrade is using ldapi now.
However I'm curious if there is a time delay between DS is listening on ldapi socket and when DS is listening on 389 port. But in our case we plan to use just ldapi, so it should be ok.
| @@ -704,7 +704,7 @@ class LDAPClient(object): | |||
| time_limit = -1.0 # unlimited | |||
| size_limit = 0 # unlimited | |||
|
|
|||
| def __init__(self, ldap_uri, start_tls=False, force_schema_updates=False, | |||
| def __init__(self, ldap_uri, start_tls=False, force_schema_updates=True, | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add reason for this change into commit message
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in next sync.
| return 'ldap' | ||
|
|
||
| def __init__(self, host='', start_tls=False, | ||
| force_schema_updates=True, no_schema=False, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this comment should be in previous patch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in next sync.
| @@ -265,12 +261,14 @@ def __common_setup(self, enable_ssl=False): | |||
| self.step("creating directory server instance", self.__create_instance) | |||
| self.step("updating configuration in dse.ldif", self.__update_dse_ldif) | |||
| self.step("restarting directory server", self.__restart_instance) | |||
| self.step("enabling ldapi", self.__enable_ldapi) | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, I'd like to avoid additional DS restart as much as possible
| @@ -157,7 +158,8 @@ def ldap_disconnect(self): | |||
| LDAPConnectionManager.close_connection() | |||
| self.admin_conn = None | |||
|
|
|||
| def _ldap_mod(self, ldif, sub_dict=None, raise_on_err=True): | |||
| def _ldap_mod(self, ldif, sub_dict=None, raise_on_err=True, | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like to ahve so much options, can we split this into separate method _simple_ldap_mod(), nonldapi, or something
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not convinced this is a worthwhile change. But perhaps we could eliminate the ldap_uri, because it seems the method connects to the localhost anyway. But I'm not sure this is always the case, since the method uses ldap_uri from admin_conn, which could, theoretically, have different ldap_uri.
| @@ -1656,3 +1656,27 @@ def entry_exists(self, dn): | |||
| return False | |||
| else: | |||
| return True | |||
|
|
|||
|
|
|||
| class LDAPConnectionManager(object): | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because this is related only to ldapi, I would rename it to LDAPI connection manager
|
I did some inline comments, I'm not fully satisfied with implementation of the connection manager, I'll think about it tomorrow. |
0b5195b
to
ad4dfdf
Compare
|
I made some changes and removed the connection manager, as discussed with sub-team. The refactoring is not finished, but these changes can be pushed to master if it doesn't break any working tests. Jobs are currently pending in jenkins. |
ad4dfdf
to
05a3e0f
Compare
|
Please review the code. I fixed a problem that was identified with the previous set of integration tests, but I'm still waiting for results of this build. Nevertheless, no more changes should be introduced in this PR, only potential fixes of issues. |
|
Removing the connection timeout makes me a bit edgy. It is quite unclear why that's there, perhaps python-ldap doesn't provide a timeout and things hung forever, I'm not sure. I'd double-check that there is a timeout if the remote server either isn't there (probably raises a connection error), or is there but doesn't respond. |
| env._bootstrap(context='installer', log=None) | ||
| env._finalize_core(**dict(constants.DEFAULT_CONFIG)) | ||
|
|
||
| # pylint: disable=no-member |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NACK: you disabled pylint globally until end of file, enable it later or put it to the right line
| @@ -670,7 +670,7 @@ def apply_updates(self): | |||
| # very fatal errors only will raise exception | |||
| raise RuntimeError("Update failed: %s" % e) | |||
| installutils.store_version() | |||
|
|
|||
| self.ldap_disconnect() | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why it is located here, I don't see DS restart in this function, IMO this disconnect should be as close to restart as possible (i.e. in the same function as restart)
|
@rcritten Tomas removed timelimit that was used for repeated connections, it is not used for preventing hangs. (If we talk about the same commit 'ldap refactoring: remove wait/timeout during binds') We added this functionality to DS restart, restart will block code until DS is not ready on LDAPI port. @tomaskrizek I put two inline comments there, otherwise changes make sense. I'll wait for tests results |
| @@ -38,6 +38,8 @@ def _main(): | |||
|
|
|||
| syslog.syslog(syslog.LOG_NOTICE, "certmonger restarted dirsrv instance '%s'" % instance) | |||
|
|
|||
| api.Backend.ldap2.close() | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On ldap2, .disconnect() should be called rather than .close().
| self.conn.set_option(ldap.OPT_X_SASL_NOCANON, ldap.OPT_ON) | ||
|
|
||
| if start_tls: | ||
| self.conn.start_tls_s() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This block should either be moved to _connect(), or the rest of _connect() should be moved here.
05a3e0f
to
b7c9209
Compare
|
In an offline discussion we decided not to push temporary changes to master. Here's the final code for review. |
| self.__time_limit = None | ||
| self.__size_limit = None | ||
| self.__time_limit = float(LDAPClient.time_limit) | ||
| self.__size_limit = float(LDAPClient.size_limit) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Size limit is an int, not float.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok.
| self.conn.start_tls_s() | ||
|
|
||
| @staticmethod | ||
| def IPAdmin_init(host='', port=389, cacert=None, debug=None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a function called IPAdmin rather than a static method of LDAPClient, that way you don't have to modify any of the existing code which calls IPAdmin.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better solution in next push.
| @@ -27,6 +27,7 @@ disable= | |||
| too-many-boolean-expressions, | |||
| too-many-branches, | |||
| too-many-instance-attributes, | |||
| too-many-function-args, | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IPAdmin_init has too many arguments. This check didn't trigger previously when it was in IPAdmin.__init__
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer necessary.
| @@ -1085,7 +1085,7 @@ def simple_bind(self, bind_dn=DN(('cn', 'directory manager')), | |||
| self.conn.simple_bind_s( | |||
| bind_dn, bind_password, server_controls, client_controls) | |||
|
|
|||
| def external_bind(self, user_name, server_controls=None, | |||
| def external_bind(self, user_name=None, server_controls=None, | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the user_name argument altogether, as it always has to be set to the effective username.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok.
| @@ -1068,7 +1069,8 @@ def _connect(self): | |||
|
|
|||
| return conn | |||
|
|
|||
| def simple_bind(self, bind_dn, bind_password, server_controls=None, | |||
| def simple_bind(self, bind_dn=DN(('cn', 'directory manager')), | |||
| bind_password='', server_controls=None, | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would really prefer if both bind_dn and bind_password had to be always specified explicitly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed default values and specified both explicitly in function calls.
| if debug and debug.lower() == "on": | ||
| ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255) | ||
| if cacert is not None: | ||
| ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, cacert) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are global options, you should not really touch them from connection-specific code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When i tried to set OPT_X_TLS_CACERTFILE and OPT_X_TLS_REQUIRE_CERT directly to the ldap connection, ipadiscovery stopped working with the following error message
TLS error -8172:Peer's certificate issuer has been marked as not trusted by the user.
thus preventing ipa-client-install from successfully finishing.
Fixing this issue is out of the scope of this PR. This code was already present, I'm just moving it to some other place.
Merging this PR is a priority. Once that's done, feel free to investigate and submit a fix.
| ldap_client.cacert = cacert | ||
| ldap_client.ldapi = ldapi | ||
| ldap_client.realm = realm | ||
| ldap_client.suffixes = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are all of these really necessary? git grep shows me that .cacert, .realm and .suffixes are not used anywhere and .ldapi is used only in ipa-httpd-kdcproxy. That leaves us with .host and .port, which should preferrably be transformed into properties of LDAPClient rather than set here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved host and port to LDAPClient. Removed the rest.
| @@ -257,14 +257,15 @@ def __common_setup(self, enable_ssl=False): | |||
|
|
|||
| self.step("creating directory server user", create_ds_user) | |||
| self.step("creating directory server instance", self.__create_instance) | |||
| self.step("enabling ldapi", self.__enable_ldapi) | |||
| self.step("configure autobind for root", self.__root_autobind) | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather merge these into __update_dse_ldif, if possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be a nice change, as well as removing _ldap_mod() completely, but it's out of scope for this refactoring.
| def start(self, *args, **kwargs): | ||
| super(DsInstance, self).start(*args, **kwargs) | ||
| api.Backend.ldap2.connect(size_limit=LDAPClient.size_limit, | ||
| time_limit=LDAPClient.time_limit) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are the default limits in ldap2. Why do you set them again here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a temporary change that is refactored in a later commit, the final code calls api.Backend.ldap2.connect().
| self.__size_limit = int(val) | ||
| if val is not None: | ||
| val = int(val) | ||
| self.__size_limit = val | ||
|
|
||
| @size_limit.deleter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should also update the deleters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch.
| @@ -150,7 +150,7 @@ def create_connection( | |||
| Extends backend.Connectible.create_connection. | |||
| """ | |||
| if bind_dn is None: | |||
| bind_dn = DN() | |||
| bind_dn = DN(('cn', 'directory manager')) | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, if bind_dn is None, simple bind should not be attempted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's keep the default here and remove it in simple_bind() instead.
| @@ -505,7 +505,7 @@ def setup_changelog(self, conn): | |||
| def setup_chaining_backend(self, conn): | |||
| chaindn = DN(('cn', 'chaining database'), ('cn', 'plugins'), ('cn', 'config')) | |||
| benamebase = "chaindb" | |||
| urls = [self.to_ldap_url(conn)] | |||
| urls = [self.to_ldap_url()] | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather throw away self.to_ldap_url() and use conn.ldap_uri here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok.
|
In addition to my inline comments:
|
Testing whether it is possible to connect to directory server is already done in RedHatDirectoryService.restart(). https://fedorahosted.org/freeipa/ticket/6461
* Use LDAPClient.simple_bind instead of extra call to IPAdmin.do_simple_bind * Rename binddn to bind_dn * Rename bindpw to bind_password * Explicitly specify bind_dn in all calls https://fedorahosted.org/freeipa/ticket/6461
* Rename do_external_bind to external_bind
* Remove user_name argument in external_bind() and always set it
to effective user name
https://fedorahosted.org/freeipa/ticket/6461
* Rename do_sasl_gssapi_bind to gssapi_bind https://fedorahosted.org/freeipa/ticket/6461
* move IPAdmin methods to LDAPClient
* add extra arguments (cacert, sasl_nocanon) to LDAPClient.__init__()
* add host, port, _protocol to LDAPClient (parsed from ldap_uri)
* create get_ldap_uri() method to create ldap_uri from former
IPAdmin.__init__() arguments
* replace IPAdmin with LDAPClient + get_ldap_uri()
* remove ununsed function argument hostname from
enable_replication_version_checking()
https://fedorahosted.org/freeipa/ticket/6461
Remove directory manager's password from service's constructors https://fedorahosted.org/freeipa/ticket/6461
* enable ldapi and root autobind early during the ds installation * perform these changes using simple_bind with dm_password https://fedorahosted.org/freeipa/ticket/6461
* read realm from config file * configure api.env to use ldapi genrated from realm https://fedorahosted.org/freeipa/ticket/6461
Installation of Certificate Server replica requires directory manager password. Specify it explicitly in function call and pass it in through an argument. https://fedorahosted.org/freeipa/ticket/6461
* Set default time_limit and size_limit in ldap2 to unlimited.
* Set time_limit and size_limit to None in backend. This will respect
ipaconfig values.
https://fedorahosted.org/freeipa/ticket/6461
Connect and/or disconnect api.Backend.ldap2 connection when directory server is stopped/restarted. Checking is ldap2 connection is connected is neccesary for edge cases during ds installation (initial start). https://fedorahosted.org/freeipa/ticket/6461
connect/disconnect api.Backend.ldap2 connection when directory server is started/stopped https://fedorahosted.org/freeipa/ticket/6461
* Create a utility function to restart a directory server and
reconnect the api.Backend.ldap2 connection.
* Use restart_dirsrv instead of knownservices.dirsrv.restart to
ensure api.Backend.ldap2 is reconnected.
https://fedorahosted.org/freeipa/ticket/6461
Remove adhoc connects and disconnects of api.Backend.ldap2. Connection should be established only at the start of the script, destroyed at the end of the script and re-established when directory server is restarted. https://fedorahosted.org/freeipa/ticket/6461
* Move connect to the beggining of the uninstall_check and properly
close the connection at the end of the script.
* Connect to ldap in external CA installation (step2).
https://fedorahosted.org/freeipa/ticket/6461
Diconnect the established connection oncee is it no longer needed. https://fedorahosted.org/freeipa/ticket/6461
Configure ldap connection in LDAPUpdate to use ldapi. https://fedorahosted.org/freeipa/ticket/6461
Remove ldap_connect and ldap_disconnect from services. admin_conn is just an alias to api.Backend.ldap2 and therefore the connection should be managed elsewhere. https://fedorahosted.org/freeipa/ticket/6461
Properly close ldap connection. https://fedorahosted.org/freeipa/ticket/6461
Set default bind_dn to cn=directory manager. https://fedorahosted.org/freeipa/ticket/6461
* ipca-ca-install: Use a single ldap connection for the entire
script. Connecting with ccache in promote is not needed.
* ipa-cacert-manage: Always connect to ldap, since renew and install
are the only options and renew seems to need ldap connection even
for self signed certificates.
* ipa-compat-manage: Use one ldap connection for the entire script.
Replaced try-finally with proper disconnect, code block reindented.
* ipa-csreplica-manage: Properly establish and close the ldap connection.
* ipa-dns-install: Proper connect, disconnect to ldap.
* ipa-kra-install: Proper connect/disconnect for install and uninstall.
* ipa-ldap-update: Proper connect and disconnect to ldap.
* ipa-nis-manage: Proper connect/disconnect for ldap. Try-finally removed
and code block reindented.
* ipa-replica-manage: Proper connect/disconnect to ldap.
* ipa-replica-prepare: Connect added to validate_options(), where api is
initialized and disconnected added at the end of run. Reconnect in
ask_for_options() to validate directory manager password.
* ipa-server-certinstall: Use api.Backend.ldap2 for ldap connections.
* ipa-server-upgrade: Connect to and disconnect from api.Backend.ldap2.
https://fedorahosted.org/freeipa/ticket/6461
Use self.hostname instead of self.conn.host. https://fedorahosted.org/freeipa/ticket/6461
Use conn.ldap_uri everywhere. https://fedorahosted.org/freeipa/ticket/6461
* Remove unused and obsolete function arguments:
* tls_certfile
* tls_keyfile
* debug_level
* Rename tls_cacertfile to cacert (same as name in LDAPClient)
* Set cacert to constants.CACERT by default.
https://fedorahosted.org/freeipa/ticket/6461
ffad1bb
to
37fdf25
Compare
|
Updated commit messages with link to a ticket. |
Design doc: http://www.freeipa.org/page/V4/LDAP_Connection_Management_Refactoring
Many changes in current PR are only temporary for easier refactoring, but feedback is welcome.