Merge lp:~allenap/maas/shared-to-per-tenant-storage-1.2 into lp:maas/1.2

Proposed by Gavin Panella
Status: Merged
Approved by: Raphaël Badin
Approved revision: no longer in the source branch.
Merged at revision: 1367
Proposed branch: lp:~allenap/maas/shared-to-per-tenant-storage-1.2
Merge into: lp:maas/1.2
Diff against target: 632 lines (+570/-6)
9 files modified
src/maasserver/models/user.py (+1/-0)
src/maasserver/support/pertenant/migration.py (+179/-0)
src/maasserver/support/pertenant/tests/test_migration.py (+384/-0)
src/maasserver/tests/data/test_rsa0.pub (+1/-1)
src/maasserver/tests/data/test_rsa1.pub (+1/-1)
src/maasserver/tests/data/test_rsa2.pub (+1/-1)
src/maasserver/tests/data/test_rsa3.pub (+1/-1)
src/maasserver/tests/data/test_rsa4.pub (+1/-1)
src/maasserver/tests/test_sshkey.py (+1/-1)
To merge this branch: bzr merge lp:~allenap/maas/shared-to-per-tenant-storage-1.2
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+152020@code.launchpad.net

Commit message

Backport of r1449 from trunk: Add the mechanism for migrating shared-namespace file storage usage over to the per-tenant model.

