← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~rvb/maas/per-tenant-provider-state into lp:maas

 

Raphaël Badin has proposed merging lp:~rvb/maas/per-tenant-provider-state into lp:maas.

Commit message:
Add utility to extract the owner of the bootstrap node.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~rvb/maas/per-tenant-provider-state/+merge/151542

This branch adds a tiny utility to parse the provider-state file and extract the owner of the bootstrap node.  This will be used by the branch Gavin is working on right now to migrate a shared-namespace instance to a per-tenant namespace (see details here: http://goo.gl/d5XiR).
-- 
https://code.launchpad.net/~rvb/maas/per-tenant-provider-state/+merge/151542
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~rvb/maas/per-tenant-provider-state into lp:maas.
=== added directory 'src/maasserver/support'
=== added file 'src/maasserver/support/__init__.py'
=== added directory 'src/maasserver/support/pertenant'
=== added file 'src/maasserver/support/pertenant/__init__.py'
=== added directory 'src/maasserver/support/pertenant/tests'
=== added file 'src/maasserver/support/pertenant/tests/__init__.py'
=== added file 'src/maasserver/support/pertenant/tests/test_utils.py'
--- src/maasserver/support/pertenant/tests/test_utils.py	1970-01-01 00:00:00 +0000
+++ src/maasserver/support/pertenant/tests/test_utils.py	2013-03-04 16:01:27 +0000
@@ -0,0 +1,66 @@
+# Copyright 2013 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Test the utilities of the per-tenant file storage work."""
+
+from __future__ import (
+    absolute_import,
+    print_function,
+    unicode_literals,
+    )
+
+__metaclass__ = type
+__all__ = []
+
+from maasserver.support.pertenant.utils import (
+    extract_bootstrap_node_system_id,
+    get_bootstrap_node_owner,
+    PROVIDER_STATE_FILENAME,
+    )
+from maasserver.testing.factory import factory
+from maasserver.testing.testcase import TestCase
+from maastesting.utils import sample_binary_data
+
+
+class TestExtractBootstrapNodeSystemId(TestCase):
+
+    def test_parses_valid_provider_state_file(self):
+        node = factory.make_node()
+        provider_state_file = factory.make_provider_state_file(node=node)
+        system_id = extract_bootstrap_node_system_id(
+            provider_state_file.content)
+        self.assertEqual(system_id, node.system_id)
+
+    def test_returns_None_if_parsing_fails(self):
+        invalid_contents = [
+            '%',  # invalid yaml
+            sample_binary_data,  # binary content (invalid yaml)
+            'invalid content',  # invalid provider-state content
+            'zookeeper-instances: []',  # no instances listed
+        ]
+        for invalid_content in invalid_contents:
+            self.assertIsNone(
+                extract_bootstrap_node_system_id(invalid_content))
+
+
+class TestGetBootstrapNodeOwner(TestCase):
+
+    def test_returns_None_if_no_provider_state_file(self):
+        self.assertIsNone(get_bootstrap_node_owner())
+
+    def test_returns_owner_if_node_found(self):
+        node = factory.make_node(owner=factory.make_user())
+        factory.make_provider_state_file(node=node)
+        self.assertEqual(node.owner, get_bootstrap_node_owner())
+
+    def test_returns_None_if_node_does_not_exist(self):
+        node = factory.make_node(owner=factory.make_user())
+        factory.make_provider_state_file(node=node)
+        node.delete()
+        self.assertIsNone(get_bootstrap_node_owner())
+
+    def test_returns_None_if_invalid_yaml(self):
+        invalid_content = '%'.encode('ascii')
+        factory.make_file_storage(
+            filename=PROVIDER_STATE_FILENAME, content=invalid_content)
+        self.assertIsNone(get_bootstrap_node_owner())

=== added file 'src/maasserver/support/pertenant/utils.py'
--- src/maasserver/support/pertenant/utils.py	1970-01-01 00:00:00 +0000
+++ src/maasserver/support/pertenant/utils.py	2013-03-04 16:01:27 +0000
@@ -0,0 +1,65 @@
+# Copyright 2013 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Utilities for the per-tenant file storage work."""
+
+from __future__ import (
+    absolute_import,
+    print_function,
+    unicode_literals,
+    )
+
+__metaclass__ = type
+__all__ = [
+    "get_bootstrap_node_owner",
+    ]
+
+
+from maasserver.models import (
+    FileStorage,
+    Node,
+    )
+import yaml
+
+
+PROVIDER_STATE_FILENAME = 'provider-state'
+
+
+def get_bootstrap_node_owner():
+    """Return the owner of the bootstrap node or None if it cannot be found.
+
+    This method uses the unowned 'provider-state' file to extract the system_id
+    of the bootstrap node.
+    """
+    try:
+        provider_file = FileStorage.objects.get(
+            filename=PROVIDER_STATE_FILENAME, owner=None)
+    except FileStorage.DoesNotExist:
+        return None
+    system_id = extract_bootstrap_node_system_id(provider_file.content)
+    if system_id is None:
+        return None
+    try:
+        return Node.objects.get(system_id=system_id).owner
+    except Node.DoesNotExist:
+        return None
+
+
+def extract_bootstrap_node_system_id(content):
+    """Extract the system_id of the node referenced in the given
+    provider-state file.
+
+    This method implements a very defensive strategy; if the given
+    content is not in yaml format or if the owner of the bootstrap
+    node cannot be found, it returns None.
+    """
+    try:
+        state = yaml.load(content)
+    except yaml.YAMLError:
+        return None
+    try:
+        parts = state['zookeeper-instances'][0].split('/')
+    except (IndexError, TypeError):
+        return None
+    system_id = [part for part in parts if part != ''][-1]
+    return system_id

=== modified file 'src/maasserver/testing/factory.py'
--- src/maasserver/testing/factory.py	2013-02-18 14:28:22 +0000
+++ src/maasserver/testing/factory.py	2013-03-04 16:01:27 +0000
@@ -1,4 +1,4 @@
-# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# Copyright 2013 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Test object factories."""
@@ -19,6 +19,7 @@
 import time
 
 from django.contrib.auth.models import User
+from django.core.urlresolvers import reverse
 from maasserver.enum import (
     ARCHITECTURE,
     NODE_STATUS,
@@ -37,6 +38,7 @@
     Tag,
     )
 from maasserver.models.node import NODE_TRANSITIONS
+from maasserver.support.pertenant.utils import PROVIDER_STATE_FILENAME
 from maasserver.testing import (
     get_data,
     reload_object,
@@ -334,6 +336,15 @@
         fake_file = self.make_file_upload(filename, content)
         return FileStorage.objects.save_file(fake_file.name, fake_file, owner)
 
+    def make_provider_state_file(self, node=None):
+        if node is None:
+            node = factory.make_node()
+        node_link = reverse('node_handler', args=[node.system_id])
+        content = 'zookeeper-instances: [%s]\n' % node_link
+        content_data = content.encode('ascii')
+        return self.make_file_storage(
+            filename=PROVIDER_STATE_FILENAME, content=content_data, owner=None)
+
     def make_oauth_header(self, **kwargs):
         """Fake an OAuth authorization header.