| mirabal | 6c60065 | 2017-03-16 17:22:57 +0100 | [diff] [blame] | 1 | # -*- coding: utf-8 -*- |
| 2 | |
| 3 | ## |
| 4 | # Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. |
| 5 | # This file is part of openmano |
| 6 | # All Rights Reserved. |
| 7 | # |
| 8 | # Licensed under the Apache License, Version 2.0 (the "License"); you may |
| 9 | # not use this file except in compliance with the License. You may obtain |
| 10 | # a copy of the License at |
| 11 | # |
| 12 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 13 | # |
| 14 | # Unless required by applicable law or agreed to in writing, software |
| 15 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| 16 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| 17 | # License for the specific language governing permissions and limitations |
| 18 | # under the License. |
| 19 | # |
| 20 | # For those usages not covered by the Apache License, Version 2.0 please |
| 21 | # contact with: nfvlabs@tid.es |
| 22 | ## |
| 23 | import logging |
| 24 | import base64 |
| 25 | |
| 26 | """ |
| 27 | vimconn implement an Abstract class for the vim connector plugins |
| 28 | with the definition of the method to be implemented. |
| 29 | """ |
| 30 | __author__ = "Alfonso Tierno, Leonardo Mirabal" |
| 31 | __date__ = "$16-oct-2015 11:09:29$" |
| 32 | |
| 33 | |
| 34 | |
| 35 | # Error variables |
| 36 | HTTP_Bad_Request = 400 |
| 37 | HTTP_Unauthorized = 401 |
| 38 | HTTP_Not_Found = 404 |
| 39 | HTTP_Method_Not_Allowed = 405 |
| 40 | HTTP_Request_Timeout = 408 |
| 41 | HTTP_Conflict = 409 |
| 42 | HTTP_Not_Implemented = 501 |
| 43 | HTTP_Service_Unavailable = 503 |
| 44 | HTTP_Internal_Server_Error = 500 |
| 45 | |
| 46 | |
| 47 | class OpenflowconnException(Exception): |
| 48 | """Common and base class Exception for all vimconnector exceptions""" |
| 49 | def __init__(self, message, http_code=HTTP_Bad_Request): |
| 50 | Exception.__init__(self, message) |
| 51 | self.http_code = http_code |
| 52 | |
| 53 | |
| 54 | class OpenflowconnConnectionException(OpenflowconnException): |
| 55 | """Connectivity error with the VIM""" |
| 56 | def __init__(self, message, http_code=HTTP_Service_Unavailable): |
| 57 | OpenflowconnException.__init__(self, message, http_code) |
| 58 | |
| 59 | |
| 60 | class OpenflowconnUnexpectedResponse(OpenflowconnException): |
| 61 | """Get an wrong response from VIM""" |
| 62 | def __init__(self, message, http_code=HTTP_Internal_Server_Error): |
| 63 | OpenflowconnException.__init__(self, message, http_code) |
| 64 | |
| 65 | |
| 66 | class OpenflowconnAuthException(OpenflowconnException): |
| 67 | """Invalid credentials or authorization to perform this action over the VIM""" |
| 68 | def __init__(self, message, http_code=HTTP_Unauthorized): |
| 69 | OpenflowconnException.__init__(self, message, http_code) |
| 70 | |
| 71 | |
| 72 | class OpenflowconnNotFoundException(OpenflowconnException): |
| 73 | """The item is not found at VIM""" |
| 74 | def __init__(self, message, http_code=HTTP_Not_Found): |
| 75 | OpenflowconnException.__init__(self, message, http_code) |
| 76 | |
| 77 | |
| 78 | class OpenflowconnConflictException(OpenflowconnException): |
| 79 | """There is a conflict, e.g. more item found than one""" |
| 80 | def __init__(self, message, http_code=HTTP_Conflict): |
| 81 | OpenflowconnException.__init__(self, message, http_code) |
| 82 | |
| 83 | |
| 84 | class OpenflowconnNotSupportedException(OpenflowconnException): |
| 85 | """The request is not supported by connector""" |
| 86 | def __init__(self, message, http_code=HTTP_Service_Unavailable): |
| 87 | OpenflowconnException.__init__(self, message, http_code) |
| 88 | |
| 89 | |
| 90 | class OpenflowconnNotImplemented(OpenflowconnException): |
| 91 | """The method is not implemented by the connected""" |
| 92 | def __init__(self, message, http_code=HTTP_Not_Implemented): |
| 93 | OpenflowconnException.__init__(self, message, http_code) |
| 94 | |
| 95 | |
| 96 | class OpenflowConn: |
| 97 | """ |
| 98 | Openflow controller connector abstract implementeation. |
| 99 | """ |
| 100 | def __init__(self, params): |
| 101 | self.name = "openflow_conector" |
| 102 | self.headers = {'content-type': 'application/json', 'Accept': 'application/json'} |
| 103 | self.auth = None |
| 104 | self.pp2ofi = {} # From Physical Port to OpenFlow Index |
| 105 | self.ofi2pp = {} # From OpenFlow Index to Physical Port |
| 106 | self.dpid = '00:01:02:03:04:05:06:07' |
| 107 | self.id = 'openflow:00:01:02:03:04:05:06:07' |
| 108 | self.rules = {} |
| 109 | self.url = "http://%s:%s" % ('localhost', str(8081)) |
| 110 | self.auth = base64.b64encode('of_user:of_password') |
| 111 | self.headers['Authorization'] = 'Basic ' + self.auth |
| 112 | self.logger = logging.getLogger('openflow_conn') |
| 113 | self.logger.setLevel(getattr(logging, params.get("of_debug", "ERROR"))) |
| 114 | self.ip_address = None |
| 115 | |
| 116 | def get_of_switches(self): |
| 117 | """" |
| 118 | Obtain a a list of switches or DPID detected by this controller |
| 119 | :return: list length, and a list where each element a tuple pair (DPID, IP address), text_error: if fails |
| 120 | """ |
| 121 | raise OpenflowconnNotImplemented("Should have implemented this") |
| 122 | |
| 123 | def obtain_port_correspondence(self): |
| 124 | """ |
| 125 | Obtain the correspondence between physical and openflow port names |
| 126 | :return: dictionary: with physical name as key, openflow name as value, error_text: if fails |
| 127 | """ |
| 128 | raise OpenflowconnNotImplemented("Should have implemented this") |
| 129 | |
| 130 | def get_of_rules(self, translate_of_ports=True): |
| 131 | """ |
| 132 | Obtain the rules inserted at openflow controller |
| 133 | :param translate_of_ports: if True it translates ports from openflow index to physical switch name |
| 134 | :return: dict if ok: with the rule name as key and value is another dictionary with the following content: |
| 135 | priority: rule priority |
| 136 | name: rule name (present also as the master dict key) |
| 137 | ingress_port: match input port of the rule |
| 138 | dst_mac: match destination mac address of the rule, can be missing or None if not apply |
| 139 | vlan_id: match vlan tag of the rule, can be missing or None if not apply |
| 140 | actions: list of actions, composed by a pair tuples: |
| 141 | (vlan, None/int): for stripping/setting a vlan tag |
| 142 | (out, port): send to this port |
| 143 | switch: DPID, all |
| 144 | text_error if fails |
| 145 | """ |
| 146 | raise OpenflowconnNotImplemented("Should have implemented this") |
| 147 | |
| 148 | def del_flow(self, flow_name): |
| 149 | """ |
| 150 | Delete all existing rules |
| 151 | :param flow_name: flow_name, this is the rule name |
| 152 | :return: None if ok, text_error if fails |
| 153 | """ |
| 154 | raise OpenflowconnNotImplemented("Should have implemented this") |
| 155 | |
| 156 | def new_flow(self, data): |
| 157 | """ |
| 158 | Insert a new static rule |
| 159 | :param data: dictionary with the following content: |
| 160 | priority: rule priority |
| 161 | name: rule name |
| 162 | ingress_port: match input port of the rule |
| 163 | dst_mac: match destination mac address of the rule, missing or None if not apply |
| 164 | vlan_id: match vlan tag of the rule, missing or None if not apply |
| 165 | actions: list of actions, composed by a pair tuples with these posibilities: |
| 166 | ('vlan', None/int): for stripping/setting a vlan tag |
| 167 | ('out', port): send to this port |
| 168 | :return: None if ok, text_error if fails |
| 169 | """ |
| 170 | raise OpenflowconnNotImplemented("Should have implemented this") |
| 171 | |
| 172 | def clear_all_flows(self): |
| 173 | """" |
| 174 | Delete all existing rules |
| 175 | :return: None if ok, text_error if fails |
| 176 | """ |
| 177 | raise OpenflowconnNotImplemented("Should have implemented this") |
| 178 | |
| 179 | |
| 180 | class OfTestConnector(OpenflowConn): |
| 181 | """ |
| 182 | This is a fake openflow connector for testing. |
| 183 | It does nothing and it is used for running openvim without an openflow controller |
| 184 | """ |
| 185 | |
| 186 | def __init__(self, params): |
| 187 | OpenflowConn.__init__(self, params) |
| 188 | |
| 189 | name = params.get("name", "test-ofc") |
| 190 | self.name = name |
| 191 | self.dpid = params.get("dpid") |
| 192 | self.rules = {} |
| 193 | self.logger = logging.getLogger('vim.OF.TEST') |
| 194 | self.logger.setLevel(getattr(logging, params.get("of_debug", "ERROR"))) |
| 195 | self.pp2ofi = {} |
| 196 | |
| 197 | def get_of_switches(self): |
| 198 | return () |
| 199 | |
| 200 | def obtain_port_correspondence(self): |
| 201 | return () |
| 202 | |
| 203 | def del_flow(self, flow_name): |
| 204 | if flow_name in self.rules: |
| 205 | self.logger.debug("del_flow OK") |
| 206 | del self.rules[flow_name] |
| 207 | return None |
| 208 | else: |
| 209 | self.logger.warning("del_flow not found") |
| 210 | raise OpenflowconnUnexpectedResponse("flow {} not found".format(flow_name)) |
| 211 | |
| 212 | def new_flow(self, data): |
| 213 | self.rules[data["name"]] = data |
| 214 | self.logger.debug("new_flow OK") |
| 215 | return None |
| 216 | |
| 217 | def get_of_rules(self, translate_of_ports=True): |
| 218 | return self.rules |
| 219 | |
| 220 | def clear_all_flows(self): |
| 221 | self.logger.debug("clear_all_flows OK") |
| 222 | self.rules = {} |
| 223 | return None |