Skip to content
This repository has been archived by the owner on Dec 11, 2018. It is now read-only.

Commit

Permalink
ldap: Add wrapper object to help with transient errors
Browse files Browse the repository at this point in the history
Move LDAP connection and query handling to a new class. Attempt to
recover from transient LDAP errors by catching LDAPCommunicationErrors,
disconnecting, reconnecting, and retrying the search once. If that
doesn't work, give up and let the caller deal with an empty result.

Bug: #21
  • Loading branch information
bd808 committed Oct 28, 2016
1 parent fd5fb48 commit c874cb6
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 9 deletions.
2 changes: 1 addition & 1 deletion requirements.txt
@@ -1,6 +1,6 @@
elasticsearch>=1.0.0,<2.0.0
irc>=15.0.3
ldap3
ldap3==1.4.0
mwclient>=0.8.2
python-twitter>=3.1
pyyaml
74 changes: 74 additions & 0 deletions stashbot/ldap.py
@@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
#
# This file is part of bd808's stashbot application
# Copyright (C) 2016 Bryan Davis and contributors
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.

import ldap3


class Client(object):
"""LDAP client"""

def __init__(self, uri, logger):
self._uri = uri
self.logger = logger
self.conn = None

def _connect(self):
return ldap3.Connection(self._uri, auto_bind=True, auto_range=True)

def search(self, *args, **kwargs):
"""A fairly thin wrapper around ldap3.Connection.search.
Rather then just returning a boolean status as
ldap3.Connection.search does, this method returns the fetched results
if the search succeeded.
We also try to handle some LDAP errors related to the connection
itself by discarding the current connection and possibly retrying the
request. Only one retry will be done. Consecutive
ldap3.LDAPCommunicationError exceptions will result in a raised
exception to the caller. If you do not want the default singel retry,
pass `retriable=False` as a named argument to the initial call.
"""
if 'retriable' in kwargs:
retriable = kwargs['retriable']
del kwargs['retriable']
else:
retriable = True

try:
if self.conn is None:
self.conn = self._connect()
if self.conn.search(*args, **kwargs):
return self.conn.entries
else:
return False
except ldap3.LDAPCommunicationError:
self.conn = None
if retriable:
self.logger.exception(
'LDAP server connection barfed; retrying')
return self.search(*args, retriable=False, **kwargs)
else:
raise
except:
# If anything at all goes wrong, ditch the connection out of
# paranoia. We really don't want to have to restart the bot for
# dumb things like LDAP hiccups.
self.conn = None
raise

14 changes: 6 additions & 8 deletions stashbot/sal.py
Expand Up @@ -17,12 +17,12 @@
# this program. If not, see <http://www.gnu.org/licenses/>.

import datetime
import ldap3
import re
import time
import twitter

from . import acls
from . import ldap
from . import mediawiki

RE_PHAB = re.compile(r'\b(T\d+)\b')
Expand All @@ -38,10 +38,7 @@ def __init__(self, irc, phab, es, config, logger):
self.config = config
self.logger = logger

self.ldap = ldap3.Connection(
self.config['ldap']['uri'],
auto_bind=True
)
self.ldap = ldap.Client(self.config['ldap']['uri'], self.logger)
self._cached_wikis = {}
self._cached_twitter = {}
self._cached_projects = None
Expand Down Expand Up @@ -201,12 +198,13 @@ def _get_ldap_names(self, ou):
"""Get a list of cn values from LDAP for a given ou."""
dn = 'ou=%s,%s' % (ou, self.config['ldap']['base'])
try:
if self.ldap.search(
res = self.ldap.search(
dn,
'(objectclass=groupofnames)',
attributes=['cn']
):
return [g.cn for g in self.ldap.entries]
)
if res is not False:
return [g.cn for g in res]
else:
self.logger.error('Failed to get LDAP data for %s', dn)
except:
Expand Down

0 comments on commit c874cb6

Please sign in to comment.