ansible: add module for controlling Stingray Traffic Managers
authorGregory Szorc <gps@mozilla.com>
Tue, 21 Apr 2015 22:07:46 -0700
changeset 360701 e40c78e67098b464d7f1e3a38780b5f7ac5ed323
parent 360700 573156744d5cd37d25de7db1b5c21b2d36c13c27
child 360702 3aaf712849f462a34a66c0a6abc90223abec1377
push id16998
push userrwood@mozilla.com
push dateMon, 02 May 2016 19:42:03 +0000
ansible: add module for controlling Stingray Traffic Managers Mozilla uses the Stingray Traffic Manager for load balancing. In order to write Ansible playbooks that perform rolling updates with load balancer coordination, we need a way to modify state of the Stingray load balancer. Fortunately, the Stingray load balancer has a JSON API. This commit whips up some basic APIs for talking to said API.
.hgignore
ansible/library/stingray_node.py
create-test-environment
pylib/mozansible/mozansible/__init__.py
pylib/mozansible/mozansible/stingray.py
pylib/mozansible/setup.py
--- a/.hgignore
+++ b/.hgignore
@@ -1,11 +1,12 @@
 coverage/
 docs/_build/
 hghooks/Mozilla_Hg_Hooks.egg-info/
+pylib/mozansible/mozansible.egg-info/
 pylib/mozreview/mozreview.egg-info/
 pylib/rbbz/rbbz.egg-info/
 pylib/Bugsy/bugsy.egg-info/
 testing/bmoserver/.vagrant
 testing/puppet/files/Mozilla-Bugzilla-Public*
 testing/unifiedserver/.vagrant
 testing/vcttesting.egg-info/
 venv/
new file mode 100755
--- /dev/null
+++ b/ansible/library/stingray_node.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from mozansible.stingray import StingrayConnection
+
+
+def main():
+    module = AnsibleModule(
+        argument_spec = {
+            'pool': {'required': True},
+            'node': {'required': True},
+            'state': {
+                'required': False,
+                'choices': ['active', 'disabled', 'draining'],
+            },
+            'url': {'required': True},
+            'username': {'required': True},
+            'password': {'password': True},
+        },
+    )
+
+    url = module.params['url']
+    username = module.params['username']
+    password = module.params['password']
+    pool = module.params['pool']
+    node = module.params['node']
+    state = module.params['state']
+
+    conn = StingrayConnection(module, url, username, password)
+    changed = conn.set_node_state(pool, node, state)
+
+    module.exit_json(changed=changed, msg='Set %s state to %s' % (node, state))
+
+
+from ansible.module_utils.basic import *
+main()
--- a/create-test-environment
+++ b/create-test-environment
@@ -12,16 +12,20 @@ pip install --upgrade -r test-requiremen
 
 # ReviewBoard doesn't work with pip, sadly.
 easy_install ReviewBoard==2.0.12
 
 cd pylib/Bugsy
 python setup.py develop
 cd ../..
 
+cd pylib/mozansible
+python setup.py develop
+cd ../..
+
 cd pylib/mozreview
 python setup.py develop
 cd ../..
 
 cd pylib/rbbz
 python setup.py develop
 cd ../..
 
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/pylib/mozansible/mozansible/stingray.py
@@ -0,0 +1,74 @@
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 3 or any later version.
+
+import requests
+
+
+class StingrayConnection(object):
+    """Represents a connection to a Stringray Traffic Manager."""
+
+    def __init__(self, module, url, username, password):
+        self.module = module
+        self.session = requests.Session()
+        self.session.auth = (username, password)
+        # TODO plug in SSL verification
+        self.session.verify = False
+        self._url = '%s/api/tm/3.2' % url.rstrip('/')
+
+    def request(self, path, method='GET', **kwargs):
+        response = self.session.request(
+                method,
+                '%s/%s' % (self._url, path.lstrip('/')),
+                **kwargs)
+
+        if response.status_code == 404:
+            return None
+
+        j = response.json()
+        if 'error_text' in j:
+            self.module.fail_json(
+                    msg='Error talking to Stingray: %s' % j['error_text'])
+
+        return j
+
+    def get_pool_state(self, pool):
+        state = self.request('config/active/pools/%s' % pool)
+
+        if not state:
+            self.module.fail_json(msg='Pool %s not found' % pool)
+
+        return state
+
+    def set_node_state(self, pool, node, state):
+        """Set the state of a node in a pool.
+
+        This is used to mark a node active, disabled, or draining.
+
+        This will error if the pool or node is not known.
+
+        Returns a boolean indicating whether anything changed.
+        """
+        pool_state = self.get_pool_state(pool)
+
+        nodes = []
+        found = False
+        for n in pool_state['properties']['basic']['nodes_table']:
+            if n['node'] == node:
+                if n['state'] == state:
+                    return False
+
+                found = True
+                n['state'] = state
+
+            nodes.append(n)
+
+        if not found:
+            self.module.fail_json(msg='Node %s not found in pool %s' % (
+                node, pool))
+
+        new_state = {'properties': {'basic': {'nodes_table': nodes}}}
+
+        self.request('config/active/pools/%s' % pool, method='PUT',
+                     json=new_state)
+
+        return True
new file mode 100644
--- /dev/null
+++ b/pylib/mozansible/setup.py
@@ -0,0 +1,8 @@
+from setuptools import setup
+
+setup(name='mozansible',
+      version='0.1',
+      description='Support packages related to Ansible at Mozilla',
+      author='Mozilla Developer Services',
+      packages = ['mozansible'],
+)