#!/usr/share/ucs-test/runner python
## desc: Test the Samba4 GPC objects and links replication from DC-Slave to DC-Master OpenLDAP.
## bugs: [34214]
## roles: [domaincontroller_slave]
## packages: [univention-samba4, ucs-school-slave]
## tags: [apptest, ucsschool]
## exposure: dangerous

import ldap
from sys import exit
from re import search
from time import sleep

import univention.admin.uexceptions
from univention.admin.uldap import getMachineConnection

import ucsschool.lib.models
from essential.test_samba4 import TestSamba4

import univention.testing.utils as utils
from univention.testing.strings import random_username


class TestGPCReplicationOpenLDAP(TestSamba4):
	def main(self):
		"""
		Tests the Samba4 GPC objects (and GPO links) replication from
		DC-Slave to DC-Master OpenLDAP.
		"""
		try:
			self.get_ucr_test_credentials()
			(self.ldap, _pos) = getMachineConnection(ldap_master=True)

			# Create GPO and check replication against master:
			gpo_reference = self.create_gpo()
			self.check_gpo_replicated(gpo_reference)

			# Create GPO link to School OU and check replication against master:
			school_ou_dn = self.find_school_ou()
			self.create_gpo_link(school_ou_dn, gpo_reference)
			self.check_gpo_link_replicated(school_ou_dn, gpo_reference)
		finally:
			if self.gpo_reference:
				sleep(30)  # wait before deleting (for LDAP entries)
				self.delete_samba_gpo()

	def create_gpo(self):
		"""
		Using samba-tool creates a GPO.
		"""
		display_name = 'ucs_test_school_gpo_' + random_username(8)

		print(("\nCreating a Group Policy Object (GPO) with a display "
		       "name '%s' using 'samba-tool'") % display_name)

		stdout, stderr = self.samba_tool("gpo", "create", display_name)
		if stderr:
			print(("\nAn error message while creating a GPO using "
			       "'samba-tool' on the remote host '%s'. STDERR:\n%s")
			      % (self.remote_host, stderr))
		if not stdout:
			utils.fail(("The 'samba-tool' did not produce any output "
			            "to STDOUT, while a GPO reference was expected"))

		stdout = stdout.rstrip()
		print "\nSamba-tool produced the following output:", stdout

		try:
			# extracting the GPO reference from the stdout:
			return '{' + search('{(.+?)}', stdout).group(1) + '}'
		except AttributeError as exc:
			utils.fail(("Could not find the GPO reference in the STDOUT "
			            "'%s' of the 'samba-tool', error: '%s'") % (stdout, exc))

	def samba_tool(self, *args):
		"""
		Call `samba-tool` with the given args, while ensuring the proper
		authentication arguments are passed.
		"""
		auth_args = ("-k", "no", "--username", self.admin_username,
		             "--password", self.admin_password)
		cmd = ("samba-tool",) + args + auth_args
		return self.create_and_run_process(cmd)

	def check_gpo_replicated(self, gpo_reference):
		"""
		Check the Master DC OpenLDAP for a created GPO object.
		"""
		gpo_search = ldap.filter.filter_format("(cn=%s)", (gpo_reference,))
		for _ in range(5):
			try:
				result = self.ldap.search(filter=gpo_search, attr=["cn"], required=True)
			except univention.admin.uexceptions.noObject:
				print("GPO not yet replicated, sleeping..")
				sleep(5)
			else:
				break
		else:
			utils.fail("GPO %s not replicated to DC Master LDAP" % gpo_reference)
		print("Found the created GPO: %s" % result)

	def find_school_ou(self):
		schools = ucsschool.lib.models.School.get_all(self.ldap)
		if not schools:
			utils.fail("Could not find the a School-OU")
		return schools[0].dn

	def create_gpo_link(self, container_dn, gpo_reference):
		"""
		Creates a GPO link to a given 'container_dn' for 'gpo_reference'
		 using 'samba-tool'.
		"""
		print("\nLinking '%s' container and '%s' GPO using 'samba-tool'"
		       % (container_dn, gpo_reference))

		stdout, stderr = self.samba_tool("gpo", "setlink", container_dn, gpo_reference)
		if stderr:
			print(("\nAn error message while creating a GPO link using "
			       "'samba-tool'. STDERR:\n%s") % stderr)

		if not stdout:
			utils.fail(("The 'samba-tool' did not produce any output to "
			            "STDOUT, while a GPO link confirmation was expected"))
		if container_dn not in stdout:
			utils.fail(("The linked School OU (Container) was not referenced "
			            "in the 'samba-tool' output"))
		if self.gpo_reference not in stdout:
			utils.fail("The linked GPO was not referenced in the 'samba-tool' output")

		print "\nSamba-tool produced the following output:\n", stdout

	def check_gpo_link_replicated(self, container_dn, gpo_reference):
		"""
		Check the Master DC OpenLDAP for a created `msGPOLink` in the given
		`container_dn`.
		"""
		for _ in range(5):
			try:
				gp_link = self.ldap.getAttr(container_dn, "msGPOLink", required=True)
			except ldap.NO_SUCH_OBJECT:
				print("GPO Link not yet replicated, sleeping..")
				sleep(5)
			else:
				gpo_dns = self.parse_gp_link_into_dns(gp_link)
				if not any(gpo_reference in dn for dn in gpo_dns):
					print("GPO Link not yet replicated, sleeping..")
					sleep(10)
				else:
					break
		else:
			utils.fail("GPO link %s -> %s not replicated to DC Master LDAP."
			           % (container_dn, gpo_reference))
		print("Found msGPOLink attribute: %s" % gp_link)

	def parse_gp_link_into_dns(self, gp_link):
		"""
		Parse a given `gp_link` (`msGPOLink`) attribute into a list of
		referenced GPO-DNs.
		"""
		# There seems to be no proper way to do this
		# https://github.com/samba-team/samba/blob/87afc3aee1ea/python/samba/netcmd/gpo.py#L86-L97

		# The first and last chars must be `[` and `]`, so strip them
		links = (g.rsplit(";", 1) for g in gp_link[0][1:-1].split("]["))
		return [dn[len("LDAP://"):] for (dn, _options) in links]


if __name__ == '__main__':
	TestGPCReplication = TestGPCReplicationOpenLDAP()
	exit(TestGPCReplication.main())
