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