3 # Helper for determining USN ranges created of modified by provision and
5 # Copyright (C) Matthieu Patou <mat@matws.net> 2009-2011
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 sys
.path
.insert(0, "bin/python")
27 from samba
.credentials
import DONT_USE_KERBEROS
28 from samba
.auth
import system_session
32 import samba
.getopt
as options
33 from samba
import param
34 from samba
import _glue
35 from samba
.upgradehelpers
import get_paths
36 from samba
.ndr
import ndr_unpack
37 from samba
.dcerpc
import drsblobs
, misc
39 parser
= optparse
.OptionParser("provision [options]")
40 sambaopts
= options
.SambaOptions(parser
)
41 parser
.add_option_group(sambaopts
)
42 parser
.add_option_group(options
.VersionOptions(parser
))
43 parser
.add_option("--storedir", type="string", help="Directory where to store result files")
44 credopts
= options
.CredentialsOptions(parser
)
45 parser
.add_option_group(credopts
)
46 opts
= parser
.parse_args()[0]
47 lp
= sambaopts
.get_loadparm()
48 smbconf
= lp
.configfile
50 creds
= credopts
.get_credentials(lp
)
51 creds
.set_kerberos_state(DONT_USE_KERBEROS
)
52 session
= system_session()
53 paths
= get_paths(param
, smbconf
=smbconf
)
54 basedn
="DC=" + lp
.get("realm").replace(".",",DC=")
55 samdb
= Ldb(paths
.samdb
, session_info
=session
, credentials
=creds
,lp
=lp
)
61 res
= samdb
.search(base
="", scope
=ldb
.SCOPE_BASE
, attrs
=["dsServiceName"])
64 if res
and len(res
) == 1 and res
[0]["dsServiceName"] != None:
65 dn
= ldb
.Dn(samdb
, str(res
[0]["dsServiceName"]))
66 res
= samdb
.search(base
=str(dn
), scope
=ldb
.SCOPE_BASE
, attrs
=["invocationId"],
67 controls
=["search_options:1:2"])
69 if res
and len(res
) == 1 and res
[0]["invocationId"]:
70 invocation
= str(ndr_unpack(misc
.GUID
, res
[0]["invocationId"][0]))
72 print "Unable to find invocation ID"
75 print "Unable to find attribute dsServiceName in rootDSE"
78 res
= samdb
.search(base
=basedn
, expression
="objectClass=*",
79 scope
=ldb
.SCOPE_SUBTREE
,
80 attrs
=["replPropertyMetaData"],
81 controls
=["search_options:1:2"])
85 obj
= ndr_unpack(drsblobs
.replPropertyMetaDataBlob
,
86 str(e
["replPropertyMetaData"])).ctr
89 # like a timestamp but with the resolution of 1 minute
90 minutestamp
=_glue
.nttime2unix(o
.originating_change_time
)/60
91 hash_ts
= hash_id
.get(str(o
.originating_invocation_id
))
94 ob
["min"] = o
.originating_usn
95 ob
["max"] = o
.originating_usn
97 ob
["list"] = [str(e
.dn
)]
100 ob
= hash_ts
.get(minutestamp
)
103 ob
["min"] = o
.originating_usn
104 ob
["max"] = o
.originating_usn
106 ob
["list"] = [str(e
.dn
)]
108 if ob
["min"] > o
.originating_usn
:
109 ob
["min"] = o
.originating_usn
110 if ob
["max"] < o
.originating_usn
:
111 ob
["max"] = o
.originating_usn
112 if not (str(e
.dn
) in ob
["list"]):
113 ob
["num"] = ob
["num"] + 1
114 ob
["list"].append(str(e
.dn
))
115 hash_ts
[minutestamp
] = ob
116 hash_id
[str(o
.originating_invocation_id
)] = hash_ts
119 print "Here is a list of changes that modified more than %d objects in 1 minute." % minobj
120 print "Usually changes made by provision and upgradeprovision are those who affect a couple"\
121 " of hundred of objects or more"
122 print "Total number of objects: %d" % nb_obj
126 hash_ts
= hash_id
[id]
128 sorted_keys
.extend(hash_ts
.keys())
132 for k
in sorted_keys
:
134 if obj
["num"] > minobj
:
135 dt
= _glue
.nttime2string(_glue
.unix2nttime(k
*60))
136 print "%s # of modification: %d \tmin: %d max: %d" % (dt
, obj
["num"],
139 if hash_ts
[k
]["num"] > 600:
140 kept_record
.append(k
)
142 # Let's try to concatenate consecutive block if they are in the almost same minutestamp
143 for i
in range(0, len(kept_record
)):
145 key1
= kept_record
[i
]
146 key2
= kept_record
[i
-1]
148 # previous record is just 1 minute away from current
149 if int(hash_ts
[key1
]["min"]) == int(hash_ts
[key2
]["max"]) + 1:
150 # Copy the highest USN in the previous record
151 # and mark the current as skipped
152 hash_ts
[key2
]["max"] = hash_ts
[key1
]["max"]
153 hash_ts
[key1
]["skipped"] = True
155 for k
in kept_record
:
157 if obj
.get("skipped") == None:
158 ldif
= "%slastProvisionUSN: %d-%d;%s\n" % (ldif
, obj
["min"],
166 file = tempfile
.mktemp(dir=dest
, prefix
="usnprov", suffix
=".ldif")
168 print "To track the USNs modified/created by provision and upgrade proivsion,"
169 print " the following ranges are proposed to be added to your provision sam.ldb: \n%s" % ldif
170 print "We recommend to review them, and if it's correct to integrate the following ldif: %s in your sam.ldb" % file
171 print "You can load this file like this: ldbadd -H %s %s\n"%(str(paths
.samdb
),file)
172 ldif
= "dn: @PROVISION\nprovisionnerID: %s\n%s" % (invocation
, ldif
)
173 open(file,'w').write(ldif
)