Update Makefile to be more generic and more version number friendly
[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             if route_obj.protocol:
96                 route.match['protocol'].append(route_obj.protocol)
97             try:
98                 if route_obj.port:
99                     for port in route_obj.port.all():
100                         route.match['port'].append(port.port)
101             except:
102                 pass
103             try:
104                 if route_obj.destinationport:
105                     for port in route_obj.destinationport.all():
106                         route.match['destination-port'].append(port.port)
107             except:
108                 pass
109             try:
110                 if route_obj.sourceport:
111                     for port in route_obj.sourceport.all():
112                         route.match['source-port'].append(port.port)
113             except:
114                 pass
115             if route_obj.icmpcode:
116                 route.match['icmp-code'].append(route_obj.icmpcode)
117             if route_obj.icmptype:
118                 route.match['icmp-type'].append(route_obj.icmptype)
119             if route_obj.tcpflag:
120                 route.match['tcp-flags'].append(route_obj.tcpflag)
121             try:
122                 if route_obj.dscp:
123                     for dscp in route_obj.dscp.all():
124                         route.match['dscp'].append(dscp.dscp)
125             except:
126                 pass
127             if route_obj.fragmenttype:
128                 route.match['fragment'].append(route_obj.fragmenttype)
129             for thenaction in route_obj.then.all():
130                 if thenaction.action_value:
131                     route.then[thenaction.action] = thenaction.action_value
132                 else:
133                     route.then[thenaction.action] = True
134             if operation == "replace":
135                 logger.info("Requesting a replace operation")
136                 route.operation = operation
137             device = device.export(netconf_config=True)
138             return ET.tostring(device)
139         else:
140             return False
141
142     def delete_routes(self):
143         if self.route_objects:
144             logger.info("Generating XML config")
145             device = np.Device()
146             flow = np.Flow()
147             for route_object in self.route_objects:
148                 route_obj = route_object
149                 route = np.Route()
150                 flow.routes.append(route)
151                 route.name = route_obj.name
152                 route.operation = 'delete'
153             device.routing_options.append(flow)
154             device = device.export(netconf_config=True)
155             return ET.tostring(device)
156         else:
157             return False    
158     
159     def apply(self, configuration = None, operation=None):
160         reason = None
161         if not configuration:
162             configuration = self.to_xml(operation=operation)
163         edit_is_successful = False
164         commit_confirmed_is_successful = False
165         commit_is_successful = False
166         if configuration:
167             with manager.connect(host=self.device, port=830, username=self.username, password=self.password, unknown_host_cb=fod_unknown_host_cb) as m:
168                 assert(":candidate" in m.server_capabilities)
169                 with m.locked(target='candidate'):
170                     m.discard_changes()
171                     try:
172                         edit_response = m.edit_config(target='candidate', config=configuration, test_option='test-then-set')
173                         edit_is_successful, reason = is_successful(edit_response)
174                         logger.info("Successfully edited @ %s" % self.device)
175                         if not edit_is_successful:
176                             raise Exception()
177                     except Exception as e:
178                         cause="Caught edit exception: %s %s" %(e,reason)
179                         cause=cause.replace('\n', '')
180                         logger.error(cause)
181                         m.discard_changes()
182                         return False, cause
183                     if edit_is_successful:
184                         try:
185                             commit_confirmed_response = m.commit(confirmed=True, timeout=settings.COMMIT_CONFIRMED_TIMEOUT)
186                             commit_confirmed_is_successful, reason = is_successful(commit_confirmed_response)
187                                 
188                             if not commit_confirmed_is_successful:
189                                 raise Exception()
190                             else:
191                                 logger.info("Successfully confirmed committed @ %s" % self.device)
192                                 if not settings.COMMIT:
193                                     return True, "Successfully confirmed committed"
194                         except Exception as e:
195                             cause="Caught commit confirmed exception: %s %s" %(e,reason)
196                             cause=cause.replace('\n', '')
197                             logger.error(cause)
198                             return False, cause
199                         if settings.COMMIT:
200                             if edit_is_successful and commit_confirmed_is_successful:
201                                 try:
202                                     commit_response = m.commit(confirmed=False)
203                                     commit_is_successful, reason = is_successful(commit_response)
204                                     logger.info("Successfully committed @ %s" % self.device)
205                                     newconfig = m.get_config(source='running', filter=('subtree',settings.ROUTES_FILTER)).data_xml
206                                     retrieve = Retriever(xml=newconfig)
207                                     logger.info("[CACHE] caching device configuration")
208                                     cache.set("device", retrieve.proccess_xml(), 3600)
209                                     
210                                     if not commit_is_successful:
211                                         raise Exception()
212                                     else:
213                                         logger.info("Successfully cached device configuration")
214                                         return True, "Successfully committed"
215                                 except Exception as e:
216                                     cause="Caught commit exception: %s %s" %(e,reason)
217                                     cause=cause.replace('\n', '')
218                                     logger.error(cause)
219                                     return False, cause
220         else:
221             return False, "No configuration was supplied"
222             
223 def is_successful(response):
224     from StringIO import StringIO
225     doc = parsexml_(StringIO(response))
226     rootNode = doc.getroot()
227     success_list = rootNode.xpath("//*[local-name()='ok']")
228     if len(success_list)>0:
229         return True, None
230     else:
231         reason_return = ''
232         reason_list = rootNode.xpath("//*[local-name()='error-message']")
233         for reason in reason_list:
234             reason_return = "%s %s" %(reason_return, reason.text)  
235         return False, reason_return
236     
237     
238 def parsexml_(*args, **kwargs):
239     if 'parser' not in kwargs:
240         kwargs['parser'] = ET.ETCompatXMLParser()
241     doc = ET.parse(*args, **kwargs)
242     return doc
243