Merge lp:~nealpzhang/juju-ci-tools/assess_multi_users into lp:juju-ci-tools
- assess_multi_users
- Merge into trunk
Status: | Merged |
---|---|
Merge reported by: | Leo Zhang |
Merged at revision: | not available |
Proposed branch: | lp:~nealpzhang/juju-ci-tools/assess_multi_users |
Merge into: | lp:juju-ci-tools |
Diff against target: |
958 lines (+529/-198) 7 files modified
assess_container_networking.py (+1/-1) assess_unregister.py (+1/-2) assess_user_grant_revoke.py (+264/-91) jujupy.py (+61/-2) tests/test_assess_unregister.py (+7/-6) tests/test_assess_user_grant_revoke.py (+85/-93) tests/test_jujupy.py (+110/-3) |
To merge this branch: | bzr merge lp:~nealpzhang/juju-ci-tools/assess_multi_users |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Nicholas Skaggs (community) | Approve | ||
Aaron Bentley (community) | Approve | ||
Review via email: mp+299463@code.launchpad.net |
Commit message
Added multi-users' tests
Description of the change
Added tests for multi-users.
Nicholas Skaggs (nskaggs) wrote : | # |
Added a few more comments.
Aaron Bentley (abentley) wrote : | # |
This is improved, but there are still some issues, as noted below.
Nicholas Skaggs (nskaggs) wrote : | # |
Again some more comments.
Nicholas Skaggs (nskaggs) wrote : | # |
More comments. A minor quibble, but I would like to see your assess_* functions be named assert_*. The only assess_* function should be the primary test function in following with project naming.
Also, try to format your code in paragraphs. Feel free to use spacing to logically group statements together. Currently the compressed nature of the code makes it harder to read.
Finally, it would be nice to better frame your assert functions so conditionals are not required. Instead clear expectations of what a user should and should not be able to do (based purely upon there permission levels), which you should pass in. You should run the asserts for all users -- even those who you expect to not be able to perform an action.
Leo Zhang (nealpzhang) wrote : | # |
Aaron, Nicholas:
New updates after review.
Thanks
Aaron Bentley (abentley) wrote : | # |
In general, it is confusing to provide "assertion" as a variable name. Assertions are statements, not boolean variables. I think "has_permission" would work as a variable name everywhere you've used "assertion".
Please be clear about whether you're testing model or controller permissions. assert_read is really assert_read_model, assert_write is really assert_write_model, and assert_admin is really assert_
Please use narrow try/except blocks. Don't include anything in the try block unless you expect it to raise the exception. So for example:
code = ''.join(
try:
except subprocess.
You don't expect random.choice or xrange to raise CalledProcessError, so don't put them in the try block. If they do raise CalledProcessError, something very strange is happening and your test should fail with a traceback.
There's some wiggle room. It would be annoying to have separate try/except blocks for each child.expect() in case it raised pexpect.TIMEOUT, so putting a series of expect/sendline calls together in a single try/except block is fine. But I don't think child.isalive can raise pexpect.TIMEOUT, so it should go outside the try/except block.
It would be nice to check stderr to make sure that it is really a permissions error.
Leo Zhang (nealpzhang) wrote : | # |
Aaron/Nicholas,
New updates after review.
Thanks
Aaron Bentley (abentley) wrote : | # |
Some style changes to fix, but otherwise, I think this is landable.
Nicholas Skaggs (nskaggs) wrote : | # |
A couple final comments. Please address them, but you have my approval. I don't need to re-review before merging. Thanks for working on this!
I'll note remove user is landing, and this test should be updated to support it as well (but not i this MP).
Preview Diff
1 | === modified file 'assess_container_networking.py' |
2 | --- assess_container_networking.py 2016-06-13 20:06:40 +0000 |
3 | +++ assess_container_networking.py 2016-07-25 20:33:36 +0000 |
4 | @@ -299,7 +299,7 @@ |
5 | d = re.search(r'^default\s+via\s+([\d\.]+)\s+', routes, re.MULTILINE) |
6 | if d: |
7 | rc = client.juju('ssh', ('--proxy', target, |
8 | - 'ping -c1 -q ' + d.group(1)), check=False) |
9 | + 'ping -c1 -q ' + d.group(1)), check=False) |
10 | if rc != 0: |
11 | raise ValueError('%s unable to ping default route' % target) |
12 | else: |
13 | |
14 | === modified file 'assess_unregister.py' |
15 | --- assess_unregister.py 2016-06-21 05:59:02 +0000 |
16 | +++ assess_unregister.py 2016-07-25 20:33:36 +0000 |
17 | @@ -25,7 +25,6 @@ |
18 | |
19 | from assess_user_grant_revoke import ( |
20 | User, |
21 | - register_user, |
22 | ) |
23 | from deploy_stack import ( |
24 | BootstrapManager, |
25 | @@ -48,7 +47,7 @@ |
26 | user = User('testuser', 'read', []) |
27 | user_controller_name = '{}_controller'.format(user.name) |
28 | with temp_dir() as fake_home: |
29 | - user_client = register_user(user, client, fake_home) |
30 | + user_client = client.register_user(user, fake_home) |
31 | assert_controller_list( |
32 | user_client, |
33 | [user_controller_name]) |
34 | |
35 | === modified file 'assess_user_grant_revoke.py' |
36 | --- assess_user_grant_revoke.py 2016-07-06 17:55:43 +0000 |
37 | +++ assess_user_grant_revoke.py 2016-07-25 20:33:36 +0000 |
38 | @@ -11,7 +11,11 @@ |
39 | |
40 | import argparse |
41 | from collections import namedtuple |
42 | +import copy |
43 | +import json |
44 | import logging |
45 | +import random |
46 | +import string |
47 | import subprocess |
48 | import sys |
49 | |
50 | @@ -20,12 +24,11 @@ |
51 | from deploy_stack import ( |
52 | BootstrapManager, |
53 | ) |
54 | - |
55 | -from jujupy import Controller |
56 | from utility import ( |
57 | add_basic_testing_arguments, |
58 | configure_logging, |
59 | temp_dir, |
60 | + wait_for_removed_services, |
61 | ) |
62 | |
63 | __metaclass__ = type |
64 | @@ -36,109 +39,279 @@ |
65 | User = namedtuple('User', ['name', 'permissions', 'expect']) |
66 | |
67 | |
68 | +user_list_admin = [{"user-name": "admin", "display-name": "admin"}] |
69 | +user_list_admin_read = copy.deepcopy(user_list_admin) |
70 | +# Created user has no display name, bug 1606354 |
71 | +user_list_admin_read.append({"user-name": "readuser", "display-name": ""}) |
72 | +user_list_admin_read_write = copy.deepcopy(user_list_admin_read) |
73 | +# bug 1606354 |
74 | +user_list_admin_read_write.append({"user-name": "writeuser", |
75 | + "display-name": ""}) |
76 | +user_list_all = copy.deepcopy(user_list_admin_read_write) |
77 | +# bug 1606354 |
78 | +user_list_all.append({"user-name": "adminuser", "display-name": ""}) |
79 | +share_list_admin = {"admin@local": {"display-name": "admin", |
80 | + "access": "admin"}} |
81 | +share_list_admin_read = copy.deepcopy(share_list_admin) |
82 | +share_list_admin_read["readuser@local"] = {"access": "read"} |
83 | +share_list_admin_read_write = copy.deepcopy(share_list_admin_read) |
84 | +share_list_admin_read_write["writeuser@local"] = {"access": "write"} |
85 | +del share_list_admin_read_write['readuser@local'] |
86 | +share_list_all = copy.deepcopy(share_list_admin_read_write) |
87 | +share_list_all["adminuser@local"] = {"access": "admin"} |
88 | +share_list_all["writeuser@local"]["access"] = "read" |
89 | + |
90 | + |
91 | # This needs refactored out to utility |
92 | class JujuAssertionError(AssertionError): |
93 | """Exception for juju assertion failures.""" |
94 | |
95 | |
96 | -def register_user(user, client, fake_home): |
97 | - """Register `user` for the `client` return the cloned client used.""" |
98 | - # needs support to passing register command with arguments |
99 | - # refactor once supported, bug 1573099 |
100 | +def list_users(client): |
101 | + """Test listing all the users""" |
102 | + users_list = json.loads(client.get_juju_output('list-users', '--format', |
103 | + 'json', include_e=False)) |
104 | + for user in users_list: |
105 | + # Pop date-created and last-connection if existed for comparison |
106 | + user.pop("date-created", None) |
107 | + user.pop("last-connection", None) |
108 | + return users_list |
109 | + |
110 | + |
111 | +def list_shares(client): |
112 | + """Test listing users' shares""" |
113 | + share_list = json.loads(client.get_juju_output('list-shares', '--format', |
114 | + 'json', include_e=False)) |
115 | + for key, value in share_list.iteritems(): |
116 | + # Pop last-connection if existed for comparison |
117 | + value.pop("last-connection", None) |
118 | + return share_list |
119 | + |
120 | + |
121 | +def show_user(client): |
122 | + """Test showing a user's status""" |
123 | + user_status = json.loads(client.get_juju_output('show-user', '--format', |
124 | + 'json', include_e=False)) |
125 | + # Pop date-created and last-connection if existed for comparison |
126 | + user_status.pop("date-created", None) |
127 | + user_status.pop("last-connection", None) |
128 | + return user_status |
129 | + |
130 | + |
131 | +def assert_read_model(client, permission, has_permission): |
132 | + """Test if the user has or doesn't have the read permission""" |
133 | + if has_permission: |
134 | + try: |
135 | + client.show_status() |
136 | + except subprocess.CalledProcessError: |
137 | + raise JujuAssertionError( |
138 | + 'User could not check status with {} permission'.format( |
139 | + permission)) |
140 | + else: |
141 | + try: |
142 | + client.show_status() |
143 | + except subprocess.CalledProcessError: |
144 | + pass |
145 | + else: |
146 | + raise JujuAssertionError( |
147 | + 'User checked status without {} permission'.format(permission)) |
148 | + |
149 | + |
150 | +def assert_write_model(client, permission, has_permission): |
151 | + """Test if the user has or doesn't have the write permission""" |
152 | + if has_permission: |
153 | + try: |
154 | + client.deploy('cs:ubuntu') |
155 | + except subprocess.CalledProcessError: |
156 | + raise JujuAssertionError( |
157 | + 'User could not deploy with {} permission'.format(permission)) |
158 | + else: |
159 | + client.remove_service('ubuntu') |
160 | + else: |
161 | + try: |
162 | + client.deploy('cs:ubuntu') |
163 | + except subprocess.CalledProcessError: |
164 | + pass |
165 | + else: |
166 | + raise JujuAssertionError( |
167 | + 'User deployed without {} permission'.format(permission)) |
168 | + |
169 | + |
170 | +def assert_admin_controller(client, permission, has_permission): |
171 | + """Test if the user has or doesn't have the admin permission""" |
172 | + if has_permission: |
173 | + code = ''.join(random.choice( |
174 | + string.ascii_letters + string.digits) for _ in xrange(4)) |
175 | + try: |
176 | + client.add_user(permission + code, permissions='read') |
177 | + except subprocess.CalledProcessError: |
178 | + raise JujuAssertionError( |
179 | + 'User could not add user with {} permission'.format( |
180 | + permission)) |
181 | + else: |
182 | + try: |
183 | + client.add_user(permission + 'false', permissions='read') |
184 | + except subprocess.CalledProcessError: |
185 | + pass |
186 | + else: |
187 | + raise JujuAssertionError( |
188 | + 'User added user without {} permission'.format(permission)) |
189 | + |
190 | + |
191 | +def assert_user_permissions(user, user_client, controller_client): |
192 | + """Test if users' permissions are within expectations""" |
193 | + expect = iter(user.expect) |
194 | + permission = user.permissions |
195 | + assert_read_model(user_client, permission, expect.next()) |
196 | + assert_write_model(user_client, permission, expect.next()) |
197 | + assert_admin_controller(user_client, permission, expect.next()) |
198 | + |
199 | + log.debug("Revoking %s permission from %s" % (user.permissions, user.name)) |
200 | + controller_client.revoke(user.name, permissions=user.permissions) |
201 | + |
202 | + assert_read_model(user_client, permission, expect.next()) |
203 | + assert_write_model(user_client, permission, expect.next()) |
204 | + assert_admin_controller(user_client, permission, expect.next()) |
205 | + |
206 | + |
207 | +def assert_users_shares(controller_client, client, user): |
208 | + """Test if user_list and share_list are expected""" |
209 | + if user.name == 'readuser': |
210 | + user_list_expected = user_list_admin_read |
211 | + share_list_expected = share_list_admin_read |
212 | + else: |
213 | + user_list_expected = user_list_admin_read_write |
214 | + share_list_expected = share_list_all |
215 | + user_list = list_users(controller_client) |
216 | + share_list = list_shares(controller_client) |
217 | + if sorted(user_list) != sorted(user_list_expected): |
218 | + raise JujuAssertionError(user_list) |
219 | + if sorted(share_list) != sorted(share_list_expected): |
220 | + raise JujuAssertionError(share_list) |
221 | + |
222 | + |
223 | +def assert_change_password(client, user): |
224 | + """Test changing user's password""" |
225 | + try: |
226 | + child = client.expect('change-user-password', (user.name,), |
227 | + include_e=False) |
228 | + child.expect('(?i)password') |
229 | + child.sendline(user.name + '_password_2') |
230 | + child.expect('(?i)password') |
231 | + child.sendline(user.name + '_password_2') |
232 | + child.expect(pexpect.EOF) |
233 | + except pexpect.TIMEOUT: |
234 | + raise JujuAssertionError( |
235 | + 'Changing user password failed: pexpect session timed out') |
236 | + if child.isalive(): |
237 | + raise JujuAssertionError( |
238 | + 'Changing user password failed: pexpect session still alive') |
239 | + |
240 | + |
241 | +def assert_disable_enable(controller_client, user): |
242 | + """Test disabling and enabling users""" |
243 | + controller_client.disable_user(user.name) |
244 | + user_list = list_users(controller_client) |
245 | + if sorted(user_list) != sorted(user_list_admin_read): |
246 | + raise JujuAssertionError(user_list) |
247 | + controller_client.enable_user(user.name) |
248 | + user_list = list_users(controller_client) |
249 | + if sorted(user_list) != sorted(user_list_admin_read_write): |
250 | + raise JujuAssertionError(user_list) |
251 | + |
252 | + |
253 | +def assert_logout_login(controller_client, user_client, user, fake_home): |
254 | + """Test users' login and logout""" |
255 | + user_client.logout() |
256 | + user_list = list_users(controller_client) |
257 | + if sorted(user_list) != sorted(user_list_admin_read): |
258 | + raise JujuAssertionError(user_list) |
259 | username = user.name |
260 | controller_name = '{}_controller'.format(username) |
261 | - token = client.add_user(username, permissions=user.permissions) |
262 | - user_client, user_env = create_cloned_environment( |
263 | - client, fake_home, controller_name) |
264 | - |
265 | + client = controller_client.create_cloned_environment( |
266 | + fake_home, controller_name) |
267 | try: |
268 | - child = user_client.expect( |
269 | - 'register', (token), extra_env=user_env, include_e=False) |
270 | - child.expect('(?i)name') |
271 | - child.sendline(controller_name) |
272 | - child.expect('(?i)password') |
273 | - child.sendline(username + '_password') |
274 | - child.expect('(?i)password') |
275 | - child.sendline(username + '_password') |
276 | + child = client.expect('login', (user.name, '-c', controller_name), |
277 | + include_e=False) |
278 | + child.expect('(?i)password') |
279 | + child.sendline(user.name + '_password_2') |
280 | child.expect(pexpect.EOF) |
281 | if child.isalive(): |
282 | raise JujuAssertionError( |
283 | - 'Registering user failed: pexpect session still alive') |
284 | + 'Login user failed: pexpect session still alive') |
285 | except pexpect.TIMEOUT: |
286 | raise JujuAssertionError( |
287 | - 'Registering user failed: pexpect session timed out') |
288 | - return user_client |
289 | - |
290 | - |
291 | -def create_cloned_environment(client, cloned_juju_home, controller_name): |
292 | - user_client = client.clone(env=client.env.clone()) |
293 | - user_client.env.juju_home = cloned_juju_home |
294 | - # New user names the controller. |
295 | - user_client.env.controller = Controller(controller_name) |
296 | - user_client_env = user_client._shell_environ() |
297 | - return user_client, user_client_env |
298 | - |
299 | - |
300 | -def assert_read(client, permission): |
301 | - if permission: |
302 | - try: |
303 | - client.show_status() |
304 | - except subprocess.CalledProcessError: |
305 | - raise JujuAssertionError( |
306 | - 'User could not check status with read permission') |
307 | - else: |
308 | - try: |
309 | - client.show_status() |
310 | - except subprocess.CalledProcessError: |
311 | - pass |
312 | - else: |
313 | - raise JujuAssertionError( |
314 | - 'User checked status without read permission') |
315 | - |
316 | - |
317 | -def assert_write(client, permission): |
318 | - if permission: |
319 | - try: |
320 | - client.deploy('cs:ubuntu') |
321 | - except subprocess.CalledProcessError: |
322 | - raise JujuAssertionError( |
323 | - 'User could not deploy with write permission') |
324 | - else: |
325 | - try: |
326 | - client.deploy('cs:ubuntu') |
327 | - except subprocess.CalledProcessError: |
328 | - pass |
329 | - else: |
330 | - raise JujuAssertionError('User deployed without write permission') |
331 | - |
332 | - |
333 | -def assert_user_permissions(user, user_client, admin_client): |
334 | - expect = iter(user.expect) |
335 | - assert_read(user_client, expect.next()) |
336 | - assert_write(user_client, expect.next()) |
337 | - |
338 | - log.debug("Revoking %s permission from %s" % (user.permissions, user.name)) |
339 | - admin_client.revoke(user.name, permissions=user.permissions) |
340 | - |
341 | - assert_read(user_client, expect.next()) |
342 | - assert_write(user_client, expect.next()) |
343 | - |
344 | - |
345 | -def assess_user_grant_revoke(admin_client): |
346 | - # Wait for the deployment to finish. |
347 | - admin_client.wait_for_started() |
348 | - |
349 | + 'Login user failed: pexpect session timed out') |
350 | + |
351 | + |
352 | +def assert_read_user(controller_client, user): |
353 | + """Assess the operations of read user""" |
354 | + with temp_dir() as fake_home: |
355 | + user_client = controller_client.register_user( |
356 | + user, fake_home) |
357 | + user_list = list_users(controller_client) |
358 | + share_list = list_shares(controller_client) |
359 | + if sorted(user_list) != sorted(user_list_admin_read): |
360 | + raise JujuAssertionError(user_list) |
361 | + if sorted(share_list) != sorted(share_list_admin_read): |
362 | + raise JujuAssertionError(share_list) |
363 | + assert_change_password(user_client, user) |
364 | + assert_logout_login(controller_client, user_client, user, fake_home) |
365 | + assert_user_permissions(user, user_client, controller_client) |
366 | + |
367 | + |
368 | +def assert_write_user(controller_client, user): |
369 | + """Assess the operations of write user""" |
370 | + with temp_dir() as fake_home: |
371 | + user_client = controller_client.register_user( |
372 | + user, fake_home) |
373 | + user_list = list_users(controller_client) |
374 | + share_list = list_shares(controller_client) |
375 | + if sorted(user_list) != sorted(user_list_admin_read_write): |
376 | + raise JujuAssertionError(user_list) |
377 | + if sorted(share_list) != sorted(share_list_admin_read_write): |
378 | + raise JujuAssertionError(share_list) |
379 | + assert_disable_enable(controller_client, user) |
380 | + assert_user_permissions(user, user_client, controller_client) |
381 | + wait_for_removed_services(user_client, 'cs:ubuntu') |
382 | + |
383 | + |
384 | +def assert_admin_user(controller_client, user): |
385 | + """Assess the operations of admin user""" |
386 | + with temp_dir() as fake_home: |
387 | + user_client = controller_client.register_user( |
388 | + user, fake_home) |
389 | + user_list = list_users(controller_client) |
390 | + share_list = list_shares(controller_client) |
391 | + if sorted(user_list) != sorted(user_list_all): |
392 | + raise JujuAssertionError(user_list) |
393 | + if sorted(share_list) != sorted(share_list_all): |
394 | + raise JujuAssertionError(share_list) |
395 | + assert_user_permissions(user, user_client, controller_client) |
396 | + |
397 | + |
398 | +def assess_user_grant_revoke(controller_client): |
399 | + """Test multi-users functionality""" |
400 | + controller_client.env.user_name = 'admin' |
401 | log.debug("Creating Users") |
402 | - read_user = User('readuser', 'read', [True, False, False, False]) |
403 | - write_user = User('adminuser', 'write', [True, True, True, False]) |
404 | - users = [read_user, write_user] |
405 | - |
406 | - for user in users: |
407 | - log.debug("Testing %s" % user.name) |
408 | - with temp_dir() as fake_home: |
409 | - user_client = register_user( |
410 | - user, admin_client, fake_home) |
411 | - assert_user_permissions(user, user_client, admin_client) |
412 | + read_user = User('readuser', 'read', |
413 | + [True, False, False, False, False, False]) |
414 | + write_user = User('writeuser', 'write', |
415 | + [True, True, False, True, False, False]) |
416 | + admin_user = User('adminuser', 'admin', |
417 | + [True, True, True, True, True, True]) |
418 | + user_list = list_users(controller_client) |
419 | + share_list = list_shares(controller_client) |
420 | + user_status = show_user(controller_client) |
421 | + if sorted(user_list) != sorted(user_list_admin): |
422 | + raise JujuAssertionError(user_list) |
423 | + if share_list != share_list_admin: |
424 | + raise JujuAssertionError(share_list) |
425 | + if user_status != user_list_admin[0]: |
426 | + raise JujuAssertionError(user_status) |
427 | + assert_read_user(controller_client, read_user) |
428 | + assert_write_user(controller_client, write_user) |
429 | + assert_admin_user(controller_client, admin_user) |
430 | |
431 | |
432 | def parse_args(argv): |
433 | |
434 | === modified file 'jujupy.py' |
435 | --- jujupy.py 2016-07-22 18:12:55 +0000 |
436 | +++ jujupy.py 2016-07-25 20:33:36 +0000 |
437 | @@ -215,6 +215,7 @@ |
438 | |
439 | def __init__(self, environment, config=None, juju_home=None, |
440 | controller=None): |
441 | + self.user_name = None |
442 | if controller is None: |
443 | controller = Controller(environment) |
444 | self.controller = controller |
445 | @@ -663,7 +664,7 @@ |
446 | raise subprocess.CalledProcessError(retcode, full_args) |
447 | |
448 | def get_juju_output(self, command, args, used_feature_flags, |
449 | - juju_home, model=None, timeout=None): |
450 | + juju_home, model=None, timeout=None, user_name=None): |
451 | args = self.full_args(command, args, model, timeout) |
452 | env = self.shell_environ(used_feature_flags, juju_home) |
453 | log.debug(args) |
454 | @@ -1136,7 +1137,7 @@ |
455 | timeout = kwargs.get('timeout') |
456 | return self._backend.get_juju_output( |
457 | command, args, self.used_feature_flags, self.env.juju_home, |
458 | - model, timeout) |
459 | + model, timeout, user_name=self.env.user_name) |
460 | |
461 | def show_status(self): |
462 | """Print the status to output.""" |
463 | @@ -1807,6 +1808,64 @@ |
464 | (name, provider, |
465 | 'size={}'.format(size))) |
466 | |
467 | + def disable_user(self, user_name): |
468 | + """Disable an user""" |
469 | + self.controller_juju('disable-user', (user_name,)) |
470 | + |
471 | + def enable_user(self, user_name): |
472 | + """Enable an user""" |
473 | + self.controller_juju('enable-user', (user_name,)) |
474 | + |
475 | + def logout(self): |
476 | + """Logout an user""" |
477 | + self.controller_juju('logout', ()) |
478 | + |
479 | + def register_user(self, user, juju_home): |
480 | + """Register `user` for the `client` return the cloned client used.""" |
481 | + username = user.name |
482 | + controller_name = '{}_controller'.format(username) |
483 | + |
484 | + model = self.env.environment |
485 | + token = self.add_user(username, models=model + ',controller', |
486 | + permissions=user.permissions) |
487 | + user_client = self.create_cloned_environment(juju_home, |
488 | + controller_name) |
489 | + |
490 | + try: |
491 | + child = user_client.expect( |
492 | + 'register', (token), include_e=False) |
493 | + child.expect('(?i)name') |
494 | + child.sendline(username + '_controller') |
495 | + child.expect('(?i)password') |
496 | + child.sendline(username + '_password') |
497 | + child.expect('(?i)password') |
498 | + child.sendline(username + '_password') |
499 | + child.expect(pexpect.EOF) |
500 | + if child.isalive(): |
501 | + raise Exception( |
502 | + 'Registering user failed: pexpect session still alive') |
503 | + except pexpect.TIMEOUT: |
504 | + raise Exception( |
505 | + 'Registering user failed: pexpect session timed out') |
506 | + user_client.env.user_name = username |
507 | + return user_client |
508 | + |
509 | + def create_cloned_environment(self, cloned_juju_home, controller_name): |
510 | + """Create a cloned environment""" |
511 | + user_client = self.clone(env=self.env.clone()) |
512 | + user_client.env.juju_home = cloned_juju_home |
513 | + # New user names the controller. |
514 | + user_client.env.controller = Controller(controller_name) |
515 | + # user_client_env = user_client._shell_environ() |
516 | + return user_client |
517 | + |
518 | + def grant(self, user_name, permission, model=None): |
519 | + """Grant the user with a model.""" |
520 | + if model is None: |
521 | + model = self.model_name |
522 | + self.juju('grant', (user_name, model, '--acl', permission), |
523 | + include_e=False) |
524 | + |
525 | |
526 | class EnvJujuClient2B8(EnvJujuClient): |
527 | |
528 | |
529 | === modified file 'tests/test_assess_unregister.py' |
530 | --- tests/test_assess_unregister.py 2016-07-08 13:22:35 +0000 |
531 | +++ tests/test_assess_unregister.py 2016-07-25 20:33:36 +0000 |
532 | @@ -63,12 +63,13 @@ |
533 | |
534 | def test_unregister(self): |
535 | fake_user = Mock() |
536 | - with patch.object(a_unreg, 'register_user', |
537 | - return_value=fake_user): |
538 | - with patch.object(a_unreg, 'assert_controller_list', |
539 | - autospec=True) as mock_assert_list: |
540 | - with patch.object(a_unreg, 'assert_switch_raises_error', |
541 | - autospec=True): |
542 | + with patch('jujupy.EnvJujuClient.register_user', |
543 | + return_value=fake_user): |
544 | + with patch.object( |
545 | + a_unreg, 'assert_controller_list', |
546 | + autospec=True) as mock_assert_list: |
547 | + with patch.object( |
548 | + a_unreg, 'assert_switch_raises_error', autospec=True): |
549 | fake_client = Mock(wraps=fake_juju_client()) |
550 | fake_client.env.controller.name = 'testing-controller' |
551 | fake_client.bootstrap() |
552 | |
553 | === modified file 'tests/test_assess_user_grant_revoke.py' |
554 | --- tests/test_assess_user_grant_revoke.py 2016-07-08 17:30:56 +0000 |
555 | +++ tests/test_assess_user_grant_revoke.py 2016-07-25 20:33:36 +0000 |
556 | @@ -10,18 +10,14 @@ |
557 | import StringIO |
558 | from subprocess import CalledProcessError |
559 | |
560 | -import pexpect |
561 | - |
562 | from assess_user_grant_revoke import ( |
563 | - assert_read, |
564 | + assert_read_model, |
565 | assert_user_permissions, |
566 | - assert_write, |
567 | + assert_write_model, |
568 | assess_user_grant_revoke, |
569 | - create_cloned_environment, |
570 | JujuAssertionError, |
571 | main, |
572 | parse_args, |
573 | - register_user, |
574 | ) |
575 | from jujupy import JUJU_DEV_FEATURE_FLAGS |
576 | from tests import ( |
577 | @@ -112,47 +108,60 @@ |
578 | class TestAsserts(TestCase): |
579 | |
580 | def test_assert_user_permissions(self): |
581 | - user = namedtuple('user', ['name', 'permissions', 'expect']) |
582 | - read_user = user('readuser', 'read', [True, False, False, False]) |
583 | - write_user = user('adminuser', 'write', [True, True, True, False]) |
584 | - users = [read_user, write_user] |
585 | + User = namedtuple('user', ['name', 'permissions', 'expect']) |
586 | + read_user = User('readuser', 'read', |
587 | + [True, False, False, False, False, False]) |
588 | + write_user = User('writeuser', 'write', |
589 | + [True, True, False, True, False, False]) |
590 | + admin_user = User('adminuser', 'admin', |
591 | + [True, True, True, True, False, False]) |
592 | + users = [read_user, write_user, admin_user] |
593 | |
594 | for user in users: |
595 | fake_client = fake_juju_client() |
596 | fake_admin_client = fake_juju_client() |
597 | + ac = patch("assess_user_grant_revoke.assert_admin_controller", |
598 | + return_value=True) |
599 | with patch("jujupy.EnvJujuClient.revoke", return_value=True): |
600 | - with patch("assess_user_grant_revoke.assert_read", |
601 | + with patch("assess_user_grant_revoke.assert_read_model", |
602 | return_value=True) as read_mock: |
603 | - with patch("assess_user_grant_revoke.assert_write", |
604 | + with patch("assess_user_grant_revoke.assert_write_model", |
605 | return_value=True) as write_mock: |
606 | - assert_user_permissions(user, fake_client, |
607 | - fake_admin_client) |
608 | - self.assertEqual(read_mock.call_count, 2) |
609 | - self.assertEqual(write_mock.call_count, 2) |
610 | + with ac as admin_mock: |
611 | + assert_user_permissions(user, fake_client, |
612 | + fake_admin_client) |
613 | + self.assertEqual(read_mock.call_count, 2) |
614 | + self.assertEqual(write_mock.call_count, 2) |
615 | + self.assertEqual(admin_mock.call_count, 2) |
616 | |
617 | - def test_assert_read(self): |
618 | + def test_assert_read_model(self): |
619 | fake_client = fake_juju_client() |
620 | with patch.object(fake_client, 'show_status', return_value=True): |
621 | - assert_read(fake_client, True) |
622 | + assert_read_model(fake_client, 'read', True) |
623 | with self.assertRaises(JujuAssertionError): |
624 | - assert_read(fake_client, False) |
625 | + assert_read_model(fake_client, 'read', False) |
626 | with patch.object(fake_client, 'show_status', return_value=False, |
627 | side_effect=CalledProcessError(None, None, None)): |
628 | - assert_read(fake_client, False) |
629 | + assert_read_model(fake_client, 'read', False) |
630 | with self.assertRaises(JujuAssertionError): |
631 | - assert_read(fake_client, True) |
632 | + assert_read_model(fake_client, 'read', True) |
633 | |
634 | - def test_assert_write(self): |
635 | + def test_assert_write_model(self): |
636 | fake_client = fake_juju_client() |
637 | with patch.object(fake_client, 'deploy', return_value=True): |
638 | - assert_write(fake_client, True) |
639 | + with patch.object(fake_client, 'remove_service', autospec=True): |
640 | + assert_write_model(fake_client, 'write', True) |
641 | with self.assertRaises(JujuAssertionError): |
642 | - assert_write(fake_client, False) |
643 | + with patch.object(fake_client, 'remove_service', |
644 | + autospec=True): |
645 | + assert_write_model(fake_client, 'write', False) |
646 | with patch.object(fake_client, 'deploy', return_value=False, |
647 | side_effect=CalledProcessError(None, None, None)): |
648 | - assert_write(fake_client, False) |
649 | + assert_write_model(fake_client, 'write', False) |
650 | with self.assertRaises(JujuAssertionError): |
651 | - assert_write(fake_client, True) |
652 | + with patch.object(fake_client, 'remove_service', |
653 | + autospec=True): |
654 | + assert_write_model(fake_client, 'write', True) |
655 | |
656 | |
657 | def make_fake_client(): |
658 | @@ -169,70 +178,53 @@ |
659 | def test_user_grant_revoke(self): |
660 | fake_client = make_fake_client() |
661 | fake_client.bootstrap() |
662 | - |
663 | - user = namedtuple('user', ['name', 'permissions', 'expect']) |
664 | - read_user = user('readuser', 'read', [True, False, False, False]) |
665 | - write_user = user('adminuser', 'write', [True, True, True, False]) |
666 | - |
667 | - with patch("assess_user_grant_revoke.register_user", |
668 | - return_value=True) as reg_mock: |
669 | - with patch("assess_user_grant_revoke.assert_user_permissions", |
670 | - autospec=True) as perm_mock: |
671 | - assess_user_grant_revoke(fake_client) |
672 | - |
673 | - self.assertEqual(reg_mock.call_count, 2) |
674 | - self.assertEqual(perm_mock.call_count, 2) |
675 | - |
676 | - read_user_call, write_user_call = perm_mock.call_args_list |
677 | - read_user_args, read_user_kwargs = read_user_call |
678 | - write_user_args, write_user_kwargs = write_user_call |
679 | - |
680 | - self.assertEqual(read_user_args[0], read_user) |
681 | - self.assertEqual(read_user_args[2], fake_client) |
682 | - self.assertEqual(write_user_args[0], write_user) |
683 | - self.assertEqual(write_user_args[2], fake_client) |
684 | - |
685 | - def test_create_cloned_environment(self): |
686 | - fake_client = make_fake_client() |
687 | - fake_client.bootstrap() |
688 | - fake_client_environ = fake_client._shell_environ() |
689 | - controller_name = 'user_controller' |
690 | - cloned, cloned_environ = create_cloned_environment( |
691 | - fake_client, |
692 | - 'fakehome', |
693 | - controller_name |
694 | - ) |
695 | - self.assertIs(fake_client.__class__, type(cloned)) |
696 | - self.assertEqual(cloned.env.juju_home, 'fakehome') |
697 | - self.assertNotEqual(cloned_environ, fake_client_environ) |
698 | - self.assertEqual(cloned_environ['JUJU_DATA'], 'fakehome') |
699 | - self.assertEqual(cloned.env.controller.name, controller_name) |
700 | - self.assertEqual(fake_client.env.controller.name, 'name') |
701 | - |
702 | - def test_register_user(self): |
703 | - FakeUser = namedtuple('user', ['name', 'permissions']) |
704 | - user = FakeUser('fakeuser', 'read') |
705 | - |
706 | - class FakeClient: |
707 | - """Lightweight fake client for testing.""" |
708 | - def add_user(self, username, permissions): |
709 | - return 'token' |
710 | - |
711 | - def expect(self, *args, **kwargs): |
712 | - return PexpectInteraction( |
713 | - [ |
714 | - user.name + '_controller', |
715 | - user.name + '_password', |
716 | - user.name + '_password', |
717 | - pexpect.EOF], |
718 | - [ |
719 | - '(?i)name', |
720 | - '(?i)password', |
721 | - '(?i)password', |
722 | - pexpect.EOF]) |
723 | - |
724 | - fake_client = FakeClient() |
725 | - with patch( |
726 | - 'assess_user_grant_revoke.create_cloned_environment', |
727 | - return_value=(fake_client, {})): |
728 | - register_user(user, fake_client, '/tmp/dir/path') |
729 | + assert_read_calls = [True, False, True, True, True, True] |
730 | + assert_write_calls = [False, False, True, False, True, True] |
731 | + assert_admin_calls = [False, False, False, False, True, True] |
732 | + cpass = patch("assess_user_grant_revoke.assert_change_password", |
733 | + autospec=True) |
734 | + able = patch("assess_user_grant_revoke.assert_disable_enable", |
735 | + autospec=True) |
736 | + log = patch("assess_user_grant_revoke.assert_logout_login", |
737 | + autospec=True) |
738 | + expect = patch("jujupy.EnvJujuClient.expect", |
739 | + autospec=True) |
740 | + read = patch("assess_user_grant_revoke.assert_read_model", |
741 | + autospec=True) |
742 | + write = patch("assess_user_grant_revoke.assert_write_model", |
743 | + autospec=True) |
744 | + admin = patch("assess_user_grant_revoke.assert_admin_controller", |
745 | + autospec=True) |
746 | + rm = patch("utility.wait_for_removed_services", |
747 | + autospec=True) |
748 | + with cpass as pass_mock, able as able_mock, log as log_mock: |
749 | + with read as read_mock, write as write_mock, admin as admin_mock: |
750 | + with expect as expect_mock: |
751 | + expect_mock.return_value.isalive.return_value = False |
752 | + with rm: |
753 | + assess_user_grant_revoke(fake_client) |
754 | + |
755 | + self.assertEqual(pass_mock.call_count, 1) |
756 | + self.assertEqual(able_mock.call_count, 1) |
757 | + self.assertEqual(log_mock.call_count, 1) |
758 | + |
759 | + self.assertEqual(read_mock.call_count, 6) |
760 | + self.assertEqual(write_mock.call_count, 6) |
761 | + self.assertEqual(admin_mock.call_count, 6) |
762 | + |
763 | + read_calls = [ |
764 | + call[0][2] for call in |
765 | + read_mock.call_args_list] |
766 | + write_calls = [ |
767 | + call[0][2] for call in |
768 | + write_mock.call_args_list] |
769 | + admin_calls = [ |
770 | + call[0][2] for call in |
771 | + admin_mock.call_args_list] |
772 | + |
773 | + self.assertEqual(read_calls, |
774 | + assert_read_calls) |
775 | + self.assertEqual(write_calls, |
776 | + assert_write_calls) |
777 | + self.assertEqual(admin_calls, |
778 | + assert_admin_calls) |
779 | |
780 | === modified file 'tests/test_jujupy.py' |
781 | --- tests/test_jujupy.py 2016-07-25 18:08:15 +0000 |
782 | +++ tests/test_jujupy.py 2016-07-25 20:33:36 +0000 |
783 | @@ -127,6 +127,13 @@ |
784 | def __init__(self): |
785 | self.state = 'not-bootstrapped' |
786 | self.models = {} |
787 | + self.users = { |
788 | + 'admin': { |
789 | + 'state': '', |
790 | + 'permission': 'write' |
791 | + } |
792 | + } |
793 | + self.shares = ['admin'] |
794 | |
795 | def add_model(self, name): |
796 | state = FakeEnvironmentState() |
797 | @@ -471,6 +478,45 @@ |
798 | self.controller_state.models.values()] |
799 | return {'models': [{'name': n} for n in model_names]} |
800 | |
801 | + def list_users(self): |
802 | + user_names = [name for name in |
803 | + self.controller_state.users.keys()] |
804 | + user_list = [] |
805 | + for n in user_names: |
806 | + if n == 'admin': |
807 | + append_dict = {'user-name': n, 'display-name': n} |
808 | + else: |
809 | + append_dict = {'user-name': n, 'display-name': ''} |
810 | + user_list.append(append_dict) |
811 | + return user_list |
812 | + |
813 | + def show_user(self, user_name): |
814 | + if user_name is None: |
815 | + raise Exception("No user specified") |
816 | + if user_name == 'admin': |
817 | + user_status = {'user-name': user_name, 'display-name': user_name} |
818 | + else: |
819 | + user_status = {'user-name': user_name, 'display-name': ''} |
820 | + return user_status |
821 | + |
822 | + def list_shares(self): |
823 | + share_names = self.controller_state.shares |
824 | + permissions = [] |
825 | + for key, value in self.controller_state.users.iteritems(): |
826 | + if key in share_names: |
827 | + permissions.append(value['permission']) |
828 | + share_list = {} |
829 | + for i, (share_name, permission) in enumerate( |
830 | + zip(share_names, permissions)): |
831 | + name = share_name + '@local' |
832 | + share_list[name] = {'display-name': share_name, |
833 | + 'access': permission} |
834 | + if name != 'admin@local': |
835 | + share_list[name].pop('display-name') |
836 | + else: |
837 | + share_list[name]['access'] = 'admin' |
838 | + return share_list |
839 | + |
840 | def _log_command(self, command, args, model, level=logging.INFO): |
841 | full_args = ['juju', command] |
842 | if model is not None: |
843 | @@ -566,6 +612,16 @@ |
844 | parser.add_argument('model_name') |
845 | parsed = parser.parse_args(args) |
846 | self.controller_state.add_model(parsed.model_name) |
847 | + if command == 'revoke': |
848 | + user_name = args[2] |
849 | + permissions = args[5] |
850 | + per = self.controller_state.users[user_name]['permission'] |
851 | + if per == permissions: |
852 | + if permissions == 'read': |
853 | + self.controller_state.shares.remove(user_name) |
854 | + per = '' |
855 | + else: |
856 | + per = 'read' |
857 | |
858 | @contextmanager |
859 | def juju_async(self, command, args, used_feature_flags, |
860 | @@ -576,7 +632,7 @@ |
861 | |
862 | @check_juju_output |
863 | def get_juju_output(self, command, args, used_feature_flags, |
864 | - juju_home, model=None, timeout=None): |
865 | + juju_home, model=None, timeout=None, user_name=None): |
866 | if 'service' in command: |
867 | raise Exception('No service') |
868 | self._log_command(command, args, model, logging.DEBUG) |
869 | @@ -597,6 +653,12 @@ |
870 | return yaml.safe_dump(self.make_controller_dict(args[0])) |
871 | if command == 'list-models': |
872 | return yaml.safe_dump(self.list_models()) |
873 | + if command == 'list-users': |
874 | + return json.dumps(self.list_users()) |
875 | + if command == 'list-shares': |
876 | + return json.dumps(self.list_shares()) |
877 | + if command == 'show-user': |
878 | + return json.dumps(self.show_user(user_name)) |
879 | if command == 'add-user': |
880 | permissions = 'read' |
881 | if set(["--acl", "write"]).issubset(args): |
882 | @@ -606,6 +668,9 @@ |
883 | info_string = \ |
884 | 'User "{}" added\nUser "{}"granted {} access to model "{}\n"' \ |
885 | .format(username, username, permissions, model) |
886 | + self.controller_state.users.update( |
887 | + {username: {'state': '', 'permission': permissions}}) |
888 | + self.controller_state.shares.append(username) |
889 | register_string = get_user_register_command_info(username) |
890 | return info_string + register_string |
891 | if command == 'show-status': |
892 | @@ -3045,7 +3110,8 @@ |
893 | gjo_mock.assert_called_once_with( |
894 | 'run', ('--format', 'json', '--application', 'foo,bar', 'wname'), |
895 | frozenset( |
896 | - ['address-allocation', 'migration']), 'foo', 'name:name', None) |
897 | + ['address-allocation', 'migration']), 'foo', |
898 | + 'name:name', None, user_name=None) |
899 | |
900 | def test_list_space(self): |
901 | client = EnvJujuClient(JujuData(None, {'type': 'local'}), |
902 | @@ -3248,6 +3314,46 @@ |
903 | get_output.assert_called_with( |
904 | 'add-user', *expected_args, include_e=False) |
905 | |
906 | + def test_disable_user(self): |
907 | + env = JujuData('foo') |
908 | + username = 'fakeuser' |
909 | + client = EnvJujuClient(env, None, None) |
910 | + with patch.object(client, 'juju') as mock: |
911 | + client.disable_user(username) |
912 | + mock.assert_called_with( |
913 | + 'disable-user', ('-c', 'foo', 'fakeuser'), include_e=False) |
914 | + |
915 | + def test_enable_user(self): |
916 | + env = JujuData('foo') |
917 | + username = 'fakeuser' |
918 | + client = EnvJujuClient(env, None, None) |
919 | + with patch.object(client, 'juju') as mock: |
920 | + client.enable_user(username) |
921 | + mock.assert_called_with( |
922 | + 'enable-user', ('-c', 'foo', 'fakeuser'), include_e=False) |
923 | + |
924 | + def test_logout(self): |
925 | + env = JujuData('foo') |
926 | + client = EnvJujuClient(env, None, None) |
927 | + with patch.object(client, 'juju') as mock: |
928 | + client.logout() |
929 | + mock.assert_called_with( |
930 | + 'logout', ('-c', 'foo'), include_e=False) |
931 | + |
932 | + def test_create_cloned_environment(self): |
933 | + fake_client = fake_juju_client() |
934 | + fake_client.bootstrap() |
935 | + # fake_client_environ = fake_client._shell_environ() |
936 | + controller_name = 'user_controller' |
937 | + cloned = fake_client.create_cloned_environment( |
938 | + 'fakehome', |
939 | + controller_name |
940 | + ) |
941 | + self.assertIs(fake_client.__class__, type(cloned)) |
942 | + self.assertEqual(cloned.env.juju_home, 'fakehome') |
943 | + self.assertEqual(cloned.env.controller.name, controller_name) |
944 | + self.assertEqual(fake_client.env.controller.name, 'name') |
945 | + |
946 | |
947 | class TestEnvJujuClient2B8(ClientTest): |
948 | |
949 | @@ -5070,7 +5176,8 @@ |
950 | gjo_mock.assert_called_once_with( |
951 | 'run', ('--format', 'json', '--service', 'foo,bar', 'wname'), |
952 | frozenset( |
953 | - ['address-allocation', 'migration']), 'foo', 'name', None) |
954 | + ['address-allocation', 'migration']), |
955 | + 'foo', 'name', None, user_name=None) |
956 | |
957 | def test_list_space(self): |
958 | client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}), |
See comments inline.
The big thing is you can't store the user name on the backing state, because the backing state should be shared by all clients.