northbound_api.py 11.5 KB
Newer Older
Binh Nguyen's avatar
Binh Nguyen committed
1
# Copyright (C) 2017 Binh Nguyen binh@cs.utah.edu.
2
# Copyright (C) 2018 Simon Redman sredman@cs.utah.edu
Binh Nguyen's avatar
Binh Nguyen committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
from webob import Response
Binh Nguyen's avatar
Binh Nguyen committed
19 20 21
from northbound_match import Match as Match
from northbound_actions import Actions as Actions
from sr_flows_mgmt import SR_flows_mgmt as SR_flows_mgmt
Simon Redman's avatar
Simon Redman committed
22
import nlsdn_controller
23
from nlsdn_controller import nlsdnController
Binh Nguyen's avatar
Binh Nguyen committed
24
from ospf_monitor import *
25
import json
Simon Redman's avatar
Simon Redman committed
26
from typing import List
27 28

from ryu.app.wsgi import ControllerBase
Binh Nguyen's avatar
Binh Nguyen committed
29

Binh Nguyen's avatar
Binh Nguyen committed
30

31
LOG = logging.getLogger('ryu.app.North_api')
Binh Nguyen's avatar
Binh Nguyen committed
32
LOG.setLevel(logging.INFO)
Binh Nguyen's avatar
Binh Nguyen committed
33 34 35 36 37
HEADERS = {
                        'Access-Control-Allow-Origin': '*',
                        'Access-Control-Allow-Methods': 'GET, POST',
                        'Access-Control-Allow-Headers': 'Origin, Content-Type',
                        'Content-Type':'application/json'}
Binh Nguyen's avatar
Binh Nguyen committed
38 39

class North_api(ControllerBase):
40 41
    def __init__(self, req, link, data, **config):
        super(North_api, self).__init__(req, link, data, **config)
42
        self.nlsdn_controller: nlsdnController = data['nlsdn-controller']
43 44

    def delete_single_flow(self, req, **_kwargs):
Simon Redman's avatar
Simon Redman committed
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
        """
        Delete one or more flows from the specified router which match the passed match

        Params:
          - dpid: ID of router to delete from
          - match: Match rules as defined by the Match type

        Example usage:
            curl -H "Content-Type: application/json" --data '{ "dpid":2,
                            "match": {
                                "ipv6_dst": "fd00:10:10:12:1::fed2",
                                "ipv6_src": "fd00:10:10:5::/64"
                            }
                }' http://localhost:8080/flow_mgmt/delete
            Will command the SDN router with ID 2 to delete the match on all traffic destined for fd00:10:10:12:1::fed
            from the subnet fd00:10:10:5::/64
        """
        body = json.loads(s=req.body.decode('utf-8'))
63 64
        SR = SR_flows_mgmt()

Simon Redman's avatar
Simon Redman committed
65 66 67 68 69 70 71 72
        for required_key in ['dpid', 'match']:
            if not required_key in body:
                error = "Invalid POST; missing key {key}".format(key=required_key)
                LOG.info(error)
                return Response(status=422, headers=HEADERS, body=error)

        match = Match(**body['match'])
        dpid = int(body['dpid'])
73
        priority = 0
Simon Redman's avatar
Simon Redman committed
74 75
        if 'priority' in body:
            priority = int(body['priority'])
76

Simon Redman's avatar
Simon Redman committed
77 78 79 80 81 82
        # Check whether we are doing nlsdn or OpenFlow
        nlsdn_mode = False
        openflow_mode = False
        if dpid in SR.dpid_to_datapath:
            openflow_mode = True
            LOG.warning("OpenFlow Mode Detected. This will almost certainly not work because the author is not maintaining OpenFlow in favour of nlsdn")
83

Simon Redman's avatar
Simon Redman committed
84 85 86 87 88 89 90 91 92 93 94 95 96
        if dpid in self.nlsdn_controller.id_map:
            nlsdn_mode = True

        if not (nlsdn_mode or openflow_mode):
            raise ValueError("DPID {dpid} matched neither openflow nor nlsdn".format(dpid=dpid))
        if nlsdn_mode and openflow_mode:
            raise ValueError("Found a dpid match for both openflow and nlsdn")

        LOG.info("RECEIVED NB API: delete_single_flow: (dpid, match) = (%s, %s)" % (dpid, match) )

        if nlsdn_mode:
            try:
                self.nlsdn_controller.delete_flows(dpid, match)
