Fix some bugs when testing opensds ansible
[stor4nfv.git] / src / ceph / qa / workunits / mon / caps.py
1 #!/usr/bin/python
2
3 import json
4 import subprocess
5 import shlex
6 from StringIO import StringIO
7 import errno
8 import sys
9 import os
10 import io
11 import re
12
13
14 import rados
15 from ceph_argparse import *
16
17 keyring_base = '/tmp/cephtest-caps.keyring'
18
19 class UnexpectedReturn(Exception):
20   def __init__(self, cmd, ret, expected, msg):
21     if isinstance(cmd, list):
22       self.cmd = ' '.join(cmd)
23     else:
24       assert isinstance(cmd, str) or isinstance(cmd, unicode), \
25           'cmd needs to be either a list or a str'
26       self.cmd = cmd
27     self.cmd = str(self.cmd)
28     self.ret = int(ret)
29     self.expected = int(expected)
30     self.msg = str(msg)
31
32   def __str__(self):
33     return repr('{c}: expected return {e}, got {r} ({o})'.format(
34         c=self.cmd, e=self.expected, r=self.ret, o=self.msg))
35
36 def call(cmd):
37   if isinstance(cmd, list):
38     args = cmd
39   elif isinstance(cmd, str) or isinstance(cmd, unicode):
40     args = shlex.split(cmd)
41   else:
42     assert False, 'cmd is not a string/unicode nor a list!'
43
44   print 'call: {0}'.format(args)
45   proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
46   ret = proc.wait()
47
48   return (ret, proc)
49
50 def expect(cmd, expected_ret):
51
52   try:
53     (r, p) = call(cmd)
54   except ValueError as e:
55     print >> sys.stderr, \
56              'unable to run {c}: {err}'.format(c=repr(cmd), err=e.message)
57     return errno.EINVAL
58
59   assert r == p.returncode, \
60       'wth? r was supposed to match returncode!'
61
62   if r != expected_ret:
63     raise UnexpectedReturn(repr(cmd), r, expected_ret, str(p.stderr.read()))
64
65   return p
66
67 def expect_to_file(cmd, expected_ret, out_file, mode='a'):
68
69   # Let the exception be propagated to the caller
70   p = expect(cmd, expected_ret)
71   assert p.returncode == expected_ret, \
72       'expected result doesn\'t match and no exception was thrown!'
73
74   with io.open(out_file, mode) as file:
75     file.write(unicode(p.stdout.read()))
76
77   return p
78
79 class Command:
80   def __init__(self, cid, j):
81     self.cid = cid[3:]
82     self.perms = j['perm']
83     self.module = j['module']
84
85     self.sig = ''
86     self.args = []
87     for s in j['sig']:
88       if not isinstance(s, dict):
89         assert isinstance(s, str) or isinstance(s,unicode), \
90             'malformatted signature cid {0}: {1}\n{2}'.format(cid,s,j)
91         if len(self.sig) > 0:
92           self.sig += ' '
93         self.sig += s
94       else:
95         self.args.append(s)
96
97   def __str__(self):
98     return repr('command {0}: {1} (requires \'{2}\')'.format(self.cid,\
99           self.sig, self.perms))
100
101
102 def destroy_keyring(path):
103   if not os.path.exists(path):
104     raise Exception('oops! cannot remove inexistent keyring {0}'.format(path))
105
106   # grab all client entities from the keyring
107   entities = [m.group(1) for m in [re.match(r'\[client\.(.*)\]', l)
108                 for l in [str(line.strip())
109                   for line in io.open(path,'r')]] if m is not None]
110
111   # clean up and make sure each entity is gone
112   for e in entities:
113     expect('ceph auth del client.{0}'.format(e), 0)
114     expect('ceph auth get client.{0}'.format(e), errno.ENOENT)
115
116   # remove keyring
117   os.unlink(path)
118
119   return True
120
121 def test_basic_auth():
122   # make sure we can successfully add/del entities, change their caps
123   # and import/export keyrings.
124
125   expect('ceph auth add client.basicauth', 0)
126   expect('ceph auth caps client.basicauth mon \'allow *\'', 0)
127   # entity exists and caps do not match
128   expect('ceph auth add client.basicauth', errno.EINVAL)
129   # this command attempts to change an existing state and will fail
130   expect('ceph auth add client.basicauth mon \'allow w\'', errno.EINVAL)
131   expect('ceph auth get-or-create client.basicauth', 0)
132   expect('ceph auth get-key client.basicauth', 0)
133   expect('ceph auth get-or-create client.basicauth2', 0)
134   # cleanup
135   expect('ceph auth del client.basicauth', 0)
136   expect('ceph auth del client.basicauth2', 0)
137
138   return True
139
140 def gen_module_keyring(module):
141   module_caps = [
142       ('all', '{t} \'allow service {s} rwx\'', 0),
143       ('none', '', errno.EACCES),
144       ('wrong', '{t} \'allow service foobar rwx\'', errno.EACCES),
145       ('right', '{t} \'allow service {s} {p}\'', 0),
146       ('no-execute', '{t} \'allow service {s} x\'', errno.EACCES)
147       ]
148
149   keyring = '{0}.service-{1}'.format(keyring_base,module)
150   for perms in 'r rw x'.split():
151     for (n,p,r) in module_caps:
152       c = p.format(t='mon', s=module, p=perms)
153       expect_to_file(
154           'ceph auth get-or-create client.{cn}-{cp} {caps}'.format(
155           cn=n,cp=perms,caps=c), 0, keyring)
156
157   return keyring
158
159
160 def test_all():
161
162
163   perms = {
164       'good': {
165         'broad':[
166           ('rwx', 'allow *'),
167           ('r', 'allow r'),
168           ('rw', 'allow rw'),
169           ('x', 'allow x'),
170           ],
171         'service':[
172           ('rwx', 'allow service {s} rwx'),
173           ('r', 'allow service {s} r'),
174           ('rw', 'allow service {s} rw'),
175           ('x', 'allow service {s} x'),
176           ],
177         'command':[
178           ('rwx', 'allow command "{c}"'),
179           ],
180         'command-with':[
181           ('rwx', 'allow command "{c}" with {kv}')
182           ],
183         'command-with-prefix':[
184           ('rwx', 'allow command "{c}" with {key} prefix {val}')
185           ]
186         },
187       'bad': {
188         'broad':[
189           ('none', ''),
190           ],
191         'service':[
192           ('none1', 'allow service foo rwx'),
193           ('none2', 'allow service foo r'),
194           ('none3', 'allow service foo rw'),
195           ('none4', 'allow service foo x'),
196           ],
197         'command':[
198           ('none', 'allow command foo'),
199           ],
200         'command-with':[
201           ('none', 'allow command "{c}" with foo=bar'),
202           ],
203         'command-with-prefix':[
204           ('none', 'allow command "{c}" with foo prefix bar'),
205           ],
206         }
207       }
208
209   cmds = {
210       '':[
211         {
212           'cmd':('status', '', 'r')
213           },
214         {
215           'pre':'heap start_profiler',
216           'cmd':('heap', 'heapcmd=stats', 'rw'),
217           'post':'heap stop_profiler'
218           }
219         ],
220       'auth':[
221         {
222           'pre':'',
223           'cmd':('auth ls', '', 'r'),
224           'post':''
225           },
226         {
227           'pre':'auth get-or-create client.foo mon \'allow *\'',
228           'cmd':('auth caps', 'entity="client.foo"', 'rw'),
229           'post':'auth del client.foo'
230           }
231         ],
232       'pg':[
233         {
234           'cmd':('pg getmap', '', 'r'),
235           },
236         ],
237       'mds':[
238         {
239           'cmd':('mds getmap', '', 'r'),
240           },
241         {
242           'cmd':('mds cluster_down', '', 'rw'),
243           'post':'mds cluster_up'
244           },
245         ],
246       'mon':[
247         {
248           'cmd':('mon getmap', '', 'r')
249           },
250         {
251           'cmd':('mon remove', 'name=a', 'rw')
252           }
253         ],
254       'osd':[
255         {
256           'cmd':('osd getmap', '', 'r'),
257           },
258         {
259           'cmd':('osd pause', '', 'rw'),
260           'post':'osd unpause'
261           },
262         {
263           'cmd':('osd crush dump', '', 'r')
264           },
265         ],
266       'config-key':[
267           {
268             'pre':'config-key set foo bar',
269             'cmd':('config-key get', 'key=foo', 'r')
270             },
271           {
272             'pre':'config-key set foo bar',
273             'cmd':('config-key del', 'key=foo', 'rw')
274             }
275           ]
276       }
277
278   for (module,cmd_lst) in cmds.iteritems():
279     k = keyring_base + '.' + module
280     for cmd in cmd_lst:
281
282       (cmd_cmd, cmd_args, cmd_perm) = cmd['cmd']
283       cmd_args_key = ''
284       cmd_args_val = ''
285       if len(cmd_args) > 0:
286         (cmd_args_key, cmd_args_val) = cmd_args.split('=')
287
288       print 'generating keyring for {m}/{c}'.format(m=module,c=cmd_cmd)
289       # gen keyring
290       for (good_or_bad,kind_map) in perms.iteritems():
291         for (kind,lst) in kind_map.iteritems():
292           for (perm, cap) in lst:
293             cap_formatted = cap.format(
294                 s=module,
295                 c=cmd_cmd,
296                 kv=cmd_args,
297                 key=cmd_args_key,
298                 val=cmd_args_val)
299
300             if len(cap_formatted) == 0:
301               run_cap = ''
302             else:
303               run_cap = 'mon \'{fc}\''.format(fc=cap_formatted)
304
305             cname = 'client.{gb}-{kind}-{p}'.format(
306                 gb=good_or_bad,kind=kind,p=perm)
307             expect_to_file(
308                 'ceph auth get-or-create {n} {c}'.format(
309                   n=cname,c=run_cap), 0, k)
310       # keyring generated
311       print 'testing {m}/{c}'.format(m=module,c=cmd_cmd)
312
313       # test
314       for good_bad in perms.iterkeys():
315         for (kind,lst) in perms[good_bad].iteritems():
316           for (perm,_) in lst:
317             cname = 'client.{gb}-{k}-{p}'.format(gb=good_bad,k=kind,p=perm)
318
319           if good_bad == 'good':
320             expect_ret = 0
321           else:
322             expect_ret = errno.EACCES
323
324           if ( cmd_perm not in perm ):
325             expect_ret = errno.EACCES
326           if 'with' in kind and len(cmd_args) == 0:
327             expect_ret = errno.EACCES
328           if 'service' in kind and len(module) == 0:
329             expect_ret = errno.EACCES
330
331           if 'pre' in cmd and len(cmd['pre']) > 0:
332             expect('ceph {0}'.format(cmd['pre']), 0)
333           expect('ceph -n {cn} -k {k} {c} {arg_val}'.format(
334             cn=cname,k=k,c=cmd_cmd,arg_val=cmd_args_val), expect_ret)
335           if 'post' in cmd and len(cmd['post']) > 0:
336             expect('ceph {0}'.format(cmd['post']), 0)
337       # finish testing
338       destroy_keyring(k)
339
340
341   return True
342
343
344 def test_misc():
345
346   k = keyring_base + '.misc'
347   expect_to_file(
348       'ceph auth get-or-create client.caps mon \'allow command "auth caps"' \
349           ' with entity="client.caps"\'', 0, k)
350   expect('ceph -n client.caps -k {kf} mon_status'.format(kf=k), errno.EACCES)
351   expect('ceph -n client.caps -k {kf} auth caps client.caps mon \'allow *\''.format(kf=k), 0)
352   expect('ceph -n client.caps -k {kf} mon_status'.format(kf=k), 0)
353   destroy_keyring(k)
354
355 def main():
356
357   test_basic_auth()
358   test_all()
359   test_misc()
360
361   print 'OK'
362
363   return 0
364
365 if __name__ == '__main__':
366   main()