Skip to content

Instantly share code, notes, and snippets.

@crazed
Last active October 11, 2016 08:30
Show Gist options
  • Save crazed/7203476 to your computer and use it in GitHub Desktop.
Save crazed/7203476 to your computer and use it in GitHub Desktop.
sample using trigger with a custom loader
import sys
import logging
import xml.etree.ElementTree as ET
import re
import types
from uuid import uuid4
import cyclone.httpclient
import cyclone.jsonrpc
import cyclone.xmlrpc
# trigger imports
import trigger.utils.xmltodict as xmltodict
from trigger.cmds import Commando as CommandoBase
from trigger.netdevices.loader import BaseLoader
from trigger.utils import strip_juniper_namespace
from twisted.internet import defer, task, reactor
from twisted.python import log
# custom client into centralized data store
import optopus
class MyLoader(BaseLoader):
is_usable = True
def get_data(self, data_source):
client = optopus.Client()
data = client.network_nodes
devices = []
# map optopus data to what trigger expects
mapping = { 'nodeName': 'hostname' }
fact_mapping = {
'site': 'location',
'manufacturer': 'boardmanufacturer',
}
# expression used to create a deviceType attribute
exp = re.compile('ops-([a-z]+)\d+\..*\.shuttercorp\.net')
for device_data in data:
device_data = device_data['network_node']
netdev_data = {'adminStatus': 'PRODUCTION'}
for key, value in mapping.items():
if value in device_data:
netdev_data[key] = str(device_data[value])
for key, value in fact_mapping.items():
if value in device_data['facts']:
netdev_data[key] = str(device_data['facts'][value])
m = exp.match(device_data['hostname'])
if m:
if m.group(1) == 'fw':
devtype = 'firewall'
elif m.group(1) == 'lb':
devtype = 'loadbalancer'
else:
devtype = m.group(1)
netdev_data['deviceType'] = devtype.upper()
devices.append(netdev_data)
return devices
def load_data_source(self, data_source, **kwargs):
return self.get_data(data_source)
# Setup our loader with tigger
import trigger
trigger.conf.global_settings.NETDEVICES_LOADERS = [ 'test.MyLoader' ]
nd = trigger.netdevices.NetDevices()
class ReactorlessCommando(CommandoBase):
"""
A reactor-less Commando subclass.
This allows multiple instances to coexist, with the side-effect that you
have to manage the reactor start/stop manually.
"""
def _start(self):
"""Initializes ``all_done`` instead of starting the reactor"""
log.msg("._start() called")
self.all_done = False
def _stop(self):
"""Sets ``all_done`` to True instead of stopping the reactor"""
log.msg("._stop() called")
self.all_done = True
def run(self):
"""
We've overloaded the run method to return a Deferred task object.
"""
log.msg(".run() called")
# This is the default behavior
super(ReactorlessCommando, self).run()
# Setup a deferred to hold the delayed result and not return it until
# it's done. This object will be populated with the value of the
# results once all commands have been executed on all devices.
d = defer.Deferred()
# Add monitor_result as a callback
d.addCallback(self.monitor_result, reactor)
# Tell the reactor to call the callback above when it starts
reactor.callWhenRunning(d.callback, reactor)
return d
def monitor_result(self, result, reactor):
"""
Loop periodically or until the factory stops to check if we're
``all_done`` and then return the results.
"""
# Once we're done, return the results
if self.all_done:
return self.results
# Otherwise tell the reactor to call me again after 0.5 seconds.
return task.deferLater(reactor, 0.5, self.monitor_result, result, reactor)
class SwitcherooCommand(ReactorlessCommando):
def to_juniper(self, device, commands=None, extra=None):
"""
The default Commando stuff will attempt to wrap my commands in
<command> elements. We do not want this here, since we are using the
XMLRPC API directly.
"""
return self.commands
def from_juniper(self, data, device):
"""
Convert xml to json for user friendly-ness
"""
results = {}
for xml in data:
jdata = xmltodict.parse(
ET.tostring(xml),
postprocessor=strip_juniper_namespace,
xml_attribs=False)
results.update(jdata['rpc-reply'])
self.store_results(device, results)
from cyclone.web import HTTPError
class JsonrpcHandler(cyclone.jsonrpc.JsonrpcRequestHandler):
RESULTS = {}
def prepare(self):
try:
print self.request.body
self.json_request = cyclone.escape.json_decode(self.request.body)
except Exception, e:
log.msg("Bad Request: %s" % str(e))
raise HTTPError(400)
def post(self, *args):
self._auto_finish = False
try:
req = self.json_request
jsonid = req["id"]
method = req["method"]
assert isinstance(method, types.StringTypes), \
"Invalid method type: %s" % type(method)
params = req["params"]
assert isinstance(params, (types.ListType, types.TupleType)), \
"Invalid params type: %s" % type(params)
except Exception, e:
log.msg("Bad Request: %s" % str(e))
raise HTTPError(400)
self.jsonid = jsonid
self.method = method
self.params = params
function = getattr(self, "jsonrpc_%s" % method, None)
if callable(function):
args = list(args) + params
d = defer.maybeDeferred(function, *args)
d.addBoth(self._cbResult, jsonid)
else:
self._cbResult(AttributeError("method not found: %s" % method),
jsonid)
def store_results(self, results, id):
self.RESULTS[id] = results
def jsonrpc_match(self, match_dict):
devices = nd.match(**match_dict)
ret = {}
for dev in devices:
ret[dev.nodeName] = {
'site': dev.site,
'manufacturer': dev.manufacturer,
'serialNumber': dev.serialNumber,
}
return {'devices': ret, 'match': match_dict}
def jsonrpc_get_results(self, id):
if id in self.RESULTS:
return self.RESULTS[id]
else:
return None
def jsonrpc_run(self, xml_string, kwargs):
devices = []
if 'hosts' in kwargs:
for host in kwargs['hosts']:
devices.append(nd[host])
elif 'match' in kwargs:
print kwargs['match']
devices = nd.match(**kwargs['match'])
else:
log.msg("Request did not have hosts or match arguments!")
raise HTTPError(400)
self.RESULTS.pop(self.jsonid, None)
commands = [ET.fromstring(xml_string)]
print devices
action = SwitcherooCommand(commands=commands, devices=devices)
d = action.run()
d.addCallback(self.store_results, self.jsonid)
return {'id': self.jsonid}
def main():
log.startLogging(sys.stdout, setStdout=False)
application = cyclone.web.Application([
(r"/jsonrpc", JsonrpcHandler),
])
reactor.listenTCP(8889, application)
reactor.run()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment