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