97
                return Response(status=200, headers=HEADERS)
Simon Redman's avatar
Simon Redman committed
98 99 100 101 102 103 104 105 106 107 108
            except nlsdn_controller.NoSuchMatchError:
                return Response(
                    status=404,
                    headers=HEADERS,
                    body="No matches of node with ID {dpid} matched filter".format(dpid=dpid)
                )

        if openflow_mode:
            if SR.delete_single_flow(dpid, priority, match):
                LOG.info("Deleted a flow.")
                return Response(status=200, headers=HEADERS)
109 110
        return Response(status=500, headers=HEADERS)

111
    def delete_all_flows(self, req, **_kwargs):
Simon Redman's avatar
Simon Redman committed
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
        """
        Delete all flows of the reqested routers

        Params:
          - dpid: List of IDs which are matched to either OpenFlow or nlsdn nodes

        Example Usage:
            curl -H "Content-Type: application/json" --data '{ "dpids": [3] }'
                http://localhost:8080/flow_mgmt/delete_all_flows
            Will command the SDN router with ID 3 to drop its table of matches and actions
        """
        body = json.loads(s=req.body.decode('utf-8'))

        for required_key in ['dpids']:
            if not required_key in body:
                error = "Invalid POST; missing key {key}".format(key=required_key)
                LOG.info(error)
                return Response(status=422, headers=HEADERS, body=error)
130 131
        SR = SR_flows_mgmt()

Simon Redman's avatar
Simon Redman committed
132 133 134 135 136 137 138 139 140
        dpids: List[int] = list(map(lambda x: int(x), body['dpids']))

        for dpid in dpids:
            # Check whether we are doing nlsdn or OpenFlow
            nlsdn_mode = False
            openflow_mode = False
            if dpid in SR.dpid_to_datapath:
                openflow_mode = True
                LOG.warning("OpenFlow Mode Detected. This will almost certainly not work because the author is not maintaining OpenFlow in favour of nlsdn")
141

Simon Redman's avatar
Simon Redman committed
142 143 144 145 146 147 148 149 150 151 152 153
            if dpid in self.nlsdn_controller.id_map:
                nlsdn_mode = True

            if not (nlsdn_mode or openflow_mode):
                raise ValueError("DPID {dpid} matched neither openflow nor nlsdn".format(dpid=dpid))
            if nlsdn_mode and openflow_mode:
                raise ValueError("Found a dpid match for both openflow and nlsdn")

            LOG.debug("RECEIVED NB API: delete_all_flows: (dpid) = (%s)" % (dpid) )

            if nlsdn_mode:
                self.nlsdn_controller.delete_flows(dpid)
154
                return Response(status=200, headers=HEADERS)
Simon Redman's avatar
Simon Redman committed
155 156 157 158
            if openflow_mode:
                if SR.delete_all_flows(dpid):
                    LOG.info("Deleted all flows in switch %s." % dpid)
                    return Response(status=200, headers=HEADERS)
159
        return Response(status=500, headers=HEADERS)
Binh Nguyen's avatar
Binh Nguyen committed
160

161
    def insert_single_flow(self, req, **_kwargs):
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
        """
        Insert a single flow

        This method was originally designed for the single-purpose of OpenFlow dpid inserts, but now handles
        both nlsdn and OpenFlow. Thus, dpid is used for both cases, and we just check for a node in either
        which is assigned a matching ID

        Params:
          - dpid: ID of router to push a rule
          - match: Match rules as defined by the Match type
          - action: Action rules as defined by the Actions type
          - priority (optional): OpenFlow rule priority value. Not used by nlsdn (at the moment)

        Example usage:
            curl -H "Content-Type: application/json" --data '{ "dpid":2,
                            "match": {
178 179
                                "ipv6_dst": "fd00:10:10:12:1::fed2",
                                "ipv6_src": "fd00:10:10:5::/64"
180 181 182 183 184
                            },
                            "actions": {
                                "srv6_dst": ["fd00:10:10:3:1::efdd","fd00:10:10:4:1::4f4f"]
                            }
                }' http://localhost:8080/flow_mgmt/insert
185 186
            Will command the SDN router with ID 2 to match all traffic destined for fd00:10:10:12:1::fed
            from the subnet fd00:10:10:5::/64 to go via fd00:10:10:3:1::efdd then fd00:10:10:4:1::4f4f
187 188 189 190 191 192 193 194 195 196 197 198
        """
        body = json.loads(s=req.body.decode('utf-8'))

        for required_key in ['dpid', 'match', 'actions']:
            if not required_key in body:
                error = "Invalid POST; missing key {key}".format(key=required_key)
                LOG.info(error)
                return Response(status=422, headers=HEADERS, body=error)

        actions = Actions(**body['actions'])
        match = Match(**body['match'])
        dpid = int(body['dpid'])
