← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~mwhudson/launchpad/feature-flag-xmlrpc into lp:launchpad

 

Michael Hudson-Doyle has proposed merging lp:~mwhudson/launchpad/feature-flag-xmlrpc into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~mwhudson/launchpad/feature-flag-xmlrpc/+merge/75673

This branch adds a private XML-RPC interface to query feature flags.  It supports the team: scope (if you pass a username) and arbitrary other scopes.

It's been a while, so I've probably forgotten some coding standards things.  I did remember to run "make lint" though :)
-- 
https://code.launchpad.net/~mwhudson/launchpad/feature-flag-xmlrpc/+merge/75673
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~mwhudson/launchpad/feature-flag-xmlrpc into lp:launchpad.
=== modified file 'lib/canonical/launchpad/interfaces/launchpad.py'
--- lib/canonical/launchpad/interfaces/launchpad.py	2011-05-27 19:53:20 +0000
+++ lib/canonical/launchpad/interfaces/launchpad.py	2011-09-19 00:33:20 +0000
@@ -152,6 +152,8 @@
     softwarecenteragent = Attribute(
         """Software center agent XML-RPC end point.""")
 
+    featureflags = Attribute("""Feature flag information endpoint""")
+
 
 class IAuthServerApplication(ILaunchpadApplication):
     """Launchpad legacy AuthServer application root."""

=== modified file 'lib/canonical/launchpad/xmlrpc/application.py'
--- lib/canonical/launchpad/xmlrpc/application.py	2011-04-28 21:36:47 +0000
+++ lib/canonical/launchpad/xmlrpc/application.py	2011-09-19 00:33:20 +0000
@@ -36,6 +36,7 @@
     )
 from lp.registry.interfaces.mailinglist import IMailingListApplication
 from lp.registry.interfaces.person import ISoftwareCenterAgentApplication
+from lp.services.features.xmlrpc import IFeatureFlagApplication
 
 
 # NOTE: If you add a traversal here, you should update
@@ -73,6 +74,11 @@
         """See `IPrivateApplication`."""
         return getUtility(ISoftwareCenterAgentApplication)
 
+    @property
+    def featureflags(self):
+        """See `IPrivateApplication`."""
+        return getUtility(IFeatureFlagApplication)
+
 
 class ISelfTest(Interface):
     """XMLRPC external interface for testing the XMLRPC external interface."""

=== modified file 'lib/lp/services/features/configure.zcml'
--- lib/lp/services/features/configure.zcml	2010-08-12 06:55:37 +0000
+++ lib/lp/services/features/configure.zcml	2011-09-19 00:33:20 +0000
@@ -19,4 +19,11 @@
         handler="lp.services.features.webapp.end_request"
         />
 
+    <securedutility
+        class="lp.services.features.xmlrpc.FeatureFlagApplication"
+        provides="lp.services.features.xmlrpc.IFeatureFlagApplication">
+        <allow
+            interface="lp.services.features.xmlrpc.IFeatureFlagApplication"/>
+    </securedutility>
+
 </configure>

