[Freeipa-devel] Exception model proposal

Kevin McCarthy kmccarth at redhat.com
Wed Aug 22 00:17:05 UTC 2007


I've been playing with the exception model, and here's my code so far.
This uses Simo's suggestion of segmenting errors into 16 bit categories
and 16 bit "detail" error codes.

This is not ready for committing - just wanted to get some feedback
before I finish working on it tomorrow.


All exceptions inherit from IPAError.

Sample code to throw an exception:
    raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE)

Override the message:
    raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE, "It's a dup")

Embed the causing error:
   except ldap.LDAPError, e:
      raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)


Catching a particular exception:
    try:
       ...
    except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):

Feedback?

Thanks,

-Kevin


-------------- next part --------------
diff -r 734082398066 ipa-python/ipaerror.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ipa-python/ipaerror.py	Tue Aug 21 17:06:52 2007 -0700
@@ -0,0 +1,126 @@
+#! /usr/bin/python -E
+#
+# Copyright (C) 2007    Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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; version 2 or later
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+import exceptions
+import types
+
+class IPAError(exceptions.Exception):
+    """Base error class for IPA Code"""
+
+    def __init__(self, code, message="", detail=None):
+        """code is the IPA error code.
+           message is a human viewable error message.
+           detail is an optional exception that provides more detail about the
+           error."""
+        self.code = code
+        self.message = message
+        self.detail = detail
+
+    def __str__(self):
+        return self.message
+
+    def __repr__(self):
+        repr = "%d: %s" % (self.code, self.message)
+        if self.detail:
+            repr += "\n%s" % str(self.detail)
+        return repr
+
+
+###############
+# Error codes #
+###############
+
+code_map_dict = {}
+
+def gen_exception(code, message=None, nested_exception=None):
+    """This should be used by IPA code to translate error codes into the
+       correct exception/message to throw.
+
+       message is an optional argument which overrides the default message.
+
+       nested_exception is an optional argument providing more details
+       about the error."""
+    (default_message, exception) = code_map_dict.get(code, ("unknown", IPAError))
+    if not message:
+        message = default_message
+    return exception(code, message, nested_exception)
+
+def exception_for(code):
+    """Used to look up the corresponding exception for an error code.
+       Will usually be used for an except block."""
+    (default_message, exception) = code_map_dict.get(code, ("unknown", IPAError))
+    return exception
+
+def gen_error_code(category, detail, message, exception=IPAError):
+    """Private method used to generate exception codes.
+       category is one of the 16 bit error code category constants.
+       detail is a 16 bit code within the category.
+       message is a human readable description on the error.
+       exception is the exception to throw for this error code."""
+    code = (category << 16) + detail
+    exception = types.ClassType("IPAError%d" % code,
+                      (IPAError,),
+                      {})
+    code_map_dict[code] = (message, exception)
+
+    return code
+
+#
+# Error codes are broken into two 16-bit values: category and detail
+#
+
+#
+# LDAP Errors:   0x0001
+#
+LDAP_CATEGORY = 0x0001
+
+LDAP_DATABASE_ERROR = gen_error_code(
+        LDAP_CATEGORY,
+        0x0001,
+        "A database error occurred")
+
+LDAP_MIDAIR_COLLISION = gen_error_code(
+        LDAP_CATEGORY,
+        0x0002,
+        "Change collided with another change")
+
+LDAP_NOT_FOUND = gen_error_code(
+        LDAP_CATEGORY,
+        0x0003,
+        "Entry not found")
+
+LDAP_DUPLICATE = gen_error_code(
+        LDAP_CATEGORY,
+        0x0004,
+        "Duplicate entry already in LDAP")
+
+LDAP_MISSING_DN = gen_error_code(
+        LDAP_CATEGORY,
+        0x0005,
+        "Entry missing dn")
+
+#
+# Input errors  (sample - replace me)
+#
+INPUT_CATEGORY = 0x0002
+
+INPUT_INVALID_ERROR = gen_error_code(
+        INPUT_CATEGORY,
+        0x0001,
+        "Illegal input")
diff -r 734082398066 ipa-server/ipaserver/ipaldap.py
--- a/ipa-server/ipaserver/ipaldap.py	Tue Aug 21 14:26:36 2007 -0700
+++ b/ipa-server/ipaserver/ipaldap.py	Tue Aug 21 16:56:59 2007 -0700
@@ -39,13 +39,7 @@ from ldap.modlist import modifyModlist
 
 from ldap.ldapobject import SimpleLDAPObject
 
