From 3a9a8069ef24cfcfc49db7e9aa747a4a6a4fd785 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Fri, 14 Jun 2019 11:16:43 -0400
Subject: [PATCH] Issue 7975 - Accept 389-ds JSON replication status messages

Description:  389-ds now stores a replication agreement status message in
              a JSON string in a new attribute:

                  replicaLastInitStatusJSON
                  replicaLastUpdateStatusJSON

              The original status attributes' values are not changing at
              this time, but there are plans to do so eventually as
              the old status format is confusing.

http://www.port389.org/docs/389ds/design/repl-agmt-status-design.html

https://pagure.io/freeipa/issue/7975

Reviewed by: ?
---
 install/ui/test/data/ipa_init_objects.json |  2 ++
 install/updates/20-aci.update              |  4 ++--
 ipaserver/install/replication.py           | 21 ++++++++++++++++++---
 3 files changed, 22 insertions(+), 5 deletions(-)

diff --git a/install/ui/test/data/ipa_init_objects.json b/install/ui/test/data/ipa_init_objects.json
index 6f9b13e92c..90514493a9 100644
--- a/install/ui/test/data/ipa_init_objects.json
+++ b/install/ui/test/data/ipa_init_objects.json
@@ -11160,9 +11160,11 @@
                "nsds5replicalastinitend",
                "nsds5replicalastinitstart",
                "nsds5replicalastinitstatus",
+               "nsds5replicalastinitstatusjson",
                "nsds5replicalastupdateend",
                "nsds5replicalastupdatestart",
                "nsds5replicalastupdatestatus",
+               "nsds5replicalastupdatestatusjson",
                "nsds5replicalegacyconsumer",
                "nsds5replicaname",
                "nsds5replicaport",
diff --git a/install/updates/20-aci.update b/install/updates/20-aci.update
index 7650cb4810..91d1a0c352 100644
--- a/install/updates/20-aci.update
+++ b/install/updates/20-aci.update
@@ -84,7 +84,7 @@ dn: cn=mapping tree,cn=config
 add: aci: (targetattr=*)(version 3.0;acl "permission:Add Replication Agreements";allow (add) groupdn = "ldap:///cn=Add Replication Agreements,cn=permissions,cn=pbac,$SUFFIX";)
 add: aci: (targetattr=*)(targetfilter="(|(objectclass=nsds5Replica)(objectclass=nsds5replicationagreement)(objectclass=nsDSWindowsReplicationAgreement)(objectClass=nsMappingTree))")(version 3.0; acl "permission:Modify Replication Agreements"; allow (read, write, search) groupdn = "ldap:///cn=Modify Replication Agreements,cn=permissions,cn=pbac,$SUFFIX";)
 add: aci: (targetattr=*)(targetfilter="(|(objectclass=nsds5replicationagreement)(objectclass=nsDSWindowsReplicationAgreement))")(version 3.0;acl "permission:Remove Replication Agreements";allow (delete) groupdn = "ldap:///cn=Remove Replication Agreements,cn=permissions,cn=pbac,$SUFFIX";)
