Statistics
| Branch: | Tag: | Revision:

root / utils / proxy.py @ fb5ebbe2

History | View | Annotate | Download (11.6 kB)

1
# -*- coding: utf-8 -*- vim:fileencoding=utf-8:
2
# vim: tabstop=4:shiftwidth=4:softtabstop=4:expandtab
3

    
4
# Copyright © 2011-2014 Greek Research and Technology Network (GRNET S.A.)
5
# Copyright © 2011-2014 Leonidas Poulopoulos (@leopoul)
6
# 
7
# Permission to use, copy, modify, and/or distribute this software for any
8
# purpose with or without fee is hereby granted, provided that the above
9
# copyright notice and this permission notice appear in all copies.
10
# 
11
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
12
# TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
13
# FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
14
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
15
# DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
16
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
17
# SOFTWARE.
18

    
19
import nxpy as np
20
from ncclient import manager
21
from ncclient.transport.errors import AuthenticationError, SSHError
22
from lxml import etree as ET
23
from django.conf import settings
24
import logging
25
from django.core.cache import cache
26
import os
27

    
28
cwd = os.getcwd()
29
    
30

    
31
LOG_FILENAME = os.path.join(settings.LOG_FILE_LOCATION, 'celery_jobs.log')
32

    
33
#FORMAT = '%(asctime)s %(levelname)s: %(message)s'
34
#logging.basicConfig(format=FORMAT)
35
formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s')
36

    
37
logger = logging.getLogger(__name__)
38
logger.setLevel(logging.DEBUG)
39
handler = logging.FileHandler(LOG_FILENAME)
40
handler.setFormatter(formatter)
41
logger.addHandler(handler)
42

    
43
def fod_unknown_host_cb(host, fingerprint):
44
    return True
45

    
46
class Retriever(object):
47
    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):
48
        self.device = device
49
        self.username = username
50
        self.password = password
51
        self.port = port
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=self.port, username=self.username, password=self.password, hostkey_verify=False) 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, port=settings.NETCONF_PORT):
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
        self.port = port
94
    
95
    def to_xml(self, operation=None):
96
        logger.info("Operation: %s"%operation)
97
        if self.route_object:
98
            logger.info("Generating XML config")
99
            route_obj = self.route_object
100
            device = np.Device()
101
            flow = np.Flow()
102
            route = np.Route()
103
            flow.routes.append(route)
104
            device.routing_options.append(flow)
105
            route.name = route_obj.name
106
            if operation == "delete":
107
                logger.info("Requesting a delete operation")
108
                route.operation = operation
109
                device = device.export(netconf_config=True)
110
                return ET.tostring(device)
111
            if route_obj.source:
112
                route.match['source'].append(route_obj.source)
113
            if route_obj.destination:
114
                route.match['destination'].append(route_obj.destination)
115
            try:
116
                if route_obj.protocol:
117
                    for protocol in route_obj.protocol.all():
118
                        route.match['protocol'].append(protocol.protocol)
119
            except:
120
                pass
121
            try:
122
                if route_obj.port:
123
                    for port in route_obj.port.all():
124
                        route.match['port'].append(port.port)
125
            except:
126
                pass
127
            try:
128
                if route_obj.destinationport:
129
                    for port in route_obj.destinationport.all():
130
                        route.match['destination-port'].append(port.port)
131
            except:
132
                pass
133
            try:
134
                if route_obj.sourceport:
135
                    for port in route_obj.sourceport.all():
136
                        route.match['source-port'].append(port.port)
137
            except:
138
                pass
139
            if route_obj.icmpcode:
140
                route.match['icmp-code'].append(route_obj.icmpcode)
141
            if route_obj.icmptype:
142
                route.match['icmp-type'].append(route_obj.icmptype)
143
            if route_obj.tcpflag:
144
                route.match['tcp-flags'].append(route_obj.tcpflag)
145
            try:
146
                if route_obj.dscp:
147
                    for dscp in route_obj.dscp.all():
148
                        route.match['dscp'].append(dscp.dscp)
149
            except:
150
                pass
151
            
152
            try:
153
                if route_obj.fragmenttype:
154
                    for frag in route_obj.fragmenttype.all():
155
                        route.match['fragment'].append(frag.fragmenttype)
156
            except:
157
                pass
158
            
159
            for thenaction in route_obj.then.all():
160
                if thenaction.action_value:
161
                    route.then[thenaction.action] = thenaction.action_value
162
                else:
163
                    route.then[thenaction.action] = True
164
            if operation == "replace":
