diff options
author | Karl MacMillan <kmacmill@redhat.com> | 2007-11-21 18:04:13 -0500 |
---|---|---|
committer | Karl MacMillan <kmacmill@redhat.com> | 2007-11-21 18:04:13 -0500 |
commit | 7ce4df70388ebfa34eed8ac05effb72593e06cd8 (patch) | |
tree | 65b49a796a9fb68a6ec815c34d75c0d12f7e2dbd | |
parent | c373ed5c5ccbee64c956a9a682a1427387498d8d (diff) | |
download | freeipa-7ce4df70388ebfa34eed8ac05effb72593e06cd8.tar.gz freeipa-7ce4df70388ebfa34eed8ac05effb72593e06cd8.tar.xz freeipa-7ce4df70388ebfa34eed8ac05effb72593e06cd8.zip |
Added replication.py
-rw-r--r-- | ipa-server/ipaserver/replication.py | 316 |
1 files changed, 316 insertions, 0 deletions
diff --git a/ipa-server/ipaserver/replication.py b/ipa-server/ipaserver/replication.py new file mode 100644 index 000000000..580ec27bf --- /dev/null +++ b/ipa-server/ipaserver/replication.py @@ -0,0 +1,316 @@ +# Authors: Karl MacMillan <kmacmillan@mentalrootkit.com> +# +# Copyright (C) 2007 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 or later +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +import time, logging + +import ipaldap, ldap, dsinstance +from ipa import ipaerror + +DIRMAN_CN = "cn=directory manager" +PORT = 389 +TIMEOUT = 120 + +class ReplicationManager: + """Manage replicatin agreements between DS servers""" + def __init__(self, hostname, dirman_passwd): + self.hostname = hostname + self.dirman_passwd = dirman_passwd + self.conn = ipaldap.IPAdmin(hostname) + self.conn.do_simple_bind(bindpw=dirman_passwd) + + self.repl_man_passwd = dirman_passwd + + # these are likely constant, but you could change them + # at runtime if you really want + self.repl_man_dn = "cn=replication manager,cn=config" + self.repl_man_cn = "replication manager" + self.suffix = "" + + def find_replication_dns(self, conn): + filt = "(objectlcass=nsds5ReplicationAgreement)" + try: + ents = conn.search_s("cn=mapping tree,cn-config", ldap.SCOPE_SUBTREE, filt, ["cn"]) + except ldap.NO_SUCH_OBJECT: + return [] + return [ent.dn for ent in ents] + + def add_replication_manager(self, conn, passwd=None): + """ + Create a pseudo user to use for replication. If no password + is provided the directory manager password will be used. + """ + + if passwd: + self.repl_man_passwd = passwd + + ent = ipaldap.Entry(self.repl_man_dn) + ent.setValues("objectclass", "top", "person") + ent.setValues("cn", self.repl_man_cn) + ent.setValues("userpassword", self.repl_man_passwd) + ent.setValues("sn", "replication manager pseudo user") + + try: + conn.add_s(ent) + except ldap.ALREADY_EXISTS: + # should we set the password here? + pass + + def delete_replication_manager(self, conn, dn="cn=replication manager,cn=config"): + try: + conn.delete_s(dn) + except ldap.NO_SUCH_OBJECT: + pass + + def get_replica_type(self, master): + if master: + return "3" + else: + return "2" + + def replica_dn(self): + return 'cn=replica, cn="%s", cn=mapping tree, cn=config' % self.suffix + + + def local_replica_config(self, conn, master, replica_id): + dn = self.replica_dn() + + try: + conn.getEntry(dn, ldap.SCOPE_BASE) + # replication is already configured + return + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): + pass + + replica_type = self.get_replica_type(master) + + entry = ipaldap.Entry(dn) + entry.setValues('objectclass', "top", "nsds5replica", "extensibleobject") + entry.setValues('cn', "replica") + entry.setValues('nsds5replicaroot', self.suffix) + entry.setValues('nsds5replicaid', str(replica_id)) + entry.setValues('nsds5replicatype', replica_type) + entry.setValues('nsds5flags', "1") + entry.setValues('nsds5replicabinddn', [self.repl_man_dn]) + entry.setValues('nsds5replicalegacyconsumer', "off") + + conn.add_s(entry) + + def setup_changelog(self, conn): + dn = "cn=changelog5, cn=config" + dirpath = conn.dbdir + "/cldb" + entry = ipaldap.Entry(dn) + entry.setValues('objectclass', "top", "extensibleobject") + entry.setValues('cn', "changelog5") + entry.setValues('nsslapd-changelogdir', dirpath) + try: + conn.add_s(entry) + except ldap.ALREADY_EXISTS: + return + + def setup_chaining_backend(self, conn): + chaindn = "cn=chaining database, cn=plugins, cn=config" + benamebase = "chaindb" + urls = [self.to_ldap_url(conn)] + cn = "" + benum = 1 + done = False + while not done: + try: + cn = benamebase + str(benum) # e.g. localdb1 + dn = "cn=" + cn + ", " + chaindn + entry = ipaldap.Entry(dn) + entry.setValues('objectclass', 'top', 'extensibleObject', 'nsBackendInstance') + entry.setValues('cn', cn) + entry.setValues('nsslapd-suffix', self.suffix) + entry.setValues('nsfarmserverurl', urls) + entry.setValues('nsmultiplexorbinddn', self.repl_man_dn) + entry.setValues('nsmultiplexorcredentials', self.repl_man_passwd) + + self.conn.add_s(entry) + done = True + except ldap.ALREADY_EXISTS: + benum += 1 + except ldap.LDAPError, e: + print "Could not add backend entry " + dn, e + raise + + return cn + + def to_ldap_url(self, conn): + return "ldap://%s:%d/" % (conn.host, conn.port) + + def setup_chaining_farm(self, conn): + try: + conn.modify_s(self.suffix, [(ldap.MOD_ADD, 'aci', + [ "(targetattr = \"*\")(version 3.0; acl \"Proxied authorization for database links\"; allow (proxy) userdn = \"ldap:///%s\";)" % self.repl_man_dn ])]) + except ldap.TYPE_OR_VALUE_EXISTS: + logging.debug("proxy aci already exists in suffix %s on %s" % (self.suffix, conn.host)) + + def get_mapping_tree_entry(self): + try: + entry = self.conn.getEntry("cn=mapping tree,cn=config", ldap.SCOPE_ONELEVEL, + "(cn=\"%s\")" % (self.suffix)) + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND), e: + logging.debug("failed to find mappting tree entry for %s" % self.suffix) + raise e + + return entry + + + def enable_chain_on_update(self, bename): + mtent = self.get_mapping_tree_entry() + dn = mtent.dn + + plgent = self.conn.getEntry("cn=Multimaster Replication Plugin,cn=plugins,cn=config", + ldap.SCOPE_BASE, "(objectclass=*)", ['nsslapd-pluginPath']) + path = plgent.getValue('nsslapd-pluginPath') + + mod = [(ldap.MOD_REPLACE, 'nsslapd-state', 'backend'), + (ldap.MOD_ADD, 'nsslapd-backend', bename), + (ldap.MOD_ADD, 'nsslapd-distribution-plugin', path), + (ldap.MOD_ADD, 'nsslapd-distribution-funct', 'repl_chain_on_update')] + + try: + self.conn.modify_s(dn, mod) + except ldap.TYPE_OR_VALUE_EXISTS: + logging.debug("chainOnUpdate already enabled for %s" % self.suffix) + + + def setup_chain_on_update(self, other_conn): + chainbe = self.setup_chaining_backend(other_conn) + self.enable_chain_on_update(chainbe) + + + def agreement_dn(self, conn): + cn = "meTo%s%d" % (conn.host, PORT) + dn = "cn=%s, %s" % (cn, self.replica_dn()) + + return (cn, dn) + + + def setup_agreement(self, a, b): + cn, dn = self.agreement_dn(b) + try: + a.getEntry(dn, ldap.SCOPE_BASE) + return + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): + pass + + entry = ipaldap.Entry(dn) + entry.setValues('objectclass', "top", "nsds5replicationagreement") + entry.setValues('cn', cn) + entry.setValues('nsds5replicahost', b.host) + entry.setValues('nsds5replicaport', str(PORT)) + entry.setValues('nsds5replicatimeout', str(TIMEOUT)) + entry.setValues('nsds5replicabinddn', self.repl_man_dn) + entry.setValues('nsds5replicacredentials', self.repl_man_passwd) + entry.setValues('nsds5replicabindmethod', 'simple') + entry.setValues('nsds5replicaroot', self.suffix) + entry.setValues('nsds5replicaupdateschedule', '0000-2359 0123456') + entry.setValues('description', "me to %s%d" % (b.host, PORT)) + + a.add_s(entry) + + entry = a.waitForEntry(entry) + + + def check_repl_init(self, conn, agmtdn): + done = False + hasError = 0 + attrlist = ['cn', 'nsds5BeginReplicaRefresh', 'nsds5replicaUpdateInProgress', + 'nsds5ReplicaLastInitStatus', 'nsds5ReplicaLastInitStart', + 'nsds5ReplicaLastInitEnd'] + entry = conn.getEntry(agmtdn, ldap.SCOPE_BASE, "(objectclass=*)", attrlist) + if not entry: + print "Error reading status from agreement", agmtdn + hasError = 1 + else: + refresh = entry.nsds5BeginReplicaRefresh + inprogress = entry.nsds5replicaUpdateInProgress + status = entry.nsds5ReplicaLastInitStatus + if not refresh: # done - check status + if not status: + print "No status yet" + elif status.find("replica busy") > -1: + print "Update failed - replica busy - status", status + done = True + hasError = 2 + elif status.find("Total update succeeded") > -1: + print "Update succeeded" + done = True + elif inprogress.lower() == 'true': + print "Update in progress yet not in progress" + else: + print "Update failed: status", status + hasError = 1 + done = True + else: + print "Update in progress" + + return done, hasError + + + def wait_for_repl_init(self, conn, agmtdn): + done = False + haserror = 0 + while not done and not haserror: + time.sleep(1) # give it a few seconds to get going + done, haserror = self.check_repl_init(conn, agmtdn) + return haserror + + def start_replication(self, other_conn): + print "starting replication" + cn, dn = self.agreement_dn(self.conn) + + mod = [(ldap.MOD_ADD, 'nsds5BeginReplicaRefresh', 'start')] + other_conn.modify_s(dn, mod) + + return self.wait_for_repl_init(other_conn, dn) + + + def basic_replication_setup(self, conn, master, replica_id): + self.add_replication_manager(conn) + self.local_replica_config(conn, master, replica_id) + if master: + self.setup_changelog(conn) + + def setup_replication(self, other_hostname, realm_name, master=True): + """ + NOTES: + - the directory manager password needs to be the same on + both directories. + """ + other_conn = ipaldap.IPAdmin(other_hostname) + other_conn.do_simple_bind(bindpw=self.dirman_passwd) + self.suffix = ipaldap.IPAdmin.normalizeDN(dsinstance.realm_to_suffix(realm_name)) + + self.basic_replication_setup(self.conn, master, 1) + self.basic_replication_setup(other_conn, True, 2) + + self.setup_agreement(other_conn, self.conn) + if master: + self.setup_agreement(self.conn, other_conn) + else: + self.setup_chaining_farm(other_conn) + self.setup_chain_on_update(other_conn) + + return self.start_replication(other_conn) + + + |