Fix: Remove monkey patching to progress with migration to Python3
[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")