-class Error(Exception): pass
-class InvalidArgumentError(Error):
-    def __init__(self,message): self.message = message
-    def __repr__(self): return message
-class NoSuchEntryError(Error):
-    def __init__(self,message): self.message = message
-    def __repr__(self): return message
+import ipaerror
 
 class Entry:
     """This class represents an LDAP Entry object.  An LDAP entry consists of a DN
@@ -192,7 +186,8 @@ class IPAdmin(SimpleLDAPObject):
                 instdir = ent.getValue('nsslapd-instancedir')
                 self.sroot, self.inst = re.match(r'(.*)[\/]slapd-(\w+)$', instdir).groups()
                 self.errlog = ent.getValue('nsslapd-errorlog')
-            except (ldap.INSUFFICIENT_ACCESS, ldap.CONNECT_ERROR, NoSuchEntryError):
+            except (ldap.INSUFFICIENT_ACCESS, ldap.CONNECT_ERROR,
+                    ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND)):
                 pass # usually means 
 #                print "ignored exception"
             except ldap.LDAPError, e:
@@ -268,7 +263,8 @@ class IPAdmin(SimpleLDAPObject):
 
         type, obj = self.result(res)
         if not obj:
-            raise NoSuchEntryError("no such entry for " + str(args))
+            raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND,
+                    "no such entry for " + str(args))
         elif isinstance(obj,Entry):
             return obj
         else: # assume list/tuple
@@ -285,7 +281,8 @@ class IPAdmin(SimpleLDAPObject):
         res = self.search(*args)
         type, obj = self.result(res)
         if not obj:
-            raise NoSuchEntryError("no such entry for " + str(args))
+            raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND,
+                    "no such entry for " + str(args))
 
         all_users = []
         for s in obj:
@@ -303,9 +300,7 @@ class IPAdmin(SimpleLDAPObject):
             self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
             self.add_s(*args)
         except ldap.ALREADY_EXISTS:
-            raise ldap.ALREADY_EXISTS
-        except ldap.LDAPError, e:
-            raise e
+            raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE)
         return "Success"
 
     def updateEntry(self,dn,olduser,newuser):
@@ -429,7 +424,8 @@ class IPAdmin(SimpleLDAPObject):
         while not entry and int(time.time()) < timeout:
             try:
                 entry = self.getEntry(dn, scope, filter, attrlist)
-            except NoSuchEntryError: pass # found entry, but no attr
+            except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
+                pass # found entry, but no attr
             except ldap.NO_SUCH_OBJECT: pass # no entry yet
             except ldap.LDAPError, e: # badness
                 print "\nError reading entry", dn, e
diff -r 734082398066 ipa-server/xmlrpc-server/funcs.py
--- a/ipa-server/xmlrpc-server/funcs.py	Tue Aug 21 14:26:36 2007 -0700
+++ b/ipa-server/xmlrpc-server/funcs.py	Tue Aug 21 17:09:14 2007 -0700
@@ -24,10 +24,12 @@ import ipaserver.dsinstance
 import ipaserver.dsinstance
 import ipaserver.ipaldap
 import ipaserver.util
+import xmlrpclib
+import ipa.config
+import ipaerror
+
 import string
 from types import *
-import xmlrpclib
-import ipa.config
 import os
 import re
 
@@ -89,9 +91,7 @@ class IPAServer:
             ent = m1.getEntry(self.basedn, self.scope, filter, ['dn'])
             _LDAPPool.releaseConn(m1)
         except ldap.LDAPError, e:
-            raise xmlrpclib.Fault(1, e)
-        except ipaserver.ipaldap.NoSuchEntryError:
-            raise xmlrpclib.Fault(2, "No such user")
+            raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
     
         return "dn:" + ent.dn
     
@@ -153,12 +153,7 @@ class IPAServer:
         if (isinstance(username, tuple)):
             username = username[0]
     
-        try:
-            dn = self.get_dn_from_principal(self.princ)
-        except ldap.LDAPError, e:
-            raise xmlrpclib.Fault(1, e)
-        except ipaserver.ipaldap.NoSuchEntryError:
-            raise xmlrpclib.Fault(2, "No such user")
+        dn = self.get_dn_from_principal(self.princ)
     
         filter = "(uid=" + username + ")"
         try:
@@ -166,9 +161,7 @@ class IPAServer:
             ent = m1.getEntry(self.basedn, self.scope, filter, sattrs)
             _LDAPPool.releaseConn(m1)
         except ldap.LDAPError, e:
-            raise xmlrpclib.Fault(1, e)
-        except ipaserver.ipaldap.NoSuchEntryError:
-            raise xmlrpclib.Fault(2, "No such user")
+            raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
     
         return self.convert_entry(ent)
     
@@ -220,12 +213,7 @@ class IPAServer:
         if opts:
             self.set_principal(opts['remoteuser'])
     
-        try:
-            dn = self.get_dn_from_principal(self.princ)
-        except ldap.LDAPError, e:
-            raise xmlrpclib.Fault(1, e)
-        except ipaserver.ipaldap.NoSuchEntryError:
-            raise xmlrpclib.Fault(2, "No such user")
+        dn = self.get_dn_from_principal(self.princ)
 
         try:
             m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
@@ -233,9 +221,9 @@ class IPAServer:
             _LDAPPool.releaseConn(m1)
             return res
         except ldap.ALREADY_EXISTS:
-            raise xmlrpclib.Fault(3, "User already exists")
-        except ldap.LDAPError, e:
-            raise xmlrpclib.Fault(1, str(e))
+            raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE)
+        except ldap.LDAPError, e:
+            raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
     
     def get_add_schema (self):
         """Get the list of fields to be used when adding users in the GUI."""
@@ -290,12 +278,7 @@ class IPAServer:
         if opts:
             self.set_principal(opts['remoteuser'])
 
-        try:
-            dn = self.get_dn_from_principal(self.princ)
-        except ldap.LDAPError, e:
-            raise xmlrpclib.Fault(1, e)
-        except ipaserver.ipaldap.NoSuchEntryError:
-            raise xmlrpclib.Fault(2, "No such user")
+        dn = self.get_dn_from_principal(self.princ)
     
         # FIXME: Is this the filter we want or should it be more specific?
         filter = "(objectclass=posixAccount)"
@@ -304,9 +287,7 @@ class IPAServer:
             all_users = m1.getList(self.basedn, self.scope, filter, None)
             _LDAPPool.releaseConn(m1)
         except ldap.LDAPError, e:
-            raise xmlrpclib.Fault(1, e)
-        except ipaserver.ipaldap.NoSuchEntryError:
-            raise xmlrpclib.Fault(2, "No such user")
+            raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
     
         users = []
         for u in all_users:
@@ -338,12 +319,7 @@ class IPAServer:
         if opts:
             self.set_principal(opts['remoteuser'])
 
-        try:
-            dn = self.get_dn_from_principal(self.princ)
-        except ldap.LDAPError, e:
-            raise xmlrpclib.Fault(1, e)
-        except ipaserver.ipaldap.NoSuchEntryError:
-            raise xmlrpclib.Fault(2, "No such user")
+        dn = self.get_dn_from_principal(self.princ)
 
         # TODO: this escaper assumes the python-ldap library will error out
         #       on invalid codepoints.  we need to check malformed utf-8 input
@@ -360,8 +336,8 @@ class IPAServer:
             results = m1.getList(self.basedn, self.scope, filter, sattrs)
             _LDAPPool.releaseConn(m1)
         except ldap.LDAPError, e:
-            raise xmlrpclib.Fault(1, e)
-        except ipaserver.ipaldap.NoSuchEntryError:
+            raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
+        except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
             results = []
             # raise xmlrpclib.Fault(2, "No such user")
     
@@ -412,17 +388,12 @@ class IPAServer:
         try:
             moddn = olduser['dn']
         except KeyError, e:
-            raise xmlrpclib.Fault(4, "Old user has no dn")
-
-        if opts:
-            self.set_principal(opts['remoteuser'])
-    
-        try:
-            proxydn = self.get_dn_from_principal(self.princ)
-        except ldap.LDAPError, e:
-            raise xmlrpclib.Fault(1, e)
-        except ipaserver.ipaldap.NoSuchEntryError:
-            raise xmlrpclib.Fault(2, "No such user")
+            raise ipaerror.gen_exception(ipaerror.LDAP_MISSING_DN)
+
+        if opts:
+            self.set_principal(opts['remoteuser'])
+    
+        proxydn = self.get_dn_from_principal(self.princ)
 
         try:
             m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,proxydn)
@@ -430,7 +401,7 @@ class IPAServer:
             _LDAPPool.releaseConn(m1)
             return res
         except ldap.LDAPError, e:
-            raise xmlrpclib.Fault(1, str(e))
+            raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
 
     def mark_user_deleted (self, args, opts=None):
         """Mark a user as inactive in LDAP. We aren't actually deleting
