Giuseppe Lavagetto has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/238152

Change subject: Add instrumentation
......................................................................

Add instrumentation

Pybal has always been pretty private on its own internal state to
outside observers, making it a bit difficult to monitor or poll its
state.

We add a simple HTTP listener chain of monitoring endpoints describing
the actual state (pooled/depooled, up/down) of all servers (or a single
one) in a pool

Url space is left free for others to use for stats or other kind of
instrumentation in the future.

UNTESTED MOCK.

Bug: T102394
Change-Id: Ied88d44e63f91df3816df4b44f29bb0cd444cd05
---
M pybal/__init__.py
A pybal/instrumentation.py
M pybal/pybal.py
3 files changed, 115 insertions(+), 3 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/operations/debs/pybal 
refs/changes/52/238152/1

diff --git a/pybal/__init__.py b/pybal/__init__.py
index 20d9e50..e838db5 100644
--- a/pybal/__init__.py
+++ b/pybal/__init__.py
@@ -11,4 +11,4 @@
 USER_AGENT_STRING = 'PyBal/%s' % __version__
 
 __all__ = ('ipvs', 'monitor', 'pybal', 'util', 'monitors', 'bgp',
-           'config', 'USER_AGENT_STRING')
+           'config', 'instrumentation', 'USER_AGENT_STRING')
diff --git a/pybal/instrumentation.py b/pybal/instrumentation.py
new file mode 100644
index 0000000..c76ef80
--- /dev/null
+++ b/pybal/instrumentation.py
@@ -0,0 +1,98 @@
+"""
+  Instrumentation HTTP server for PyBal
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+  A simple http server that can return the state of a pool,
+  or the state of a single server within a pool.
+
+  Urls:
+
+  /pools  - a list of the available pools
+  /pools/<pool> - The full state of a pool
+  /pools/<pool>/<host> - the state of a single host in a pool
+
+  All results are returned either as human-readable lists or as json
+  structures, depending on the Accept header of the request.
+"""
+
+from twisted.web.resource import Resource
+import json
+
+
+def renderConditional(msg, request):
+    if request.requestHeaders.hasHeader('Accept'):
+        val = request.requestHeaders.getRawHeaders('Accept')
+        if 'application/json' in val:
+            return json.dumps(msg)
+        else:
+            return str(msg)
+
+
+class Resp404(Resource):
+    isLeaf = True
+
+    def render_GET(self, request):
+        request.setResponseCode(404)
+        msg = {'error':
+               "The desired url {} was not found".format(request.path)}
+        return renderConditional(msg, request)
+
+
+def Server(Resource):
+
+    def getChild(self, path, request):
+        if path == 'pools':
+            return PoolsRoot()
+        else:
+            return Resp404()
+
+
+class PoolsRoot(Resource):
+    _pools = {}
+
+    def __init__(self):
+        Resource.__init__(self)
+
+    @classmethod
+    def addPool(cls, coordinator):
+        cls._pools[coordinator.lvsservice.name] = coordinator
+
+    def getChild(self, path, request):
+        if not path:
+            return self
+        if path in self._pools:
+            return PoolServers(self._pools[path])
+        return Resp404()
+
+    def render_GET(self, request):
+        return renderConditional(self._pools.keys())
+
+
+class PoolServers(Resource):
+
+    def __init__(self, coordinator):
+        Resource.__init__(self)
+        self.coordinator = coordinator
+
+    def getChild(self, path, request):
+        if not path:
+            return self
+        if path in self.coordinator.servers:
+            return PoolServer(self.coordinator.servers[path])
+        return Resp404()
+
+    def render_GET(self, request):
+        res = {}
+        for hostname, server in self.coordinator.servers.items():
+            res[hostname] = server.dumpState()
+        return renderConditional(res, request)
+
+
+class PoolServer(Resource):
+    isLeaf = True
+
+    def __init__(self, server):
+        self.state = server.dumpState()
+
+    def render_GET(self, request):
+        return renderConditional(self.state, request)
diff --git a/pybal/pybal.py b/pybal/pybal.py
index ea6cf63..e0eb286 100755
--- a/pybal/pybal.py
+++ b/pybal/pybal.py
@@ -10,7 +10,7 @@
 from __future__ import absolute_import
 
 import os, sys, signal, socket, random
-from pybal import ipvs, monitor, util, config
+from pybal import ipvs, monitor, util, config, instrumentation
 
 from twisted.python import failure, filepath
 from twisted.internet import reactor, defer
@@ -25,6 +25,7 @@
     from pybal import bgp
 except ImportError:
     pass
+
 
 class Server:
     """
@@ -245,6 +246,10 @@
         self.maintainState()
         self.modified = True    # Indicate that this instance previously 
existed
 
+    def dumpState(self):
+        """Dump current state of the server"""
+        return {'pooled': self.pooled, 'weight': self.weight, 'up': self.up}
+
     @classmethod
     def buildServer(cls, hostName, configuration, lvsservice):
         """
@@ -257,6 +262,7 @@
         server.modified = False
 
         return server
+
 
 class Coordinator:
     """
@@ -413,6 +419,7 @@
 
         # Assign the updated list of enabled servers to the LVSService instance
         self.assignServers()
+
 
 class BGPFailover:
     """Class for maintaining a BGP session to a router for IP address 
failover"""
@@ -681,7 +688,7 @@
                 crd = Coordinator(services[section],
                     configUrl=config.get(section, 'config'))
                 print "Created LVS service '%s'" % section
-
+                instrumentation.PoolsRoot.addPool(crd)
 
         # Set up BGP
         try:
@@ -691,6 +698,13 @@
         configdict.update(cliconfig)
         bgpannouncement = BGPFailover(configdict)
 
+        # Run the web server for instrumentation
+        if configdict.get('instrumentation', False):
+            from twisted.web.server import Site
+            port = configdict.get('instrumentation_port', 9090)
+            factory = Site(instrumentation.Server())
+            reactor.listenTCP(port, factory)
+
         reactor.run()
     finally:
         terminate()

-- 
To view, visit https://gerrit.wikimedia.org/r/238152
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ied88d44e63f91df3816df4b44f29bb0cd444cd05
Gerrit-PatchSet: 1
Gerrit-Project: operations/debs/pybal
Gerrit-Branch: master
Gerrit-Owner: Giuseppe Lavagetto <glavage...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to