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