=== modified file 'lib/lp/services/features/scopes.py'
--- lib/lp/services/features/scopes.py	2011-08-02 23:42:08 +0000
+++ lib/lp/services/features/scopes.py	2011-09-19 00:33:20 +0000
@@ -9,7 +9,9 @@
 """
 
 __all__ = [
+    'DefaultScope',
     'HANDLERS',
+    'MultiScopeHandler',
     'ScopesForScript',
     'ScopesFromRequest',
     'undocumented_scopes',

=== added file 'lib/lp/services/features/tests/test_xmlrpc.py'
--- lib/lp/services/features/tests/test_xmlrpc.py	1970-01-01 00:00:00 +0000
+++ lib/lp/services/features/tests/test_xmlrpc.py	2011-09-19 00:33:20 +0000
@@ -0,0 +1,103 @@
+# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests for FeatureFlagApplication."""
+
+__metaclass__ = type
+
+import xmlrpclib
+
+from canonical.testing.layers import DatabaseFunctionalLayer
+from lp.services import features
+from lp.services.features.flags import FeatureController
+from lp.services.features.rulesource import StormFeatureRuleSource
+from lp.services.features.scopes import (
+    BaseScope,
+    DefaultScope,
+    MultiScopeHandler,
+    )
+from lp.services.features.xmlrpc import FeatureFlagApplication
+from lp.testing import (
+    feature_flags,
+    set_feature_flag,
+    TestCaseWithFactory,
+    )
+from lp.testing.xmlrpc import XMLRPCTestTransport
+
+
+class FixedScope(BaseScope):
+    pattern = r'fixed$'
+
+    def lookup(self, scope_name):
+        return True
+
+
+class TestGetFeatureFlag(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        TestCaseWithFactory.setUp(self)
+        self.endpoint = FeatureFlagApplication()
+
+    def installFeatureController(self, feature_controller):
+        old_features = features.get_relevant_feature_controller()
+        features.install_feature_controller(feature_controller)
+        self.addCleanup(
+            features.install_feature_controller, old_features)
+
+    def test_getFeatureFlag_returns_None_by_default(self):
+        self.assertIs(None, self.endpoint.getFeatureFlag(u'unknown'))
+
+    def test_getFeatureFlag_returns_true_for_set_flag(self):
+        flag_name = u'flag'
+        with feature_flags():
+            set_feature_flag(flag_name, u'1')
+            self.assertEqual(u'1', self.endpoint.getFeatureFlag(flag_name))
+
+    def test_getFeatureFlag_ignores_relevant_feature_controller(self):
+        # getFeatureFlag should only consider the scopes it is asked to
+        # consider, not any that happen to be active due to the XML-RPC
+        # request itself.
+        self.installFeatureController(
+            FeatureController(
+                MultiScopeHandler([DefaultScope(), FixedScope()]).lookup,
+                StormFeatureRuleSource()))
+        flag_name = u'flag'
+        set_feature_flag(flag_name, u'1', u'fixed')
+        self.assertEqual(None, self.endpoint.getFeatureFlag(flag_name))
+
+    def test_getFeatureFlag_considers_supplied_scope(self):
+        flag_name = u'flag'
+        scope_name = u'scope'
+        with feature_flags():
+            set_feature_flag(flag_name, u'value', scope_name)
+            self.assertEqual(
+                u'value',
+                self.endpoint.getFeatureFlag(flag_name, scopes=[scope_name]))
+
+    def test_getFeatureFlag_evaluates_team_scope(self):
+        flag_name = u'flag'
+        person = self.factory.makePerson()
+        team = self.factory.makeTeam(members=[person])
+        with feature_flags():
+            set_feature_flag(flag_name, u'value', u'team:' + team.name)
+            self.assertEqual(
+                u'value',
+                self.endpoint.getFeatureFlag(
+                    flag_name, username=person.name))
+
+    def test_xmlrpc_interface_unset(self):
+        sp = xmlrpclib.ServerProxy(
+            'http://xmlrpc-private.launchpad.dev:8087/featureflags/',
+            transport=XMLRPCTestTransport(), allow_none=True)
+        self.assertEqual(None, sp.getFeatureFlag(u'flag'))
+
+    def test_xmlrpc_interface_set(self):
+        sp = xmlrpclib.ServerProxy(
+            'http://xmlrpc-private.launchpad.dev:8087/featureflags/',
+            transport=XMLRPCTestTransport(), allow_none=True)
+        flag_name = u'flag'
+        with feature_flags():
+            set_feature_flag(flag_name, u'1')
+            self.assertEqual(u'1', sp.getFeatureFlag(flag_name))

=== added file 'lib/lp/services/features/xmlrpc.py'
--- lib/lp/services/features/xmlrpc.py	1970-01-01 00:00:00 +0000
+++ lib/lp/services/features/xmlrpc.py	2011-09-19 00:33:20 +0000
@@ -0,0 +1,57 @@
+# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""FeatureFlagApplication allows access to information about feature flags."""
+
+__metaclass__ = type
+__all__ = [
+    'IFeatureFlagApplication',
+    'FeatureFlagApplication',
+    ]
+
+from zope.component import getUtility
+from zope.interface import implements
+
+from canonical.launchpad.webapp.interfaces import ILaunchpadApplication
+from lp.registry.interfaces.person import IPersonSet
+from lp.services.features.flags import FeatureController
+from lp.services.features.rulesource import StormFeatureRuleSource
+from lp.services.features.scopes import (
+    DefaultScope,
+    MultiScopeHandler,
+    )
+
+
+class IFeatureFlagApplication(ILaunchpadApplication):
+    """Mailing lists application root."""
+
+    def getFeatureFlag(flag_name, username=None, scopes=()):
+        """Return the value of the given feature flag.
+
+        :param flag_name: The name of the flag to query.
+        :param username: If supplied, the name of a Person to use in
+            evaluating the 'team:' scope.
+        :param scopes: A list of scopes to consider active.  The 'default'
+            scope is always considered to be active, and does not need to be
+            included here.
+        """
+
+
+class FeatureFlagApplication:
+
+    implements(IFeatureFlagApplication)
+
+    def getFeatureFlag(self, flag_name, username=None, scopes=()):
+        scopes = tuple(scopes) + ('default',)
+        person = None
+        if username:
+            person = getUtility(IPersonSet).getByName(username)
+        def scope_lookup(scope):
+            if person is not None and scope.startswith('team:'):
+                team_name = scope[len('team:'):]
+                return person.inTeam(team_name)
+            else:
+                return scope in scopes
+        flag_name = unicode(flag_name)
+        controller = FeatureController(scope_lookup, StormFeatureRuleSource())
+        return controller.getFlag(flag_name)

=== modified file 'utilities/page-performance-report.ini'
--- utilities/page-performance-report.ini	2011-04-28 21:52:17 +0000
+++ utilities/page-performance-report.ini	2011-09-19 00:33:20 +0000
@@ -11,7 +11,8 @@
     [^/]+($|/
      (?!\+haproxy|\+opstats|\+access-token
       |((authserver|bugs|bazaar|codehosting|
-         codeimportscheduler|mailinglists|softwarecenteragent)/\w+$)))
+         codeimportscheduler|mailinglists|softwarecenteragent|
+         featureflags)/\w+$)))
 Other=^/
 
 Launchpad Frontpage=^https?://launchpad\.[^/]+(/index\.html)?$
@@ -54,7 +55,7 @@
 Private XML-RPC=^https://(launchpad|xmlrpc)[^/]+/
     (authserver|bugs|codehosting|
      codeimportscheduler|mailinglists|
-     softwarecenteragent)/\w+$
+     softwarecenteragent|featureflags)/\w+$
 
 [metrics]
 ppr_all=All Launchpad except operational pages


Follow ups