root / utils / proxy.py @ 0492a5b5
History | View | Annotate | Download (12.7 kB)
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 |
|