199 200
        SR = SR_flows_mgmt()
        priority = 0
201 202 203 204 205 206 207 208 209 210 211 212 213
        if 'priority' in body:
            priority = int(body['priority'])

        # Check whether we are doing nlsdn or OpenFlow
        nlsdn_mode = False
        openflow_mode = False
        if dpid in SR.dpid_to_datapath:
            openflow_mode = True
            LOG.warning("OpenFlow Mode Detected. This will almost certainly not work because the author is not maintaining OpenFlow in favour of nlsdn")

        if dpid in self.nlsdn_controller.id_map:
            nlsdn_mode = True

214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
        if not (nlsdn_mode or openflow_mode):
            raise ValueError("DPID {dpid} matched neither openflow nor nlsdn".format(dpid=dpid))
        if nlsdn_mode and openflow_mode:
            raise ValueError("Found a dpid match for both openflow and nlsdn")

        LOG.info("RECEIVED NB_API: insert_single_flow: (dpid, match, actions) = (%s,%s,%s)" % (dpid, match, actions))

        if nlsdn_mode:
            self.nlsdn_controller.insert_single_flow(dpid, match, actions)

        if openflow_mode:
            if not actions or not match:
                LOG.error("Actions or match fields are empty: actions = %s, match = %s" % (actions, match))
                return Response(status = 500, headers=HEADERS)
            if not SR.insert_single_flow(dpid, priority, match, actions):
                LOG.info("Inserted a flow.")
                return Response(status=200, headers=HEADERS)
            else:
                LOG.error("Can't insert a flow!")
                return Response(status=500, headers=HEADERS)
Binh Nguyen's avatar
Binh Nguyen committed
234

235 236
    def handle_http_options(self, req, **_kwargs):
                return Response(content_type='application/json', headers=HEADERS)
237 238 239 240

    def get_nlsdn_ids(self, req):
        """
        Return a dictionary mapping management IPs to ids useful for calling nlsdn endpoints
241 242

        Example usage: curl http://localhost:8080/nlsdn-mapping
243 244 245 246 247 248
        """
        headers = {
            'Access-Control-Allow-Origin': '*', # Anybody may request this resource
            'Access-Control-Allow-Methods': 'GET',
            'Access-Control-Allow-Headers': 'Origin, Content-Type',
        }
249 250 251 252
        id_mapping = self.nlsdn_controller.id_map

        # The mapping stored by nlsdn_controller is id -> IP. Swap it
        ip_mapping = { ip: nid for nid, ip in id_mapping.items() }
253 254 255

        return Response(status=200,
                        content_type='application/json',
256
                        json=ip_mapping,
257 258
                        headers=headers,
                        )
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282

    def get_routes_of_node(self, req):
        """
        Return the result of asking for /service/nl/v1/routes
        (Assumes an nlsdn ID)
        :param req:
        """
        body = json.loads(s=req.body.decode('utf-8'))

        for required_key in ['dpid']:
            if not required_key in body:
                error = "Invalid POST; missing key {key}".format(key=required_key)
                LOG.info(error)
                return Response(status=422, headers=HEADERS, body=error)

        dpid: int = int(body['dpid'])

        routes = self.nlsdn_controller.get_routes(dpid)

        return Response(status=200,
                        content_type='application/json',
                        json=routes,
                        headers=HEADERS,
                        )