2 # -*- coding: utf-8 -*-
5 # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
6 # This file is part of openvim
9 # Licensed under the Apache License, Version 2.0 (the "License"); you may
10 # not use this file except in compliance with the License. You may obtain
11 # a copy of the License at
13 # http://www.apache.org/licenses/LICENSE-2.0
15 # Unless required by applicable law or agreed to in writing, software
16 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
17 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
18 # License for the specific language governing permissions and limitations
21 # For those usages not covered by the Apache License, Version 2.0 please
22 # contact with: nfvlabs@tid.es
26 Implement the plugging for OpendayLight openflow controller
27 It creates the class OF_conn to create dataplane connections
28 with static rules based on packet destination MAC address
31 __author__
="Pablo Montes, Alfonso Tierno"
32 __date__
="$28-oct-2014 12:07:15$"
42 class OF_conn(openflow_conn
.OpenflowConn
):
43 """OpenDayLight connector. No MAC learning is used"""
45 def __init__(self
, params
):
47 Params: dictionary with the following keys:
48 of_dpid: DPID to use for this controller
49 of_ip: controller IP address
50 of_port: controller TCP port
51 of_user: user credentials, can be missing or None
52 of_password: password credentials
53 of_debug: debug level for logging. Default to ERROR
54 other keys are ignored
55 Raise an exception if same parameter is missing or wrong
59 if "of_ip" not in params
or params
["of_ip"]==None or "of_port" not in params
or params
["of_port"]==None:
60 raise ValueError("IP address and port must be provided")
62 openflow_conn
.OpenflowConn
.__init
__(self
, params
)
64 self
.name
= "OpenDayLight"
65 self
.headers
= {'content-type': 'application/json', 'Accept': 'application/json'}
67 self
.pp2ofi
={} # From Physical Port to OpenFlow Index
68 self
.ofi2pp
={} # From OpenFlow Index to Physical Port
70 self
.dpid
= str(params
["of_dpid"])
71 self
.id = 'openflow:'+str(int(self
.dpid
.replace(':', ''), 16))
72 self
.url
= "http://%s:%s" %( str(params
["of_ip"]), str(params
["of_port"] ) )
73 if "of_user" in params
and params
["of_user"]!=None:
74 if not params
.get("of_password"):
77 of_password
=str(params
["of_password"])
78 self
.auth
= base64
.b64encode(str(params
["of_user"])+":"+of_password
)
79 self
.headers
['Authorization'] = 'Basic '+self
.auth
81 self
.logger
= logging
.getLogger('vim.OF.ODL')
82 self
.logger
.setLevel( getattr(logging
, params
.get("of_debug", "ERROR")) )
84 def get_of_switches(self
):
86 Obtain a a list of switches or DPID detected by this controller
87 :return: list length, and a list where each element a tuple pair (DPID, IP address)
88 Raise an OpenflowconnConnectionException exception if fails with text_error
91 of_response
= requests
.get(self
.url
+"/restconf/operational/opendaylight-inventory:nodes",
93 error_text
= "Openflow response %d: %s" % (of_response
.status_code
, of_response
.text
)
94 if of_response
.status_code
!= 200:
95 self
.logger
.warning("get_of_switches " + error_text
)
96 raise openflow_conn
.OpenflowconnUnexpectedResponse("Error get_of_switches " + error_text
)
98 self
.logger
.debug("get_of_switches " + error_text
)
99 info
= of_response
.json()
101 if type(info
) != dict:
102 self
.logger
.error("get_of_switches. Unexpected response, not a dict: %s", str(info
))
103 raise openflow_conn
.OpenflowconnUnexpectedResponse("Unexpected response, not a dict. Wrong version?")
105 nodes
= info
.get('nodes')
106 if type(nodes
) is not dict:
107 self
.logger
.error("get_of_switches. Unexpected response at 'nodes', not found or not a dict: %s", str(type(info
)))
108 raise openflow_conn
.OpenflowconnUnexpectedResponse("Unexpected response at 'nodes', not found or "
109 "not a dict. Wrong version?")
111 node_list
= nodes
.get('node')
112 if type(node_list
) is not list:
113 self
.logger
.error("get_of_switches. Unexpected response, at 'nodes':'node', "
114 "not found or not a list: %s", str(type(node_list
)))
115 raise openflow_conn
.OpenflowconnUnexpectedResponse("Unexpected response, at 'nodes':'node', not found "
116 "or not a list. Wrong version?")
119 for node
in node_list
:
120 node_id
= node
.get('id')
122 self
.logger
.error("get_of_switches. Unexpected response at 'nodes':'node'[]:'id', not found: %s", str(node
))
123 raise openflow_conn
.OpenflowconnUnexpectedResponse("Unexpected response at 'nodes':'node'[]:'id', "
124 "not found . Wrong version?")
126 if node_id
== 'controller-config':
129 node_ip_address
= node
.get('flow-node-inventory:ip-address')
130 if node_ip_address
is None:
131 self
.logger
.error("get_of_switches. Unexpected response at 'nodes':'node'[]:'flow-node-inventory:"
132 "ip-address', not found: %s", str(node
))
133 raise openflow_conn
.OpenflowconnUnexpectedResponse("Unexpected response at 'nodes':'node'[]:"
134 "'flow-node-inventory:ip-address', "
135 "not found. Wrong version?")
137 node_id_hex
=hex(int(node_id
.split(':')[1])).split('x')[1].zfill(16)
138 switch_list
.append( (':'.join(a
+b
for a
,b
in zip(node_id_hex
[::2], node_id_hex
[1::2])), node_ip_address
))
140 return len(switch_list
), switch_list
141 except requests
.exceptions
.RequestException
as e
:
142 error_text
= type(e
).__name
__ + ": " + str(e
)
143 self
.logger
.error("get_of_switches " + error_text
)
144 raise openflow_conn
.OpenflowconnConnectionException(error_text
)
145 except ValueError as e
:
146 # ValueError in the case that JSON can not be decoded
147 error_text
= type(e
).__name
__ + ": " + str(e
)
148 self
.logger
.error("get_of_switches " + error_text
)
149 raise openflow_conn
.OpenflowconnUnexpectedResponse(error_text
)
151 def obtain_port_correspondence(self
):
153 Obtain the correspondence between physical and openflow port names
154 :return: dictionary: with physical name as key, openflow name as value,
155 Raise a OpenflowconnConnectionException expection in case of failure
158 of_response
= requests
.get(self
.url
+"/restconf/operational/opendaylight-inventory:nodes",
159 headers
=self
.headers
)
160 error_text
= "Openflow response %d: %s" % (of_response
.status_code
, of_response
.text
)
161 if of_response
.status_code
!= 200:
162 self
.logger
.warning("obtain_port_correspondence " + error_text
)
163 raise openflow_conn
.OpenflowconnUnexpectedResponse(error_text
)
164 self
.logger
.debug("obtain_port_correspondence " + error_text
)
165 info
= of_response
.json()
167 if type(info
) != dict:
168 self
.logger
.error("obtain_port_correspondence. Unexpected response not a dict: %s", str(info
))
169 raise openflow_conn
.OpenflowconnUnexpectedResponse("Unexpected openflow response, not a dict. "
172 nodes
= info
.get('nodes')
173 if type(nodes
) is not dict:
174 self
.logger
.error("obtain_port_correspondence. Unexpected response at 'nodes', "
175 "not found or not a dict: %s", str(type(nodes
)))
176 raise openflow_conn
.OpenflowconnUnexpectedResponse("Unexpected response at 'nodes',not found or not a dict. Wrong version?")
178 node_list
= nodes
.get('node')
179 if type(node_list
) is not list:
180 self
.logger
.error("obtain_port_correspondence. Unexpected response, at 'nodes':'node', "
181 "not found or not a list: %s", str(type(node_list
)))
182 raise openflow_conn
.OpenflowconnUnexpectedResponse("Unexpected response, at 'nodes':'node', "
183 "not found or not a list. Wrong version?")
185 for node
in node_list
:
186 node_id
= node
.get('id')
188 self
.logger
.error("obtain_port_correspondence. Unexpected response at 'nodes':'node'[]:'id', "
189 "not found: %s", str(node
))
190 raise openflow_conn
.OpenflowconnUnexpectedResponse("Unexpected response at 'nodes':'node'[]:'id', "
191 "not found . Wrong version?")
193 if node_id
== 'controller-config':
196 # Figure out if this is the appropriate switch. The 'id' is 'openflow:' plus the decimal value
198 # In case this is not the desired switch, continue
199 if self
.id != node_id
:
202 node_connector_list
= node
.get('node-connector')
203 if type(node_connector_list
) is not list:
204 self
.logger
.error("obtain_port_correspondence. Unexpected response at "
205 "'nodes':'node'[]:'node-connector', not found or not a list: %s", str(node
))
206 raise openflow_conn
.OpenflowconnUnexpectedResponse("Unexpected response at 'nodes':'node'[]:"
207 "'node-connector', not found or not a list. "
210 for node_connector
in node_connector_list
:
211 self
.pp2ofi
[ str(node_connector
['flow-node-inventory:name']) ] = str(node_connector
['id'] )
212 self
.ofi2pp
[ node_connector
['id'] ] = str(node_connector
['flow-node-inventory:name'])
214 node_ip_address
= node
.get('flow-node-inventory:ip-address')
215 if node_ip_address
is None:
216 self
.logger
.error("obtain_port_correspondence. Unexpected response at 'nodes':'node'[]:"
217 "'flow-node-inventory:ip-address', not found: %s", str(node
))
218 raise openflow_conn
.OpenflowconnUnexpectedResponse("Unexpected response at 'nodes':'node'[]:"
219 "'flow-node-inventory:ip-address', not found. Wrong version?")
220 self
.ip_address
= node_ip_address
222 # If we found the appropriate dpid no need to continue in the for loop
225 # print self.name, ": obtain_port_correspondence ports:", self.pp2ofi
227 except requests
.exceptions
.RequestException
as e
:
228 error_text
= type(e
).__name
__ + ": " + str(e
)
229 self
.logger
.error("obtain_port_correspondence " + error_text
)
230 raise openflow_conn
.OpenflowconnConnectionException(error_text
)
231 except ValueError as e
:
232 # ValueError in the case that JSON can not be decoded
233 error_text
= type(e
).__name
__ + ": " + str(e
)
234 self
.logger
.error("obtain_port_correspondence " + error_text
)
235 raise openflow_conn
.OpenflowconnUnexpectedResponse(error_text
)
237 def get_of_rules(self
, translate_of_ports
=True):
239 Obtain the rules inserted at openflow controller
240 :param translate_of_ports:
241 :return: dict if ok: with the rule name as key and value is another dictionary with the following content:
242 priority: rule priority
243 name: rule name (present also as the master dict key)
244 ingress_port: match input port of the rule
245 dst_mac: match destination mac address of the rule, can be missing or None if not apply
246 vlan_id: match vlan tag of the rule, can be missing or None if not apply
247 actions: list of actions, composed by a pair tuples:
248 (vlan, None/int): for stripping/setting a vlan tag
249 (out, port): send to this port
251 Raise a OpenflowconnConnectionException expection in case of failure
257 if len(self
.ofi2pp
) == 0:
258 self
.obtain_port_correspondence()
260 of_response
= requests
.get(self
.url
+"/restconf/config/opendaylight-inventory:nodes/node/" + self
.id +
261 "/table/0", headers
=self
.headers
)
262 error_text
= "Openflow response %d: %s" % (of_response
.status_code
, of_response
.text
)
264 # The configured page does not exist if there are no rules installed. In that case we return an empty dict
265 if of_response
.status_code
== 404:
268 elif of_response
.status_code
!= 200:
269 self
.logger
.warning("get_of_rules " + error_text
)
270 raise openflow_conn
.OpenflowconnUnexpectedResponse(error_text
)
272 self
.logger
.debug("get_of_rules " + error_text
)
274 info
= of_response
.json()
276 if type(info
) != dict:
277 self
.logger
.error("get_of_rules. Unexpected response not a dict: %s", str(info
))
278 raise openflow_conn
.OpenflowconnUnexpectedResponse("Unexpected openflow response, not a dict. "
281 table
= info
.get('flow-node-inventory:table')
282 if type(table
) is not list:
283 self
.logger
.error("get_of_rules. Unexpected response at 'flow-node-inventory:table', "
284 "not a list: %s", str(type(table
)))
285 raise openflow_conn
.OpenflowconnUnexpectedResponse("Unexpected response at 'flow-node-inventory:table',"
286 " not a list. Wrong version?")
288 flow_list
= table
[0].get('flow')
289 if flow_list
is None:
292 if type(flow_list
) is not list:
293 self
.logger
.error("get_of_rules. Unexpected response at 'flow-node-inventory:table'[0]:'flow', not a list: %s", str(type(flow_list
)))
294 raise openflow_conn
.OpenflowconnUnexpectedResponse("Unexpected response at 'flow-node-inventory:"
295 "table'[0]:'flow', not a list. Wrong version?")
297 # TODO translate ports according to translate_of_ports parameter
300 for flow
in flow_list
:
301 if not ('id' in flow
and 'match' in flow
and 'instructions' in flow
and
302 'instruction' in flow
['instructions'] and
303 'apply-actions' in flow
['instructions']['instruction'][0] and
304 'action' in flow
['instructions']['instruction'][0]['apply-actions']):
305 raise openflow_conn
.OpenflowconnUnexpectedResponse("unexpected openflow response, one or more "
306 "elements are missing. Wrong version?")
308 flow
['instructions']['instruction'][0]['apply-actions']['action']
311 rule
['switch'] = self
.dpid
312 rule
['priority'] = flow
.get('priority')
313 # rule['name'] = flow['id']
314 # rule['cookie'] = flow['cookie']
315 if 'in-port' in flow
['match']:
316 in_port
= flow
['match']['in-port']
317 if not in_port
in self
.ofi2pp
:
318 raise openflow_conn
.OpenflowconnUnexpectedResponse("Error: Ingress port " + in_port
+
319 " is not in switch port list")
321 if translate_of_ports
:
322 in_port
= self
.ofi2pp
[in_port
]
324 rule
['ingress_port'] = in_port
326 if 'vlan-match' in flow
['match'] and 'vlan-id' in flow
['match']['vlan-match'] and \
327 'vlan-id' in flow
['match']['vlan-match']['vlan-id'] and \
328 'vlan-id-present' in flow
['match']['vlan-match']['vlan-id'] and \
329 flow
['match']['vlan-match']['vlan-id']['vlan-id-present'] == True:
330 rule
['vlan_id'] = flow
['match']['vlan-match']['vlan-id']['vlan-id']
332 if 'ethernet-match' in flow
['match'] and 'ethernet-destination' in flow
['match']['ethernet-match'] and \
333 'address' in flow
['match']['ethernet-match']['ethernet-destination']:
334 rule
['dst_mac'] = flow
['match']['ethernet-match']['ethernet-destination']['address']
336 instructions
=flow
['instructions']['instruction'][0]['apply-actions']['action']
339 for instruction
in instructions
:
340 if instruction
['order'] > max_index
:
341 max_index
= instruction
['order']
343 actions
=[None]*(max_index
+1)
344 for instruction
in instructions
:
345 if 'output-action' in instruction
:
346 if not 'output-node-connector' in instruction
['output-action']:
347 raise openflow_conn
.OpenflowconnUnexpectedResponse("unexpected openflow response, one or "
348 "more elementa are missing. "
351 out_port
= instruction
['output-action']['output-node-connector']
352 if not out_port
in self
.ofi2pp
:
353 raise openflow_conn
.OpenflowconnUnexpectedResponse("Error: Output port " + out_port
+
354 " is not in switch port list")
356 if translate_of_ports
:
357 out_port
= self
.ofi2pp
[out_port
]
359 actions
[instruction
['order']] = ('out',out_port
)
361 elif 'strip-vlan-action' in instruction
:
362 actions
[instruction
['order']] = ('vlan', None)
364 elif 'set-field' in instruction
:
365 if not ('vlan-match' in instruction
['set-field'] and 'vlan-id' in instruction
['set-field']['vlan-match'] and 'vlan-id' in instruction
['set-field']['vlan-match']['vlan-id']):
366 raise openflow_conn
.OpenflowconnUnexpectedResponse("unexpected openflow response, one or "
367 "more elements are missing. "
370 actions
[instruction
['order']] = ('vlan', instruction
['set-field']['vlan-match']['vlan-id']['vlan-id'])
372 actions
= [x
for x
in actions
if x
!= None]
374 rule
['actions'] = list(actions
)
375 rules
[flow
['id']] = dict(rule
)
380 #flow['match']['in-port']
381 #flow['match']['vlan-match']['vlan-id']['vlan-id']
383 # -> vlan-match -> vlan-id -> vlan-id
384 #flow['match']['vlan-match']['vlan-id']['vlan-id-present']
385 #TODO we asume that is not using rules with vlan-id-present:false
386 #instructions -> instruction -> apply-actions -> action
387 #instructions=flow['instructions']['instruction'][0]['apply-actions']['action']
388 #Es una lista. Posibles elementos:
390 #for instruction in instructions:
391 # if instruction['order'] > max_index:
392 # max_index = instruction['order']
393 #actions=[None]*(max_index+1)
394 #for instruction in instructions:
395 # if 'output-action' in instruction:
396 # actions[instruction['order']] = ('out',instruction['output-action']['output-node-connector'])
397 # elif 'strip-vlan-action' in instruction:
398 # actions[instruction['order']] = ('vlan', None)
399 # elif 'set-field' in instruction:
400 # actions[instruction['order']] = ('vlan', instruction['set-field']['vlan-match']['vlan-id']['vlan-id'])
402 #actions = [x for x in actions if x != None]
403 # -> output-action -> output-node-connector
406 except requests
.exceptions
.RequestException
as e
:
407 error_text
= type(e
).__name
__ + ": " + str(e
)
408 self
.logger
.error("get_of_rules " + error_text
)
409 raise openflow_conn
.OpenflowconnConnectionException(error_text
)
410 except ValueError as e
:
411 # ValueError in the case that JSON can not be decoded
412 error_text
= type(e
).__name
__ + ": " + str(e
)
413 self
.logger
.error("get_of_rules " + error_text
)
414 raise openflow_conn
.OpenflowconnUnexpectedResponse(error_text
)
416 def del_flow(self
, flow_name
):
418 Delete an existing rule
419 :param flow_name: flow_name, this is the rule name
420 :return: Raise a OpenflowconnConnectionException expection in case of failure
424 of_response
= requests
.delete(self
.url
+"/restconf/config/opendaylight-inventory:nodes/node/" + self
.id +
425 "/table/0/flow/"+flow_name
, headers
=self
.headers
)
426 error_text
= "Openflow response %d: %s" % (of_response
.status_code
, of_response
.text
)
427 if of_response
.status_code
!= 200:
428 self
.logger
.warning("del_flow " + error_text
)
429 raise openflow_conn
.OpenflowconnUnexpectedResponse(error_text
)
430 self
.logger
.debug("del_flow OK " + error_text
)
432 except requests
.exceptions
.RequestException
as e
:
433 # raise an exception in case of contection error
434 error_text
= type(e
).__name
__ + ": " + str(e
)
435 self
.logger
.error("del_flow " + error_text
)
436 raise openflow_conn
.OpenflowconnConnectionException(error_text
)
438 def new_flow(self
, data
):
440 Insert a new static rule
441 :param data: dictionary with the following content:
442 priority: rule priority
444 ingress_port: match input port of the rule
445 dst_mac: match destination mac address of the rule, missing or None if not apply
446 vlan_id: match vlan tag of the rule, missing or None if not apply
447 actions: list of actions, composed by a pair tuples with these posibilities:
448 ('vlan', None/int): for stripping/setting a vlan tag
449 ('out', port): send to this port
450 :return: Raise a OpenflowconnConnectionException expection in case of failure
455 if len(self
.pp2ofi
) == 0:
456 self
.obtain_port_correspondence()
458 # We have to build the data for the opendaylight call from the generic data
460 sdata
['flow-node-inventory:flow'] = list()
461 sdata
['flow-node-inventory:flow'].append(dict())
462 flow
= sdata
['flow-node-inventory:flow'][0]
463 flow
['id'] = data
['name']
464 flow
['flow-name'] = data
['name']
465 flow
['idle-timeout'] = 0
466 flow
['hard-timeout'] = 0
468 flow
['priority'] = data
.get('priority')
469 flow
['match'] = dict()
470 if not data
['ingress_port'] in self
.pp2ofi
:
471 error_text
= 'Error. Port '+data
['ingress_port']+' is not present in the switch'
472 self
.logger
.warning("new_flow " + error_text
)
473 raise openflow_conn
.OpenflowconnUnexpectedResponse(error_text
)
474 flow
['match']['in-port'] = self
.pp2ofi
[data
['ingress_port']]
475 if 'dst_mac' in data
:
476 flow
['match']['ethernet-match'] = dict()
477 flow
['match']['ethernet-match']['ethernet-destination'] = dict()
478 flow
['match']['ethernet-match']['ethernet-destination']['address'] = data
['dst_mac']
479 if data
.get('vlan_id'):
480 flow
['match']['vlan-match'] = dict()
481 flow
['match']['vlan-match']['vlan-id'] = dict()
482 flow
['match']['vlan-match']['vlan-id']['vlan-id-present'] = True
483 flow
['match']['vlan-match']['vlan-id']['vlan-id'] = int(data
['vlan_id'])
484 flow
['instructions'] = dict()
485 flow
['instructions']['instruction'] = list()
486 flow
['instructions']['instruction'].append(dict())
487 flow
['instructions']['instruction'][0]['order'] = 1
488 flow
['instructions']['instruction'][0]['apply-actions'] = dict()
489 flow
['instructions']['instruction'][0]['apply-actions']['action'] = list()
490 actions
= flow
['instructions']['instruction'][0]['apply-actions']['action']
493 for action
in data
['actions']:
494 new_action
= { 'order': order
}
495 if action
[0] == "vlan":
496 if action
[1] == None:
498 new_action
['strip-vlan-action'] = dict()
500 new_action
['set-field'] = dict()
501 new_action
['set-field']['vlan-match'] = dict()
502 new_action
['set-field']['vlan-match']['vlan-id'] = dict()
503 new_action
['set-field']['vlan-match']['vlan-id']['vlan-id-present'] = True
504 new_action
['set-field']['vlan-match']['vlan-id']['vlan-id'] = int(action
[1])
505 elif action
[0] == 'out':
506 new_action
['output-action'] = dict()
507 if not action
[1] in self
.pp2ofi
:
508 error_msj
= 'Port '+action
[1]+' is not present in the switch'
509 raise openflow_conn
.OpenflowconnUnexpectedResponse(error_msj
)
511 new_action
['output-action']['output-node-connector'] = self
.pp2ofi
[ action
[1] ]
513 error_msj
= "Unknown item '%s' in action list" % action
[0]
514 self
.logger
.error("new_flow " + error_msj
)
515 raise openflow_conn
.OpenflowconnUnexpectedResponse(error_msj
)
517 actions
.append(new_action
)
520 # print json.dumps(sdata)
521 of_response
= requests
.put(self
.url
+"/restconf/config/opendaylight-inventory:nodes/node/" + self
.id +
522 "/table/0/flow/" + data
['name'],
523 headers
=self
.headers
, data
=json
.dumps(sdata
) )
524 error_text
= "Openflow response %d: %s" % (of_response
.status_code
, of_response
.text
)
525 if of_response
.status_code
!= 200:
526 self
.logger
.warning("new_flow " + error_text
)
527 raise openflow_conn
.OpenflowconnUnexpectedResponse(error_text
)
528 self
.logger
.debug("new_flow OK " + error_text
)
531 except requests
.exceptions
.RequestException
as e
:
532 # raise an exception in case of contection error
533 error_text
= type(e
).__name
__ + ": " + str(e
)
534 self
.logger
.error("new_flow " + error_text
)
535 raise openflow_conn
.OpenflowconnConnectionException(error_text
)
537 def clear_all_flows(self
):
539 Delete all existing rules
540 :return: Raise a OpenflowconnConnectionException expection in case of failure
543 of_response
= requests
.delete(self
.url
+"/restconf/config/opendaylight-inventory:nodes/node/" + self
.id +
544 "/table/0", headers
=self
.headers
)
545 error_text
= "Openflow response %d: %s" % (of_response
.status_code
, of_response
.text
)
546 if of_response
.status_code
!= 200 and of_response
.status_code
!= 404: #HTTP_Not_Found
547 self
.logger
.warning("clear_all_flows " + error_text
)
548 raise openflow_conn
.OpenflowconnUnexpectedResponse(error_text
)
549 self
.logger
.debug("clear_all_flows OK " + error_text
)
550 except requests
.exceptions
.RequestException
as e
:
551 error_text
= type(e
).__name
__ + ": " + str(e
)
552 self
.logger
.error("clear_all_flows " + error_text
)
553 raise openflow_conn
.OpenflowconnConnectionException(error_text
)