Replikace dat mezi 389 Directory Server a SUN Directory Server

Pro jednoho zákazníka řešíme přechod z adresářového serveru SUN Directory Server 6.3 (Solaris)  na 389 Directory Server na linuxu (RHEL). V průběhu přechodu je nutné zajistit replikaci dat z nových serverů 389 DS na staré servery SUN DS co jsou rozmístěné na pobočkách různě po republice.

I když vychází adresářové servery 389 DS a SUN DS ze stejného základu, z iPlanetího kódu, je mezi SUN DS a 389 DS nemožné replikovat data pomocí standardní replikace. Replikační protokol se liší.

Režim replikace, který potřebujeme, je pouze jednosměrný Master->Slave z 389 DS na SUN DS. Počet a četnost replikovaných změn není nějak zvlášť velká.

Požadavky na replikaci:

  • Přenesení změn v rozumném čase – v řádu sekund
  • Automatické navázání replikace po přerušení v místě kde replikace skončila

Replikaci realizujeme skriptem, který čte data z auditního logu 389 DS a zapisuje je přes LDAP spojení do SUN DS. Server SUN DS do kterého se zapisuje skriptem je vyhrazený jen pro tento účel a z něj se poté standardní cestou replikují data na zbývající pobočkové servery.

Uvedený skript možná není dokonalý, ale na dočasnou replikaci změn z 389DS do SUNDS po dobu než budou nahrazeny všechny servery je plně dostatečný. Případné použití je pouze na vaše vlastní nebezpečí :-)

#!/usr/bin/perl -Tw
BEGIN {
  our $pidname = "repl_pl.pid"; 
  our $piddir = "/var/tmp";
  our $pidsilent = 0;
}
package main;

use strict;
use warnings;

my $audit_file = "/var/log/dirsrv/slapd-ldap1/audit";
my $state_file = "/var/lib/dirsrv_repl/.repl_pl";

$ENV{PATH}='/sbin:/usr/sbin:/bin:/usr/bin';
$ENV{IFS}='';

use Fcntl qw/ SEEK_SET O_RDWR O_CREAT /;
use NDBM_File;
use Net::LDAP;
use Net::LDAP::LDIF;

my $sunds;

LDAPCONN: {
	while (1) {
		$sunds = Net::LDAP->new('127.0.0.1:1389', onerror => 'warn') and last LDAPCONN;
		print STDERR "Cekam na SUNDS ldap\n";
		sleep 10;
	}
}

my $result = $sunds->bind("cn=Directory Manager", password=>"PASSWORD");
if ( $result->code ) {
	LDAPerror ( "Binding", $result );
}

my %states;
tie(%states, 'NDBM_File', $state_file, O_CREAT | O_RDWR, 0660)
        or die("cannot tie state to $state_file : $!");

#print STDERR "Obsah stavoveho souboru:\n";
#while ((my $key, my $val) = each %states) {
#	print STDERR $key, ' = ', $val, "\n";
#}

while (1) {
        if (! -r $audit_file) {
                next;
        }
        my @file_stats = stat($audit_file);
        my $device = $file_stats[0];
        my $inode = $file_stats[1];
        my $size = $file_stats[7];
        my $state_key = $device . "/" .$inode;

	if (defined $states{"lastinode"} && $states{"lastinode"} != $inode) {
		# inode se lisi => nove vytvoreny soubor
		print STDERR "$audit_file je novy soubor.\n";
	}

        if (! open(FILE, $audit_file) ) {
                print STDERR "Nemohu otevrit soubor $audit_file : $!";
                next;
        }

	# posledni zpracovavany soubor
	$states{"lastinode"} = $inode;

        my $offset = $states{$state_key} || 0;
        if ($offset <= $size) {
                sysseek(FILE, $offset, SEEK_SET);
        } else {
                $offset = 0;
        }

        my $buffer;
	my $RAW_LDIF = undef;
        while ((my $read_count = sysread(FILE, $buffer, 4096)) > 0) {
                $offset += $read_count;
		$RAW_LDIF=$RAW_LDIF.$buffer;
        }
        close(FILE);

	if ($RAW_LDIF) {
                # tady je možné měnit načítaný LDIF z auditu
		$RAW_LDIF =~ s/^\t389-Directory.+\n\tldap.+\n\n//gm;
		$RAW_LDIF =~ s/^time: \d{14}\ndn:/dn:/gm;
		$RAW_LDIF =~ s/^dn: .+\nchangetype: modify\nreplace: passwordgraceusertime\npasswordGraceUserTime: \d+\n-\n\n//gim;
	}
	if ($RAW_LDIF && ($RAW_LDIF ne "")) {
		open(FH, '<', \$RAW_LDIF);
		my $ldif = Net::LDAP::LDIF->new( *FH{IO}, "r", onerror => 'die');
		my $ldifcnt = 1;
		while( not $ldif->eof() ) {
			my $entry = $ldif->read_entry();
			if ( $ldif->error() ) {
				print STDERR "Error msg: ", $ldif->error ( ), "\n";
				print STDERR "Error lines:\n", $ldif->error_lines ( ), "\n";
				die;
			} else {
				if (defined $states{"ldifcnt"} && $ldifcnt < $states{"ldifcnt"}) {
					# zaznam jsme uz zpracovali v drivejsim prubehu, takze ho muzeme preskocit
					next;
				}

				my $entrydn = $entry->dn();
				if ($entrydn =~ m/o=ORGANIZACE,c=cz$/) {
					$result = $entry->update($sunds);
					if ( $result->code ) {
						LDAPerror ( "Binding", $result );
						die;
					}
				}
				$states{"ldifcnt"} = $ldifcnt; # pocitadlo zpracovanych LDIF zaznamu
				$ldifcnt++;
			}
		}
		$states{"ldifcnt"} = 1; # reset pocitadla zaznamu
        	$states{$state_key} = $offset; # uspesne nactene zaznamy z dane pozice
	}

	sleep 1;
}

sub LDAPerror {
	my ($from, $mesg) = @_;
	print STDERR "Return code: ", $mesg->code;
	print STDERR "\tMessage: ", $mesg->error_name;
	print STDERR " :",          $mesg->error_text;
	print STDERR "MessageID: ", $mesg->mesg_id;
	print STDERR "\tDN: ", $mesg->dn;
}

# Osetreni zamku procesu aby se nespoustel vicekrat
BEGIN {
  use Proc::Pidfile;
  our $pid = $$; 
  our ($pidname, $piddir, $pidsilent);
  our $pidfile = Proc::Pidfile->new( pidfile => "$piddir/$pidname", silent=>$pidsilent );
}
END {
  our ($pidfile, %states);
  untie(%states);
  undef $pidfile;
}