def list(self, suffix=None, consumer_host=None, consumer_port=None, agmtdn=None): ''' Returns the search result of the replica agreement(s) under the replica (replicaRoot is 'suffix'). Either 'suffix' or 'agmtdn' need to be specfied. 'consumer_host' and 'consumer_port' are either not specified or specified both. If 'agmtdn' is specified, it returns the search result entry of that replication agreement. else if consumer host/port are specified it returns the replica agreements toward that consumer host:port. Finally if neither 'agmtdn' nor 'consumser host/port' are specifies it returns all the replica agreements under the replica (replicaRoot is 'suffix'). @param - suffix is the suffix targeted by the total update @param - consumer_host hostname of the consumer @param - consumer_port port of the consumer @param - agmtdn DN of the replica agreement @return - search result of the replica agreements @raise - InvalidArgument: if missing mandatory argument (agmtdn or suffix, then host and port) - ValueError - if some properties are not valid - NoSuchEntryError - If no replica defined for the suffix ''' if not suffix and not agmtdn: raise InvalidArgumentError("suffix or agmtdn are required") if (consumer_host and not consumer_port) or (not consumer_host and consumer_port): raise InvalidArgumentError( "consumer host/port are required together") if agmtdn: # easy case, just return the RA filt = "objectclass=*" return self.conn.search_s(agmtdn, ldap.SCOPE_BASE, filt) else: # Retrieve the replica replica_entries = self.conn.replica.list(suffix) if not replica_entries: raise NoSuchEntryError("Error: no replica set up for suffix " + suffix) replica_entry = replica_entries[0] # Now returns the replica agreement for that suffix that replicates to # consumer host/port if consumer_host and consumer_port: filt = "(&(|(objectclass=%s)(objectclass=%s))(%s=%s)(%s=%d))" % ( RA_OBJECTCLASS_VALUE, RA_WINDOWS_OBJECTCLASS_VALUE, RA_PROPNAME_TO_ATTRNAME[RA_CONSUMER_HOST], consumer_host, RA_PROPNAME_TO_ATTRNAME[RA_CONSUMER_PORT], consumer_port) else: filt = "(|(objectclass=%s)(objectclass=%s))" % ( RA_OBJECTCLASS_VALUE, RA_WINDOWS_OBJECTCLASS_VALUE) return self.conn.search_s(replica_entry.dn, ldap.SCOPE_ONELEVEL, filt)
def init(self, suffix=None, consumer_host=None, consumer_port=None): """Trigger a total update of the consumer replica - self is the supplier, - consumer is a DirSrv object (consumer can be a master) - cn_format - use this string to format the agreement name @param - suffix is the suffix targeted by the total update [mandatory] @param - consumer_host hostname of the consumer [mandatory] @param - consumer_port port of the consumer [mandatory] @raise InvalidArgument: if missing mandatory argurment (suffix/host/port) """ # # check the required parameters are set # if not suffix: self.log.fatal("initAgreement: suffix is missing") raise InvalidArgumentError('suffix is mandatory argument') nsuffix = normalizeDN(suffix) if not consumer_host: self.log.fatal("initAgreement: host is missing") raise InvalidArgumentError('host is mandatory argument') if not consumer_port: self.log.fatal("initAgreement: port is missing") raise InvalidArgumentError('port is mandatory argument') # # check the replica agreement already exist # replica_entries = self.conn.replica.list(suffix) if not replica_entries: raise NoSuchEntryError("Error: no replica set up for suffix " + suffix) replica_entry = replica_entries[0] self.log.debug( "initAgreement: looking for replica agreements under %s" % replica_entry.dn) try: filt = ( "(&(objectclass=nsds5replicationagreement)(nsds5replicahost=" + "%s)(nsds5replicaport=%d)(nsds5replicaroot=%s))" % (consumer_host, consumer_port, nsuffix)) entry = self.conn.getEntry(replica_entry.dn, ldap.SCOPE_ONELEVEL, filt) except ldap.NO_SUCH_OBJECT: self.log.fatal( "initAgreement: No replica agreement to %s:%d for suffix %s" % (consumer_host, consumer_port, nsuffix)) raise # # trigger the total init # self.log.info("Starting total init %s" % entry.dn) mod = [(ldap.MOD_ADD, 'nsds5BeginReplicaRefresh', 'start')] self.conn.modify_s(entry.dn, mod)
def setProperties(self, changelogdn=None, properties=None): if not changelogdn: raise InvalidArgumentError("changelog DN is missing") ents = self.changelog.list(changelogdn=changelogdn) if len(ents) != 1: raise ValueError("Changelog entry not found: %s" % changelogdn) # check that the given properties are valid for prop in properties: # skip the prefix to add/del value if not inProperties(prop, CHANGELOG_PROPNAME_TO_ATTRNAME): raise ValueError("unknown property: %s" % prop) # build the MODS mods = [] for prop in properties: # take the operation type from the property name val = rawProperty(prop) if str(prop).startswith('+'): op = ldap.MOD_ADD elif str(prop).startswith('-'): op = ldap.MOD_DELETE else: op = ldap.MOD_REPLACE mods.append( (op, REPLICA_PROPNAME_TO_ATTRNAME[val], properties[prop])) # that is fine now to apply the MOD self.conn.modify_s(ents[0].dn, mods)
def schedule(self, agmtdn=None, interval=ALWAYS): """Schedule the replication agreement :param agmtdn: DN of the replica agreement :type agmtdn: str :param interval: - 'HHMM-HHMM D+' With D=[0123456]+ - Agreement.ALWAYS - Agreement.NEVER :type interval: str :returns: None :raises: - ValueError - if interval is not valid; - ldap.NO_SUCH_OBJECT - if agmtdn does not exist """ if not agmtdn: raise InvalidArgumentError("agreement DN is missing") # check the validity of the interval if interval != Agreement.ALWAYS and interval != Agreement.NEVER: self._check_interval(interval) # Check if the replica agreement exists try: self.conn.getEntry(agmtdn, ldap.SCOPE_BASE) except ldap.NO_SUCH_OBJECT: raise # update it self.log.info("Schedule replication agreement %s" % agmtdn) mod = [(ldap.MOD_REPLACE, 'nsds5replicaupdateschedule', [ensure_bytes(interval)])] self.conn.modify_s(agmtdn, mod)
def disable(self, name=None, plugin_dn=None): ''' Disable a plugin If 'plugin_dn' and 'name' are provided, plugin_dn is used and 'name is not considered @param name - name of the plugin @param plugin_dn - DN of the plugin @return None @raise ValueError - if 'name' or 'plugin_dn' lead to unknown plugin InvalidArgumentError - if 'name' and 'plugin_dn' are missing ''' dn = plugin_dn or "cn=%s,%s" % (name, DN_PLUGIN) filt = "(objectclass=%s)" % PLUGINS_OBJECTCLASS_VALUE if not dn: raise InvalidArgumentError( "plugin 'name' and 'plugin_dn' are missing") ents = self.conn.search_s(dn, ldap.SCOPE_BASE, filt) if len(ents) != 1: raise ValueError("%s is unknown") self.conn.modify_s( dn, [(ldap.MOD_REPLACE, PLUGIN_PROPNAME_TO_ATTRNAME[PLUGIN_ENABLE], PLUGINS_ENABLE_OFF_VALUE)])
def getParent(self, suffix=None): ''' Returns the DN of a suffix that is the parent of the provided 'suffix'. If 'suffix' has no parent, it returns None @param suffix - suffix DN of the backend @return parent suffix DN @return ValueError - if suffix is not provided InvalidArgumentError - if suffix is not implemented on the server ''' if not suffix: raise ValueError("suffix is mandatory") ents = self.conn.mappingtree.list(suffix=suffix) if len(ents) == 0: raise InvalidArgumentError( "suffix %s is not implemented on that server" % suffix) mapping_tree = ents[0] if mapping_tree.hasValue(MT_PROPNAME_TO_ATTRNAME[MT_PARENT_SUFFIX]): return mapping_tree.getValue( MT_PROPNAME_TO_ATTRNAME[MT_PARENT_SUFFIX]) else: return None raise NotImplemented
def deleteAgreements(self, suffix=None): ''' Delete all the agreements for the suffix ''' # check the validity of the suffix if not suffix: self.log.fatal("disableReplication: suffix is missing") raise InvalidArgumentError("suffix missing") else: nsuffix = normalizeDN(suffix) # Build the replica config DN mtents = self.conn.mappingtree.list(suffix=nsuffix) mtent = mtents[0] dn_replica = ','.join((RDN_REPLICA, mtent.dn)) # Delete the agreements try: agmts = self.conn.agreement.list(suffix=suffix) for agmt in agmts: try: self.conn.delete_s(agmt.dn) except ldap.LDAPError, e: self.log.fatal( 'Failed to delete replica agreement (%s), error: %s' % (admt.dn, e.message('desc'))) raise ldap.LDAPError except ldap.LDAPError, e: self.log.fatal( 'Failed to search for replication agreements under (%s), error: %s' % (dn_replica, e.message('desc'))) raise ldap.LDAPError
def disableReplication(self, suffix=None): ''' Delete a replica related to the provided suffix. If this replica role was REPLICAROLE_HUB or REPLICAROLE_MASTER, it also deletes the changelog associated to that replica. If it exists some replication agreement below that replica, they are deleted. @param suffix - dn of suffix @return None @raise InvalidArgumentError - if suffix is missing ldap.LDAPError - for all other update failures ''' # check the validity of the suffix if not suffix: self.log.fatal("disableReplication: suffix is missing") raise InvalidArgumentError("suffix missing") else: nsuffix = normalizeDN(suffix) # Build the replica config DN mtents = self.conn.mappingtree.list(suffix=nsuffix) mtent = mtents[0] dn_replica = ','.join((RDN_REPLICA, mtent.dn)) # Delete the agreements try: self.deleteAgreements(nsuffix) except ldap.LDAPError, e: self.log.fatal('Failed to delete replica agreements!') raise ldap.LDAPError
def toSuffix(self, entry=None, name=None): ''' Return, for a given backend entry, the suffix values. Suffix values are identical from a LDAP point of views. Suffix values may be surrounded by ", or containing '\' escape characters. @param entry - LDAP entry of the backend @param name - backend DN @result list of values of suffix attribute (aka 'cn') @raise ldap.NO_SUCH_OBJECT - in name is invalid DN ValueError - entry does not contains the suffix attribute InvalidArgumentError - if both entry/name are missing ''' attr_suffix = BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_SUFFIX] if entry: if not entry.hasValue(attr_suffix): raise ValueError("Entry has no %s attribute %r" % (attr_suffix, entry)) return entry.getValues(attr_suffix) elif name: filt = "(objectclass=%s)" % BACKEND_OBJECTCLASS_VALUE try: attrs = [attr_suffix] ent = self.conn.getEntry(name, ldap.SCOPE_BASE, filt, attrs) self.log.debug("toSuffix: %s found by its DN" % ent.dn) except NoSuchEntryError: raise ldap.NO_SUCH_OBJECT("Backend DN not found: %s" % name) if not ent.hasValue(attr_suffix): raise ValueError("Entry has no %s attribute %r" % (attr_suffix, ent)) return ent.getValues(attr_suffix) else: raise InvalidArgumentError("entry or name are mandatory")
def getProperties(self, agmnt_dn=None, properties=None): """Get a dictionary of the requested properties. If properties parameter is missing, it returns all the properties. :param agmtdn: DN of the replica agreement :type agmtdn: str :param properties: List of properties name :type properties: list :returns: Returns a dictionary of the properties :raises: - ValueError - if invalid property name - ldap.NO_SUCH_OBJECT - if agmtdn does not exist - InvalidArgumentError - missing mandatory argument :supported properties are: RA_NAME, RA_SUFFIX, RA_BINDDN, RA_BINDPW, RA_METHOD, RA_DESCRIPTION, RA_SCHEDULE, RA_TRANSPORT_PROT, RA_FRAC_EXCLUDE, RA_FRAC_EXCLUDE_TOTAL_UPDATE, RA_FRAC_STRIP, RA_CONSUMER_PORT, RA_CONSUMER_HOST, RA_CONSUMER_TOTAL_INIT, RA_TIMEOUT, RA_CHANGES """ if not agmnt_dn: raise InvalidArgumentError("agmtdn is a mandatory argument") # # prepare the attribute list to retrieve from the RA # if properties is None, all RA attributes are retrieved # attrs = [] if properties: for prop_name in properties: prop_attr = RA_PROPNAME_TO_ATTRNAME[prop_name] if not prop_attr: raise ValueError("Improper property name: %s ", prop_name) attrs.append(prop_attr) filt = "(objectclass=*)" result = {} try: entry = self.conn.getEntry(agmnt_dn, ldap.SCOPE_BASE, filt, attrs) # Build the result from the returned attributes for attr in entry.getAttrs(): # given an attribute name retrieve the property name props = [ k for k, v in six.iteritems(RA_PROPNAME_TO_ATTRNAME) if v.lower() == attr.lower() ] # If this attribute is present in the RA properties, adds it to # result if len(props) > 0: result[props[0]] = entry.getValues(attr) except ldap.NO_SUCH_OBJECT: raise return result
def list(self, suffix=None, changelogdn=None): if not changelogdn: raise InvalidArgumentError("changelog DN is missing") base = changelogdn filtr = "(objectclass=extensibleobject)" # now do the effective search ents = self.conn.search_s(base, ldap.SCOPE_BASE, filtr) return ents
def setProperties(self, changelogdn=None, properties=None): """Set the properties of the changelog entry. :param changelogdn: DN of the changelog :type changelogdn: str :param properties: Dictionary of properties :type properties: dict :returns: None :raises: - ValueError - if invalid properties - ValueError - if changelog entry is not found - InvalidArgumentError - changelog DN is missing :supported properties are: CHANGELOG_NAME, CHANGELOG_DIR, CHANGELOG_MAXAGE, CHANGELOG_MAXENTRIES, CHANGELOG_TRIM_INTERVAL, CHANGELOG_COMPACT_INTV, CHANGELOG_CONCURRENT_WRITES, CHANGELOG_ENCRYPT_ALG, CHANGELOG_SYM_KEY """ if not changelogdn: raise InvalidArgumentError("changelog DN is missing") ents = self.conn.changelog.list(changelogdn=changelogdn) if len(ents) != 1: raise ValueError("Changelog entry not found: %s" % changelogdn) # check that the given properties are valid for prop in properties: # skip the prefix to add/del value if not inProperties(prop, CHANGELOG_PROPNAME_TO_ATTRNAME): raise ValueError("unknown property: %s" % prop) # build the MODS mods = [] for prop in properties: # take the operation type from the property name val = rawProperty(prop) if str(prop).startswith('+'): op = ldap.MOD_ADD elif str(prop).startswith('-'): op = ldap.MOD_DELETE else: op = ldap.MOD_REPLACE mods.append( (op, CHANGELOG_PROPNAME_TO_ATTRNAME[val], properties[prop])) # that is fine now to apply the MOD self.conn.modify_s(ents[0].dn, mods)
def delete(self, suffix=None, consumer_host=None, consumer_port=None, agmtdn=None): """Delete a replication agreement :param suffix: The suffix that the agreement is configured for :type suffix: str :param consumer_host: Host of the server that the agreement points to :type consumer_host: str :param consumer_port: Port of the server that the agreement points to :type consumer_port: int :param agmtdn: DN of the replica agreement :type agmtdn: str :returns: None :raises: - ldap.LDAPError - for ldap operation failures - TypeError - if too many agreements were found - NoSuchEntryError - if no agreements were found """ if not (suffix and consumer_host and consumer_port) and not agmtdn: raise InvalidArgumentError( "Suffix with consumer_host and consumer_port" + " or agmtdn are required") agmts = self.list(suffix, consumer_host, consumer_port, agmtdn) if agmts: if len(agmts) > 1: raise TypeError('Too many agreements were found') else: # Delete the agreement try: agmt_dn = agmts[0].dn self.conn.delete_s(agmt_dn) self.log.info('Agreement (%s) was successfully removed', agmt_dn) except ldap.LDAPError as e: self.log.error( 'Failed to delete agreement (%s), ' 'error: %s', agmt_dn, e) raise else: raise NoSuchEntryError('No agreements were found')
def create(self, suffix=None, host=None, port=None, properties=None): """Create (and return) a replication agreement from self to consumer. - self is the supplier, @param consumer: one of the following (consumer can be a master) * a DirSrv object if chaining * an object with attributes: host, port, sslport, __str__ @param suffix - eg. 'dc=babel,dc=it' @param properties - further properties dict. Support properties RA_NAME RA_SUFFIX RA_BINDDN RA_BINDPW RA_METHOD RA_DESCRIPTION RA_SCHEDULE RA_TRANSPORT_PROT RA_FRAC_EXCLUDE RA_FRAC_EXCLUDE_TOTAL_UPDATE RA_FRAC_STRIP RA_CONSUMER_PORT RA_CONSUMER_HOST RA_CONSUMER_TOTAL_INIT RA_TIMEOUT RA_CHANGES @return dn_agreement - DN of the created agreement @raise InvalidArgumentError - If the suffix is missing @raise NosuchEntryError - if a replica doesn't exist for that suffix @raise UNWILLING_TO_PERFORM if the database was previously in read-only state. To create new agreements you need to *restart* the directory server """ import string # Check we have a suffix [ mandatory ] if not suffix: self.log.warning("create: suffix is missing") raise InvalidArgumentError('suffix is mandatory') if properties: binddn = properties.get( RA_BINDDN) or defaultProperties[REPLICATION_BIND_DN] bindpw = properties.get( RA_BINDPW) or defaultProperties[REPLICATION_BIND_PW] bindmethod = properties.get( RA_METHOD) or defaultProperties[REPLICATION_BIND_METHOD] format = properties.get(RA_NAME) or r'meTo_$host:$port' description = properties.get(RA_DESCRIPTION) or format transport = properties.get( RA_TRANSPORT_PROT) or defaultProperties[REPLICATION_TRANSPORT] timeout = properties.get( RA_TIMEOUT) or defaultProperties[REPLICATION_TIMEOUT] else: binddn = defaultProperties[REPLICATION_BIND_DN] bindpw = defaultProperties[REPLICATION_BIND_PW] bindmethod = defaultProperties[REPLICATION_BIND_METHOD] format = r'meTo_$host:$port' description = format transport = defaultProperties[REPLICATION_TRANSPORT] timeout = defaultProperties[REPLICATION_TIMEOUT] # Compute the normalized suffix to be set in RA entry nsuffix = normalizeDN(suffix) # adding agreement under the replica entry replica_entries = self.conn.replica.list(suffix) if not replica_entries: raise NoSuchEntryError("Error: no replica set up for suffix " + suffix) replica = replica_entries[0] # define agreement entry cn = string.Template(format).substitute({'host': host, 'port': port}) dn_agreement = ','.join(["cn=%s" % cn, replica.dn]) # This is probably unnecessary because # we can just raise ALREADY_EXISTS try: entry = self.conn.getEntry(dn_agreement, ldap.SCOPE_BASE) self.log.warn("Agreement already exists: %r" % dn_agreement) return dn_agreement except ldap.NO_SUCH_OBJECT: entry = None # In a separate function in this scope? entry = Entry(dn_agreement) entry.update({ 'objectclass': ["top", RA_OBJECTCLASS_VALUE], RA_PROPNAME_TO_ATTRNAME[RA_NAME]: cn, RA_PROPNAME_TO_ATTRNAME[RA_SUFFIX]: nsuffix, RA_PROPNAME_TO_ATTRNAME[RA_CONSUMER_HOST]: host, RA_PROPNAME_TO_ATTRNAME[RA_CONSUMER_PORT]: str(port), RA_PROPNAME_TO_ATTRNAME[RA_TRANSPORT_PROT]: transport, RA_PROPNAME_TO_ATTRNAME[RA_TIMEOUT]: str(timeout), RA_PROPNAME_TO_ATTRNAME[RA_BINDDN]: binddn, RA_PROPNAME_TO_ATTRNAME[RA_BINDPW]: bindpw, RA_PROPNAME_TO_ATTRNAME[RA_METHOD]: bindmethod, RA_PROPNAME_TO_ATTRNAME[RA_DESCRIPTION]: string.Template(description).substitute({ 'host': host, 'port': port }) }) # we make a copy here because we cannot change # the passed in properties dict propertiescopy = {} if properties: import copy propertiescopy = copy.deepcopy(properties) # further arguments if 'winsync' in propertiescopy: # state it clearly! self.conn.setupWinSyncAgmt(propertiescopy, entry) try: self.log.debug("Adding replica agreement: [%r]" % entry) self.conn.add_s(entry) except: # FIXME check please! raise entry = self.conn.waitForEntry(dn_agreement) if entry: # More verbose but shows what's going on if 'chain' in propertiescopy: raise NotImplementedError chain_args = { 'suffix': suffix, 'binddn': binddn, 'bindpw': bindpw } # Work on `self` aka producer if replica.nsds5replicatype == MASTER_TYPE: self.conn.setupChainingFarm(**chain_args) # Work on `consumer` # TODO - is it really required? if replica.nsds5replicatype == LEAF_TYPE: chain_args.update({ 'isIntermediate': 0, 'urls': self.conn.toLDAPURL(), 'args': propertiescopy['chainargs'] }) consumer.setupConsumerChainOnUpdate(**chain_args) elif replica.nsds5replicatype == HUB_TYPE: chain_args.update({ 'isIntermediate': 1, 'urls': self.conn.toLDAPURL(), 'args': propertiescopy['chainargs'] }) consumer.setupConsumerChainOnUpdate(**chain_args) return dn_agreement
def create(self, suffix=None, host=None, port=None, properties=None, winsync=False): """Create (and return) a replication agreement from self to consumer. Self is the supplier. :param suffix: Replication Root :type suffix: str :param host: Consumer host :type host: str :param port: Consumer port :type port: int :param winsync: Identifies the agree as a WinSync agreement :type winsync: bool :param properties: Agreement properties :type properties: dict :returns: DN of the created agreement :raises: - InvalidArgumentError - If the suffix is missing - NoSuchEntryError - if a replica doesn't exist for that suffix - ldap.LDAPError - ldap error """ # Check we have a suffix [ mandatory ] if not suffix: self.log.warning("create: suffix is missing") raise InvalidArgumentError('suffix is mandatory') if not properties: properties = {} # Compute the normalized suffix to be set in RA entry properties[RA_SUFFIX] = normalizeDN(suffix) # Adding agreement under the replica entry replica_entries = self.conn.replica.list(suffix) if not replica_entries: raise NoSuchEntryError("Error: no replica set up for suffix: %s" % suffix) replica = replica_entries[0] # Define agreement entry if RA_NAME not in properties: properties[RA_NAME] = 'meTo_%s:%s' % (host, port) dn_agreement = ','.join(["cn=%s" % properties[RA_NAME], replica.dn]) # Set the required properties(if not already set) if RA_BINDDN not in properties: properties[RA_BINDDN] = defaultProperties[REPLICATION_BIND_DN] if RA_BINDPW not in properties: properties[RA_BINDPW] = defaultProperties[REPLICATION_BIND_PW] if RA_METHOD not in properties: properties[RA_METHOD] = defaultProperties[REPLICATION_BIND_METHOD] if RA_TRANSPORT_PROT not in properties: properties[RA_TRANSPORT_PROT] = \ defaultProperties[REPLICATION_TRANSPORT] if RA_TIMEOUT not in properties: properties[RA_TIMEOUT] = defaultProperties[REPLICATION_TIMEOUT] if RA_DESCRIPTION not in properties: properties[RA_DESCRIPTION] = properties[RA_NAME] if RA_CONSUMER_HOST not in properties: properties[RA_CONSUMER_HOST] = host if RA_CONSUMER_PORT not in properties: properties[RA_CONSUMER_PORT] = str(port) # This is probably unnecessary because # we can just raise ALREADY_EXISTS try: entry = self.conn.getEntry(dn_agreement, ldap.SCOPE_BASE) self.log.warn("Agreement already exists: %r" % dn_agreement) return dn_agreement except ldap.NO_SUCH_OBJECT: entry = None # Iterate over the properties, adding them to the entry entry = Entry(dn_agreement) entry.update({'objectclass': ["top", RA_OBJECTCLASS_VALUE]}) for prop in properties: entry.update({RA_PROPNAME_TO_ATTRNAME[prop]: properties[prop]}) # we make a copy here because we cannot change # the passed in properties dict propertiescopy = {} if properties: import copy propertiescopy = copy.deepcopy(properties) # Check if this a Winsync Agreement if winsync: self.conn.setupWinSyncAgmt(propertiescopy, entry) try: self.log.debug("Adding replica agreement: [%r]" % entry) self.conn.add_s(entry) except ldap.LDAPError as e: self.log.fatal('Failed to add replication agreement: %s' % str(e)) raise e entry = self.conn.waitForEntry(dn_agreement) if entry: # More verbose but shows what's going on if 'chain' in propertiescopy: raise NotImplementedError chain_args = { 'suffix': suffix, 'binddn': binddn, 'bindpw': bindpw } # Work on `self` aka producer if replica.nsds5replicatype == MASTER_TYPE: self.conn.setupChainingFarm(**chain_args) # Work on `consumer` # TODO - is it really required? if replica.nsds5replicatype == LEAF_TYPE: chain_args.update({ 'isIntermediate': 0, 'urls': self.conn.toLDAPURL(), 'args': propertiescopy['chainargs'] }) consumer.setupConsumerChainOnUpdate(**chain_args) elif replica.nsds5replicatype == HUB_TYPE: chain_args.update({ 'isIntermediate': 1, 'urls': self.conn.toLDAPURL(), 'args': propertiescopy['chainargs'] }) consumer.setupConsumerChainOnUpdate(**chain_args) return dn_agreement
def setProperties(self, suffix=None, agmnt_dn=None, agmnt_entry=None, properties=None): """Set the properties of the agreement entry. If an 'agmnt_entry' is provided, it updates the entry, else it updates the entry on the server. If the 'agmnt_dn' is provided it retrieves the entry using it, else it retrieve the agreement using the 'suffix'. :param suffix: Suffix stored in that agreement (online update) :type suffix: str :param agmnt_dn: DN of the agreement (online update) :type agmnt_dn: str :param agmnt_entry: Entry of a agreement (offline update) :type agmnt_entry: lib389.Entry :param properties: Dictionary of properties :type properties: dict :returns: None :raises: - ValueError - if invalid properties - ValueError - if invalid agreement_entry - ValueError - if agmnt_dn or suffix are not associated to a replica - InvalidArgumentError - missing mandatory argument :supported properties are: RA_NAME, RA_SUFFIX, RA_BINDDN, RA_BINDPW, RA_METHOD, RA_DESCRIPTION, RA_SCHEDULE, RA_TRANSPORT_PROT, RA_FRAC_EXCLUDE, RA_FRAC_EXCLUDE_TOTAL_UPDATE, RA_FRAC_STRIP, RA_CONSUMER_PORT, RA_CONSUMER_HOST, RA_CONSUMER_TOTAL_INIT, RA_TIMEOUT, RA_CHANGES """ # No properties provided if len(properties) == 0: return # check that the given properties are valid for prop in properties: # skip the prefix to add/del value if not inProperties(prop, RA_PROPNAME_TO_ATTRNAME): raise ValueError("unknown property: %s" % prop) else: self.log.debug("setProperties: %s:%s" % (prop, properties[prop])) # At least we need to have suffix/agmnt_dn/agmnt_entry if not suffix and not agmnt_dn and not agmnt_entry: raise InvalidArgumentError("suffix and agmnt_dn and agmnt_entry " + "are missing") # TODO if suffix: raise NotImplemented # The caller provides a set of properties to set into a replica entry if agmnt_entry: if not isinstance(agmnt_entry, Entry): raise ValueError("invalid instance of the agmnt_entry") # that is fine, now set the values for prop in properties: val = rawProperty(prop) # for Entry update it is a replace agmnt_entry.update( {RA_PROPNAME_TO_ATTRNAME[val]: properties[prop]}) return # for each provided property build the mod mod = [] for prop in properties: # retrieve/check the property name # and if the value needs to be added/deleted/replaced if prop.startswith('+'): mod_type = ldap.MOD_ADD prop_name = prop[1:] elif prop.startswith('-'): mod_type = ldap.MOD_DELETE prop_name = prop[1:] else: mod_type = ldap.MOD_REPLACE prop_name = prop attr = RA_PROPNAME_TO_ATTRNAME[prop_name] if not attr: raise ValueError("invalid property name %s" % prop_name) # Now do the value checking we can do if prop_name == RA_SCHEDULE: self._check_interval(properties[prop]) mod.append((mod_type, attr, ensure_bytes(properties[prop]))) # Now time to run the effective modify self.conn.modify_s(agmnt_dn, mod)
def create(self, suffix=None, properties=None): """ Creates backend entry and returns its dn. If the properties 'chain-bind-pwd' and 'chain-bind-dn' and 'chain-urls' are specified the backend is a chained backend. A chaining backend is created under 'cn=chaining database,cn=plugins,cn=config'. A local backend is created under 'cn=ldbm database,cn=plugins,cn=config' @param suffix - suffix stored in the backend @param properties - dictionary with properties values supported properties are BACKEND_NAME = 'name' BACKEND_READONLY = 'read-only' BACKEND_REQ_INDEX = 'require-index' BACKEND_CACHE_ENTRIES = 'entry-cache-number' BACKEND_CACHE_SIZE = 'entry-cache-size' BACKEND_DNCACHE_SIZE = 'dn-cache-size' BACKEND_DIRECTORY = 'directory' BACKEND_DB_DEADLOCK = 'db-deadlock' BACKEND_CHAIN_BIND_DN = 'chain-bind-dn' BACKEND_CHAIN_BIND_PW = 'chain-bind-pw' BACKEND_CHAIN_URLS = 'chain-urls' @return backend DN of the created backend @raise ValueError - If missing suffix InvalidArgumentError - If it already exists a backend for that suffix or a backend with the same DN """ def _getBackendName(parent): ''' Use to build a backend name that is not already used ''' index = 1 while True: bename = "local%ddb" % index base = "%s=%s,%s" % (BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_NAME], bename, parent) scope = ldap.SCOPE_BASE filt = "(objectclass=%s)" % BACKEND_OBJECTCLASS_VALUE self.log.debug("_getBackendName: baser=%s : fileter=%s" % (base, filt)) try: ents = self.conn.getEntry(base, ldap.SCOPE_BASE, filt) except (NoSuchEntryError, ldap.NO_SUCH_OBJECT) as e: self.log.info("backend name will be %s" % bename) return bename index += 1 # suffix is mandatory if not suffix: raise ValueError("suffix is mandatory") else: nsuffix = normalizeDN(suffix) # Check it does not already exist a backend for that suffix ents = self.conn.backend.list(suffix=suffix) if len(ents) != 0: raise InvalidArgumentError("It already exists backend(s) for %s: %s" % (suffix, ents[0].dn)) # Check if we are creating a local/chained backend chained_suffix = properties and (BACKEND_CHAIN_BIND_DN in properties) and (BACKEND_CHAIN_BIND_PW in properties) and (BACKEND_CHAIN_URLS in properties) if chained_suffix: self.log.info("Creating a chaining backend") dnbase = DN_CHAIN else: self.log.info("Creating a local backend") dnbase = DN_LDBM # Get the future backend name if properties and BACKEND_NAME in properties: cn = properties[BACKEND_NAME] else: cn = _getBackendName(dnbase) # Check the future backend name does not already exists # we can imagine having no backends for 'suffix' but having a backend # with the same name dn = "%s=%s,%s" % (BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_NAME], cn, dnbase) ents = self.conn.backend.list(backend_dn=dn) if ents: raise InvalidArgumentError("It already exists a backend with that DN: %s" % ents[0].dn) # All checks are done, Time to create the backend try: entry = Entry(dn) entry.update({ 'objectclass': ['top', 'extensibleObject', BACKEND_OBJECTCLASS_VALUE], BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_NAME]: cn, BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_SUFFIX]: nsuffix }) if chained_suffix: entry.update({ BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_CHAIN_URLS]: properties[BACKEND_CHAIN_URLS], BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_CHAIN_BIND_DN]: properties[BACKEND_CHAIN_BIND_DN], BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_CHAIN_BIND_PW]: properties[BACKEND_CHAIN_BIND_PW] }) self.log.debug("adding entry: %r" % entry) self.conn.add_s(entry) except ldap.ALREADY_EXISTS, e: self.log.error("Entry already exists: %r" % dn) raise ldap.ALREADY_EXISTS("%s : %r" % (e, dn))
def delete(self, suffix=None, backend_dn=None, bename=None): """ Deletes the backend entry with the following steps: Delete the indexes entries under this backend Delete the encrypted attributes entries under this backend Delete the encrypted attributes keys entries under this backend If a mapping tree entry uses this backend (nsslapd-backend), it raise UnwillingToPerformError If 'suffix'/'backend_dn'/'benamebase' are specified. It uses 'backend_dn' first, then 'suffix', then 'benamebase'. If neither 'suffix', 'backend_dn' and 'benamebase' are specified, it raise InvalidArgumentError @param suffix - suffix of the backend @param backend_dn - DN of the backend entry @param bename - 'commonname'/'cn' of the backend (e.g. 'userRoot') @return None @raise InvalidArgumentError - if missing arguments or invalid UnwillingToPerformError - if several backends match the argument provided suffix does not match backend suffix It exists a mapping tree that use that backend """ # First check the backend exists and retrieved its suffix be_ents = self.conn.backend.list(suffix=suffix, backend_dn=backend_dn, bename=bename) if len(be_ents) == 0: raise InvalidArgumentError("Unable to retrieve the backend (%r, %r, %r)" % (suffix, backend_dn, bename)) elif len(be_ents) > 1: for ent in be_ents: self.log.fatal("Multiple backend match the definition: %s" % ent.dn) if (not suffix) and (not backend_dn) and (not bename): raise InvalidArgumentError("suffix and backend DN and backend name are missing") raise UnwillingToPerformError("Not able to identify the backend to delete") else: be_ent = be_ents[0] be_suffix = be_ent.getValue(BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_SUFFIX]) # Verify the provided suffix is the one stored in the found backend if suffix: if normalizeDN(suffix) != normalizeDN(be_suffix): raise UnwillingToPerformError("provided suffix (%s) differs from backend suffix (%s)" % (suffix, be_suffix)) # now check there is no mapping tree for that suffix mt_ents = self.conn.mappingtree.list(suffix=be_suffix) if len(mt_ents) > 0: raise UnwillingToPerformError("It still exists a mapping tree (%s) for that backend (%s)" % (mt_ents[0].dn, be_ent.dn)) # Now delete the indexes found_bename = be_ent.getValue(BACKEND_PROPNAME_TO_ATTRNAME[BACKEND_NAME]) if not bename: bename = found_bename elif bename != found_bename: raise UnwillingToPerformError("Backend name specified (%s) differs from the retrieved one (%s)" % (bename, found_bename)) self.conn.index.delete_all(bename) # finally delete the backend children and the backend itself ents = self.conn.search_s(be_ent.dn, ldap.SCOPE_ONELEVEL) for ent in ents: self.log.debug("Delete entry children %s" % (ent.dn)) self.conn.delete_s(ent.dn) self.log.debug("Delete backend entry %s" % (be_ent.dn)) self.conn.delete_s(be_ent.dn) return
def list(self, suffix=None, consumer_host=None, consumer_port=None, agmtdn=None): """Returns the search result of the replica agreement(s) under the replica (replicaRoot is 'suffix'). Either 'suffix' or 'agmtdn' need to be specfied. 'consumer_host' and 'consumer_port' are either not specified or specified both. If 'agmtdn' is specified, it returns the search result entry of that replication agreement, else if consumer host/port are specified it returns the replica agreements toward that consumer host:port. Finally if neither 'agmtdn' nor 'consumser host/port' are specifies it returns all the replica agreements under the replica (replicaRoot is 'suffix'). :param suffix: The suffix targeted by the total update :type suffix: str :param consumer_host: Hostname of the consumer :type consumer_host: str :param consumer_port: Port of the consumer :type consumer_port: int :param agmtdn: DN of the replica agreement :type agmtdn: str :returns: Search result of the replica agreements :raises: - InvalidArgument - if missing mandatory argument (agmtdn or suffix, then host and port) - ValueError - if some properties are not valid - NoSuchEntryError - If no replica defined for the suffix """ if not suffix and not agmtdn: raise InvalidArgumentError("suffix or agmtdn are required") if (consumer_host and not consumer_port) or (not consumer_host and consumer_port): raise InvalidArgumentError( "consumer host/port are required together") if agmtdn: # easy case, just return the RA filt = "objectclass=*" return self.conn.search_s(agmtdn, ldap.SCOPE_BASE, filt) else: # Retrieve the replica replica_entries = self.conn.replica.list(suffix) if not replica_entries: raise NoSuchEntryError("Error: no replica set up for suffix " "(%s)" % suffix) replica_entry = replica_entries[0] # Now returns the replica agreement for that suffix that replicates # to consumer host/port if consumer_host and consumer_port: ''' Currently python does not like long continuous lines when it comes to string formatting, so we need to separate each line like this. ''' filt = "(&(|(objectclass=%s)" % RA_OBJECTCLASS_VALUE filt += "(objectclass=%s))" % RA_WINDOWS_OBJECTCLASS_VALUE filt += "(%s=%s)" % (RA_PROPNAME_TO_ATTRNAME[RA_CONSUMER_HOST], consumer_host) filt += "(%s=%d))" % ( RA_PROPNAME_TO_ATTRNAME[RA_CONSUMER_PORT], consumer_port) else: filt = ("(|(objectclass=%s)(objectclass=%s))" % (RA_OBJECTCLASS_VALUE, RA_WINDOWS_OBJECTCLASS_VALUE)) return self.conn.search_s(replica_entry.dn, ldap.SCOPE_ONELEVEL, filt)
def setProperties(self, suffix=None, replica_dn=None, replica_entry=None, properties=None): ''' Set the properties of the replica. If an 'replica_entry' (Entry) is provided, it updates the entry, else it updates the entry on the server. If the 'replica_dn' is provided it retrieves the entry using it, else it retrieve the replica using the 'suffix'. @param suffix : suffix stored in that replica (online update) @param replica_dn: DN of the replica (online update) @param replica_entry: Entry of a replica (offline update) @param properties: dictionary of properties Supported properties are: REPLICA_SUFFIX REPLICA_ID REPLICA_TYPE REPLICA_LEGACY_CONS REPLICA_BINDDN REPLICA_PURGE_INTERVAL REPLICA_PURGE_DELAY REPLICA_PRECISE_PURGING REPLICA_REFERRAL REPLICA_FLAGS @return None @raise ValueError: if unknown properties ValueError: if invalid replica_entry ValueError: if replica_dn or suffix are not associated to a replica InvalidArgumentError: If missing mandatory parameter ''' # No properties provided if len(properties) == 0: return # check that the given properties are valid for prop in properties: # skip the prefix to add/del value if not inProperties(prop, REPLICA_PROPNAME_TO_ATTRNAME): raise ValueError("unknown property: %s" % prop) else: self.log.debug("setProperties: %s:%s" % (prop, properties[prop])) # At least we need to have suffix/replica_dn/replica_entry if not suffix and not replica_dn and not replica_entry: raise InvalidArgumentError( "suffix and replica_dn and replica_entry are missing") # the caller provides a set of properties to set into a replica entry if replica_entry: if not isinstance(replica_entry, Entry): raise ValueError("invalid instance of the replica_entry") # that is fine, now set the values for prop in properties: val = rawProperty(prop) # for Entry update it is a replace replica_entry.update( {REPLICA_PROPNAME_TO_ATTRNAME[val]: properties[prop]}) return # If it provides the suffix or the replicaDN, replica.list will # return the appropriate entry ents = self.conn.replica.list(suffix=suffix, replica_dn=replica_dn) if len(ents) != 1: if replica_dn: raise ValueError("invalid replica DN: %s" % replica_dn) else: raise ValueError("invalid suffix: %s" % suffix) # build the MODS mods = [] for prop in properties: # take the operation type from the property name val = rawProperty(prop) if str(prop).startswith('+'): op = ldap.MOD_ADD elif str(prop).startswith('-'): op = ldap.MOD_DELETE else: op = ldap.MOD_REPLACE mods.append( (op, REPLICA_PROPNAME_TO_ATTRNAME[val], properties[prop])) # that is fine now to apply the MOD self.conn.modify_s(ents[0].dn, mods)
def init(self, suffix=None, consumer_host=None, consumer_port=None): """Trigger a total update of the consumer replica - self is the supplier, - consumer is a DirSrv object (consumer can be a master) - cn_format - use this string to format the agreement name :param suffix: The suffix targeted by the total update [mandatory] :type suffix: str :param consumer_host: Hostname of the consumer [mandatory] :type consumer_host: str :param consumer_port: Port of the consumer [mandatory] :type consumer_port: int :returns: None :raises: InvalidArgument - if missing mandatory argument """ # # check the required parameters are set # if not suffix: self.log.fatal("initAgreement: suffix is missing") raise InvalidArgumentError('suffix is mandatory argument') nsuffix = normalizeDN(suffix) if not consumer_host: self.log.fatal("initAgreement: host is missing") raise InvalidArgumentError('host is mandatory argument') if not consumer_port: self.log.fatal("initAgreement: port is missing") raise InvalidArgumentError('port is mandatory argument') # # check the replica agreement already exist # replica_entries = self.conn.replica.list(suffix) if not replica_entries: raise NoSuchEntryError("Error: no replica set up for suffix " + suffix) replica_entry = replica_entries[0] self.log.debug("initAgreement: looking for replica agreements " + "under %s" % replica_entry.dn) try: ''' Currently python does not like long continuous lines when it comes to string formatting, so we need to separate each line like this. ''' filt = "(&(objectclass=nsds5replicationagreement)" filt += "(nsds5replicahost=%s)" % consumer_host filt += "(nsds5replicaport=%d)" % consumer_port filt += "(nsds5replicaroot=%s))" % nsuffix entry = self.conn.getEntry(replica_entry.dn, ldap.SCOPE_ONELEVEL, filt) except ldap.NO_SUCH_OBJECT: msg = ('initAgreement: No replica agreement to ' + '{host}:{port} for suffix {suffix}'.format( host=consumer_host, port=consumer_port, suffix=nsuffix)) self.log.fatal(msg) raise # # trigger the total init # self.log.info("Starting total init %s" % entry.dn) mod = [(ldap.MOD_ADD, 'nsds5BeginReplicaRefresh', ensure_bytes('start'))] self.conn.modify_s(entry.dn, mod)
def create(self, suffix=None, role=None, rid=None, args=None): """ Create a replica entry on an existing suffix. @param suffix - dn of suffix @param role - REPLICAROLE_MASTER, REPLICAROLE_HUB or REPLICAROLE_CONSUMER @param rid - number that identify the supplier replica (role=REPLICAROLE_MASTER) in the topology. For hub/consumer (role=REPLICAROLE_HUB or REPLICAROLE_CONSUMER), rid value is not used. This parameter is mandatory for supplier. @param args - dictionnary of initial replica's properties Supported properties are: REPLICA_SUFFIX REPLICA_ID REPLICA_TYPE REPLICA_LEGACY_CONS ['off'] REPLICA_BINDDN [defaultProperties[REPLICATION_BIND_DN]] REPLICA_PURGE_INTERVAL REPLICA_PURGE_DELAY REPLICA_PRECISE_PURGING REPLICA_REFERRAL REPLICA_FLAGS @return replica DN @raise InvalidArgumentError - if missing mandatory arguments ValueError - argument with invalid value """ # Check validity of role if not role: self.log.fatal( "Replica.create: replica role not specify (REPLICAROLE_*)") raise InvalidArgumentError("role missing") if not Replica._valid_role(role): self.log.fatal("enableReplication: replica role invalid (%s) " % role) raise ValueError("invalid role: %s" % role) # check the validity of 'rid' if not Replica._valid_rid(role, rid=rid): self.log.fatal( "Replica.create: replica role is master but 'rid' is missing or invalid value" ) raise InvalidArgumentError("rid missing or invalid value") # check the validity of the suffix if not suffix: self.log.fatal("Replica.create: suffix is missing") raise InvalidArgumentError("suffix missing") else: nsuffix = normalizeDN(suffix) # role is fine, set the replica type if role == REPLICAROLE_MASTER: rtype = REPLICA_RDWR_TYPE else: rtype = REPLICA_RDONLY_TYPE # Set the properties provided as mandatory parameter # The attribute name is not prefixed '+'/'-' => ldap.MOD_REPLACE properties = { REPLICA_SUFFIX: nsuffix, REPLICA_ID: str(rid), REPLICA_TYPE: str(rtype) } # If the properties in args are valid # add them to the 'properties' dictionary # The attribute name may be prefixed '+'/'-' => keep MOD type as provided in args if args: for prop in args: if not inProperties(prop, REPLICA_PROPNAME_TO_ATTRNAME): raise ValueError("unknown property: %s" % prop) properties[prop] = args[prop] # Now set default values of unset properties Replica._set_or_default(REPLICA_LEGACY_CONS, properties, 'off') Replica._set_or_default(REPLICA_BINDDN, properties, [defaultProperties[REPLICATION_BIND_DN]]) if role != REPLICAROLE_CONSUMER: properties[REPLICA_FLAGS] = "1" # create replica entry in mapping-tree mtents = self.conn.mappingtree.list(suffix=nsuffix) mtent = mtents[0] dn_replica = ','.join((RDN_REPLICA, mtent.dn)) try: entry = self.conn.getEntry(dn_replica, ldap.SCOPE_BASE) self.log.warn("Already setup replica for suffix %r" % nsuffix) self.conn.suffixes.setdefault(nsuffix, {}) self.conn.replica.setProperties(replica_dn=dn_replica, properties=properties) return dn_replica except ldap.NO_SUCH_OBJECT: entry = None # # Now create the replica entry # entry = Entry(dn_replica) entry.setValues("objectclass", "top", REPLICA_OBJECTCLASS_VALUE, "extensibleobject") self.conn.replica.setProperties(replica_entry=entry, properties=properties) self.conn.add_s(entry) # check if the entry exists TODO better to raise! self.conn._test_entry(dn_replica, ldap.SCOPE_BASE) self.conn.suffixes[nsuffix] = {'dn': dn_replica, 'type': rtype} return dn_replica