-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathswift-report.py
More file actions
executable file
·282 lines (240 loc) · 8.74 KB
/
swift-report.py
File metadata and controls
executable file
·282 lines (240 loc) · 8.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
#!/usr/bin/python
##
## Master for Swift consistency checking
##
TAG="swift-report"
# The master host where this is run must have an ssh access to
# storage nodes as defined in the ring. Sometimes it's the proxy,
# sometimes a master admin node and a bastion. Just make sure IPs
# are routable and ~/.ssh/authorized_keys is set.
# XXX This currently requires pre-positioning of swift-report-collect.py
# on the storage nodes in ${collect_path}. No attempt is made
# to upload swift-report-collect.py through ssh. It is all pretty
# badly hardcoded and incomplete.
#import os
import subprocess
import sys
from StringIO import StringIO
from iniparse import ConfigParser
from ConfigParser import NoSectionError, NoOptionError
from keystoneclient.v2_0 import client as keystone_client
from swift.common.ring import Ring
from swift.common.utils import hash_path
class LocalError(Exception):
pass
class JointAccount(object):
def __init__(self, accstr):
self.accstr = accstr
self.in_swift = False
self.in_keystone = False
self.name = None # only valid when in_keystone
# config()
class ConfigError(Exception):
pass
def config(cfgname, inisect):
cfg = { }
cfgpr = ConfigParser()
try:
cfgfp = open(cfgname)
except IOError, e:
# This one contains the cfgname
raise ConfigError(str(e))
cfgpr.readfp(cfgfp)
try:
# Using traditional keystonerc names
cfg["tenant"] = cfgpr.get(inisect, "os_tenant_name")
cfg["user"] = cfgpr.get(inisect, "os_username")
cfg["pass"] = cfgpr.get(inisect, "os_password")
cfg["authurl"] = cfgpr.get(inisect, "os_auth_url")
#
cfg["collpath"] = cfgpr.get(inisect, "collect_path")
except NoSectionError, e:
raise ConfigError(cfgname+": "+str(e))
except NoOptionError, e:
raise ConfigError(cfgname+": "+str(e))
return cfg
# Param
class ParamError(Exception):
pass
class Param:
def __init__(self, argv):
skip = 1;
self.cfgname = None
self.ringfn = None
self.verbose = False
for i in range(len(argv)):
if skip:
skip = 0
continue
arg = argv[i]
if len(arg) != 0 and arg[0] == '-':
if arg == "-c":
if i+1 == len(argv):
raise ParamError("Parameter -c needs an argument")
self.cfgname = argv[i+1];
skip = 1;
elif arg == "-v":
self.verbose = True
else:
raise ParamError("Unknown parameter " + arg)
else:
# raise ParamError("Parameters start with dashes")
self.ringfn = arg
if self.cfgname == None:
raise ParamError("Mandatory parameter -c is missing")
if self.ringfn == None:
raise ParamError("Mandatory parameter account.ring.gz is missing")
# convenience
self.cfg = None
# XXX These are not "hosts" but "devices", so we may re-visit the same host.
# Returns a list of tuples [(ip,part)].
def get_stor_hosts(r):
ret = []
for dev in r.devs:
# discard dev['port'] - not needed for ssh
ret.append((dev['ip'],dev['device']))
return ret
# Run ssh to every known device's host and collect accounts;
# Update accset in-place.
def fetch_swift_accounts(accset, par, stordevs):
ssh_opts="-o ConnectTimeout=1 -o StrictHostKeyChecking=no"
# XXX Silly to have just path and explicit python, make it 755 or something
ssh_cmd="python %s" % par.cfg["collpath"]
for dev in stordevs:
ssh_remote = "%s %s" % (ssh_cmd, dev[1])
pargs = "ssh %s %s %s" % (ssh_opts, dev[0], ssh_remote)
# XXX go to shell=False later, needs tokenizing arguments
p = subprocess.Popen(pargs, stdout=subprocess.PIPE, shell=True)
# The errors actually go to terminal, so no need to capture them here.
# We capture all of the out pipe because we do not know how to plug
# into Popen.communicate(). Then, we make a file-like object from
# the pipe contents string and use readline() on it. Yee-haaw.
out = p.communicate()[0]
sfp = StringIO(out)
while 1:
line = sfp.readline()
if len(line)==0:
break
accstr = line.rstrip("\r\n")
# Check basic syntax, just in case
if len(accstr.split('/')) != 3:
continue
a = accset.get(accstr, JointAccount(accstr))
a.in_swift = True
accset[accstr] = a
# XXX Check exit code
excode = p.wait()
def find_storage_url(par, keystone):
# Ultimately, we want for each user on ulist to find the corresponding
# Storage URL. Swift client does it by supplying credentials for
# authentication to Keystone server, which then substitutes into
# the endpoint pattern and returns the result as a side effect.
# We cannot do that, because we do not have a password, and Keystone
# does not have a way to issue such 3-rd party lookup. Therefore,
# we find the Swift endpoint outselves, and substitute, essentially
# replicating what Keystone server does.
slist = keystone.services.list()
swift_svc = None
for svc in slist:
if svc.type == 'object-store':
swift_svc = svc
break
if not swift_svc:
raise LocalError("No 'object-store' service")
if par.verbose:
print "SwiftID", swift_svc.id
elist = keystone.endpoints.list()
swift_ep = None
for ep in elist:
if ep.service_id == swift_svc.id:
swift_ep = ep
break
# print ep.service_id, ep.publicurl
if not swift_ep:
raise LocalError("No endpoint for service id %s" % swift_svc.id)
if par.verbose:
print "SwiftURL", swift_ep.publicurl
return swift_ep.publicurl
def interpolate(swift_url, user_id):
v = swift_url.replace('$(', '%(')
d = {'tenant_id': user_id}
return v % d
# Take the StorageURL and identify the account.
# Swift server does it as a part of generic URL parsing.
# Our function only works for URL without container (and/or object, of course).
def url_to_swift_account(storage_url):
return storage_url.split('/')[-1]
# Update accset in-place with Keystone accounts.
def fetch_keystone_accounts(accset, par, r):
cfg = par.cfg
keystone = keystone_client.Client(username=cfg["user"],
password=cfg["pass"], tenant_name=cfg["tenant"],
auth_url=cfg["authurl"])
# keystone.authenticate()
# XXX relocate to main() because sys.exit()
try:
swift_url = find_storage_url(par, keystone)
except LocalError, e:
print >>sys.stderr, TAG+":", str(e)
sys.exit(1)
ulist = keystone.tenants.list()
for user in ulist:
# print user.id, "enabled" if user.enabled else "disabled", user.name
account = url_to_swift_account(interpolate(swift_url, user.id))
hash_str = hash_path(account)
part, nodes = r.get_nodes(account)
accstr = "%s/%s/%s" % (part, hash_str[-3:], hash_str)
a = accset.get(accstr, JointAccount(accstr))
a.in_keystone = True
a.name = user.name
accset[accstr] = a
# 1. find list of hosts from Ring
# 2. log to them, collect Swift accounts
# 3. compare with known accounts (from Keystone, e.g.)
def main():
try:
par = Param(sys.argv)
except ParamError, e:
print >>sys.stderr, TAG+": Error in arguments:", e
print >>sys.stderr,\
"Usage:", TAG+" [-v] -c "+TAG+".conf"+" account.ring.gz"
sys.exit(1)
try:
par.cfg = config(par.cfgname, "main")
except ConfigError, e:
print >>sys.stderr, TAG+":", e
sys.exit(1)
# This used to be a set, but we want attributes attached to its members.
accset = dict()
try:
r = Ring(par.ringfn)
except IOError, e:
# ENOENT most likely
print >>sys.stderr, TAG+":", e
sys.exit(1)
if par.verbose:
print "Loading"
stordevs = get_stor_hosts(r)
if par.verbose:
print "Scanning hosts"
fetch_swift_accounts(accset, par, stordevs)
if par.verbose:
print "Poking Keystone"
fetch_keystone_accounts(accset, par, r)
lmax = 0
for key in accset:
l = len(key)
if l > lmax: lmax = l
for key in accset:
a = accset[key]
keystr = " "*(lmax-len(key)) + key
bitstr = ""
bitstr += ("S" if a.in_swift else "-")
bitstr += ("K" if a.in_keystone else "-")
if a.name: # same as a.in_keystone in this case
namestr = a.name
else:
namestr = "-"
print keystr, bitstr, namestr
if __name__ == "__main__":
main()