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