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