Merge branch 'topic_benavrhm_rfc8527a_20201103' of https://github.com/benavrhm/clixon into benavrhm-topic_benavrhm_rfc8527a_20201103

This commit is contained in:
Olof hagsand 2020-11-11 13:41:05 +01:00
commit a0006075c3
26 changed files with 921 additions and 792 deletions

View file

@ -56,6 +56,18 @@ enum restconf_media{
};
typedef enum restconf_media restconf_media;
/* @See https://tools.ietf.org/html/rfc8342, ietf-datastores@2018-02-14.yang */
enum ietf_ds {
IETF_DS_NONE = 0,
IETF_DS_RUNNING,
IETF_DS_CANDIDATE,
IETF_DS_STARTUP,
IETF_DS_INTENDED,
IETF_DS_DYNAMIC,
IETF_DS_OPERATIONAL
};
typedef enum ietf_ds ietf_ds_t;
/*
* Prototypes
*/

View file

@ -230,6 +230,7 @@ match_list_keys(yang_stmt *y,
* PUT: If it does not, set op to create, otherwise replace
* PATCH: If it does not, fail, otherwise replace/merge
* @param[in] plain_patch fail if object does not exists AND merge (not replace)
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
*/
static int
api_data_write(clicon_handle h,
@ -242,7 +243,8 @@ api_data_write(clicon_handle h,
int pretty,
restconf_media media_in,
restconf_media media_out,
int plain_patch)
int plain_patch,
ietf_ds_t ds)
{
int retval = -1;
enum operation_type op;
@ -596,12 +598,12 @@ api_data_write(clicon_handle h,
NETCONF_BASE_NAMESPACE); /* bind nc to netconf namespace */
cprintf(cbx, "<edit-config");
/* RFC8040 Sec 1.4:
* If the NETCONF server supports :startup, the RESTCONF server MUST
* automatically update the non-volatile startup configuration
* datastore, after the "running" datastore has been altered as a
* consequence of a RESTCONF edit operation.
* If this is a "data" request and the NETCONF server supports :startup,
* the RESTCONF server MUST automatically update the non-volatile startup
* configuration datastore, after the "running" datastore has been altered
* as a consequence of a RESTCONF edit operation.
*/
if (if_feature(yspec, "ietf-netconf", "startup"))
if ((IETF_DS_NONE == ds) && if_feature(yspec, "ietf-netconf", "startup"))
cprintf(cbx, " copystartup=\"true\"");
cprintf(cbx, " autocommit=\"true\"");
cprintf(cbx, "><target><candidate /></target>");
@ -695,13 +697,14 @@ api_data_put(clicon_handle h,
cvec *qvec,
char *data,
int pretty,
restconf_media media_out)
restconf_media media_out,
ietf_ds_t ds)
{
restconf_media media_in;
media_in = restconf_content_type(h);
return api_data_write(h, req, api_path0, pcvec, pi, qvec, data, pretty,
media_in, media_out, 0);
media_in, media_out, 0, ds);
}
/*! Generic REST PATCH method for plain patch
@ -730,7 +733,8 @@ api_data_patch(clicon_handle h,
cvec *qvec,
char *data,
int pretty,
restconf_media media_out)
restconf_media media_out,
ietf_ds_t ds)
{
restconf_media media_in;
int ret;
@ -740,7 +744,7 @@ api_data_patch(clicon_handle h,
case YANG_DATA_XML:
case YANG_DATA_JSON: /* plain patch */
ret = api_data_write(h, req, api_path0, pcvec, pi, qvec, data, pretty,
media_in, media_out, 1);
media_in, media_out, 1, ds);
break;
case YANG_PATCH_XML:
case YANG_PATCH_JSON: /* RFC 8072 patch */
@ -760,6 +764,7 @@ api_data_patch(clicon_handle h,
* @param[in] pi Offset, where path starts
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
* See RFC 8040 Sec 4.7
* Example:
* curl -X DELETE http://127.0.0.1/restconf/data/interfaces/interface=eth0
@ -771,7 +776,8 @@ api_data_delete(clicon_handle h,
char *api_path,
int pi,
int pretty,
restconf_media media_out)
restconf_media media_out,
ietf_ds_t ds)
{
int retval = -1;
int i;
@ -834,12 +840,12 @@ api_data_delete(clicon_handle h,
cprintf(cbx, "<edit-config");
/* RFC8040 Sec 1.4:
* If the NETCONF server supports :startup, the RESTCONF server MUST
* automatically update the non-volatile startup configuration
* datastore, after the "running" datastore has been altered as a
* consequence of a RESTCONF edit operation.
* If this is a "data" request and the NETCONF server supports :startup,
* the RESTCONF server MUST automatically update the non-volatile startup
* configuration datastore, after the "running" datastore has been altered
* as a consequence of a RESTCONF edit operation.
*/
if (if_feature(yspec, "ietf-netconf", "startup"))
if ((IETF_DS_NONE == ds) && if_feature(yspec, "ietf-netconf", "startup"))
cprintf(cbx, " copystartup=\"true\"");
cprintf(cbx, " autocommit=\"true\"");
cprintf(cbx, "><target><candidate /></target>");

View file

@ -46,14 +46,14 @@ int api_data_options(clicon_handle h, void *req);
int api_data_put(clicon_handle h, void *req, char *api_path,
cvec *pcvec, int pi,
cvec *qvec, char *data,
int pretty, restconf_media media_out);
int pretty, restconf_media media_out, ietf_ds_t ds);
int api_data_patch(clicon_handle h, void *req, char *api_path,
cvec *pcvec, int pi,
cvec *qvec, char *data, int pretty,
restconf_media media_out);
restconf_media media_out, ietf_ds_t ds);
int api_data_delete(clicon_handle h, void *req, char *api_path, int pi,
int pretty, restconf_media media_out);
int pretty, restconf_media media_out, ietf_ds_t ds);
#endif /* _RESTCONF_METHODS_H_ */

