diff options
-rwxr-xr-x | install/tools/ipa-compat-manage | 2 | ||||
-rw-r--r-- | ipalib/cli.py | 8 | ||||
-rw-r--r-- | ipalib/errors.py | 441 | ||||
-rw-r--r-- | ipalib/errors2.py | 174 | ||||
-rw-r--r-- | ipalib/plugins/basegroup.py | 2 | ||||
-rw-r--r-- | ipalib/plugins/passwd.py | 14 | ||||
-rw-r--r-- | ipalib/util.py | 21 | ||||
-rw-r--r-- | ipaserver/install/ldapupdate.py | 12 | ||||
-rw-r--r-- | ipaserver/ipaldap.py | 110 | ||||
-rw-r--r-- | ipaserver/plugins/ldap2.py | 94 | ||||
-rw-r--r-- | ipaserver/servercore.py | 32 | ||||
-rw-r--r-- | tests/test_ipalib/test_errors.py | 289 | ||||
-rw-r--r-- | tests/test_ipalib/test_frontend.py | 2 | ||||
-rw-r--r-- | tests/test_ipalib/test_plugable.py | 2 | ||||
-rw-r--r-- | tests/test_xmlrpc/xmlrpc_test.py | 1 |
15 files changed, 350 insertions, 854 deletions
diff --git a/install/tools/ipa-compat-manage b/install/tools/ipa-compat-manage index b3217876..8c707e5b 100755 --- a/install/tools/ipa-compat-manage +++ b/install/tools/ipa-compat-manage @@ -26,7 +26,7 @@ try: from ipapython import entity, ipautil, config from ipaserver.install import installutils from ipaserver.install.ldapupdate import LDAPUpdate, BadSyntax, UPDATES_DIR - from ipalib import errors, errors2 + from ipalib import errors2 import ldap import logging import re diff --git a/ipalib/cli.py b/ipalib/cli.py index 11b56e36..36c945f2 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -36,8 +36,7 @@ import frontend import backend import plugable import util -from errors2 import PublicError, CommandError, HelpError, InternalError -import errors +from errors2 import PublicError, CommandError, HelpError, InternalError, NoSuchNamespaceError, ValidationError from constants import CLI_TAB from parameters import Password, Bytes from request import ugettext as _ @@ -456,7 +455,7 @@ class show_api(frontend.Application): else: for name in namespaces: if name not in self.api: - raise errors.NoSuchNamespaceError(name) + raise NoSuchNamespaceError(name=name) names = namespaces lines = self.__traverse(names) ml = max(len(l[1]) for l in lines) @@ -478,7 +477,6 @@ class show_api(frontend.Application): s = '%d attributes show.' % len(lines) self.Backend.textui.print_dashed(s) - def __traverse(self, names): lines = [] for name in names: @@ -635,7 +633,7 @@ class cli(backend.Executioner): if value is not None: kw[param.name] = value break - except errors.ValidationError, e: + except ValidationError, e: error = e.error diff --git a/ipalib/errors.py b/ipalib/errors.py deleted file mode 100644 index c27d85de..00000000 --- a/ipalib/errors.py +++ /dev/null @@ -1,441 +0,0 @@ -# Authors: -# Jason Gerard DeRose <jderose@redhat.com> -# -# Copyright (C) 2008 Red Hat -# see file 'COPYING' for use and warranty inmsgion -# -# 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 only -# -# 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 - -""" -All custom errors raised by `ipalib` package. - -Also includes a few utility functions for raising exceptions. -""" - -IPA_ERROR_BASE = 1000 - -TYPE_FORMAT = '%s: need a %r; got %r' - -def raise_TypeError(value, type_, name): - """ - Raises a TypeError with a nicely formatted message and helpful attributes. - - The TypeError raised will have three custom attributes: - - ``value`` - The value (of incorrect type) passed as argument. - - ``type`` - The type expected for the argument. - - ``name`` - The name (identifier) of the argument in question. - - There is no edict that all TypeError should be raised with raise_TypeError, - but when it fits, use it... it makes the unit tests faster to write and - the debugging easier to read. - - Here is an example: - - >>> raise_TypeError(u'Hello, world!', str, 'message') - Traceback (most recent call last): - File "<stdin>", line 1, in <module> - File "ipalib/errors.py", line 65, in raise_TypeError - raise e - TypeError: message: need a <type 'str'>; got u'Hello, world!' - - :param value: The value (of incorrect type) passed as argument. - :param type_: The type expected for the argument. - :param name: The name (identifier) of the argument in question. - """ - - assert type(type_) is type, TYPE_FORMAT % ('type_', type, type_) - assert type(value) is not type_, 'value: %r is a %r' % (value, type_) - assert type(name) is str, TYPE_FORMAT % ('name', str, name) - e = TypeError(TYPE_FORMAT % (name, type_, value)) - setattr(e, 'value', value) - setattr(e, 'type', type_) - setattr(e, 'name', name) - raise e - - -def check_type(value, type_, name, allow_none=False): - assert type(name) is str, TYPE_FORMAT % ('name', str, name) - assert type(type_) is type, TYPE_FORMAT % ('type_', type, type_) - assert type(allow_none) is bool, TYPE_FORMAT % ('allow_none', bool, allow_none) - if value is None and allow_none: - return - if type(value) is not type_: - raise_TypeError(value, type_, name) - return value - - -def check_isinstance(value, type_, name, allow_none=False): - assert type(type_) is type, TYPE_FORMAT % ('type_', type, type_) - assert type(name) is str, TYPE_FORMAT % ('name', str, name) - assert type(allow_none) is bool, TYPE_FORMAT % ('allow_none', bool, allow_none) - if value is None and allow_none: - return - if not isinstance(value, type_): - raise_TypeError(value, type_, name) - return value - - -class IPAError(StandardError): - """ - Base class for all custom IPA errors. - - Use this base class for your custom IPA errors unless there is a - specific reason to subclass from AttributeError, KeyError, etc. - """ - - format = None - faultCode = 1 - - def __init__(self, *args): - self.args = args - - def __str__(self): - """ - Returns the string representation of this exception. - """ - return self.format % self.args - - -class InvocationError(IPAError): - pass - - -class UnknownCommandError(InvocationError): - format = 'unknown command "%s"' - -class NoSuchNamespaceError(InvocationError): - format = 'api has no such namespace: %s' - -def _(text): - return text - - -class SubprocessError(StandardError): - def __init__(self, returncode, argv): - self.returncode = returncode - self.argv = argv - StandardError.__init__(self, - 'return code %d from %r' % (returncode, argv) - ) - -class HandledError(StandardError): - """ - Base class for errors that can be raised across a remote procedure call. - """ - - code = 1 - - def __init__(self, message=None, **kw): - self.kw = kw - if message is None: - message = self.format % kw - StandardError.__init__(self, message) - - -class UnknownError(HandledError): - """ - Raised when the true error is not a handled error. - """ - - format = _('An unknown internal error has occurred') - - -class CommandError(HandledError): - """ - Raised when an unknown command is called client-side. - """ - format = _('Unknown command %(name)r') - - -class RemoteCommandError(HandledError): - format = 'Server at %(uri)r has no command %(name)r' - - -class UnknownHelpError(InvocationError): - format = 'no command nor topic "%s"' - - -class ArgumentError(IPAError): - """ - Raised when a command is called with wrong number of arguments. - """ - - format = '%s %s' - - def __init__(self, command, error): - self.command = command - self.error = error - IPAError.__init__(self, command.name, error) - - -class ValidationError(IPAError): - """ - Base class for all types of validation errors. - """ - - format = 'invalid %r value %r: %s' - - def __init__(self, name, value, error, index=None): - """ - :param name: The name of the value that failed validation. - :param value: The value that failed validation. - :param error: The error message describing the failure. - :param index: If multivalue, index of value in multivalue tuple - """ - assert type(name) is str - assert index is None or (type(index) is int and index >= 0) - self.name = name - self.value = value - self.error = error - self.index = index - IPAError.__init__(self, name, value, error) - - -class ConversionError(ValidationError): - """ - Raised when a value cannot be converted to the correct type. - """ - - def __init__(self, name, value, type_, index=None): - self.type = type_ - ValidationError.__init__(self, name, value, type_.conversion_error, - index=index, - ) - - -class RuleError(ValidationError): - """ - Raised when a value fails a validation rule. - """ - def __init__(self, name, value, error, rule, index=None): - assert callable(rule) - self.rule = rule - ValidationError.__init__(self, name, value, error, index=index) - - -class RequirementError(ValidationError): - """ - Raised when a required option was not provided. - """ - def __init__(self, name): - ValidationError.__init__(self, name, None, 'Required') - - -class RegistrationError(IPAError): - """ - Base class for errors that occur during plugin registration. - """ - - -class SubclassError(RegistrationError): - """ - Raised when registering a plugin that is not a subclass of one of the - allowed bases. - """ - msg = 'plugin %r not subclass of any base in %r' - - def __init__(self, cls, allowed): - self.cls = cls - self.allowed = allowed - - def __str__(self): - return self.msg % (self.cls, self.allowed) - - -class DuplicateError(RegistrationError): - """ - Raised when registering a plugin whose exact class has already been - registered. - """ - msg = '%r at %d was already registered' - - def __init__(self, cls): - self.cls = cls - - def __str__(self): - return self.msg % (self.cls, id(self.cls)) - - -class OverrideError(RegistrationError): - """ - Raised when override=False yet registering a plugin that overrides an - existing plugin in the same namespace. - """ - msg = 'unexpected override of %s.%s with %r (use override=True if intended)' - - def __init__(self, base, cls): - self.base = base - self.cls = cls - - def __str__(self): - return self.msg % (self.base.__name__, self.cls.__name__, self.cls) - - -class MissingOverrideError(RegistrationError): - """ - Raised when override=True yet no preexisting plugin with the same name - and base has been registered. - """ - msg = '%s.%s has not been registered, cannot override with %r' - - def __init__(self, base, cls): - self.base = base - self.cls = cls - - def __str__(self): - return self.msg % (self.base.__name__, self.cls.__name__, self.cls) - -class GenericError(IPAError): - """Base class for our custom exceptions""" - faultCode = 1000 - fromFault = False - def __str__(self): - try: - return str(self.args[0]['args'][0]) - except: - try: - return str(self.args[0]) - except: - return str(self.__dict__) - -class DatabaseError(GenericError): - """A database error has occurred""" - faultCode = 1001 - -class MidairCollision(GenericError): - """Change collided with another change""" - faultCode = 1002 - -class MissingDN(GenericError): - """The distinguished name (DN) is missing""" - faultCode = 1005 - -class EmptyModlist(GenericError): - """No modifications to be performed""" - faultCode = 1006 - -class InputError(GenericError): - """Error on input""" - faultCode = 1007 - -class SameGroupError(InputError): - """You can't add a group to itself""" - faultCode = 1008 - -class NotGroupMember(InputError): - """This entry is not a member of the group""" - faultCode = 1009 - -class AdminsImmutable(InputError): - """The admins group cannot be renamed""" - faultCode = 1010 - -class UsernameTooLong(InputError): - """The requested username is too long""" - faultCode = 1011 - -class PrincipalError(GenericError): - """There is a problem with the kerberos principal""" - faultCode = 1012 - -class PrincipalRequired(PrincipalError): - """You cannot remove IPA server service principals""" - faultCode = 1015 - -class InactivationError(GenericError): - """This entry cannot be inactivated""" - faultCode = 1016 - -class AlreadyActiveError(InactivationError): - """This entry is already locked""" - faultCode = 1017 - -class AlreadyInactiveError(InactivationError): - """This entry is already unlocked""" - faultCode = 1018 - -class HasNSAccountLock(InactivationError): - """This entry appears to have the nsAccountLock attribute in it so the Class of Service activation/inactivation will not work. You will need to remove the attribute nsAccountLock for this to work.""" - faultCode = 1019 - -class ConnectionError(GenericError): - """Connection to database failed""" - faultCode = 1020 - -class NoCCacheError(GenericError): - """No Kerberos credentials cache is available. Connection cannot be made""" - faultCode = 1021 - -class GSSAPIError(GenericError): - """GSSAPI Authorization error""" - faultCode = 1022 - -class ServerUnwilling(GenericError): - """Account inactivated. Server is unwilling to perform""" - faultCode = 1023 - -class ConfigurationError(GenericError): - """A configuration error occurred""" - faultCode = 1024 - -class DefaultGroup(ConfigurationError): - """You cannot remove the default users group""" - faultCode = 1025 - -class InvalidUserPrincipal(GenericError): - """Invalid user principal""" - faultCode = 1028 - -class FunctionDeprecated(GenericError): - """Raised by a deprecated function""" - faultCode = 2000 - -def convertFault(fault): - """Convert a fault to the corresponding Exception type, if possible""" - code = getattr(fault,'faultCode',None) - if code is None: - return fault - for v in globals().values(): - if type(v) == type(Exception) and issubclass(v,GenericError) and \ - code == getattr(v,'faultCode',None): - ret = v(fault.faultString) - ret.fromFault = True - return ret - #otherwise... - return fault - -def listFaults(): - """Return a list of faults - - Returns a list of dictionaries whose keys are: - faultCode: the numeric code used in fault conversion - name: the name of the exception - desc: the description of the exception (docstring) - """ - ret = [] - for n,v in globals().items(): - if type(v) == type(Exception) and issubclass(v,GenericError): - code = getattr(v,'faultCode',None) - if code is None: - continue - info = {} - info['faultCode'] = code - info['name'] = n - info['desc'] = getattr(v,'__doc__',None) - ret.append(info) - ret.sort(lambda a,b: cmp(a['faultCode'],b['faultCode'])) - return ret diff --git a/ipalib/errors2.py b/ipalib/errors2.py index 7e752d82..341421e5 100644 --- a/ipalib/errors2.py +++ b/ipalib/errors2.py @@ -699,6 +699,21 @@ class ValidationError(InvocationError): format = _('invalid %(name)r: %(error)s') +class NoSuchNamespaceError(InvocationError): + """ + **3010** Raised when an unknown namespace is requested. + + For example: + + >>> raise NoSuchNamespaceError(name='Plugins') + Traceback (most recent call last): + ... + NoSuchNamespaceError: api has no such namespace: Plugins + """ + + errno = 3010 + format = _('api has no such namespace: %(name)r') + ############################################################################## # 4000 - 4999: Execution errors @@ -822,6 +837,102 @@ class AlreadyPosixGroup(ExecutionError): errno = 4007 format = _('This is already a posix group') +class MalformedUserPrincipal(ExecutionError): + """ + **4008** Raised when a user principal is not of the form: user@REALM + + For example: + + >>> raise MalformedUserPrincipal(principal=jsmith@@EXAMPLE.COM) + Traceback (most recent call last): + ... + MalformedUserPrincipal: Principal is not of the form user@REALM: jsmith@@EXAMPLE.COM + + """ + + errno = 4008 + format = _('Principal is not of the form user@REALM: %(principal)r') + +class AlreadyActive(ExecutionError): + """ + **4009** Raised when an entry is made active that is already active + + For example: + + >>> raise AlreadyActive() + Traceback (most recent call last): + ... + AlreadyActive: This entry is already unlocked + + """ + + errno = 4009 + format = _('This entry is already unlocked') + +class AlreadyInactive(ExecutionError): + """ + **4010** Raised when an entry is made inactive that is already inactive + + For example: + + >>> raise AlreadyInactive() + Traceback (most recent call last): + ... + AlreadyInactive: This entry is already locked + + """ + + errno = 4010 + format = _('This entry is already locked') + +class HasNSAccountLock(ExecutionError): + """ + **4011** Raised when an entry has the nsAccountLock attribute set + + For example: + + >>> raise HasNSAccountLock() + Traceback (most recent call last): + ... + HasNSAccountLock: This entry has nsAccountLock set, it cannot be locked or unlocked + + """ + + errno = 4011 + format = _('This entry has nsAccountLock set, it cannot be locked or unlocked') + +class NotGroupMember(ExecutionError): + """ + **4012** Raised when a non-member is attempted to be removed from a group + + For example: + + >>> raise NotGroupMember() + Traceback (most recent call last): + ... + NotGroupMember: This entry is not a member of the group + + """ + + errno = 4012 + format = _('This entry is not a member of the group') + +class RecursiveGroup(ExecutionError): + """ + **4013** Raised when a group is added as a member of itself + + For example: + + >>> raise RecursiveGroup() + Traceback (most recent call last): + ... + RecursiveGroup: A group may not be a member of itself + + """ + + errno = 4013 + format = _('A group may not be a member of itself') + class BuiltinError(ExecutionError): """ **4100** Base class for builtin execution errors (*4100 - 4199*). @@ -854,6 +965,69 @@ class LDAPError(ExecutionError): errno = 4200 +class MidairCollision(ExecutionError): + """ + **4201** Raised when a change collides with another change + + For example: + + >>> raise MidairCollision() + Traceback (most recent call last): + ... + MidairCollision: change collided with another change + """ + + errno = 4201 + format = _('change collided with another change') + + +class EmptyModlist(ExecutionError): + """ + **4202** Raised when an LDAP update makes no changes + + For example: + + >>> raise EmptyModlist() + Traceback (most recent call last): + ... + EmptyModlist: no modifications to be performed + """ + + errno = 4202 + format = _('no modifications to be performed') + + +class DatabaseError(ExecutionError): + """ + **4203** Raised when an LDAP error is not otherwise handled + + For example: + + >>> raise DatabaseError(desc="Can't contact LDAP server", info="") + Traceback (most recent call last): + ... + DatabaseError: Can't contact LDAP server: + """ + + errno = 4203 + format = _('%(desc)r:%(info)r') + + +class LimitsExceeded(ExecutionError): + """ + **4204** Raised when search limits are exceeded. + + For example: + + >>> raise LimitsExceeded() + Traceback (most recent call last): + ... + LimitsExceeded: limits exceeded for this query + """ + + errno = 4204 + format = _('limits exceeded for this query') + ############################################################################## # 5000 - 5999: Generic errors diff --git a/ipalib/plugins/basegroup.py b/ipalib/plugins/basegroup.py index 7cba75a4..b93fdc8d 100644 --- a/ipalib/plugins/basegroup.py +++ b/ipalib/plugins/basegroup.py @@ -21,7 +21,7 @@ Base plugin for groups. """ -from ipalib import api, crud, errors, errors2 +from ipalib import api, crud, errors2 from ipalib import Object, Command # Plugin base classes from ipalib import Str, Int, Flag, List # Parameter types from ldap.dn import escape_dn_chars diff --git a/ipalib/plugins/passwd.py b/ipalib/plugins/passwd.py index a599276f..cc7ab586 100644 --- a/ipalib/plugins/passwd.py +++ b/ipalib/plugins/passwd.py @@ -21,7 +21,7 @@ Frontend plugins for password changes. """ -from ipalib import api, errors, util +from ipalib import api, errors2, util from ipalib import Command # Plugin base classes from ipalib import Str, Password # Parameter types @@ -30,13 +30,13 @@ class passwd(Command): 'Edit existing password policy.' takes_args = ( - Password('password'), - Str('principal?', + Str('principal', cli_name='user', primary_key=True, autofill=True, - default_from=util.get_current_principal, + create_default=lambda **kw: util.get_current_principal(), ), + Password('password'), ) def execute(self, principal, password): @@ -48,13 +48,13 @@ class passwd(Command): Returns the entry - :param param uid: The login name of the user being updated. - :param kw: Not used. + :param principal: The login name or principal of the user + :param password: the new password """ if principal.find('@') > 0: u = principal.split('@') if len(u) > 2: - raise errors.InvalidUserPrincipal(principal) + raise errors2.MalformedUserPrincipal(principal=principal) else: principal = principal+"@"+self.api.env.realm dn = self.Backend.ldap.find_entry_dn( diff --git a/ipalib/util.py b/ipalib/util.py index 3e47cc3a..54529b14 100644 --- a/ipalib/util.py +++ b/ipalib/util.py @@ -22,24 +22,21 @@ Various utility functions. """ import os -from os import path import imp import optparse import logging import time -from types import NoneType -from xmlrpclib import Binary import krbV import socket +from ipalib import errors2 def get_current_principal(): try: - return krbV.default_context().default_ccache().principal().name + return unicode(krbV.default_context().default_ccache().principal().name) except krbV.Krb5Error: - #TODO: do a kinit - print "Unable to get kerberos principal" - return None + #TODO: do a kinit? + raise errors2.CCacheError() def get_fqdn(): fqdn = "" @@ -57,16 +54,16 @@ def find_modules_in_dir(src_dir): """ Iterate through module names found in ``src_dir``. """ - if not (path.abspath(src_dir) == src_dir and path.isdir(src_dir)): + if not (os.path.abspath(src_dir) == src_dir and os.path.isdir(src_dir)): return - if path.islink(src_dir): + if os.path.islink(src_dir): return suffix = '.py' for name in sorted(os.listdir(src_dir)): if not name.endswith(suffix): continue - pyfile = path.join(src_dir, name) - if path.islink(pyfile) or not path.isfile(pyfile): + pyfile = os.path.join(src_dir, name) + if os.path.islink(pyfile) or not os.path.isfile(pyfile): continue module = name[:-len(suffix)] if module == '__init__': @@ -92,7 +89,7 @@ def import_plugins_subpackage(name): plugins = __import__(name + '.plugins').plugins except ImportError: return - src_dir = path.dirname(path.abspath(plugins.__file__)) + src_dir = os.path.dirname(os.path.abspath(plugins.__file__)) for name in find_modules_in_dir(src_dir): full_name = '%s.%s' % (plugins.__name__, name) __import__(full_name) diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py index f002595d..17b519b3 100644 --- a/ipaserver/install/ldapupdate.py +++ b/ipaserver/install/ldapupdate.py @@ -29,7 +29,7 @@ from ipaserver.install import installutils from ipaserver import ipaldap from ipapython import entity, ipautil from ipalib import util -from ipalib import errors, errors2 +from ipalib import errors2 import ldap import logging import krbV @@ -310,10 +310,10 @@ class LDAPUpdate: while True: try: entry = self.conn.getEntry(dn, ldap.SCOPE_BASE, "(objectclass=*)", attrlist) - except errors2.NotFound: + except errors2.NotFound, e: logging.error("Task not found: %s", dn) return - except errors.DatabaseError, e: + except errors2.DatabaseError, e: logging.error("Task lookup failure %s", e) return @@ -484,7 +484,7 @@ class LDAPUpdate: # Doesn't exist, start with the default entry entry = new_entry logging.info("New entry: %s", entry.dn) - except errors.DatabaseError: + except errors2.DatabaseError: # Doesn't exist, start with the default entry entry = new_entry logging.info("New entry, using default value: %s", entry.dn) @@ -521,10 +521,10 @@ class LDAPUpdate: if self.live_run and updated: self.conn.updateEntry(entry.dn, entry.origDataDict(), entry.toDict()) logging.info("Done") - except errors.EmptyModlist: + except errors2.EmptyModlist: logging.info("Entry already up-to-date") updated = False - except errors.DatabaseError, e: + except errors2.DatabaseError, e: logging.error("Update failed: %s", e) updated = False diff --git a/ipaserver/ipaldap.py b/ipaserver/ipaldap.py index 01370b86..e63fe55b 100644 --- a/ipaserver/ipaldap.py +++ b/ipaserver/ipaldap.py @@ -32,7 +32,7 @@ import ldap.sasl from ldap.controls import LDAPControl,DecodeControlTuples,EncodeControlTuples from ldap.ldapobject import SimpleLDAPObject from ipaserver import ipautil -from ipalib import errors, errors2 +from ipalib import errors2 # Global variable to define SASL auth sasl_auth = ldap.sasl.sasl({},'GSSAPI') @@ -264,6 +264,50 @@ class IPAdmin(SimpleLDAPObject): return sctrl + def __handle_errors(self, e, **kw): + """ + Centralize error handling in one place. + + e is the error to be raised + **kw is an exception-specific list of options + """ + if not isinstance(e,ldap.TIMEOUT): + desc = e.args[0]['desc'].strip() + info = e.args[0].get('info','').strip() + else: + desc = '' + info = '' + + try: + # re-raise the error so we can handle it + raise e + except ldap.NO_SUCH_OBJECT, e: + args = kw.get('args', '') + raise errors2.NotFound(msg=notfound(args)) + except ldap.ALREADY_EXISTS, e: + raise errors2.DuplicateEntry() + except ldap.CONSTRAINT_VIOLATION, e: + # This error gets thrown by the uniqueness plugin + if info == 'Another entry with the same attribute value already exists': + raise errors2.DuplicateEntry() + else: + raise errors2.DatabaseError(desc=desc,info=info) + except ldap.INSUFFICIENT_ACCESS, e: + raise errors2.ACIError(info=info) + except ldap.NO_SUCH_ATTRIBUTE: + # this is raised when a 'delete' attribute isn't found. + # it indicates the previous attribute was removed by another + # update, making the oldentry stale. + raise errors2.MidairCollision() + except ldap.ADMINLIMIT_EXCEEDED, e: + raise errors2.LimitsExceeded() + except ldap.SIZELIMIT_EXCEEDED, e: + raise errors2.LimitsExceeded() + except ldap.TIMELIMIT_EXCEEDED, e: + raise errors2.LimitsExceeded() + except ldap.LDAPError, e: + raise errors2.DatabaseError(desc=desc,info=info) + def toLDAPURL(self): return "ldap://%s:%d/" % (self.host,self.port) @@ -271,11 +315,14 @@ class IPAdmin(SimpleLDAPObject): self.proxydn = proxydn def set_krbccache(self, krbccache, principal): - if krbccache is not None: - os.environ["KRB5CCNAME"] = krbccache - self.sasl_interactive_bind_s("", sasl_auth) - self.principal = principal - self.proxydn = None + try: + if krbccache is not None: + os.environ["KRB5CCNAME"] = krbccache + self.sasl_interactive_bind_s("", sasl_auth) + self.principal = principal + self.proxydn = None + except ldap.LDAPError, e: + self.__handle_errors(e, **{}) def do_simple_bind(self, binddn="cn=directory manager", bindpw=""): self.binddn = binddn @@ -293,10 +340,9 @@ class IPAdmin(SimpleLDAPObject): try: res = self.search(*args) objtype, obj = self.result(res) - except ldap.NO_SUCH_OBJECT, e: - raise errors2.NotFound(msg=notfound(args)) except ldap.LDAPError, e: - raise errors.DatabaseError, e + kw = {'args': args} + self.__handle_errors(e, **kw) if not obj: raise errors2.NotFound(msg=notfound(args)) @@ -316,11 +362,9 @@ class IPAdmin(SimpleLDAPObject): try: res = self.search(*args) objtype, obj = self.result(res) - except (ldap.ADMINLIMIT_EXCEEDED, ldap.SIZELIMIT_EXCEEDED), e: - # Too many results returned by search - raise e except ldap.LDAPError, e: - raise errors.DatabaseError, e + kw = {'args': args} + self.__handle_errors(e, **kw) if not obj: raise errors2.NotFound(msg=notfound(args)) @@ -357,7 +401,8 @@ class IPAdmin(SimpleLDAPObject): ldap.TIMELIMIT_EXCEEDED), e: partial = 1 except ldap.LDAPError, e: - raise errors.DatabaseError, e + kw = {'args': args} + self.__handle_errors(e, **kw) if not entries: raise errors2.NotFound(msg=notfound(args)) @@ -379,18 +424,9 @@ class IPAdmin(SimpleLDAPObject): if sctrl is not None: self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) self.add_s(*args) - except ldap.ALREADY_EXISTS, e: - raise errors2.DuplicateEntry - except ldap.CONSTRAINT_VIOLATION, e: - # This error gets thrown by the uniqueness plugin - if e.args[0].get('info','') == 'Another entry with the same attribute value already exists': - raise errors2.DuplicateEntry - else: - raise errors.DatabaseError, e - except ldap.INSUFFICIENT_ACCESS, e: - raise errors2.ACIError(info=e.args[0].get('info','')) except ldap.LDAPError, e: - raise errors.DatabaseError, e + kw = {'args': args} + self.__handle_errors(e, **kw) return True def updateRDN(self, dn, newrdn): @@ -407,7 +443,8 @@ class IPAdmin(SimpleLDAPObject): self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) self.modrdn_s(dn, newrdn, delold=1) except ldap.LDAPError, e: - raise errors.DatabaseError, e + kw = {'args': args} + self.__handle_errors(e, **kw) return True def updateEntry(self,dn,oldentry,newentry): @@ -425,15 +462,9 @@ class IPAdmin(SimpleLDAPObject): if sctrl is not None: self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) self.modify_s(dn, modlist) - # this is raised when a 'delete' attribute isn't found. - # it indicates the previous attribute was removed by another - # update, making the oldentry stale. - except ldap.NO_SUCH_ATTRIBUTE: - raise errors.MidairCollision - except ldap.INSUFFICIENT_ACCESS, e: - raise errors2.ACIError(info=e.args[0].get('info','')) except ldap.LDAPError, e: - raise errors.DatabaseError, e + kw = {'args': args} + self.__handle_errors(e, **kw) return True def generateModList(self, old_entry, new_entry): @@ -491,7 +522,8 @@ class IPAdmin(SimpleLDAPObject): self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) self.modify_s(dn, modlist) except ldap.LDAPError, e: - raise errors.DatabaseError, e + kw = {'args': args} + self.__handle_errors(e, **kw) return True def deleteEntry(self,*args): @@ -503,10 +535,9 @@ class IPAdmin(SimpleLDAPObject): if sctrl is not None: self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) self.delete_s(*args) - except ldap.INSUFFICIENT_ACCESS, e: - raise errors2.ACIError(info=e.args[0].get('info','')) except ldap.LDAPError, e: - raise errors.DatabaseError, e + kw = {'args': args} + self.__handle_errors(e, **kw) return True def modifyPassword(self,dn,oldpass,newpass): @@ -524,7 +555,8 @@ class IPAdmin(SimpleLDAPObject): self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) self.passwd_s(dn, oldpass, newpass) except ldap.LDAPError, e: - raise errors.DatabaseError, e + kw = {'args': args} + self.__handle_errors(e, **kw) return True def __wrapmethods(self): diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py index ca084902..b823c2ac 100644 --- a/ipaserver/plugins/ldap2.py +++ b/ipaserver/plugins/ldap2.py @@ -44,7 +44,7 @@ from ldap.controls import LDAPControl from ldap.ldapobject import SimpleLDAPObject from ipalib import api -from ipalib import errors, errors2 +from ipalib import errors2 from ipalib.crud import CrudBackend # attribute syntax to python type mapping, 'SYNTAX OID': type @@ -87,7 +87,7 @@ def _load_schema(host, port): conn.unbind_s() except _ldap.LDAPError, e: # TODO: raise a more appropriate exception - raise errors.DatabaseError + self.__handle_errors(e, **{}) except IndexError: # no 'cn=schema' entry in LDAP? some servers use 'cn=subschema' # TODO: DS uses 'cn=schema', support for other server? @@ -168,6 +168,51 @@ class ldap2(CrudBackend): else: entry_attrs[k] = attr_type(v) + def __handle_errors(self, e, **kw): + """ + Centralize error handling in one place. + + e is the error to be raised + **kw is an exception-specific list of options + """ + if not isinstance(e,ldap.TIMEOUT): + desc = e.args[0]['desc'].strip() + info = e.args[0].get('info','').strip() + else: + desc = '' + info = '' + + try: + # re-raise the error so we can handle it + raise e + except _ldap.NO_SUCH_OBJECT, e: + # args = kw.get('args', '') + # raise errors2.NotFound(msg=notfound(args)) + raise errors2.NotFound() + except _ldap.ALREADY_EXISTS, e: + raise errors2.DuplicateEntry() + except _ldap.CONSTRAINT_VIOLATION, e: + # This error gets thrown by the uniqueness plugin + if info == 'Another entry with the same attribute value already exists': + raise errors2.DuplicateEntry() + else: + raise errors2.DatabaseError(desc=desc,info=info) + except _ldap.INSUFFICIENT_ACCESS, e: + raise errors2.ACIError(info=info) + except _ldap.NO_SUCH_ATTRIBUTE: + # this is raised when a 'delete' attribute isn't found. + # it indicates the previous attribute was removed by another + # update, making the oldentry stale. + raise errors2.MidairCollision() + except _ldap.ADMINLIMIT_EXCEEDED, e: + raise errors2.LimitsExceeded() + except _ldap.SIZELIMIT_EXCEEDED, e: + raise errors2.LimitsExceeded() + except _ldap.TIMELIMIT_EXCEEDED, e: + raise errors2.LimitsExceeded() + except _ldap.LDAPError, e: + raise errors2.DatabaseError(desc=desc,info=info) + def create_connection(self, host=None, port=None, ccache=None, bind_dn='', bind_pw='', debug_level=255, tls_cacertfile=None, tls_certfile=None, tls_keyfile=None): @@ -291,15 +336,8 @@ class ldap2(CrudBackend): # pass arguments to python-ldap try: self.conn.add_s(dn, list(entry_attrs_copy.iteritems())) - except _ldap.ALREADY_EXISTS, e: - raise errors2.DuplicateEntry - except _ldap.CONSTRAINT_VIOLATION, e: - if e.args[0].get('info', '') == _uniqueness_plugin_error: - raise errors2.DuplicateEntry - else: - raise errors.DatabaseError, e except _ldap.LDAPError, e: - raise errors.DatabaseError, e + self.__handle_errors(e, **{}) # generating filters for find_entry # some examples: @@ -403,7 +441,7 @@ class ldap2(CrudBackend): _ldap.SIZELIMIT_EXCEEDED), e: raise e except _ldap.LDAPError, e: - raise errors.DatabaseError, e + self.__handle_errors(e, **{}) if not res: raise errors2.NotFound() @@ -450,7 +488,7 @@ class ldap2(CrudBackend): try: self.conn.rename_s(dn, new_rdn, delold=int(del_old)) except _ldap.LDAPError, e: - raise errors.DatabaseError, e + self.__handle_errors(e, **{}) def _generate_modlist(self, dn, entry_attrs): # get original entry @@ -500,15 +538,13 @@ class ldap2(CrudBackend): # generate modlist modlist = self._generate_modlist(dn, entry_attrs_copy) if not modlist: - raise errors.EmptyModlist + raise errors2.EmptyModlist() # pass arguments to python-ldap try: self.conn.modify_s(dn, modlist) - except _ldap.NO_SUCH_ATTRIBUTE: - raise errors.MidairCollision except _ldap.LDAPError, e: - raise errors.DatabaseError, e + self.__handle_errors(e, **{}) def delete_entry(self, dn): """Delete entry.""" @@ -519,10 +555,8 @@ class ldap2(CrudBackend): # pass arguments to python-ldap try: self.conn.delete_s(dn) - except _ldap.INSUFFICIENT_ACCESS, e: - raise errors.InsuficientAccess, e except _ldap.LDAPError, e: - raise errors.DatabaseError, e + self.__handle_errors(e, **{}) def modify_password(self, dn, old_pass, new_pass): """Set user password.""" @@ -536,7 +570,7 @@ class ldap2(CrudBackend): try: self.passwd_s(dn, odl_pass, new_pass) except _ldap.LDAPError, e: - raise errors.DatabaseError, e + self.__handle_errors(e, **{}) def add_entry_to_group(self, dn, group_dn, member_attr='member'): """Add entry to group.""" @@ -545,7 +579,7 @@ class ldap2(CrudBackend): group_dn = self.normalize_dn(group_dn) # check if we're not trying to add group into itself if dn == group_dn: - raise errors.SameGroupError + raise errors2.SameGroupError() # check if the entry exists (dn, entry_attrs) = self.get_entry(dn, ['objectClass']) @@ -575,7 +609,7 @@ class ldap2(CrudBackend): try: members.remove(dn) except ValueError: - raise errors.NotGroupMember + raise errors2.NotGroupMember() group_entry_attrs[member_attr] = members # update group entry @@ -592,11 +626,11 @@ class ldap2(CrudBackend): account_lock_attr = account_lock_attr[0].lower() if active: if account_lock_attr == 'false': - raise errors.AlreadyActiveError + raise errors2.AlreadyActive() else: if account_lock_attr == 'true': - raise errors.AlreadyInactiveError - + raise errors2.AlreadyInactive() + # check if nsAccountLock attribute is in the entry itself is_member = False member_of_attr = entry_attrs.get('memberOf', []) @@ -605,7 +639,7 @@ class ldap2(CrudBackend): is_member = True break if not is_member and entry_attrs.has_key('nsAccountLock'): - raise errors.HasNSAccountLock + raise errors2.HasNSAccountLock() activated_filter = '(cn=activated)' inactivated_filter = '(cn=inactivated)' @@ -619,7 +653,7 @@ class ldap2(CrudBackend): (group_dn, group_entry_attrs) = entries[0] try: self.remove_entry_from_group(dn, group_dn) - except errors.NotGroupMember: + except errors2.NotGroupMember: pass # add the entry to the activated/inactivated group if necessary @@ -638,11 +672,11 @@ class ldap2(CrudBackend): (group_dn, group_entry_attrs) = entries[0] try: self.add_entry_to_group(dn, group_dn) - except errors.EmptyModlist: + except errors2.EmptyModlist: if active: - raise errors.AlreadyActiveError + raise errors2.AlreadyActive() else: - raise errors.AlreadyInactiveError + raise errors2.AlreadyInactive() def activate_entry(self, dn): """Mark entry active.""" diff --git a/ipaserver/servercore.py b/ipaserver/servercore.py index bf3b457f..ee0e518d 100644 --- a/ipaserver/servercore.py +++ b/ipaserver/servercore.py @@ -23,7 +23,7 @@ import re from ipalib.request import context from ipaserver import ipaldap import ipautil -from ipalib import errors, errors2 +from ipalib import errors2 from ipalib import api def convert_entry(ent): @@ -341,16 +341,16 @@ def mark_entry_active (dn): if entry.get('nsaccountlock', 'false').lower() == "false": api.log.debug("IPA: already active") - raise errors.AlreadyActiveError + raise errors2.AlreadyActive() if has_nsaccountlock(dn): api.log.debug("IPA: appears to have the nsaccountlock attribute") - raise errors.HasNSAccountLock + raise errors2.HasNSAccountLock() group = get_entry_by_cn("inactivated", None) try: remove_member_from_group(entry.get('dn'), group.get('dn')) - except errors.NotGroupMember: + except errors2.NotGroupMember: # Perhaps the user is there as a result of group membership pass @@ -377,18 +377,18 @@ def mark_entry_inactive (dn): if entry.get('nsaccountlock', 'false').lower() == "true": api.log.debug("IPA: already marked as inactive") - raise errors.AlreadyInactiveError + raise errors2.AlreadyInactive() if has_nsaccountlock(dn): api.log.debug("IPA: appears to have the nsaccountlock attribute") - raise errors.HasNSAccountLock + raise errors2.HasNSAccountLock() # First see if they are in the activated group as this will override # the our inactivation. group = get_entry_by_cn("activated", None) try: remove_member_from_group(dn, group.get('dn')) - except errors.NotGroupMember: + except errors2.NotGroupMember: # this is fine, they may not be explicitly in this group pass @@ -405,7 +405,7 @@ def add_member_to_group(member_dn, group_dn, memberattr='member'): api.log.info("IPA: add_member_to_group '%s' to '%s'" % (member_dn, group_dn)) if member_dn.lower() == group_dn.lower(): # You can't add a group to itself - raise errors.SameGroupError + raise errors2.RecursiveGroup() group = get_entry_by_dn(group_dn, None) if group is None: @@ -423,10 +423,7 @@ def add_member_to_group(member_dn, group_dn, memberattr='member'): members.append(member_dn) group[memberattr] = members - try: - return update_entry(group) - except errors.EmptyModlist: - raise + return update_entry(group) def remove_member_from_group(member_dn, group_dn, memberattr='member'): """Remove a member_dn from an existing group.""" @@ -444,7 +441,7 @@ def remove_member_from_group(member_dn, group_dn, memberattr='member'): members = group.get(memberattr, False) if not members: - raise errors.NotGroupMember + raise errors2.NotGroupMember() if isinstance(members,basestring): members = [members] @@ -453,15 +450,10 @@ def remove_member_from_group(member_dn, group_dn, memberattr='member'): try: members.remove(member_dn) except ValueError: - # member is not in the group - # FIXME: raise more specific error? - raise errors.NotGroupMember + raise errors2.NotGroupMember() except Exception, e: raise e group[memberattr] = members - try: - return update_entry(group) - except errors.EmptyModlist: - raise + return update_entry(group) diff --git a/tests/test_ipalib/test_errors.py b/tests/test_ipalib/test_errors.py deleted file mode 100644 index f1dd5dc8..00000000 --- a/tests/test_ipalib/test_errors.py +++ /dev/null @@ -1,289 +0,0 @@ -# Authors: -# Jason Gerard DeRose <jderose@redhat.com> -# -# Copyright (C) 2008 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 only -# -# 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 - -""" -Test the `ipalib.errors` module. -""" - -from tests.util import raises, ClassChecker -from ipalib import errors - - -type_format = '%s: need a %r; got %r' - - -def check_TypeError(f, value, type_, name, **kw): - e = raises(TypeError, f, value, type_, name, **kw) - assert e.value is value - assert e.type is type_ - assert e.name is name - assert str(e) == type_format % (name, type_, value) - - -def test_raise_TypeError(): - """ - Test the `ipalib.errors.raise_TypeError` function. - """ - f = errors.raise_TypeError - value = 'Hello.' - type_ = unicode - name = 'message' - - check_TypeError(f, value, type_, name) - - # name not an str - fail_name = 42 - e = raises(AssertionError, f, value, type_, fail_name) - assert str(e) == type_format % ('name', str, fail_name), str(e) - - # type_ not a type: - fail_type = unicode() - e = raises(AssertionError, f, value, fail_type, name) - assert str(e) == type_format % ('type_', type, fail_type) - - # type(value) is type_: - fail_value = u'How are you?' - e = raises(AssertionError, f, fail_value, type_, name) - assert str(e) == 'value: %r is a %r' % (fail_value, type_) - - -def test_check_type(): - """ - Test the `ipalib.errors.check_type` function. - """ - f = errors.check_type - value = 'How are you?' - type_ = str - name = 'greeting' - - # Should pass: - assert value is f(value, type_, name) - assert None is f(None, type_, name, allow_none=True) - - # Should raise TypeError - check_TypeError(f, None, type_, name) - check_TypeError(f, value, basestring, name) - check_TypeError(f, value, unicode, name) - - # name not an str - fail_name = unicode(name) - e = raises(AssertionError, f, value, type_, fail_name) - assert str(e) == type_format % ('name', str, fail_name) - - # type_ not a type: - fail_type = 42 - e = raises(AssertionError, f, value, fail_type, name) - assert str(e) == type_format % ('type_', type, fail_type) - - # allow_none not a bool: - fail_bool = 0 - e = raises(AssertionError, f, value, type_, name, allow_none=fail_bool) - assert str(e) == type_format % ('allow_none', bool, fail_bool) - - -def test_check_isinstance(): - """ - Test the `ipalib.errors.check_isinstance` function. - """ - f = errors.check_isinstance - value = 'How are you?' - type_ = str - name = 'greeting' - - # Should pass: - assert value is f(value, type_, name) - assert value is f(value, basestring, name) - assert None is f(None, type_, name, allow_none=True) - - # Should raise TypeError - check_TypeError(f, None, type_, name) - check_TypeError(f, value, unicode, name) - - # name not an str - fail_name = unicode(name) - e = raises(AssertionError, f, value, type_, fail_name) - assert str(e) == type_format % ('name', str, fail_name) - - # type_ not a type: - fail_type = 42 - e = raises(AssertionError, f, value, fail_type, name) - assert str(e) == type_format % ('type_', type, fail_type) - - # allow_none not a bool: - fail_bool = 0 - e = raises(AssertionError, f, value, type_, name, allow_none=fail_bool) - assert str(e) == type_format % ('allow_none', bool, fail_bool) - - -class test_IPAError(ClassChecker): - """ - Test the `ipalib.errors.IPAError` exception. - """ - _cls = errors.IPAError - - def test_class(self): - """ - Test the `ipalib.errors.IPAError` exception. - """ - assert self.cls.__bases__ == (StandardError,) - - def test_init(self): - """ - Test the `ipalib.errors.IPAError.__init__` method. - """ - args = ('one fish', 'two fish') - e = self.cls(*args) - assert e.args == args - assert self.cls().args == tuple() - - def test_str(self): - """ - Test the `ipalib.errors.IPAError.__str__` method. - """ - f = 'The %s color is %s.' - class custom_error(self.cls): - format = f - for args in [('sexiest', 'red'), ('most-batman-like', 'black')]: - e = custom_error(*args) - assert e.args == args - assert str(e) == f % args - - -class test_ValidationError(ClassChecker): - """ - Test the `ipalib.errors.ValidationError` exception. - """ - _cls = errors.ValidationError - - def test_class(self): - """ - Test the `ipalib.errors.ValidationError` exception. - """ - assert self.cls.__bases__ == (errors.IPAError,) - - def test_init(self): - """ - Test the `ipalib.errors.ValidationError.__init__` method. - """ - name = 'login' - value = 'Whatever' - error = 'Must be lowercase.' - for index in (None, 3): - e = self.cls(name, value, error, index=index) - assert e.name is name - assert e.value is value - assert e.error is error - assert e.index is index - assert str(e) == 'invalid %r value %r: %s' % (name, value, error) - # Check that index default is None: - assert self.cls(name, value, error).index is None - # Check non str name raises AssertionError: - raises(AssertionError, self.cls, unicode(name), value, error) - # Check non int index raises AssertionError: - raises(AssertionError, self.cls, name, value, error, index=5.0) - # Check negative index raises AssertionError: - raises(AssertionError, self.cls, name, value, error, index=-2) - - -class test_ConversionError(ClassChecker): - """ - Test the `ipalib.errors.ConversionError` exception. - """ - _cls = errors.ConversionError - - def test_class(self): - """ - Test the `ipalib.errors.ConversionError` exception. - """ - assert self.cls.__bases__ == (errors.ValidationError,) - - def test_init(self): - """ - Test the `ipalib.errors.ConversionError.__init__` method. - """ - name = 'some_arg' - value = '42.0' - class type_(object): - conversion_error = 'Not an integer' - for index in (None, 7): - e = self.cls(name, value, type_, index=index) - assert e.name is name - assert e.value is value - assert e.type is type_ - assert e.error is type_.conversion_error - assert e.index is index - assert str(e) == 'invalid %r value %r: %s' % (name, value, - type_.conversion_error) - # Check that index default is None: - assert self.cls(name, value, type_).index is None - - -class test_RuleError(ClassChecker): - """ - Test the `ipalib.errors.RuleError` exception. - """ - _cls = errors.RuleError - - def test_class(self): - """ - Test the `ipalib.errors.RuleError` exception. - """ - assert self.cls.__bases__ == (errors.ValidationError,) - - def test_init(self): - """ - Test the `ipalib.errors.RuleError.__init__` method. - """ - name = 'whatever' - value = 'The smallest weird number.' - def my_rule(value): - return 'Value is bad.' - error = my_rule(value) - for index in (None, 42): - e = self.cls(name, value, error, my_rule, index=index) - assert e.name is name - assert e.value is value - assert e.error is error - assert e.rule is my_rule - # Check that index default is None: - assert self.cls(name, value, error, my_rule).index is None - - -class test_RequirementError(ClassChecker): - """ - Test the `ipalib.errors.RequirementError` exception. - """ - _cls = errors.RequirementError - - def test_class(self): - """ - Test the `ipalib.errors.RequirementError` exception. - """ - assert self.cls.__bases__ == (errors.ValidationError,) - - def test_init(self): - """ - Test the `ipalib.errors.RequirementError.__init__` method. - """ - name = 'givenname' - e = self.cls(name) - assert e.name is name - assert e.value is None - assert e.error == 'Required' - assert e.index is None diff --git a/tests/test_ipalib/test_frontend.py b/tests/test_ipalib/test_frontend.py index 7be94c4f..74437041 100644 --- a/tests/test_ipalib/test_frontend.py +++ b/tests/test_ipalib/test_frontend.py @@ -26,7 +26,7 @@ from tests.util import check_TypeError, ClassChecker, create_test_api from tests.util import assert_equal from ipalib.constants import TYPE_ERROR from ipalib.base import NameSpace -from ipalib import frontend, backend, plugable, errors2, errors, parameters, config +from ipalib import frontend, backend, plugable, errors2, parameters, config def test_RULE_FLAG(): assert frontend.RULE_FLAG == 'validation_rule' diff --git a/tests/test_ipalib/test_plugable.py b/tests/test_ipalib/test_plugable.py index c6c84fa1..4f54d3f7 100644 --- a/tests/test_ipalib/test_plugable.py +++ b/tests/test_ipalib/test_plugable.py @@ -25,7 +25,7 @@ import inspect from tests.util import raises, no_set, no_del, read_only from tests.util import getitem, setitem, delitem from tests.util import ClassChecker, create_test_api -from ipalib import plugable, errors, errors2 +from ipalib import plugable, errors2 class test_SetProxy(ClassChecker): diff --git a/tests/test_xmlrpc/xmlrpc_test.py b/tests/test_xmlrpc/xmlrpc_test.py index 8e626600..7ff43c74 100644 --- a/tests/test_xmlrpc/xmlrpc_test.py +++ b/tests/test_xmlrpc/xmlrpc_test.py @@ -26,7 +26,6 @@ import socket import nose from ipalib import api, request from ipalib import errors2 -from ipalib import errors # Initialize the API. We do this here so that one can run the tests # individually instead of at the top-level. If API.bootstrap() |