]> Frank Brehm's Git Trees - pixelpark/pp-admin-tools.git/commitdiff
Merge branch 'develop' of git.pixelpark.com:ppadmin/admin-tools into develop
authorFrank Brehm <frank.brehm@pixelpark.com>
Tue, 30 Jan 2024 11:25:18 +0000 (12:25 +0100)
committerFrank Brehm <frank.brehm@pixelpark.com>
Tue, 30 Jan 2024 11:25:18 +0000 (12:25 +0100)
1  2 
lib/pp_admintools/app/ldap.py
lib/pp_admintools/app/mirror_ldap.py
lib/pp_admintools/errors.py

index ec7786277e3b9a450c356dfd05e0e6aa26e70426,b650ba2378a2637d48f549ffdd3a474704a7d9e0..220d0d1dd839c286d57d076b9026c9ce154e0be0
@@@ -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:
                  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',
              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',
          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))
index 0bd2e467ef817e72e2225cd19e9d80a2229a3e17,a1522d5046750e67fa8484d4a54f3d7ca01664eb..1bf50a502d738758003e1a6f9f593d45057e8023
@@@ -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)
  
          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)
              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)
              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
index 8b8b11a571023a3ffbc45298bf33c23867eb2c46,7175cafd39af4d18b04ad4adf1f26523e85c2e8b..2c87075ffa90ccb7f0b1d2e7ce7ca5d4acc2fc6f
  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__':