View file

@ -363,7 +363,8 @@ api_data_head(clicon_handle h,
int pi,
cvec *qvec,
int pretty,
restconf_media media_out)
restconf_media media_out,
ietf_ds_t ds)
{
return api_data_get2(h, req, api_path, pcvec, pi, qvec, pretty, media_out, 1);
}
@ -402,7 +403,8 @@ api_data_get(clicon_handle h,
int pi,
cvec *qvec,
int pretty,
restconf_media media_out)
restconf_media media_out,
ietf_ds_t ds)
{
return api_data_get2(h, req, api_path, pcvec, pi, qvec, pretty, media_out, 0);
}

View file

@ -42,9 +42,9 @@
* Prototypes
*/
int api_data_head(clicon_handle h, void *req, char *api_path, cvec *pcvec, int pi,
cvec *qvec, int pretty, restconf_media media_out);
cvec *qvec, int pretty, restconf_media media_out, ietf_ds_t ds);
int api_data_get(clicon_handle h, void *req, char *api_path, cvec *pcvec, int pi,
cvec *qvec, int pretty, restconf_media media_out);
cvec *qvec, int pretty, restconf_media media_out, ietf_ds_t ds);
int api_operations_get(clicon_handle h, void *req,
char *api_path, int pi, cvec *qvec, char *data,
int pretty, restconf_media media_out);

View file

