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
1=== modified file 'src/maasserver/models/user.py'
2--- src/maasserver/models/user.py 2012-08-13 04:47:10 +0000
3+++ src/maasserver/models/user.py 2013-03-06 17:48:23 +0000
4@@ -15,6 +15,7 @@
5 'create_user',
6 'get_auth_tokens',
7 'get_creds_tuple',
8+ 'SYSTEM_USERS',
9 ]
10
11 from maasserver import worker_user
12
13=== added file 'src/maasserver/support/pertenant/migration.py'
14--- src/maasserver/support/pertenant/migration.py 1970-01-01 00:00:00 +0000
15+++ src/maasserver/support/pertenant/migration.py 2013-03-06 17:48:23 +0000
16@@ -0,0 +1,179 @@
17+# Copyright 2012 Canonical Ltd. This software is licensed under the
18+# GNU Affero General Public License version 3 (see the file LICENSE).
19+
20+"""Shared namespace --> per-tenant namespace migration.
21+
22+Perform the following steps to migrate:
23+
24+1. When no files exist (i.e. no Juju environments exist): do nothing
25+1a. When no *unowned* files exist: do nothing.
26+
27+2. When there's only one user: assign ownership of all files to user.
28+
29+3. When there are multiple users and a `provider-state` file: parse that file
30+ to extract the instance id of the bootstrap node. From that instance id,
31+ get the identity of the user who deployed this environment (that's the
32+ owner of the bootstrap node). Then proceed as in 4, using that user as the
33+ "legacy" user.
34+
35+4. When there are multiple users: create a new "legacy" user, assign ownership
36+ of all files and allocated/owned nodes to this user, copy all public SSH
37+ keys to this user, and move all API credentials to this user.
38+
39+There's not a lot we can do about SSH keys authorised to connect to the
40+already deployed nodes in #3, but this set will only ever decrease: nodes
41+allocated after this migration will permit access from any of the users with
42+SSH keys prior to the migration.
43+"""
44+
45+from __future__ import (
46+ absolute_import,
47+ print_function,
48+ unicode_literals,
49+ )
50+
51+__metaclass__ = type
52+__all__ = [
53+ "migrate",
54+ ]
55+
56+from django.contrib.auth.models import User
57+from maasserver.models import (
58+ FileStorage,
59+ Node,
60+ SSHKey,
61+ )
62+from maasserver.models.user import (
63+ get_auth_tokens,
64+ SYSTEM_USERS,
65+ )
66+from maasserver.support.pertenant.utils import get_bootstrap_node_owner
67+from maasserver.utils.orm import get_one
68+
69+
70+legacy_user_name = "shared-environment"
71+
72+
73+def get_legacy_user():
74+ """Return the legacy namespace user, creating it if need be."""
75+ try:
76+ legacy_user = User.objects.get(username=legacy_user_name)
77+ except User.DoesNotExist:
78+ # Create the legacy user with a local, probably non-working, email
79+ # address, and an unusable password.
80+ legacy_user = User.objects.create_user(
81+ email="%s@localhost" % legacy_user_name,
82+ username=legacy_user_name)
83+ legacy_user.first_name = "Shared"
84+ legacy_user.last_name = "Environment"
85+ legacy_user.is_active = True
86+ return legacy_user
87+
88+
89+def get_unowned_files():
90+ """Returns a `QuerySet` of unowned files."""
91+ return FileStorage.objects.filter(owner=None)
92+
93+
94+def get_real_users():
95+ """Returns a `QuerySet` of real. not system, users."""
96+ users = User.objects.exclude(username__in=SYSTEM_USERS)
97+ users = users.exclude(username=legacy_user_name)
98+ return users
99+
100+
101+def get_owned_nodes():
102+ """Returns a `QuerySet` of nodes owned by real users."""
103+ return Node.objects.filter(owner__in=get_real_users())
104+
105+
106+def get_owned_nodes_owners():
107+ """Returns a `QuerySet` of the owners of nodes owned by real users."""
108+ owner_ids = get_owned_nodes().values_list("owner", flat=True)
109+ return User.objects.filter(id__in=owner_ids.distinct())
110+
111+
112+def get_destination_user():
113+ """Return the user to which resources should be assigned."""
114+ real_users = get_real_users()
115+ if real_users.count() == 1:
116+ return get_one(real_users)
117+ else:
118+ bootstrap_user = get_bootstrap_node_owner()
119+ if bootstrap_user is None:
120+ return get_legacy_user()
121+ else:
122+ return bootstrap_user
123+
124+
125+def get_ssh_keys(user):
126+ """Return the SSH key strings belonging to the specified user."""
127+ return SSHKey.objects.filter(user=user).values_list("key", flat=True)
128+
129+
130+def copy_ssh_keys(user_from, user_dest):
131+ """Copies SSH keys from one user to another.
132+
133+ This is idempotent, and does not clobber the destination user's existing
134+ keys.
135+ """
136+ user_from_keys = get_ssh_keys(user_from)
137+ user_dest_keys = get_ssh_keys(user_dest)
138+ for key in set(user_from_keys).difference(user_dest_keys):
139+ ssh_key = SSHKey(user=user_dest, key=key)
140+ ssh_key.save()
141+
142+
143+def give_file_to_user(file, user):
144+ """Give a file to a user."""
145+ file.owner = user
146+ file.save()
147+
148+
149+def give_api_credentials_to_user(user_from, user_dest):
150+ """Gives one user's API credentials to another.
151+
152+ This ensures that users of the shared namespace environment continue to
153+ operate within the legacy shared namespace environment by default via the
154+ API (e.g. maas-cli and Juju).
155+ """
156+ for token in get_auth_tokens(user_from):
157+ consumer = token.consumer
158+ consumer.user = user_dest
159+ consumer.save()
160+ token.user = user_dest
161+ token.save()
162+
163+
164+def give_node_to_user(node, user):
165+ """Changes a node's ownership for the legacy shared environment."""
166+ node.owner = user
167+ node.save()
168+
169+
170+def migrate_to_user(user):
171+ """Migrate files and nodes to the specified user.
172+
173+ This also copies, to the destination user, the public SSH keys of any
174+ owned nodes' owners. This is so that those users who had allocated nodes
175+ (i.e. active users of a shared-namespace environment) can access newly
176+ created nodes in the legacy shared-namespace environment.
177+ """
178+ for unowned_file in get_unowned_files():
179+ give_file_to_user(unowned_file, user)
180+ for node_owner in get_owned_nodes_owners():
181+ copy_ssh_keys(node_owner, user)
182+ give_api_credentials_to_user(node_owner, user)
183+ for owned_node in get_owned_nodes():
184+ give_node_to_user(owned_node, user)
185+
186+
187+def migrate():
188+ """Migrate files to a per-tenant namespace."""
189+ if get_unowned_files().exists():
190+ # 2, 3, and 4
191+ user = get_destination_user()
192+ migrate_to_user(user)
193+ else:
194+ # 1 and 1a
195+ pass
196
197=== added file 'src/maasserver/support/pertenant/tests/test_migration.py'
198--- src/maasserver/support/pertenant/tests/test_migration.py 1970-01-01 00:00:00 +0000
199+++ src/maasserver/support/pertenant/tests/test_migration.py 2013-03-06 17:48:23 +0000
200@@ -0,0 +1,384 @@
201+# Copyright 2012 Canonical Ltd. This software is licensed under the
202+# GNU Affero General Public License version 3 (see the file LICENSE).
203+
204+"""Test `maasserver.support.pertenant.migration."""
205+
206+from __future__ import (
207+ absolute_import,
208+ print_function,
209+ unicode_literals,
210+ )
211+
212+__metaclass__ = type
213+__all__ = []
214+
215+from django.contrib.auth.models import User
216+from maasserver.models import (
217+ Node,
218+ SSHKey,
219+ )
220+from maasserver.support.pertenant import migration
221+from maasserver.support.pertenant.migration import (
222+ copy_ssh_keys,
223+ get_destination_user,
224+ get_legacy_user,
225+ get_owned_nodes,
226+ get_owned_nodes_owners,
227+ get_real_users,
228+ get_ssh_keys,
229+ get_unowned_files,
230+ give_api_credentials_to_user,
231+ give_file_to_user,
232+ give_node_to_user,
233+ legacy_user_name,
234+ migrate,
235+ migrate_to_user,
236+ )
237+from maasserver.support.pertenant.tests.test_utils import (
238+ make_provider_state_file,
239+ )
240+from maasserver.testing import (
241+ get_data,
242+ reload_object,
243+ )
244+from maasserver.testing.factory import factory
245+from maasserver.testing.testcase import TestCase
246+from mock import (
247+ call,
248+ sentinel,
249+ )
250+from testtools.matchers import MatchesStructure
251+
252+
253+def get_ssh_key_string(num=0):
254+ return get_data('data/test_rsa%d.pub' % num)
255+
256+
257+class TestFunctions(TestCase):
258+
259+ def find_legacy_user(self):
260+ return User.objects.filter(username=legacy_user_name)
261+
262+ def test_get_legacy_user_creates_user(self):
263+ self.assertEqual([], list(self.find_legacy_user()))
264+ legacy_user = get_legacy_user()
265+ self.assertEqual([legacy_user], list(self.find_legacy_user()))
266+ self.assertThat(
267+ legacy_user, MatchesStructure.byEquality(
268+ first_name="Shared", last_name="Environment",
269+ email=legacy_user_name + "@localhost", is_active=True))
270+
271+ def test_get_legacy_user_creates_user_only_once(self):
272+ legacy_user1 = get_legacy_user()
273+ self.assertEqual([legacy_user1], list(self.find_legacy_user()))
274+ legacy_user2 = get_legacy_user()
275+ self.assertEqual([legacy_user2], list(self.find_legacy_user()))
276+ self.assertEqual(legacy_user1, legacy_user2)
277+
278+ def test_get_unowned_files_no_files(self):
279+ self.assertEqual([], list(get_unowned_files()))
280+
281+ def test_get_unowned_files(self):
282+ user = factory.make_user()
283+ files = [
284+ factory.make_file_storage(owner=None),
285+ factory.make_file_storage(owner=user),
286+ factory.make_file_storage(owner=None),
287+ ]
288+ self.assertSetEqual(
289+ {files[0], files[2]},
290+ set(get_unowned_files()))
291+
292+ def test_get_real_users_no_users(self):
293+ get_legacy_user() # Ensure at least the legacy user exists.
294+ self.assertEqual([], list(get_real_users()))
295+
296+ def test_get_real_users(self):
297+ get_legacy_user() # Ensure at least the legacy user exists.
298+ users = [
299+ factory.make_user(),
300+ factory.make_user(),
301+ ]
302+ self.assertSetEqual(set(users), set(get_real_users()))
303+
304+ def test_get_owned_nodes_no_nodes(self):
305+ self.assertEqual([], list(get_owned_nodes()))
306+
307+ def test_get_owned_nodes_no_owned_nodes(self):
308+ factory.make_node()
309+ self.assertEqual([], list(get_owned_nodes()))
310+
311+ def test_get_owned_nodes_with_owned_nodes(self):
312+ nodes = {
313+ factory.make_node(owner=factory.make_user()),
314+ factory.make_node(owner=factory.make_user()),
315+ }
316+ self.assertSetEqual(nodes, set(get_owned_nodes()))
317+
318+ def test_get_owned_nodes_with_nodes_owned_by_system_users(self):
319+ factory.make_node(owner=get_legacy_user()),
320+ self.assertEqual([], list(get_owned_nodes()))
321+
322+ def test_get_owned_nodes_owners_no_users(self):
323+ self.assertEqual([], list(get_owned_nodes_owners()))
324+
325+ def test_get_owned_nodes_owners_no_nodes(self):
326+ factory.make_user()
327+ self.assertEqual([], list(get_owned_nodes_owners()))
328+
329+ def test_get_owned_nodes_owners_no_owned_nodes(self):
330+ factory.make_user()
331+ factory.make_node(owner=None)
332+ self.assertEqual([], list(get_owned_nodes_owners()))
333+
334+ def test_get_owned_nodes_owners(self):
335+ user1 = factory.make_user()
336+ user2 = factory.make_user()
337+ factory.make_user()
338+ factory.make_node(owner=user1)
339+ factory.make_node(owner=user2)
340+ factory.make_node(owner=None)
341+ self.assertSetEqual({user1, user2}, set(get_owned_nodes_owners()))
342+
343+ def test_get_destination_user_one_real_user(self):
344+ user = factory.make_user()
345+ self.assertEqual(user, get_destination_user())
346+
347+ def test_get_destination_user_two_real_users(self):
348+ factory.make_user()
349+ factory.make_user()
350+ self.assertEqual(get_legacy_user(), get_destination_user())
351+
352+ def test_get_destination_user_no_real_users(self):
353+ self.assertEqual(get_legacy_user(), get_destination_user())
354+
355+ def test_get_destination_user_with_user_from_juju_state(self):
356+ user1, user2 = factory.make_user(), factory.make_user()
357+ node = factory.make_node(owner=user1)
358+ make_provider_state_file(node)
359+ self.assertEqual(user1, get_destination_user())
360+
361+ def test_get_destination_user_with_orphaned_juju_state(self):
362+ user1, user2 = factory.make_user(), factory.make_user()
363+ node = factory.make_node(owner=user1)
364+ make_provider_state_file(node)
365+ node.delete() # Orphan the state.
366+ self.assertEqual(get_legacy_user(), get_destination_user())
367+
368+
369+class TestCopySSHKeys(TestCase):
370+ """Tests for copy_ssh_keys()."""
371+
372+ def test_noop_when_there_are_no_keys(self):
373+ user1 = factory.make_user()
374+ user2 = factory.make_user()
375+ copy_ssh_keys(user1, user2)
376+ ssh_keys = SSHKey.objects.filter(user__in={user1, user2})
377+ self.assertEqual([], list(ssh_keys))
378+
379+ def test_copy(self):
380+ user1 = factory.make_user()
381+ key1 = factory.make_sshkey(user1)
382+ user2 = factory.make_user()
383+ copy_ssh_keys(user1, user2)
384+ user2s_ssh_keys = SSHKey.objects.filter(user=user2)
385+ self.assertSetEqual(
386+ {key1.key}, {ssh_key.key for ssh_key in user2s_ssh_keys})
387+
388+ def test_copy_is_idempotent(self):
389+ # When the destination user already has a key, copy_ssh_keys() is a
390+ # noop for that key.
391+ user1 = factory.make_user()
392+ key1 = factory.make_sshkey(user1)
393+ user2 = factory.make_user()
394+ key2 = factory.make_sshkey(user2, key1.key)
395+ copy_ssh_keys(user1, user2)
396+ user2s_ssh_keys = SSHKey.objects.filter(user=user2)
397+ self.assertSetEqual(
398+ {key2.key}, {ssh_key.key for ssh_key in user2s_ssh_keys})
399+
400+ def test_copy_does_not_clobber(self):
401+ # When the destination user already has some keys, copy_ssh_keys()
402+ # adds to them; it does not remove them.
403+ user1 = factory.make_user()
404+ key1 = factory.make_sshkey(user1, get_ssh_key_string(1))
405+ user2 = factory.make_user()
406+ key2 = factory.make_sshkey(user2, get_ssh_key_string(2))
407+ copy_ssh_keys(user1, user2)
408+ user2s_ssh_keys = SSHKey.objects.filter(user=user2)
409+ self.assertSetEqual(
410+ {key1.key, key2.key},
411+ {ssh_key.key for ssh_key in user2s_ssh_keys})
412+
413+
414+class TestGiveFileToUser(TestCase):
415+
416+ def test_give_unowned_file(self):
417+ user = factory.make_user()
418+ file = factory.make_file_storage(owner=None)
419+ give_file_to_user(file, user)
420+ self.assertEqual(user, file.owner)
421+
422+ def test_give_owned_file(self):
423+ user1 = factory.make_user()
424+ user2 = factory.make_user()
425+ file = factory.make_file_storage(owner=user1)
426+ give_file_to_user(file, user2)
427+ self.assertEqual(user2, file.owner)
428+
429+ def test_file_saved(self):
430+ user = factory.make_user()
431+ file = factory.make_file_storage(owner=None)
432+ save = self.patch(file, "save")
433+ give_file_to_user(file, user)
434+ save.assert_called_once()
435+
436+
437+class TestGiveCredentialsToUser(TestCase):
438+
439+ def test_give(self):
440+ user1 = factory.make_user()
441+ user2 = factory.make_user()
442+ profile = user1.get_profile()
443+ consumer, token = profile.create_authorisation_token()
444+ give_api_credentials_to_user(user1, user2)
445+ self.assertEqual(user2, reload_object(consumer).user)
446+ self.assertEqual(user2, reload_object(token).user)
447+
448+
449+class TestGiveNodeToUser(TestCase):
450+
451+ def test_give(self):
452+ user1 = factory.make_user()
453+ user2 = factory.make_user()
454+ node = factory.make_node(owner=user1)
455+ give_node_to_user(node, user2)
456+ self.assertEqual(user2, reload_object(node).owner)
457+
458+
459+class TestMigrateToUser(TestCase):
460+
461+ def test_migrate(self):
462+ # This is a mechanical test, to demonstrate that migrate_to_user() is
463+ # wired up correctly: it should not really contain much logic because
464+ # it is meant only as a convenient wrapper around other functions.
465+ # Those functions are unit tested individually, and the overall
466+ # behaviour of migrate() is tested too; this is another layer of
467+ # verification. It's also a reminder not to stuff logic into
468+ # migrate_to_user(); extract it into functions instead and unit test
469+ # those.
470+
471+ # migrate_to_user() will give all unowned files to a specified user.
472+ get_unowned_files = self.patch(migration, "get_unowned_files")
473+ get_unowned_files.return_value = [sentinel.file1, sentinel.file2]
474+ give_file_to_user = self.patch(migration, "give_file_to_user")
475+ # migrate_to_user() will copy all SSH keys and give all API
476+ # credentials belonging to node owners over to a specified user.
477+ get_owned_nodes_owners = self.patch(
478+ migration, "get_owned_nodes_owners")
479+ get_owned_nodes_owners.return_value = [
480+ sentinel.node_owner1, sentinel.node_owner2]
481+ copy_ssh_keys = self.patch(migration, "copy_ssh_keys")
482+ give_api_credentials_to_user = self.patch(
483+ migration, "give_api_credentials_to_user")
484+ # migrate_to_user() will give all owned nodes to a specified user.
485+ get_owned_nodes = self.patch(migration, "get_owned_nodes")
486+ get_owned_nodes.return_value = [sentinel.node1, sentinel.node2]
487+ give_node_to_user = self.patch(migration, "give_node_to_user")
488+
489+ migrate_to_user(sentinel.user)
490+
491+ # Each unowned file is given to the destination user one at a time.
492+ get_unowned_files.assert_called_once()
493+ self.assertEqual(
494+ [call(sentinel.file1, sentinel.user),
495+ call(sentinel.file2, sentinel.user)],
496+ give_file_to_user.call_args_list)
497+ # The SSH keys of each node owner are copied to the destination user,
498+ # one at a time, and the credentials of these users are given to the
499+ # destination user.
500+ get_owned_nodes_owners.assert_called_once()
501+ self.assertEqual(
502+ [call(sentinel.node_owner1, sentinel.user),
503+ call(sentinel.node_owner2, sentinel.user)],
504+ copy_ssh_keys.call_args_list)
505+ self.assertEqual(
506+ [call(sentinel.node_owner1, sentinel.user),
507+ call(sentinel.node_owner2, sentinel.user)],
508+ give_api_credentials_to_user.call_args_list)
509+ # Each owned node is given to the destination user one at a time.
510+ get_owned_nodes.assert_called_once()
511+ self.assertEqual(
512+ [call(sentinel.node1, sentinel.user),
513+ call(sentinel.node2, sentinel.user)],
514+ give_node_to_user.call_args_list)
515+
516+
517+class TestMigrate(TestCase):
518+
519+ def test_migrate_runs_when_no_files_exist(self):
520+ migrate()
521+
522+ def test_migrate_runs_when_no_unowned_files_exist(self):
523+ factory.make_file_storage(owner=factory.make_user())
524+ migrate()
525+
526+ def test_migrate_all_files_to_single_user_when_only_one_user(self):
527+ user = factory.make_user()
528+ stored = factory.make_file_storage(owner=None)
529+ migrate()
530+ self.assertEqual(user, reload_object(stored).owner)
531+
532+ def test_migrate_all_files_to_new_legacy_user_when_multiple_users(self):
533+ stored = factory.make_file_storage(owner=None)
534+ user1 = factory.make_user()
535+ user2 = factory.make_user()
536+ migrate()
537+ stored = reload_object(stored)
538+ self.assertNotIn(stored.owner, {user1, user2, None})
539+
540+ def test_migrate_all_nodes_to_new_legacy_user_when_multiple_users(self):
541+ factory.make_file_storage(owner=None)
542+ user1 = factory.make_user()
543+ node1 = factory.make_node(owner=user1)
544+ user2 = factory.make_user()
545+ node2 = factory.make_node(owner=user2)
546+ migrate()
547+ self.assertNotIn(reload_object(node1).owner, {user1, user2, None})
548+ self.assertNotIn(reload_object(node2).owner, {user1, user2, None})
549+
550+ def test_migrate_all_nodes_to_bootstrap_owner_when_multiple_users(self):
551+ user1 = factory.make_user()
552+ node1 = factory.make_node(owner=user1)
553+ user2 = factory.make_user()
554+ node2 = factory.make_node(owner=user2)
555+ make_provider_state_file(node1)
556+ migrate()
557+ self.assertEqual(
558+ (user1, user1),
559+ (reload_object(node1).owner,
560+ reload_object(node2).owner))
561+
562+ def test_migrate_ancillary_data_to_legacy_user_when_multiple_users(self):
563+ factory.make_file_storage(owner=None)
564+ # Create two users, both with API credentials, an SSH key and a node.
565+ user1 = factory.make_user()
566+ consumer1, token1 = user1.get_profile().create_authorisation_token()
567+ key1 = factory.make_sshkey(user1, get_ssh_key_string(1))
568+ node1 = factory.make_node(owner=user1)
569+ user2 = factory.make_user()
570+ consumer2, token2 = user2.get_profile().create_authorisation_token()
571+ key2 = factory.make_sshkey(user2, get_ssh_key_string(2))
572+ node2 = factory.make_node(owner=user2)
573+ migrate()
574+ # The SSH keys have been copied to the legacy user.
575+ legacy_user = get_legacy_user()
576+ legacy_users_ssh_keys = get_ssh_keys(legacy_user)
577+ self.assertSetEqual({key1.key, key2.key}, set(legacy_users_ssh_keys))
578+ # The API credentials have been moved to the legacy user.
579+ legacy_users_nodes = Node.objects.filter(owner=legacy_user)
580+ self.assertSetEqual({node1, node2}, set(legacy_users_nodes))
581+ self.assertEqual(
582+ (legacy_user, legacy_user, legacy_user, legacy_user),
583+ (reload_object(consumer1).user, reload_object(token1).user,
584+ reload_object(consumer2).user, reload_object(token2).user))
585
586=== modified file 'src/maasserver/tests/data/test_rsa0.pub'
587--- src/maasserver/tests/data/test_rsa0.pub 2012-04-06 09:52:45 +0000
588+++ src/maasserver/tests/data/test_rsa0.pub 2013-03-06 17:48:23 +0000
589@@ -1,1 +1,1 @@
590-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDdrzzDZNwyMVBvBTT6kBnrfPZv/AUbkxj7G5CaMTdw6xkKthV22EntD3lxaQxRKzQTfCc2d/CC1K4ushCcRs1S6SQ2zJ2jDq1UmOUkDMgvNh4JVhJYSKc6mu8i3s7oGSmBado5wvtlpSzMrscOpf8Qe/wmT5fH12KB9ipJqoFNQMVbVcVarE/v6wpn3GZC62YRb5iaz9/M+t92Qhu50W2u+KfouqtKB2lwIDDKZMww38ExtdMouh2FZpxaoh4Uey5bRp3tM3JgnWcX6fyUOp2gxJRPIlD9rrZhX5IkEkZM8MQbdPTQLgIf98oFph5RG6w1t02BvI9nJKM7KkKEfBHt ubuntu@server-7476
591+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDdrzzDZNwyMVBvBTT6kBnrfPZv/AUbkxj7G5CaMTdw6xkKthV22EntD3lxaQxRKzQTfCc2d/CC1K4ushCcRs1S6SQ2zJ2jDq1UmOUkDMgvNh4JVhJYSKc6mu8i3s7oGSmBado5wvtlpSzMrscOpf8Qe/wmT5fH12KB9ipJqoFNQMVbVcVarE/v6wpn3GZC62YRb5iaz9/M+t92Qhu50W2u+KfouqtKB2lwIDDKZMww38ExtdMouh2FZpxaoh4Uey5bRp3tM3JgnWcX6fyUOp2gxJRPIlD9rrZhX5IkEkZM8MQbdPTQLgIf98oFph5RG6w1t02BvI9nJKM7KkKEfBHt ubuntu@test_rsa0.pub
592
593=== modified file 'src/maasserver/tests/data/test_rsa1.pub'
594--- src/maasserver/tests/data/test_rsa1.pub 2012-04-06 09:52:45 +0000
595+++ src/maasserver/tests/data/test_rsa1.pub 2013-03-06 17:48:23 +0000
596@@ -1,1 +1,1 @@
597-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC6Gkj1y8/0T7q/FqBSr9xRBO9GzT+JeoWNXaqhUBg179Zd53XM4qblVwz/rsMa70te8CYNIFU+GbcNY1tNCo78NlHjQA8H98COnbVWKxvABECHrJ8nbYB4lWH9wI8/uvR0um6yUb/tZYbiSqnQxhoGAF/uQQfhqzc+tc7uTjnsa6krrNqQCdpFbAVVy+vZzvcJl6CX8nu5uJ8jedWfXOZJFcQPH+VwkUT0oV+1zVeLpE4LFkRO52JrC9Dy1xgrYM0EhcrShBdD1GQx9IXdW4Z9PIaVcq/y4Qv574yHMvi+6hwG6xpCtRXmy0lG0LiG60c1yOredkO6U0MJIVbeZ/+r ubuntu@server-7493
598+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC6Gkj1y8/0T7q/FqBSr9xRBO9GzT+JeoWNXaqhUBg179Zd53XM4qblVwz/rsMa70te8CYNIFU+GbcNY1tNCo78NlHjQA8H98COnbVWKxvABECHrJ8nbYB4lWH9wI8/uvR0um6yUb/tZYbiSqnQxhoGAF/uQQfhqzc+tc7uTjnsa6krrNqQCdpFbAVVy+vZzvcJl6CX8nu5uJ8jedWfXOZJFcQPH+VwkUT0oV+1zVeLpE4LFkRO52JrC9Dy1xgrYM0EhcrShBdD1GQx9IXdW4Z9PIaVcq/y4Qv574yHMvi+6hwG6xpCtRXmy0lG0LiG60c1yOredkO6U0MJIVbeZ/+r ubuntu@test_rsa1.pub
599
600=== modified file 'src/maasserver/tests/data/test_rsa2.pub'
601--- src/maasserver/tests/data/test_rsa2.pub 2012-04-06 09:52:45 +0000
602+++ src/maasserver/tests/data/test_rsa2.pub 2013-03-06 17:48:23 +0000
603@@ -1,1 +1,1 @@
604-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKVdMk4Q+13uUvXjb6iU+oB2Auk0HpaILZ8Pw/V63PTJ+QXtEp0vTe6DEvr9uF2vl6tF+AosiG4krEwqBNGx/h8MmFO7BgNTxn9eU2VwfHzmQ2nqkXHsXgp66cNT0Yd0nfvVV/fsMpKN9fUaYrXjAlFxvC9iQ33Rp6vj/X+oqDvYf3xZjbuZy+BxdJnmiTAJcFouTyrdy1Em1EZITq5M4EXw93/O2vAPYSFPAeELBE+mIMJxOCY1Fm101oAqO0qof3Rb2hZxc2WINjmqZIxoi+sviU0ny/dIFknhYEg1Xh2hObPn0nN5+4VHjBTdRmpRXqggotc53sYC5udVmFsW8B ubuntu@server-7493
605+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKVdMk4Q+13uUvXjb6iU+oB2Auk0HpaILZ8Pw/V63PTJ+QXtEp0vTe6DEvr9uF2vl6tF+AosiG4krEwqBNGx/h8MmFO7BgNTxn9eU2VwfHzmQ2nqkXHsXgp66cNT0Yd0nfvVV/fsMpKN9fUaYrXjAlFxvC9iQ33Rp6vj/X+oqDvYf3xZjbuZy+BxdJnmiTAJcFouTyrdy1Em1EZITq5M4EXw93/O2vAPYSFPAeELBE+mIMJxOCY1Fm101oAqO0qof3Rb2hZxc2WINjmqZIxoi+sviU0ny/dIFknhYEg1Xh2hObPn0nN5+4VHjBTdRmpRXqggotc53sYC5udVmFsW8B ubuntu@test_rsa2.pub
606
607=== modified file 'src/maasserver/tests/data/test_rsa3.pub'
608--- src/maasserver/tests/data/test_rsa3.pub 2012-04-06 09:52:45 +0000
609+++ src/maasserver/tests/data/test_rsa3.pub 2013-03-06 17:48:23 +0000
610@@ -1,1 +1,1 @@
611-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDai2ir5yxckoYTHUbFL6pe01Kx+Dy6nw9p7LhFaBixUOh8G7eIgFBguYcir2ZKBfM/lbTnW+MSiGF2VMlXX0+X9Ux2iwPSJa2wIA7Cc5prCz/RnMRKQ+2S1JJuORoi8tDI0p1R0sGWMXCwaj30oRN0THWz884+d3YlDD/O39h74gnLNEx/TQig/r/Aev3VfeKO6dlbbX81vSad2JVncislyMq1TgJdhn2/JI8t+LW0xVc6ZgQr94YB2M2DNjFSisP2vDrV5LWM+IqiF8T/YHkcSsANr8WWvZWa79uHyRBU3xr2qZZqMjMVL0B/NOJYXyGBIJ7HQnlVLmqFenKl8ZtL ubuntu@server-7493
612+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDai2ir5yxckoYTHUbFL6pe01Kx+Dy6nw9p7LhFaBixUOh8G7eIgFBguYcir2ZKBfM/lbTnW+MSiGF2VMlXX0+X9Ux2iwPSJa2wIA7Cc5prCz/RnMRKQ+2S1JJuORoi8tDI0p1R0sGWMXCwaj30oRN0THWz884+d3YlDD/O39h74gnLNEx/TQig/r/Aev3VfeKO6dlbbX81vSad2JVncislyMq1TgJdhn2/JI8t+LW0xVc6ZgQr94YB2M2DNjFSisP2vDrV5LWM+IqiF8T/YHkcSsANr8WWvZWa79uHyRBU3xr2qZZqMjMVL0B/NOJYXyGBIJ7HQnlVLmqFenKl8ZtL ubuntu@test_rsa3.pub
613
614=== modified file 'src/maasserver/tests/data/test_rsa4.pub'
615--- src/maasserver/tests/data/test_rsa4.pub 2012-04-06 09:52:45 +0000
616+++ src/maasserver/tests/data/test_rsa4.pub 2013-03-06 17:48:23 +0000
617@@ -1,1 +1,1 @@
618-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNDA4vXVTxHuKikIXeA6/K/X7hKpJcOJV0HcXUHlSNa9phNW0f8vbci+BxcLAqIz/U+BPiQ9lCxz7so+qCTFrM4poOdkTyup8VUxUqntiaxgiCJZ1of+eMe39+S9XQk6RogiCpExanhD9xPLkK/mLr5phnQwDjEDJwD4OOF0rYsbYoqje/0Pd+Tm0PIepq/qwsu5PAKPJU8dfnp8BWLCuIJ+DA2lfRUjmxWwLczfM/4hu1bZlYp1mzJJgMIOY92/pUToYxvBiIiKs3qWh6HC5Vxo5Vz4w5WLnTnIPDvpYBvWj8LGXJwHuhqlzed2icwPk8krip2BzwsHotru3UXtKf ubuntu@server-7493
619+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNDA4vXVTxHuKikIXeA6/K/X7hKpJcOJV0HcXUHlSNa9phNW0f8vbci+BxcLAqIz/U+BPiQ9lCxz7so+qCTFrM4poOdkTyup8VUxUqntiaxgiCJZ1of+eMe39+S9XQk6RogiCpExanhD9xPLkK/mLr5phnQwDjEDJwD4OOF0rYsbYoqje/0Pd+Tm0PIepq/qwsu5PAKPJU8dfnp8BWLCuIJ+DA2lfRUjmxWwLczfM/4hu1bZlYp1mzJJgMIOY92/pUToYxvBiIiKs3qWh6HC5Vxo5Vz4w5WLnTnIPDvpYBvWj8LGXJwHuhqlzed2icwPk8krip2BzwsHotru3UXtKf ubuntu@test_rsa4.pub
620
621=== modified file 'src/maasserver/tests/test_sshkey.py'
622--- src/maasserver/tests/test_sshkey.py 2012-05-29 10:24:47 +0000
623+++ src/maasserver/tests/test_sshkey.py 2013-03-06 17:48:23 +0000
624@@ -226,7 +226,7 @@
625 key = SSHKey(key=key_string, user=user)
626 display = key.display_html()
627 self.assertEqual(
628- 'ssh-rsa AAAAB3NzaC1yc2E… ubuntu@server-7476', display)
629+ 'ssh-rsa AAAAB3NzaC1yc… ubuntu@test_rsa0.pub', display)
630
631 def test_sshkey_display_is_marked_as_HTML_safe(self):
632 key_string = get_data('data/test_rsa0.pub')

Subscribers

People subscribed via source and target branches

to status/vote changes: