ldappool-2.4.1/0000775000175000017500000000000013435060356013357 5ustar zuulzuul00000000000000ldappool-2.4.1/MANIFEST.in0000666000175000017500000000012013435060152015102 0ustar zuulzuul00000000000000include README.rst include MANIFEST.in include CHANGES.rst include CONTRIBUTORS ldappool-2.4.1/test-requirements.txt0000666000175000017500000000102313435060152017610 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. # of appearance. hacking>=1.1.0,<1.2.0 # Apache-2.0 flake8-docstrings==0.2.1.post1 # MIT coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD openstackdocstheme>=1.18.1 # Apache-2.0 stestr>=2.0.0 # Apache-2.0 testresources>=2.0.0 # Apache-2.0/BSD testtools>=2.2.0 # MIT ldappool-2.4.1/CONTRIBUTORS0000666000175000017500000000014113435060152015227 0ustar zuulzuul00000000000000By order of appearance: - Tarek Ziadé - Chris McDonough ldappool-2.4.1/setup.py0000666000175000017500000000200613435060152015063 0ustar zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) ldappool-2.4.1/doc/0000775000175000017500000000000013435060356014124 5ustar zuulzuul00000000000000ldappool-2.4.1/doc/source/0000775000175000017500000000000013435060356015424 5ustar zuulzuul00000000000000ldappool-2.4.1/doc/source/conf.py0000666000175000017500000001610513435060152016722 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # # ldappool documentation build configuration file, created by # sphinx-quickstart on Sun Dec 6 14:19:25 2009. # # This file is execfile()d with the current directory set to its containing # dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. from __future__ import unicode_literals import os import subprocess import sys import warnings import pbr.version sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))) # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.append(os.path.abspath('.')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.intersphinx', 'openstackdocstheme', ] todo_include_todos = True # Add any paths that contain templates here, relative to this directory. # templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = 'ldappool' copyright = 'OpenStack Contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. version_info = pbr.version.VersionInfo('ldappool') # The short X.Y version. version = version_info.version_string() # The full version, including alpha/beta/rc tags. release = version_info.release_string() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = [] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. modindex_common_prefix = ['ldappool.'] # Grouping the document tree for man pages. # List of tuples 'sourcefile', 'target', 'title', 'Authors name', 'manual' #man_pages = [] # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. #html_theme_path = ["."] #html_theme = '_theme' html_theme = 'openstackdocs' # 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 # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". #html_static_path = ['static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. html_last_updated_fmt = '%Y-%m-%d %H:%M' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'ldappooldoc' # -- Options for LaTeX output ------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]) # . latex_documents = [ ('index', 'ldappool.tex', 'ldappool Documentation', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True intersphinx_mapping = { 'python': ('http://docs.python.org/', None), } # -- Options for openstackdocstheme ------------------------------------------- repository_name = 'openstack/ldappool' bug_project = 'ldappool' bug_tag = '' ldappool-2.4.1/doc/source/history.rst0000666000175000017500000000003513435060152017651 0ustar zuulzuul00000000000000.. include:: ../../ChangeLog ldappool-2.4.1/doc/source/index.rst0000666000175000017500000000116613435060152017265 0ustar zuulzuul00000000000000ldappool ======== Contents: .. toctree:: :maxdepth: 1 api/modules Release Notes ============= .. toctree:: :maxdepth: 1 history Contributing ============ Code is hosted `on GitHub`_. Submit bugs to the Keystone project on `Launchpad`_. Submit code to the ``openstack/ldappool`` project using `Gerrit`_. .. _on GitHub: https://github.com/openstack/ldappool .. _Launchpad: https://launchpad.net/ldappool .. _Gerrit: https://docs.openstack.org/infra/manual/developers.html#development-workflow Run tests with ``tox``. Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` ldappool-2.4.1/doc/requirements.txt0000666000175000017500000000014513435060152017404 0ustar zuulzuul00000000000000openstackdocstheme>=1.18.1 # Apache-2.0 reno>=2.5.0 # Apache-2.0 sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD ldappool-2.4.1/doc/Makefile0000666000175000017500000000614613435060152015567 0ustar zuulzuul00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXSOURCE = source PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SPHINXSOURCE) .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/keystoneauth.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/keystoneauth.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." ldappool-2.4.1/requirements.txt0000666000175000017500000000040313435060152016634 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. python-ldap>=3.0.0 # PSF PrettyTable<0.8,>=0.7.2 ldappool-2.4.1/AUTHORS0000664000175000017500000000210413435060356014424 0ustar zuulzuul0000000000000098k <18552437190@163.com> Andreas Jaeger Charles Duffy Charles Duffy Chris McDonough Colleen Murphy Colleen Murphy Colleen Murphy Corey Bryant Dirk Mueller Doug Hellmann Lorenzo M. Catucci Monty Taylor Morgan Fainberg Nathan Kinder Nick Wilburn Samriddhi Jain Steve Martinelli Tarek Ziade Tarek Ziade Tarek Ziadé Tony Breeds Van Hung Pham Vieri <15050873171@163.com> Vu Cong Tuan huang.zhiping pallavi qingszhao ricolin zhouxinyong ldappool-2.4.1/releasenotes/0000775000175000017500000000000013435060356016050 5ustar zuulzuul00000000000000ldappool-2.4.1/releasenotes/source/0000775000175000017500000000000013435060356017350 5ustar zuulzuul00000000000000ldappool-2.4.1/releasenotes/source/conf.py0000666000175000017500000002175013435060152020650 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # ldappool Release Notes documentation build configuration file, created # by sphinx-quickstart on Tue Nov 3 17:40:50 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'openstackdocstheme', 'reno.sphinxext', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'ldappool Release Notes' copyright = u'2018, ldappool Developers' # Release notes are version independent. # The short X.Y version. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'openstackdocs' # 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 # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' html_last_updated_fmt = '%Y-%m-%d %H:%M' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'ldappoolReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'ldappoolReleaseNotes.tex', u'ldappool Release Notes Documentation', u'ldappool Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'ldappoolreleasenotes', u'ldappool Release Notes Documentation', [u'ldappool Developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'ldappoolReleaseNotes', u'ldappool Release Notes Documentation', u'ldappool Developers', 'ldappoolReleaseNotes', 'Authentication plugins for the OpenStack Identity service.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] # -- Options for openstackdocstheme ------------------------------------------- repository_name = 'openstack/ldappool' bug_project = 'ldappool' bug_tag = 'doc' ldappool-2.4.1/releasenotes/source/index.rst0000666000175000017500000000016213435060152021204 0ustar zuulzuul00000000000000====================== ldappool Release Notes ====================== .. toctree:: :maxdepth: 1 unreleased ldappool-2.4.1/releasenotes/source/unreleased.rst0000666000175000017500000000016013435060152022222 0ustar zuulzuul00000000000000============================== Current Series Release Notes ============================== .. release-notes:: ldappool-2.4.1/releasenotes/source/_static/0000775000175000017500000000000013435060356020776 5ustar zuulzuul00000000000000ldappool-2.4.1/releasenotes/source/_static/.placeholder0000666000175000017500000000000013435060152023243 0ustar zuulzuul00000000000000ldappool-2.4.1/README.rst0000666000175000017500000000626513435060152015053 0ustar zuulzuul00000000000000ldappool ======== A simple connector pool for python-ldap. The pool keeps LDAP connectors alive and let you reuse them, drastically reducing the time spent to initiate a ldap connection. The pool has useful features like: - transparent reconnection on failures or server restarts - configurable pool size and connectors timeouts - configurable max lifetime for connectors - a context manager to simplify acquiring and releasing a connector **You need python-ldap in order to use this library** Quickstart :::::::::: To work with the pool, you just need to create it, then use it as a context manager with the *connection* method:: from ldappool import ConnectionManager cm = ConnectionManager('ldap://localhost') with cm.connection('uid=adminuser,ou=logins,dc=mozilla', 'password') as conn: .. do something with conn .. The connector returned by *connection* is a LDAPObject, that's binded to the server. See https://pypi.org/project/python-ldap/ for details on how to use a connector. It is possible to check the state of the pool by representing the pool as a string:: from ldappool import ConnectionManager cm = ConnectionManager('ldap://localhost', size=2) .. do something with cm .. print(cm) This will result in output similar to this table:: +--------------+-----------+----------+------------------+--------------------+------------------------------+ | Slot (2 max) | Connected | Active | URI | Lifetime (600 max) | Bind DN | +--------------+-----------+----------+------------------+--------------------+------------------------------+ | 1 | connected | inactive | ldap://localhost | 0.00496101379395 | uid=tuser,dc=example,dc=test | | 2 | connected | inactive | ldap://localhost | 0.00532603263855 | uid=tuser,dc=example,dc=test | +--------------+-----------+----------+------------------+--------------------+------------------------------+ ConnectionManager options ::::::::::::::::::::::::: Here are the options you can use when instanciating the pool: - **uri**: ldap server uri **[mandatory]** - **bind**: default bind that will be used to bind a connector. **default: None** - **passwd**: default password that will be used to bind a connector. **default: None** - **size**: pool size. **default: 10** - **retry_max**: number of attempts when a server is down. **default: 3** - **retry_delay**: delay in seconds before a retry. **default: .1** - **use_tls**: activate TLS when connecting. **default: False** - **timeout**: connector timeout. **default: -1** - **use_pool**: activates the pool. If False, will recreate a connector each time. **default: True** The **uri** option will accept a comma or whitespace separated list of LDAP server URIs to allow for failover behavior when connection errors are encountered. Connections will be attempted against the servers in order, with **retry_max** attempts per URI before failing over to the next server. The **connection** method takes two options: - **bind**: bind used to connect. If None, uses the pool default's. **default: None** - **passwd**: password used to connect. If None, uses the pool default's. **default: None** ldappool-2.4.1/CHANGES.rst0000666000175000017500000000036513435060152015161 0ustar zuulzuul00000000000000Releases :::::::: 1.1 - ? ------- ??? 1.0 - 2012-02-27 ---------------- - Fix ``use_tls`` flag to ConnectionManager; it previously was always set ``False`` no matter what was passed. 0.9 - 2011-10-28 ---------------- - initial release. ldappool-2.4.1/.stestr.conf0000666000175000017500000000006113435060152015621 0ustar zuulzuul00000000000000[DEFAULT] test_path=./ldappool/tests top_dir=./ ldappool-2.4.1/tox.ini0000666000175000017500000000401513435060156014672 0ustar zuulzuul00000000000000[tox] minversion = 2.0 skipsdist = True envlist = py27,py35,py36,py37,pep8,cover,docs,releasenotes [testenv] usedevelop = True install_command = pip install {opts} {packages} setenv = VIRTUAL_ENV={envdir} OS_STDOUT_NOCAPTURE=False OS_STDERR_NOCAPTURE=False deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = find . -type f -name "*.pyc" -delete stestr run --slowest {posargs} whitelist_externals = find [testenv:pep8] basepython = python3 commands = flake8 [testenv:venv] basepython = python3 commands = {posargs} [testenv:cover] basepython = python3 setenv = PYTHON=coverage run --source ldappool --parallel-mode commands = stestr run {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml [flake8] # D100: Missing docstring in public module # D101: Missing docstring in public class # D102: Missing docstring in public method # D103: Missing docstring in public function # D104: Missing docstring in public package # D105: Missing docstring in magic method # D200: One-line docstring should fit on one line with quotes # D210: No whitespaces allowed surrounding docstring text # D401: First line should be in imperative mood ignore = D100,D101,D102,D104,D105,D200,D210,D401 show-source = True exclude = .venv,.tox,dist,doc,*egg,build [testenv:docs] basepython = python3 commands= python setup.py build_sphinx [testenv:releasenotes] basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:lower-constraints] basepython = python3 deps = -c{toxinidir}/lower-constraints.txt -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt ldappool-2.4.1/PKG-INFO0000664000175000017500000001100613435060356014452 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: ldappool Version: 2.4.1 Summary: A simple connector pool for python-ldap. Home-page: https://git.openstack.org/cgit/openstack/ldappool Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: ldappool ======== A simple connector pool for python-ldap. The pool keeps LDAP connectors alive and let you reuse them, drastically reducing the time spent to initiate a ldap connection. The pool has useful features like: - transparent reconnection on failures or server restarts - configurable pool size and connectors timeouts - configurable max lifetime for connectors - a context manager to simplify acquiring and releasing a connector **You need python-ldap in order to use this library** Quickstart :::::::::: To work with the pool, you just need to create it, then use it as a context manager with the *connection* method:: from ldappool import ConnectionManager cm = ConnectionManager('ldap://localhost') with cm.connection('uid=adminuser,ou=logins,dc=mozilla', 'password') as conn: .. do something with conn .. The connector returned by *connection* is a LDAPObject, that's binded to the server. See https://pypi.org/project/python-ldap/ for details on how to use a connector. It is possible to check the state of the pool by representing the pool as a string:: from ldappool import ConnectionManager cm = ConnectionManager('ldap://localhost', size=2) .. do something with cm .. print(cm) This will result in output similar to this table:: +--------------+-----------+----------+------------------+--------------------+------------------------------+ | Slot (2 max) | Connected | Active | URI | Lifetime (600 max) | Bind DN | +--------------+-----------+----------+------------------+--------------------+------------------------------+ | 1 | connected | inactive | ldap://localhost | 0.00496101379395 | uid=tuser,dc=example,dc=test | | 2 | connected | inactive | ldap://localhost | 0.00532603263855 | uid=tuser,dc=example,dc=test | +--------------+-----------+----------+------------------+--------------------+------------------------------+ ConnectionManager options ::::::::::::::::::::::::: Here are the options you can use when instanciating the pool: - **uri**: ldap server uri **[mandatory]** - **bind**: default bind that will be used to bind a connector. **default: None** - **passwd**: default password that will be used to bind a connector. **default: None** - **size**: pool size. **default: 10** - **retry_max**: number of attempts when a server is down. **default: 3** - **retry_delay**: delay in seconds before a retry. **default: .1** - **use_tls**: activate TLS when connecting. **default: False** - **timeout**: connector timeout. **default: -1** - **use_pool**: activates the pool. If False, will recreate a connector each time. **default: True** The **uri** option will accept a comma or whitespace separated list of LDAP server URIs to allow for failover behavior when connection errors are encountered. Connections will be attempted against the servers in order, with **retry_max** attempts per URI before failing over to the next server. The **connection** method takes two options: - **bind**: bind used to connect. If None, uses the pool default's. **default: None** - **passwd**: password used to connect. If None, uses the pool default's. **default: None** Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 ldappool-2.4.1/ChangeLog0000664000175000017500000000523013435060356015131 0ustar zuulzuul00000000000000CHANGES ======= 2.4.1 ----- * Add py37 tox env * add python 3.7 unit test job * Use template for lower-constraints * Fix releasenotes build * Change openstack-dev to openstack-discuss * Replacing the HTTP protocal with HTTPS in index.rst * Add py36 tox environment * Add release notes jobs 2.4.0 ----- * Allow pool status to be printed as a table * Add plumbing to support reno release notes * Handle retry logic for timeouts with multiple LDAP servers * Improve connection retry logging 2.3.1 ----- * PY3: switch to using unicode text values * Don't quote {posargs} in tox.ini * Removed older version of python added 3.5 * add python 3.6 unit test job * import zuul job settings from project-config * fix tox python3 overrides * fix ldappool bad password retry logic 2.3.0 ----- * Add author email to setup.cfg * Switch to python-ldap again * Bump to hacking 1.1.x * Switch to stestr * add lower-constraints job * Updated from global requirements * Updated from global requirements 2.2.0 ----- * Updated from global requirements * Avoid tox\_install.sh for constraints support * Updated from global requirements * Updated from global requirements * Updated from global requirements * Turn on warning-is-error for sphinx build * Switch from oslosphinx to openstackdocstheme * Fix html\_last\_updated\_fmt for Python3 2.1.0 ----- * Updated from global requirements * Updated from global requirements * Updated from global requirements * Don't call start\_tls\_s() twice * [Fix gate]Update test requirement * Add Constraints support * update README to reflect actual ldap dependency * Expose SERVER\_DOWN if connection fails * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements 2.0.0 ----- * Add py3 info to setup.cfg * Updated from global requirements * make ldappool py3 compatible * use standard docstring convention for parameters * Use standard-library logging to record errors * Raise an explicit BackendError on TLS failures * Fix pool\_full race condition * additional files to ignore in .gitignore * Fix license in setup.py * add .gitreview and fix ldappool gate * Add test-requirements for py27 testing * PEP8 fixes * Add support for tox unit testing * Initialize conn in \_create\_connector (fixes: #7) * Use setuptools when available * #4: UTF-8 encode passwd only when set * starting 1.1 1.0 --- * raised version * preparing 1.0 * fix use\_tls flag * packaging tweaks * added a MANIFEST template * added a few keywords for pypi indexation * simplified setup * more docs * initial import of server-core's ldappool * first commit ldappool-2.4.1/.zuul.yaml0000666000175000017500000000037713435060152015323 0ustar zuulzuul00000000000000- project: templates: - check-requirements - openstack-lower-constraints-jobs - openstack-python-jobs - openstack-python35-jobs - openstack-python36-jobs - openstack-python37-jobs - release-notes-jobs-python3 ldappool-2.4.1/ldappool.egg-info/0000775000175000017500000000000013435060356016663 5ustar zuulzuul00000000000000ldappool-2.4.1/ldappool.egg-info/not-zip-safe0000664000175000017500000000000113435060356021111 0ustar zuulzuul00000000000000 ldappool-2.4.1/ldappool.egg-info/requires.txt0000664000175000017500000000005313435060356021261 0ustar zuulzuul00000000000000python-ldap>=3.0.0 PrettyTable<0.8,>=0.7.2 ldappool-2.4.1/ldappool.egg-info/top_level.txt0000664000175000017500000000001113435060356021405 0ustar zuulzuul00000000000000ldappool ldappool-2.4.1/ldappool.egg-info/dependency_links.txt0000664000175000017500000000000113435060356022731 0ustar zuulzuul00000000000000 ldappool-2.4.1/ldappool.egg-info/PKG-INFO0000664000175000017500000001100613435060356017756 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: ldappool Version: 2.4.1 Summary: A simple connector pool for python-ldap. Home-page: https://git.openstack.org/cgit/openstack/ldappool Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: ldappool ======== A simple connector pool for python-ldap. The pool keeps LDAP connectors alive and let you reuse them, drastically reducing the time spent to initiate a ldap connection. The pool has useful features like: - transparent reconnection on failures or server restarts - configurable pool size and connectors timeouts - configurable max lifetime for connectors - a context manager to simplify acquiring and releasing a connector **You need python-ldap in order to use this library** Quickstart :::::::::: To work with the pool, you just need to create it, then use it as a context manager with the *connection* method:: from ldappool import ConnectionManager cm = ConnectionManager('ldap://localhost') with cm.connection('uid=adminuser,ou=logins,dc=mozilla', 'password') as conn: .. do something with conn .. The connector returned by *connection* is a LDAPObject, that's binded to the server. See https://pypi.org/project/python-ldap/ for details on how to use a connector. It is possible to check the state of the pool by representing the pool as a string:: from ldappool import ConnectionManager cm = ConnectionManager('ldap://localhost', size=2) .. do something with cm .. print(cm) This will result in output similar to this table:: +--------------+-----------+----------+------------------+--------------------+------------------------------+ | Slot (2 max) | Connected | Active | URI | Lifetime (600 max) | Bind DN | +--------------+-----------+----------+------------------+--------------------+------------------------------+ | 1 | connected | inactive | ldap://localhost | 0.00496101379395 | uid=tuser,dc=example,dc=test | | 2 | connected | inactive | ldap://localhost | 0.00532603263855 | uid=tuser,dc=example,dc=test | +--------------+-----------+----------+------------------+--------------------+------------------------------+ ConnectionManager options ::::::::::::::::::::::::: Here are the options you can use when instanciating the pool: - **uri**: ldap server uri **[mandatory]** - **bind**: default bind that will be used to bind a connector. **default: None** - **passwd**: default password that will be used to bind a connector. **default: None** - **size**: pool size. **default: 10** - **retry_max**: number of attempts when a server is down. **default: 3** - **retry_delay**: delay in seconds before a retry. **default: .1** - **use_tls**: activate TLS when connecting. **default: False** - **timeout**: connector timeout. **default: -1** - **use_pool**: activates the pool. If False, will recreate a connector each time. **default: True** The **uri** option will accept a comma or whitespace separated list of LDAP server URIs to allow for failover behavior when connection errors are encountered. Connections will be attempted against the servers in order, with **retry_max** attempts per URI before failing over to the next server. The **connection** method takes two options: - **bind**: bind used to connect. If None, uses the pool default's. **default: None** - **passwd**: password used to connect. If None, uses the pool default's. **default: None** Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 ldappool-2.4.1/ldappool.egg-info/SOURCES.txt0000664000175000017500000000134713435060356020554 0ustar zuulzuul00000000000000.stestr.conf .zuul.yaml AUTHORS CHANGES.rst CONTRIBUTORS ChangeLog MANIFEST.in README.rst lower-constraints.txt requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/Makefile doc/requirements.txt doc/source/conf.py doc/source/history.rst doc/source/index.rst ldappool/__init__.py ldappool.egg-info/PKG-INFO ldappool.egg-info/SOURCES.txt ldappool.egg-info/dependency_links.txt ldappool.egg-info/not-zip-safe ldappool.egg-info/pbr.json ldappool.egg-info/requires.txt ldappool.egg-info/top_level.txt ldappool/tests/__init__.py ldappool/tests/test_ldapconnection.py ldappool/tests/test_ldappool.py releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholderldappool-2.4.1/ldappool.egg-info/pbr.json0000664000175000017500000000005613435060356020342 0ustar zuulzuul00000000000000{"git_version": "01d0eb3", "is_release": true}ldappool-2.4.1/setup.cfg0000666000175000017500000000161013435060356015200 0ustar zuulzuul00000000000000[metadata] name = ldappool summary = A simple connector pool for python-ldap. description-file = README.rst author = OpenStack author-email = openstack-discuss@lists.openstack.org home-page = https://git.openstack.org/cgit/openstack/ldappool classifier = Intended Audience :: Developers License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 [files] packages = ldappool [build_sphinx] source-dir = doc/source build-dir = doc/build all_files = 1 warning-is-error = 1 [upload_sphinx] upload-dir = doc/build/html [pbr] autodoc_tree_index_modules = True autodoc_tree_excludes = setup.py ldappool/tests/ [wheel] universal = 1 [egg_info] tag_build = tag_date = 0 ldappool-2.4.1/lower-constraints.txt0000666000175000017500000000115713435060152017615 0ustar zuulzuul00000000000000alabaster==0.7.10 Babel==2.3.4 coverage==4.0 docutils==0.11 dulwich==0.15.0 extras==1.0.0 fixtures==3.0.0 flake8-docstrings==0.2.1.post1 flake8==2.5.5 hacking==0.12.0 imagesize==0.7.1 Jinja2==2.10 linecache2==1.0.0 MarkupSafe==1.0 mccabe==0.2.1 openstackdocstheme==1.18.1 pbr==2.0.0 pep257==0.7.0 pep8==1.5.7 prettytable==0.7.2 pyflakes==0.8.1 Pygments==2.2.0 python-ldap==3.0.0 python-mimeparse==1.6.0 python-subunit==1.0.0 pytz==2013.6 requests==2.14.2 six==1.10.0 snowballstemmer==1.2.1 Sphinx==1.6.2 sphinxcontrib-websupport==1.0.1 stestr==2.0.0 testresources==2.0.0 testtools==2.2.0 traceback2==1.4.0 unittest2==1.1.0 ldappool-2.4.1/ldappool/0000775000175000017500000000000013435060356015171 5ustar zuulzuul00000000000000ldappool-2.4.1/ldappool/__init__.py0000666000175000017500000003611313435060152017302 0ustar zuulzuul00000000000000# ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1/GPL 2.0/LGPL 2.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is Sync Server # # The Initial Developer of the Original Code is the Mozilla Foundation. # Portions created by the Initial Developer are Copyright (C) 2010 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Tarek Ziade (tarek@mozilla.com) # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your # decision by deleting the provisions above and replace them with the notice # and other provisions required by the GPL or the LGPL. If you do not delete # the provisions above, a recipient may use your version of this file under # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** """ LDAP Connection Pool. """ import codecs from contextlib import contextmanager import logging from threading import RLock import time import ldap from ldap.ldapobject import ReconnectLDAPObject from prettytable import PrettyTable import re import six from six import PY2 log = logging.getLogger(__name__) _utf8_encoder = codecs.getencoder('utf-8') def utf8_encode(value): """Encode a basestring to UTF-8. If the string is unicode encode it to UTF-8, if the string is str then assume it's already encoded. Otherwise raise a TypeError. :param value: A basestring :returns: UTF-8 encoded version of value :raises TypeError: If value is not basestring """ if isinstance(value, six.text_type): return _utf8_encoder(value)[0] elif isinstance(value, six.binary_type): return value else: raise TypeError("bytes or Unicode expected, got %s" % type(value).__name__) class MaxConnectionReachedError(Exception): pass class BackendError(Exception): def __init__(self, msg, backend): self.bacend = backend Exception.__init__(self, msg) class StateConnector(ReconnectLDAPObject): """Just remembers who is connected, and if connected.""" def __init__(self, *args, **kw): ReconnectLDAPObject.__init__(self, *args, **kw) self.connected = False self.who = '' self.cred = '' self._connection_time = None def get_lifetime(self): """Returns the lifetime of the connection on the server in seconds.""" if self._connection_time is None: return 0 return time.time() - self._connection_time def simple_bind_s(self, who='', cred='', serverctrls=None, clientctrls=None): res = ReconnectLDAPObject.simple_bind_s(self, who, cred, serverctrls, clientctrls) self.connected = True self.who = who self.cred = cred if self._connection_time is None: self._connection_time = time.time() return res def unbind_ext_s(self, serverctrls=None, clientctrls=None): try: return ReconnectLDAPObject.unbind_ext_s(self, serverctrls, clientctrls) finally: self.connected = False self.who = None self.cred = None def add_s(self, *args, **kwargs): return self._apply_method_s(ReconnectLDAPObject.add_s, *args, **kwargs) def modify_s(self, *args, **kwargs): return self._apply_method_s(ReconnectLDAPObject.modify_s, *args, **kwargs) def __str__(self): res = 'LDAP Connector' if self.connected: res += ' (connected)' else: res += ' (disconnected)' if self.who != '': res += ' - who: %r' % self.who if self._uri != '': res += ' - uri: %r' % self._uri return res class ConnectionManager(object): """LDAP Connection Manager. Provides a context manager for LDAP connectors. """ def __init__(self, uri, bind=None, passwd=None, size=10, retry_max=3, retry_delay=.1, use_tls=False, timeout=-1, connector_cls=StateConnector, use_pool=True, max_lifetime=600): self._pool = [] self.size = size self.retry_max = retry_max self.retry_delay = retry_delay self.uri = uri self.bind = bind self.passwd = passwd self._pool_lock = RLock() self.use_tls = use_tls self.timeout = timeout self.connector_cls = connector_cls self.use_pool = use_pool self.max_lifetime = max_lifetime def __len__(self): return len(self._pool) def _match(self, bind, passwd): if passwd is not None: if PY2: passwd = utf8_encode(passwd) with self._pool_lock: inactives = [] for conn in reversed(self._pool): # already in usage if conn.active: continue # let's check the lifetime if conn.get_lifetime() > self.max_lifetime: # this connector has lived for too long, # we want to unbind it and remove it from the pool try: conn.unbind_s() except Exception: log.debug('Failure attempting to unbind after ' 'timeout; should be harmless', exc_info=True) self._pool.remove(conn) continue # we found a connector for this bind if conn.who == bind and conn.cred == passwd: conn.active = True return conn inactives.append(conn) # no connector was available, let's rebind the latest inactive one if len(inactives) > 0: for conn in inactives: try: self._bind(conn, bind, passwd) return conn except Exception: log.debug('Removing connection from pool after ' 'failure to rebind', exc_info=True) self._pool.remove(conn) return None # There are no connector that match return None def _bind(self, conn, bind, passwd): # let's bind if self.use_tls: try: conn.start_tls_s() except Exception: raise BackendError('Could not activate TLS on established ' 'connection with %s' % self.uri, backend=conn) if bind is not None: conn.simple_bind_s(bind, passwd) conn.active = True def _create_connector(self, bind, passwd): """Creates a connector, binds it, and returns it. :param bind: user login :type bind: string :param passwd: user password :type passwd: string :returns: StateConnector :raises BackendError: If unable to connect to LDAP """ connected = False if passwd is not None: if PY2: passwd = utf8_encode(passwd) # If multiple server URIs have been provided, loop through # each one in turn in case of connection failures (server down, # timeout, etc.). URIs can be delimited by either commas or # whitespace. for server in re.split('[\s,]+', self.uri): tries = 0 exc = None conn = None # trying retry_max times in a row with a fresh connector while tries < self.retry_max and not connected: try: log.debug('Attempting to create a new connector ' 'to %s (attempt %d)', server, tries + 1) conn = self.connector_cls(server, retry_max=self.retry_max, retry_delay=self.retry_delay) conn.timeout = self.timeout self._bind(conn, bind, passwd) connected = True except ldap.INVALID_CREDENTIALS as error: # Treat this as a hard failure instead of retrying to # avoid locking out the LDAP account due to successive # failed bind attempts. We also don't want to try # connecting to additional servers if multiple URIs were # provide, as failed bind attempts may be replicated # across multiple LDAP servers. exc = error log.error('Invalid credentials. Cancelling retry', exc_info=True) raise exc except ldap.LDAPError as error: exc = error tries += 1 if tries < self.retry_max: log.info('Failure attempting to create and bind ' 'connector; will retry after %r seconds', self.retry_delay, exc_info=True) time.sleep(self.retry_delay) else: log.error('Failure attempting to create and bind ' 'connector', exc_info=True) # We successfully connected to one of the servers, so # we can just return the connection and stop processing # any additional URIs. if connected: return conn # We failed to connect to any of the servers, # so raise an appropriate exception. if not connected: if isinstance(exc, (ldap.NO_SUCH_OBJECT, ldap.SERVER_DOWN, ldap.TIMEOUT)): raise exc # that's something else raise BackendError(str(exc), backend=conn) def _get_connection(self, bind=None, passwd=None): if bind is None: bind = self.bind if passwd is None: passwd = self.passwd if self.use_pool: # let's try to recycle an existing one conn = self._match(bind, passwd) if conn is not None: return conn # the pool is full if len(self._pool) >= self.size: raise MaxConnectionReachedError(self.uri) # we need to create a new connector conn = self._create_connector(bind, passwd) # adding it to the pool if self.use_pool: with self._pool_lock: self._pool.append(conn) else: # with no pool, the connector is always active conn.active = True return conn def _release_connection(self, connection): if self.use_pool: with self._pool_lock: if not connection.connected: # unconnected connector, let's drop it self._pool.remove(connection) else: # can be reused - let's mark is as not active connection.active = False # done. return else: connection.active = False # let's try to unbind it try: connection.unbind_ext_s() except ldap.LDAPError: # avoid error on invalid state log.debug('Failure attempting to unbind on release; ' 'should be harmless', exc_info=True) @contextmanager def connection(self, bind=None, passwd=None): """Creates a context'ed connector, binds it, and returns it. :param bind: user login :type bind: string :param passwd: user password :type passwd: string :returns: StateConnector :raises MaxConnectionReachedError: If unable to connect to LDAP """ tries = 0 conn = None while tries < self.retry_max: try: conn = self._get_connection(bind, passwd) except MaxConnectionReachedError: tries += 1 time.sleep(0.1) # removing the first inactive connector going backward with self._pool_lock: reversed_list = reversed(list(enumerate(self._pool))) for index, conn_ in reversed_list: if not conn_.active: self._pool.pop(index) break else: break if conn is None: raise MaxConnectionReachedError(self.uri) try: yield conn finally: self._release_connection(conn) def purge(self, bind, passwd=None): """Purge a connector. :param bind: user login :type bind: string :param passwd: user password :type passwd: string """ if self.use_pool: return if passwd is not None: passwd = utf8_encode(passwd) with self._pool_lock: for conn in list(self._pool): if conn.who != bind: continue if passwd is not None and conn.cred == passwd: continue # let's drop it try: conn.unbind_ext_s() except ldap.LDAPError: # invalid state log.debug('Failure attempting to unbind on purge; ' 'should be harmless', exc_info=True) self._pool.remove(conn) def __str__(self): table = PrettyTable() table.field_names = ['Slot (%d max)' % self.size, 'Connected', 'Active', 'URI', 'Lifetime (%d max)' % self.max_lifetime, 'Bind DN'] with self._pool_lock: for slot, conn in enumerate(self._pool): table.add_row([ slot + 1, 'connected' if conn.connected else 'not connected', 'active' if conn.active else 'inactive', conn._uri, conn.get_lifetime(), conn.who]) return str(table) ldappool-2.4.1/ldappool/tests/0000775000175000017500000000000013435060356016333 5ustar zuulzuul00000000000000ldappool-2.4.1/ldappool/tests/__init__.py0000666000175000017500000000000013435060152020426 0ustar zuulzuul00000000000000ldappool-2.4.1/ldappool/tests/test_ldapconnection.py0000666000175000017500000002535713435060152022754 0ustar zuulzuul00000000000000# ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1/GPL 2.0/LGPL 2.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is Sync Server # # The Initial Developer of the Original Code is the Mozilla Foundation. # Portions created by the Initial Developer are Copyright (C) 2011 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Tarek Ziade (tarek@mozilla.com) # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your # decision by deleting the provisions above and replace them with the notice # and other provisions required by the GPL or the LGPL. If you do not delete # the provisions above, a recipient may use your version of this file under # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** import unittest import ldap import ldappool def _bind(self, who='', cred='', **kw): self.connected = True self.who = who self.cred = cred return 1 def _bind_fails(self, who='', cred='', **kw): raise ldap.LDAPError('LDAP connection invalid') def _bind_fails_server_down(self, who='', cred='', **kw): raise ldap.SERVER_DOWN('LDAP connection invalid') def _bind_fails_server_down_failover(self, who='', cred='', **kw): # Raise a server down error unless the URI is 'ldap://GOOD' if self._uri == 'ldap://GOOD': self.connected = True self.who = who self.cred = cred return 1 else: raise ldap.SERVER_DOWN('LDAP connection invalid') def _bind_fails_timeout(self, who='', cred='', **kw): raise ldap.TIMEOUT('LDAP connection timeout') def _bind_fails_timeout_failover(self, who='', cred='', **kw): # Raise a timeout error unless the URI is 'ldap://GOOD' if self._uri == 'ldap://GOOD': self.connected = True self.who = who self.cred = cred return 1 else: raise ldap.TIMEOUT('LDAP connection timeout') def _bind_fails_invalid_credentials(self, who='', cred='', **kw): raise ldap.INVALID_CREDENTIALS('LDAP connection invalid') def _bind_fails_invalid_credentials_failover(self, who='', cred='', **kw): # Raise invalid credentials erorr unless the URI is 'ldap://GOOD' if self._uri == 'ldap://GOOD': self.connected = True self.who = who self.cred = cred return 1 else: raise ldap.INVALID_CREDENTIALS('LDAP connection invalid') def _start_tls_s(self): if self.start_tls_already_called_flag: raise ldap.LOCAL_ERROR else: self.start_tls_already_called_flag = True class TestLDAPConnection(unittest.TestCase): def setUp(self): self.old = ldappool.StateConnector.simple_bind_s ldappool.StateConnector.simple_bind_s = _bind self.old_start_tls_s = ldappool.StateConnector.start_tls_s ldappool.StateConnector.start_tls_s = _start_tls_s ldappool.StateConnector.start_tls_already_called_flag = False def tearDown(self): ldappool.StateConnector.simple_bind_s = self.old ldappool.StateConnector.start_tls_s = self.old_start_tls_s def test_connection(self): uri = '' dn = 'uid=adminuser,ou=logins,dc=mozilla' passwd = 'adminuser' cm = ldappool.ConnectionManager(uri, dn, passwd, use_pool=True, size=2) self.assertEqual(len(cm), 0) with cm.connection('dn', 'pass'): self.assertEqual(len(cm), 1) # if we ask a new one the pool will grow with cm.connection('dn', 'pass'): self.assertEqual(len(cm), 2) # every connector is marked active self.assertTrue(cm._pool[0].active) self.assertTrue(cm._pool[1].active) # if we ask a new one the pool is full try: with cm.connection('dn', 'pass'): pass except ldappool.MaxConnectionReachedError: pass else: raise AssertionError() # down to one active self.assertFalse(cm._pool[1].active) self.assertTrue(cm._pool[0].active) # if we ask a new one the pool is full # but we get the inactive one with cm.connection('dn', 'pass'): self.assertEqual(len(cm), 2) self.assertFalse(cm._pool[1].active) self.assertTrue(cm._pool[0].active) # if we ask a new one the pool is full # but we get the inactive one, and rebind it with cm.connection('dn2', 'pass'): self.assertEqual(len(cm), 2) # the pool is still 2 self.assertEqual(len(cm), 2) # every connector is marked inactive self.assertFalse(cm._pool[0].active) self.assertFalse(cm._pool[1].active) def test_tls_connection(self): uri = '' dn = 'uid=adminuser,ou=logins,dc=mozilla' passwd = 'adminuser' cm = ldappool.ConnectionManager(uri, dn, passwd, use_pool=True, size=2, use_tls=True) with cm.connection(): pass def test_simple_bind_fails(self): unbinds = [] def _unbind(self): unbinds.append(1) # the binding fails with an LDAPError ldappool.StateConnector.simple_bind_s = _bind_fails_server_down ldappool.StateConnector.unbind_s = _unbind uri = '' dn = 'uid=adminuser,ou=logins,dc=mozilla' passwd = 'adminuser' cm = ldappool.ConnectionManager(uri, dn, passwd, use_pool=True, size=2) self.assertEqual(len(cm), 0) try: with cm.connection('dn', 'pass'): pass except ldap.SERVER_DOWN: pass else: raise AssertionError() def test_simple_bind_fails_failover(self): unbinds = [] def _unbind(self): unbinds.append(1) # the binding to any server other than 'ldap://GOOD' fails # with ldap.SERVER_DOWN ldappool.StateConnector.simple_bind_s = \ _bind_fails_server_down_failover ldappool.StateConnector.unbind_s = _unbind uri = 'ldap://BAD,ldap://GOOD' dn = 'uid=adminuser,ou=logins,dc=mozilla' passwd = 'adminuser' cm = ldappool.ConnectionManager(uri, dn, passwd, use_pool=True, size=2) self.assertEqual(len(cm), 0) try: with cm.connection('dn', 'pass') as conn: # Ensure we failed over to the second URI self.assertTrue(conn.active) self.assertEqual(conn._uri, 'ldap://GOOD') pass except Exception: raise AssertionError() def test_simple_bind_fails_timeout(self): unbinds = [] def _unbind(self): unbinds.append(1) # the binding fails with ldap.TIMEOUT ldappool.StateConnector.simple_bind_s = _bind_fails_timeout ldappool.StateConnector.unbind_s = _unbind uri = '' dn = 'uid=adminuser,ou=logins,dc=mozilla' passwd = 'adminuser' cm = ldappool.ConnectionManager(uri, dn, passwd, use_pool=True, size=2) self.assertEqual(len(cm), 0) try: with cm.connection('dn', 'pass'): pass except ldap.TIMEOUT: pass else: raise AssertionError() def test_simple_bind_fails_timeout_failover(self): unbinds = [] def _unbind(self): unbinds.append(1) # the binding to any server other than 'ldap://GOOD' fails # with ldap.TIMEOUT ldappool.StateConnector.simple_bind_s = _bind_fails_timeout_failover ldappool.StateConnector.unbind_s = _unbind uri = 'ldap://BAD,ldap://GOOD' dn = 'uid=adminuser,ou=logins,dc=mozilla' passwd = 'adminuser' cm = ldappool.ConnectionManager(uri, dn, passwd, use_pool=True, size=2) self.assertEqual(len(cm), 0) try: with cm.connection('dn', 'pass') as conn: # Ensure we failed over to the second URI self.assertTrue(conn.active) self.assertEqual(conn._uri, 'ldap://GOOD') pass except Exception: raise AssertionError() def test_simple_bind_fails_invalid_credentials(self): unbinds = [] def _unbind(self): unbinds.append(1) # the binding fails with an LDAPError ldappool.StateConnector.simple_bind_s = _bind_fails_invalid_credentials ldappool.StateConnector.unbind_s = _unbind uri = '' dn = 'uid=adminuser,ou=logins,dc=mozilla' passwd = 'adminuser' cm = ldappool.ConnectionManager(uri, dn, passwd, use_pool=True, size=2) self.assertEqual(len(cm), 0) try: with cm.connection('dn', 'pass'): pass except ldap.INVALID_CREDENTIALS: pass else: raise AssertionError() def test_simple_bind_fails_invalid_credentials_failover(self): unbinds = [] def _unbind(self): unbinds.append(1) # the binding to any server other than 'ldap://GOOD' fails # with ldap.INVALID_CREDENTIALS ldappool.StateConnector.simple_bind_s = \ _bind_fails_invalid_credentials_failover ldappool.StateConnector.unbind_s = _unbind uri = 'ldap://BAD,ldap://GOOD' dn = 'uid=adminuser,ou=logins,dc=mozilla' passwd = 'adminuser' cm = ldappool.ConnectionManager(uri, dn, passwd, use_pool=True, size=2) self.assertEqual(len(cm), 0) try: # We expect this to throw an INVALID_CREDENTIALS exception for the # first URI, as this is a hard-failure where we don't want failover # to occur to subsequent URIs. with cm.connection('dn', 'pass'): pass except ldap.INVALID_CREDENTIALS: pass else: raise AssertionError() ldappool-2.4.1/ldappool/tests/test_ldappool.py0000666000175000017500000001746513435060152021567 0ustar zuulzuul00000000000000# ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1/GPL 2.0/LGPL 2.1 # # The contents of this file are subject to the Mozilla Public License Version # 1.1 (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License # for the specific language governing rights and limitations under the # License. # # The Original Code is Sync Server # # The Initial Developer of the Original Code is the Mozilla Foundation. # Portions created by the Initial Developer are Copyright (C) 2010 # the Initial Developer. All Rights Reserved. # # Contributor(s): # Tarek Ziade (tarek@mozilla.com) # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your # decision by deleting the provisions above and replace them with the notice # and other provisions required by the GPL or the LGPL. If you do not delete # the provisions above, a recipient may use your version of this file under # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** import threading import time import unittest import ldap import ldappool # patching StateConnector ldappool.StateConnector.users = { 'uid=tarek,ou=users,dc=mozilla': {'uidNumber': ['1'], 'account-enabled': ['Yes'], 'mail': ['tarek@mozilla.com'], 'cn': ['tarek']}, 'cn=admin,dc=mozilla': {'cn': ['admin'], 'mail': ['admin'], 'uidNumber': ['100']}} def _simple_bind(self, who='', cred='', *args): self.connected = True self.who = who self.cred = cred ldappool.StateConnector.simple_bind_s = _simple_bind def _search(self, dn, *args, **kw): if dn in self.users: return [(dn, self.users[dn])] elif dn == 'ou=users,dc=mozilla': uid = kw['filterstr'].split('=')[-1][:-1] for dn_, value in self.users.items(): if value['uidNumber'][0] != uid: continue return [(dn_, value)] raise ldap.NO_SUCH_OBJECT ldappool.StateConnector.search_s = _search def _add(self, dn, user): self.users[dn] = {} for key, value in user: if not isinstance(value, list): value = [value] self.users[dn][key] = value return ldap.RES_ADD, '' ldappool.StateConnector.add_s = _add def _modify(self, dn, user): if dn in self.users: for type_, key, value in user: if not isinstance(value, list): value = [value] self.users[dn][key] = value return ldap.RES_MODIFY, '' ldappool.StateConnector.modify_s = _modify def _delete(self, dn): if dn in self.users: del self.users[dn] return ldap.RES_DELETE, '' ldappool.StateConnector.delete_s = _delete class LDAPWorker(threading.Thread): def __init__(self, pool): threading.Thread.__init__(self) self.pool = pool self.results = [] def run(self): dn = 'cn=admin,dc=mozilla' for i in range(10): with self.pool.connection() as conn: res = conn.search_s(dn, ldap.SCOPE_BASE, attrlist=['cn']) self.results.append(res) class TestLDAPSQLAuth(unittest.TestCase): def test_ctor_args(self): pool = ldappool.ConnectionManager('ldap://localhost', use_tls=True) self.assertEqual(pool.use_tls, True) def test_pool(self): dn = 'uid=adminuser,ou=logins,dc=mozilla' passwd = 'adminuser' pool = ldappool.ConnectionManager('ldap://localhost', dn, passwd) workers = [LDAPWorker(pool) for i in range(10)] for worker in workers: worker.start() for worker in workers: worker.join() self.assertEqual(len(worker.results), 10) cn = worker.results[0][0][1]['cn'] self.assertEqual(cn, ['admin']) def test_pool_full(self): dn = 'uid=adminuser,ou=logins,dc=mozilla' passwd = 'adminuser' pool = ldappool.ConnectionManager( 'ldap://localhost', dn, passwd, size=1, retry_delay=1., retry_max=5, use_pool=True) class Worker(threading.Thread): def __init__(self, pool, duration): threading.Thread.__init__(self) self.pool = pool self.duration = duration def run(self): with self.pool.connection() as conn: # NOQA time.sleep(self.duration) def tryit(): time.sleep(0.1) with pool.connection() as conn: # NOQA pass # an attempt on a full pool should eventually work # because the connector is reused for i in range(10): tryit() # we have 1 non-active connector now self.assertEqual(len(pool), 1) # an attempt with a full pool should succeed if a # slot gets freed in less than one second. worker1 = Worker(pool, .4) worker1.start() try: tryit() finally: worker1.join() # an attempt with a full pool should fail # if no slot gets freed in less than one second. worker1 = Worker(pool, 1.1) worker1.start() try: self.assertRaises(ldappool.MaxConnectionReachedError, tryit) finally: worker1.join() # we still have one active connector self.assertEqual(len(pool), 1) def test_pool_cleanup(self): dn = 'uid=adminuser,ou=logins,dc=mozilla' passwd = 'adminuser' pool = ldappool.ConnectionManager('ldap://localhost', dn, passwd, size=1, use_pool=True) with pool.connection('bind1') as conn: # NOQA pass with pool.connection('bind2') as conn: # NOQA pass # the second call should have removed the first conn self.assertEqual(len(pool), 1) def test_pool_reuse(self): dn = 'uid=adminuser,ou=logins,dc=mozilla' passwd = 'adminuser' pool = ldappool.ConnectionManager('ldap://localhost', dn, passwd, use_pool=True) with pool.connection() as conn: self.assertTrue(conn.active) self.assertFalse(conn.active) self.assertTrue(conn.connected) with pool.connection() as conn2: pass self.assertTrue(conn is conn2) with pool.connection() as conn: conn.connected = False with pool.connection() as conn2: pass self.assertTrue(conn is not conn2) # same bind and password: reuse with pool.connection('bind', 'passwd') as conn: self.assertTrue(conn.active) self.assertFalse(conn.active) self.assertTrue(conn.connected) with pool.connection('bind', 'passwd') as conn2: pass self.assertTrue(conn is conn2) # same bind different password: rebind ! with pool.connection('bind', 'passwd') as conn: self.assertTrue(conn.active) self.assertFalse(conn.active) self.assertTrue(conn.connected) with pool.connection('bind', 'passwd2') as conn2: pass self.assertTrue(conn is conn2)