From: Frank Brehm Date: Tue, 30 Jan 2024 11:25:18 +0000 (+0100) Subject: Merge branch 'develop' of git.pixelpark.com:ppadmin/admin-tools into develop X-Git-Tag: 1.0.0~1^2~33 X-Git-Url: https://git.uhu-banane.de/?a=commitdiff_plain;h=dd0cab5722638d43689f7edf44568057aad90455;p=pixelpark%2Fpp-admin-tools.git Merge branch 'develop' of git.pixelpark.com:ppadmin/admin-tools into develop --- dd0cab5722638d43689f7edf44568057aad90455 diff --cc lib/pp_admintools/app/ldap.py index ec77862,b650ba2..220d0d1 --- a/lib/pp_admintools/app/ldap.py +++ b/lib/pp_admintools/app/ldap.py @@@ -35,24 -36,21 +36,29 @@@ from ldap3 import BASE, SUBTRE from ldap3 import Connection, DSA, IP_V4_PREFERRED, SAFE_SYNC, Server from ldap3 import MODIFY_ADD, MODIFY_DELETE, MODIFY_REPLACE from ldap3.core.exceptions import LDAPBindError +# from ldap3.core.exceptions import LDAPCommunicationError from ldap3.core.exceptions import LDAPException + from ldap3.core.exceptions import LDAPSessionTerminatedByServerError + from ldap3.core.exceptions import LDAPSocketOpenError + from ldap3.core.exceptions import LDAPSocketReceiveError # Own modules -from . import BaseDPXApplication, DPXAppError +from . import BaseDPXApplication from .. import DEFAULT_CONFIG_DIR, MAX_PORT_NUMBER from .. import __version__ as GLOBAL_VERSION from .. import pp from ..config.ldap import DEFAULT_TIMEOUT from ..config.ldap import LdapConfiguration, LdapConnectionInfo +# from ..errors import DpxAppError +from ..errors import DpxDeleteLdapItemError +from ..errors import DpxLdapError +from ..errors import DpxLdapParseError ++from ..errors import DpxLdapReadError ++from ..errors import DpxLdapSessionError +from ..errors import DpxWriteLdapItemError from ..xlate import XLATOR, format_list --__version__ = '1.0.0' ++__version__ = '1.0.1' LOG = logging.getLogger(__name__) _ = XLATOR.gettext @@@ -1233,10 -1403,41 +1356,41 @@@ class BaseLdapApplication(BaseDPXApplic msg = _('Searching DN {dn!r} in {uri}.').format(dn=dn, uri=connect_info.url) LOG.debug(msg) - req_status, req_result, req_response, req_whatever = ldap.search( - search_base=dn, search_scope=BASE, attributes=attributes, - get_operational_attributes=operational_attributes, search_filter=sfilter, - time_limit=self.cfg.ldap_timeout) + cur_try = 0 + e_msg = None + while cur_try < tries: + + if inst not in self.ldap_connection: + self.connect_instance(inst) + ldap = self.ldap_connection[inst] + e_msg = None + cur_try += 1 + try: + req_status, req_result, req_response, req_whatever = ldap.search( + search_base=dn, search_scope=BASE, attributes=attributes, + get_operational_attributes=operational_attributes, search_filter=sfilter, + time_limit=self.cfg.ldap_timeout) + + except (LDAPSocketReceiveError, LDAPSocketOpenError) as e: + e_msg = str(e) + if cur_try >= tries: + break + LOG.debug(_('Waiting because of a failing read operation.')) + time.sleep(self.wait_on_read_error) + + except LDAPSessionTerminatedByServerError as e: + msg = _('Session terminated on reading entry {dn!r} from instance {i!r}:').format( + dn=dn, i=inst) + ' ' + str(e) - raise LdapSessionError(msg) ++ raise DpxLdapSessionError(msg) + + finally: + if not self.single_session: + self.disconnect_instance(inst) + + if e_msg: + msg = _('Error on reading entry {dn!r} from instance {inst!r}:').format( + dn=dn, inst=inst) + ' ' + e_msg - raise LdapReadError(msg) ++ raise DpxLdapReadError(msg) if req_status: if self.verbose > 4: @@@ -1310,7 -1517,10 +1470,10 @@@ dn=dn, c=e.__class__.__name__, e=e) msg += '\nobjectClasses:\n' + pp(object_classes) msg += '\nAttributes:\n' + pp(target_entry) - raise WriteLDAPItemError(msg) + raise DpxWriteLdapItemError(msg) + finally: + if not self.single_session or not keep_ldap: + self.disconnect_instance(inst) # Example result on a not successful modification: # { 'description': 'objectClassViolation', @@@ -1357,7 -1573,10 +1526,10 @@@ msg = _('Modification of {dn!r} was NOT successfull - {c}: {e}').format( dn=dn, c=e.__class__.__name__, e=e) msg += '\n' + _('Changes:') + '\n' + pp(changes) - raise WriteLDAPItemError(msg) + raise DpxWriteLdapItemError(msg) + finally: + if not self.single_session or not keep_ldap: + self.disconnect_instance(inst) # Example result on a not successful modification: # { 'description': 'objectClassViolation', @@@ -1401,7 -1626,10 +1579,10 @@@ except LDAPException as e: msg = _('Deletion of {dn!r} was NOT successfull - {c}: {e}').format( c=e.__class__.__name__, e=e) - raise DeleteLDAPItemError(msg) + raise DpxDeleteLdapItemError(msg) + finally: + if not self.single_session or not keep_ldap: + self.disconnect_instance(inst) if self.verbose > 1: LOG.debug(_('Deletion status: {!r}.').format(req_status)) diff --cc lib/pp_admintools/app/mirror_ldap.py index 0bd2e46,a1522d5..1bf50a5 --- a/lib/pp_admintools/app/mirror_ldap.py +++ b/lib/pp_admintools/app/mirror_ldap.py @@@ -23,16 -24,21 +24,19 @@@ from fb_tools.xlate import format_lis # from ldap3 import MODIFY_REPLACE, MODIFY_ADD, MODIFY_DELETE from ldap3 import ALL_ATTRIBUTES + from ldap3.core.exceptions import LDAPSocketReceiveError # Own modules -# from .ldap import LdapAppError, FatalLDAPError from .ldap import BaseLdapApplication -from .ldap import LdapAppError -from .ldap import LdapReadError -from .ldap import LdapSessionError from .. import pp from ..argparse_actions import LimitedFloatOptionAction from ..argparse_actions import NonNegativeIntegerOptionAction from ..config.mirror_ldap import MirrorLdapConfiguration ++from ..errors import DpxLdapReadError ++from ..errors import DpxLdapSessionError from ..xlate import XLATOR - __version__ = '1.0.0' -__version__ = '1.1.2' ++__version__ = '1.2.1' LOG = logging.getLogger(__name__) _ = XLATOR.gettext @@@ -293,6 -339,12 +330,12 @@@ class MirrorLdapApplication(BaseLdapApp self.empty_line() - except (LdapReadError, LdapSessionError) as e: ++ except (DpxLdapReadError, DpxLdapSessionError) as e: + cls = e.__class__.__name__ + msg = _('Got a {}:').format(cls) + ' ' + str(e) + LOG.error(msg) + self.exit(11) + except KeyboardInterrupt: msg = _('Got a {}:').format('KeyboardInterrupt') + ' ' + _('Interrupted on demand.') LOG.error(msg) @@@ -592,77 -644,90 +635,90 @@@ count = 0 - attributes = [ALL_ATTRIBUTES, 'aci'] - for dn in dns: - if self.verbose > 1: - self.empty_line() + if self.mirror_entry(dn): + count += 1 - if dn in self.keep_entry_dns: - if self.verbose > 1: - LOG.debug(_('Entry {!r} is set to be kept.').format(dn)) - continue + if self.limit and self.mirrored_entries >= self.limit: + break - if self.verbose > 1: - LOG.debug(_('Mirroring entry {!r} ...').format(dn)) + self.empty_line() + if count: + msg = ngettext( + 'Mirrored one structural entry in target LDAP instance.', + 'Mirrored {no} structural entries to target LDAP instance.', + count).format(no=count) + else: + msg = _('Mirrored no structural entries to target LDAP instance.') + LOG.info(msg) + + # ------------------------------------------------------------------------- + def mirror_entry(self, dn): + """Mirror the entry with the given DN from source to target.""" + attributes = [ALL_ATTRIBUTES, 'aci'] + + if self.verbose > 1: + self.empty_line() + + if dn in self.keep_entry_dns: + LOG.debug(_('Entry {!r} is set to be kept.').format(dn)) + return False + if self.verbose > 1: + LOG.debug(_('Mirroring entry {!r} ...').format(dn)) + + try: src_entry = self.get_entry(dn, self.src_instance, attributes) - if not src_entry: - msg = _('Did not found {!r} in the source LDAP.').format(dn) - LOG.warn(msg) - continue - src_attribs = self.normalized_attributes( - src_entry, omit_members=True, omit_memberof=True) + + except LDAPSocketReceiveError as e: + msg = _('Error on reading entry {!r} from source:').format(dn) + ' ' + str(e) - raise LdapReadError(msg) ++ raise DpxLdapReadError(msg) + + if not src_entry: + msg = _('Did not found {!r} in the source LDAP.').format(dn) + LOG.warn(msg) + return False + + src_attribs = self.normalized_attributes(src_entry, omit_members=True, omit_memberof=True) + src_oclasses = src_attribs['objectClass'].as_list() + src_attribs_dict = src_attribs.dict() + src_attribs_dict['objectClass'] = src_oclasses + + if self.verbose > 2: + LOG.debug('Got source entry:\n' + pp(src_attribs_dict)) + + if self.modify_src_attribs(dn, src_attribs): src_oclasses = src_attribs['objectClass'].as_list() src_attribs_dict = src_attribs.dict() src_attribs_dict['objectClass'] = src_oclasses + if self.verbose > 1: + LOG.debug('Modified source entry:\n' + pp(src_attribs_dict)) - if self.verbose > 2: - LOG.debug('Got source entry:\n' + pp(src_attribs_dict)) + try: + tgt_entry = self.get_entry(dn, self.tgt_instance, attributes, tries=1) + except LDAPSocketReceiveError as e: + msg = _('Error on reading entry {!r} from target:').format(dn) + ' ' + str(e) - raise LdapReadError(msg) ++ raise DpxLdapReadError(msg) + if tgt_entry: + tgt_attribs = self.normalized_attributes( + tgt_entry, omit_members=True, omit_memberof=True) + tgt_oclasses = tgt_attribs['objectClass'].as_list() + tgt_attribs_dict = tgt_attribs.dict() + tgt_attribs_dict['objectClass'] = tgt_oclasses - tgt_entry = self.get_entry(dn, self.tgt_instance, attributes) - if tgt_entry: - tgt_attribs = self.normalized_attributes( - tgt_entry, omit_members=True, omit_memberof=True) - tgt_oclasses = tgt_attribs['objectClass'].as_list() - tgt_attribs_dict = tgt_attribs.dict() - tgt_attribs_dict['objectClass'] = tgt_oclasses - - if self.verbose > 2: - LOG.debug('Got target entry:\n' + pp(tgt_attribs_dict)) - - changes = self.generate_modify_data(dn, src_attribs, tgt_attribs) - if changes: - self.empty_line() - LOG.info(_('Modifying entry {!r} ...').format(dn)) - msg = _('Got modify data for DN {!r}:').format(dn) - LOG.debug(msg + '\n' + pp(changes)) - self.modify_entry(self.tgt_instance, dn, changes) - self.mirrored_entries += 1 - count += 1 - self.total_updated += 1 - self.mirrored_dns.add(dn) - if self.wait_after_write and not self.simulate: - time.sleep(self.wait_after_write) - else: - if self.verbose > 1: - LOG.debug(_('No changes necessary on DN {!r}.').format(dn)) - continue + if self.verbose > 2: + LOG.debug('Got target entry:\n' + pp(tgt_attribs_dict)) - else: - LOG.debug(_('Target entry {!r} not found.').format(dn)) - (object_classes, target_entry) = self.generate_create_entry(src_attribs) + changes = self.generate_modify_data(dn, src_attribs, tgt_attribs) + if changes: self.empty_line() - LOG.info(_('Creating entry {!r} ...').format(dn)) - msg = _('Got create data for DN {!r}:').format(dn) - msg += '\nobjectClasses:\n' + pp(object_classes) - msg += '\nAttributes:\n' + pp(target_entry) - LOG.debug(msg) - self.add_entry(self.tgt_instance, dn, object_classes, target_entry) + LOG.info(_('Modifying entry {!r} ...').format(dn)) + msg = _('Got modify data for DN {!r}:').format(dn) + LOG.debug(msg + '\n' + pp(changes)) + self.modify_entry(self.tgt_instance, dn, changes) self.mirrored_entries += 1 - count += 1 - self.total_created += 1 + self.total_updated += 1 self.mirrored_dns.add(dn) if self.wait_after_write and not self.simulate: time.sleep(self.wait_after_write) @@@ -812,7 -980,11 +971,11 @@@ if self.verbose > 1: LOG.debug(_('Mirroring entry {!r} ...').format(dn)) - src_entry = self.get_entry(dn, self.src_instance, attributes) + try: + src_entry = self.get_entry(dn, self.src_instance, attributes) + except LDAPSocketReceiveError as e: + msg = _('Error on reading entry {!r} from source:').format(dn) + ' ' + str(e) - raise LdapReadError(msg) ++ raise DpxLdapReadError(msg) if not src_entry: msg = _('Did not found {!r} in the source LDAP.').format(dn) LOG.warn(msg) @@@ -822,7 -994,11 +985,11 @@@ if self.verbose > 2: LOG.debug('Got source entry:\n' + pp(src_attribs_dict)) - tgt_entry = self.get_entry(dn, self.tgt_instance, attributes) + try: + tgt_entry = self.get_entry(dn, self.tgt_instance, attributes, tries=1) + except LDAPSocketReceiveError as e: + msg = _('Error on reading entry {!r} from target:').format(dn) + ' ' + str(e) - raise LdapReadError(msg) ++ raise DpxLdapReadError(msg) if not tgt_entry: LOG.warn(_('Target entry {!r} not found.').format(dn)) continue diff --cc lib/pp_admintools/errors.py index 8b8b11a,7175caf..2c87075 --- a/lib/pp_admintools/errors.py +++ b/lib/pp_admintools/errors.py @@@ -10,10 -10,10 +10,10 @@@ from fb_tools.errors import FbAppError, FbError - __version__ = '0.7.0' -__version__ = '0.6.1' ++__version__ = '0.8.0' # ============================================================================= -class PpError(FbError): +class DpxError(FbError): """Base error class for all other self defined exceptions.""" pass @@@ -26,81 -26,6 +26,95 @@@ class DpxAppError(DpxError, FbAppError) pass +# ============================================================================= +class DpxAbortAppError(DpxAppError): + """Special exception class interrupting the application.""" + + pass + +# ============================================================================= +class DpxLdapError(DpxAppError): + """Base exception class for all exceptions in all LDAP using application classes.""" + + pass + + +# ============================================================================= +class DpxFatalLdapError(DpxLdapError): + """Fatal errors leading to interrupt the current application.""" + + pass + + ++# ============================================================================= ++class DpxLdapReadError(DpxLdapError): ++ """Exception during reading data from a LDAP instance.""" ++ ++ pass ++ ++ +# ============================================================================= +class DpxLdapExecError(DpxFatalLdapError): + """Error class in case, a LDAP operation was not successful.""" + + pass + + +# ============================================================================= +class DpxWriteLdapItemError(DpxFatalLdapError): + """Error class in case, a LDAP item could not be written.""" + + pass + + +# ============================================================================= +class DpxDeleteLdapItemError(DpxFatalLdapError): + """Error class in case, a LDAP item could not be deleted.""" + + pass + + +# ============================================================================= +class DpxLdapParseError(DpxFatalLdapError): + """Error on parsing LDAP stuff.""" + + pass + + ++# ============================================================================= ++class DpxLdapSessionError(DpxFatalLdapError): ++ """Error in session handling with LDAP.""" ++ ++ pass ++ ++ +# ============================================================================= +class DpxMailAppError(DpxAppError): + """Base exception class for all exceptions in all mail sending application classes.""" + + pass + + +# ============================================================================= +class DpxPDNSAppError(DpxMailAppError): + """Base error class for PowerDNS handling modules.""" + + pass + + +# ============================================================================= +class DpxDeployZonesError(DpxPDNSAppError): + """Special exception class to use in this module app.dns_deploy_zones.""" + + pass + +# ============================================================================= +class CheckLdapDnAttributesError(DpxLdapError): + """Special exception class for exceptions in module app.check_ldap_dn_attributes.""" + + pass + + # ============================================================================= if __name__ == '__main__':