2 # -*- coding: utf-8 -*- vim:fileencoding=utf-8:
3 #Copyright © 2011-2013 Greek Research and Technology Network (GRNET S.A.)
5 #Developed by Leonidas Poulopoulos (leopoul-at-noc-dot-grnet-dot-gr),
8 #Permission to use, copy, modify, and/or distribute this software for any
9 #purpose with or without fee is hereby granted, provided that the above
10 #copyright notice and this permission notice appear in all copies.
12 #THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD
13 #TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
14 #FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
15 #CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
16 #DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
17 #ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
21 from ncclient import manager
22 from ncclient.transport.errors import AuthenticationError, SSHError
23 from lxml import etree as ET
24 from django.conf import settings
26 from django.core.cache import cache
32 LOG_FILENAME = os.path.join(settings.LOG_FILE_LOCATION, 'celery_jobs.log')
34 #FORMAT = '%(asctime)s %(levelname)s: %(message)s'
35 #logging.basicConfig(format=FORMAT)
36 formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s')
38 logger = logging.getLogger(__name__)
39 logger.setLevel(logging.DEBUG)
40 handler = logging.FileHandler(LOG_FILENAME)
41 handler.setFormatter(formatter)
42 logger.addHandler(handler)
44 def fod_unknown_host_cb(host, fingerprint):
47 class Retriever(object):
48 def __init__(self, device=settings.NETCONF_DEVICE, username=settings.NETCONF_USER, password=settings.NETCONF_PASS, filter=settings.ROUTES_FILTER, route_name=None, xml=None):
50 self.username = username
51 self.password = password
55 self.filter = settings.ROUTE_FILTER%route_name
58 with manager.connect(host=self.device, port=830, username=self.username, password=self.password, unknown_host_cb=fod_unknown_host_cb) as m:
59 xmlconfig = m.get_config(source='running', filter=('subtree',self.filter)).data_xml
62 def proccess_xml(self):
66 xmlconfig = self.fetch_xml()
68 parser.confile = xmlconfig
69 device = parser.export()
72 def fetch_device(self):
73 device = cache.get("device")
74 logger.info("[CACHE] hit! got device")
78 device = self.proccess_xml()
79 if device.routing_options:
80 cache.set("device", device, 3600)
81 logger.info("[CACHE] miss, setting device")
86 class Applier(object):
87 def __init__(self, route_objects = [], route_object=None, device=settings.NETCONF_DEVICE, username=settings.NETCONF_USER, password=settings.NETCONF_PASS):
88 self.route_object = route_object
89 self.route_objects = route_objects
91 self.username = username
92 self.password = password
94 def to_xml(self, operation=None):
95 logger.info("Operation: %s"%operation)
97 logger.info("Generating XML config")
98 route_obj = self.route_object
102 flow.routes.append(route)
103 device.routing_options.append(flow)
104 route.name = route_obj.name
105 if operation == "delete":
106 logger.info("Requesting a delete operation")
107 route.operation = operation
108 device = device.export(netconf_config=True)
109 return ET.tostring(device)
111 route.match['source'].append(route_obj.source)
112 if route_obj.destination:
113 route.match['destination'].append(route_obj.destination)
115 if route_obj.protocol:
116 for protocol in route_obj.protocol.all():
117 route.match['protocol'].append(protocol.protocol)
122 for port in route_obj.port.all():
123 route.match['port'].append(port.port)
127 if route_obj.destinationport:
128 for port in route_obj.destinationport.all():
129 route.match['destination-port'].append(port.port)
133 if route_obj.sourceport:
134 for port in route_obj.sourceport.all():
135 route.match['source-port'].append(port.port)
138 if route_obj.icmpcode:
139 route.match['icmp-code'].append(route_obj.icmpcode)
140 if route_obj.icmptype:
141 route.match['icmp-type'].append(route_obj.icmptype)
142 if route_obj.tcpflag:
143 route.match['tcp-flags'].append(route_obj.tcpflag)
146 for dscp in route_obj.dscp.all():
147 route.match['dscp'].append(dscp.dscp)
152 if route_obj.fragmenttype:
153 for frag in route_obj.fragmenttype.all():
154 route.match['fragment'].append(frag.fragmenttype)
158 for thenaction in route_obj.then.all():
159 if thenaction.action_value:
160 route.then[thenaction.action] = thenaction.action_value
162 route.then[thenaction.action] = True
163 if operation == "replace":
164 logger.info("Requesting a replace operation")
165 route.operation = operation
166 device = device.export(netconf_config=True)
167 return ET.tostring(device)
171 def delete_routes(self):
172 if self.route_objects:
173 logger.info("Generating XML config")
176 for route_object in self.route_objects:
177 route_obj = route_object
179 flow.routes.append(route)
180 route.name = route_obj.name
181 route.operation = 'delete'
182 device.routing_options.append(flow)
183 device = device.export(netconf_config=True)
184 return ET.tostring(device)
188 def apply(self, configuration = None, operation=None):
190 if not configuration:
191 configuration = self.to_xml(operation=operation)
192 edit_is_successful = False
193 commit_confirmed_is_successful = False
194 commit_is_successful = False
196 with manager.connect(host=self.device, port=830, username=self.username, password=self.password, unknown_host_cb=fod_unknown_host_cb) as m:
197 assert(":candidate" in m.server_capabilities)
198 with m.locked(target='candidate'):
201 edit_response = m.edit_config(target='candidate', config=configuration, test_option='test-then-set')
202 edit_is_successful, reason = is_successful(edit_response)
203 logger.info("Successfully edited @ %s" % self.device)
204 if not edit_is_successful:
206 except Exception as e:
207 cause="Caught edit exception: %s %s" %(e,reason)
208 cause=cause.replace('\n', '')
212 if edit_is_successful:
214 commit_confirmed_response = m.commit(confirmed=True, timeout=settings.COMMIT_CONFIRMED_TIMEOUT)
215 commit_confirmed_is_successful, reason = is_successful(commit_confirmed_response)
217 if not commit_confirmed_is_successful:
220 logger.info("Successfully confirmed committed @ %s" % self.device)
221 if not settings.COMMIT:
222 return True, "Successfully confirmed committed"
223 except Exception as e:
224 cause="Caught commit confirmed exception: %s %s" %(e,reason)
225 cause=cause.replace('\n', '')
229 if edit_is_successful and commit_confirmed_is_successful:
231 commit_response = m.commit(confirmed=False)
232 commit_is_successful, reason = is_successful(commit_response)
233 logger.info("Successfully committed @ %s" % self.device)
234 newconfig = m.get_config(source='running', filter=('subtree',settings.ROUTES_FILTER)).data_xml
235 retrieve = Retriever(xml=newconfig)
236 logger.info("[CACHE] caching device configuration")
237 cache.set("device", retrieve.proccess_xml(), 3600)
239 if not commit_is_successful:
242 logger.info("Successfully cached device configuration")
243 return True, "Successfully committed"
244 except Exception as e:
245 cause="Caught commit exception: %s %s" %(e,reason)
246 cause=cause.replace('\n', '')
250 return False, "No configuration was supplied"
252 def is_successful(response):
253 from StringIO import StringIO
254 doc = parsexml_(StringIO(response))
255 rootNode = doc.getroot()
256 success_list = rootNode.xpath("//*[local-name()='ok']")
257 if len(success_list)>0:
261 reason_list = rootNode.xpath("//*[local-name()='error-message']")
262 for reason in reason_list:
263 reason_return = "%s %s" %(reason_return, reason.text)
264 return False, reason_return
267 def parsexml_(*args, **kwargs):
268 if 'parser' not in kwargs:
269 kwargs['parser'] = ET.ETCompatXMLParser()
270 doc = ET.parse(*args, **kwargs)