-add: aci: (targetattr = "cn || createtimestamp || description || entryusn || modifytimestamp || nsds50ruv || nsds5beginreplicarefresh || nsds5debugreplicatimeout || nsds5flags || nsds5replicaabortcleanruv || nsds5replicaautoreferral || nsds5replicabackoffmax || nsds5replicabackoffmin || nsds5replicabinddn || nsds5replicabindmethod || nsds5replicabusywaittime || nsds5replicachangecount || nsds5replicachangessentsincestartup || nsds5replicacleanruv || nsds5replicacleanruvnotified || nsds5replicacredentials || nsds5replicaenabled || nsds5replicahost || nsds5replicaid || nsds5replicalastinitend || nsds5replicalastinitstart || nsds5replicalastinitstatus || nsds5replicalastupdateend || nsds5replicalastupdatestart || nsds5replicalastupdatestatus || nsds5replicalegacyconsumer || nsds5replicaname || nsds5replicaport || nsds5replicaprotocoltimeout || nsds5replicapurgedelay || nsds5replicareferral || nsds5replicaroot || nsds5replicasessionpausetime || nsds5replicastripattrs || nsds5replicatedattributelist || nsds5replicatedattributelisttotal || nsds5replicatimeout || nsds5replicatombstonepurgeinterval || nsds5replicatransportinfo || nsds5replicatype || nsds5replicaupdateinprogress || nsds5replicaupdateschedule || nsds5task || nsds7directoryreplicasubtree || nsds7dirsynccookie || nsds7newwingroupsyncenabled || nsds7newwinusersyncenabled || nsds7windowsdomain || nsds7windowsreplicasubtree || nsruvreplicalastmodified || nsstate || objectclass || onewaysync || winsyncdirectoryfilter || winsyncinterval || winsyncmoveaction || winsyncsubtreepair || winsyncwindowsfilter")(targetfilter = "(|(objectclass=nsds5Replica)(objectclass=nsds5replicationagreement)(objectclass=nsDSWindowsReplicationAgreement)(objectClass=nsMappingTree))")(version 3.0;acl "permission:Read Replication Agreements";allow (compare,read,search) groupdn = "ldap:///cn=Read Replication Agreements,cn=permissions,cn=pbac,$SUFFIX";)
+add: aci: (targetattr = "cn || createtimestamp || description || entryusn || modifytimestamp || nsds50ruv || nsds5beginreplicarefresh || nsds5debugreplicatimeout || nsds5flags || nsds5replicaabortcleanruv || nsds5replicaautoreferral || nsds5replicabackoffmax || nsds5replicabackoffmin || nsds5replicabinddn || nsds5replicabindmethod || nsds5replicabusywaittime || nsds5replicachangecount || nsds5replicachangessentsincestartup || nsds5replicacleanruv || nsds5replicacleanruvnotified || nsds5replicacredentials || nsds5replicaenabled || nsds5replicahost || nsds5replicaid || nsds5replicalastinitend || nsds5replicalastinitstart || nsds5replicalastinitstatus || nsds5replicalastinitstatusjson || nsds5replicalastupdateend || nsds5replicalastupdatestart || nsds5replicalastupdatestatus || nsds5replicalastupdatestatusjson || nsds5replicalegacyconsumer || nsds5replicaname || nsds5replicaport || nsds5replicaprotocoltimeout || nsds5replicapurgedelay || nsds5replicareferral || nsds5replicaroot || nsds5replicasessionpausetime || nsds5replicastripattrs || nsds5replicatedattributelist || nsds5replicatedattributelisttotal || nsds5replicatimeout || nsds5replicatombstonepurgeinterval || nsds5replicatransportinfo || nsds5replicatype || nsds5replicaupdateinprogress || nsds5replicaupdateschedule || nsds5task || nsds7directoryreplicasubtree || nsds7dirsynccookie || nsds7newwingroupsyncenabled || nsds7newwinusersyncenabled || nsds7windowsdomain || nsds7windowsreplicasubtree || nsruvreplicalastmodified || nsstate || objectclass || onewaysync || winsyncdirectoryfilter || winsyncinterval || winsyncmoveaction || winsyncsubtreepair || winsyncwindowsfilter")(targetfilter = "(|(objectclass=nsds5Replica)(objectclass=nsds5replicationagreement)(objectclass=nsDSWindowsReplicationAgreement)(objectClass=nsMappingTree))")(version 3.0;acl "permission:Read Replication Agreements";allow (compare,read,search) groupdn = "ldap:///cn=Read Replication Agreements,cn=permissions,cn=pbac,$SUFFIX";)
 
 dn: cn="$SUFFIX",cn=mapping tree,cn=config
 remove:aci: (targetattr=*)(version 3.0;acl "permission:Add Replication Agreements";allow (add) groupdn = "ldap:///cn=Add Replication Agreements,cn=permissions,cn=pbac,$SUFFIX";)
