Add new theme plus new icons
[flowspy] / utils / proxy.py
1 # -*- coding: utf-8 -*- vim:fileencoding=utf-8:
2 # vim: tabstop=4:shiftwidth=4:softtabstop=4:expandtab
3
4 # Copyright © 2011-2014 Greek Research and Technology Network (GRNET S.A.)
5 # Copyright © 2011-2014 Leonidas Poulopoulos (@leopoul)
6
7 # Permission to use, copy, modify, and/or distribute this software for any
8 # purpose with or without fee is hereby granted, provided that the above
9 # copyright notice and this permission notice appear in all copies.
10
11 # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
12 # TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
13 # FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
14 # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
15 # DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
16 # ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
17 # SOFTWARE.
18
19 import nxpy as np
20 from ncclient import manager
21 from ncclient.transport.errors import AuthenticationError, SSHError
22 from lxml import etree as ET
23 from django.conf import settings
24 import logging
25 from django.core.cache import cache
26 import os
27
28 cwd = os.getcwd()
29     
30
31 LOG_FILENAME = os.path.join(settings.LOG_FILE_LOCATION, 'celery_jobs.log')
32
33 #FORMAT = '%(asctime)s %(levelname)s: %(message)s'
34 #logging.basicConfig(format=FORMAT)
35 formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s')
36
37 logger = logging.getLogger(__name__)
38 logger.setLevel(logging.DEBUG)
39 handler = logging.FileHandler(LOG_FILENAME)
40 handler.setFormatter(formatter)
41 logger.addHandler(handler)
42
43 def fod_unknown_host_cb(host, fingerprint):
44     return True
45
46 class Retriever(object):
47     def __init__(self, device=settings.NETCONF_DEVICE, username=settings.NETCONF_USER, password=settings.NETCONF_PASS, filter=settings.ROUTES_FILTER, port=settings.NETCONF_PORT, route_name=None, xml=None):
48         self.device = device
49         self.username = username
50         self.password = password
51         self.port = port
52         self.filter = filter
53         self.xml = xml
54         if route_name:
55             self.filter = settings.ROUTE_FILTER%route_name
56     
57     def fetch_xml(self):
58         with manager.connect(host=self.device, port=self.port, 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
60         return xmlconfig
61     
62     def proccess_xml(self):
63         if self.xml:
64             xmlconfig = self.xml
65         else:
66             xmlconfig = self.fetch_xml()
67         parser = np.Parser()
68         parser.confile = xmlconfig
69         device = parser.export()
70         return device    
71     
72     def fetch_device(self):
73         device = cache.get("device")
74         logger.info("[CACHE] hit! got device")
75         if device:
76             return device
77         else:
78             device = self.proccess_xml()
79             if device.routing_options:
80                 cache.set("device", device, 3600)
81                 logger.info("[CACHE] miss, setting device")
82                 return device
83             else:
84                 return False
85
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, port=settings.NETCONF_PORT):
88         self.route_object = route_object
89         self.route_objects = route_objects
90         self.device = device
91         self.username = username
92         self.password = password
93         self.port = port
94     
95     def to_xml(self, operation=None):
96         logger.info("Operation: %s"%operation)
97         if self.route_object:
98             logger.info("Generating XML config")
99             route_obj = self.route_object
100             device = np.Device()
101             flow = np.Flow()
102             route = np.Route()
103             flow.routes.append(route)
104             device.routing_options.append(flow)
105             route.name = route_obj.name
106             if operation == "delete":
107                 logger.info("Requesting a delete operation")
108                 route.operation = operation
109                 device = device.export(netconf_config=True)
110                 return ET.tostring(device)
111             if route_obj.source:
112                 route.match['source'].append(route_obj.source)
113             if route_obj.destination:
114                 route.match['destination'].append(route_obj.destination)
115             try:
116                 if route_obj.protocol:
117                     for protocol in route_obj.protocol.all():
118                         route.match['protocol'].append(protocol.protocol)
119             except:
120                 pass
121             try:
122                 if route_obj.port:
123                     for port in route_obj.port.all():
124                         route.match['port'].append(port.port)
125             except:
126                 pass
127             try:
128                 if route_obj.destinationport:
129                     for port in route_obj.destinationport.all():
130                         route.match['destination-port'].append(port.port)
131             except:
132                 pass
133             try:
134                 if route_obj.sourceport:
135                     for port in route_obj.sourceport.all():
136                         route.match['source-port'].append(port.port)
137             except:
138                 pass
139             if route_obj.icmpcode:
140                 route.match['icmp-code'].append(route_obj.icmpcode)
141             if route_obj.icmptype:
142                 route.match['icmp-type'].append(route_obj.icmptype)
143             if route_obj.tcpflag:
144                 route.match['tcp-flags'].append(route_obj.tcpflag)
145             try:
146                 if route_obj.dscp:
147                     for dscp in route_obj.dscp.all():
148                         route.match['dscp'].append(dscp.dscp)
149             except:
150                 pass
151             
152             try:
153                 if route_obj.fragmenttype:
154                     for frag in route_obj.fragmenttype.all():
155                         route.match['fragment'].append(frag.fragmenttype)
156             except:
157                 pass
158             
159             for thenaction in route_obj.then.all():
160                 if thenaction.action_value:
161                     route.then[thenaction.action] = thenaction.action_value
162                 else:
163                     route.then[thenaction.action] = True
164             if operation == "replace":
165                 logger.info("Requesting a replace operation")
166                 route.operation = operation
167             device = device.export(netconf_config=True)
168             return ET.tostring(device)
169         else:
170             return False
171
172     def delete_routes(self):
173         if self.route_objects:
174             logger.info("Generating XML config")
175             device = np.Device()
176             flow = np.Flow()
177             for route_object in self.route_objects:
178                 route_obj = route_object
179                 route = np.Route()
180                 flow.routes.append(route)
181                 route.name = route_obj.name
182                 route.operation = 'delete'
183             device.routing_options.append(flow)
184             device = device.export(netconf_config=True)
185             return ET.tostring(device)
186         else:
187             return False    
188     
189     def apply(self, configuration = None, operation=None):
190         reason = None
191         if not configuration:
192             configuration = self.to_xml(operation=operation)
193         edit_is_successful = False
194         commit_confirmed_is_successful = False
195         commit_is_successful = False
196         if configuration:
197             with manager.connect(host=self.device, port=830, username=self.username, password=self.password, unknown_host_cb=fod_unknown_host_cb) as m:
198                 assert(":candidate" in m.server_capabilities)
199                 with m.locked(target='candidate'):
200                     m.discard_changes()
201                     try:
202                         edit_response = m.edit_config(target='candidate', config=configuration, test_option='test-then-set')
203                         edit_is_successful, reason = is_successful(edit_response)
204                         logger.info("Successfully edited @ %s" % self.device)
205                         if not edit_is_successful:
206                             raise Exception()
207                     except Exception as e:
208                         cause="Caught edit exception: %s %s" %(e,reason)
209                         cause=cause.replace('\n', '')
210                         logger.error(cause)
211                         m.discard_changes()
212                         return False, cause
213                     if edit_is_successful:
214                         try:
215                             commit_confirmed_response = m.commit(confirmed=True, timeout=settings.COMMIT_CONFIRMED_TIMEOUT)
216                             commit_confirmed_is_successful, reason = is_successful(commit_confirmed_response)
217                                 
218                             if not commit_confirmed_is_successful:
219                                 raise Exception()
220                             else:
221                                 logger.info("Successfully confirmed committed @ %s" % self.device)
222                                 if not settings.COMMIT:
223                                     return True, "Successfully confirmed committed"
224                         except Exception as e:
225                             cause="Caught commit confirmed exception: %s %s" %(e,reason)
226                             cause=cause.replace('\n', '')
227                             logger.error(cause)
228                             return False, cause
229                         if settings.COMMIT:
230                             if edit_is_successful and commit_confirmed_is_successful:
231                                 try:
232                                     commit_response = m.commit(confirmed=False)
233                                     commit_is_successful, reason = is_successful(commit_response)
234                                     logger.info("Successfully committed @ %s" % self.device)
235                                     newconfig = m.get_config(source='running', filter=('subtree',settings.ROUTES_FILTER)).data_xml
236                                     retrieve = Retriever(xml=newconfig)
237                                     logger.info("[CACHE] caching device configuration")
238                                     cache.set("device", retrieve.proccess_xml(), 3600)
239                                     
240                                     if not commit_is_successful:
241                                         raise Exception()
242                                     else:
243                                         logger.info("Successfully cached device configuration")
244                                         return True, "Successfully committed"
245                                 except Exception as e:
246                                     cause="Caught commit exception: %s %s" %(e,reason)
247                                     cause=cause.replace('\n', '')
248                                     logger.error(cause)
249                                     return False, cause
250         else:
251             return False, "No configuration was supplied"
252             
253 def is_successful(response):
254     from StringIO import StringIO
255     doc = parsexml_(StringIO(response))
256     rootNode = doc.getroot()
257     success_list = rootNode.xpath("//*[local-name()='ok']")
258     if len(success_list)>0:
259         return True, None
260     else:
261         reason_return = ''
262         reason_list = rootNode.xpath("//*[local-name()='error-message']")
263         for reason in reason_list:
264             reason_return = "%s %s" %(reason_return, reason.text)  
265         return False, reason_return
266     
267     
268 def parsexml_(*args, **kwargs):
269     if 'parser' not in kwargs:
270         kwargs['parser'] = ET.ETCompatXMLParser()
271     doc = ET.parse(*args, **kwargs)
272     return doc
273