uptodateness: extract function get_utdv_edges
[amitay/samba.git] / python / samba / uptodateness.py
1 # Uptodateness utils
2 #
3 # Copyright (C) Andrew Bartlett 2015, 2018
4 # Copyright (C) Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
5 # Copyright (C) Joe Guo <joeg@catalyst.net.nz>
6 #
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 from __future__ import print_function
20
21 import sys
22 import time
23
24 from ldb import SCOPE_BASE, LdbError
25
26 from samba import nttime2unix, dsdb
27 from samba.netcmd import CommandError
28 from samba.samdb import SamDB
29
30
31 def get_partition_maps(samdb):
32     """Generate dictionaries mapping short partition names to the
33     appropriate DNs."""
34     base_dn = samdb.domain_dn()
35     short_to_long = {
36         "DOMAIN": base_dn,
37         "CONFIGURATION": str(samdb.get_config_basedn()),
38         "SCHEMA": "CN=Schema,%s" % samdb.get_config_basedn(),
39         "DNSDOMAIN": "DC=DomainDnsZones,%s" % base_dn,
40         "DNSFOREST": "DC=ForestDnsZones,%s" % base_dn
41     }
42
43     long_to_short = {}
44     for s, l in short_to_long.items():
45         long_to_short[l] = s
46
47     return short_to_long, long_to_short
48
49
50 def get_partition(samdb, part):
51     # Allow people to say "--partition=DOMAIN" rather than
52     # "--partition=DC=blah,DC=..."
53     if part is not None:
54         short_partitions, long_partitions = get_partition_maps(samdb)
55         part = short_partitions.get(part.upper(), part)
56         if part not in long_partitions:
57             raise CommandError("unknown partition %s" % part)
58     return part
59
60
61 def get_utdv(samdb, dn):
62     """This finds the uptodateness vector in the database."""
63     cursors = []
64     config_dn = samdb.get_config_basedn()
65     for c in dsdb._dsdb_load_udv_v2(samdb, dn):
66         inv_id = str(c.source_dsa_invocation_id)
67         res = samdb.search(base=config_dn,
68                            expression=("(&(invocationId=%s)"
69                                        "(objectClass=nTDSDSA))" % inv_id),
70                            attrs=["distinguishedName", "invocationId"])
71         settings_dn = str(res[0]["distinguishedName"][0])
72         prefix, dsa_dn = settings_dn.split(',', 1)
73         if prefix != 'CN=NTDS Settings':
74             raise CommandError("Expected NTDS Settings DN, got %s" %
75                                settings_dn)
76
77         cursors.append((dsa_dn,
78                         inv_id,
79                         int(c.highest_usn),
80                         nttime2unix(c.last_sync_success)))
81     return cursors
82
83
84 def get_own_cursor(samdb):
85     res = samdb.search(base="",
86                        scope=SCOPE_BASE,
87                        attrs=["highestCommittedUSN"])
88     usn = int(res[0]["highestCommittedUSN"][0])
89     now = int(time.time())
90     return (usn, now)
91
92
93 def get_utdv_edges(local_kcc, dsas, part_dn, lp, creds):
94     # we talk to each remote and make a matrix of the vectors
95     # for each partition
96     # normalise by oldest
97     utdv_edges = {}
98     for dsa_dn in dsas:
99         res = local_kcc.samdb.search(dsa_dn,
100                                      scope=SCOPE_BASE,
101                                      attrs=["dNSHostName"])
102         ldap_url = "ldap://%s" % res[0]["dNSHostName"][0]
103         try:
104             samdb = SamDB(url=ldap_url, credentials=creds, lp=lp)
105             cursors = get_utdv(samdb, part_dn)
106             own_usn, own_time = get_own_cursor(samdb)
107             remotes = {dsa_dn: own_usn}
108             for dn, guid, usn, t in cursors:
109                 remotes[dn] = usn
110         except LdbError as e:
111             print("Could not contact %s (%s)" % (ldap_url, e),
112                   file=sys.stderr)
113             continue
114         utdv_edges[dsa_dn] = remotes
115     return utdv_edges