The idea behind it is to ease a simple way to enable/disable functionalities to a subset of users in a production (or any other) environment. This is in general handy upon deploying a new version of your product, in order to test the new functionalities in a subset of users. It could be useful as well as ACL mechanism.
- Enable a functionality globally (for every user using the system).
- Enable a functionality to a percentage of users via Cyclic Redundancy Check(user identifier) % 100.
- Enable a functionality to a percentage of users via a predefined rule using a Regular Expression.
- Enable a functionality to specific user identifiers.
- Variants support (new in 0.0.4): inspired in feature by Esty and sixpack,
hanoinow supports variant for providing to users different options for an experiment.
- Check if the functionality A is enabled for user B.
- Pre-check while executing a function (functionality A) that user B (received as parameter in the function call) is granted permissions.
@roll.check('A', 1) # Check if user B (argument 1) is granted permissions to execute A def execute_a_logic(user): pass # Business logic here execute_a_logic(B)
- Pre-check to ensure user B (attached to the process/thread) can execute functionality A (ACL mechanism). Be aware if your environment requires thread-safe behavior.
rollout.set_current_id(B) @roll.check('A') # Check if the current user (B) is granted permissions to execute A def execute_a_logic(): pass # Business logic here execute_a_logic()
- Retrieving a valid variant for an experiment A for user B
variant = rollout.variant('A', B)
Examples of usage
# Setting the configuration # ------------------------- # bootstrap.py import re import hanoi rollout = hanoi.Rollout(hanoi.RedisHighPerfBackEnd()) rollout.add_func( 'cdc_on', # Functionality name (CDC on) percentage=80, # Percentage for toggle ON variants=('foo', 'bar') # Valid variants in case of toggle ON ) rollout.register('cdc_on', '447568110000') # Register a specific user rollout.register('cdc_on', re.compile(r'01$') # Register a subset of users def get_rollout(): return rollout # Using Rollout # ------------- # service.py import bootstrap roll = bootstrap.get_rollout() # Define the current user (kind of ThreadLocal) roll.set_current_id('444401') @roll.check('cdc_on') # Check if the current user is registerd to `cdc_on` def execute_cdc_logic(): pass # Based on the rules defined in bootstrap.py, # the decorator will not allow the function execution, # as zlib.crc32('444401') % 100 = 89, and the predefined percentage is 80 execute_cdc_logic() # Check if it's enabled `cdc_on` to the user `44488` # Based on the rules defined in bootstrap.py, it will return False print roll.is_enabled('cdc_on', '44488') # Check if it's enabled `cdc_on` to the second parameter @roll.check('cdc_on', 2) def execute_again_cdc_logic(parameter, user): return "I'm in" # Based on the rules defined in bootstrap.py, # the decorator will allow the function execution, as 443301 matches the reg expr. print execute_again_cdc_logic('foo', '443301') # Get a valid variant for user `443301` print roll.variant('cdc_on', '443301')
Currently there're three implemented BackEnds:
MemoryBackEnd: useful for development or where you have predefined rules and don't need to share information between different processes.
RedisBackEnd: useful for distributed environments, where you need to easily update functionalities, rules or users attached to a specific functionality.
RedisHighPerfBackEnd: useful for distributed environments and high performance. It uses SET to store users and reduce significantly the time to verify an user. Check and execute
benchmark.pyfile for details.
TODO before BETA
- Finish unit testing Rollout class
- Finish unit testing Function class
- Implement Redis BackEnd
- Finish unit testing RedisBackEnd class
- Document the different use cases and when to use both backends
- Integrate travis.ci
- Upload a beta version to pypi
- Think about a cooler name
😉. We'll stay with hanoi
- Write a blog post