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 mininet
.node
import OVSSwitch
32 from flask
import Flask
33 from flask
import Response
, request
34 from flask_restful
import Api
, Resource
37 class ChainApi(Resource
):
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.
43 def __init__(self
, inc_ip
, inc_port
, manage
):
45 self
.app
= Flask(__name__
)
46 self
.api
= Api(self
.app
)
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")
70 @self.app
.after_request
71 def add_access_control_header(response
):
72 response
.headers
['Access-Control-Allow-Origin'] = '*'
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)
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
,
90 logfile
.write(data
+ "\n")
93 class Shutdown(Resource
):
95 logging
.debug(("%s is beeing shut down") % (__name__
))
96 func
= request
.environ
.get('werkzeug.server.shutdown')
98 raise RuntimeError('Not running with the Werkzeug Server')
102 class ChainVersionsList(Resource
):
104 Entrypoint to find versions of the chain api.
107 def __init__(self
, api
):
112 :return: flask.Response containing the openstack like description of the chain api
114 # at least let it look like an open stack function
123 "href": "http://%s:%d/v1/",
130 "updated": "2013-07-23T11:33:21Z"
134 """ % (self
.api
.ip
, self
.api
.port
)
136 return Response(resp
, status
=200, mimetype
="application/json")
138 except Exception as ex
:
140 u
"%s: Could not show list of versions." % __name__
)
141 return ex
.message
, 500
144 class ChainList(Resource
):
146 Will retrieve all chains including their paths.
149 def __init__(self
, api
):
154 :return: flask.Response containing all live chains
156 # at least let it look like an open stack function
158 resp
= {"chains": list()}
160 for chain
in self
.api
.manage
.full_chain_data
.values():
161 resp
["chains"].append(chain
)
163 return Response(json
.dumps(resp
), status
=200,
164 mimetype
="application/json")
166 except Exception as ex
:
168 u
"%s: Could not list all network chains." % __name__
)
169 return ex
.message
, 500
172 class BalanceHostList(Resource
):
174 Will retrieve all loadbalance rules including their paths.
177 def __init__(self
, api
):
182 :return: flask.Response containing all live loadbalancer rules
184 # at least let it look like an open stack function
186 resp
= {"loadbalancers": list()}
188 for lb
in self
.api
.manage
.full_lb_data
.values():
189 resp
["loadbalancers"].append(lb
)
191 return Response(json
.dumps(resp
), status
=200,
192 mimetype
="application/json")
194 except Exception as ex
:
196 u
"%s: Could not list all live loadbalancers." % __name__
)
197 return ex
.message
, 500
200 class ChainVnfInterfaces(Resource
):
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
206 def __init__(self
, api
):
209 def put(self
, src_vnf
, src_intfs
, dst_vnf
, dst_intfs
):
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.
215 Does not allow a custom path. Uses ``.post``
216 Internally just makes a POST request with no POST data!
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`
230 return self
.post(src_vnf
, src_intfs
, dst_vnf
, dst_intfs
)
232 def post(self
, src_vnf
, src_intfs
, dst_vnf
, dst_intfs
):
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:
241 This is a limitation of Ryu, as Ryu does not allow the `INPUT_PORT` action!
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`
258 path
= request
.json
.get('path')
259 layer2
= request
.json
.get('layer2', True)
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")
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")
279 except Exception as e
:
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")
285 def delete(self
, src_vnf
, src_intfs
, dst_vnf
, dst_intfs
):
287 A DELETE request to "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>"
288 will delete a previously created chain.
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`
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")
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
:
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")
324 class ChainVnfDcStackInterfaces(Resource
):
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.
330 def __init__(self
, api
):
333 def put(self
, src_dc
, src_stack
, src_vnf
, src_intfs
,
334 dst_dc
, dst_stack
, dst_vnf
, dst_intfs
):
336 A PUT request to "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>"
339 :Note: PUT Requests can not set up custom paths!
341 :param src_dc: Name of the source datacenter
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`
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
370 container_src
, container_dst
, interface_src
, interface_dst
= real_names
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")
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,
386 resp
= {'cookie': cookie
}
387 return Response(json
.dumps(resp
), status
=200,
388 mimetype
="application/json")
390 except Exception as e
:
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")
396 def post(self
, src_dc
, src_stack
, src_vnf
, src_intfs
,
397 dst_dc
, dst_stack
, dst_vnf
, dst_intfs
):
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:
406 This is a limitation of Ryu, as Ryu does not allow the `INPUT_PORT` action!
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`
422 path
= request
.json
.get('path')
423 layer2
= request
.json
.get('layer2', True)
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
435 container_src
, container_dst
, interface_src
, interface_dst
= real_names
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")
445 except Exception as e
:
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")
451 def delete(self
, src_dc
, src_stack
, src_vnf
, src_intfs
,
452 dst_dc
, dst_stack
, dst_vnf
, dst_intfs
):
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.
457 :param src_dc: Name of the source datacenter
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`
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
486 container_src
, container_dst
, interface_src
, interface_dst
= real_names
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
:
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")
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
512 from openstack_api_endpoint
import OpenstackApiEndpoint
513 for api
in OpenstackApiEndpoint
.dc_apis
:
514 if api
.compute
.dc
== dc_src
:
516 if api
.compute
.dc
== dc_dst
:
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")
524 for stack
in api_src
.compute
.stacks
.values():
525 if stack
.stack_name
== src_stack
:
527 for stack
in api_dst
.compute
.stacks
.values():
528 if stack
.stack_name
== 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")
536 for server
in stack_src
.servers
.values():
537 if server
.template_name
== src_vnf
:
540 for server
in stack_dst
.servers
.values():
541 if server
.template_name
== dst_vnf
:
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")
548 container_src
= server_src
.name
549 container_dst
= server_dst
.name
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")
562 interface_src
= port_src
.intf_name
563 interface_dst
= port_dst
.intf_name
565 return container_src
, container_dst
, interface_src
, interface_dst
568 class BalanceHostDcStack(Resource
):
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.
574 def __init__(self
, api
):
577 def post(self
, src_dc
, src_stack
, vnf_src_name
, vnf_src_interface
):
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.
583 See :class:`heat.chain_api.BalanceHost.post`
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.
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`
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")
604 dst_vnfs
= req
.get('dst_vnf_interfaces')
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
616 container_src
, interface_src
= real_src
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
630 real_dst_dict
[real_dst
[0]] = real_dst
[1]
632 input_object
= {"dst_vnf_interfaces": real_dst_dict
,
633 "path": req
.get("path", None)}
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")
641 cookie
, floating_ip
= self
.api
.manage
.add_floating_lb(
642 src_dc
, lb_data
=input_object
)
644 return Response(json
.dumps({"cookie": "%d" % cookie
, "floating_ip": "%s" % floating_ip
}),
645 status
=200, mimetype
="application/json")
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")
655 def delete(self
, src_dc
, src_stack
, vnf_src_name
, vnf_src_interface
):
657 Will delete a load balancer that sits behind a specified interface at a vnf for a specific stack
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``
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`
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
680 container_src
, interface_src
= real_src
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")
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")
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")
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
710 from openstack_api_endpoint
import OpenstackApiEndpoint
711 for api
in OpenstackApiEndpoint
.dc_apis
:
712 if api
.compute
.dc
== dc_real
:
715 return Response(u
"OpenStackAPI does not exist",
716 status
=500, mimetype
="application/json")
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")
727 for server
in stack_real
.servers
.values():
728 if server
.template_name
== vnf
:
731 if server_real
is None:
732 return Response(u
"VNF does not exist", status
=500,
733 mimetype
="application/json")
735 container_real
= server_real
.name
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")
745 interface_real
= port_real
.intf_name
747 return container_real
, interface_real
750 class BalanceHost(Resource
):
752 Handles requests at "/v1/lb/<vnf_src_name>/<vnf_src_interface>"
753 to set up or delete Load Balancers.
756 def __init__(self
, api
):
759 def post(self
, vnf_src_name
, vnf_src_interface
):
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
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`
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")
779 if vnf_src_name
!= "floating":
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
),
785 mimetype
="application/json")
786 self
.api
.manage
.add_loadbalancer(
787 vnf_src_name
, vnf_src_interface
, lb_data
=req
)
789 return Response(u
"Loadbalancer set up at %s:%s" % (vnf_src_name
, vnf_src_interface
),
790 status
=200, mimetype
="application/json")
792 cookie
, floating_ip
= self
.api
.manage
.add_floating_lb(
793 vnf_src_interface
, lb_data
=req
)
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")
803 def delete(self
, vnf_src_name
, vnf_src_interface
):
805 Will delete a load balancer that sits behind a specified interface at a vnf
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`
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")
822 logging
.debug("Deleting loadbalancer at %s: interface: %s" %
823 (vnf_src_name
, vnf_src_interface
))
824 net
= self
.api
.manage
.net
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")
832 self
.api
.manage
.delete_loadbalancer(
833 vnf_src_name
, vnf_src_interface
)
835 return Response(u
"Loadbalancer deleted at %s:%s" % (vnf_src_name
, vnf_src_interface
),
836 status
=200, mimetype
="application/json")
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")
849 class QueryTopology(Resource
):
851 Handles requests at "/v1/topo/"
854 def __init__(self
, api
):
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.
862 :return: 200 if successful with the network graph as json dict, else 500
866 logging
.debug("Querying topology")
867 graph
= self
.api
.manage
.net
.DCNetwork_graph
868 net
= self
.api
.manage
.net
870 topology
= {"nodes": list()}
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
):
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
)
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"
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
)
902 # do not add any links to the floating switch
904 if graph_node
== "fs1":
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
)
913 topology
["nodes"].append(node
)
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" %
920 return Response(u
"%s: Error querying topology.\n %s" %
921 (__name__
, e
), status
=500, mimetype
="application/json")