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