2 # -*- coding: utf-8 -*-
5 # Copyright 2015 Telefonica Investigacion 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 floodligth 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$"
37 from osm_ro_plugin
.openflow_conn
import OpenflowConn
, OpenflowConnUnexpectedResponse
, OpenflowConnConnectionException
40 class OfConnFloodLight(OpenflowConn
):
42 Openflow Connector for Floodlight.
43 No MAC learning is used
44 version 0.9 or 1.X is autodetected
45 version 1.X is in progress, not finished!!!
48 def __init__(self
, params
):
51 :param params: dictionary with the following keys:
52 of_dpid: DPID to use for this controller ?? Does a controller have a dpid?
53 url: must be [http://HOST:PORT/]
54 of_user: user credentials, can be missing or None
55 of_password: password credentials
56 of_debug: debug level for logging. Default to ERROR
57 other keys are ignored
58 Raise an exception if same parameter is missing or wrong
61 url
= params
.get("of_url")
63 raise ValueError("'url' must be provided")
64 if not url
.startswith("http"):
66 if not url
.endswith("/"):
70 OpenflowConn
.__init
__(self
, params
)
72 self
.name
= "Floodlight"
73 self
.dpid
= str(params
["of_dpid"])
75 self
.pp2ofi
= {} # From Physical Port to OpenFlow Index
76 self
.ofi2pp
= {} # From OpenFlow Index to Physical Port
77 self
.headers
= {'content-type': 'application/json', 'Accept': 'application/json'}
79 self
.logger
= logging
.getLogger('SDN.floodlightOF')
80 self
.logger
.setLevel(params
.get("of_debug", "ERROR"))
81 self
._set
_version
(params
.get("of_version"))
83 def _set_version(self
, version
):
85 set up a version of the controller.
86 Depending on the version it fills the self.ver_names with the naming used in this version
87 :param version: Openflow controller version
88 :return: Raise an ValueError exception if same parameter is missing or wrong
90 # static version names
93 elif version
== "0.9":
94 self
.version
= version
95 self
.name
= "Floodlightv0.9"
98 "URLmodifier": "staticflowentrypusher",
101 "inport": "ingress-port",
102 "setvlan": "set-vlan-id",
103 "stripvlan": "strip-vlan",
105 elif version
[0] == "1": # version 1.X
106 self
.version
= version
107 self
.name
= "Floodlightv1.X"
109 "dpid": "switchDPID",
110 "URLmodifier": "staticflowpusher",
111 "destmac": "eth_dst",
112 "vlanid": "eth_vlan_vid",
114 "setvlan": "set_vlan_vid",
115 "stripvlan": "strip_vlan",
118 raise ValueError("Invalid version for floodlight controller")
120 def get_of_switches(self
):
122 Obtain a a list of switches or DPID detected by this controller
123 :return: list where each element a tuple pair (DPID, IP address)
124 Raise an OpenflowconnConnectionException or OpenflowconnConnectionException exception if same
125 parameter is missing or wrong
128 of_response
= requests
.get(self
.url
+ "wm/core/controller/switches/json", headers
=self
.headers
)
129 error_text
= "Openflow response {}: {}".format(of_response
.status_code
, of_response
.text
)
130 if of_response
.status_code
!= 200:
131 self
.logger
.warning("get_of_switches " + error_text
)
132 raise OpenflowConnUnexpectedResponse(error_text
)
133 self
.logger
.debug("get_of_switches " + error_text
)
134 info
= of_response
.json()
135 if not isinstance(info
, (list, tuple)):
136 self
.logger
.error("get_of_switches. Unexpected response not a list %s", str(type(info
)))
137 raise OpenflowConnUnexpectedResponse("Unexpected response, not a list. Wrong version?")
140 # autodiscover version
141 if self
.version
is None:
142 if 'dpid' in info
[0] and 'inetAddress' in info
[0]:
143 self
._set
_version
("0.9")
144 # elif 'switchDPID' in info[0] and 'inetAddress' in info[0]:
145 # self._set_version("1.X")
147 self
.logger
.error("get_of_switches. Unexpected response, not found 'dpid' or 'switchDPID' "
148 "field: %s", str(info
[0]))
149 raise OpenflowConnUnexpectedResponse("Unexpected response, not found 'dpid' or "
150 "'switchDPID' field. Wrong version?")
154 switch_list
.append((switch
[self
.ver_names
["dpid"]], switch
['inetAddress']))
156 except requests
.exceptions
.RequestException
as e
:
157 error_text
= type(e
).__name
__ + ": " + str(e
)
158 self
.logger
.error("get_of_switches " + error_text
)
159 raise OpenflowConnConnectionException(error_text
)
160 except Exception as e
:
161 # ValueError in the case that JSON can not be decoded
162 error_text
= type(e
).__name
__ + ": " + str(e
)
163 self
.logger
.error("get_of_switches " + error_text
)
164 raise OpenflowConnUnexpectedResponse(error_text
)
166 def get_of_rules(self
, translate_of_ports
=True):
168 Obtain the rules inserted at openflow controller
169 :param translate_of_ports: if True it translates ports from openflow index to physical switch name
170 :return: list where each item is a dictionary with the following content:
171 priority: rule priority
172 name: rule name (present also as the master dict key)
173 ingress_port: match input port of the rule
174 dst_mac: match destination mac address of the rule, can be missing or None if not apply
175 vlan_id: match vlan tag of the rule, can be missing or None if not apply
176 actions: list of actions, composed by a pair tuples:
177 (vlan, None/int): for stripping/setting a vlan tag
178 (out, port): send to this port
180 Raise an openflowconnUnexpectedResponse exception if fails with text_error
184 # get translation, autodiscover version
185 if len(self
.ofi2pp
) == 0:
186 self
.obtain_port_correspondence()
188 of_response
= requests
.get(self
.url
+ "wm/{}/list/{}/json".format(self
.ver_names
["URLmodifier"], self
.dpid
),
189 headers
=self
.headers
)
190 error_text
= "Openflow response {}: {}".format(of_response
.status_code
, of_response
.text
)
191 if of_response
.status_code
!= 200:
192 self
.logger
.warning("get_of_rules " + error_text
)
193 raise OpenflowConnUnexpectedResponse(error_text
)
194 self
.logger
.debug("get_of_rules " + error_text
)
195 info
= of_response
.json()
196 if type(info
) != dict:
197 self
.logger
.error("get_of_rules. Unexpected response not a dict %s", str(type(info
)))
198 raise OpenflowConnUnexpectedResponse("Unexpected response, not a dict. Wrong version?")
200 for switch
, switch_info
in info
.items():
201 if switch_info
is None:
203 if str(switch
) != self
.dpid
:
205 for name
, details
in switch_info
.items():
208 "switch": str(switch
)
210 # rule["active"] = "true"
211 rule
["priority"] = int(details
["priority"])
212 if self
.version
[0] == "0":
213 if translate_of_ports
:
214 rule
["ingress_port"] = self
.ofi2pp
[details
["match"]["inputPort"]]
216 rule
["ingress_port"] = str(details
["match"]["inputPort"])
217 dst_mac
= details
["match"]["dataLayerDestination"]
218 if dst_mac
!= "00:00:00:00:00:00":
219 rule
["dst_mac"] = dst_mac
220 vlan
= details
["match"]["dataLayerVirtualLan"]
222 rule
["vlan_id"] = vlan
224 for action
in details
["actions"]:
225 if action
["type"] == "OUTPUT":
226 if translate_of_ports
:
227 port
= self
.ofi2pp
[action
["port"]]
229 port
= action
["port"]
230 actionlist
.append(("out", port
))
231 elif action
["type"] == "STRIP_VLAN":
232 actionlist
.append(("vlan", None))
233 elif action
["type"] == "SET_VLAN_ID":
234 actionlist
.append(("vlan", action
["virtualLanIdentifier"]))
236 actionlist
.append((action
["type"], str(action
)))
237 self
.logger
.warning("get_of_rules() Unknown action in rule %s: %s", rule
["name"],
239 rule
["actions"] = actionlist
240 elif self
.version
[0] == "1":
241 if translate_of_ports
:
242 rule
["ingress_port"] = self
.ofi2pp
[details
["match"]["in_port"]]
244 rule
["ingress_port"] = details
["match"]["in_port"]
245 if "eth_dst" in details
["match"]:
246 dst_mac
= details
["match"]["eth_dst"]
247 if dst_mac
!= "00:00:00:00:00:00":
248 rule
["dst_mac"] = dst_mac
249 if "eth_vlan_vid" in details
["match"]:
250 vlan
= int(details
["match"]["eth_vlan_vid"], 16) & 0xFFF
251 rule
["vlan_id"] = str(vlan
)
253 for action
in details
["instructions"]["instruction_apply_actions"]:
254 if action
== "output":
255 if translate_of_ports
:
256 port
= self
.ofi2pp
[details
["instructions"]["instruction_apply_actions"]["output"]]
258 port
= details
["instructions"]["instruction_apply_actions"]["output"]
259 actionlist
.append(("out", port
))
260 elif action
== "strip_vlan":
261 actionlist
.append(("vlan", None))
262 elif action
== "set_vlan_vid":
264 ("vlan", details
["instructions"]["instruction_apply_actions"]["set_vlan_vid"]))
266 self
.logger
.error("get_of_rules Unknown action in rule %s: %s", rule
["name"],
268 # actionlist.append((action, str(details["instructions"]["instruction_apply_actions"])))
269 rule_list
.append(rule
)
271 except requests
.exceptions
.RequestException
as e
:
272 error_text
= type(e
).__name
__ + ": " + str(e
)
273 self
.logger
.error("get_of_rules " + error_text
)
274 raise OpenflowConnConnectionException(error_text
)
275 except Exception as e
:
276 # ValueError in the case that JSON can not be decoded
277 error_text
= type(e
).__name
__ + ": " + str(e
)
278 self
.logger
.error("get_of_rules " + error_text
)
279 raise OpenflowConnUnexpectedResponse(error_text
)
281 def obtain_port_correspondence(self
):
283 Obtain the correspondence between physical and openflow port names
284 :return: dictionary: with physical name as key, openflow name as value
285 Raise an openflowconnUnexpectedResponse exception if fails with text_error
288 of_response
= requests
.get(self
.url
+ "wm/core/controller/switches/json", headers
=self
.headers
)
289 # print vim_response.status_code
290 error_text
= "Openflow response {}: {}".format(of_response
.status_code
, of_response
.text
)
291 if of_response
.status_code
!= 200:
292 self
.logger
.warning("obtain_port_correspondence " + error_text
)
293 raise OpenflowConnUnexpectedResponse(error_text
)
294 self
.logger
.debug("obtain_port_correspondence " + error_text
)
295 info
= of_response
.json()
297 if not isinstance(info
, (list, tuple)):
298 raise OpenflowConnUnexpectedResponse("unexpected openflow response, not a list. Wrong version?")
302 # autodiscover version
303 if self
.version
is None:
304 if 'dpid' in info
[0] and 'ports' in info
[0]:
305 self
._set
_version
("0.9")
306 elif 'switchDPID' in info
[0]:
307 self
._set
_version
("1.X")
309 raise OpenflowConnUnexpectedResponse("unexpected openflow response, Wrong version?")
311 for i
, info_item
in enumerate(info
):
312 if info_item
[self
.ver_names
["dpid"]] == self
.dpid
:
316 text
= "DPID '{}' not present in controller {}".format(self
.dpid
, self
.url
)
317 # print self.name, ": get_of_controller_info ERROR", text
318 raise OpenflowConnUnexpectedResponse(text
)
320 if self
.version
[0] == "0":
321 ports
= info
[index
]["ports"]
323 of_response
= requests
.get(self
.url
+ "wm/core/switch/{}/port-desc/json".format(self
.dpid
),
324 headers
=self
.headers
)
325 # print vim_response.status_code
326 error_text
= "Openflow response {}: {}".format(of_response
.status_code
, of_response
.text
)
327 if of_response
.status_code
!= 200:
328 self
.logger
.warning("obtain_port_correspondence " + error_text
)
329 raise OpenflowConnUnexpectedResponse(error_text
)
330 self
.logger
.debug("obtain_port_correspondence " + error_text
)
331 info
= of_response
.json()
332 if type(info
) != dict:
333 raise OpenflowConnUnexpectedResponse("unexpected openflow port-desc response, "
334 "not a dict. Wrong version?")
335 if "portDesc" not in info
:
336 raise OpenflowConnUnexpectedResponse("unexpected openflow port-desc response, "
337 "'portDesc' not found. Wrong version?")
338 if type(info
["portDesc"]) != list and type(info
["portDesc"]) != tuple:
339 raise OpenflowConnUnexpectedResponse("unexpected openflow port-desc response at "
340 "'portDesc', not a list. Wrong version?")
341 ports
= info
["portDesc"]
343 self
.pp2ofi
[str(port
["name"])] = str(port
["portNumber"])
344 self
.ofi2pp
[port
["portNumber"]] = str(port
["name"])
345 # print self.name, ": get_of_controller_info ports:", self.pp2ofi
347 except requests
.exceptions
.RequestException
as e
:
348 error_text
= type(e
).__name
__ + ": " + str(e
)
349 self
.logger
.error("obtain_port_correspondence " + error_text
)
350 raise OpenflowConnConnectionException(error_text
)
351 except Exception as e
:
352 # ValueError in the case that JSON can not be decoded
353 error_text
= type(e
).__name
__ + ": " + str(e
)
354 self
.logger
.error("obtain_port_correspondence " + error_text
)
355 raise OpenflowConnUnexpectedResponse(error_text
)
357 def del_flow(self
, flow_name
):
359 Delete an existing rule
360 :param flow_name: this is the rule name
362 Raise an openflowconnUnexpectedResponse exception if fails with text_error
365 if self
.version
is None:
366 self
.get_of_switches()
368 of_response
= requests
.delete(self
.url
+ "wm/{}/json".format(self
.ver_names
["URLmodifier"]),
369 headers
=self
.headers
,
370 data
='{{"switch":"{}","name":"{}"}}'.format(self
.dpid
, flow_name
))
371 error_text
= "Openflow response {}: {}".format(of_response
.status_code
, of_response
.text
)
372 if of_response
.status_code
!= 200:
373 self
.logger
.warning("del_flow " + error_text
)
374 raise OpenflowConnUnexpectedResponse(error_text
)
375 self
.logger
.debug("del_flow OK " + error_text
)
378 except requests
.exceptions
.RequestException
as e
:
379 error_text
= type(e
).__name
__ + ": " + str(e
)
380 self
.logger
.error("del_flow " + error_text
)
381 raise OpenflowConnConnectionException(error_text
)
382 except Exception as e
:
383 # ValueError in the case that JSON can not be decoded
384 error_text
= type(e
).__name
__ + ": " + str(e
)
385 self
.logger
.error("del_flow " + error_text
)
386 raise OpenflowConnUnexpectedResponse(error_text
)
388 def new_flow(self
, data
):
390 Insert a new static rule
391 :param data: dictionary with the following content:
392 priority: rule priority
394 ingress_port: match input port of the rule
395 dst_mac: match destination mac address of the rule, missing or None if not apply
396 vlan_id: match vlan tag of the rule, missing or None if not apply
397 actions: list of actions, composed by a pair tuples with these posibilities:
398 ('vlan', None/int): for stripping/setting a vlan tag
399 ('out', port): send to this port
401 Raise an openflowconnUnexpectedResponse exception if fails with text_error
403 # get translation, autodiscover version
404 if len(self
.pp2ofi
) == 0:
405 self
.obtain_port_correspondence()
408 # We have to build the data for the floodlight call from the generic data
409 sdata
= {'active': "true", "name": data
["name"]}
410 if data
.get("priority"):
411 sdata
["priority"] = str(data
["priority"])
412 if data
.get("vlan_id"):
413 sdata
[self
.ver_names
["vlanid"]] = data
["vlan_id"]
414 if data
.get("dst_mac"):
415 sdata
[self
.ver_names
["destmac"]] = data
["dst_mac"]
416 sdata
['switch'] = self
.dpid
417 if not data
['ingress_port'] in self
.pp2ofi
:
418 error_text
= 'Error. Port {} is not present in the switch'.format(data
['ingress_port'])
419 self
.logger
.warning("new_flow " + error_text
)
420 raise OpenflowConnUnexpectedResponse(error_text
)
422 sdata
[self
.ver_names
["inport"]] = self
.pp2ofi
[data
['ingress_port']]
423 sdata
['actions'] = ""
425 for action
in data
['actions']:
426 if len(sdata
['actions']) > 0:
427 sdata
['actions'] += ','
428 if action
[0] == "vlan":
429 if action
[1] is None:
430 sdata
['actions'] += self
.ver_names
["stripvlan"]
432 sdata
['actions'] += self
.ver_names
["setvlan"] + "=" + str(action
[1])
433 elif action
[0] == 'out':
434 sdata
['actions'] += "output=" + self
.pp2ofi
[action
[1]]
436 of_response
= requests
.post(self
.url
+ "wm/{}/json".format(self
.ver_names
["URLmodifier"]),
437 headers
=self
.headers
, data
=json
.dumps(sdata
))
438 error_text
= "Openflow response {}: {}".format(of_response
.status_code
, of_response
.text
)
439 if of_response
.status_code
!= 200:
440 self
.logger
.warning("new_flow " + error_text
)
441 raise OpenflowConnUnexpectedResponse(error_text
)
442 self
.logger
.debug("new_flow OK" + error_text
)
445 except requests
.exceptions
.RequestException
as e
:
446 error_text
= type(e
).__name
__ + ": " + str(e
)
447 self
.logger
.error("new_flow " + error_text
)
448 raise OpenflowConnConnectionException(error_text
)
449 except Exception as e
:
450 # ValueError in the case that JSON can not be decoded
451 error_text
= type(e
).__name
__ + ": " + str(e
)
452 self
.logger
.error("new_flow " + error_text
)
453 raise OpenflowConnUnexpectedResponse(error_text
)
455 def clear_all_flows(self
):
457 Delete all existing rules
459 Raise an openflowconnUnexpectedResponse exception if fails with text_error
463 # autodiscover version
464 if self
.version
is None:
465 sw_list
= self
.get_of_switches()
466 if len(sw_list
) == 0: # empty
469 url
= self
.url
+ "wm/{}/clear/{}/json".format(self
.ver_names
["URLmodifier"], self
.dpid
)
470 of_response
= requests
.get(url
)
471 error_text
= "Openflow response {}: {}".format(of_response
.status_code
, of_response
.text
)
472 if of_response
.status_code
< 200 or of_response
.status_code
>= 300:
473 self
.logger
.warning("clear_all_flows " + error_text
)
474 raise OpenflowConnUnexpectedResponse(error_text
)
475 self
.logger
.debug("clear_all_flows OK " + error_text
)
477 except requests
.exceptions
.RequestException
as e
:
478 error_text
= type(e
).__name
__ + ": " + str(e
)
479 self
.logger
.error("clear_all_flows " + error_text
)
480 raise OpenflowConnConnectionException(error_text
)
481 except Exception as e
:
482 # ValueError in the case that JSON can not be decoded
483 error_text
= type(e
).__name
__ + ": " + str(e
)
484 self
.logger
.error("clear_all_flows " + error_text
)
485 raise OpenflowConnUnexpectedResponse(error_text
)