Merge branch 'master' of https://code.grnet.gr/git/flowspy
[flowspy] / utils / proxy.py
1 import nxpy as np
2 from ncclient import manager
3 from ncclient.transport.errors import AuthenticationError, SSHError
4 from lxml import etree as ET
5 from django.conf import settings
6 import logging
7 from django.core.cache import cache
8 import os
9
10 cwd = os.getcwd()
11     
12
13 LOG_FILENAME = os.path.join(settings.LOG_FILE_LOCATION, 'celery_jobs.log')
14
15 #FORMAT = '%(asctime)s %(levelname)s: %(message)s'
16 #logging.basicConfig(format=FORMAT)
17 formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s')
18
19 logger = logging.getLogger(__name__)
20 logger.setLevel(logging.DEBUG)
21 handler = logging.FileHandler(LOG_FILENAME)
22 handler.setFormatter(formatter)
23 logger.addHandler(handler)
24
25 def fod_unknown_host_cb(host, fingerprint):
26     return True
27
28 class Retriever(object):
29     def __init__(self, device=settings.NETCONF_DEVICE, username=settings.NETCONF_USER, password=settings.NETCONF_PASS, filter=settings.ROUTES_FILTER, route_name=None, xml=None):
30         self.device = device
31         self.username = username
32         self.password = password
33         self.filter = filter
34         self.xml = xml
35         if route_name:
36             self.filter = settings.ROUTE_FILTER%route_name
37     
38     def fetch_xml(self):
39         with manager.connect(host=self.device, port=830, username=self.username, password=self.password, unknown_host_cb=fod_unknown_host_cb) as m:
40             xmlconfig = m.get_config(source='running', filter=('subtree',self.filter)).data_xml
41         return xmlconfig
42     
43     def proccess_xml(self):
44         if self.xml:
45             xmlconfig = self.xml
46         else:
47             xmlconfig = self.fetch_xml()
48         parser = np.Parser()
49         parser.confile = xmlconfig
50         device = parser.export()
51         return device    
52     
53     def fetch_device(self):
54         device = cache.get("device")
55         logger.info("[CACHE] hit! got device")
56         if device:
57             return device
58         else:
59             device = self.proccess_xml()
60             if device.routing_options:
61                 cache.set("device", device, 3600)
62                 logger.info("[CACHE] miss, setting device")
63                 return device
64             else:
65                 return False
66
67 class Applier(object):
68     def __init__(self, route_objects = [], route_object=None, device=settings.NETCONF_DEVICE, username=settings.NETCONF_USER, password=settings.NETCONF_PASS):
69         self.route_object = route_object
70         self.route_objects = route_objects
71         self.device = device
72         self.username = username
73         self.password = password
74     
75     def to_xml(self, operation=None):
76         logger.info("Operation: %s"%operation)
77         if self.route_object:
78             logger.info("Generating XML config")
79             route_obj = self.route_object
80             device = np.Device()
81             flow = np.Flow()
82             route = np.Route()
83             flow.routes.append(route)
84             device.routing_options.append(flow)
85             route.name = route_obj.name
86             if operation == "delete":
87                 logger.info("Requesting a delete operation")
88                 route.operation = operation
89                 device = device.export(netconf_config=True)
90                 return ET.tostring(device)
91             if route_obj.source:
92                 route.match['source'].append(route_obj.source)
93             if route_obj.destination:
94                 route.match['destination'].append(route_obj.destination)
95             try:
96                 if route_obj.protocol:
97                     for protocol in route_obj.protocol.all():
98                         route.match['protocol'].append(protocol.protocol)
99             except:
100                 pass
101             try:
102                 if route_obj.port:
103                     for port in route_obj.port.all():
104                         route.match['port'].append(port.port)
105             except:
106                 pass
107             try:
108                 if route_obj.destinationport:
109                     for port in route_obj.destinationport.all():
110                         route.match['destination-port'].append(port.port)
111             except:
112                 pass
113             try:
114                 if route_obj.sourceport:
115                     for port in route_obj.sourceport.all():
116                         route.match['source-port'].append(port.port)
117             except:
118                 pass
119             if route_obj.icmpcode:
120                 route.match['icmp-code'].append(route_obj.icmpcode)
121             if route_obj.icmptype:
122                 route.match['icmp-type'].append(route_obj.icmptype)
123             if route_obj.tcpflag:
124                 route.match['tcp-flags'].append(route_obj.tcpflag)
125             try:
126                 if route_obj.dscp:
127                     for dscp in route_obj.dscp.all():
128                         route.match['dscp'].append(dscp.dscp)
129             except:
130                 pass
131             if route_obj.fragmenttype:
132                 route.match['fragment'].append(route_obj.fragmenttype)
133             for thenaction in route_obj.then.all():
134                 if thenaction.action_value:
135                     route.then[thenaction.action] = thenaction.action_value
136                 else:
137                     route.then[thenaction.action] = True
138             if operation == "replace":
139                 logger.info("Requesting a replace operation")
140                 route.operation = operation
141             device = device.export(netconf_config=True)
142             return ET.tostring(device)
143         else:
144             return False
145
146     def delete_routes(self):
147         if self.route_objects:
148             logger.info("Generating XML config")
149             device = np.Device()
150             flow = np.Flow()
151             for route_object in self.route_objects:
152                 route_obj = route_object
153                 route = np.Route()
154                 flow.routes.append(route)
155                 route.name = route_obj.name
156                 route.operation = 'delete'
157             device.routing_options.append(flow)
158             device = device.export(netconf_config=True)
159             return ET.tostring(device)
160         else:
161             return False    
162     
163     def apply(self, configuration = None, operation=None):
164         reason = None
165         if not configuration:
166             configuration = self.to_xml(operation=operation)
167         edit_is_successful = False
168         commit_confirmed_is_successful = False
169         commit_is_successful = False
170         if configuration:
171             with manager.connect(host=self.device, port=830, username=self.username, password=self.password, unknown_host_cb=fod_unknown_host_cb) as m:
172                 assert(":candidate" in m.server_capabilities)
173                 with m.locked(target='candidate'):
174                     m.discard_changes()
175                     try:
176                         edit_response = m.edit_config(target='candidate', config=configuration, test_option='test-then-set')
177                         edit_is_successful, reason = is_successful(edit_response)
178                         logger.info("Successfully edited @ %s" % self.device)
179                         if not edit_is_successful:
180                             raise Exception()
181                     except Exception as e:
182                         cause="Caught edit exception: %s %s" %(e,reason)
183                         cause=cause.replace('\n', '')
184                         logger.error(cause)
185                         m.discard_changes()
186                         return False, cause
187                     if edit_is_successful:
188                         try:
189                             commit_confirmed_response = m.commit(confirmed=True, timeout=settings.COMMIT_CONFIRMED_TIMEOUT)
190                             commit_confirmed_is_successful, reason = is_successful(commit_confirmed_response)
191                                 
192                             if not commit_confirmed_is_successful:
193                                 raise Exception()
194                             else:
195                                 logger.info("Successfully confirmed committed @ %s" % self.device)
196                                 if not settings.COMMIT:
197                                     return True, "Successfully confirmed committed"
198                         except Exception as e:
199                             cause="Caught commit confirmed exception: %s %s" %(e,reason)
200                             cause=cause.replace('\n', '')
201                             logger.error(cause)
202                             return False, cause
203                         if settings.COMMIT:
204                             if edit_is_successful and commit_confirmed_is_successful:
205                                 try:
206                                     commit_response = m.commit(confirmed=False)
207                                     commit_is_successful, reason = is_successful(commit_response)
208                                     logger.info("Successfully committed @ %s" % self.device)
209                                     newconfig = m.get_config(source='running', filter=('subtree',settings.ROUTES_FILTER)).data_xml
210                                     retrieve = Retriever(xml=newconfig)
211                                     logger.info("[CACHE] caching device configuration")
212                                     cache.set("device", retrieve.proccess_xml(), 3600)
213                                     
214                                     if not commit_is_successful:
215                                         raise Exception()
216                                     else:
217                                         logger.info("Successfully cached device configuration")
218                                         return True, "Successfully committed"
219                                 except Exception as e:
220                                     cause="Caught commit exception: %s %s" %(e,reason)
221                                     cause=cause.replace('\n', '')
222                                     logger.error(cause)
223                                     return False, cause
224         else:
225             return False, "No configuration was supplied"
226             
227 def is_successful(response):
228     from StringIO import StringIO
229     doc = parsexml_(StringIO(response))
230     rootNode = doc.getroot()
231     success_list = rootNode.xpath("//*[local-name()='ok']")
232     if len(success_list)>0:
233         return True, None
234     else:
235         reason_return = ''
236         reason_list = rootNode.xpath("//*[local-name()='error-message']")
237         for reason in reason_list:
238             reason_return = "%s %s" %(reason_return, reason.text)  
239         return False, reason_return
240     
241     
242 def parsexml_(*args, **kwargs):
243     if 'parser' not in kwargs:
244         kwargs['parser'] = ET.ETCompatXMLParser()
245     doc = ET.parse(*args, **kwargs)
246     return doc
247