Skip to content

Commit

Permalink
Hateoas support for polycube (#284)
Browse files Browse the repository at this point in the history
* Added Hateoas support for polycube
  • Loading branch information
DavideAG authored Apr 30, 2020
1 parent 6cf3e5b commit 2e4f4e2
Show file tree
Hide file tree
Showing 15 changed files with 852 additions and 5 deletions.
145 changes: 145 additions & 0 deletions Documentation/developers/hateoas.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
Hateoas
=======

Polycube supports the Hateoas standard.
Thanks to this feature the daemon (:doc:`polycubed<../polycubed/polycubed>`) is able to provide the list
of valid endpoints to the client that can be used to make requests to the server.
The endpoints provided are those of the level just after the one requested.
From the :doc:`polycubed<../polycubed/polycubed>` root you can explore the service through the Hateoas standard.


What can Hateoas be used for?
-----------------------------

The Hateoas standard can be used to explore endpoints that can be used in Polycube.
In this way a client will be able to access Polycube resources and thus cubes independently.
This feature can also be useful in case you want to implement an alternative client to :doc:`polycubectl<../polycubectl/polycubectl>`.


How to use Hateoas
------------------
This feature can be used by observing the json of :doc:`polycubed<../polycubed/polycubed>` responses.
You can use an http client (e.g. Postman) to execute requests to the Polycube daemon.



Example
-------

In this example if the client request is

::

GET -> localhost:9000/polycube/v1/simplebridge/sb1/

then the server response will include all endpoints a of the level just after the simplebridge name (sb1).

::

{
"name": "sb1",
"uuid": "d138da68-f6f4-4ee9-8238-34e9ab1df156",
"service-name": "simplebridge",
"type": "TC",
"loglevel": "INFO",
"ports": [
{
"name": "tovethe1",
"uuid": "b2d6ebcb-163c-4ee8-a943-ba46b5fa4bda",
"status": "UP",
"peer": "veth1"
},
{
"name": "tovethe2",
"uuid": "e760a44d-5e9f-47e9-85d9-38e144400e83",
"status": "UP",
"peer": "veth2"
}
],
"shadow": false,
"span": false,
"fdb": {
"aging-time": 300,
"entry": [
{
"address": "da:55:44:5a:b1:b1",
"port": "tovethe1",
"age": 29
},
{
"address": "11:ff:fe:80:00:00",
"port": "tovethe2",
"age": 26
},
{
"address": "ee:c9:0e:19:c7:2a",
"port": "tovethe2",
"age": 1
}
]
},
"_links": [
{
"rel": "self",
"href": "//127.0.0.1:9000/polycube/v1/simplebridge/sb1/"
},
{
"rel": "uuid",
"href": "//127.0.0.1:9000/polycube/v1/simplebridge/sb1/uuid/"
},
{
"rel": "type",
"href": "//127.0.0.1:9000/polycube/v1/simplebridge/sb1/type/"
},
{
"rel": "service-name",
"href": "//127.0.0.1:9000/polycube/v1/simplebridge/sb1/service-name/"
},
{
"rel": "loglevel",
"href": "//127.0.0.1:9000/polycube/v1/simplebridge/sb1/loglevel/"
},
{
"rel": "ports",
"href": "//127.0.0.1:9000/polycube/v1/simplebridge/sb1/ports/"
},
{
"rel": "shadow",
"href": "//127.0.0.1:9000/polycube/v1/simplebridge/sb1/shadow/"
},
{
"rel": "span",
"href": "//127.0.0.1:9000/polycube/v1/simplebridge/sb1/span/"
},
{
"rel": "fdb",
"href": "//127.0.0.1:9000/polycube/v1/simplebridge/sb1/fdb/"
}
]

}

As we can see from this answer json, the "_links" section contains all the endpoints
that the client can use to contact :doc:`polycubed<../polycubed/polycubed>` starting from the service name (sb1).

In this way a client can explore the service level by level.



How to know endpoints without hateoas?
--------------------------------------
Well, there is an alternative (harder) way to know the endpoints of a cube that can be used in a Polycube.
The swaggerfile must be produced from the yang datamodel of a service (option **-s** in swagger-codegen_).

To do this you have to provide the datamodel of a service as input to the swagger codegen (**-i** option).
The swaggerfile is a large and not very understandable json.
How can we study it in order to know APIs?
We have to use the swagger-editor_ that can accept the swaggerfile generated and
provides a more user friendly way to observe api's of a service.
Swagger editor allows you to view endpoints and verbs that can be used to interact with a Polycube.

Note: using this method you will only know the endpoints of a cube and not all the endpoints offered by :doc:`polycubed<../polycubed/polycubed>`.


.. _swagger-codegen: https://github.com/polycube-network/polycube-codegen#full-installation-from-sources
.. _swagger-editor: https://editor.swagger.io/
1 change: 1 addition & 0 deletions Documentation/developers/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ This guide represents an initial starting point for developers that want to impl
debugging
profiler
hints
hateoas



Expand Down
1 change: 1 addition & 0 deletions Documentation/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ Note: if you have llvm 6.0 installed (check with ``apt list --installed | grep "
In this case, remove llvm 6.0 before starting the installation script:

::

sudo apt remove llvm-6.0 llvm-6.0-dev llvm-6.0-runtime


Expand Down
4 changes: 4 additions & 0 deletions src/polycubectl/prettyprint.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,10 @@ func print(indent int, path string, hideList map[string]bool,

// print arrays
for _, key := range children.Keys() {
//Links are not printed because they are not intended for humans
if key == "_links"{
continue;
}
child_, _ := children.Get(key)
child := child_.(*gabs2.Container)
if reflect.ValueOf(child.Data()).Kind() == reflect.Slice {
Expand Down
39 changes: 38 additions & 1 deletion src/polycubed/src/rest_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "server/Server/ResponseGenerator.h"
#include "config.h"
#include "cubes_dump.h"
#include "server/Resources/Endpoint/Hateoas.h"

namespace polycube {
namespace polycubed {
Expand All @@ -39,9 +40,11 @@ std::string RestServer::blacklist_cert_path;
const std::string RestServer::base = "/polycube/v1/";

// start http server for Management APIs
// Incapsultate a core object // TODO probably there are best ways...
// Encapsulate a core object // TODO probably there are best ways...
RestServer::RestServer(Pistache::Address addr, PolycubedCore &core)
: core(core),
host(addr.host()),
port(addr.port().toString()),
httpEndpoint_(std::make_unique<Pistache::Http::Endpoint>(addr)),
logger(spdlog::get("polycubed")) {
logger->info("rest server listening on '{0}:{1}'", addr.host(), addr.port());
Expand Down Expand Up @@ -218,6 +221,15 @@ void RestServer::setup_routes() {
using Pistache::Rest::Routes::bind;
router_->options(base + std::string("/"),
bind(&RestServer::root_handler, this));

/* binding root_handler in order to handle get at root.
* It's necessary to provide a way to reach the root to the client.
* Thanks this the client can explore the service using Hateoas.
*
* see server/Resources/Endpoint/Hateoas.h
*/
router_->get(base + std::string("/"),
bind(&RestServer::get_root_handler, this));
// servicectrls
router_->post(base + std::string("/services"),
bind(&RestServer::post_servicectrl, this));
Expand All @@ -227,6 +239,7 @@ void RestServer::setup_routes() {
bind(&RestServer::get_servicectrl, this));
router_->del(base + std::string("/services/:name"),
bind(&RestServer::delete_servicectrl, this));
Hateoas::addRoute("services", base);

// cubes
router_->get(base + std::string("/cubes"),
Expand All @@ -242,6 +255,7 @@ void RestServer::setup_routes() {

router_->options(base + std::string("/cubes/:cubeName"),
bind(&RestServer::cube_help, this));
Hateoas::addRoute("cubes", base);

// netdevs
router_->get(base + std::string("/netdevs"),
Expand All @@ -254,10 +268,12 @@ void RestServer::setup_routes() {

router_->options(base + std::string("/netdevs/:netdevName"),
bind(&RestServer::netdev_help, this));
Hateoas::addRoute("netdevs", base);

// version
router_->get(base + std::string("/version"),
bind(&RestServer::get_version, this));
Hateoas::addRoute("version", base);

// connect & disconnect
router_->post(base + std::string("/connect"),
Expand All @@ -267,10 +283,14 @@ void RestServer::setup_routes() {

router_->options(base + std::string("/connect"),
bind(&RestServer::connect_help, this));
Hateoas::addRoute("connect", base);
Hateoas::addRoute("disconnect", base);

// attach & detach
router_->post(base + std::string("/attach"), bind(&RestServer::attach, this));
router_->post(base + std::string("/detach"), bind(&RestServer::detach, this));
Hateoas::addRoute("attach", base);
Hateoas::addRoute("detach", base);

// topology
router_->get(base + std::string("/topology"),
Expand All @@ -280,6 +300,7 @@ void RestServer::setup_routes() {

router_->options(base + std::string("/topology"),
bind(&RestServer::topology_help, this));
Hateoas::addRoute("topology", base);

router_->addNotFoundHandler(bind(&RestServer::redirect, this));
}
Expand All @@ -299,6 +320,14 @@ void RestServer::logJson(json j) {
#endif
}

void RestServer::get_root_handler(const Pistache::Rest::Request &request,
Pistache::Http::ResponseWriter response) {

auto js = Hateoas::HateoasSupport_root(request, host, port);
Rest::Server::ResponseGenerator::Generate({{kOk,
::strdup(js.dump().c_str())}}, std::move(response));
}

void RestServer::root_handler(const Pistache::Rest::Request &request,
Pistache::Http::ResponseWriter response) {
auto help = request.query().get("help").getOrElse("NO_HELP");
Expand Down Expand Up @@ -894,5 +923,13 @@ void RestServer::redirect(const Pistache::Rest::Request &request,
response.send(Pistache::Http::Code::Permanent_Redirect);
}

const std::string &RestServer::getHost() {
return host;
}

const std::string &RestServer::getPort() {
return port;
}

} // namespace polycubed
} // namespace polycube
7 changes: 6 additions & 1 deletion src/polycubed/src/rest_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,16 @@ class RestServer {
void start();
void shutdown();
void load_last_topology();
const std::string& getHost();
const std::string& getPort();

private:
void setup_routes();
// this function has to be static because has to be passed as a callback.
static int client_verify_callback(int preverify_ok, void *ctx);

void get_root_handler(const Pistache::Rest::Request &request,
Pistache::Http::ResponseWriter response);
void root_handler(const Pistache::Rest::Request &request,
Pistache::Http::ResponseWriter response);
void root_help(HelpType type, Pistache::Http::ResponseWriter response);
Expand Down Expand Up @@ -143,7 +147,8 @@ class RestServer {
PolycubedCore &core;
std::unique_ptr<Pistache::Http::Endpoint> httpEndpoint_;
std::shared_ptr<Pistache::Rest::Router> router_;

const std::string host;
const std::string port;
static std::string whitelist_cert_path;
static std::string blacklist_cert_path;

Expand Down
1 change: 1 addition & 0 deletions src/polycubed/src/server/Resources/Endpoint/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ set(endpoint_sources
${CMAKE_CURRENT_LIST_DIR}/CaseResource.cpp
${CMAKE_CURRENT_LIST_DIR}/Service.cpp
${CMAKE_CURRENT_LIST_DIR}/PathParamField.cpp
${CMAKE_CURRENT_LIST_DIR}/Hateoas.cpp
PARENT_SCOPE)
Loading

0 comments on commit 2e4f4e2

Please sign in to comment.