Switch to GPLv3
[flowspy] / utils / proxy.py
1 # -*- coding: utf-8 -*- vim:fileencoding=utf-8:
2 # vim: tabstop=4:shiftwidth=4:softtabstop=4:expandtab
3
4 # Copyright (C) 2010-2014 GRNET S.A.
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
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 from celery.exceptions import TimeLimitExceeded, SoftTimeLimitExceeded
29
30 cwd = os.getcwd()
31     
32
33 LOG_FILENAME = os.path.join(settings.LOG_FILE_LOCATION, 'celery_jobs.log')
34
35 #FORMAT = '%(asctime)s %(levelname)s: %(message)s'
36 #logging.basicConfig(format=FORMAT)
37 formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s')
38
39 logger = logging.getLogger(__name__)
40 logger.setLevel(logging.DEBUG)
41 handler = logging.FileHandler(LOG_FILENAME)
42 handler.setFormatter(formatter)
43 logger.addHandler(handler)
44
45 def fod_unknown_host_cb(host, fingerprint):
46     return True
47
48 class Retriever(object):
49     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):
50         self.device = device
51         self.username = username
52         self.password = password
53         self.port = port
54         self.filter = filter
55         self.xml = xml
56         if route_name:
57             self.filter = settings.ROUTE_FILTER%route_name
58     
59     def fetch_xml(self):
60         with manager.connect(host=self.device, port=self.port, username=self.username, password=self.password, hostkey_verify=False) as m:
61             xmlconfig = m.get_config(source='running', filter=('subtree',self.filter)).data_xml
62         return xmlconfig
63     
64     def proccess_xml(self):
65         if self.xml:
66             xmlconfig = self.xml
67         else:
68             xmlconfig = self.fetch_xml()
69         parser = np.Parser()
70         parser.confile = xmlconfig
71         device = parser.export()
72         return device    
73     
74     def fetch_device(self):
75         device = cache.get("device")
76         logger.info("[CACHE] hit! got device")
77         if device:
78             return device
79         else:
80             device = self.proccess_xml()
81             if device.routing_options:
82                 cache.set("device", device, 3600)
83                 logger.info("[CACHE] miss, setting device")
84                 return device
85             else:
86                 return False
87
88 class Applier(object):
89     def __init__(self, route_objects = [], route_object=None, device=settings.NETCONF_DEVICE, username=settings.NETCONF_USER, password=settings.NETCONF_PASS, port=settings.NETCONF_PORT):
90         self.route_object = route_object
91         self.route_objects = route_objects
92         self.device = device
93         self.username = username
94         self.password = password
95         self.port = port
96     
97     def to_xml(self, operation=None):
98         logger.info("Operation: %s"%operation)
99         if self.route_object:
100             logger.info("Generating XML config")
101             route_obj = self.route_object
102             device = np.Device()
103             flow = np.Flow()
104             route = np.Route()
105             flow.routes.append(route)
106             device.routing_options.append(flow)
107             route.name = route_obj.name
108             if operation == "delete":
109                 logger.info("Requesting a delete operation")
110                 route.operation = operation
111                 device = device.export(netconf_config=True)
112                 return ET.tostring(device)
113             if route_obj.source:
114                 route.match['source'].append(route_obj.source)
115             if route_obj.destination:
116                 route.match['destination'].append(route_obj.destination)
117             try:
118                 if route_obj.protocol:
119                     for protocol in route_obj.protocol.all():
120                         route.match['protocol'].append(protocol.protocol)
121             except:
122                 pass
123             try:
124                 if route_obj.port:
125                     for port in route_obj.port.all():
126                         route.match['port'].append(port.port)
127             except:
128                 pass
129             try:
130                 if route_obj.destinationport:
131                     for port in route_obj.destinationport.all():
132                         route.match['destination-port'].append(port.port)
133             except:
134                 pass
135             try:
136                 if route_obj.sourceport:
137                     for port in route_obj.sourceport.all():
138                         route.match['source-port'].append(port.port)
139             except:
140                 pass
141             if route_obj.icmpcode:
142                 route.match['icmp-code'].append(route_obj.icmpcode)
143             if route_obj.icmptype:
144                 route.match['icmp-type'].append(route_obj.icmptype)
145             if route_obj.tcpflag:
146                 route.match['tcp-flags'].append(route_obj.tcpflag)
147             try:
148                 if route_obj.dscp:
149                     for dscp in route_obj.dscp.all():
150                         route.match['dscp'].append(dscp.dscp)
151             except:
152                 pass
153             
154             try:
155                 if route_obj.fragmenttype:
156                     for frag in route_obj.fragmenttype.all():
157                         route.match['fragment'].append(frag.fragmenttype)
158             except:
159                 pass
160             
161             for thenaction in route_obj.then.all():
162                 if thenaction.action_value:
163                     route.then[thenaction.action] = thenaction.action_value
164                 else:
165                     route.then[thenaction.action] = True
166             if operation == "replace":
167                 logger.info("Requesting a replace operation")
168                 route.operation = operation
169             device = device.export(netconf_config=True)
170             return ET.tostring(device)
171         else:
172             return False
173
174     def delete_routes(self):
175         if self.route_objects:
176             logger.info("Generating XML config")
177             device = np.Device()
178             flow = np.Flow()
179             for route_object in self.route_objects:
180                 route_obj = route_object
181                 route = np.Route()
182                 flow.routes.append(route)
183                 route.name = route_obj.name
184                 route.operation = 'delete'
185             device.routing_options.append(flow)
186             device = device.export(netconf_config=True)
187             return ET.tostring(device)
188         else:
189             return False    
190     
191     def apply(self, configuration = None, operation=None):
192         reason = None
193         if not configuration:
194             configuration = self.to_xml(operation=operation)
195         edit_is_successful = False
196         commit_confirmed_is_successful = False
197         commit_is_successful = False
198         if configuration:
199             with manager.connect(host=self.device, port=self.port, username=self.username, password=self.password, hostkey_verify=False) as m:
200                 assert(":candidate" in m.server_capabilities)
201                 with m.locked(target='candidate'):
202                     m.discard_changes()
203                     try:
204                         edit_response = m.edit_config(target='candidate', config=configuration, test_option='test-then-set')
205                         edit_is_successful, reason = is_successful(edit_response)
206                         logger.info("Successfully edited @ %s" % self.device)
207                         if not edit_is_successful:
208                             raise Exception()
209                     except SoftTimeLimitExceeded:
210                         cause="Task timeout"
211                         logger.error(cause)
212                         return False, cause
213                     except TimeLimitExceeded:
214                         cause="Task timeout"
215                         logger.error(cause)
216                         return False, cause
217                     except Exception as e:
218                         cause="Caught edit exception: %s %s" %(e,reason)
219                         cause=cause.replace('\n', '')
220                         logger.error(cause)
221                         m.discard_changes()
222                         return False, cause
223                     if edit_is_successful:
224                         try:
225                             commit_confirmed_response = m.commit(confirmed=True, timeout=settings.COMMIT_CONFIRMED_TIMEOUT)
226                             commit_confirmed_is_successful, reason = is_successful(commit_confirmed_response)
227                                 
228                             if not commit_confirmed_is_successful:
229                                 raise Exception()
230                             else:
231                                 logger.info("Successfully confirmed committed @ %s" % self.device)
232                                 if not settings.COMMIT:
233                                     return True, "Successfully confirmed committed"
234                         except SoftTimeLimitExceeded:
235                             cause="Task timeout"
236                             logger.error(cause)
237                             return False, cause
238                         except TimeLimitExceeded:
239                             cause="Task timeout"
240                             logger.error(cause)
241                             return False, cause
242                         except Exception as e:
243                             cause="Caught commit confirmed exception: %s %s" %(e,reason)
244                             cause=cause.replace('\n', '')
245                             logger.error(cause)
246                             return False, cause
247                         
248                         if settings.COMMIT:
249                             if edit_is_successful and commit_confirmed_is_successful:
250                                 try:
251                                     commit_response = m.commit(confirmed=False)
252                                     commit_is_successful, reason = is_successful(commit_response)
253                                     logger.info("Successfully committed @ %s" % self.device)
254                                     newconfig = m.get_config(source='running', filter=('subtree',settings.ROUTES_FILTER)).data_xml
255                                     retrieve = Retriever(xml=newconfig)
256                                     logger.info("[CACHE] caching device configuration")
257                                     cache.set("device", retrieve.proccess_xml(), 3600)
258                                     
259                                     if not commit_is_successful:
260                                         raise Exception()
261                                     else:
262                                         logger.info("Successfully cached device configuration")
263                                         return True, "Successfully committed"
264                                 except SoftTimeLimitExceeded:
265                                     cause="Task timeout"
266                                     logger.error(cause)
267                                     return False, cause
268                                 except TimeLimitExceeded:
269                                     cause="Task timeout"
270                                     logger.error(cause)
271                                     return False, cause
272                                 except Exception as e:
273                                     cause="Caught commit exception: %s %s" %(e,reason)
274                                     cause=cause.replace('\n', '')
275                                     logger.error(cause)
276                                     return False, cause
277         else:
278             return False, "No configuration was supplied"
279             
280 def is_successful(response):
281     from StringIO import StringIO
282     doc = parsexml_(StringIO(response))
283     rootNode = doc.getroot()
284     success_list = rootNode.xpath("//*[local-name()='ok']")
285     if len(success_list)>0:
286         return True, None
287     else:
288         reason_return = ''
289         reason_list = rootNode.xpath("//*[local-name()='error-message']")
290         for reason in reason_list:
291             reason_return = "%s %s" %(reason_return, reason.text)  
292         return False, reason_return
293     
294     
295 def parsexml_(*args, **kwargs):
296     if 'parser' not in kwargs:
297         kwargs['parser'] = ET.ETCompatXMLParser()
298     doc = ET.parse(*args, **kwargs)
299     return doc
300