@ -152,7 +152,8 @@ api_data_post(clicon_handle h,
cvec *qvec,
char *data,
int pretty,
restconf_media media_out)
restconf_media media_out,
ietf_ds_t ds)
{
int retval = -1;
enum operation_type op = OP_CREATE;
@ -377,12 +378,12 @@ api_data_post(clicon_handle h,
cprintf(cbx, "<edit-config");
/* RFC8040 Sec 1.4:
* If the NETCONF server supports :startup, the RESTCONF server MUST
* automatically update the non-volatile startup configuration
* datastore, after the "running" datastore has been altered as a
* consequence of a RESTCONF edit operation.
* If this is a "data" request and the NETCONF server supports :startup,
* the RESTCONF server MUST automatically update the non-volatile startup
* configuration datastore, after the "running" datastore has been altered
* as a consequence of a RESTCONF edit operation.
*/
if (if_feature(yspec, "ietf-netconf", "startup"))
if ((IETF_DS_NONE == ds) && if_feature(yspec, "ietf-netconf", "startup"))
cprintf(cbx, " copystartup=\"true\"");
cprintf(cbx, " autocommit=\"true\"");
cprintf(cbx, "><target><candidate /></target>");

View file

@ -44,7 +44,7 @@
int api_data_post(clicon_handle h, void *req, char *api_path,
int pi, cvec *qvec, char *data,
int pretty,
restconf_media media_out);
restconf_media media_out, ietf_ds_t ds);
int api_operations_post(clicon_handle h, void *req, char *api_path,
int pi, cvec *qvec, char *data,

View file

@ -153,7 +153,8 @@ api_root_restconf_exact(clicon_handle h,
goto done;
if (clixon_xml_parse_string("<restconf xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><data/>"
"<operations/><yang-library-version>2016-06-21</yang-library-version></restconf>",
"<operations/><yang-library-version>" IETF_YANG_LIBRARY_REVISION
"</yang-library-version></restconf>",
YB_MODULE, yspec, &xt, NULL) < 0)
goto done;
@ -187,6 +188,24 @@ api_root_restconf_exact(clicon_handle h,
return retval;
}
/** A stub implementation of the operational state datastore. The full
* implementation is required by https://tools.ietf.org/html/rfc8527#section-3.1
*/
static int
api_operational_state(clicon_handle h,
void *req,
char *request_method,
int pretty,
restconf_media media_out)
{
clicon_debug(1, "%s request method:%s", __FUNCTION__, request_method);
/* We are not implementing this method at this time, 20201105 despite it
* being mandatory https://tools.ietf.org/html/rfc8527#section-3.1 */
return restconf_notimplemented(req);
}
/*!
* See https://tools.ietf.org/html/rfc7895
*/
@ -200,7 +219,6 @@ api_yang_library_version(clicon_handle h,
int retval = -1;
cxobj *xt = NULL;
cbuf *cb = NULL;
char *ietf_yang_library_revision = "2016-06-21"; /* XXX */
clicon_debug(1, "%s", __FUNCTION__);
if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0)
@ -209,7 +227,7 @@ api_yang_library_version(clicon_handle h,
goto done;
if (clixon_xml_parse_va(YB_NONE, NULL, &xt, NULL,
"<yang-library-version>%s</yang-library-version>",
ietf_yang_library_revision) < 0)
IETF_YANG_LIBRARY_REVISION) < 0)
goto done;
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
@ -250,6 +268,7 @@ api_yang_library_version(clicon_handle h,
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_in Input media
* @param[in] media_out Output media
* @param[in] ds 0 if "data" resource, 1 if rfc8527 "ds" resource
*/
static int
api_data(clicon_handle h,
@ -260,28 +279,58 @@ api_data(clicon_handle h,
cvec *qvec,
char *data,
int pretty,
restconf_media media_out)
restconf_media media_out,
ietf_ds_t ds)
{
int retval = -1;
int read_only = 0, dynamic = 0;
char *request_method;
clicon_debug(1, "%s", __FUNCTION__);
request_method = restconf_param_get(h, "REQUEST_METHOD");
clicon_debug(1, "%s method:%s", __FUNCTION__, request_method);
/* https://tools.ietf.org/html/rfc8527#section-3.2 */
/* We assume that dynamic datastores are read only at this time 20201105 */
if (IETF_DS_DYNAMIC == ds)
dynamic = 1;
if ((IETF_DS_INTENDED == ds) || (IETF_DS_RUNNING == ds)
|| (IETF_DS_DYNAMIC == ds) || (IETF_DS_OPERATIONAL == ds)) {
read_only = 1;
}
if (strcmp(request_method, "OPTIONS")==0)
retval = api_data_options(h, req);
else if (strcmp(request_method, "HEAD")==0)
retval = api_data_head(h, req, api_path, pcvec, pi, qvec, pretty, media_out);
else if (strcmp(request_method, "GET")==0)
retval = api_data_get(h, req, api_path, pcvec, pi, qvec, pretty, media_out);
else if (strcmp(request_method, "POST")==0)
retval = api_data_post(h, req, api_path, pi, qvec, data, pretty, media_out);
else if (strcmp(request_method, "PUT")==0)
retval = api_data_put(h, req, api_path, pcvec, pi, qvec, data, pretty, media_out);
else if (strcmp(request_method, "PATCH")==0)
retval = api_data_patch(h, req, api_path, pcvec, pi, qvec, data, pretty, media_out);
else if (strcmp(request_method, "DELETE")==0)
retval = api_data_delete(h, req, api_path, pi, pretty, media_out);
else if (strcmp(request_method, "HEAD")==0) {
if (dynamic) {
retval = restconf_method_notallowed(req, "GET,POST");
}
retval = api_data_head(h, req, api_path, pcvec, pi, qvec, pretty, media_out, ds);
}
else if (strcmp(request_method, "GET")==0) {
retval = api_data_get(h, req, api_path, pcvec, pi, qvec, pretty, media_out, ds);
}
else if (strcmp(request_method, "POST")==0) {
retval = api_data_post(h, req, api_path, pi, qvec, data, pretty, media_out, ds);
}
else if (strcmp(request_method, "PUT")==0) {
if (read_only) {
retval = restconf_method_notallowed(req, "GET,POST");
}
retval = api_data_put(h, req, api_path, pcvec, pi, qvec, data, pretty, media_out, ds);
}
else if (strcmp(request_method, "PATCH")==0) {
if (read_only) {
retval = restconf_method_notallowed(req, "GET,POST");
}
retval = api_data_patch(h, req, api_path, pcvec, pi, qvec, data, pretty, media_out, ds);
}
else if (strcmp(request_method, "DELETE")==0) {
if (read_only) {
retval = restconf_method_notallowed(req, "GET,POST");
}
retval = api_data_delete(h, req, api_path, pi, pretty, media_out, ds);
}
else
retval = restconf_notfound(h, req);
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
@ -441,7 +490,41 @@ api_root_restconf(clicon_handle h,
}
else if (strcmp(api_resource, "data") == 0){ /* restconf, skip /api/data */
if (api_data(h, req, path, pcvec, 2, qvec, indata,
pretty, media_out) < 0)
pretty, media_out, IETF_DS_NONE) < 0)
goto done;
}
else if (strcmp(api_resource, "ds") == 0) {
/* We should really be getting the supported datastore types from the
* application model, but at this time the datastore model of startup/
* running/cadidate is hardcoded into the clixon implementation. 20201104 */
ietf_ds_t ds = IETF_DS_NONE;
if (4 > pn) { /* Malformed request, no "ietf-datastores:<datastore>" component */
restconf_notfound(h, req);
goto done;
}
/* Assign ds; See https://tools.ietf.org/html/rfc8342#section-7 */
if (0 == strcmp(pvec[3], "ietf-datastores:running"))
ds = IETF_DS_RUNNING;
else if (0 == strcmp(pvec[3], "ietf-datastores:candidate"))
ds = IETF_DS_CANDIDATE;
else if (0 == strcmp(pvec[3], "ietf-datastores:startup"))
ds = IETF_DS_STARTUP;
else if (0 == strcmp(pvec[3], "ietf-datastores:operational")) {
/* See https://tools.ietf.org/html/rfc8527#section-3.1
* https://tools.ietf.org/html/rfc8342#section-5.3 */
if (0 > api_operational_state(h, req, request_method, pretty, media_out)) {
goto done;
}
goto ok;
}
else { /* Malformed request, unsupported datastore type */
restconf_notfound(h, req);
goto done;
}
/* ds is assigned at this point */
if (0 > api_data(h, req, path, pcvec, 3, qvec, indata, pretty, media_out, ds))
goto done;
}
else if (strcmp(api_resource, "operations") == 0){ /* rpc */

View file

@ -42,6 +42,7 @@
* Constants
*/
#define RESTCONF_API "restconf"
#define IETF_YANG_LIBRARY_REVISION "2019-01-04"
/* RESTCONF enables deployments to specify where the RESTCONF API is
located. The client discovers this by getting the "/.well-known/host-meta"