[MediaWiki-commits] [Gerrit] Add instrumentation - change (operations...pybal)
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)
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