ChetanGaonker | 720ea61 | 2016-06-21 17:54:25 -0700 | [diff] [blame] | 1 | # |
Chetan Gaonker | cfcce78 | 2016-05-10 10:10:42 -0700 | [diff] [blame] | 2 | # Copyright 2016-present Ciena Corporation |
| 3 | # |
| 4 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | # you may not use this file except in compliance with the License. |
| 6 | # You may obtain a copy of the License at |
ChetanGaonker | 720ea61 | 2016-06-21 17:54:25 -0700 | [diff] [blame] | 7 | # |
Chetan Gaonker | cfcce78 | 2016-05-10 10:10:42 -0700 | [diff] [blame] | 8 | # http://www.apache.org/licenses/LICENSE-2.0 |
ChetanGaonker | 720ea61 | 2016-06-21 17:54:25 -0700 | [diff] [blame] | 9 | # |
Chetan Gaonker | cfcce78 | 2016-05-10 10:10:42 -0700 | [diff] [blame] | 10 | # Unless required by applicable law or agreed to in writing, software |
| 11 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | # See the License for the specific language governing permissions and |
| 14 | # limitations under the License. |
| 15 | # |
Chetan Gaonker | 7ab338c | 2016-04-15 17:23:17 -0700 | [diff] [blame] | 16 | import json |
| 17 | import requests |
| 18 | import os,sys,time |
| 19 | from nose.tools import * |
A.R Karthick | 2e99c47 | 2017-03-22 19:13:51 -0700 | [diff] [blame] | 20 | import logging |
| 21 | logging.getLogger('scapy.runtime').setLevel(logging.ERROR) |
Chetan Gaonker | 7ab338c | 2016-04-15 17:23:17 -0700 | [diff] [blame] | 22 | from scapy.all import * |
A R Karthick | 456e9cf | 2016-10-03 14:37:44 -0700 | [diff] [blame] | 23 | from OnosCtrl import OnosCtrl, get_controller |
Chetan Gaonker | 7ab338c | 2016-04-15 17:23:17 -0700 | [diff] [blame] | 24 | |
| 25 | class OnosFlowCtrl: |
| 26 | |
| 27 | auth = ('karaf', 'karaf') |
A R Karthick | 456e9cf | 2016-10-03 14:37:44 -0700 | [diff] [blame] | 28 | controller = get_controller() |
Chetan Gaonker | 7ab338c | 2016-04-15 17:23:17 -0700 | [diff] [blame] | 29 | cfg_url = 'http://%s:8181/onos/v1/flows/' %(controller) |
ChetanGaonker | 720ea61 | 2016-06-21 17:54:25 -0700 | [diff] [blame] | 30 | |
Chetan Gaonker | 7ab338c | 2016-04-15 17:23:17 -0700 | [diff] [blame] | 31 | def __init__( self, |
| 32 | deviceId, |
| 33 | appId=0, |
| 34 | ingressPort="", |
| 35 | egressPort="", |
| 36 | ethType="", |
| 37 | ethSrc="", |
| 38 | ethDst="", |
| 39 | vlan="", |
| 40 | ipProto="", |
| 41 | ipSrc=(), |
| 42 | ipDst=(), |
| 43 | tcpSrc="", |
| 44 | tcpDst="", |
| 45 | udpDst="", |
| 46 | udpSrc="", |
ChetanGaonker | 720ea61 | 2016-06-21 17:54:25 -0700 | [diff] [blame] | 47 | mpls="", |
| 48 | dscp="", |
| 49 | icmpv4_type="", |
| 50 | icmpv4_code="", |
| 51 | icmpv6_type="", |
| 52 | icmpv6_code="", |
| 53 | ipv6flow_label="", |
| 54 | ecn="", |
| 55 | ipv6_target="", |
| 56 | ipv6_sll="", |
| 57 | ipv6_tll="", |
ChetanGaonker | dbd4e4b | 2016-10-28 17:40:11 -0700 | [diff] [blame] | 58 | ipv6_extension="", |
| 59 | controller=None): |
Chetan Gaonker | 7ab338c | 2016-04-15 17:23:17 -0700 | [diff] [blame] | 60 | self.deviceId = deviceId |
| 61 | self.appId = appId |
| 62 | self.ingressPort = ingressPort |
| 63 | self.egressPort = egressPort |
| 64 | self.ethType = ethType |
| 65 | self.ethSrc = ethSrc |
| 66 | self.ethDst = ethDst |
| 67 | self.vlan = vlan |
| 68 | self.ipProto = ipProto |
| 69 | self.ipSrc = ipSrc |
| 70 | self.ipDst = ipDst |
| 71 | self.tcpSrc = tcpSrc |
| 72 | self.tcpDst = tcpDst |
| 73 | self.udpDst = udpDst |
| 74 | self.udpSrc = udpSrc |
| 75 | self.mpls = mpls |
ChetanGaonker | 720ea61 | 2016-06-21 17:54:25 -0700 | [diff] [blame] | 76 | self.dscp = dscp |
| 77 | self.icmpv4_type = icmpv4_type |
| 78 | self.icmpv4_code = icmpv4_code |
| 79 | self.icmpv6_type = icmpv6_type |
| 80 | self.icmpv6_code = icmpv6_code |
| 81 | self.ipv6flow_label = ipv6flow_label |
| 82 | self.ecn = ecn |
| 83 | self.ipv6_target = ipv6_target |
| 84 | self.ipv6_sll = ipv6_sll |
| 85 | self.ipv6_tll = ipv6_tll |
| 86 | self.ipv6_extension = ipv6_extension |
ChetanGaonker | dbd4e4b | 2016-10-28 17:40:11 -0700 | [diff] [blame] | 87 | if controller is not None: |
| 88 | self.controller=controller |
| 89 | self.cfg_url = 'http://%s:8181/onos/v1/flows/' %(self.controller) |
Chetan Gaonker | 7ab338c | 2016-04-15 17:23:17 -0700 | [diff] [blame] | 90 | |
| 91 | @classmethod |
ChetanGaonker | dbd4e4b | 2016-10-28 17:40:11 -0700 | [diff] [blame] | 92 | def get_flows(cls, device_id,controller=None): |
| 93 | return OnosCtrl.get_flows(device_id,controller=controller) |
Chetan Gaonker | 7ab338c | 2016-04-15 17:23:17 -0700 | [diff] [blame] | 94 | |
| 95 | def addFlow(self): |
| 96 | """ |
| 97 | Description: |
| 98 | Creates a single flow in the specified device |
| 99 | Required: |
| 100 | * deviceId: id of the device |
| 101 | Optional: |
| 102 | * ingressPort: port ingress device |
| 103 | * egressPort: port of egress device |
| 104 | * ethType: specify ethType |
| 105 | * ethSrc: specify ethSrc ( i.e. src mac addr ) |
| 106 | * ethDst: specify ethDst ( i.e. dst mac addr ) |
| 107 | * ipProto: specify ip protocol |
| 108 | * ipSrc: specify ip source address with mask eg. ip#/24 |
| 109 | as a tuple (type, ip#) |
| 110 | * ipDst: specify ip destination address eg. ip#/24 |
| 111 | as a tuple (type, ip#) |
| 112 | * tcpSrc: specify tcp source port |
| 113 | * tcpDst: specify tcp destination port |
| 114 | Returns: |
| 115 | True for successful requests; |
| 116 | False for failure/error on requests |
| 117 | """ |
| 118 | flowJson = { "priority":100, |
| 119 | "isPermanent":"true", |
| 120 | "timeout":0, |
| 121 | "deviceId":self.deviceId, |
| 122 | "treatment":{"instructions":[]}, |
| 123 | "selector": {"criteria":[]}} |
| 124 | if self.appId: |
| 125 | flowJson[ "appId" ] = self.appId |
ChetanGaonker | 720ea61 | 2016-06-21 17:54:25 -0700 | [diff] [blame] | 126 | |
Chetan Gaonker | 7ab338c | 2016-04-15 17:23:17 -0700 | [diff] [blame] | 127 | if self.egressPort: |
| 128 | flowJson[ 'treatment' ][ 'instructions' ].append( { |
| 129 | "type":"OUTPUT", |
| 130 | "port":self.egressPort } ) |
| 131 | if self.ingressPort: |
| 132 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 133 | "type":"IN_PORT", |
| 134 | "port":self.ingressPort } ) |
| 135 | if self.ethType: |
| 136 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 137 | "type":"ETH_TYPE", |
| 138 | "ethType":self.ethType } ) |
| 139 | if self.ethSrc: |
| 140 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 141 | "type":"ETH_SRC", |
| 142 | "mac":self.ethSrc } ) |
| 143 | if self.ethDst: |
| 144 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 145 | "type":"ETH_DST", |
| 146 | "mac":self.ethDst } ) |
| 147 | if self.vlan: |
| 148 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 149 | "type":"VLAN_VID", |
| 150 | "vlanId":self.vlan } ) |
| 151 | if self.mpls: |
| 152 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 153 | "type":"MPLS_LABEL", |
| 154 | "label":self.mpls } ) |
| 155 | if self.ipSrc: |
| 156 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 157 | "type":self.ipSrc[0], |
| 158 | "ip":self.ipSrc[1] } ) |
| 159 | if self.ipDst: |
| 160 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 161 | "type":self.ipDst[0], |
| 162 | "ip":self.ipDst[1] } ) |
| 163 | if self.tcpSrc: |
| 164 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 165 | "type":"TCP_SRC", |
| 166 | "tcpPort": self.tcpSrc } ) |
| 167 | if self.tcpDst: |
| 168 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 169 | "type":"TCP_DST", |
| 170 | "tcpPort": self.tcpDst } ) |
| 171 | if self.udpSrc: |
| 172 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 173 | "type":"UDP_SRC", |
| 174 | "udpPort": self.udpSrc } ) |
| 175 | if self.udpDst: |
| 176 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 177 | "type":"UDP_DST", |
| 178 | "udpPort": self.udpDst } ) |
| 179 | if self.ipProto: |
| 180 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 181 | "type":"IP_PROTO", |
| 182 | "protocol": self.ipProto } ) |
ChetanGaonker | 720ea61 | 2016-06-21 17:54:25 -0700 | [diff] [blame] | 183 | if self.dscp: |
| 184 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 185 | "type":"IP_DSCP", |
| 186 | "ipDscp": self.dscp } ) |
| 187 | |
| 188 | if self.icmpv4_type: |
| 189 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 190 | "type":'ICMPV4_TYPE', |
| 191 | "icmpType":self.icmpv4_type } ) |
| 192 | |
| 193 | if self.icmpv6_type: |
| 194 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 195 | "type":'ICMPV6_TYPE', |
| 196 | "icmpv6Type":self.icmpv6_type } ) |
| 197 | |
| 198 | if self.icmpv4_code: |
| 199 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 200 | "type":'ICMPV4_CODE', |
| 201 | "icmpCode": self.icmpv4_code } ) |
| 202 | |
| 203 | if self.icmpv6_code: |
| 204 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 205 | "type":'ICMPV6_CODE', |
| 206 | "icmpv6Code": self.icmpv6_code } ) |
| 207 | |
| 208 | if self.ipv6flow_label: |
| 209 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 210 | "type":'IPV6_FLABEL', |
| 211 | "flowLabel": self.ipv6flow_label } ) |
| 212 | |
| 213 | if self.ecn: |
| 214 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 215 | "type":"IP_ECN", |
| 216 | "ipEcn": self.ecn } ) |
| 217 | |
| 218 | if self.ipv6_target: |
| 219 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 220 | "type":'IPV6_ND_TARGET', |
| 221 | "targetAddress": self.ipv6_target } ) |
| 222 | |
| 223 | if self.ipv6_sll: |
| 224 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 225 | "type":'IPV6_ND_SLL', |
| 226 | "mac": self.ipv6_sll } ) |
| 227 | |
| 228 | if self.ipv6_tll: |
| 229 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 230 | "type":'IPV6_ND_TLL', |
| 231 | "mac": self.ipv6_tll } ) |
| 232 | |
| 233 | |
| 234 | if self.ipv6_extension: |
| 235 | flowJson[ 'selector' ][ 'criteria' ].append( { |
| 236 | "type":'IPV6_EXTHDR', |
| 237 | "exthdrFlags": self.ipv6_extension } ) |
| 238 | |
| 239 | |
| 240 | |
Chetan Gaonker | 7ab338c | 2016-04-15 17:23:17 -0700 | [diff] [blame] | 241 | |
| 242 | return self.sendFlow( deviceId=self.deviceId, flowJson=flowJson) |
| 243 | |
| 244 | def removeFlow(self, deviceId, flowId): |
| 245 | """ |
| 246 | Description: |
| 247 | Remove specific device flow |
| 248 | Required: |
| 249 | str deviceId - id of the device |
| 250 | str flowId - id of the flow |
| 251 | Return: |
| 252 | Returns True if successfully deletes flows, otherwise False |
| 253 | """ |
| 254 | # NOTE: REST url requires the intent id to be in decimal form |
| 255 | query = self.cfg_url + str( deviceId ) + '/' + str( int( flowId ) ) |
| 256 | response = requests.delete(query, auth = self.auth) |
| 257 | if response: |
| 258 | if 200 <= response.status_code <= 299: |
| 259 | return True |
| 260 | else: |
| 261 | return False |
| 262 | |
| 263 | return True |
| 264 | |
| 265 | def findFlow(self, deviceId, **criterias): |
ChetanGaonker | dbd4e4b | 2016-10-28 17:40:11 -0700 | [diff] [blame] | 266 | flows = self.get_flows(deviceId,controller=self.controller) |
Chetan Gaonker | 7ab338c | 2016-04-15 17:23:17 -0700 | [diff] [blame] | 267 | match_keys = criterias.keys() |
| 268 | matches = len(match_keys) |
| 269 | num_matched = 0 |
| 270 | for f in flows: |
| 271 | criteria = f['selector']['criteria'] |
| 272 | for c in criteria: |
| 273 | if c['type'] not in match_keys: |
| 274 | continue |
| 275 | match_key, match_val = criterias.get(c['type']) |
| 276 | val = c[match_key] |
| 277 | if val == match_val: |
| 278 | num_matched += 1 |
ChetanGaonker | 720ea61 | 2016-06-21 17:54:25 -0700 | [diff] [blame] | 279 | if num_matched == matches: |
Chetan Gaonker | 7ab338c | 2016-04-15 17:23:17 -0700 | [diff] [blame] | 280 | return f['id'] |
| 281 | return None |
ChetanGaonker | 720ea61 | 2016-06-21 17:54:25 -0700 | [diff] [blame] | 282 | |
Chetan Gaonker | 7ab338c | 2016-04-15 17:23:17 -0700 | [diff] [blame] | 283 | def sendFlow(self, deviceId, flowJson): |
| 284 | """ |
| 285 | Description: |
| 286 | Sends a single flow to the specified device. This function exists |
| 287 | so you can bypass the addFLow driver and send your own custom flow. |
| 288 | Required: |
| 289 | * The flow in json |
| 290 | * the device id to add the flow to |
| 291 | Returns: |
| 292 | True for successful requests |
| 293 | False for error on requests; |
| 294 | """ |
| 295 | url = self.cfg_url + str(deviceId) |
| 296 | response = requests.post(url, auth = self.auth, data = json.dumps(flowJson) ) |
| 297 | if response.ok: |
| 298 | if response.status_code in [200, 201]: |
| 299 | log.info('Successfully POSTED flow for device %s' %str(deviceId)) |
| 300 | return True |
| 301 | else: |
ChetanGaonker | 720ea61 | 2016-06-21 17:54:25 -0700 | [diff] [blame] | 302 | log.info('Post flow for device %s failed with status %d' %(str(deviceId), |
Chetan Gaonker | 7ab338c | 2016-04-15 17:23:17 -0700 | [diff] [blame] | 303 | response.status_code)) |
| 304 | return False |
| 305 | else: |
| 306 | log.error('Flow post request returned with status %d' %response.status_code) |
| 307 | |
| 308 | return False |