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