From b74780e4dbbc9c3ef894accc3c9696a1a2a8bc4b Mon Sep 17 00:00:00 2001 From: William Brown Date: Dec 20 2019 01:51:40 +0000 Subject: Ticket 50649 - lib389 without defaults.inf Bug Description: In deploying 389-ds-portal to a container, I notice that lib389 would fail with "unable to locate defaults.inf". This means we need some way to "proceed" sanely when we can't find defaults.inf. Fix Description: Re-arrange some parts of the init functions to make subobjects init later, and have paths able to access versions online instead. This also flags in paths if we are local or remote. https://pagure.io/389-ds-base/issue/50649 Author: William Brown Review by: spichugi (Thanks!) --- diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py index 0d1ab57..4f4705e 100644 --- a/src/lib389/lib389/__init__.py +++ b/src/lib389/lib389/__init__.py @@ -402,10 +402,11 @@ class DirSrv(SimpleLDAPObject, object): self.confdir = None - self.ds_paths = Paths(instance=self) + # We can't assume the paths state yet ... + self.ds_paths = Paths(instance=self, local=False) # Set the default systemd status. This MAY be overidden in the setup utils # as required. - self.systemd = self.ds_paths.with_systemd + # self.systemd = self.ds_paths.with_systemd # Reset the args (py.test reuses the args_instance for each test case) # We allocate a "default" prefix here which allows an un-allocate or @@ -416,7 +417,6 @@ class DirSrv(SimpleLDAPObject, object): # self.ds_paths.prefix = args_instance[SER_DEPLOYED_DIR] self.__wrapmethods() - self.__add_brookers__() def __str__(self): """XXX and in SSL case?""" @@ -441,7 +441,7 @@ class DirSrv(SimpleLDAPObject, object): # The lack of this value basically rules it out in most cases self.isLocal = True - self.ds_paths = Paths(serverid, instance=self) + self.ds_paths = Paths(serverid, instance=self, local=self.isLocal) self.serverid = serverid # Do we have ldapi settings? @@ -487,7 +487,7 @@ class DirSrv(SimpleLDAPObject, object): self.log.debug('SER_SERVERID_PROP not provided, assuming non-local instance') # The lack of this value basically rules it out in most cases self.isLocal = False - self.ds_paths = Paths(instance=self) + self.ds_paths = Paths(instance=self, local=self.isLocal) # Do we have ldapi settings? # Do we really need .strip() on this? @@ -532,13 +532,13 @@ class DirSrv(SimpleLDAPObject, object): raise ValueError("invalid state for calling allocate: %s" % self.state) - self.isLocal = False + self.isLocal = True if SER_SERVERID_PROP not in args: self.log.debug('SER_SERVERID_PROP not provided, assuming non-local instance') # The lack of this value basically rules it out in most cases - self.ds_paths = Paths(instance=self) + self.ds_paths = Paths(instance=self, local=self.isLocal) else: - self.ds_paths = Paths(serverid=args[SER_SERVERID_PROP], instance=self) + self.ds_paths = Paths(serverid=args[SER_SERVERID_PROP], instance=self, local=self.isLocal) # Settings from args of server attributes self.serverid = args.get(SER_SERVERID_PROP, None) # Probably local? @@ -1082,9 +1082,11 @@ class DirSrv(SimpleLDAPObject, object): Authenticated, now finish the initialization """ self.log.debug("open(): bound as %s", self.binddn) - if not connOnly: + if not connOnly and self.isLocal: self.__initPart2() self.state = DIRSRV_STATE_ONLINE + # Now that we're online, some of our methods may try to query the version online. + self.__add_brookers__() def close(self): ''' @@ -1710,7 +1712,7 @@ class DirSrv(SimpleLDAPObject, object): return self.ds_paths.asan_enabled def with_systemd(self): - return self.systemd + return self.ds_paths.with_systemd def get_server_tls_subject(self): """ Get the servers TLS subject line for enrollment purposes. diff --git a/src/lib389/lib389/idm/user.py b/src/lib389/lib389/idm/user.py index 4d3acf8..9922fed 100644 --- a/src/lib389/lib389/idm/user.py +++ b/src/lib389/lib389/idm/user.py @@ -53,7 +53,7 @@ class nsUserAccount(Account): :type dn: str """ def __init__(self, instance, dn=None): - if ds_is_older('1.4.0'): + if ds_is_older('1.4.0', instance=instance): raise Exception("Not supported") super(nsUserAccount, self).__init__(instance, dn) self._rdn_attribute = RDN @@ -154,11 +154,11 @@ class UserAccount(Account): 'inetOrgPerson', 'organizationalPerson', ] - if ds_is_older('1.3.7'): + if ds_is_older('1.3.7', instance=instance): self._create_objectclasses.append('inetUser') else: self._create_objectclasses.append('nsMemberOf') - if not ds_is_older('1.4.0'): + if not ds_is_older('1.4.0', instance=instance): self._create_objectclasses.append('nsAccount') user_compare_exclude = [ 'nsUniqueId', diff --git a/src/lib389/lib389/instance/setup.py b/src/lib389/lib389/instance/setup.py index 073c7c7..c1a85e5 100644 --- a/src/lib389/lib389/instance/setup.py +++ b/src/lib389/lib389/instance/setup.py @@ -807,8 +807,8 @@ class SetupDs(object): # Should I move this import? I think this prevents some recursion from lib389 import DirSrv ds_instance = DirSrv(self.verbose) - if self.containerised: - ds_instance.systemd = general['systemd'] + # if self.containerised: + # ds_instance.systemd = general['systemd'] args = { SER_PORT: slapd['port'], SER_SERVERID_PROP: slapd['instance_name'], diff --git a/src/lib389/lib389/monitor.py b/src/lib389/lib389/monitor.py index 290cad5..283c9fb 100644 --- a/src/lib389/lib389/monitor.py +++ b/src/lib389/lib389/monitor.py @@ -118,7 +118,7 @@ class MonitorLDBM(DSLdapObject): 'dbcacheroevict', 'dbcacherwevict', ] - if not ds_is_older("1.4.0"): + if not ds_is_older("1.4.0", instance=instance): self._backend_keys.extend([ 'normalizeddncachetries', 'normalizeddncachehits', diff --git a/src/lib389/lib389/paths.py b/src/lib389/lib389/paths.py index b1734ed..ebada88 100644 --- a/src/lib389/lib389/paths.py +++ b/src/lib389/lib389/paths.py @@ -82,13 +82,14 @@ CONFIG_MAP = { 'access_log' : ('cn=config', 'nsslapd-accesslog'), 'audit_log' : ('cn=config', 'nsslapd-auditlog'), 'ldapi': ('cn=config', 'nsslapd-ldapifilepath'), + 'version': ('', 'vendorVersion'), } SECTION = 'slapd' class Paths(object): - def __init__(self, serverid=None, instance=None): + def __init__(self, serverid=None, instance=None, local=True): """ Parses and uses a set of default paths from wellknown locations. The list of keys available is from the MUST attribute in this module. @@ -113,6 +114,7 @@ class Paths(object): self._config = None self._serverid = serverid self._instance = instance + self._islocal = local def _get_defaults_loc(self, search_paths): ## THIS IS HOW WE HANDLE A PREFIX INSTALL @@ -148,7 +150,7 @@ class Paths(object): def __getattr__(self, name): from lib389.utils import ensure_str - if self._defaults_cached is False: + if self._defaults_cached is False and self._islocal: self._read_defaults() self._validate_defaults() # Are we online? Is our key in the config map? @@ -158,8 +160,14 @@ class Paths(object): ent = self._instance.getEntry(dn, attrlist=[attr,]) # If the server doesn't have it, fall back to our configuration. if attr is not None: - return ensure_str(ent.getValue(attr)) - + v = ensure_str(ent.getValue(attr)) + # Do we need to post-process the value? + if name == 'version': + # We need to post process this - it's 389-Directory/1.4.2.2.20191031git8166d8345 B2019.304.19 + # but we need a string like: 1.4.2.2.20191031git8166d8345 + v = v.split('/')[1].split()[0] + return v + # Else get from the config if self._serverid is not None: return ensure_str(self._config.get(SECTION, name).format(instance_name=self._serverid)) else: @@ -167,7 +175,7 @@ class Paths(object): @property def asan_enabled(self): - if self._defaults_cached is False: + if self._defaults_cached is False and self._islocal: self._read_defaults() self._validate_defaults() if self._config.has_option(SECTION, 'asan_enabled'): @@ -177,7 +185,7 @@ class Paths(object): @property def with_systemd(self): - if self._defaults_cached is False: + if self._defaults_cached is False and self._islocal: self._read_defaults() self._validate_defaults() if self._is_container: @@ -190,10 +198,10 @@ class Paths(object): @property def perl_enabled(self): - if self._defaults_cached is False: + if self._defaults_cached is False and self._islocal: self._read_defaults() self._validate_defaults() if self._config.has_option(SECTION, 'enable_perl'): - if self._config.get(SECTION, 'enable_perl') == 'no': - return False - return True + if self._config.get(SECTION, 'enable_perl') == 'yes': + return True + return False diff --git a/src/lib389/lib389/utils.py b/src/lib389/lib389/utils.py index 36422dd..f0c94a2 100644 --- a/src/lib389/lib389/utils.py +++ b/src/lib389/lib389/utils.py @@ -1060,7 +1060,7 @@ def generate_ds_params(inst_num, role=ReplicaRole.STANDALONE): return instance_data -def get_ds_version(): +def get_ds_version(paths=None): """ Return version of ns-slapd installed on this system. This is determined by the defaults.inf file, so is correct for the built and installed ns-slapd binary. This function works without @@ -1068,16 +1068,21 @@ def get_ds_version(): returns a string of the form: 1.3.4.8 """ - p = Paths() - return p.version + if paths is None: + paths = Paths() + return paths.version -def ds_is_related(relation, ds_ver, *ver): +def ds_is_related(relation, *ver, instance=None): """ Return a result of a comparison between the current version of ns-slapd and a provided version. """ ops = {'older': operator.lt, 'newer': operator.ge} + if instance is None: + ds_ver = get_ds_version() + else: + ds_ver = get_ds_version(instance.ds_paths) if len(ver) > 1: for cmp_ver in ver: if cmp_ver.startswith(ds_ver[:3]): @@ -1086,20 +1091,18 @@ def ds_is_related(relation, ds_ver, *ver): return ops[relation](LegacyVersion(ds_ver), LegacyVersion(ver[0])) -def ds_is_older(*ver): +def ds_is_older(*ver, instance=None): """ Return True if the current version of ns-slapd is older than a provided version """ - ds_ver = get_ds_version() - return ds_is_related('older', ds_ver, *ver) + return ds_is_related('older', *ver, instance=instance) -def ds_is_newer(*ver): +def ds_is_newer(*ver, instance=None): """ Return True if the current version of ns-slapd is newer than a provided version """ - ds_ver = get_ds_version() - return ds_is_related('newer', ds_ver, *ver) + return ds_is_related('newer', *ver, instance=instance) def gentime_to_datetime(gentime):