Skip to content

Commit 2e4f4e2

Browse files
authored
Hateoas support for polycube (#284)
* Added Hateoas support for polycube
1 parent 6cf3e5b commit 2e4f4e2

File tree

15 files changed

+852
-5
lines changed

15 files changed

+852
-5
lines changed

Documentation/developers/hateoas.rst

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
Hateoas
2+
=======
3+
4+
Polycube supports the Hateoas standard.
5+
Thanks to this feature the daemon (:doc:`polycubed<../polycubed/polycubed>`) is able to provide the list
6+
of valid endpoints to the client that can be used to make requests to the server.
7+
The endpoints provided are those of the level just after the one requested.
8+
From the :doc:`polycubed<../polycubed/polycubed>` root you can explore the service through the Hateoas standard.
9+
10+
11+
What can Hateoas be used for?
12+
-----------------------------
13+
14+
The Hateoas standard can be used to explore endpoints that can be used in Polycube.
15+
In this way a client will be able to access Polycube resources and thus cubes independently.
16+
This feature can also be useful in case you want to implement an alternative client to :doc:`polycubectl<../polycubectl/polycubectl>`.
17+
18+
19+
How to use Hateoas
20+
------------------
21+
This feature can be used by observing the json of :doc:`polycubed<../polycubed/polycubed>` responses.
22+
You can use an http client (e.g. Postman) to execute requests to the Polycube daemon.
23+
24+
25+
26+
Example
27+
-------
28+
29+
In this example if the client request is
30+
31+
::
32+
33+
GET -> localhost:9000/polycube/v1/simplebridge/sb1/
34+
35+
then the server response will include all endpoints a of the level just after the simplebridge name (sb1).
36+
37+
::
38+
39+
{
40+
"name": "sb1",
41+
"uuid": "d138da68-f6f4-4ee9-8238-34e9ab1df156",
42+
"service-name": "simplebridge",
43+
"type": "TC",
44+
"loglevel": "INFO",
45+
"ports": [
46+
{
47+
"name": "tovethe1",
48+
"uuid": "b2d6ebcb-163c-4ee8-a943-ba46b5fa4bda",
49+
"status": "UP",
50+
"peer": "veth1"
51+
},
52+
{
53+
"name": "tovethe2",
54+
"uuid": "e760a44d-5e9f-47e9-85d9-38e144400e83",
55+
"status": "UP",
56+
"peer": "veth2"
57+
}
58+
],
59+
"shadow": false,
60+
"span": false,
61+
"fdb": {
62+
"aging-time": 300,
63+
"entry": [
64+
{
65+
"address": "da:55:44:5a:b1:b1",
66+
"port": "tovethe1",
67+
"age": 29
68+
},
69+
{
70+
"address": "11:ff:fe:80:00:00",
71+
"port": "tovethe2",
72+
"age": 26
73+
},
74+
{
75+
"address": "ee:c9:0e:19:c7:2a",
76+
"port": "tovethe2",
77+
"age": 1
78+
}
79+
]
80+
},
81+
"_links": [
82+
{
83+
"rel": "self",
84+
"href": "//127.0.0.1:9000/polycube/v1/simplebridge/sb1/"
85+
},
86+
{
87+
"rel": "uuid",
88+
"href": "//127.0.0.1:9000/polycube/v1/simplebridge/sb1/uuid/"
89+
},
90+
{
91+
"rel": "type",
92+
"href": "//127.0.0.1:9000/polycube/v1/simplebridge/sb1/type/"
93+
},
94+
{
95+
"rel": "service-name",
96+
"href": "//127.0.0.1:9000/polycube/v1/simplebridge/sb1/service-name/"
97+
},
98+
{
99+
"rel": "loglevel",
100+
"href": "//127.0.0.1:9000/polycube/v1/simplebridge/sb1/loglevel/"
101+
},
102+
{
103+
"rel": "ports",
104+
"href": "//127.0.0.1:9000/polycube/v1/simplebridge/sb1/ports/"
105+
},
106+
{
107+
"rel": "shadow",
108+
"href": "//127.0.0.1:9000/polycube/v1/simplebridge/sb1/shadow/"
109+
},
110+
{
111+
"rel": "span",
112+
"href": "//127.0.0.1:9000/polycube/v1/simplebridge/sb1/span/"
113+
},
114+
{
115+
"rel": "fdb",
116+
"href": "//127.0.0.1:9000/polycube/v1/simplebridge/sb1/fdb/"
117+
}
118+
]
119+
120+
}
121+
122+
As we can see from this answer json, the "_links" section contains all the endpoints
123+
that the client can use to contact :doc:`polycubed<../polycubed/polycubed>` starting from the service name (sb1).
124+
125+
In this way a client can explore the service level by level.
126+
127+
128+
129+
How to know endpoints without hateoas?
130+
--------------------------------------
131+
Well, there is an alternative (harder) way to know the endpoints of a cube that can be used in a Polycube.
132+
The swaggerfile must be produced from the yang datamodel of a service (option **-s** in swagger-codegen_).
133+
134+
To do this you have to provide the datamodel of a service as input to the swagger codegen (**-i** option).
135+
The swaggerfile is a large and not very understandable json.
136+
How can we study it in order to know APIs?
137+
We have to use the swagger-editor_ that can accept the swaggerfile generated and
138+
provides a more user friendly way to observe api's of a service.
139+
Swagger editor allows you to view endpoints and verbs that can be used to interact with a Polycube.
140+
141+
Note: using this method you will only know the endpoints of a cube and not all the endpoints offered by :doc:`polycubed<../polycubed/polycubed>`.
142+
143+
144+
.. _swagger-codegen: https://github.com/polycube-network/polycube-codegen#full-installation-from-sources
145+
.. _swagger-editor: https://editor.swagger.io/

Documentation/developers/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ This guide represents an initial starting point for developers that want to impl
1515
debugging
1616
profiler
1717
hints
18+
hateoas
1819

1920

2021

Documentation/installation.rst

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ Note: if you have llvm 6.0 installed (check with ``apt list --installed | grep "
6060
In this case, remove llvm 6.0 before starting the installation script:
6161

6262
::
63+
6364
sudo apt remove llvm-6.0 llvm-6.0-dev llvm-6.0-runtime
6465

6566

src/polycubectl/prettyprint.go

+4
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,10 @@ func print(indent int, path string, hideList map[string]bool,
272272

273273
// print arrays
274274
for _, key := range children.Keys() {
275+
//Links are not printed because they are not intended for humans
276+
if key == "_links"{
277+
continue;
278+
}
275279
child_, _ := children.Get(key)
276280
child := child_.(*gabs2.Container)
277281
if reflect.ValueOf(child.Data()).Kind() == reflect.Slice {

src/polycubed/src/rest_server.cpp

+38-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "server/Server/ResponseGenerator.h"
3030
#include "config.h"
3131
#include "cubes_dump.h"
32+
#include "server/Resources/Endpoint/Hateoas.h"
3233

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

4142
// start http server for Management APIs
42-
// Incapsultate a core object // TODO probably there are best ways...
43+
// Encapsulate a core object // TODO probably there are best ways...
4344
RestServer::RestServer(Pistache::Address addr, PolycubedCore &core)
4445
: core(core),
46+
host(addr.host()),
47+
port(addr.port().toString()),
4548
httpEndpoint_(std::make_unique<Pistache::Http::Endpoint>(addr)),
4649
logger(spdlog::get("polycubed")) {
4750
logger->info("rest server listening on '{0}:{1}'", addr.host(), addr.port());
@@ -218,6 +221,15 @@ void RestServer::setup_routes() {
218221
using Pistache::Rest::Routes::bind;
219222
router_->options(base + std::string("/"),
220223
bind(&RestServer::root_handler, this));
224+
225+
/* binding root_handler in order to handle get at root.
226+
* It's necessary to provide a way to reach the root to the client.
227+
* Thanks this the client can explore the service using Hateoas.
228+
*
229+
* see server/Resources/Endpoint/Hateoas.h
230+
*/
231+
router_->get(base + std::string("/"),
232+
bind(&RestServer::get_root_handler, this));
221233
// servicectrls
222234
router_->post(base + std::string("/services"),
223235
bind(&RestServer::post_servicectrl, this));
@@ -227,6 +239,7 @@ void RestServer::setup_routes() {
227239
bind(&RestServer::get_servicectrl, this));
228240
router_->del(base + std::string("/services/:name"),
229241
bind(&RestServer::delete_servicectrl, this));
242+
Hateoas::addRoute("services", base);
230243

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

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

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

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

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

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

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

271289
// attach & detach
272290
router_->post(base + std::string("/attach"), bind(&RestServer::attach, this));
273291
router_->post(base + std::string("/detach"), bind(&RestServer::detach, this));
292+
Hateoas::addRoute("attach", base);
293+
Hateoas::addRoute("detach", base);
274294

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

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

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

323+
void RestServer::get_root_handler(const Pistache::Rest::Request &request,
324+
Pistache::Http::ResponseWriter response) {
325+
326+
auto js = Hateoas::HateoasSupport_root(request, host, port);
327+
Rest::Server::ResponseGenerator::Generate({{kOk,
328+
::strdup(js.dump().c_str())}}, std::move(response));
329+
}
330+
302331
void RestServer::root_handler(const Pistache::Rest::Request &request,
303332
Pistache::Http::ResponseWriter response) {
304333
auto help = request.query().get("help").getOrElse("NO_HELP");
@@ -894,5 +923,13 @@ void RestServer::redirect(const Pistache::Rest::Request &request,
894923
response.send(Pistache::Http::Code::Permanent_Redirect);
895924
}
896925

926+
const std::string &RestServer::getHost() {
927+
return host;
928+
}
929+
930+
const std::string &RestServer::getPort() {
931+
return port;
932+
}
933+
897934
} // namespace polycubed
898935
} // namespace polycube

src/polycubed/src/rest_server.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,16 @@ class RestServer {
6363
void start();
6464
void shutdown();
6565
void load_last_topology();
66+
const std::string& getHost();
67+
const std::string& getPort();
6668

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

74+
void get_root_handler(const Pistache::Rest::Request &request,
75+
Pistache::Http::ResponseWriter response);
7276
void root_handler(const Pistache::Rest::Request &request,
7377
Pistache::Http::ResponseWriter response);
7478
void root_help(HelpType type, Pistache::Http::ResponseWriter response);
@@ -143,7 +147,8 @@ class RestServer {
143147
PolycubedCore &core;
144148
std::unique_ptr<Pistache::Http::Endpoint> httpEndpoint_;
145149
std::shared_ptr<Pistache::Rest::Router> router_;
146-
150+
const std::string host;
151+
const std::string port;
147152
static std::string whitelist_cert_path;
148153
static std::string blacklist_cert_path;
149154

src/polycubed/src/server/Resources/Endpoint/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ set(endpoint_sources
88
${CMAKE_CURRENT_LIST_DIR}/CaseResource.cpp
99
${CMAKE_CURRENT_LIST_DIR}/Service.cpp
1010
${CMAKE_CURRENT_LIST_DIR}/PathParamField.cpp
11+
${CMAKE_CURRENT_LIST_DIR}/Hateoas.cpp
1112
PARENT_SCOPE)

0 commit comments

Comments
 (0)