def setup_mt(self, suffix, bename, parent=None): """Setup a suffix with the given backend-name. @param suffix @param bename @param parent - the parent suffix @param verbose - None This method does not create the matching entry in the tree, nor the given backend. Both should be created apart. Ex. setup_mt(suffix='o=addressbook1', bename='addressbook1') creates: - the mapping in "cn=mapping tree,cn=config" you have to create: - the backend - the ldap entry "o=addressbook1" *after* """ nsuffix = normalizeDN(suffix) #escapedn = escapeDNValue(nsuffix) if parent: nparent = normalizeDN(parent) else: nparent = "" filt = suffixfilt(suffix) # if suffix exists, return try: entry = self.conn.getEntry( DN_MAPPING_TREE, ldap.SCOPE_SUBTREE, filt) return entry except NoSuchEntryError: entry = None # fix me when we can actually used escaped DNs #dn = "cn=%s,cn=mapping tree,cn=config" % escapedn dn = ','.join(('cn="%s"' % nsuffix, DN_MAPPING_TREE)) entry = Entry(dn) entry.update({ 'objectclass': ['top', 'extensibleObject', 'nsMappingTree'], 'nsslapd-state': 'backend', # the value in the dn has to be DN escaped # internal code will add the quoted value - unquoted value is useful for searching 'cn': nsuffix, 'nsslapd-backend': bename }) #entry.setValues('cn', [escapedn, nsuffix]) # the value in the dn has to be DN escaped # the other value can be the unescaped value if parent: entry.setValues('nsslapd-parent-suffix', nparent) try: self.log.debug("Creating entry: %r" % entry) self.conn.add_s(entry) except ldap.LDAPError, e: raise ldap.LDAPError("Error adding suffix entry " + dn, e)
def ruv(self, suffix, tryrepl=False): """return a replica update vector for the given suffix. @param suffix - eg. 'o=netscapeRoot' @raises NoSuchEntryError if missing """ uuid = "ffffffff-ffffffff-ffffffff-ffffffff" filt = "(&(nsUniqueID=%s)(objectclass=nsTombstone))" % uuid attrs = ['nsds50ruv', 'nsruvReplicaLastModified'] ents = self.conn.search_s(suffix, ldap.SCOPE_SUBTREE, filt, attrs) ent = None if ents and (len(ents) > 0): ent = ents[0] elif tryrepl: self.log.warn("Could not get RUV from %r entry - trying cn=replica" % suffix) ensuffix = escapeDNValue(normalizeDN(suffix)) dn = ','.join(("cn=replica", "cn=%s" % ensuffix, DN_MAPPING_TREE)) ents = self.conn.search_s(dn, ldap.SCOPE_BASE, "objectclass=*", attrs) if ents and (len(ents) > 0): ent = ents[0] self.log.debug("RUV entry is %r" % ent) return RUV(ent) raise NoSuchEntryError("RUV not found: suffix: %r" % suffix)
def handle(self, dn, entry): """ Append single record to dictionary of all records. """ if not dn: dn = '' newentry = Entry((dn, entry)) self.dndict[normalizeDN(dn)] = newentry self.dnlist.append(newentry)
def _list_by_suffix(self, suffix, attrs=None): if suffix: nsuffix = normalizeDN(suffix) else: suffix = nsuffix = '*' entries = self.conn.search_s("cn=plugins,cn=config", ldap.SCOPE_SUBTREE, "(&(objectclass=nsBackendInstance)(|(nsslapd-suffix=%s)(nsslapd-suffix=%s)))" % (suffix, nsuffix), attrs) return entries
def add(self, suffix, binddn=None, bindpw=None, urls=None, attrvals=None, benamebase='localdb', setupmt=False, parent=None): """Setup a backend and return its dn. Blank on error XXX should RAISE! @param suffix @param benamebase - the backend common name @param setupmt - eventually setup Mapping Tree entry @param urls - a string of ldapurl - create a chaining backend @oaram binddn - chaining proxy user @param bindpw - chaining proxy password @param attrvals: a dict with further params like for ldbm { 'nsslapd-cachememsize': '1073741824', 'nsslapd-cachesize': '-1', } for chain { 'nsmaxresponsedelay': '60', 'nsslapd-sizelimit': '-1' } ex. benamebase="Isle0-0" urls=[ "ldaps://f0-ldap-vip.example.it:636/", "ldaps://ldap-18.example.it:636/", "ldaps://ldap-19.example.it:636/" ] NOTE: The suffix attribute is a mere string for the backend. the following action will work nicely: c.backend.add(suffix="foo=example,dc=com",benamebase="db1") TODO: rename benamebase with cn TODO: split CHAIN and LDBM ? eg. backend.add_chain """ attrvals = attrvals or {} dnbase = "" # figure out what type of be based on args if binddn and bindpw and urls: # its a chaining be dnbase = DN_CHAIN else: # its a ldbm be dnbase = DN_LDBM nsuffix = normalizeDN(suffix) try: cn = benamebase self.log.debug("create backend with cn: %s" % cn) dn = "cn=" + cn + "," + dnbase entry = Entry(dn) entry.update({ 'objectclass': ['top', 'extensibleObject', 'nsBackendInstance'], 'cn': cn, 'nsslapd-suffix': nsuffix }) if binddn and bindpw and urls: # its a chaining be entry.update({ 'nsfarmserverurl': urls, 'nsmultiplexorbinddn': binddn, 'nsmultiplexorcredentials': bindpw }) # set attrvals (but not cn, because it's in dn) # TODO do it in Entry if attrvals: entry.update(attrvals) 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 agreement_add(self, consumer, suffix=None, binddn=None, bindpw=None, cn_format=r'meTo_$host:$port', description_format=r'me to $host:$port', timeout=120, auto_init=False, bindmethod='simple', starttls=False, schedule=ALWAYS, args=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 DSAdmin object if chaining * an object with attributes: host, port, sslport, __str__ @param suffix - eg. 'dc=babel,dc=it' @param binddn - @param bindpw - @param cn_format - string.Template to format the agreement name @param timeout - replica timeout in seconds @param auto_init - start replication immediately @param bindmethod- 'simple' @param starttls - True or False @param schedule - when to schedule the replication. default: ALWAYS @param args - further args dict. Allowed keys: 'fractional', 'stripattrs', 'winsync' @raise NosuchEntryError - if a replica doesn't exist for that suffix @raise ALREADY_EXISTS @raise UNWILLING_TO_PERFORM if the database was previously in read-only state. To create new agreements you need to *restart* the directory server NOTE: this method doesn't cache connection entries TODO: test winsync TODO: test chain """ import string assert binddn and bindpw and suffix args = args or {} othhost, othport, othsslport = ( consumer.host, consumer.port, consumer.sslport) othport = othsslport or othport nsuffix = normalizeDN(suffix) # adding agreement to previously created replica replica_entries = self.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(cn_format).substitute({'host': othhost, 'port': othport}) 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 exists: %r" % dn_agreement) raise ldap.ALREADY_EXISTS except ldap.NO_SUCH_OBJECT: entry = None # In a separate function in this scope? entry = Entry(dn_agreement) entry.update({ 'objectclass': ["top", "nsds5replicationagreement"], 'cn': cn, 'nsds5replicahost': consumer.host, 'nsds5replicatimeout': str(timeout), 'nsds5replicabinddn': binddn, 'nsds5replicacredentials': bindpw, 'nsds5replicabindmethod': bindmethod, 'nsds5replicaroot': nsuffix, 'description': string.Template(description_format).substitute({'host': othhost, 'port': othport}) }) if schedule: if not re.match(r'\d{4}-\d{4} [0-6]{1,7}', schedule): # TODO put the regexp in a separate variable raise ValueError("Bad schedule format %r" % schedule) entry.update({'nsds5replicaupdateschedule': schedule}) if starttls: entry.setValues('nsds5replicatransportinfo', 'TLS') entry.setValues('nsds5replicaport', str(othport)) elif othsslport: entry.setValues('nsds5replicatransportinfo', 'SSL') entry.setValues('nsds5replicaport', str(othsslport)) else: entry.setValues('nsds5replicatransportinfo', 'LDAP') entry.setValues('nsds5replicaport', str(othport)) if auto_init: entry.setValues('nsds5BeginReplicaRefresh', 'start') # further arguments if 'fractional' in args: entry.setValues('nsDS5ReplicatedAttributeList', args['fractional']) if 'stripattrs' in args: entry.setValues('nsds5ReplicaStripAttrs', args['stripattrs']) if 'winsync' in args: # state it clearly! self.conn.setupWinSyncAgmt(args, entry) try: self.log.debug("Adding replica agreement: [%s]" % 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 args: chain_args = { 'suffix': suffix, 'binddn': binddn, 'bindpw': bindpw } # Work on `self` aka producer if replica.nsds5replicatype == MASTER_TYPE: self.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': args['chainargs'] }) consumer.setupConsumerChainOnUpdate(**chain_args) elif replica.nsds5replicatype == HUB_TYPE: chain_args.update({ 'isIntermediate': 1, 'urls': self.conn.toLDAPURL(), 'args': args['chainargs'] }) consumer.setupConsumerChainOnUpdate(**chain_args) return dn_agreement
def add(self, suffix, binddn, bindpw, rtype=MASTER_TYPE, rid=None, tombstone_purgedelay=None, purgedelay=None, referrals=None, legacy=False): """Setup a replica entry on an existing suffix. @param suffix - dn of suffix @param binddn - the replication bind dn for this replica can also be a list ["cn=r1,cn=config","cn=r2,cn=config"] @param bindpw - used to eventually provision the replication entry @param rtype - master, hub, leaf (see above for values) - default is master @param rid - replica id or - if not given - an internal sequence number will be assigned # further args @param legacy - true or false - for legacy consumer @param tombstone_purgedelay @param purgedelay - changelog expiration time in seconds @param referrals Ex. replica.add(**{ 'suffix': "dc=example,dc=com", 'type' : dsadmin.MASTER_TYPE, 'binddn': "cn=replication manager,cn=config" }) binddn TODO: this method does not update replica type """ # set default values if rtype == MASTER_TYPE: rtype = REPLICA_RDWR_TYPE else: rtype = REPLICA_RDONLY_TYPE if legacy: legacy = 'on' else: legacy = 'off' # create replica entry in mapping-tree nsuffix = normalizeDN(suffix) mtent = self.conn.getMTEntry(suffix) dn_replica = ','.join(("cn=replica", mtent.dn)) try: entry = self.conn.getEntry(dn_replica, ldap.SCOPE_BASE) self.log.warn("Already setup replica for suffix %r" % suffix) rec = self.conn.suffixes.setdefault(nsuffix, {}) rec.update({'dn': dn_replica, 'type': rtype}) return rec except ldap.NO_SUCH_OBJECT: entry = None # If a replica does not exist binddnlist = [] if hasattr(binddn, '__iter__'): binddnlist = binddn else: binddnlist.append(binddn) entry = Entry(dn_replica) entry.update({ 'objectclass': ("top", "nsds5replica", "extensibleobject"), 'cn': "replica", 'nsds5replicaroot': nsuffix, 'nsds5replicaid': str(rid), 'nsds5replicatype': str(rtype), 'nsds5replicalegacyconsumer': legacy, 'nsds5replicabinddn': binddnlist }) if rtype != LEAF_TYPE: entry.setValues('nsds5flags', "1") # other args if tombstone_purgedelay is not None: entry.setValues( 'nsds5replicatombstonepurgeinterval', str(tombstone_purgedelay)) if purgedelay is not None: entry.setValues('nsds5ReplicaPurgeDelay', str(purgedelay)) if referrals: entry.setValues('nsds5ReplicaReferral', referrals) 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': dn_replica, 'type': rtype}
def get(self, dn): ndn = normalizeDN(dn) return self.dndict.get(ndn, Entry(None))
def agreement_add(self, consumer, suffix=None, binddn=None, bindpw=None, cn_format=r'meTo_$host:$port', description_format=r'me to $host:$port', timeout=120, auto_init=False, bindmethod='simple', starttls=False, schedule=ALWAYS, args=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 DSAdmin object if chaining * an object with attributes: host, port, sslport, __str__ @param suffix - eg. 'dc=babel,dc=it' @param binddn - @param bindpw - @param cn_format - string.Template to format the agreement name @param timeout - replica timeout in seconds @param auto_init - start replication immediately @param bindmethod- 'simple' @param starttls - True or False @param schedule - when to schedule the replication. default: ALWAYS @param args - further args dict. Allowed keys: 'fractional', 'stripattrs', 'winsync' @raise NosuchEntryError - if a replica doesn't exist for that suffix @raise ALREADY_EXISTS @raise UNWILLING_TO_PERFORM if the database was previously in read-only state. To create new agreements you need to *restart* the directory server NOTE: this method doesn't cache connection entries TODO: test winsync TODO: test chain """ import string assert binddn and bindpw and suffix args = args or {} othhost, othport, othsslport = ( consumer.host, consumer.port, consumer.sslport) othport = othsslport or othport nsuffix = normalizeDN(suffix) # adding agreement to previously created replica replica_entries = self.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(cn_format).substitute({'host': othhost, 'port': othport}) 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 exists: %r" % dn_agreement) raise ldap.ALREADY_EXISTS except ldap.NO_SUCH_OBJECT: entry = None # In a separate function in this scope? entry = Entry(dn_agreement) entry.update({ 'objectclass': ["top", "nsds5replicationagreement"], 'cn': cn, 'nsds5replicahost': consumer.host, 'nsds5replicatimeout': str(timeout), 'nsds5replicabinddn': binddn, 'nsds5replicacredentials': bindpw, 'nsds5replicabindmethod': bindmethod, 'nsds5replicaroot': nsuffix, 'description': string.Template(description_format).substitute({'host': othhost, 'port': othport}) }) if schedule: if not re.match(r'\d{4}-\d{4} [0-6]{1,7}', schedule): # TODO put the regexp in a separate variable raise ValueError("Bad schedule format %r" % schedule) entry.update({'nsds5replicaupdateschedule': schedule}) if starttls: entry.setValues('nsds5replicatransportinfo', 'TLS') entry.setValues('nsds5replicaport', str(othport)) elif othsslport: entry.setValues('nsds5replicatransportinfo', 'SSL') entry.setValues('nsds5replicaport', str(othsslport)) else: entry.setValues('nsds5replicatransportinfo', 'LDAP') entry.setValues('nsds5replicaport', str(othport)) if auto_init: entry.setValues('nsds5BeginReplicaRefresh', 'start') # further arguments if 'fractional' in args: entry.setValues('nsDS5ReplicatedAttributeList', args['fractional']) # use the specified fractional total - if not specified, use the # specified fractional - if not specified, skip frac_total = args.get('fractional_total', args.get('fractional', None)) if frac_total: entry.setValues('nsDS5ReplicatedAttributeListTotal', frac_total) if 'stripattrs' in args: entry.setValues('nsds5ReplicaStripAttrs', args['stripattrs']) if 'winsync' in args: # state it clearly! self.conn.setupWinSyncAgmt(args, entry) try: self.log.debug("Adding replica agreement: [%s]" % 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 args: chain_args = { 'suffix': suffix, 'binddn': binddn, 'bindpw': bindpw } # Work on `self` aka producer if replica.nsds5replicatype == MASTER_TYPE: self.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': args['chainargs'] }) consumer.setupConsumerChainOnUpdate(**chain_args) elif replica.nsds5replicatype == HUB_TYPE: chain_args.update({ 'isIntermediate': 1, 'urls': self.conn.toLDAPURL(), 'args': args['chainargs'] }) consumer.setupConsumerChainOnUpdate(**chain_args) return dn_agreement