Fixed missing license headers
[osm/vim-emu.git] / src / emuvim / api / openstack / chain_api.py
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 """
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")