| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # -*- coding: utf-8 -*- |
| 3 | |
| 4 | ## |
| 5 | # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. |
| tierno | 9a61c6b | 2016-09-08 10:57:02 +0200 | [diff] [blame] | 6 | # This file is part of openvim |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 7 | # All Rights Reserved. |
| 8 | # |
| 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 |
| 12 | # |
| 13 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 14 | # |
| 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 |
| 19 | # under the License. |
| 20 | # |
| 21 | # For those usages not covered by the Apache License, Version 2.0 please |
| 22 | # contact with: nfvlabs@tid.es |
| 23 | ## |
| 24 | |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 25 | """ |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 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 |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 29 | """ |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 30 | |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 31 | __author__ = "Pablo Montes, Alfonso Tierno" |
| 32 | __date__ = "$28-oct-2014 12:07:15$" |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 33 | |
| 34 | import json |
| 35 | import requests |
| 36 | import logging |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 37 | import openflow_conn |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 38 | |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 39 | |
| 40 | class OF_conn(openflow_conn.OpenflowConn): |
| 41 | """ |
| 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!!! |
| 46 | """ |
| 47 | |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 48 | def __init__(self, params): |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 49 | """ |
| 50 | Constructor |
| 51 | :param self: |
| 52 | :param params: dictionay with the following keys: |
| 53 | of_dpid: DPID to use for this controller |
| 54 | of_ip: controller IP address |
| 55 | of_port: controller TCP port |
| 56 | of_version: version, can be "0.9" or "1.X". By default it is autodetected |
| 57 | of_debug: debug level for logging. Default to ERROR |
| 58 | other keys are ignored |
| 59 | :return: Raise an ValueError exception if same parameter is missing or wrong |
| 60 | """ |
| 61 | # check params |
| 62 | if "of_ip" not in params or params["of_ip"] == None or "of_port" not in params or params["of_port"] == None: |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 63 | raise ValueError("IP address and port must be provided") |
| 64 | |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 65 | openflow_conn.OpenflowConn.__init__(self, params) |
| 66 | |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 67 | self.name = "Floodlight" |
| 68 | self.dpid = str(params["of_dpid"]) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 69 | self.url = "http://%s:%s" % (str(params["of_ip"]), str(params["of_port"])) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 70 | |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 71 | self.pp2ofi = {} # From Physical Port to OpenFlow Index |
| 72 | self.ofi2pp = {} # From OpenFlow Index to Physical Port |
| 73 | self.headers = {'content-type': 'application/json', 'Accept': 'application/json'} |
| 74 | self.version = None |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 75 | self.logger = logging.getLogger('vim.OF.FL') |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 76 | self.logger.setLevel(getattr(logging, params.get("of_debug", "ERROR"))) |
| 77 | self._set_version(params.get("of_version")) |
| 78 | |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 79 | def _set_version(self, version): |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 80 | """ |
| 81 | set up a version of the controller. |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 82 | Depending on the version it fills the self.ver_names with the naming used in this version |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 83 | :param version: Openflow controller version |
| 84 | :return: Raise an ValueError exception if same parameter is missing or wrong |
| 85 | """ |
| 86 | # static version names |
| 87 | if version == None: |
| 88 | self.version = None |
| 89 | elif version == "0.9": |
| 90 | self.version = version |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 91 | self.name = "Floodlightv0.9" |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 92 | self.ver_names = { |
| 93 | "dpid": "dpid", |
| 94 | "URLmodifier": "staticflowentrypusher", |
| 95 | "destmac": "dst-mac", |
| 96 | "vlanid": "vlan-id", |
| 97 | "inport": "ingress-port", |
| 98 | "setvlan": "set-vlan-id", |
| 99 | "stripvlan": "strip-vlan", |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 100 | } |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 101 | elif version[0] == "1": # version 1.X |
| 102 | self.version = version |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 103 | self.name = "Floodlightv1.X" |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 104 | self.ver_names = { |
| 105 | "dpid": "switchDPID", |
| 106 | "URLmodifier": "staticflowpusher", |
| 107 | "destmac": "eth_dst", |
| 108 | "vlanid": "eth_vlan_vid", |
| 109 | "inport": "in_port", |
| 110 | "setvlan": "set_vlan_vid", |
| 111 | "stripvlan": "strip_vlan", |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 112 | } |
| 113 | else: |
| 114 | raise ValueError("Invalid version for floodlight controller") |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 115 | |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 116 | def get_of_switches(self): |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 117 | """ |
| 118 | Obtain a a list of switches or DPID detected by this controller |
| 119 | :return: list where each element a tuple pair (DPID, IP address) |
| 120 | Raise an OpenflowconnConnectionException or OpenflowconnConnectionException exception if same |
| 121 | parameter is missing or wrong |
| 122 | """ |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 123 | try: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 124 | of_response = requests.get(self.url + "/wm/core/controller/switches/json", headers=self.headers) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 125 | error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text) |
| 126 | if of_response.status_code != 200: |
| 127 | self.logger.warning("get_of_switches " + error_text) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 128 | raise openflow_conn.OpenflowconnUnexpectedResponse(error_text) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 129 | self.logger.debug("get_of_switches " + error_text) |
| 130 | info = of_response.json() |
| 131 | if type(info) != list and type(info) != tuple: |
| 132 | self.logger.error("get_of_switches. Unexpected response not a list %s", str(type(info))) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 133 | raise openflow_conn.OpenflowconnUnexpectedResponse("Unexpected response, not a list. Wrong version?") |
| 134 | if len(info) == 0: |
| 135 | return info |
| 136 | # autodiscover version |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 137 | if self.version == None: |
| 138 | if 'dpid' in info[0] and 'inetAddress' in info[0]: |
| 139 | self._set_version("0.9") |
| tierno | e8b761a | 2018-07-13 15:11:44 +0200 | [diff] [blame^] | 140 | # elif 'switchDPID' in info[0] and 'inetAddress' in info[0]: |
| 141 | # self._set_version("1.X") |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 142 | else: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 143 | self.logger.error( |
| 144 | "get_of_switches. Unexpected response, not found 'dpid' or 'switchDPID' field: %s", |
| 145 | str(info[0])) |
| 146 | raise openflow_conn.OpenflowconnUnexpectedResponse("Unexpected response, not found 'dpid' or " |
| 147 | "'switchDPID' field. Wrong version?") |
| 148 | |
| 149 | switch_list = [] |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 150 | for switch in info: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 151 | switch_list.append((switch[self.ver_names["dpid"]], switch['inetAddress'])) |
| 152 | return switch_list |
| 153 | except requests.exceptions.RequestException as e: |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 154 | error_text = type(e).__name__ + ": " + str(e) |
| 155 | self.logger.error("get_of_switches " + error_text) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 156 | raise openflow_conn.OpenflowconnConnectionException(error_text) |
| tierno | e8b761a | 2018-07-13 15:11:44 +0200 | [diff] [blame^] | 157 | except Exception as e: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 158 | # ValueError in the case that JSON can not be decoded |
| 159 | error_text = type(e).__name__ + ": " + str(e) |
| 160 | self.logger.error("get_of_switches " + error_text) |
| 161 | raise openflow_conn.OpenflowconnUnexpectedResponse(error_text) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 162 | |
| 163 | def get_of_rules(self, translate_of_ports=True): |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 164 | """ |
| 165 | Obtain the rules inserted at openflow controller |
| 166 | :param translate_of_ports: if True it translates ports from openflow index to physical switch name |
| 167 | :return: dict if ok: with the rule name as key and value is another dictionary with the following content: |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 168 | priority: rule priority |
| 169 | name: rule name (present also as the master dict key) |
| 170 | ingress_port: match input port of the rule |
| 171 | dst_mac: match destination mac address of the rule, can be missing or None if not apply |
| 172 | vlan_id: match vlan tag of the rule, can be missing or None if not apply |
| 173 | actions: list of actions, composed by a pair tuples: |
| 174 | (vlan, None/int): for stripping/setting a vlan tag |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 175 | (out, port): send to this port |
| 176 | switch: DPID, all |
| 177 | Raise an openflowconnUnexpectedResponse exception if fails with text_error |
| 178 | """ |
| 179 | |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 180 | try: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 181 | # get translation, autodiscover version |
| 182 | if len(self.ofi2pp) == 0: |
| 183 | self.obtain_port_correspondence() |
| 184 | |
| 185 | of_response = requests.get(self.url + "/wm/%s/list/%s/json" % (self.ver_names["URLmodifier"], self.dpid), |
| 186 | headers=self.headers) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 187 | error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text) |
| 188 | if of_response.status_code != 200: |
| 189 | self.logger.warning("get_of_rules " + error_text) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 190 | raise openflow_conn.OpenflowconnUnexpectedResponse(error_text) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 191 | self.logger.debug("get_of_rules " + error_text) |
| 192 | info = of_response.json() |
| 193 | if type(info) != dict: |
| 194 | self.logger.error("get_of_rules. Unexpected response not a dict %s", str(type(info))) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 195 | raise openflow_conn.OpenflowconnUnexpectedResponse("Unexpected response, not a dict. Wrong version?") |
| 196 | rule_dict = {} |
| 197 | for switch, switch_info in info.iteritems(): |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 198 | if switch_info == None: |
| 199 | continue |
| 200 | if str(switch) != self.dpid: |
| 201 | continue |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 202 | for name, details in switch_info.iteritems(): |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 203 | rule = {} |
| 204 | rule["switch"] = str(switch) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 205 | # rule["active"] = "true" |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 206 | rule["priority"] = int(details["priority"]) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 207 | if self.version[0] == "0": |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 208 | if translate_of_ports: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 209 | rule["ingress_port"] = self.ofi2pp[details["match"]["inputPort"]] |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 210 | else: |
| 211 | rule["ingress_port"] = str(details["match"]["inputPort"]) |
| 212 | dst_mac = details["match"]["dataLayerDestination"] |
| 213 | if dst_mac != "00:00:00:00:00:00": |
| 214 | rule["dst_mac"] = dst_mac |
| 215 | vlan = details["match"]["dataLayerVirtualLan"] |
| 216 | if vlan != -1: |
| 217 | rule["vlan_id"] = vlan |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 218 | actionlist = [] |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 219 | for action in details["actions"]: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 220 | if action["type"] == "OUTPUT": |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 221 | if translate_of_ports: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 222 | port = self.ofi2pp[action["port"]] |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 223 | else: |
| 224 | port = action["port"] |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 225 | actionlist.append(("out", port)) |
| 226 | elif action["type"] == "STRIP_VLAN": |
| 227 | actionlist.append(("vlan", None)) |
| 228 | elif action["type"] == "SET_VLAN_ID": |
| 229 | actionlist.append(("vlan", action["virtualLanIdentifier"])) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 230 | else: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 231 | actionlist.append((action["type"], str(action))) |
| 232 | self.logger.warning("get_of_rules() Unknown action in rule %s: %s", rule["name"], |
| 233 | str(action)) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 234 | rule["actions"] = actionlist |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 235 | elif self.version[0] == "1": |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 236 | if translate_of_ports: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 237 | rule["ingress_port"] = self.ofi2pp[details["match"]["in_port"]] |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 238 | else: |
| 239 | rule["ingress_port"] = details["match"]["in_port"] |
| 240 | if "eth_dst" in details["match"]: |
| 241 | dst_mac = details["match"]["eth_dst"] |
| 242 | if dst_mac != "00:00:00:00:00:00": |
| 243 | rule["dst_mac"] = dst_mac |
| 244 | if "eth_vlan_vid" in details["match"]: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 245 | vlan = int(details["match"]["eth_vlan_vid"], 16) & 0xFFF |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 246 | rule["vlan_id"] = str(vlan) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 247 | actionlist = [] |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 248 | for action in details["instructions"]["instruction_apply_actions"]: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 249 | if action == "output": |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 250 | if translate_of_ports: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 251 | port = self.ofi2pp[details["instructions"]["instruction_apply_actions"]["output"]] |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 252 | else: |
| 253 | port = details["instructions"]["instruction_apply_actions"]["output"] |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 254 | actionlist.append(("out", port)) |
| 255 | elif action == "strip_vlan": |
| 256 | actionlist.append(("vlan", None)) |
| 257 | elif action == "set_vlan_vid": |
| 258 | actionlist.append( |
| 259 | ("vlan", details["instructions"]["instruction_apply_actions"]["set_vlan_vid"])) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 260 | else: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 261 | self.logger.error("get_of_rules Unknown action in rule %s: %s", rule["name"], |
| 262 | str(action)) |
| 263 | # actionlist.append( (action, str(details["instructions"]["instruction_apply_actions"]) )) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 264 | rule_dict[str(name)] = rule |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 265 | return rule_dict |
| 266 | except requests.exceptions.RequestException as e: |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 267 | error_text = type(e).__name__ + ": " + str(e) |
| 268 | self.logger.error("get_of_rules " + error_text) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 269 | raise openflow_conn.OpenflowconnConnectionException(error_text) |
| tierno | e8b761a | 2018-07-13 15:11:44 +0200 | [diff] [blame^] | 270 | except Exception as e: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 271 | # ValueError in the case that JSON can not be decoded |
| 272 | error_text = type(e).__name__ + ": " + str(e) |
| 273 | self.logger.error("get_of_rules " + error_text) |
| 274 | raise openflow_conn.OpenflowconnUnexpectedResponse(error_text) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 275 | |
| 276 | def obtain_port_correspondence(self): |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 277 | """ |
| 278 | Obtain the correspondence between physical and openflow port names |
| 279 | :return: dictionary: with physical name as key, openflow name as value |
| 280 | Raise an openflowconnUnexpectedResponse exception if fails with text_error |
| 281 | """ |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 282 | try: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 283 | of_response = requests.get(self.url + "/wm/core/controller/switches/json", headers=self.headers) |
| 284 | # print vim_response.status_code |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 285 | error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text) |
| 286 | if of_response.status_code != 200: |
| 287 | self.logger.warning("obtain_port_correspondence " + error_text) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 288 | raise openflow_conn.OpenflowconnUnexpectedResponse(error_text) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 289 | self.logger.debug("obtain_port_correspondence " + error_text) |
| 290 | info = of_response.json() |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 291 | |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 292 | if type(info) != list and type(info) != tuple: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 293 | raise openflow_conn.OpenflowconnUnexpectedResponse("unexpected openflow response, not a list. " |
| 294 | "Wrong version?") |
| 295 | |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 296 | index = -1 |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 297 | if len(info) > 0: |
| 298 | # autodiscover version |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 299 | if self.version == None: |
| 300 | if 'dpid' in info[0] and 'ports' in info[0]: |
| 301 | self._set_version("0.9") |
| 302 | elif 'switchDPID' in info[0]: |
| 303 | self._set_version("1.X") |
| 304 | else: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 305 | raise openflow_conn.OpenflowconnUnexpectedResponse("unexpected openflow response, " |
| 306 | "Wrong version?") |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 307 | |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 308 | for i in range(0, len(info)): |
| 309 | if info[i][self.ver_names["dpid"]] == self.dpid: |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 310 | index = i |
| 311 | break |
| 312 | if index == -1: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 313 | text = "DPID '" + self.dpid + "' not present in controller " + self.url |
| 314 | # print self.name, ": get_of_controller_info ERROR", text |
| 315 | raise openflow_conn.OpenflowconnUnexpectedResponse(text) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 316 | else: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 317 | if self.version[0] == "0": |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 318 | ports = info[index]["ports"] |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 319 | else: # version 1.X |
| 320 | of_response = requests.get(self.url + "/wm/core/switch/%s/port-desc/json" % self.dpid, |
| 321 | headers=self.headers) |
| 322 | # print vim_response.status_code |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 323 | error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text) |
| 324 | if of_response.status_code != 200: |
| 325 | self.logger.warning("obtain_port_correspondence " + error_text) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 326 | raise openflow_conn.OpenflowconnUnexpectedResponse(error_text) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 327 | self.logger.debug("obtain_port_correspondence " + error_text) |
| 328 | info = of_response.json() |
| 329 | if type(info) != dict: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 330 | raise openflow_conn.OpenflowconnUnexpectedResponse("unexpected openflow port-desc response, " |
| 331 | "not a dict. Wrong version?") |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 332 | if "portDesc" not in info: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 333 | raise openflow_conn.OpenflowconnUnexpectedResponse("unexpected openflow port-desc response, " |
| 334 | "'portDesc' not found. Wrong version?") |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 335 | if type(info["portDesc"]) != list and type(info["portDesc"]) != tuple: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 336 | raise openflow_conn.OpenflowconnUnexpectedResponse("unexpected openflow port-desc response at " |
| 337 | "'portDesc', not a list. Wrong version?") |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 338 | ports = info["portDesc"] |
| 339 | for port in ports: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 340 | self.pp2ofi[str(port["name"])] = str(port["portNumber"]) |
| 341 | self.ofi2pp[port["portNumber"]] = str(port["name"]) |
| 342 | # print self.name, ": get_of_controller_info ports:", self.pp2ofi |
| 343 | return self.pp2ofi |
| 344 | except requests.exceptions.RequestException as e: |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 345 | error_text = type(e).__name__ + ": " + str(e) |
| 346 | self.logger.error("obtain_port_correspondence " + error_text) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 347 | raise openflow_conn.OpenflowconnConnectionException(error_text) |
| tierno | e8b761a | 2018-07-13 15:11:44 +0200 | [diff] [blame^] | 348 | except Exception as e: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 349 | # ValueError in the case that JSON can not be decoded |
| 350 | error_text = type(e).__name__ + ": " + str(e) |
| 351 | self.logger.error("obtain_port_correspondence " + error_text) |
| 352 | raise openflow_conn.OpenflowconnUnexpectedResponse(error_text) |
| 353 | |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 354 | def del_flow(self, flow_name): |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 355 | """ |
| 356 | Delete an existing rule |
| 357 | :param flow_name: this is the rule name |
| 358 | :return: None if ok |
| 359 | Raise an openflowconnUnexpectedResponse exception if fails with text_error |
| 360 | """ |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 361 | try: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 362 | |
| 363 | # Raise an openflowconnUnexpectedResponse exception if fails with text_error |
| 364 | # autodiscover version |
| 365 | |
| 366 | if self.version == None: |
| 367 | self.get_of_switches() |
| 368 | |
| 369 | of_response = requests.delete(self.url + "/wm/%s/json" % self.ver_names["URLmodifier"], |
| 370 | headers=self.headers, |
| 371 | data='{"switch":"%s","name":"%s"}' % (self.dpid, flow_name) |
| 372 | ) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 373 | error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text) |
| 374 | if of_response.status_code != 200: |
| 375 | self.logger.warning("del_flow " + error_text) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 376 | raise openflow_conn.OpenflowconnUnexpectedResponse(error_text) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 377 | self.logger.debug("del_flow OK " + error_text) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 378 | return None |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 379 | |
| 380 | except requests.exceptions.RequestException as e: |
| 381 | error_text = type(e).__name__ + ": " + str(e) |
| 382 | self.logger.error("del_flow " + error_text) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 383 | raise openflow_conn.OpenflowconnConnectionException(error_text) |
| tierno | e8b761a | 2018-07-13 15:11:44 +0200 | [diff] [blame^] | 384 | except Exception as e: |
| 385 | # ValueError in the case that JSON can not be decoded |
| 386 | error_text = type(e).__name__ + ": " + str(e) |
| 387 | self.logger.error("del_flow " + error_text) |
| 388 | raise openflow_conn.OpenflowconnUnexpectedResponse(error_text) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 389 | |
| 390 | def new_flow(self, data): |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 391 | """ |
| 392 | Insert a new static rule |
| 393 | :param data: dictionary with the following content: |
| 394 | priority: rule priority |
| 395 | name: rule name |
| 396 | ingress_port: match input port of the rule |
| 397 | dst_mac: match destination mac address of the rule, missing or None if not apply |
| 398 | vlan_id: match vlan tag of the rule, missing or None if not apply |
| 399 | actions: list of actions, composed by a pair tuples with these posibilities: |
| 400 | ('vlan', None/int): for stripping/setting a vlan tag |
| 401 | ('out', port): send to this port |
| 402 | :return: None if ok |
| 403 | Raise an openflowconnUnexpectedResponse exception if fails with text_error |
| 404 | """ |
| 405 | # get translation, autodiscover version |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 406 | if len(self.pp2ofi) == 0: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 407 | self.obtain_port_correspondence() |
| 408 | |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 409 | try: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 410 | # We have to build the data for the floodlight call from the generic data |
| 411 | sdata = {'active': "true", "name": data["name"]} |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 412 | if data.get("priority"): |
| 413 | sdata["priority"] = str(data["priority"]) |
| 414 | if data.get("vlan_id"): |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 415 | sdata[self.ver_names["vlanid"]] = data["vlan_id"] |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 416 | if data.get("dst_mac"): |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 417 | sdata[self.ver_names["destmac"]] = data["dst_mac"] |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 418 | sdata['switch'] = self.dpid |
| 419 | if not data['ingress_port'] in self.pp2ofi: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 420 | error_text = 'Error. Port ' + data['ingress_port'] + ' is not present in the switch' |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 421 | self.logger.warning("new_flow " + error_text) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 422 | raise openflow_conn.OpenflowconnUnexpectedResponse(error_text) |
| 423 | |
| 424 | sdata[self.ver_names["inport"]] = self.pp2ofi[data['ingress_port']] |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 425 | sdata['actions'] = "" |
| 426 | |
| 427 | for action in data['actions']: |
| 428 | if len(sdata['actions']) > 0: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 429 | sdata['actions'] += ',' |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 430 | if action[0] == "vlan": |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 431 | if action[1] == None: |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 432 | sdata['actions'] += self.ver_names["stripvlan"] |
| 433 | else: |
| 434 | sdata['actions'] += self.ver_names["setvlan"] + "=" + str(action[1]) |
| 435 | elif action[0] == 'out': |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 436 | sdata['actions'] += "output=" + self.pp2ofi[action[1]] |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 437 | |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 438 | of_response = requests.post(self.url + "/wm/%s/json" % self.ver_names["URLmodifier"], |
| 439 | headers=self.headers, data=json.dumps(sdata)) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 440 | error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text) |
| 441 | if of_response.status_code != 200: |
| 442 | self.logger.warning("new_flow " + error_text) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 443 | raise openflow_conn.OpenflowconnUnexpectedResponse(error_text) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 444 | self.logger.debug("new_flow OK" + error_text) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 445 | return None |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 446 | |
| 447 | except requests.exceptions.RequestException as e: |
| 448 | error_text = type(e).__name__ + ": " + str(e) |
| 449 | self.logger.error("new_flow " + error_text) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 450 | raise openflow_conn.OpenflowconnConnectionException(error_text) |
| tierno | e8b761a | 2018-07-13 15:11:44 +0200 | [diff] [blame^] | 451 | except Exception as e: |
| 452 | # ValueError in the case that JSON can not be decoded |
| 453 | error_text = type(e).__name__ + ": " + str(e) |
| 454 | self.logger.error("new_flow " + error_text) |
| 455 | raise openflow_conn.OpenflowconnUnexpectedResponse(error_text) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 456 | |
| 457 | def clear_all_flows(self): |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 458 | """ |
| 459 | Delete all existing rules |
| 460 | :return: None if ok |
| 461 | Raise an openflowconnUnexpectedResponse exception if fails with text_error |
| 462 | """ |
| 463 | |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 464 | try: |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 465 | # autodiscover version |
| 466 | if self.version == None: |
| 467 | sw_list = self.get_of_switches() |
| 468 | if len(sw_list) == 0: # empty |
| 469 | return None |
| 470 | |
| 471 | url = self.url + "/wm/%s/clear/%s/json" % (self.ver_names["URLmodifier"], self.dpid) |
| 472 | of_response = requests.get(url) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 473 | error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text) |
| 474 | if of_response.status_code < 200 or of_response.status_code >= 300: |
| 475 | self.logger.warning("clear_all_flows " + error_text) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 476 | raise openflow_conn.OpenflowconnUnexpectedResponse(error_text) |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 477 | self.logger.debug("clear_all_flows OK " + error_text) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 478 | return None |
| tierno | f7aa8c4 | 2016-09-06 16:43:04 +0200 | [diff] [blame] | 479 | except requests.exceptions.RequestException as e: |
| 480 | error_text = type(e).__name__ + ": " + str(e) |
| 481 | self.logger.error("clear_all_flows " + error_text) |
| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 482 | raise openflow_conn.OpenflowconnConnectionException(error_text) |
| tierno | e8b761a | 2018-07-13 15:11:44 +0200 | [diff] [blame^] | 483 | except Exception as e: |
| 484 | # ValueError in the case that JSON can not be decoded |
| 485 | error_text = type(e).__name__ + ": " + str(e) |
| 486 | self.logger.error("clear_all_flows " + error_text) |
| 487 | raise openflow_conn.OpenflowconnUnexpectedResponse(error_text) |