| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 1 | # Copyright (c) 2015 SONATA-NFV and Paderborn University |
| 2 | # ALL RIGHTS RESERVED. |
| 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 |
| 7 | # |
| 8 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | # |
| 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 | # |
| 16 | # Neither the name of the SONATA-NFV, Paderborn University |
| 17 | # nor the names of its contributors may be used to endorse or promote |
| 18 | # products derived from this software without specific prior written |
| 19 | # permission. |
| 20 | # |
| 21 | # This work has been performed in the framework of the SONATA project, |
| 22 | # funded by the European Commission under Grant number 671517 through |
| 23 | # the Horizon 2020 and 5G-PPP programmes. The authors would like to |
| 24 | # acknowledge the contributions of their colleagues of the SONATA |
| 25 | # partner consortium (www.sonata-nfv.eu). |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 26 | import json |
| 27 | import logging |
| 28 | import copy |
| 29 | |
| peusterm | a05cee0 | 2019-11-14 13:22:19 +0100 | [diff] [blame] | 30 | # from gevent import monkey |
| schillinge | 68d0421 | 2019-03-11 17:57:41 +0100 | [diff] [blame] | 31 | from gevent.pywsgi import WSGIServer |
| 32 | |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 33 | from mininet.node import OVSSwitch |
| 34 | |
| 35 | from flask import Flask |
| 36 | from flask import Response, request |
| 37 | from flask_restful import Api, Resource |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 38 | |
| peusterm | a05cee0 | 2019-11-14 13:22:19 +0100 | [diff] [blame] | 39 | # monkey.patch_all() |
| schillinge | 68d0421 | 2019-03-11 17:57:41 +0100 | [diff] [blame] | 40 | |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 41 | |
| 42 | class ChainApi(Resource): |
| 43 | """ |
| 44 | The chain API is a component that is not used in OpenStack. |
| 45 | It is a custom built REST API that can be used to create network chains and loadbalancers. |
| 46 | """ |
| 47 | |
| 48 | def __init__(self, inc_ip, inc_port, manage): |
| 49 | # setup Flask |
| 50 | self.app = Flask(__name__) |
| 51 | self.api = Api(self.app) |
| 52 | self.ip = inc_ip |
| 53 | self.port = inc_port |
| 54 | self.manage = manage |
| 55 | self.playbook_file = '/tmp/son-emu-requests.log' |
| 56 | self.api.add_resource(ChainVersionsList, "/", |
| 57 | resource_class_kwargs={'api': self}) |
| 58 | self.api.add_resource(ChainList, "/v1/chain/list", |
| 59 | resource_class_kwargs={'api': self}) |
| 60 | self.api.add_resource(ChainVnfInterfaces, "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>", |
| 61 | resource_class_kwargs={'api': self}) |
| 62 | self.api.add_resource(ChainVnfDcStackInterfaces, |
| 63 | "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>", |
| 64 | resource_class_kwargs={'api': self}) |
| 65 | self.api.add_resource(BalanceHostList, "/v1/lb/list", |
| 66 | resource_class_kwargs={'api': self}) |
| 67 | self.api.add_resource(BalanceHost, "/v1/lb/<vnf_src_name>/<vnf_src_interface>", |
| 68 | resource_class_kwargs={'api': self}) |
| 69 | self.api.add_resource(BalanceHostDcStack, "/v1/lb/<src_dc>/<src_stack>/<vnf_src_name>/<vnf_src_interface>", |
| 70 | resource_class_kwargs={'api': self}) |
| 71 | self.api.add_resource(QueryTopology, "/v1/topo", |
| 72 | resource_class_kwargs={'api': self}) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 73 | |
| 74 | @self.app.after_request |
| 75 | def add_access_control_header(response): |
| 76 | response.headers['Access-Control-Allow-Origin'] = '*' |
| 77 | return response |
| 78 | |
| 79 | def _start_flask(self): |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 80 | logging.info("Starting %s endpoint @ http://%s:%d" % |
| 81 | ("ChainDummyApi", self.ip, self.port)) |
| schillinge | 68d0421 | 2019-03-11 17:57:41 +0100 | [diff] [blame] | 82 | self.http_server = WSGIServer( |
| 83 | (self.ip, self.port), |
| 84 | self.app, |
| 85 | log=open("/dev/null", "w") # don't show http logs |
| 86 | ) |
| 87 | self.http_server.serve_forever(stop_timeout=1) |
| 88 | logging.info('Stopped %s' % self.__class__.__name__) |
| 89 | |
| 90 | def stop(self): |
| 91 | if self.http_server: |
| 92 | logging.info('Stopping %s' % self.__class__.__name__) |
| 93 | self.http_server.stop(timeout=1) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 94 | |
| 95 | def dump_playbook(self): |
| 96 | with self.manage.lock: |
| 97 | with open(self.playbook_file, 'a') as logfile: |
| 98 | if len(request.data) > 0: |
| 99 | data = "# CHAIN API\n" |
| 100 | data += "curl -X {type} -H \"Content-type: application/json\" -d '{data}' {url}".format(type=request.method, |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 101 | data=request.data, |
| 102 | url=request.url) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 103 | logfile.write(data + "\n") |
| 104 | |
| 105 | |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 106 | class ChainVersionsList(Resource): |
| 107 | ''' |
| 108 | Entrypoint to find versions of the chain api. |
| 109 | ''' |
| 110 | |
| 111 | def __init__(self, api): |
| 112 | self.api = api |
| 113 | |
| 114 | def get(self): |
| 115 | ''' |
| 116 | :return: flask.Response containing the openstack like description of the chain api |
| 117 | ''' |
| 118 | # at least let it look like an open stack function |
| 119 | try: |
| 120 | resp = """ |
| 121 | { |
| 122 | "versions": [ |
| 123 | { |
| 124 | "id": "v1", |
| 125 | "links": [ |
| 126 | { |
| 127 | "href": "http://%s:%d/v1/", |
| 128 | "rel": "self" |
| 129 | } |
| 130 | ], |
| 131 | "status": "CURRENT", |
| 132 | "version": "1", |
| 133 | "min_version": "1", |
| 134 | "updated": "2013-07-23T11:33:21Z" |
| 135 | } |
| 136 | ] |
| 137 | } |
| 138 | """ % (self.api.ip, self.api.port) |
| 139 | |
| 140 | return Response(resp, status=200, mimetype="application/json") |
| 141 | |
| 142 | except Exception as ex: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 143 | logging.exception( |
| 144 | u"%s: Could not show list of versions." % __name__) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 145 | return ex.message, 500 |
| 146 | |
| 147 | |
| 148 | class ChainList(Resource): |
| 149 | ''' |
| 150 | Will retrieve all chains including their paths. |
| 151 | ''' |
| 152 | |
| 153 | def __init__(self, api): |
| 154 | self.api = api |
| 155 | |
| 156 | def get(self): |
| 157 | ''' |
| 158 | :return: flask.Response containing all live chains |
| 159 | ''' |
| 160 | # at least let it look like an open stack function |
| 161 | try: |
| 162 | resp = {"chains": list()} |
| 163 | |
| 164 | for chain in self.api.manage.full_chain_data.values(): |
| 165 | resp["chains"].append(chain) |
| 166 | |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 167 | return Response(json.dumps(resp), status=200, |
| 168 | mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 169 | |
| 170 | except Exception as ex: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 171 | logging.exception( |
| 172 | u"%s: Could not list all network chains." % __name__) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 173 | return ex.message, 500 |
| 174 | |
| 175 | |
| 176 | class BalanceHostList(Resource): |
| 177 | ''' |
| 178 | Will retrieve all loadbalance rules including their paths. |
| 179 | ''' |
| 180 | |
| 181 | def __init__(self, api): |
| 182 | self.api = api |
| 183 | |
| 184 | def get(self): |
| 185 | ''' |
| 186 | :return: flask.Response containing all live loadbalancer rules |
| 187 | ''' |
| 188 | # at least let it look like an open stack function |
| 189 | try: |
| 190 | resp = {"loadbalancers": list()} |
| 191 | |
| 192 | for lb in self.api.manage.full_lb_data.values(): |
| 193 | resp["loadbalancers"].append(lb) |
| 194 | |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 195 | return Response(json.dumps(resp), status=200, |
| 196 | mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 197 | |
| 198 | except Exception as ex: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 199 | logging.exception( |
| 200 | u"%s: Could not list all live loadbalancers." % __name__) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 201 | return ex.message, 500 |
| 202 | |
| 203 | |
| 204 | class ChainVnfInterfaces(Resource): |
| 205 | """ |
| 206 | Handles requests targeted at: "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>" |
| 207 | Requests are for tearing down or setting up a chain between two vnfs |
| 208 | """ |
| 209 | |
| 210 | def __init__(self, api): |
| 211 | self.api = api |
| 212 | |
| 213 | def put(self, src_vnf, src_intfs, dst_vnf, dst_intfs): |
| 214 | """ |
| 215 | A put request to "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>" |
| 216 | will create a chain between two interfaces at the specified vnfs. |
| 217 | |
| 218 | Note: |
| 219 | Does not allow a custom path. Uses ``.post`` |
| 220 | Internally just makes a POST request with no POST data! |
| 221 | |
| 222 | :param src_vnf: Name of the source VNF |
| 223 | :type src_vnf: ``str`` |
| 224 | :param src_intfs: Name of the source VNF interface to chain on |
| 225 | :type src_intfs: ``str`` |
| 226 | :param dst_vnf: Name of the destination VNF |
| 227 | :type dst_vnf: ``str`` |
| 228 | :param dst_intfs: Name of the destination VNF interface to chain on |
| 229 | :type dst_intfs: ``str`` |
| 230 | :return: flask.Response 200 if set up correctly else 500 also returns the cookie as dict {'cookie': value} |
| 231 | 501 if one of the VNF / intfs does not exist |
| 232 | :rtype: :class:`flask.Response` |
| 233 | """ |
| 234 | return self.post(src_vnf, src_intfs, dst_vnf, dst_intfs) |
| 235 | |
| 236 | def post(self, src_vnf, src_intfs, dst_vnf, dst_intfs): |
| 237 | """ |
| 238 | A post request to "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>" |
| 239 | will create a chain between two interfaces at the specified vnfs. |
| 240 | The POST data contains the path like this. |
| 241 | { "path": ["dc1.s1", "s1", "dc4.s1"]} |
| 242 | path specifies the destination vnf and interface and contains a list of switches |
| 243 | that the path traverses. The path may not contain single hop loops like: |
| 244 | [s1, s2, s1]. |
| 245 | This is a limitation of Ryu, as Ryu does not allow the `INPUT_PORT` action! |
| 246 | |
| 247 | :param src_vnf: Name of the source VNF |
| 248 | :type src_vnf: ``str`` |
| 249 | :param src_intfs: Name of the source VNF interface to chain on |
| 250 | :type src_intfs: ``str`` |
| 251 | :param dst_vnf: Name of the destination VNF |
| 252 | :type dst_vnf: ``str`` |
| 253 | :param dst_intfs: Name of the destination VNF interface to chain on |
| 254 | :type dst_intfs: ``str`` |
| 255 | :return: flask.Response 200 if set up correctly else 500 also returns the cookie as dict {'cookie': value} |
| 256 | 501 if one of the VNF / intfs does not exist |
| 257 | :rtype: :class:`flask.Response` |
| 258 | |
| 259 | """ |
| 260 | |
| 261 | if request.is_json: |
| 262 | path = request.json.get('path') |
| 263 | layer2 = request.json.get('layer2', True) |
| 264 | else: |
| 265 | path = None |
| 266 | layer2 = True |
| 267 | |
| 268 | # check if both VNFs exist |
| 269 | if not self.api.manage.check_vnf_intf_pair(src_vnf, src_intfs): |
| 270 | return Response(u"VNF %s or intfs %s does not exist" % (src_vnf, src_intfs), status=501, |
| 271 | mimetype="application/json") |
| 272 | if not self.api.manage.check_vnf_intf_pair(dst_vnf, dst_intfs): |
| 273 | return Response(u"VNF %s or intfs %s does not exist" % (dst_vnf, dst_intfs), status=501, |
| 274 | mimetype="application/json") |
| 275 | try: |
| 276 | cookie = self.api.manage.network_action_start(src_vnf, dst_vnf, vnf_src_interface=src_intfs, |
| 277 | vnf_dst_interface=dst_intfs, bidirectional=True, |
| 278 | path=path, layer2=layer2) |
| 279 | resp = {'cookie': cookie} |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 280 | return Response(json.dumps(resp), status=200, |
| 281 | mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 282 | |
| 283 | except Exception as e: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 284 | logging.exception( |
| 285 | u"%s: Error setting up the chain.\n %s" % (__name__, e)) |
| 286 | return Response(u"Error setting up the chain", |
| 287 | status=500, mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 288 | |
| 289 | def delete(self, src_vnf, src_intfs, dst_vnf, dst_intfs): |
| 290 | """ |
| 291 | A DELETE request to "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>" |
| 292 | will delete a previously created chain. |
| 293 | |
| 294 | :param src_vnf: Name of the source VNF |
| 295 | :type src_vnf: ``str`` |
| 296 | :param src_intfs: Name of the source VNF interface to chain on |
| 297 | :type src_intfs: ``str`` |
| 298 | :param dst_vnf: Name of the destination VNF |
| 299 | :type dst_vnf: ``str`` |
| 300 | :param dst_intfs: Name of the destination VNF interface to chain on |
| 301 | :type dst_intfs: ``str`` |
| 302 | :return: flask.Response 200 if set up correctly else 500\ |
| 303 | also returns the cookie as dict {'cookie': value} |
| 304 | 501 if one of the VNF / intfs does not exist |
| 305 | :rtype: :class:`flask.Response` |
| 306 | |
| 307 | """ |
| 308 | # check if both VNFs exist |
| 309 | # check if both VNFs exist |
| 310 | if not self.api.manage.check_vnf_intf_pair(src_vnf, src_intfs): |
| 311 | return Response(u"VNF %s or intfs %s does not exist" % (src_vnf, src_intfs), status=501, |
| 312 | mimetype="application/json") |
| 313 | if not self.api.manage.check_vnf_intf_pair(dst_vnf, dst_intfs): |
| 314 | return Response(u"VNF %s or intfs %s does not exist" % (dst_vnf, dst_intfs), status=501, |
| 315 | mimetype="application/json") |
| 316 | try: |
| 317 | cookie = self.api.manage.network_action_stop(src_vnf, dst_vnf, vnf_src_interface=src_intfs, |
| 318 | vnf_dst_interface=dst_intfs, bidirectional=True) |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 319 | return Response(json.dumps(cookie), status=200, |
| 320 | mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 321 | except Exception as e: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 322 | logging.exception( |
| 323 | u"%s: Error deleting the chain.\n %s" % (__name__, e)) |
| 324 | return Response(u"Error deleting the chain", |
| 325 | status=500, mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 326 | |
| 327 | |
| 328 | class ChainVnfDcStackInterfaces(Resource): |
| 329 | ''' |
| 330 | Handles requests targeted at: "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>" |
| 331 | Handles tearing down or setting up a chain between two vnfs for stacks. |
| 332 | ''' |
| 333 | |
| 334 | def __init__(self, api): |
| 335 | self.api = api |
| 336 | |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 337 | def put(self, src_dc, src_stack, src_vnf, src_intfs, |
| 338 | dst_dc, dst_stack, dst_vnf, dst_intfs): |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 339 | """ |
| 340 | A PUT request to "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>" |
| 341 | will set up chain. |
| 342 | |
| 343 | :Note: PUT Requests can not set up custom paths! |
| 344 | |
| 345 | :param src_dc: Name of the source datacenter |
| 346 | :type src_dc: `str` |
| 347 | :param src_stack: Name of the source stack |
| 348 | :type src_stack: `str` |
| 349 | :param src_vnf: Name of the source VNF |
| 350 | :type src_vnf: ``str`` |
| 351 | :param src_intfs: Name of the source VNF interface to chain on |
| 352 | :type src_intfs: ``str`` |
| 353 | :param dst_dc: Name of the destination datacenter |
| 354 | :type dst_dc: ``str`` |
| 355 | :param dst_stack: Name of the destination stack |
| 356 | :type dst_stack: ``str`` |
| 357 | :param dst_vnf: Name of the destination VNF |
| 358 | :type dst_vnf: ``str`` |
| 359 | :param dst_intfs: Name of the destination VNF interface to chain on |
| 360 | :type dst_intfs: ``str`` |
| 361 | :return: flask.Response 200 if set up correctly else 500\ |
| 362 | also returns the cookie as dict {'cookie': value} |
| 363 | 501 if VNF or intfs does not exist |
| 364 | :rtype: :class:`flask.Response` |
| 365 | |
| 366 | """ |
| 367 | # search for real names |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 368 | real_names = self._findNames( |
| 369 | src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs) |
| 370 | if not isinstance(real_names, tuple): |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 371 | # something went wrong |
| 372 | return real_names |
| 373 | |
| 374 | container_src, container_dst, interface_src, interface_dst = real_names |
| 375 | |
| 376 | # check if both VNFs exist |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 377 | if not self.api.manage.check_vnf_intf_pair( |
| 378 | container_src, interface_src): |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 379 | return Response(u"VNF %s or intfs %s does not exist" % (container_src, interface_src), status=501, |
| 380 | mimetype="application/json") |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 381 | if not self.api.manage.check_vnf_intf_pair( |
| 382 | container_dst, interface_dst): |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 383 | return Response(u"VNF %s or intfs %s does not exist" % (container_dst, interface_dst), status=501, |
| 384 | mimetype="application/json") |
| 385 | |
| 386 | try: |
| 387 | cookie = self.api.manage.network_action_start(container_src, container_dst, vnf_src_interface=interface_src, |
| 388 | vnf_dst_interface=interface_dst, bidirectional=True, |
| 389 | layer2=True) |
| 390 | resp = {'cookie': cookie} |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 391 | return Response(json.dumps(resp), status=200, |
| 392 | mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 393 | |
| 394 | except Exception as e: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 395 | logging.exception( |
| 396 | u"%s: Error setting up the chain.\n %s" % (__name__, e)) |
| 397 | return Response(u"Error setting up the chain", |
| 398 | status=500, mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 399 | |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 400 | def post(self, src_dc, src_stack, src_vnf, src_intfs, |
| 401 | dst_dc, dst_stack, dst_vnf, dst_intfs): |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 402 | """ |
| 403 | A post request to "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>" |
| 404 | will create a chain between two interfaces at the specified vnfs. |
| 405 | The POST data contains the path like this. |
| 406 | { "path": ["dc1.s1", "s1", "dc4.s1"]} |
| 407 | path specifies the destination vnf and interface and contains a list of switches |
| 408 | that the path traverses. The path may not contain single hop loops like: |
| 409 | [s1, s2, s1]. |
| 410 | This is a limitation of Ryu, as Ryu does not allow the `INPUT_PORT` action! |
| 411 | |
| 412 | :param src_vnf: Name of the source VNF |
| 413 | :type src_vnf: ``str`` |
| 414 | :param src_intfs: Name of the source VNF interface to chain on |
| 415 | :type src_intfs: ``str`` |
| 416 | :param dst_vnf: Name of the destination VNF |
| 417 | :type dst_vnf: ``str`` |
| 418 | :param dst_intfs: Name of the destination VNF interface to chain on |
| 419 | :type dst_intfs: ``str`` |
| 420 | :return: flask.Response 200 if set up correctly else 500 also returns the cookie as dict {'cookie': value} |
| 421 | 501 if vnf / intfs do not exist |
| 422 | :rtype: :class:`flask.Response` |
| 423 | |
| 424 | """ |
| 425 | if request.is_json: |
| 426 | path = request.json.get('path') |
| 427 | layer2 = request.json.get('layer2', True) |
| 428 | else: |
| 429 | path = None |
| 430 | layer2 = True |
| 431 | |
| 432 | # search for real names |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 433 | real_names = self._findNames( |
| 434 | src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs) |
| 435 | if not isinstance(real_names, tuple): |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 436 | # something went wrong |
| 437 | return real_names |
| 438 | |
| 439 | container_src, container_dst, interface_src, interface_dst = real_names |
| 440 | |
| 441 | try: |
| 442 | cookie = self.api.manage.network_action_start(container_src, container_dst, vnf_src_interface=interface_src, |
| 443 | vnf_dst_interface=interface_dst, bidirectional=True, |
| 444 | path=path, layer2=layer2) |
| 445 | resp = {'cookie': cookie} |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 446 | return Response(json.dumps(resp), status=200, |
| 447 | mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 448 | |
| 449 | except Exception as e: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 450 | logging.exception( |
| 451 | u"%s: Error setting up the chain.\n %s" % (__name__, e)) |
| 452 | return Response(u"Error setting up the chain", |
| 453 | status=500, mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 454 | |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 455 | def delete(self, src_dc, src_stack, src_vnf, src_intfs, |
| 456 | dst_dc, dst_stack, dst_vnf, dst_intfs): |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 457 | """ |
| 458 | A DELETE request to "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>" |
| 459 | will delete a previously created chain. |
| 460 | |
| 461 | :param src_dc: Name of the source datacenter |
| 462 | :type src_dc: `str` |
| 463 | :param src_stack: Name of the source stack |
| 464 | :type src_stack: `str` |
| 465 | :param src_vnf: Name of the source VNF |
| 466 | :type src_vnf: ``str`` |
| 467 | :param src_intfs: Name of the source VNF interface to chain on |
| 468 | :type src_intfs: ``str`` |
| 469 | :param dst_dc: Name of the destination datacenter |
| 470 | :type dst_dc: ``str`` |
| 471 | :param dst_stack: Name of the destination stack |
| 472 | :type dst_stack: ``str`` |
| 473 | :param dst_vnf: Name of the destination VNF |
| 474 | :type dst_vnf: ``str`` |
| 475 | :param dst_intfs: Name of the destination VNF interface to chain on |
| 476 | :type dst_intfs: ``str`` |
| 477 | :return: flask.Response 200 if set up correctly else 500\ |
| 478 | also returns the cookie as dict {'cookie': value} |
| 479 | 501 if one of the VNF / intfs does not exist |
| 480 | :rtype: :class:`flask.Response` |
| 481 | |
| 482 | """ |
| 483 | # search for real names |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 484 | real_names = self._findNames( |
| 485 | src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs) |
| 486 | if not isinstance(real_names, tuple): |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 487 | # something went wrong, real_names is a Response object |
| 488 | return real_names |
| 489 | |
| 490 | container_src, container_dst, interface_src, interface_dst = real_names |
| 491 | |
| 492 | try: |
| 493 | cookie = self.api.manage.network_action_stop(container_src, container_dst, vnf_src_interface=interface_src, |
| 494 | vnf_dst_interface=interface_dst, bidirectional=True) |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 495 | return Response(json.dumps(cookie), status=200, |
| 496 | mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 497 | except Exception as e: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 498 | logging.exception( |
| 499 | u"%s: Error deleting the chain.\n %s" % (__name__, e)) |
| 500 | return Response(u"Error deleting the chain", |
| 501 | status=500, mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 502 | |
| 503 | # Tries to find real container and interface names according to heat template names |
| 504 | # Returns a tuple of 4 or a Response object |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 505 | def _findNames(self, src_dc, src_stack, src_vnf, src_intfs, |
| 506 | dst_dc, dst_stack, dst_vnf, dst_intfs): |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 507 | # search for datacenters |
| 508 | if src_dc not in self.api.manage.net.dcs or dst_dc not in self.api.manage.net.dcs: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 509 | return Response(u"At least one DC does not exist", |
| 510 | status=500, mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 511 | dc_src = self.api.manage.net.dcs[src_dc] |
| 512 | dc_dst = self.api.manage.net.dcs[dst_dc] |
| 513 | # search for related OpenStackAPIs |
| 514 | api_src = None |
| 515 | api_dst = None |
| 516 | from openstack_api_endpoint import OpenstackApiEndpoint |
| 517 | for api in OpenstackApiEndpoint.dc_apis: |
| 518 | if api.compute.dc == dc_src: |
| 519 | api_src = api |
| 520 | if api.compute.dc == dc_dst: |
| 521 | api_dst = api |
| 522 | if api_src is None or api_dst is None: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 523 | return Response(u"At least one OpenStackAPI does not exist", |
| 524 | status=500, mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 525 | # search for stacks |
| 526 | stack_src = None |
| 527 | stack_dst = None |
| 528 | for stack in api_src.compute.stacks.values(): |
| 529 | if stack.stack_name == src_stack: |
| 530 | stack_src = stack |
| 531 | for stack in api_dst.compute.stacks.values(): |
| 532 | if stack.stack_name == dst_stack: |
| 533 | stack_dst = stack |
| 534 | if stack_src is None or stack_dst is None: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 535 | return Response(u"At least one Stack does not exist", |
| 536 | status=500, mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 537 | # search for servers |
| 538 | server_src = None |
| 539 | server_dst = None |
| 540 | for server in stack_src.servers.values(): |
| 541 | if server.template_name == src_vnf: |
| 542 | server_src = server |
| 543 | break |
| 544 | for server in stack_dst.servers.values(): |
| 545 | if server.template_name == dst_vnf: |
| 546 | server_dst = server |
| 547 | break |
| 548 | if server_src is None or server_dst is None: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 549 | return Response(u"At least one VNF does not exist", |
| 550 | status=500, mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 551 | |
| 552 | container_src = server_src.name |
| 553 | container_dst = server_dst.name |
| 554 | |
| 555 | # search for ports |
| 556 | port_src = None |
| 557 | port_dst = None |
| 558 | if src_intfs in server_src.port_names: |
| 559 | port_src = stack_src.ports[src_intfs] |
| 560 | if dst_intfs in server_dst.port_names: |
| 561 | port_dst = stack_dst.ports[dst_intfs] |
| 562 | if port_src is None or port_dst is None: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 563 | return Response(u"At least one Port does not exist", |
| 564 | status=500, mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 565 | |
| 566 | interface_src = port_src.intf_name |
| 567 | interface_dst = port_dst.intf_name |
| 568 | |
| 569 | return container_src, container_dst, interface_src, interface_dst |
| 570 | |
| 571 | |
| 572 | class BalanceHostDcStack(Resource): |
| 573 | """ |
| 574 | Handles requests to "/v1/lb/<src_dc>/<src_stack>/<vnf_src_name>/<vnf_src_interface>" |
| 575 | Sets up LoadBalancers for VNFs that are belonging to a certain stack. |
| 576 | """ |
| 577 | |
| 578 | def __init__(self, api): |
| 579 | self.api = api |
| 580 | |
| 581 | def post(self, src_dc, src_stack, vnf_src_name, vnf_src_interface): |
| 582 | """ |
| 583 | A POST request to "/v1/lb/<src_dc>/<src_stack>/<vnf_src_name>/<vnf_src_interface>" |
| 584 | will set up a loadbalancer. The target VNFs and interfaces are in the post data. |
| 585 | |
| 586 | :Example: |
| 587 | See :class:`heat.chain_api.BalanceHost.post` |
| 588 | |
| 589 | :param src_dc: Name of the source VNF |
| 590 | :type src_dc: ``str`` |
| 591 | :param src_stack: Name of the source VNF interface to chain on |
| 592 | :type src_stack: ``str`` |
| 593 | * src_stack == "floating" sets up a new floating node, so only use this name if you know what you are doing. |
| 594 | :param vnf_src_name: |
| 595 | :type vnf_src_name: ``str`` |
| 596 | :param vnf_src_interface: |
| 597 | :type vnf_src_interface: ``str`` |
| 598 | :return: flask.Response 200 if set up correctly else 500 |
| 599 | :rtype: :class:`flask.Response` |
| 600 | |
| 601 | """ |
| 602 | try: |
| 603 | req = request.json |
| 604 | if req is None or len(req) == 0 or "dst_vnf_interfaces" not in req: |
| 605 | return Response(u"You have to specify destination vnfs via the POST data.", |
| 606 | status=500, mimetype="application/json") |
| 607 | |
| 608 | dst_vnfs = req.get('dst_vnf_interfaces') |
| 609 | container_src = None |
| 610 | interface_src = None |
| 611 | |
| 612 | # check src vnf/port |
| 613 | if src_stack != "floating": |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 614 | real_src = self._findName( |
| 615 | src_dc, src_stack, vnf_src_name, vnf_src_interface) |
| 616 | if not isinstance(real_src, tuple): |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 617 | # something went wrong, real_src is a Response object |
| 618 | return real_src |
| 619 | |
| 620 | container_src, interface_src = real_src |
| 621 | |
| 622 | real_dst_dict = {} |
| 623 | for dst_vnf in dst_vnfs: |
| 624 | dst_dc = dst_vnf.get('pop', None) |
| 625 | dst_stack = dst_vnf.get('stack', None) |
| 626 | dst_server = dst_vnf.get('server', None) |
| 627 | dst_port = dst_vnf.get('port', None) |
| 628 | if dst_dc is not None and dst_stack is not None and dst_server is not None and dst_port is not None: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 629 | real_dst = self._findName( |
| 630 | dst_dc, dst_stack, dst_server, dst_port) |
| 631 | if not isinstance(real_dst, tuple): |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 632 | # something went wrong, real_dst is a Response object |
| 633 | return real_dst |
| 634 | real_dst_dict[real_dst[0]] = real_dst[1] |
| 635 | |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 636 | input_object = {"dst_vnf_interfaces": real_dst_dict, |
| 637 | "path": req.get("path", None)} |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 638 | |
| 639 | if src_stack != "floating": |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 640 | self.api.manage.add_loadbalancer( |
| 641 | container_src, interface_src, lb_data=input_object) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 642 | return Response(u"Loadbalancer set up at %s:%s" % (container_src, interface_src), |
| 643 | status=200, mimetype="application/json") |
| 644 | else: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 645 | cookie, floating_ip = self.api.manage.add_floating_lb( |
| 646 | src_dc, lb_data=input_object) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 647 | |
| 648 | return Response(json.dumps({"cookie": "%d" % cookie, "floating_ip": "%s" % floating_ip}), |
| 649 | status=200, mimetype="application/json") |
| 650 | |
| 651 | except Exception as e: |
| 652 | logging.exception(u"%s: Error setting up the loadbalancer at %s %s %s:%s.\n %s" % |
| 653 | (__name__, src_dc, src_stack, vnf_src_name, vnf_src_interface, e)) |
| 654 | return Response(u"%s: Error setting up the loadbalancer at %s %s %s:%s.\n %s" % |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 655 | (__name__, src_dc, src_stack, vnf_src_name, |
| 656 | vnf_src_interface, e), status=500, |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 657 | mimetype="application/json") |
| 658 | |
| 659 | def delete(self, src_dc, src_stack, vnf_src_name, vnf_src_interface): |
| 660 | """ |
| 661 | Will delete a load balancer that sits behind a specified interface at a vnf for a specific stack |
| 662 | |
| 663 | :param src_dc: Name of the source VNF |
| 664 | :type src_dc: ``str`` |
| 665 | :param src_stack: Name of the source VNF interface to chain on |
| 666 | :type src_stack: ``str`` |
| 667 | :param vnf_src_name: |
| 668 | :type vnf_src_name: ``str`` |
| 669 | :param vnf_src_interface: |
| 670 | :type vnf_src_interface: ``str`` |
| 671 | :return: flask.Response 200 if set up correctly else 500 |
| 672 | :rtype: :class:`flask.Response` |
| 673 | |
| 674 | """ |
| 675 | try: |
| 676 | # check src vnf/port |
| 677 | if src_stack != "floating": |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 678 | real_src = self._findName( |
| 679 | src_dc, src_stack, vnf_src_name, vnf_src_interface) |
| 680 | if not isinstance(real_src, tuple): |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 681 | # something went wrong, real_src is a Response object |
| 682 | return real_src |
| 683 | |
| 684 | container_src, interface_src = real_src |
| 685 | |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 686 | self.api.manage.delete_loadbalancer( |
| 687 | container_src, interface_src) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 688 | return Response(u"Loadbalancer deleted at %s:%s" % (vnf_src_name, vnf_src_interface), |
| 689 | status=200, mimetype="application/json") |
| 690 | else: |
| 691 | cookie = vnf_src_name |
| 692 | self.api.manage.delete_floating_lb(cookie) |
| 693 | return Response(u"Floating loadbalancer with cookie %s deleted" % (cookie), |
| 694 | status=200, mimetype="application/json") |
| 695 | |
| 696 | except Exception as e: |
| 697 | logging.exception(u"%s: Error deleting the loadbalancer at %s %s %s%s.\n %s" % |
| 698 | (__name__, src_dc, src_stack, vnf_src_name, vnf_src_interface, e)) |
| 699 | return Response(u"%s: Error deleting the loadbalancer at %s %s %s%s." % |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 700 | (__name__, src_dc, src_stack, vnf_src_name, |
| 701 | vnf_src_interface), status=500, |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 702 | mimetype="application/json") |
| 703 | |
| 704 | # Tries to find real container and port name according to heat template names |
| 705 | # Returns a string or a Response object |
| 706 | def _findName(self, dc, stack, vnf, port): |
| 707 | # search for datacenters |
| 708 | if dc not in self.api.manage.net.dcs: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 709 | return Response(u"DC does not exist", status=500, |
| 710 | mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 711 | dc_real = self.api.manage.net.dcs[dc] |
| 712 | # search for related OpenStackAPIs |
| 713 | api_real = None |
| 714 | from openstack_api_endpoint import OpenstackApiEndpoint |
| 715 | for api in OpenstackApiEndpoint.dc_apis: |
| 716 | if api.compute.dc == dc_real: |
| 717 | api_real = api |
| 718 | if api_real is None: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 719 | return Response(u"OpenStackAPI does not exist", |
| 720 | status=500, mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 721 | # search for stacks |
| 722 | stack_real = None |
| 723 | for stackObj in api_real.compute.stacks.values(): |
| 724 | if stackObj.stack_name == stack: |
| 725 | stack_real = stackObj |
| 726 | if stack_real is None: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 727 | return Response(u"Stack does not exist", status=500, |
| 728 | mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 729 | # search for servers |
| 730 | server_real = None |
| 731 | for server in stack_real.servers.values(): |
| 732 | if server.template_name == vnf: |
| 733 | server_real = server |
| 734 | break |
| 735 | if server_real is None: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 736 | return Response(u"VNF does not exist", status=500, |
| 737 | mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 738 | |
| 739 | container_real = server_real.name |
| 740 | |
| 741 | # search for ports |
| 742 | port_real = None |
| 743 | if port in server_real.port_names: |
| 744 | port_real = stack_real.ports[port] |
| 745 | if port_real is None: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 746 | return Response(u"At least one Port does not exist", |
| 747 | status=500, mimetype="application/json") |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 748 | |
| 749 | interface_real = port_real.intf_name |
| 750 | |
| 751 | return container_real, interface_real |
| 752 | |
| 753 | |
| 754 | class BalanceHost(Resource): |
| 755 | """ |
| 756 | Handles requests at "/v1/lb/<vnf_src_name>/<vnf_src_interface>" |
| 757 | to set up or delete Load Balancers. |
| 758 | """ |
| 759 | |
| 760 | def __init__(self, api): |
| 761 | self.api = api |
| 762 | |
| 763 | def post(self, vnf_src_name, vnf_src_interface): |
| 764 | """ |
| 765 | Will set up a Load balancer behind an interface at a specified vnf |
| 766 | We need both to avoid naming conflicts as interface names are not unique |
| 767 | |
| 768 | :param vnf_src_name: Name of the source VNF |
| 769 | :type vnf_src_name: ``str`` |
| 770 | :param vnf_src_interface: Name of the source VNF interface to chain on |
| 771 | :type vnf_src_interface: ``str`` |
| 772 | :return: flask.Response 200 if set up correctly else 500 |
| 773 | 501 if VNF or intfs does not exist |
| 774 | :rtype: :class:`flask.Response` |
| 775 | |
| 776 | """ |
| 777 | try: |
| 778 | req = request.json |
| 779 | if req is None or len(req) == 0 or "dst_vnf_interfaces" not in req: |
| 780 | return Response(u"You have to specify destination vnfs via the POST data.", |
| 781 | status=500, mimetype="application/json") |
| 782 | |
| 783 | if vnf_src_name != "floating": |
| 784 | # check if VNF exist |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 785 | if not self.api.manage.check_vnf_intf_pair( |
| 786 | vnf_src_name, vnf_src_interface): |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 787 | return Response(u"VNF %s or intfs %s does not exist" % (vnf_src_name, vnf_src_interface), |
| 788 | status=501, |
| 789 | mimetype="application/json") |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 790 | self.api.manage.add_loadbalancer( |
| 791 | vnf_src_name, vnf_src_interface, lb_data=req) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 792 | |
| 793 | return Response(u"Loadbalancer set up at %s:%s" % (vnf_src_name, vnf_src_interface), |
| 794 | status=200, mimetype="application/json") |
| 795 | else: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 796 | cookie, floating_ip = self.api.manage.add_floating_lb( |
| 797 | vnf_src_interface, lb_data=req) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 798 | |
| 799 | return Response(json.dumps({"cookie": "%d" % cookie, "floating_ip": "%s" % floating_ip}), |
| 800 | status=200, mimetype="application/json") |
| 801 | except Exception as e: |
| 802 | logging.exception(u"%s: Error setting up the loadbalancer at %s:%s.\n %s" % |
| 803 | (__name__, vnf_src_name, vnf_src_interface, e)) |
| 804 | return Response(u"%s: Error setting up the loadbalancer at %s:%s.\n %s" % |
| 805 | (__name__, vnf_src_name, vnf_src_interface, e), status=500, mimetype="application/json") |
| 806 | |
| 807 | def delete(self, vnf_src_name, vnf_src_interface): |
| 808 | """ |
| 809 | Will delete a load balancer that sits behind a specified interface at a vnf |
| 810 | |
| 811 | :param vnf_src_name: Name of the source VNF |
| 812 | :type vnf_src_name: ``str`` |
| 813 | :param vnf_src_interface: Name of the source VNF interface to chain on |
| 814 | :type vnf_src_interface: ``str`` |
| 815 | :return: flask.Response 200 if set up correctly else 500 |
| 816 | 501 if VNF or intfs does not exist |
| 817 | :rtype: :class:`flask.Response` |
| 818 | |
| 819 | """ |
| 820 | # check if VNF exist |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 821 | if not self.api.manage.check_vnf_intf_pair( |
| 822 | vnf_src_name, vnf_src_interface): |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 823 | return Response(u"VNF %s or intfs %s does not exist" % (vnf_src_name, vnf_src_interface), status=501, |
| 824 | mimetype="application/json") |
| 825 | try: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 826 | logging.debug("Deleting loadbalancer at %s: interface: %s" % |
| 827 | (vnf_src_name, vnf_src_interface)) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 828 | net = self.api.manage.net |
| 829 | |
| 830 | if vnf_src_name != "floating": |
| 831 | # check if VNF exists |
| 832 | if vnf_src_name not in net: |
| 833 | return Response(u"Source VNF or interface can not be found." % vnf_src_name, |
| 834 | status=404, mimetype="application/json") |
| 835 | |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 836 | self.api.manage.delete_loadbalancer( |
| 837 | vnf_src_name, vnf_src_interface) |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 838 | |
| 839 | return Response(u"Loadbalancer deleted at %s:%s" % (vnf_src_name, vnf_src_interface), |
| 840 | status=200, mimetype="application/json") |
| 841 | else: |
| 842 | cookie = vnf_src_name |
| 843 | self.api.manage.delete_floating_lb(cookie) |
| 844 | return Response(u"Floating loadbalancer with cookie %s removed" % (cookie), |
| 845 | status=200, mimetype="application/json") |
| 846 | except Exception as e: |
| 847 | logging.exception(u"%s: Error deleting the loadbalancer at %s%s.\n %s" % |
| 848 | (__name__, vnf_src_name, vnf_src_interface, e)) |
| 849 | return Response(u"%s: Error deleting the loadbalancer at %s%s." % |
| 850 | (__name__, vnf_src_name, vnf_src_interface), status=500, mimetype="application/json") |
| 851 | |
| 852 | |
| 853 | class QueryTopology(Resource): |
| 854 | """ |
| 855 | Handles requests at "/v1/topo/" |
| 856 | """ |
| 857 | |
| 858 | def __init__(self, api): |
| 859 | self.api = api |
| 860 | |
| 861 | def get(self): |
| 862 | """ |
| 863 | Answers GET requests for the current network topology at "/v1/topo". |
| 864 | This will only return switches and datacenters and ignore currently deployed VNFs. |
| 865 | |
| 866 | :return: 200 if successful with the network graph as json dict, else 500 |
| 867 | |
| 868 | """ |
| 869 | try: |
| 870 | logging.debug("Querying topology") |
| 871 | graph = self.api.manage.net.DCNetwork_graph |
| 872 | net = self.api.manage.net |
| 873 | # root node is nodes |
| 874 | topology = {"nodes": list()} |
| 875 | |
| 876 | for n in graph: |
| 877 | # remove root node as well as the floating switch fs1 |
| 878 | if n != "root" and n != "fs1": |
| 879 | # we only want to return switches! |
| 880 | if not isinstance(net[n], OVSSwitch): |
| 881 | continue |
| 882 | node = dict() |
| 883 | |
| 884 | # get real datacenter label |
| 885 | for dc in self.api.manage.net.dcs.values(): |
| 886 | if str(dc.switch) == str(n): |
| 887 | node["name"] = str(n) |
| 888 | node["type"] = "Datacenter" |
| 889 | node["label"] = str(dc.label) |
| 890 | break |
| 891 | |
| 892 | # node is not a datacenter. It has to be a switch |
| 893 | if node.get("type", "") != "Datacenter": |
| 894 | node["name"] = str(n) |
| 895 | node["type"] = "Switch" |
| 896 | |
| 897 | node["links"] = list() |
| 898 | # add links to the topology |
| 899 | for graph_node, data in graph[n].items(): |
| 900 | # only add links to the topology that connect switches |
| 901 | if isinstance(net[graph_node], OVSSwitch): |
| 902 | # we allow multiple edges between switches, so add them all |
| 903 | # with their unique keys |
| 904 | link = copy.copy(data) |
| 905 | for edge in link: |
| peusterm | 72f0988 | 2018-05-15 17:10:27 +0200 | [diff] [blame] | 906 | # do not add any links to the floating switch |
| 907 | # to the topology! |
| peusterm | 0019978 | 2017-05-17 08:48:12 +0200 | [diff] [blame] | 908 | if graph_node == "fs1": |
| 909 | continue |
| 910 | # the translator wants everything as a string! |
| 911 | for key, value in link[edge].items(): |
| 912 | link[edge][key] = str(value) |
| 913 | # name of the destination |
| 914 | link[edge]["name"] = graph_node |
| 915 | node["links"].append(link) |
| 916 | |
| 917 | topology["nodes"].append(node) |
| 918 | |
| 919 | return Response(json.dumps(topology), |
| 920 | status=200, mimetype="application/json") |
| 921 | except Exception as e: |
| 922 | logging.exception(u"%s: Error querying topology.\n %s" % |
| 923 | (__name__, e)) |
| 924 | return Response(u"%s: Error querying topology.\n %s" % |
| 925 | (__name__, e), status=500, mimetype="application/json") |