165
                logger.info("Requesting a replace operation")
166
                route.operation = operation
167
            device = device.export(netconf_config=True)
168
            return ET.tostring(device)
169
        else:
170
            return False
171

    
172
    def delete_routes(self):
173
        if self.route_objects:
174
            logger.info("Generating XML config")
175
            device = np.Device()
176
            flow = np.Flow()
177
            for route_object in self.route_objects:
178
                route_obj = route_object
179
                route = np.Route()
180
                flow.routes.append(route)
181
                route.name = route_obj.name
182
                route.operation = 'delete'
183
            device.routing_options.append(flow)
184
            device = device.export(netconf_config=True)
185
            return ET.tostring(device)
186
        else:
187
            return False    
188
    
189
    def apply(self, configuration = None, operation=None):
190
        reason = None
191
        if not configuration:
192
            configuration = self.to_xml(operation=operation)
193
        edit_is_successful = False
194
        commit_confirmed_is_successful = False
195
        commit_is_successful = False
196
        if configuration:
197
            with manager.connect(host=self.device, port=self.port, username=self.username, password=self.password, hostkey_verify=False) as m:
198
                assert(":candidate" in m.server_capabilities)
199
                with m.locked(target='candidate'):
200
                    m.discard_changes()
201
                    try:
202
                        edit_response = m.edit_config(target='candidate', config=configuration, test_option='test-then-set')
203
                        edit_is_successful, reason = is_successful(edit_response)
204
                        logger.info("Successfully edited @ %s" % self.device)
205
                        if not edit_is_successful:
206
                            raise Exception()
207
                    except Exception as e:
208
                        cause="Caught edit exception: %s %s" %(e,reason)
209
                        cause=cause.replace('\n', '')
210
                        logger.error(cause)
211
                        m.discard_changes()
212
                        return False, cause
213
                    if edit_is_successful:
214
                        try:
215
                            commit_confirmed_response = m.commit(confirmed=True, timeout=settings.COMMIT_CONFIRMED_TIMEOUT)
216
                            commit_confirmed_is_successful, reason = is_successful(commit_confirmed_response)
217
                                
218
                            if not commit_confirmed_is_successful:
219
                                raise Exception()
220
                            else:
221
                                logger.info("Successfully confirmed committed @ %s" % self.device)
222
                                if not settings.COMMIT:
223
                                    return True, "Successfully confirmed committed"
224
                        except Exception as e:
225
                            cause="Caught commit confirmed exception: %s %s" %(e,reason)
226
                            cause=cause.replace('\n', '')
227
                            logger.error(cause)
228
                            return False, cause
229
                        if settings.COMMIT:
230
                            if edit_is_successful and commit_confirmed_is_successful:
231
                                try:
232
                                    commit_response = m.commit(confirmed=False)
233
                                    commit_is_successful, reason = is_successful(commit_response)
234
                                    logger.info("Successfully committed @ %s" % self.device)
235
                                    newconfig = m.get_config(source='running', filter=('subtree',settings.ROUTES_FILTER)).data_xml
236
                                    retrieve = Retriever(xml=newconfig)
237
                                    logger.info("[CACHE] caching device configuration")
238
                                    cache.set("device", retrieve.proccess_xml(), 3600)
239
                                    
240
                                    if not commit_is_successful:
241
                                        raise Exception()
242
                                    else:
243
                                        logger.info("Successfully cached device configuration")
244
                                        return True, "Successfully committed"
245
                                except Exception as e:
246
                                    cause="Caught commit exception: %s %s" %(e,reason)
247
                                    cause=cause.replace('\n', '')
248
                                    logger.error(cause)
249
                                    return False, cause
250
        else:
251
            return False, "No configuration was supplied"
252
            
253
def is_successful(response):
254
    from StringIO import StringIO
255
    doc = parsexml_(StringIO(response))
256
    rootNode = doc.getroot()
257
    success_list = rootNode.xpath("//*[local-name()='ok']")
258
    if len(success_list)>0:
259
        return True, None
260
    else:
261
        reason_return = ''
262
        reason_list = rootNode.xpath("//*[local-name()='error-message']")
263
        for reason in reason_list:
264
            reason_return = "%s %s" %(reason_return, reason.text)  
265
        return False, reason_return
266
    
267
    
268
def parsexml_(*args, **kwargs):
269
    if 'parser' not in kwargs:
270
        kwargs['parser'] = ET.ETCompatXMLParser()
271
    doc = ET.parse(*args, **kwargs)
272
    return doc
273