@@ -101,7 +101,7 @@ remove:aci: (targetattr=*)(targetfilter="(|(objectclass=nsds5replicationagreemen
 dn: cn=config
 remove:aci: (targetattr != aci)(version 3.0; aci "replica admins read access"; allow (read, search, compare) groupdn = "ldap:///cn=Modify Replication Agreements,cn=permissions,cn=pbac,$SUFFIX";)
 # ticket 5631: this ACI cannot be a managed ACI, because it is located in nonreplicated container
-remove:aci: (targetattr = "cn || createtimestamp || description || entryusn || modifytimestamp || nsds50ruv || nsds5beginreplicarefresh || nsds5debugreplicatimeout || nsds5flags || nsds5replicaabortcleanruv || nsds5replicaautoreferral || nsds5replicabackoffmax || nsds5replicabackoffmin || nsds5replicabinddn || nsds5replicabindmethod || nsds5replicabusywaittime || nsds5replicachangecount || nsds5replicachangessentsincestartup || nsds5replicacleanruv || nsds5replicacleanruvnotified || nsds5replicacredentials || nsds5replicaenabled || nsds5replicahost || nsds5replicaid || nsds5replicalastinitend || nsds5replicalastinitstart || nsds5replicalastinitstatus || nsds5replicalastupdateend || nsds5replicalastupdatestart || nsds5replicalastupdatestatus || nsds5replicalegacyconsumer || nsds5replicaname || nsds5replicaport || nsds5replicaprotocoltimeout || nsds5replicapurgedelay || nsds5replicareferral || nsds5replicaroot || nsds5replicasessionpausetime || nsds5replicastripattrs || nsds5replicatedattributelist || nsds5replicatedattributelisttotal || nsds5replicatimeout || nsds5replicatombstonepurgeinterval || nsds5replicatransportinfo || nsds5replicatype || nsds5replicaupdateinprogress || nsds5replicaupdateschedule || nsds5task || nsds7directoryreplicasubtree || nsds7dirsynccookie || nsds7newwingroupsyncenabled || nsds7newwinusersyncenabled || nsds7windowsdomain || nsds7windowsreplicasubtree || nsruvreplicalastmodified || nsstate || objectclass || onewaysync || winsyncdirectoryfilter || winsyncinterval || winsyncmoveaction || winsyncsubtreepair || winsyncwindowsfilter")(targetfilter = "(|(objectclass=nsds5Replica)(objectclass=nsds5replicationagreement)(objectclass=nsDSWindowsReplicationAgreement)(objectClass=nsMappingTree))")(version 3.0;acl "permission:System: Read Replication Agreements";allow (compare,read,search) groupdn = "ldap:///cn=System: Read Replication Agreements,cn=permissions,cn=pbac,$SUFFIX";)
+remove:aci: (targetattr = "cn || createtimestamp || description || entryusn || modifytimestamp || nsds50ruv || nsds5beginreplicarefresh || nsds5debugreplicatimeout || nsds5flags || nsds5replicaabortcleanruv || nsds5replicaautoreferral || nsds5replicabackoffmax || nsds5replicabackoffmin || nsds5replicabinddn || nsds5replicabindmethod || nsds5replicabusywaittime || nsds5replicachangecount || nsds5replicachangessentsincestartup || nsds5replicacleanruv || nsds5replicacleanruvnotified || nsds5replicacredentials || nsds5replicaenabled || nsds5replicahost || nsds5replicaid || nsds5replicalastinitend || nsds5replicalastinitstart || nsds5replicalastinitstatus || nsds5replicalastinitstatusjson || nsds5replicalastupdateend || nsds5replicalastupdatestart || nsds5replicalastupdatestatus || nsds5replicalastupdatestatusjson || nsds5replicalegacyconsumer || nsds5replicaname || nsds5replicaport || nsds5replicaprotocoltimeout || nsds5replicapurgedelay || nsds5replicareferral || nsds5replicaroot || nsds5replicasessionpausetime || nsds5replicastripattrs || nsds5replicatedattributelist || nsds5replicatedattributelisttotal || nsds5replicatimeout || nsds5replicatombstonepurgeinterval || nsds5replicatransportinfo || nsds5replicatype || nsds5replicaupdateinprogress || nsds5replicaupdateschedule || nsds5task || nsds7directoryreplicasubtree || nsds7dirsynccookie || nsds7newwingroupsyncenabled || nsds7newwinusersyncenabled || nsds7windowsdomain || nsds7windowsreplicasubtree || nsruvreplicalastmodified || nsstate || objectclass || onewaysync || winsyncdirectoryfilter || winsyncinterval || winsyncmoveaction || winsyncsubtreepair || winsyncwindowsfilter")(targetfilter = "(|(objectclass=nsds5Replica)(objectclass=nsds5replicationagreement)(objectclass=nsDSWindowsReplicationAgreement)(objectClass=nsMappingTree))")(version 3.0;acl "permission:System: Read Replication Agreements";allow (compare,read,search) groupdn = "ldap:///cn=System: Read Replication Agreements,cn=permissions,cn=pbac,$SUFFIX";)
 
 dn: $SUFFIX
 remove:aci: (targetattr = "*")(target = "ldap:///cn=*,cn=roles,cn=accounts,$SUFFIX")(version 3.0; acl "No anonymous access to roles"; deny (read,search,compare) userdn != "ldap:///all";)
diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py
index 77146b0b6d..a1c60f9a7f 100644
--- a/ipaserver/install/replication.py
+++ b/ipaserver/install/replication.py
@@ -1018,6 +1018,7 @@ def check_repl_init(self, conn, agmtdn, start):
         attrlist = ['cn', 'nsds5BeginReplicaRefresh',
                     'nsds5replicaUpdateInProgress',
                     'nsds5ReplicaLastInitStatus',
+                    'nsds5ReplicaLastInitStatusJSON',
                     'nsds5ReplicaLastInitStart',
                     'nsds5ReplicaLastInitEnd']
         entry = conn.get_entry(agmtdn, attrlist)
@@ -1028,7 +1029,12 @@ def check_repl_init(self, conn, agmtdn, start):
             refresh = entry.single_value.get('nsds5BeginReplicaRefresh')
             inprogress = entry.single_value.get('nsds5replicaUpdateInProgress')
             status = entry.single_value.get('nsds5ReplicaLastInitStatus')
+            json_status = entry.single_value.get('nsds5ReplicaLastInitStatusJSON')
             if not refresh: # done - check status
+                if json_status:
+                    # Just reset status with the JSON 'message'
+                    status = json_status['message']
+
                 if not status:
                     print("No status yet")
                 elif status.find("replica busy") > -1:
@@ -1060,8 +1066,8 @@ def check_repl_update(self, conn, agmtdn):
         hasError = 0
         error_message = ''
         attrlist = ['cn', 'nsds5replicaUpdateInProgress',
-                    'nsds5ReplicaLastUpdateStatus', 'nsds5ReplicaLastUpdateStart',
-                    'nsds5ReplicaLastUpdateEnd']
+                    'nsds5ReplicaLastUpdateStatus', 'nsds5ReplicaLastUpdateStatusjson',
+                    'nsds5ReplicaLastUpdateStart', 'nsds5ReplicaLastUpdateEnd']
         entry = conn.get_entry(agmtdn, attrlist)
         if not entry:
             print("Error reading status from agreement", agmtdn)
@@ -1069,6 +1075,7 @@ def check_repl_update(self, conn, agmtdn):
         else:
             inprogress = entry.single_value.get('nsds5replicaUpdateInProgress')
             status = entry.single_value.get('nsds5ReplicaLastUpdateStatus')
+            json_status = entry.single_value.get('nsds5ReplicaLastUpdateStatusjson')
             try:
                 # nsds5ReplicaLastUpdateStart is either a GMT time
                 # ending with Z or 0 (see 389-ds ticket 47836)
@@ -1094,7 +1101,15 @@ def check_repl_update(self, conn, agmtdn):
             logger.info("Replication Update in progress: %s: status: %s: "
                         "start: %d: end: %d",
                         inprogress, status, start, end)
-            if status: # always check for errors
+            if json_status:
+                # In 389-ds-base 1.4.1.4 we have the status message available
+                # to us in a json object
+                status_obj = json.loads(json_status)
+                if status_obj['state'] != 'green':
+                    hasError = 1
+                    error_message = status_obj['message']
+                    done = True
+            elif status: # always check for errors
                 # status will usually be a number followed by a string
                 # number != 0 means error
                 # Since 389-ds-base 1.3.5 it is 'Error (%d) %s'