@@ -442,17 +413,12 @@ class IPAServer:
         if opts:
             self.set_principal(opts['remoteuser'])
 
-        try:
-            proxydn = self.get_dn_from_principal(self.princ)
-        except ldap.LDAPError, e:
-            raise xmlrpclib.Fault(1, e)
-        except ipaserver.ipaldap.NoSuchEntryError:
-            raise xmlrpclib.Fault(2, "No such user")
+        proxydn = self.get_dn_from_principal(self.princ)
 
         try:
             user = self.get_user(uid, ['dn', 'nsAccountlock'], opts)
         except ldap.LDAPError, e:
-            raise xmlrpclib.Fault(1, str(e))
+            raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
 
         # Are we doing an add or replace operation?
         if user.has_key('nsaccountlock'):
@@ -466,7 +432,7 @@ class IPAServer:
             _LDAPPool.releaseConn(m1)
             return res
         except ldap.LDAPError, e:
-            raise xmlrpclib.Fault(1, str(e))
+            raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
 
 
 def ldap_search_escape(match):
diff -r 734082398066 ipa-server/xmlrpc-server/ipaxmlrpc.py
--- a/ipa-server/xmlrpc-server/ipaxmlrpc.py	Tue Aug 21 14:26:36 2007 -0700
+++ b/ipa-server/xmlrpc-server/ipaxmlrpc.py	Tue Aug 21 16:59:26 2007 -0700
@@ -35,6 +35,8 @@ from mod_python import apache
 
 import ipaserver
 import funcs
+import ipaerror
+
 import string
 import base64
 
@@ -144,9 +146,9 @@ class ModXMLRPCRequestHandler(object):
             # wrap response in a singleton tuple
             response = (response,)
             response = dumps(response, methodresponse=1, allow_none=1)
-        except Fault, fault:
+        except ipaerror.IPAError, e:
             self.traceback = True
-            response = dumps(fault)
+            response = dumps(Fault(e.code, str(e)))
         except:
             self.traceback = True
             # report exception back to server
-------------- next part --------------
A non-text attachment was scrubbed...
Name: smime.p7s
Type: application/x-pkcs7-signature
Size: 2228 bytes
Desc: not available
URL: <http://listman.redhat.com/archives/freeipa-devel/attachments/20070821/1f195214/attachment.bin>


More information about the Freeipa-devel mailing list