There is a carefully constructed 4-step process that's intended to minimise disruption. Ultimately some eggs do need to be broken, but this branch catches their contents and tries damn hard to bake a tasty cake with it.

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/models/user.py'
--- src/maasserver/models/user.py 2012-08-13 04:47:10 +0000
+++ src/maasserver/models/user.py 2013-03-06 17:48:23 +0000
@@ -15,6 +15,7 @@
15 'create_user',15 'create_user',
16 'get_auth_tokens',16 'get_auth_tokens',
17 'get_creds_tuple',17 'get_creds_tuple',
18 'SYSTEM_USERS',
18 ]19 ]
1920
20from maasserver import worker_user21from maasserver import worker_user
2122
=== added file 'src/maasserver/support/pertenant/migration.py'
--- src/maasserver/support/pertenant/migration.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/support/pertenant/migration.py 2013-03-06 17:48:23 +0000
@@ -0,0 +1,179 @@
1# Copyright 2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Shared namespace --> per-tenant namespace migration.
5
6Perform the following steps to migrate:
7
81. When no files exist (i.e. no Juju environments exist): do nothing
91a. When no *unowned* files exist: do nothing.
10
112. When there's only one user: assign ownership of all files to user.
12
133. When there are multiple users and a `provider-state` file: parse that file
14 to extract the instance id of the bootstrap node. From that instance id,
15 get the identity of the user who deployed this environment (that's the
16 owner of the bootstrap node). Then proceed as in 4, using that user as the
17 "legacy" user.
18
194. When there are multiple users: create a new "legacy" user, assign ownership
20 of all files and allocated/owned nodes to this user, copy all public SSH
21 keys to this user, and move all API credentials to this user.
22
23There's not a lot we can do about SSH keys authorised to connect to the
24already deployed nodes in #3, but this set will only ever decrease: nodes
25allocated after this migration will permit access from any of the users with
26SSH keys prior to the migration.
27"""
28
29from __future__ import (
30 absolute_import,
31 print_function,
32 unicode_literals,
33 )
34
35__metaclass__ = type
36__all__ = [
37 "migrate",
38 ]
39
40from django.contrib.auth.models import User
41from maasserver.models import (
42 FileStorage,
43 Node,
44 SSHKey,
45 )
46from maasserver.models.user import (
47 get_auth_tokens,
48 SYSTEM_USERS,
49 )
50from maasserver.support.pertenant.utils import get_bootstrap_node_owner
51from maasserver.utils.orm import get_one
52
53
54legacy_user_name = "shared-environment"
55
56
57def get_legacy_user():
58 """Return the legacy namespace user, creating it if need be."""
59 try:
60 legacy_user = User.objects.get(username=legacy_user_name)
61 except User.DoesNotExist:
62 # Create the legacy user with a local, probably non-working, email
63 # address, and an unusable password.
64 legacy_user = User.objects.create_user(
65 email="%s@localhost" % legacy_user_name,
66 username=legacy_user_name)
67 legacy_user.first_name = "Shared"
68 legacy_user.last_name = "Environment"
69 legacy_user.is_active = True
70 return legacy_user
71
72
73def get_unowned_files():
74 """Returns a `QuerySet` of unowned files."""
75 return FileStorage.objects.filter(owner=None)
76
77
78def get_real_users():
79 """Returns a `QuerySet` of real. not system, users."""
80 users = User.objects.exclude(username__in=SYSTEM_USERS)
81 users = users.exclude(username=legacy_user_name)
82 return users
83
84
85def get_owned_nodes():
86 """Returns a `QuerySet` of nodes owned by real users."""
87 return Node.objects.filter(owner__in=get_real_users())
88
89
90def get_owned_nodes_owners():
91 """Returns a `QuerySet` of the owners of nodes owned by real users."""
92 owner_ids = get_owned_nodes().values_list("owner", flat=True)
93 return User.objects.filter(id__in=owner_ids.distinct())
94
95
96def get_destination_user():
97 """Return the user to which resources should be assigned."""
98 real_users = get_real_users()
99 if real_users.count() == 1:
100 return get_one(real_users)
101 else:
102 bootstrap_user = get_bootstrap_node_owner()
103 if bootstrap_user is None:
104 return get_legacy_user()
105 else:
106 return bootstrap_user
107
108
109def get_ssh_keys(user):
110 """Return the SSH key strings belonging to the specified user."""
111 return SSHKey.objects.filter(user=user).values_list("key", flat=True)
112
113
114def copy_ssh_keys(user_from, user_dest):
115 """Copies SSH keys from one user to another.
116
117 This is idempotent, and does not clobber the destination user's existing
118 keys.
119 """
120 user_from_keys = get_ssh_keys(user_from)
121 user_dest_keys = get_ssh_keys(user_dest)
122 for key in set(user_from_keys).difference(user_dest_keys):
123 ssh_key = SSHKey(user=user_dest, key=key)
124 ssh_key.save()
125
126
127def give_file_to_user(file, user):
128 """Give a file to a user."""
129 file.owner = user
130 file.save()
131
132
133def give_api_credentials_to_user(user_from, user_dest):
134 """Gives one user's API credentials to another.
135
136 This ensures that users of the shared namespace environment continue to
137 operate within the legacy shared namespace environment by default via the
138 API (e.g. maas-cli and Juju).
139 """
140 for token in get_auth_tokens(user_from):
141 consumer = token.consumer
142 consumer.user = user_dest
143 consumer.save()
144 token.user = user_dest
145 token.save()
146
147
148def give_node_to_user(node, user):
149 """Changes a node's ownership for the legacy shared environment."""
150 node.owner = user
151 node.save()
152
153
154def migrate_to_user(user):
155 """Migrate files and nodes to the specified user.
156
157 This also copies, to the destination user, the public SSH keys of any
158 owned nodes' owners. This is so that those users who had allocated nodes
159 (i.e. active users of a shared-namespace environment) can access newly
160 created nodes in the legacy shared-namespace environment.
161 """
162 for unowned_file in get_unowned_files():
163 give_file_to_user(unowned_file, user)
164 for node_owner in get_owned_nodes_owners():
165 copy_ssh_keys(node_owner, user)
166 give_api_credentials_to_user(node_owner, user)
167 for owned_node in get_owned_nodes():
168 give_node_to_user(owned_node, user)
169
170
171def migrate():
172 """Migrate files to a per-tenant namespace."""
173 if get_unowned_files().exists():
174 # 2, 3, and 4
175 user = get_destination_user()
176 migrate_to_user(user)
177 else:
178 # 1 and 1a
179 pass
0180
=== added file 'src/maasserver/support/pertenant/tests/test_migration.py'
--- src/maasserver/support/pertenant/tests/test_migration.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/support/pertenant/tests/test_migration.py 2013-03-06 17:48:23 +0000
@@ -0,0 +1,384 @@
1# Copyright 2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Test `maasserver.support.pertenant.migration."""
5
6from __future__ import (
7 absolute_import,
8 print_function,
9 unicode_literals,
10 )
11
12__metaclass__ = type
13__all__ = []
14
15from django.contrib.auth.models import User
16from maasserver.models import (
17 Node,
18 SSHKey,
19 )
20from maasserver.support.pertenant import migration
21from maasserver.support.pertenant.migration import (
22 copy_ssh_keys,
23 get_destination_user,
24 get_legacy_user,
25 get_owned_nodes,
26 get_owned_nodes_owners,
27 get_real_users,
28 get_ssh_keys,
29 get_unowned_files,
30 give_api_credentials_to_user,
31 give_file_to_user,
32 give_node_to_user,
33 legacy_user_name,
34 migrate,
35 migrate_to_user,
36 )
37from maasserver.support.pertenant.tests.test_utils import (
38 make_provider_state_file,
39 )
40from maasserver.testing import (
41 get_data,
42 reload_object,
43 )
44from maasserver.testing.factory import factory
45from maasserver.testing.testcase import TestCase
46from mock import (
47 call,
48 sentinel,
49 )
50from testtools.matchers import MatchesStructure
51
52
53def get_ssh_key_string(num=0):
54 return get_data('data/test_rsa%d.pub' % num)
55
56
57class TestFunctions(TestCase):
58
59 def find_legacy_user(self):
60 return User.objects.filter(username=legacy_user_name)
61
62 def test_get_legacy_user_creates_user(self):
63 self.assertEqual([], list(self.find_legacy_user()))
64 legacy_user = get_legacy_user()
65 self.assertEqual([legacy_user], list(self.find_legacy_user()))
66 self.assertThat(
67 legacy_user, MatchesStructure.byEquality(
68 first_name="Shared", last_name="Environment",
69 email=legacy_user_name + "@localhost", is_active=True))
70
71 def test_get_legacy_user_creates_user_only_once(self):
72 legacy_user1 = get_legacy_user()
73 self.assertEqual([legacy_user1], list(self.find_legacy_user()))
74 legacy_user2 = get_legacy_user()
75 self.assertEqual([legacy_user2], list(self.find_legacy_user()))
76 self.assertEqual(legacy_user1, legacy_user2)
77
78 def test_get_unowned_files_no_files(self):
79 self.assertEqual([], list(get_unowned_files()))
80
81 def test_get_unowned_files(self):
82 user = factory.make_user()
83 files = [
84 factory.make_file_storage(owner=None),
85 factory.make_file_storage(owner=user),
86 factory.make_file_storage(owner=None),
87 ]
88 self.assertSetEqual(
89 {files[0], files[2]},
90 set(get_unowned_files()))
91
92 def test_get_real_users_no_users(self):
93 get_legacy_user() # Ensure at least the legacy user exists.
94 self.assertEqual([], list(get_real_users()))
95
96 def test_get_real_users(self):
97 get_legacy_user() # Ensure at least the legacy user exists.
98 users = [
99 factory.make_user(),
100 factory.make_user(),
101 ]
102 self.assertSetEqual(set(users), set(get_real_users()))
103
104 def test_get_owned_nodes_no_nodes(self):
105 self.assertEqual([], list(get_owned_nodes()))
106
107 def test_get_owned_nodes_no_owned_nodes(self):
108 factory.make_node()
109 self.assertEqual([], list(get_owned_nodes()))
110
111 def test_get_owned_nodes_with_owned_nodes(self):
112 nodes = {
113 factory.make_node(owner=factory.make_user()),
114 factory.make_node(owner=factory.make_user()),
115 }
116 self.assertSetEqual(nodes, set(get_owned_nodes()))
117
118 def test_get_owned_nodes_with_nodes_owned_by_system_users(self):
119 factory.make_node(owner=get_legacy_user()),
120 self.assertEqual([], list(get_owned_nodes()))
121
122 def test_get_owned_nodes_owners_no_users(self):
123 self.assertEqual([], list(get_owned_nodes_owners()))
124
125 def test_get_owned_nodes_owners_no_nodes(self):
126 factory.make_user()
127 self.assertEqual([], list(get_owned_nodes_owners()))
128
129 def test_get_owned_nodes_owners_no_owned_nodes(self):
130 factory.make_user()
131 factory.make_node(owner=None)
132 self.assertEqual([], list(get_owned_nodes_owners()))
133
134 def test_get_owned_nodes_owners(self):
135 user1 = factory.make_user()
136 user2 = factory.make_user()
137 factory.make_user()
138 factory.make_node(owner=user1)
139 factory.make_node(owner=user2)
140 factory.make_node(owner=None)
141 self.assertSetEqual({user1, user2}, set(get_owned_nodes_owners()))
142
143 def test_get_destination_user_one_real_user(self):
144 user = factory.make_user()
145 self.assertEqual(user, get_destination_user())
146
147 def test_get_destination_user_two_real_users(self):
148 factory.make_user()
149 factory.make_user()
150 self.assertEqual(get_legacy_user(), get_destination_user())
151
152 def test_get_destination_user_no_real_users(self):
153 self.assertEqual(get_legacy_user(), get_destination_user())
154
155 def test_get_destination_user_with_user_from_juju_state(self):
156 user1, user2 = factory.make_user(), factory.make_user()
157 node = factory.make_node(owner=user1)
158 make_provider_state_file(node)
159 self.assertEqual(user1, get_destination_user())
160
161 def test_get_destination_user_with_orphaned_juju_state(self):
162 user1, user2 = factory.make_user(), factory.make_user()
163 node = factory.make_node(owner=user1)
164 make_provider_state_file(node)
165 node.delete() # Orphan the state.
166 self.assertEqual(get_legacy_user(), get_destination_user())
167
168
169class TestCopySSHKeys(TestCase):
170 """Tests for copy_ssh_keys()."""
171
172 def test_noop_when_there_are_no_keys(self):
173 user1 = factory.make_user()
174 user2 = factory.make_user()
175 copy_ssh_keys(user1, user2)
176 ssh_keys = SSHKey.objects.filter(user__in={user1, user2})
177 self.assertEqual([], list(ssh_keys))
178
179 def test_copy(self):
180 user1 = factory.make_user()
181 key1 = factory.make_sshkey(user1)
182 user2 = factory.make_user()
183 copy_ssh_keys(user1, user2)
184 user2s_ssh_keys = SSHKey.objects.filter(user=user2)
185 self.assertSetEqual(
186 {key1.key}, {ssh_key.key for ssh_key in user2s_ssh_keys})
187
188 def test_copy_is_idempotent(self):
189 # When the destination user already has a key, copy_ssh_keys() is a
190 # noop for that key.
191 user1 = factory.make_user()
192 key1 = factory.make_sshkey(user1)
193 user2 = factory.make_user()
194 key2 = factory.make_sshkey(user2, key1.key)
195 copy_ssh_keys(user1, user2)
196 user2s_ssh_keys = SSHKey.objects.filter(user=user2)
197 self.assertSetEqual(
198 {key2.key}, {ssh_key.key for ssh_key in user2s_ssh_keys})
199
200 def test_copy_does_not_clobber(self):
201 # When the destination user already has some keys, copy_ssh_keys()
202 # adds to them; it does not remove them.
203 user1 = factory.make_user()
204 key1 = factory.make_sshkey(user1, get_ssh_key_string(1))
205 user2 = factory.make_user()
206 key2 = factory.make_sshkey(user2, get_ssh_key_string(2))
207 copy_ssh_keys(user1, user2)
208 user2s_ssh_keys = SSHKey.objects.filter(user=user2)
209 self.assertSetEqual(
210 {key1.key, key2.key},
211 {ssh_key.key for ssh_key in user2s_ssh_keys})
212
213
214class TestGiveFileToUser(TestCase):
215
216 def test_give_unowned_file(self):
217 user = factory.make_user()
218 file = factory.make_file_storage(owner=None)
219 give_file_to_user(file, user)
220 self.assertEqual(user, file.owner)
221
222 def test_give_owned_file(self):
223 user1 = factory.make_user()
224 user2 = factory.make_user()
225 file = factory.make_file_storage(owner=user1)
226 give_file_to_user(file, user2)
227 self.assertEqual(user2, file.owner)
228
229 def test_file_saved(self):
230 user = factory.make_user()
231 file = factory.make_file_storage(owner=None)
232 save = self.patch(file, "save")
233 give_file_to_user(file, user)
234 save.assert_called_once()
235
236
237class TestGiveCredentialsToUser(TestCase):
238
239 def test_give(self):
240 user1 = factory.make_user()
241 user2 = factory.make_user()
242 profile = user1.get_profile()
243 consumer, token = profile.create_authorisation_token()
244 give_api_credentials_to_user(user1, user2)
245 self.assertEqual(user2, reload_object(consumer).user)
246 self.assertEqual(user2, reload_object(token).user)
247
248
249class TestGiveNodeToUser(TestCase):
250
251 def test_give(self):
252 user1 = factory.make_user()
253 user2 = factory.make_user()
254 node = factory.make_node(owner=user1)
255 give_node_to_user(node, user2)
256 self.assertEqual(user2, reload_object(node).owner)
257
258
259class TestMigrateToUser(TestCase):
260
261 def test_migrate(self):
262 # This is a mechanical test, to demonstrate that migrate_to_user() is
263 # wired up correctly: it should not really contain much logic because
264 # it is meant only as a convenient wrapper around other functions.
265 # Those functions are unit tested individually, and the overall
266 # behaviour of migrate() is tested too; this is another layer of
267 # verification. It's also a reminder not to stuff logic into
268 # migrate_to_user(); extract it into functions instead and unit test
269 # those.
270
271 # migrate_to_user() will give all unowned files to a specified user.
272 get_unowned_files = self.patch(migration, "get_unowned_files")
273 get_unowned_files.return_value = [sentinel.file1, sentinel.file2]
274 give_file_to_user = self.patch(migration, "give_file_to_user")
275 # migrate_to_user() will copy all SSH keys and give all API
276 # credentials belonging to node owners over to a specified user.
277 get_owned_nodes_owners = self.patch(
278 migration, "get_owned_nodes_owners")
279 get_owned_nodes_owners.return_value = [
280 sentinel.node_owner1, sentinel.node_owner2]
281 copy_ssh_keys = self.patch(migration, "copy_ssh_keys")
282 give_api_credentials_to_user = self.patch(
283 migration, "give_api_credentials_to_user")
284 # migrate_to_user() will give all owned nodes to a specified user.
285 get_owned_nodes = self.patch(migration, "get_owned_nodes")
286 get_owned_nodes.return_value = [sentinel.node1, sentinel.node2]
287 give_node_to_user = self.patch(migration, "give_node_to_user")
288
289 migrate_to_user(sentinel.user)
290
291 # Each unowned file is given to the destination user one at a time.
292 get_unowned_files.assert_called_once()
293 self.assertEqual(
294 [call(sentinel.file1, sentinel.user),
295 call(sentinel.file2, sentinel.user)],
296 give_file_to_user.call_args_list)
297 # The SSH keys of each node owner are copied to the destination user,
298 # one at a time, and the credentials of these users are given to the
299 # destination user.
300 get_owned_nodes_owners.assert_called_once()
301 self.assertEqual(
302 [call(sentinel.node_owner1, sentinel.user),
303 call(sentinel.node_owner2, sentinel.user)],
304 copy_ssh_keys.call_args_list)
305 self.assertEqual(
306 [call(sentinel.node_owner1, sentinel.user),
307 call(sentinel.node_owner2, sentinel.user)],
308 give_api_credentials_to_user.call_args_list)
309 # Each owned node is given to the destination user one at a time.
310 get_owned_nodes.assert_called_once()
311 self.assertEqual(
312 [call(sentinel.node1, sentinel.user),
313 call(sentinel.node2, sentinel.user)],
314 give_node_to_user.call_args_list)
315
316
317class TestMigrate(TestCase):
318
319 def test_migrate_runs_when_no_files_exist(self):
320 migrate()
321
322 def test_migrate_runs_when_no_unowned_files_exist(self):
323 factory.make_file_storage(owner=factory.make_user())
324 migrate()
325
326 def test_migrate_all_files_to_single_user_when_only_one_user(self):
327 user = factory.make_user()
328 stored = factory.make_file_storage(owner=None)
329 migrate()
330 self.assertEqual(user, reload_object(stored).owner)
331
332 def test_migrate_all_files_to_new_legacy_user_when_multiple_users(self):
333 stored = factory.make_file_storage(owner=None)
334 user1 = factory.make_user()
335 user2 = factory.make_user()
336 migrate()
337 stored = reload_object(stored)
338 self.assertNotIn(stored.owner, {user1, user2, None})
339
340 def test_migrate_all_nodes_to_new_legacy_user_when_multiple_users(self):
341 factory.make_file_storage(owner=None)
342 user1 = factory.make_user()
343 node1 = factory.make_node(owner=user1)
344 user2 = factory.make_user()
345 node2 = factory.make_node(owner=user2)
346 migrate()
347 self.assertNotIn(reload_object(node1).owner, {user1, user2, None})
348 self.assertNotIn(reload_object(node2).owner, {user1, user2, None})
349
350 def test_migrate_all_nodes_to_bootstrap_owner_when_multiple_users(self):
351 user1 = factory.make_user()
352 node1 = factory.make_node(owner=user1)
353 user2 = factory.make_user()
354 node2 = factory.make_node(owner=user2)
355 make_provider_state_file(node1)
356 migrate()
357 self.assertEqual(
358 (user1, user1),
359 (reload_object(node1).owner,
360 reload_object(node2).owner))
361
362 def test_migrate_ancillary_data_to_legacy_user_when_multiple_users(self):
363 factory.make_file_storage(owner=None)
364 # Create two users, both with API credentials, an SSH key and a node.
365 user1 = factory.make_user()
366 consumer1, token1 = user1.get_profile().create_authorisation_token()
367 key1 = factory.make_sshkey(user1, get_ssh_key_string(1))
368 node1 = factory.make_node(owner=user1)
369 user2 = factory.make_user()
370 consumer2, token2 = user2.get_profile().create_authorisation_token()
371 key2 = factory.make_sshkey(user2, get_ssh_key_string(2))
372 node2 = factory.make_node(owner=user2)
373 migrate()
374 # The SSH keys have been copied to the legacy user.
375 legacy_user = get_legacy_user()
376 legacy_users_ssh_keys = get_ssh_keys(legacy_user)
377 self.assertSetEqual({key1.key, key2.key}, set(legacy_users_ssh_keys))
378 # The API credentials have been moved to the legacy user.
379 legacy_users_nodes = Node.objects.filter(owner=legacy_user)
380 self.assertSetEqual({node1, node2}, set(legacy_users_nodes))
381 self.assertEqual(
382 (legacy_user, legacy_user, legacy_user, legacy_user),
383 (reload_object(consumer1).user, reload_object(token1).user,
384 reload_object(consumer2).user, reload_object(token2).user))
0385
=== modified file 'src/maasserver/tests/data/test_rsa0.pub'
--- src/maasserver/tests/data/test_rsa0.pub 2012-04-06 09:52:45 +0000
+++ src/maasserver/tests/data/test_rsa0.pub 2013-03-06 17:48:23 +0000
@@ -1,1 +1,1 @@
1ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDdrzzDZNwyMVBvBTT6kBnrfPZv/AUbkxj7G5CaMTdw6xkKthV22EntD3lxaQxRKzQTfCc2d/CC1K4ushCcRs1S6SQ2zJ2jDq1UmOUkDMgvNh4JVhJYSKc6mu8i3s7oGSmBado5wvtlpSzMrscOpf8Qe/wmT5fH12KB9ipJqoFNQMVbVcVarE/v6wpn3GZC62YRb5iaz9/M+t92Qhu50W2u+KfouqtKB2lwIDDKZMww38ExtdMouh2FZpxaoh4Uey5bRp3tM3JgnWcX6fyUOp2gxJRPIlD9rrZhX5IkEkZM8MQbdPTQLgIf98oFph5RG6w1t02BvI9nJKM7KkKEfBHt ubuntu@server-74761ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDdrzzDZNwyMVBvBTT6kBnrfPZv/AUbkxj7G5CaMTdw6xkKthV22EntD3lxaQxRKzQTfCc2d/CC1K4ushCcRs1S6SQ2zJ2jDq1UmOUkDMgvNh4JVhJYSKc6mu8i3s7oGSmBado5wvtlpSzMrscOpf8Qe/wmT5fH12KB9ipJqoFNQMVbVcVarE/v6wpn3GZC62YRb5iaz9/M+t92Qhu50W2u+KfouqtKB2lwIDDKZMww38ExtdMouh2FZpxaoh4Uey5bRp3tM3JgnWcX6fyUOp2gxJRPIlD9rrZhX5IkEkZM8MQbdPTQLgIf98oFph5RG6w1t02BvI9nJKM7KkKEfBHt ubuntu@test_rsa0.pub
22
=== modified file 'src/maasserver/tests/data/test_rsa1.pub'
--- src/maasserver/tests/data/test_rsa1.pub 2012-04-06 09:52:45 +0000
+++ src/maasserver/tests/data/test_rsa1.pub 2013-03-06 17:48:23 +0000
@@ -1,1 +1,1 @@
1ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC6Gkj1y8/0T7q/FqBSr9xRBO9GzT+JeoWNXaqhUBg179Zd53XM4qblVwz/rsMa70te8CYNIFU+GbcNY1tNCo78NlHjQA8H98COnbVWKxvABECHrJ8nbYB4lWH9wI8/uvR0um6yUb/tZYbiSqnQxhoGAF/uQQfhqzc+tc7uTjnsa6krrNqQCdpFbAVVy+vZzvcJl6CX8nu5uJ8jedWfXOZJFcQPH+VwkUT0oV+1zVeLpE4LFkRO52JrC9Dy1xgrYM0EhcrShBdD1GQx9IXdW4Z9PIaVcq/y4Qv574yHMvi+6hwG6xpCtRXmy0lG0LiG60c1yOredkO6U0MJIVbeZ/+r ubuntu@server-74931ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC6Gkj1y8/0T7q/FqBSr9xRBO9GzT+JeoWNXaqhUBg179Zd53XM4qblVwz/rsMa70te8CYNIFU+GbcNY1tNCo78NlHjQA8H98COnbVWKxvABECHrJ8nbYB4lWH9wI8/uvR0um6yUb/tZYbiSqnQxhoGAF/uQQfhqzc+tc7uTjnsa6krrNqQCdpFbAVVy+vZzvcJl6CX8nu5uJ8jedWfXOZJFcQPH+VwkUT0oV+1zVeLpE4LFkRO52JrC9Dy1xgrYM0EhcrShBdD1GQx9IXdW4Z9PIaVcq/y4Qv574yHMvi+6hwG6xpCtRXmy0lG0LiG60c1yOredkO6U0MJIVbeZ/+r ubuntu@test_rsa1.pub
22
=== modified file 'src/maasserver/tests/data/test_rsa2.pub'
--- src/maasserver/tests/data/test_rsa2.pub 2012-04-06 09:52:45 +0000
+++ src/maasserver/tests/data/test_rsa2.pub 2013-03-06 17:48:23 +0000
@@ -1,1 +1,1 @@
1ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKVdMk4Q+13uUvXjb6iU+oB2Auk0HpaILZ8Pw/V63PTJ+QXtEp0vTe6DEvr9uF2vl6tF+AosiG4krEwqBNGx/h8MmFO7BgNTxn9eU2VwfHzmQ2nqkXHsXgp66cNT0Yd0nfvVV/fsMpKN9fUaYrXjAlFxvC9iQ33Rp6vj/X+oqDvYf3xZjbuZy+BxdJnmiTAJcFouTyrdy1Em1EZITq5M4EXw93/O2vAPYSFPAeELBE+mIMJxOCY1Fm101oAqO0qof3Rb2hZxc2WINjmqZIxoi+sviU0ny/dIFknhYEg1Xh2hObPn0nN5+4VHjBTdRmpRXqggotc53sYC5udVmFsW8B ubuntu@server-74931ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKVdMk4Q+13uUvXjb6iU+oB2Auk0HpaILZ8Pw/V63PTJ+QXtEp0vTe6DEvr9uF2vl6tF+AosiG4krEwqBNGx/h8MmFO7BgNTxn9eU2VwfHzmQ2nqkXHsXgp66cNT0Yd0nfvVV/fsMpKN9fUaYrXjAlFxvC9iQ33Rp6vj/X+oqDvYf3xZjbuZy+BxdJnmiTAJcFouTyrdy1Em1EZITq5M4EXw93/O2vAPYSFPAeELBE+mIMJxOCY1Fm101oAqO0qof3Rb2hZxc2WINjmqZIxoi+sviU0ny/dIFknhYEg1Xh2hObPn0nN5+4VHjBTdRmpRXqggotc53sYC5udVmFsW8B ubuntu@test_rsa2.pub
22
=== modified file 'src/maasserver/tests/data/test_rsa3.pub'
--- src/maasserver/tests/data/test_rsa3.pub 2012-04-06 09:52:45 +0000
+++ src/maasserver/tests/data/test_rsa3.pub 2013-03-06 17:48:23 +0000
@@ -1,1 +1,1 @@
1ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDai2ir5yxckoYTHUbFL6pe01Kx+Dy6nw9p7LhFaBixUOh8G7eIgFBguYcir2ZKBfM/lbTnW+MSiGF2VMlXX0+X9Ux2iwPSJa2wIA7Cc5prCz/RnMRKQ+2S1JJuORoi8tDI0p1R0sGWMXCwaj30oRN0THWz884+d3YlDD/O39h74gnLNEx/TQig/r/Aev3VfeKO6dlbbX81vSad2JVncislyMq1TgJdhn2/JI8t+LW0xVc6ZgQr94YB2M2DNjFSisP2vDrV5LWM+IqiF8T/YHkcSsANr8WWvZWa79uHyRBU3xr2qZZqMjMVL0B/NOJYXyGBIJ7HQnlVLmqFenKl8ZtL ubuntu@server-74931ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDai2ir5yxckoYTHUbFL6pe01Kx+Dy6nw9p7LhFaBixUOh8G7eIgFBguYcir2ZKBfM/lbTnW+MSiGF2VMlXX0+X9Ux2iwPSJa2wIA7Cc5prCz/RnMRKQ+2S1JJuORoi8tDI0p1R0sGWMXCwaj30oRN0THWz884+d3YlDD/O39h74gnLNEx/TQig/r/Aev3VfeKO6dlbbX81vSad2JVncislyMq1TgJdhn2/JI8t+LW0xVc6ZgQr94YB2M2DNjFSisP2vDrV5LWM+IqiF8T/YHkcSsANr8WWvZWa79uHyRBU3xr2qZZqMjMVL0B/NOJYXyGBIJ7HQnlVLmqFenKl8ZtL ubuntu@test_rsa3.pub
22
=== modified file 'src/maasserver/tests/data/test_rsa4.pub'
--- src/maasserver/tests/data/test_rsa4.pub 2012-04-06 09:52:45 +0000
+++ src/maasserver/tests/data/test_rsa4.pub 2013-03-06 17:48:23 +0000
@@ -1,1 +1,1 @@
1ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNDA4vXVTxHuKikIXeA6/K/X7hKpJcOJV0HcXUHlSNa9phNW0f8vbci+BxcLAqIz/U+BPiQ9lCxz7so+qCTFrM4poOdkTyup8VUxUqntiaxgiCJZ1of+eMe39+S9XQk6RogiCpExanhD9xPLkK/mLr5phnQwDjEDJwD4OOF0rYsbYoqje/0Pd+Tm0PIepq/qwsu5PAKPJU8dfnp8BWLCuIJ+DA2lfRUjmxWwLczfM/4hu1bZlYp1mzJJgMIOY92/pUToYxvBiIiKs3qWh6HC5Vxo5Vz4w5WLnTnIPDvpYBvWj8LGXJwHuhqlzed2icwPk8krip2BzwsHotru3UXtKf ubuntu@server-74931ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNDA4vXVTxHuKikIXeA6/K/X7hKpJcOJV0HcXUHlSNa9phNW0f8vbci+BxcLAqIz/U+BPiQ9lCxz7so+qCTFrM4poOdkTyup8VUxUqntiaxgiCJZ1of+eMe39+S9XQk6RogiCpExanhD9xPLkK/mLr5phnQwDjEDJwD4OOF0rYsbYoqje/0Pd+Tm0PIepq/qwsu5PAKPJU8dfnp8BWLCuIJ+DA2lfRUjmxWwLczfM/4hu1bZlYp1mzJJgMIOY92/pUToYxvBiIiKs3qWh6HC5Vxo5Vz4w5WLnTnIPDvpYBvWj8LGXJwHuhqlzed2icwPk8krip2BzwsHotru3UXtKf ubuntu@test_rsa4.pub
22
=== modified file 'src/maasserver/tests/test_sshkey.py'
--- src/maasserver/tests/test_sshkey.py 2012-05-29 10:24:47 +0000
+++ src/maasserver/tests/test_sshkey.py 2013-03-06 17:48:23 +0000
@@ -226,7 +226,7 @@
226 key = SSHKey(key=key_string, user=user)226 key = SSHKey(key=key_string, user=user)
227 display = key.display_html()227 display = key.display_html()
228 self.assertEqual(228 self.assertEqual(
229 'ssh-rsa AAAAB3NzaC1yc2E… ubuntu@server-7476', display)229 'ssh-rsa AAAAB3NzaC1yc… ubuntu@test_rsa0.pub', display)
230230
231 def test_sshkey_display_is_marked_as_HTML_safe(self):231 def test_sshkey_display_is_marked_as_HTML_safe(self):
232 key_string = get_data('data/test_rsa0.pub')232 key_string = get_data('data/test_rsa0.pub')

Subscribers

People subscribed via source and target branches

to status/vote changes: