#!/usr/bin/python import argparse from contextlib import contextmanager from datetime import datetime import ldb import os from samba import Ldb from samba.auth import system_session from samba.param import LoadParm import shutil import subprocess import sys import tdb import time def install_python_lmdb(): from univention.config_registry import ConfigRegistry, handler_set ucr = ConfigRegistry() ucr.load() if not ucr.is_true("repository/online/unmaintained"): handler_set(["repository/online/unmaintained=yes"]) subprocess.call(("univention-install", "python-lmdb")) try: import lmdb except ImportError: install_python_lmdb() import lmdb # Default the mdb file size for the individual partitions to 8GB DEFAULT_BACKEND_SIZE = 8 * 1024**3 def migrate_sam_ldb_backends_from_tdb_to_mdb(ldb_tdb_backend_dir, ldb_mdb_backend_dir): print("PROCESS: Migrating sam.ldb.d backend files from TDB to MDB") ldb_tdb_backend_files = [fn for fn in os.listdir(ldb_tdb_backend_dir) if fn.endswith(".ldb")] for fn in ldb_tdb_backend_files: t = tdb.Tdb(os.path.join(ldb_tdb_backend_dir, fn)) mdb_env = lmdb.open(os.path.join(ldb_mdb_backend_dir, fn), map_size=DEFAULT_BACKEND_SIZE, subdir=False) os.chmod(mdb_env.path(), 0o600) with mdb_env.begin(write=True) as txn: for i in t: txn.put(i, t[i]) print("PROCESS: Migration of sam.ldb.d backend files done") @contextmanager def sam_ldb_backends_from_tdb_to_mdb(lp): print("PROCESS: Creating new sam.ldb.d directory for Migration") sam_backend_dir = lp.private_path("sam.ldb.d") sam_tdb_backend_dir = lp.private_path("sam.ldb.tdb.d") sam_ldb = lp.private_path("sam.ldb") sam_ldb_bak = lp.private_path("sam.ldb.tdb") try: os.rename(sam_backend_dir, sam_tdb_backend_dir) except OSError: print("ERROR: An exception occurred, aborting migration") raise os.mkdir(sam_backend_dir) os.chmod(sam_backend_dir, 0o700) shutil.copy(sam_ldb, sam_ldb_bak) os.chmod(sam_ldb_bak, 0o600) migrate_sam_ldb_backends_from_tdb_to_mdb(sam_tdb_backend_dir, sam_backend_dir) try: yield except Exception: print("ERROR: An exception occurred, reverting sam.ldb.d to original state") shutil.rmtree(sam_backend_dir) os.rename(sam_tdb_backend_dir, sam_backend_dir) os.unlink(sam_ldb) os.rename(sam_ldb_bak, sam_ldb) raise print("PROCESS: Moving metadata file to new sam.ldb.d directory") os.rename(os.path.join(sam_tdb_backend_dir, "metadata.tdb"), os.path.join(sam_backend_dir, "metadata.tdb")) def open_samdb_raw(lp): # Use options=["modules:"] to keep the attached LDB modules from loading samdb_path = lp.private_path("sam.ldb") return Ldb(url=samdb_path, session_info=system_session(), lp=lp, options=["modules:"]) def activate_mdb(lp): print("PROCESS: Switching sam.ldb from TDB to MDB") samdb = open_samdb_raw(lp) samdb.transaction_start() try: delta = ldb.Message() delta.dn = ldb.Dn(samdb, "@PARTITION") delta["backendStore"] = ldb.MessageElement("mdb", ldb.FLAG_MOD_REPLACE, "backendStore") samdb.modify(delta) delta = ldb.Message() delta.dn = ldb.Dn(samdb, "@SAMBA_DSDB") delta["requiredFeatures"] = ldb.MessageElement("lmdbLevelOne", ldb.FLAG_MOD_ADD, "requiredFeatures") samdb.modify(delta) except ldb.LdbError: samdb.transaction_cancel() print("PROCESS: Switching sam.ldb from TDB to MDB failed") raise else: samdb.transaction_commit() print("PROCESS: Switching sam.ldb from TDB to MDB successful") @contextmanager def stopped_samba_and_s4c(): print("PROCESS: Stopping Samba and S4-Connector") subprocess.call(("systemctl", "stop", "univention-s4-connector")) subprocess.call(("/etc/init.d/samba", "stop")) try: yield except Exception: raise print("PROCESS: Starting Samba and S4-Connector again") subprocess.call(("/etc/init.d/samba", "start")) time.sleep(5) subprocess.call(("systemctl", "start", "univention-s4-connector")) def sam_ldb_is_using_mdb(lp): samdb = open_samdb_raw(lp) res = samdb.search(base="@PARTITION", scope=ldb.SCOPE_BASE, attrs=["backendStore"]) return (res and "backendStore" in res[0] and str(res[0]["backendStore"]) == "mdb") if __name__ == "__main__": parser = argparse.ArgumentParser(description="Migrate sam.ldb from TDB to MDB") parser.add_argument("--skip-dbcheck", action='store_true') args = parser.parse_args() lp = LoadParm() if sam_ldb_is_using_mdb(lp): print("INFO: Nothing to do, sam.ldb is already using MDB") sys.exit(0) with stopped_samba_and_s4c(): if not args.skip_dbcheck: print("PROCESS: Running pre-migration check of sam.ldb database") if subprocess.call(("samba-tool", "dbcheck", "--cross-ncs")): sys.exit(1) t0 = datetime.now() with sam_ldb_backends_from_tdb_to_mdb(lp): activate_mdb(lp) duration = str(datetime.now() - t0).split('.')[:-1][0] print("INFO: Duration: %s" % duration) if not args.skip_dbcheck: print("PROCESS: Running post-migration check of sam.ldb database") if subprocess.call(("samba-tool", "dbcheck", "--cross-ncs")): sys.exit(1)