forked from aws/serverless-application-model
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Percentage-based Enablement for Feature Toggle (aws#1952)
* Percentage-based Enablement for Feature Toggle * Update Feature Toggle to accept stage, account_id and region during instanciation * remove unnecessary uses of dict.get method * Refactor feature toggle methods * Update test names * black reformat * Update FeatureToggle to require stage, region and account_id to instanciate * Update log message * Implement calculating account percentile based on hash of account_id and feature_name * Refactor _is_feature_enabled_for_region_config * Refactor dialup logic into its own classes * Add comments for dialup classes * Rename NeverEnabledDialup to DisabledDialup
- Loading branch information
Showing
6 changed files
with
284 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import hashlib | ||
|
||
|
||
class BaseDialup(object): | ||
"""BaseDialup class to provide an interface for all dialup classes""" | ||
|
||
def __init__(self, region_config, **kwargs): | ||
self.region_config = region_config | ||
|
||
def is_enabled(self): | ||
""" | ||
Returns a bool on whether this dialup is enabled or not | ||
""" | ||
raise NotImplementedError | ||
|
||
def __str__(self): | ||
return self.__class__.__name__ | ||
|
||
|
||
class DisabledDialup(BaseDialup): | ||
""" | ||
A dialup that is never enabled | ||
""" | ||
|
||
def __init__(self, region_config, **kwargs): | ||
super(DisabledDialup, self).__init__(region_config) | ||
|
||
def is_enabled(self): | ||
return False | ||
|
||
|
||
class ToggleDialup(BaseDialup): | ||
""" | ||
A simple toggle Dialup | ||
Example of region_config: { "type": "toggle", "enabled": True } | ||
""" | ||
|
||
def __init__(self, region_config, **kwargs): | ||
super(ToggleDialup, self).__init__(region_config) | ||
self.region_config = region_config | ||
|
||
def is_enabled(self): | ||
return self.region_config.get("enabled", False) | ||
|
||
|
||
class SimpleAccountPercentileDialup(BaseDialup): | ||
""" | ||
Simple account percentile dialup, enabling X% of | ||
Example of region_config: { "type": "account-percentile", "enabled-%": 20 } | ||
""" | ||
|
||
def __init__(self, region_config, account_id, feature_name, **kwargs): | ||
super(SimpleAccountPercentileDialup, self).__init__(region_config) | ||
self.account_id = account_id | ||
self.feature_name = feature_name | ||
|
||
def _get_account_percentile(self): | ||
""" | ||
Get account percentile based on sha256 hash of account ID and feature_name | ||
:returns: integer n, where 0 <= n < 100 | ||
""" | ||
m = hashlib.sha256() | ||
m.update(self.account_id.encode()) | ||
m.update(self.feature_name.encode()) | ||
return int(m.hexdigest(), 16) % 100 | ||
|
||
def is_enabled(self): | ||
""" | ||
Enable when account_percentile falls within target_percentile | ||
Meaning only (target_percentile)% of accounts will be enabled | ||
""" | ||
target_percentile = self.region_config.get("enabled-%", 0) | ||
return self._get_account_percentile() < target_percentile |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
from unittest import TestCase | ||
|
||
from parameterized import parameterized, param | ||
from samtranslator.feature_toggle.dialup import * | ||
|
||
|
||
class TestBaseDialup(TestCase): | ||
def test___str__(self): | ||
region_config = {} | ||
dialup = BaseDialup(region_config) | ||
self.assertEqual(str(dialup), "BaseDialup") | ||
|
||
|
||
class TestDisabledDialup(TestCase): | ||
def test_is_enabled(self): | ||
region_config = {} | ||
dialup = DisabledDialup(region_config) | ||
self.assertFalse(dialup.is_enabled()) | ||
|
||
|
||
class TestToggleDialUp(TestCase): | ||
@parameterized.expand( | ||
[ | ||
param({"type": "toggle", "enabled": True}, True), | ||
param({"type": "toggle", "enabled": False}, False), | ||
param({"type": "toggle"}, False), # missing "enabled" key | ||
] | ||
) | ||
def test_is_enabled(self, region_config, expected): | ||
dialup = ToggleDialup(region_config) | ||
self.assertEqual(dialup.is_enabled(), expected) | ||
|
||
|
||
class TestSimpleAccountPercentileDialup(TestCase): | ||
@parameterized.expand( | ||
[ | ||
param({"type": "account-percentile", "enabled-%": 10}, "feature-1", "123456789100", True), | ||
param({"type": "account-percentile", "enabled-%": 10}, "feautre-1", "123456789123", False), | ||
param({"type": "account-percentile", "enabled": True}, "feature-1", "123456789100", False), | ||
] | ||
) | ||
def test_is_enabled(self, region_config, feature_name, account_id, expected): | ||
dialup = SimpleAccountPercentileDialup( | ||
region_config=region_config, | ||
account_id=account_id, | ||
feature_name=feature_name, | ||
) | ||
self.assertEqual(dialup.is_enabled(), expected) | ||
|
||
@parameterized.expand( | ||
[ | ||
param("feature-1", "123456789123"), | ||
param("feature-2", "000000000000"), | ||
param("feature-3", "432187654321"), | ||
param("feature-4", "111222333444"), | ||
] | ||
) | ||
def test__get_account_percentile(self, account_id, feature_name): | ||
region_config = {"type": "account-percentile", "enabled-%": 10} | ||
dialup = SimpleAccountPercentileDialup( | ||
region_config=region_config, | ||
account_id=account_id, | ||
feature_name=feature_name, | ||
) | ||
self.assertTrue(0 <= dialup._get_account_percentile() < 100) |
Oops, something went wrong.