[MediaWiki-commits] [Gerrit] Add instrumentation - change (operations...pybal)

2015-09-17 Thread jenkins-bot (Code Review)
jenkins-bot has submitted this change and it was merged.

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.

Bug: T102394
Change-Id: Ied88d44e63f91df3816df4b44f29bb0cd444cd05
---
M pybal/__init__.py
A pybal/instrumentation.py
M pybal/pybal.py
M pybal/test/__init__.py
M pybal/test/fixtures.py
A pybal/test/test_instrumentation.py
6 files changed, 342 insertions(+), 4 deletions(-)

Approvals:
  Ori.livneh: Looks good to me, approved
  Filippo Giunchedi: Looks good to me, but someone else must approve
  jenkins-bot: Verified



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 000..b606ace
--- /dev/null
+++ b/pybal/instrumentation.py
@@ -0,0 +1,125 @@
+"""
+  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/ - The full state of a pool
+  /pools// - 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 wantJson(request):
+return (request.requestHeaders.hasHeader('Accept')
+and 'application/json' in
+request.requestHeaders.getRawHeaders('Accept'))
+
+
+class Resp404(Resource):
+isLeaf = True
+
+def render_GET(self, request):
+request.setResponseCode(404)
+msg = {'error':
+   "The desired url {} was not found".format(request.uri)}
+if wantJson(request):
+return json.dumps(msg)
+else:
+return msg['error']
+
+
+class ServerRoot(Resource):
+"""Root url resource"""
+
+def getChild(self, path, request):
+if path == 'pools':
+return PoolsRoot()
+else:
+return Resp404()
+
+
+class PoolsRoot(Resource):
+"""Pools base resource.
+
+Serves /pools
+
+If called directly, will list all the pools.
+"""
+_pools = {}
+
+@classmethod
+def addPool(cls, path, coordinator):
+cls._pools[path] = 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):
+pools = self._pools.keys()
+if wantJson(request):
+return json.dumps(pools)
+else:
+return "\n".join(pools)
+
+
+class PoolServers(Resource):
+"""Single pool resource.
+
+Serves /pools/
+
+It will print out the state of all the servers in the pool, one per line.
+"""
+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):
+if wantJson(request):
+res = {}
+for hostname, server in self.coordinator.servers.items():
+res[hostname] = server.dumpState()
+return json.dumps(res)
+else:
+res = ""
+for hostname, server in self.coordinator.server.items():
+res += "{}:\t{}\n".format(hostname, server.textStatus())
+return res
+
+
+class PoolServer(Resource):
+"""
+Single server resource.
+
+Serves /pools//
+"""
+isLeaf = True
+
+def __init__(self, server):
+self.server = server
+
+def render_GET(self, request):
+if wantJson(request):
+return json.dumps(self.server.dumpState())
+else:
+return self.server.textStatus()
diff --git a/pybal/pybal.py b/pybal/pybal.py
index ea6cf63..800fbf9 100755
--- a/pybal/pybal.py
+++ b/pybal/pybal.py
@@ -10,7 +10,7 @@
 from __future__ import absolute_import
 
 impor

[MediaWiki-commits] [Gerrit] Add instrumentation - change (operations...pybal)

2015-09-14 Thread Giuseppe Lavagetto (Code Review)
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 000..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/ - The full state of a pool
+  /pools// - 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 @@
 serv