#!/usr/share/ucs-test/runner python3
## -*- coding: utf-8 -*-
## desc: test automatic reconnect of uldap.py
## tags: [skip_admember,reconnect]
## roles: [domaincontroller_master]
## exposure: dangerous
## packages:
##   - python-univention-directory-manager
##   - python-univention

from __future__ import print_function
import univention.testing.ucr as ucr_test
import univention.testing.utils as utils
import univention.testing.udm
import univention.testing.ucr
import univention.testing.strings as uts
import univention.uldap
import univention.admin.uldap
import univention.admin.uexceptions
from six.moves._thread import start_new_thread
import traceback
import time
import subprocess
from ldap.filter import filter_format
from ldap import INSUFFICIENT_ACCESS, UNAVAILABLE


def delayed_slapd_restart():
	global restart_finished
	subprocess.call(['systemctl', 'daemon-reload'])
	time.sleep(3)
	print('Restarting slapd')
	subprocess.call(['service', 'slapd', 'stop'])
	subprocess.call(['service', 'slapd', 'start'])
	time.sleep(5)
	print('Restarting slapd again')
	subprocess.call(['service', 'slapd', 'stop'])
	subprocess.call(['service', 'slapd', 'start'])
	time.sleep(3)
	print('Restart finished')
	restart_finished = True


restart_finished = False


def main():
	global restart_finished
	with ucr_test.UCSTestConfigRegistry() as ucr, univention.testing.udm.UCSTestUDM() as udm:
		account = utils.UCSTestDomainAdminCredentials()

		user_dn, username = udm.create_user()

		# test with univention.uldap and univention.admin.uldap connection objects
		for access in (univention.uldap.access, univention.admin.uldap.access):
			follow_referral = False  # TODO: implement a truish variant?
			# get connection
			lo = access(
				host=ucr['hostname'],
				base=ucr.get('ldap/base'),
				binddn=account.binddn,
				bindpw=account.bindpw,
				start_tls=2,
				follow_referral=follow_referral)

			if isinstance(lo, tuple):
				lo = lo[0]

			print('Starting test set with connection %r' % (lo,))

			print('Testing lo.search operation...')
			restart_finished = False
			start_new_thread(delayed_slapd_restart, ())
			filter_s = filter_format('(uid=%s)', (account.username,))
			count = 0
			try:
				while not restart_finished:
					try:
						res = lo.search(filter=filter_s)[0][0]
						assert res == account.binddn
						count += 1
					except UNAVAILABLE:
						# ignore ldap.UNAVAILABLE, slapd is started but database backend not yet available
						# python-ldap reconnect does not handle this situation
						print('ignore UNAVAILABLE')
						pass
					except univention.admin.uexceptions.ldapError as exc:
						if 'Server is unavailable'.lower() not in str(exc).lower():
							raise
						print('ignore univention.admin.uexceptions.ldapError Server is unavailable')
				print('LDAP search finished successfully {} times.'.format(count))
			except Exception as exc:
				print('LDAP modify failed after succeeding {} times.'.format(count))
				print(traceback.format_exc())
				utils.fail('lo.search() is not restart-safe with %r follow_referral=%r: %s' % (lo, follow_referral, exc,))

			# test lo.modify operation
			restart_finished = False
			start_new_thread(delayed_slapd_restart, ())
			count = 0
			try:
				old_description = b''
				while not restart_finished:
					try:
						new_description = 'Foo bar {0}'.format(uts.random_string(5)).encode('UTF-8')
						res = lo.modify(user_dn, [['description', old_description, new_description]])
						assert res == user_dn
						old_description = new_description
						count += 1
					except UNAVAILABLE:
						# ignore ldap.UNAVAILABLE, slapd is started but database backend not yet available
						# python-ldap reconnect does not handle this situation
						pass
					except univention.admin.uexceptions.ldapError as exc:
						if 'Server is unavailable'.lower() not in str(exc).lower():
							raise
						print('ignore univention.admin.uexceptions.ldapError Server is unavailable')
				print('LDAP modify finished successfully {} times.'.format(count))
			except (INSUFFICIENT_ACCESS, univention.admin.uexceptions.permissionDenied):
				pass  # On DC Slaves no objects can be modified
			except Exception as exc:
				print('LDAP modify failed after succeeding {} times.'.format(count))
				print(traceback.format_exc())
				utils.fail('lo.modify() is not restart-safe with %r follow_referral=%r: %s' % (lo, follow_referral, exc,))

			# TODO: add a check for rename() and add()


if __name__ == '__main__':
	main()
