1 # Copyright (c) 2015 SONATA-NFV and Paderborn University
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
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
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
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).
30 # from gevent import monkey
31 from gevent
.pywsgi
import WSGIServer
33 from mininet
.node
import OVSSwitch
35 from flask
import Flask
36 from flask
import Response
, request
37 from flask_restful
import Api
, Resource
42 class ChainApi(Resource
):
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.
48 def __init__(self
, inc_ip
, inc_port
, manage
):
50 self
.app
= Flask(__name__
)
51 self
.api
= Api(self
.app
)
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
})
74 @self.app
.after_request
75 def add_access_control_header(response
):
76 response
.headers
['Access-Control-Allow-Origin'] = '*'
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(
85 log
=open("/dev/null", "w") # don't show http logs
87 self
.http_server
.serve_forever(stop_timeout
=1)
88 logging
.info('Stopped %s' % self
.__class
__.__name
__)
92 logging
.info('Stopping %s' % self
.__class
__.__name
__)
93 self
.http_server
.stop(timeout
=1)
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
,
103 logfile
.write(data
+ "\n")
106 class ChainVersionsList(Resource
):
108 Entrypoint to find versions of the chain api.
111 def __init__(self
, api
):
116 :return: flask.Response containing the openstack like description of the chain api
118 # at least let it look like an open stack function
127 "href": "http://%s:%d/v1/",
134 "updated": "2013-07-23T11:33:21Z"
138 """ % (self
.api
.ip
, self
.api
.port
)
140 return Response(resp
, status
=200, mimetype
="application/json")
142 except Exception as ex
:
144 u
"%s: Could not show list of versions." % __name__
)
145 return ex
.message
, 500
148 class ChainList(Resource
):
150 Will retrieve all chains including their paths.
153 def __init__(self
, api
):
158 :return: flask.Response containing all live chains
160 # at least let it look like an open stack function
162 resp
= {"chains": list()}
164 for chain
in self
.api
.manage
.full_chain_data
.values():
165 resp
["chains"].append(chain
)
167 return Response(json
.dumps(resp
), status
=200,
168 mimetype
="application/json")
170 except Exception as ex
:
172 u
"%s: Could not list all network chains." % __name__
)
173 return ex
.message
, 500
176 class BalanceHostList(Resource
):
178 Will retrieve all loadbalance rules including their paths.
181 def __init__(self
, api
):
186 :return: flask.Response containing all live loadbalancer rules
188 # at least let it look like an open stack function
190 resp
= {"loadbalancers": list()}
192 for lb
in self
.api
.manage
.full_lb_data
.values():
193 resp
["loadbalancers"].append(lb
)
195 return Response(json
.dumps(resp
), status
=200,
196 mimetype
="application/json")
198 except Exception as ex
:
200 u
"%s: Could not list all live loadbalancers." % __name__
)
201 return ex
.message
, 500
204 class ChainVnfInterfaces(Resource
):
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
210 def __init__(self
, api
):
213 def put(self
, src_vnf
, src_intfs
, dst_vnf
, dst_intfs
):
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.
219 Does not allow a custom path. Uses ``.post``
220 Internally just makes a POST request with no POST data!
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`
234 return self
.post(src_vnf
, src_intfs
, dst_vnf
, dst_intfs
)
236 def post(self
, src_vnf
, src_intfs
, dst_vnf
, dst_intfs
):
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:
245 This is a limitation of Ryu, as Ryu does not allow the `INPUT_PORT` action!
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`
262 path
= request
.json
.get('path')
263 layer2
= request
.json
.get('layer2', True)
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")
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")
283 except Exception as e
:
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")
289 def delete(self
, src_vnf
, src_intfs
, dst_vnf
, dst_intfs
):
291 A DELETE request to "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>"
292 will delete a previously created chain.
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`
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")
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
:
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")
328 class ChainVnfDcStackInterfaces(Resource
):
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.
334 def __init__(self
, api
):
337 def put(self
, src_dc
, src_stack
, src_vnf
, src_intfs
,
338 dst_dc
, dst_stack
, dst_vnf
, dst_intfs
):
340 A PUT request to "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>"
343 :Note: PUT Requests can not set up custom paths!
345 :param src_dc: Name of the source datacenter
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`
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
374 container_src
, container_dst
, interface_src
, interface_dst
= real_names
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")
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,
390 resp
= {'cookie': cookie
}
391 return Response(json
.dumps(resp
), status
=200,
392 mimetype
="application/json")
394 except Exception as e
:
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")
400 def post(self
, src_dc
, src_stack
, src_vnf
, src_intfs
,
401 dst_dc
, dst_stack
, dst_vnf
, dst_intfs
):
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:
410 This is a limitation of Ryu, as Ryu does not allow the `INPUT_PORT` action!
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`
426 path
= request
.json
.get('path')
427 layer2
= request
.json
.get('layer2', True)
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
439 container_src
, container_dst
, interface_src
, interface_dst
= real_names
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")
449 except Exception as e
:
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")
455 def delete(self
, src_dc
, src_stack
, src_vnf
, src_intfs
,
456 dst_dc
, dst_stack
, dst_vnf
, dst_intfs
):
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.
461 :param src_dc: Name of the source datacenter
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`
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
490 container_src
, container_dst
, interface_src
, interface_dst
= real_names
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
:
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")
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
516 from emuvim
.api
.openstack
.openstack_api_endpoint
import OpenstackApiEndpoint
517 for api
in OpenstackApiEndpoint
.dc_apis
:
518 if api
.compute
.dc
== dc_src
:
520 if api
.compute
.dc
== dc_dst
:
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")
528 for stack
in api_src
.compute
.stacks
.values():
529 if stack
.stack_name
== src_stack
:
531 for stack
in api_dst
.compute
.stacks
.values():
532 if stack
.stack_name
== 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")
540 for server
in stack_src
.servers
.values():
541 if server
.template_name
== src_vnf
:
544 for server
in stack_dst
.servers
.values():
545 if server
.template_name
== dst_vnf
:
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")
552 container_src
= server_src
.name
553 container_dst
= server_dst
.name
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")
566 interface_src
= port_src
.intf_name
567 interface_dst
= port_dst
.intf_name
569 return container_src
, container_dst
, interface_src
, interface_dst
572 class BalanceHostDcStack(Resource
):
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.
578 def __init__(self
, api
):
581 def post(self
, src_dc
, src_stack
, vnf_src_name
, vnf_src_interface
):
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.
587 See :class:`heat.chain_api.BalanceHost.post`
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.
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`
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")
608 dst_vnfs
= req
.get('dst_vnf_interfaces')
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
620 container_src
, interface_src
= real_src
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
634 real_dst_dict
[real_dst
[0]] = real_dst
[1]
636 input_object
= {"dst_vnf_interfaces": real_dst_dict
,
637 "path": req
.get("path", None)}
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")
645 cookie
, floating_ip
= self
.api
.manage
.add_floating_lb(
646 src_dc
, lb_data
=input_object
)
648 return Response(json
.dumps({"cookie": "%d" % cookie
, "floating_ip": "%s" % floating_ip
}),
649 status
=200, mimetype
="application/json")
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")
659 def delete(self
, src_dc
, src_stack
, vnf_src_name
, vnf_src_interface
):
661 Will delete a load balancer that sits behind a specified interface at a vnf for a specific stack
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``
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`
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
684 container_src
, interface_src
= real_src
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")
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")
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")
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
714 from emuvim
.api
.openstack
.openstack_api_endpoint
import OpenstackApiEndpoint
715 for api
in OpenstackApiEndpoint
.dc_apis
:
716 if api
.compute
.dc
== dc_real
:
719 return Response(u
"OpenStackAPI does not exist",
720 status
=500, mimetype
="application/json")
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")
731 for server
in stack_real
.servers
.values():
732 if server
.template_name
== vnf
:
735 if server_real
is None:
736 return Response(u
"VNF does not exist", status
=500,
737 mimetype
="application/json")
739 container_real
= server_real
.name
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")
749 interface_real
= port_real
.intf_name
751 return container_real
, interface_real
754 class BalanceHost(Resource
):
756 Handles requests at "/v1/lb/<vnf_src_name>/<vnf_src_interface>"
757 to set up or delete Load Balancers.
760 def __init__(self
, api
):
763 def post(self
, vnf_src_name
, vnf_src_interface
):
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
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`
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")
783 if vnf_src_name
!= "floating":
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
),
789 mimetype
="application/json")
790 self
.api
.manage
.add_loadbalancer(
791 vnf_src_name
, vnf_src_interface
, lb_data
=req
)
793 return Response(u
"Loadbalancer set up at %s:%s" % (vnf_src_name
, vnf_src_interface
),
794 status
=200, mimetype
="application/json")
796 cookie
, floating_ip
= self
.api
.manage
.add_floating_lb(
797 vnf_src_interface
, lb_data
=req
)
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")
807 def delete(self
, vnf_src_name
, vnf_src_interface
):
809 Will delete a load balancer that sits behind a specified interface at a vnf
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`
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")
826 logging
.debug("Deleting loadbalancer at %s: interface: %s" %
827 (vnf_src_name
, vnf_src_interface
))
828 net
= self
.api
.manage
.net
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")
836 self
.api
.manage
.delete_loadbalancer(
837 vnf_src_name
, vnf_src_interface
)
839 return Response(u
"Loadbalancer deleted at %s:%s" % (vnf_src_name
, vnf_src_interface
),
840 status
=200, mimetype
="application/json")
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")
853 class QueryTopology(Resource
):
855 Handles requests at "/v1/topo/"
858 def __init__(self
, api
):
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.
866 :return: 200 if successful with the network graph as json dict, else 500
870 logging
.debug("Querying topology")
871 graph
= self
.api
.manage
.net
.DCNetwork_graph
872 net
= self
.api
.manage
.net
874 topology
= {"nodes": list()}
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
):
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
)
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"
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
)
906 # do not add any links to the floating switch
908 if graph_node
== "fs1":
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
)
917 topology
["nodes"].append(node
)
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" %
924 return Response(u
"%s: Error querying topology.\n %s" %
925 (__name__
, e
), status
=500, mimetype
="application/json")