Add fragment type in application form
[flowspy] / utils / proxy.py
1 #
2 # -*- coding: utf-8 -*- vim:fileencoding=utf-8:
3 #Copyright © 2011-2013 Greek Research and Technology Network (GRNET S.A.)
4
5 #Developed by Leonidas Poulopoulos (leopoul-at-noc-dot-grnet-dot-gr),
6 #GRNET NOC
7 #
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.
11 #
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
18 #SOFTWARE.
19 #
20 import nxpy as np
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
25 import logging
26 from django.core.cache import cache
27 import os
28
29 cwd = os.getcwd()
30     
31
32 LOG_FILENAME = os.path.join(settings.LOG_FILE_LOCATION, 'celery_jobs.log')
33
34 #FORMAT = '%(asctime)s %(levelname)s: %(message)s'
35 #logging.basicConfig(format=FORMAT)
36 formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s')
37
38 logger = logging.getLogger(__name__)
39 logger.setLevel(logging.DEBUG)
40 handler = logging.FileHandler(LOG_FILENAME)
41 handler.setFormatter(formatter)
42 logger.addHandler(handler)
43
44 def fod_unknown_host_cb(host, fingerprint):
45     return True
46
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):
49         self.device = device
50         self.username = username
51         self.password = password
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=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
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):
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     
94     def to_xml(self, operation=None):
95         logger.info("Operation: %s"%operation)
96         if self.route_object:
97             logger.info("Generating XML config")
98             route_obj = self.route_object
99             device = np.Device()
100             flow = np.Flow()
101             route = np.Route()
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)
110             if route_obj.source:
111                 route.match['source'].append(route_obj.source)
112             if route_obj.destination:
113                 route.match['destination'].append(route_obj.destination)
114             try:
115                 if route_obj.protocol:
116                     for protocol in route_obj.protocol.all():
117                         route.match['protocol'].append(protocol.protocol)
118             except:
119                 pass
120             try:
121                 if route_obj.port:
122                     for port in route_obj.port.all():
123                         route.match['port'].append(port.port)
124             except:
125                 pass
126             try:
127                 if route_obj.destinationport:
128                     for port in route_obj.destinationport.all():
129                         route.match['destination-port'].append(port.port)
130             except:
131                 pass
132             try:
133                 if route_obj.sourceport:
134                     for port in route_obj.sourceport.all():
135                         route.match['source-port'].append(port.port)
136             except:
137                 pass
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)
144             try:
145                 if route_obj.dscp:
146                     for dscp in route_obj.dscp.all():
147                         route.match['dscp'].append(dscp.dscp)
148             except:
149                 pass
150             
151             try:
152                 if route_obj.fragmenttype:
153                     for frag in route_obj.fragmenttype.all():
154                         route.match['fragment'].append(frag.fragmenttype)
155             except:
156                 pass
157             
158             for thenaction in route_obj.then.all():
159                 if thenaction.action_value:
160                     route.then[thenaction.action] = thenaction.action_value
161                 else:
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)
168         else:
169             return False
170
171     def delete_routes(self):
172         if self.route_objects:
173             logger.info("Generating XML config")
174             device = np.Device()
175             flow = np.Flow()
176             for route_object in self.route_objects:
177                 route_obj = route_object
178                 route = np.Route()
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)
185         else:
186             return False    
187     
188     def apply(self, configuration = None, operation=None):
189         reason = 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
195         if configuration:
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'):
199                     m.discard_changes()
200                     try:
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:
205                             raise Exception()
206                     except Exception as e:
207                         cause="Caught edit exception: %s %s" %(e,reason)
208                         cause=cause.replace('\n', '')
209                         logger.error(cause)
210                         m.discard_changes()
211                         return False, cause
212                     if edit_is_successful:
213                         try:
214                             commit_confirmed_response = m.commit(confirmed=True, timeout=settings.COMMIT_CONFIRMED_TIMEOUT)
215                             commit_confirmed_is_successful, reason = is_successful(commit_confirmed_response)
216                                 
217                             if not commit_confirmed_is_successful:
218                                 raise Exception()
219                             else:
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', '')
226                             logger.error(cause)
227                             return False, cause
228                         if settings.COMMIT:
229                             if edit_is_successful and commit_confirmed_is_successful:
230                                 try:
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)
238                                     
239                                     if not commit_is_successful:
240                                         raise Exception()
241                                     else:
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', '')
247                                     logger.error(cause)
248                                     return False, cause
249         else:
250             return False, "No configuration was supplied"
251             
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:
258         return True, None
259     else:
260         reason_return = ''
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
265     
266     
267 def parsexml_(*args, **kwargs):
268     if 'parser' not in kwargs:
269         kwargs['parser'] = ET.ETCompatXMLParser()
270     doc = ET.parse(*args, **kwargs)
271     return doc
272