python/uptodateness: cope with unknown invocation ID
[metze/samba/wip.git] / python / samba / uptodateness.py
index e5e0fc30a9b84fc2e9dc3e0fc41104bf4f243363..711407e56417153b0f7122b30a5f87fc60254cf1 100644 (file)
@@ -26,6 +26,21 @@ from ldb import SCOPE_BASE, LdbError
 from samba import nttime2unix, dsdb
 from samba.netcmd import CommandError
 from samba.samdb import SamDB
+from samba.kcc import KCC
+
+
+def get_kcc_and_dsas(url, lp, creds):
+    """Get a readonly KCC object and the list of DSAs it knows about."""
+    unix_now = int(time.time())
+    kcc = KCC(unix_now, readonly=True)
+    kcc.load_samdb(url, lp, creds)
+
+    dsa_list = kcc.list_dsas()
+    dsas = set(dsa_list)
+    if len(dsas) != len(dsa_list):
+        print("There seem to be duplicate dsas", file=sys.stderr)
+
+    return kcc, dsas
 
 
 def get_partition_maps(samdb):
@@ -68,8 +83,13 @@ def get_utdv(samdb, dn):
                            expression=("(&(invocationId=%s)"
                                        "(objectClass=nTDSDSA))" % inv_id),
                            attrs=["distinguishedName", "invocationId"])
-        settings_dn = str(res[0]["distinguishedName"][0])
-        prefix, dsa_dn = settings_dn.split(',', 1)
+        try:
+            settings_dn = str(res[0]["distinguishedName"][0])
+            prefix, dsa_dn = settings_dn.split(',', 1)
+        except IndexError as e:
+            print("Unknown invocation ID %s" % inv_id,
+                  file=sys.stderr)
+            continue
         if prefix != 'CN=NTDS Settings':
             raise CommandError("Expected NTDS Settings DN, got %s" %
                                settings_dn)
@@ -144,3 +164,41 @@ def get_utdv_max_distance(distances):
         for distance in vector.values():
             max_distance = max(max_distance, distance)
     return max_distance
+
+
+def get_utdv_summary(distances, filters=None):
+    maximum = failure = 0
+    median = 0.0  # could be average of 2 median values
+    values = []
+    # put all values into a list, exclude self to self ones
+    for dn_outer, vector in distances.items():
+        for dn_inner, distance in vector.items():
+            if dn_outer != dn_inner:
+                values.append(distance)
+
+    if values:
+        values.sort()
+        maximum = values[-1]
+        length = len(values)
+        if length % 2 == 0:
+            index = length//2 - 1
+            median = (values[index] + values[index+1])/2.0
+            median = round(median, 1)  # keep only 1 decimal digit like 2.5
+        else:
+            index = (length - 1)//2
+            median = values[index]
+            median = float(median)  # ensure median is always a float like 1.0
+        # if value not exist, that's a failure
+        expected_length = len(distances) * (len(distances) - 1)
+        failure = expected_length - length
+
+    summary = {
+        'maximum': maximum,
+        'median': median,
+        'failure': failure,
+    }
+
+    if filters:
+        return {key: summary[key] for key in filters}
+    else:
+        return summary