launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #15300
[Merge] lp:~allenap/maas/shared-to-per-tenant-storage into lp:maas
Gavin Panella has proposed merging lp:~allenap/maas/shared-to-per-tenant-storage into lp:maas.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~allenap/maas/shared-to-per-tenant-storage/+merge/151858
--
https://code.launchpad.net/~allenap/maas/shared-to-per-tenant-storage/+merge/151858
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~allenap/maas/shared-to-per-tenant-storage into lp:maas.
=== 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 11:51:21 +0000
@@ -15,6 +15,7 @@
'create_user',
'get_auth_tokens',
'get_creds_tuple',
+ 'SYSTEM_USERS',
]
from maasserver import worker_user
=== 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 11:51:21 +0000
@@ -0,0 +1,179 @@
+# Copyright 2012 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Shared namespace --> per-tenant namespace migration.
+
+Perform the following steps to migrate:
+
+1. When no files exist (i.e. no Juju environments exist): do nothing
+1a. When no *unowned* files exist: do nothing.
+
+2. When there's only one user: assign ownership of all files to user.
+
+3. When there are multiple users and a `provider-state` file: parse that file
+ to extract the instance id of the bootstrap node. From that instance id,
+ get the identity of the user who deployed this environment (that's the
+ owner of the bootstrap node). Then proceed as in 4, using that user as the
+ "legacy" user.
+
+4. When there are multiple users: create a new "legacy" user, assign ownership
+ of all files and allocated/owned nodes to this user, copy all public SSH
+ keys to this user, and move all API credentials to this user.
+
+There's not a lot we can do about SSH keys authorised to connect to the
+already deployed nodes in #3, but this set will only ever decrease: nodes
+allocated after this migration will permit access from any of the users with
+SSH keys prior to the migration.
+"""
+
+from __future__ import (
+ absolute_import,
+ print_function,
+ unicode_literals,
+ )
+
+__metaclass__ = type
+__all__ = [
+ "migrate",
+ ]
+
+from django.contrib.auth.models import User
+from maasserver.models import (
+ FileStorage,
+ Node,
+ SSHKey,
+ )
+from maasserver.models.user import (
+ get_auth_tokens,
+ SYSTEM_USERS,
+ )
+from maasserver.support.pertenant.utils import get_bootstrap_node_owner
+from maasserver.utils.orm import get_one
+
+
+legacy_user_name = "shared-environment"
+
+
+def get_legacy_user():
+ """Return the legacy namespace user, creating it if need be."""
+ try:
+ legacy_user = User.objects.get(username=legacy_user_name)
+ except User.DoesNotExist:
+ # Create the legacy user with a local, probably non-working, email
+ # address, and an unusable password.
+ legacy_user = User.objects.create_user(
+ email="%s@localhost" % legacy_user_name,
+ username=legacy_user_name)
+ legacy_user.first_name = "Shared"
+ legacy_user.last_name = "Environment"
+ legacy_user.is_active = True
+ return legacy_user
+
+
+def get_unowned_files():
+ """Returns a `QuerySet` of unowned files."""
+ return FileStorage.objects.filter(owner=None)
+
+
+def get_real_users():
+ """Returns a `QuerySet` of real. not system, users."""
+ users = User.objects.exclude(username__in=SYSTEM_USERS)
+ users = users.exclude(username=legacy_user_name)
+ return users
+
+
+def get_owned_nodes():
+ """Returns a `QuerySet` of nodes owned by real users."""
+ return Node.objects.filter(owner__in=get_real_users())
+
+
+def get_owned_nodes_owners():
+ """Returns a `QuerySet` of the owners of nodes owned by real users."""
+ owner_ids = get_owned_nodes().values_list("owner", flat=True)
+ return User.objects.filter(id__in=owner_ids.distinct())
+
+
+def get_destination_user():
+ """Return the user to which resources should be assigned."""
+ real_users = get_real_users()
+ if real_users.count() == 1:
+ return get_one(real_users)
+ else:
+ bootstrap_user = get_bootstrap_node_owner()
+ if bootstrap_user is None:
+ return get_legacy_user()
+ else:
+ return bootstrap_user
+
+
+def get_ssh_keys(user):
+ """Return the SSH key strings belonging to the specified user."""
+ return SSHKey.objects.filter(user=user).values_list("key", flat=True)
+
+
+def copy_ssh_keys(user_from, user_dest):
+ """Copies SSH keys from one user to another.
+
+ This is idempotent, and does not clobber the destination user's existing
+ keys.
+ """
+ user_from_keys = get_ssh_keys(user_from)
+ user_dest_keys = get_ssh_keys(user_dest)
+ for key in set(user_from_keys).difference(user_dest_keys):
+ ssh_key = SSHKey(user=user_dest, key=key)
+ ssh_key.save()
+
+
+def give_file_to_user(file, user):
+ """Give a file to a user."""
+ file.owner = user
+ file.save()
+
+
+def give_api_credentials_to_user(user_from, user_dest):
+ """Gives one user's API credentials to another.
+
+ This ensures that users of the shared namespace environment continue to
+ operate within the legacy shared namespace environment by default via the
+ API (e.g. maas-cli and Juju).
+ """
+ for token in get_auth_tokens(user_from):
+ consumer = token.consumer
+ consumer.user = user_dest
+ consumer.save()
+ token.user = user_dest
+ token.save()
+
+
+def give_node_to_user(node, user):
+ """Changes a node's ownership for the legacy shared environment."""
+ node.owner = user
+ node.save()
+
+
+def migrate_to_user(user):
+ """Migrate files and nodes to the specified user.
+
+ This also copies, to the destination user, the public SSH keys of any
+ owned nodes' owners. This is so that those users who had allocated nodes
+ (i.e. active users of a shared-namespace environment) can access newly
+ created nodes in the legacy shared-namespace environment.
+ """
+ for unowned_file in get_unowned_files():
+ give_file_to_user(unowned_file, user)
+ for node_owner in get_owned_nodes_owners():
+ copy_ssh_keys(node_owner, user)
+ give_api_credentials_to_user(node_owner, user)
+ for owned_node in get_owned_nodes():
+ give_node_to_user(owned_node, user)
+
+
+def migrate():
+ """Migrate files to a per-tenant namespace."""
+ if get_unowned_files().exists():
+ # 2, 3, and 4
+ user = get_destination_user()
+ migrate_to_user(user)
+ else:
+ # 1 and 1a
+ pass
=== 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 11:51:21 +0000
@@ -0,0 +1,384 @@
+# Copyright 2012 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Test `maasserver.support.pertenant.migration."""
+
+from __future__ import (
+ absolute_import,
+ print_function,
+ unicode_literals,
+ )
+
+__metaclass__ = type
+__all__ = []
+
+from django.contrib.auth.models import User
+from maasserver.models import (
+ Node,
+ SSHKey,
+ )
+from maasserver.support.pertenant import migration
+from maasserver.support.pertenant.migration import (
+ copy_ssh_keys,
+ get_destination_user,
+ get_legacy_user,
+ get_owned_nodes,
+ get_owned_nodes_owners,
+ get_real_users,
+ get_ssh_keys,
+ get_unowned_files,
+ give_api_credentials_to_user,
+ give_file_to_user,
+ give_node_to_user,
+ legacy_user_name,
+ migrate,
+ migrate_to_user,
+ )
+from maasserver.support.pertenant.tests.test_utils import (
+ make_provider_state_file,
+ )
+from maasserver.testing import (
+ get_data,
+ reload_object,
+ )
+from maasserver.testing.factory import factory
+from maasserver.testing.testcase import TestCase
+from mock import (
+ call,
+ sentinel,
+ )
+from testtools.matchers import MatchesStructure
+
+
+def get_ssh_key_string(num=0):
+ return get_data('data/test_rsa%d.pub' % num)
+
+
+class TestFunctions(TestCase):
+
+ def find_legacy_user(self):
+ return User.objects.filter(username=legacy_user_name)
+
+ def test_get_legacy_user_creates_user(self):
+ self.assertEqual([], list(self.find_legacy_user()))
+ legacy_user = get_legacy_user()
+ self.assertEqual([legacy_user], list(self.find_legacy_user()))
+ self.assertThat(
+ legacy_user, MatchesStructure.byEquality(
+ first_name="Shared", last_name="Environment",
+ email=legacy_user_name + "@localhost", is_active=True))
+
+ def test_get_legacy_user_creates_user_only_once(self):
+ legacy_user1 = get_legacy_user()
+ self.assertEqual([legacy_user1], list(self.find_legacy_user()))
+ legacy_user2 = get_legacy_user()
+ self.assertEqual([legacy_user2], list(self.find_legacy_user()))
+ self.assertEqual(legacy_user1, legacy_user2)
+
+ def test_get_unowned_files_no_files(self):
+ self.assertEqual([], list(get_unowned_files()))
+
+ def test_get_unowned_files(self):
+ user = factory.make_user()
+ files = [
+ factory.make_file_storage(owner=None),
+ factory.make_file_storage(owner=user),
+ factory.make_file_storage(owner=None),
+ ]
+ self.assertSetEqual(
+ {files[0], files[2]},
+ set(get_unowned_files()))
+
+ def test_get_real_users_no_users(self):
+ get_legacy_user() # Ensure at least the legacy user exists.
+ self.assertEqual([], list(get_real_users()))
+
+ def test_get_real_users(self):
+ get_legacy_user() # Ensure at least the legacy user exists.
+ users = [
+ factory.make_user(),
+ factory.make_user(),
+ ]
+ self.assertSetEqual(set(users), set(get_real_users()))
+
+ def test_get_owned_nodes_no_nodes(self):
+ self.assertEqual([], list(get_owned_nodes()))
+
+ def test_get_owned_nodes_no_owned_nodes(self):
+ factory.make_node()
+ self.assertEqual([], list(get_owned_nodes()))
+
+ def test_get_owned_nodes_with_owned_nodes(self):
+ nodes = {
+ factory.make_node(owner=factory.make_user()),
+ factory.make_node(owner=factory.make_user()),
+ }
+ self.assertSetEqual(nodes, set(get_owned_nodes()))
+
+ def test_get_owned_nodes_with_nodes_owned_by_system_users(self):
+ factory.make_node(owner=get_legacy_user()),
+ self.assertEqual([], list(get_owned_nodes()))
+
+ def test_get_owned_nodes_owners_no_users(self):
+ self.assertEqual([], list(get_owned_nodes_owners()))
+
+ def test_get_owned_nodes_owners_no_nodes(self):
+ factory.make_user()
+ self.assertEqual([], list(get_owned_nodes_owners()))
+
+ def test_get_owned_nodes_owners_no_owned_nodes(self):
+ factory.make_user()
+ factory.make_node(owner=None)
+ self.assertEqual([], list(get_owned_nodes_owners()))
+
+ def test_get_owned_nodes_owners(self):
+ user1 = factory.make_user()
+ user2 = factory.make_user()
+ factory.make_user()
+ factory.make_node(owner=user1)
+ factory.make_node(owner=user2)
+ factory.make_node(owner=None)
+ self.assertSetEqual({user1, user2}, set(get_owned_nodes_owners()))
+
+ def test_get_destination_user_one_real_user(self):
+ user = factory.make_user()
+ self.assertEqual(user, get_destination_user())
+
+ def test_get_destination_user_two_real_users(self):
+ factory.make_user()
+ factory.make_user()
+ self.assertEqual(get_legacy_user(), get_destination_user())
+
+ def test_get_destination_user_no_real_users(self):
+ self.assertEqual(get_legacy_user(), get_destination_user())
+
+ def test_get_destination_user_with_user_from_juju_state(self):
+ user1, user2 = factory.make_user(), factory.make_user()
+ node = factory.make_node(owner=user1)
+ make_provider_state_file(node)
+ self.assertEqual(user1, get_destination_user())
+
+ def test_get_destination_user_with_orphaned_juju_state(self):
+ user1, user2 = factory.make_user(), factory.make_user()
+ node = factory.make_node(owner=user1)
+ make_provider_state_file(node)
+ node.delete() # Orphan the state.
+ self.assertEqual(get_legacy_user(), get_destination_user())
+
+
+class TestCopySSHKeys(TestCase):
+ """Tests for copy_ssh_keys()."""
+
+ def test_noop_when_there_are_no_keys(self):
+ user1 = factory.make_user()
+ user2 = factory.make_user()
+ copy_ssh_keys(user1, user2)
+ ssh_keys = SSHKey.objects.filter(user__in={user1, user2})
+ self.assertEqual([], list(ssh_keys))
+
+ def test_copy(self):
+ user1 = factory.make_user()
+ key1 = factory.make_sshkey(user1)
+ user2 = factory.make_user()
+ copy_ssh_keys(user1, user2)
+ user2s_ssh_keys = SSHKey.objects.filter(user=user2)
+ self.assertSetEqual(
+ {key1.key}, {ssh_key.key for ssh_key in user2s_ssh_keys})
+
+ def test_copy_is_idempotent(self):
+ # When the destination user already has a key, copy_ssh_keys() is a
+ # noop for that key.
+ user1 = factory.make_user()
+ key1 = factory.make_sshkey(user1)
+ user2 = factory.make_user()
+ key2 = factory.make_sshkey(user2, key1.key)
+ copy_ssh_keys(user1, user2)
+ user2s_ssh_keys = SSHKey.objects.filter(user=user2)
+ self.assertSetEqual(
+ {key2.key}, {ssh_key.key for ssh_key in user2s_ssh_keys})
+
+ def test_copy_does_not_clobber(self):
+ # When the destination user already has some keys, copy_ssh_keys()
+ # adds to them; it does not remove them.
+ user1 = factory.make_user()
+ key1 = factory.make_sshkey(user1, get_ssh_key_string(1))
+ user2 = factory.make_user()
+ key2 = factory.make_sshkey(user2, get_ssh_key_string(2))
+ copy_ssh_keys(user1, user2)
+ user2s_ssh_keys = SSHKey.objects.filter(user=user2)
+ self.assertSetEqual(
+ {key1.key, key2.key},
+ {ssh_key.key for ssh_key in user2s_ssh_keys})
+
+
+class TestGiveFileToUser(TestCase):
+
+ def test_give_unowned_file(self):
+ user = factory.make_user()
+ file = factory.make_file_storage(owner=None)
+ give_file_to_user(file, user)
+ self.assertEqual(user, file.owner)
+
+ def test_give_owned_file(self):
+ user1 = factory.make_user()
+ user2 = factory.make_user()
+ file = factory.make_file_storage(owner=user1)
+ give_file_to_user(file, user2)
+ self.assertEqual(user2, file.owner)
+
+ def test_file_saved(self):
+ user = factory.make_user()
+ file = factory.make_file_storage(owner=None)
+ save = self.patch(file, "save")
+ give_file_to_user(file, user)
+ save.assert_called_once()
+
+
+class TestGiveCredentialsToUser(TestCase):
+
+ def test_give(self):
+ user1 = factory.make_user()
+ user2 = factory.make_user()
+ profile = user1.get_profile()
+ consumer, token = profile.create_authorisation_token()
+ give_api_credentials_to_user(user1, user2)
+ self.assertEqual(user2, reload_object(consumer).user)
+ self.assertEqual(user2, reload_object(token).user)
+
+
+class TestGiveNodeToUser(TestCase):
+
+ def test_give(self):
+ user1 = factory.make_user()
+ user2 = factory.make_user()
+ node = factory.make_node(owner=user1)
+ give_node_to_user(node, user2)
+ self.assertEqual(user2, reload_object(node).owner)
+
+
+class TestMigrateToUser(TestCase):
+
+ def test_migrate(self):
+ # This is a mechanical test, to demonstrate that migrate_to_user() is
+ # wired up correctly: it should not really contain much logic because
+ # it is meant only as a convenient wrapper around other functions.
+ # Those functions are unit tested individually, and the overall
+ # behaviour of migrate() is tested too; this is another layer of
+ # verification. It's also a reminder not to stuff logic into
+ # migrate_to_user(); extract it into functions instead and unit test
+ # those.
+
+ # migrate_to_user() will give all unowned files to a specified user.
+ get_unowned_files = self.patch(migration, "get_unowned_files")
+ get_unowned_files.return_value = [sentinel.file1, sentinel.file2]
+ give_file_to_user = self.patch(migration, "give_file_to_user")
+ # migrate_to_user() will copy all SSH keys and give all API
+ # credentials belonging to node owners over to a specified user.
+ get_owned_nodes_owners = self.patch(
+ migration, "get_owned_nodes_owners")
+ get_owned_nodes_owners.return_value = [
+ sentinel.node_owner1, sentinel.node_owner2]
+ copy_ssh_keys = self.patch(migration, "copy_ssh_keys")
+ give_api_credentials_to_user = self.patch(
+ migration, "give_api_credentials_to_user")
+ # migrate_to_user() will give all owned nodes to a specified user.
+ get_owned_nodes = self.patch(migration, "get_owned_nodes")
+ get_owned_nodes.return_value = [sentinel.node1, sentinel.node2]
+ give_node_to_user = self.patch(migration, "give_node_to_user")
+
+ migrate_to_user(sentinel.user)
+
+ # Each unowned file is given to the destination user one at a time.
+ get_unowned_files.assert_called_once()
+ self.assertEqual(
+ [call(sentinel.file1, sentinel.user),
+ call(sentinel.file2, sentinel.user)],
+ give_file_to_user.call_args_list)
+ # The SSH keys of each node owner are copied to the destination user,
+ # one at a time, and the credentials of these users are given to the
+ # destination user.
+ get_owned_nodes_owners.assert_called_once()
+ self.assertEqual(
+ [call(sentinel.node_owner1, sentinel.user),
+ call(sentinel.node_owner2, sentinel.user)],
+ copy_ssh_keys.call_args_list)
+ self.assertEqual(
+ [call(sentinel.node_owner1, sentinel.user),
+ call(sentinel.node_owner2, sentinel.user)],
+ give_api_credentials_to_user.call_args_list)
+ # Each owned node is given to the destination user one at a time.
+ get_owned_nodes.assert_called_once()
+ self.assertEqual(
+ [call(sentinel.node1, sentinel.user),
+ call(sentinel.node2, sentinel.user)],
+ give_node_to_user.call_args_list)
+
+
+class TestMigrate(TestCase):
+
+ def test_migrate_runs_when_no_files_exist(self):
+ migrate()
+
+ def test_migrate_runs_when_no_unowned_files_exist(self):
+ factory.make_file_storage(owner=factory.make_user())
+ migrate()
+
+ def test_migrate_all_files_to_single_user_when_only_one_user(self):
+ user = factory.make_user()
+ stored = factory.make_file_storage(owner=None)
+ migrate()
+ self.assertEqual(user, reload_object(stored).owner)
+
+ def test_migrate_all_files_to_new_legacy_user_when_multiple_users(self):
+ stored = factory.make_file_storage(owner=None)
+ user1 = factory.make_user()
+ user2 = factory.make_user()
+ migrate()
+ stored = reload_object(stored)
+ self.assertNotIn(stored.owner, {user1, user2, None})
+
+ def test_migrate_all_nodes_to_new_legacy_user_when_multiple_users(self):
+ factory.make_file_storage(owner=None)
+ user1 = factory.make_user()
+ node1 = factory.make_node(owner=user1)
+ user2 = factory.make_user()
+ node2 = factory.make_node(owner=user2)
+ migrate()
+ self.assertNotIn(reload_object(node1).owner, {user1, user2, None})
+ self.assertNotIn(reload_object(node2).owner, {user1, user2, None})
+
+ def test_migrate_all_nodes_to_bootstrap_owner_when_multiple_users(self):
+ user1 = factory.make_user()
+ node1 = factory.make_node(owner=user1)
+ user2 = factory.make_user()
+ node2 = factory.make_node(owner=user2)
+ make_provider_state_file(node1)
+ migrate()
+ self.assertEqual(
+ (user1, user1),
+ (reload_object(node1).owner,
+ reload_object(node2).owner))
+
+ def test_migrate_ancillary_data_to_legacy_user_when_multiple_users(self):
+ factory.make_file_storage(owner=None)
+ # Create two users, both with API credentials, an SSH key and a node.
+ user1 = factory.make_user()
+ consumer1, token1 = user1.get_profile().create_authorisation_token()
+ key1 = factory.make_sshkey(user1, get_ssh_key_string(1))
+ node1 = factory.make_node(owner=user1)
+ user2 = factory.make_user()
+ consumer2, token2 = user2.get_profile().create_authorisation_token()
+ key2 = factory.make_sshkey(user2, get_ssh_key_string(2))
+ node2 = factory.make_node(owner=user2)
+ migrate()
+ # The SSH keys have been copied to the legacy user.
+ legacy_user = get_legacy_user()
+ legacy_users_ssh_keys = get_ssh_keys(legacy_user)
+ self.assertSetEqual({key1.key, key2.key}, set(legacy_users_ssh_keys))
+ # The API credentials have been moved to the legacy user.
+ legacy_users_nodes = Node.objects.filter(owner=legacy_user)
+ self.assertSetEqual({node1, node2}, set(legacy_users_nodes))
+ self.assertEqual(
+ (legacy_user, legacy_user, legacy_user, legacy_user),
+ (reload_object(consumer1).user, reload_object(token1).user,
+ reload_object(consumer2).user, reload_object(token2).user))
=== 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 11:51:21 +0000
@@ -1,1 +1,1 @@
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDdrzzDZNwyMVBvBTT6kBnrfPZv/AUbkxj7G5CaMTdw6xkKthV22EntD3lxaQxRKzQTfCc2d/CC1K4ushCcRs1S6SQ2zJ2jDq1UmOUkDMgvNh4JVhJYSKc6mu8i3s7oGSmBado5wvtlpSzMrscOpf8Qe/wmT5fH12KB9ipJqoFNQMVbVcVarE/v6wpn3GZC62YRb5iaz9/M+t92Qhu50W2u+KfouqtKB2lwIDDKZMww38ExtdMouh2FZpxaoh4Uey5bRp3tM3JgnWcX6fyUOp2gxJRPIlD9rrZhX5IkEkZM8MQbdPTQLgIf98oFph5RG6w1t02BvI9nJKM7KkKEfBHt ubuntu@server-7476
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDdrzzDZNwyMVBvBTT6kBnrfPZv/AUbkxj7G5CaMTdw6xkKthV22EntD3lxaQxRKzQTfCc2d/CC1K4ushCcRs1S6SQ2zJ2jDq1UmOUkDMgvNh4JVhJYSKc6mu8i3s7oGSmBado5wvtlpSzMrscOpf8Qe/wmT5fH12KB9ipJqoFNQMVbVcVarE/v6wpn3GZC62YRb5iaz9/M+t92Qhu50W2u+KfouqtKB2lwIDDKZMww38ExtdMouh2FZpxaoh4Uey5bRp3tM3JgnWcX6fyUOp2gxJRPIlD9rrZhX5IkEkZM8MQbdPTQLgIf98oFph5RG6w1t02BvI9nJKM7KkKEfBHt ubuntu@test_rsa0.pub
=== 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 11:51:21 +0000
@@ -1,1 +1,1 @@
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC6Gkj1y8/0T7q/FqBSr9xRBO9GzT+JeoWNXaqhUBg179Zd53XM4qblVwz/rsMa70te8CYNIFU+GbcNY1tNCo78NlHjQA8H98COnbVWKxvABECHrJ8nbYB4lWH9wI8/uvR0um6yUb/tZYbiSqnQxhoGAF/uQQfhqzc+tc7uTjnsa6krrNqQCdpFbAVVy+vZzvcJl6CX8nu5uJ8jedWfXOZJFcQPH+VwkUT0oV+1zVeLpE4LFkRO52JrC9Dy1xgrYM0EhcrShBdD1GQx9IXdW4Z9PIaVcq/y4Qv574yHMvi+6hwG6xpCtRXmy0lG0LiG60c1yOredkO6U0MJIVbeZ/+r ubuntu@server-7493
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC6Gkj1y8/0T7q/FqBSr9xRBO9GzT+JeoWNXaqhUBg179Zd53XM4qblVwz/rsMa70te8CYNIFU+GbcNY1tNCo78NlHjQA8H98COnbVWKxvABECHrJ8nbYB4lWH9wI8/uvR0um6yUb/tZYbiSqnQxhoGAF/uQQfhqzc+tc7uTjnsa6krrNqQCdpFbAVVy+vZzvcJl6CX8nu5uJ8jedWfXOZJFcQPH+VwkUT0oV+1zVeLpE4LFkRO52JrC9Dy1xgrYM0EhcrShBdD1GQx9IXdW4Z9PIaVcq/y4Qv574yHMvi+6hwG6xpCtRXmy0lG0LiG60c1yOredkO6U0MJIVbeZ/+r ubuntu@test_rsa1.pub
=== 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 11:51:21 +0000
@@ -1,1 +1,1 @@
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKVdMk4Q+13uUvXjb6iU+oB2Auk0HpaILZ8Pw/V63PTJ+QXtEp0vTe6DEvr9uF2vl6tF+AosiG4krEwqBNGx/h8MmFO7BgNTxn9eU2VwfHzmQ2nqkXHsXgp66cNT0Yd0nfvVV/fsMpKN9fUaYrXjAlFxvC9iQ33Rp6vj/X+oqDvYf3xZjbuZy+BxdJnmiTAJcFouTyrdy1Em1EZITq5M4EXw93/O2vAPYSFPAeELBE+mIMJxOCY1Fm101oAqO0qof3Rb2hZxc2WINjmqZIxoi+sviU0ny/dIFknhYEg1Xh2hObPn0nN5+4VHjBTdRmpRXqggotc53sYC5udVmFsW8B ubuntu@server-7493
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKVdMk4Q+13uUvXjb6iU+oB2Auk0HpaILZ8Pw/V63PTJ+QXtEp0vTe6DEvr9uF2vl6tF+AosiG4krEwqBNGx/h8MmFO7BgNTxn9eU2VwfHzmQ2nqkXHsXgp66cNT0Yd0nfvVV/fsMpKN9fUaYrXjAlFxvC9iQ33Rp6vj/X+oqDvYf3xZjbuZy+BxdJnmiTAJcFouTyrdy1Em1EZITq5M4EXw93/O2vAPYSFPAeELBE+mIMJxOCY1Fm101oAqO0qof3Rb2hZxc2WINjmqZIxoi+sviU0ny/dIFknhYEg1Xh2hObPn0nN5+4VHjBTdRmpRXqggotc53sYC5udVmFsW8B ubuntu@test_rsa2.pub
=== 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 11:51:21 +0000
@@ -1,1 +1,1 @@
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDai2ir5yxckoYTHUbFL6pe01Kx+Dy6nw9p7LhFaBixUOh8G7eIgFBguYcir2ZKBfM/lbTnW+MSiGF2VMlXX0+X9Ux2iwPSJa2wIA7Cc5prCz/RnMRKQ+2S1JJuORoi8tDI0p1R0sGWMXCwaj30oRN0THWz884+d3YlDD/O39h74gnLNEx/TQig/r/Aev3VfeKO6dlbbX81vSad2JVncislyMq1TgJdhn2/JI8t+LW0xVc6ZgQr94YB2M2DNjFSisP2vDrV5LWM+IqiF8T/YHkcSsANr8WWvZWa79uHyRBU3xr2qZZqMjMVL0B/NOJYXyGBIJ7HQnlVLmqFenKl8ZtL ubuntu@server-7493
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDai2ir5yxckoYTHUbFL6pe01Kx+Dy6nw9p7LhFaBixUOh8G7eIgFBguYcir2ZKBfM/lbTnW+MSiGF2VMlXX0+X9Ux2iwPSJa2wIA7Cc5prCz/RnMRKQ+2S1JJuORoi8tDI0p1R0sGWMXCwaj30oRN0THWz884+d3YlDD/O39h74gnLNEx/TQig/r/Aev3VfeKO6dlbbX81vSad2JVncislyMq1TgJdhn2/JI8t+LW0xVc6ZgQr94YB2M2DNjFSisP2vDrV5LWM+IqiF8T/YHkcSsANr8WWvZWa79uHyRBU3xr2qZZqMjMVL0B/NOJYXyGBIJ7HQnlVLmqFenKl8ZtL ubuntu@test_rsa3.pub
=== 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 11:51:21 +0000
@@ -1,1 +1,1 @@
-ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNDA4vXVTxHuKikIXeA6/K/X7hKpJcOJV0HcXUHlSNa9phNW0f8vbci+BxcLAqIz/U+BPiQ9lCxz7so+qCTFrM4poOdkTyup8VUxUqntiaxgiCJZ1of+eMe39+S9XQk6RogiCpExanhD9xPLkK/mLr5phnQwDjEDJwD4OOF0rYsbYoqje/0Pd+Tm0PIepq/qwsu5PAKPJU8dfnp8BWLCuIJ+DA2lfRUjmxWwLczfM/4hu1bZlYp1mzJJgMIOY92/pUToYxvBiIiKs3qWh6HC5Vxo5Vz4w5WLnTnIPDvpYBvWj8LGXJwHuhqlzed2icwPk8krip2BzwsHotru3UXtKf ubuntu@server-7493
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNDA4vXVTxHuKikIXeA6/K/X7hKpJcOJV0HcXUHlSNa9phNW0f8vbci+BxcLAqIz/U+BPiQ9lCxz7so+qCTFrM4poOdkTyup8VUxUqntiaxgiCJZ1of+eMe39+S9XQk6RogiCpExanhD9xPLkK/mLr5phnQwDjEDJwD4OOF0rYsbYoqje/0Pd+Tm0PIepq/qwsu5PAKPJU8dfnp8BWLCuIJ+DA2lfRUjmxWwLczfM/4hu1bZlYp1mzJJgMIOY92/pUToYxvBiIiKs3qWh6HC5Vxo5Vz4w5WLnTnIPDvpYBvWj8LGXJwHuhqlzed2icwPk8krip2BzwsHotru3UXtKf ubuntu@test_rsa4.pub
References