diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index 3c0d9569..0ce99064 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -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 */ diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index d2f38022..1fb6060f 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -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, ""); @@ -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, ""); diff --git a/apps/restconf/restconf_methods.h b/apps/restconf/restconf_methods.h index 4a665470..81d91345 100644 --- a/apps/restconf/restconf_methods.h +++ b/apps/restconf/restconf_methods.h @@ -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_ */ diff --git a/apps/restconf/restconf_methods_get.c b/apps/restconf/restconf_methods_get.c index 3fccdf74..2effa090 100644 --- a/apps/restconf/restconf_methods_get.c +++ b/apps/restconf/restconf_methods_get.c @@ -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); } diff --git a/apps/restconf/restconf_methods_get.h b/apps/restconf/restconf_methods_get.h index 56d758f5..34e4244e 100644 --- a/apps/restconf/restconf_methods_get.h +++ b/apps/restconf/restconf_methods_get.h @@ -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); diff --git a/apps/restconf/restconf_methods_post.c b/apps/restconf/restconf_methods_post.c index b51a9262..369c7c26 100644 --- a/apps/restconf/restconf_methods_post.c +++ b/apps/restconf/restconf_methods_post.c @@ -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, ""); diff --git a/apps/restconf/restconf_methods_post.h b/apps/restconf/restconf_methods_post.h index b8d8bd83..52fab3c8 100644 --- a/apps/restconf/restconf_methods_post.h +++ b/apps/restconf/restconf_methods_post.h @@ -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, diff --git a/apps/restconf/restconf_root.c b/apps/restconf/restconf_root.c index 8872e642..7a4630fc 100644 --- a/apps/restconf/restconf_root.c +++ b/apps/restconf/restconf_root.c @@ -153,7 +153,8 @@ api_root_restconf_exact(clicon_handle h, goto done; if (clixon_xml_parse_string("" - "2016-06-21", + "" IETF_YANG_LIBRARY_REVISION + "", 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, "%s", - 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:" 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 */ diff --git a/apps/restconf/restconf_root.h b/apps/restconf/restconf_root.h index 10010483..2a60c133 100644 --- a/apps/restconf/restconf_root.h +++ b/apps/restconf/restconf_root.h @@ -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"