Added restconf/operations get, see RFC8040 Sec 3.3.2:
This commit is contained in:
Olof hagsand 2018-03-04 21:24:38 +01:00
parent 6a22524d38
commit 990700b68d
16 changed files with 355 additions and 93 deletions

View file

@ -236,7 +236,7 @@ cli_dbxml(clicon_handle h,
if ((xtop = xml_new("config", NULL, NULL)) == NULL)
goto done;
xbot = xtop;
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0)
if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0)
goto done;
if ((xa = xml_new("operation", xbot, NULL)) == NULL)
goto done;

View file

@ -171,7 +171,7 @@ expand_dbvar(void *h,
/* This is primarily to get "y",
* xpath2xml would have worked!!
*/
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0)
if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0)
goto done;
/* Special case for leafref. Detect leafref via Yang-type,
* Get Yang path element, tentatively add the new syntax to the whole

View file

@ -131,11 +131,12 @@ api_data(clicon_handle h,
/*! Operations REST method, POST
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] dvec Stream input data
* @param[in] data Stream input data
* @param[in] username Authenticated user
*/
static int
api_operations(clicon_handle h,
@ -154,16 +155,42 @@ api_operations(clicon_handle h,
request_method = FCGX_GetParam("REQUEST_METHOD", r->envp);
clicon_debug(1, "%s method:%s", __FUNCTION__, request_method);
if (strcmp(request_method, "GET")==0)
retval = api_operation_get(h, r, path, pcvec, pi, qvec, data, username);
retval = api_operations_get(h, r, path, pcvec, pi, qvec, data, username);
else if (strcmp(request_method, "POST")==0)
retval = api_operation_post(h, r, path, pcvec, pi, qvec, data, username);
retval = api_operations_post(h, r, path, pcvec, pi, qvec, data, username);
else
retval = notfound(r);
return retval;
}
/*! Determine the root of the RESTCONF API
* @param[in] h Clicon handle
* @param[in] r Fastcgi request handle
* @note Hardcoded to "/restconf"
* Return see RFC8040 3.1 and RFC7320
* In line with the best practices defined by [RFC7320], RESTCONF
* enables deployments to specify where the RESTCONF API is located.
*/
static int
api_well_known(clicon_handle h,
FCGX_Request *r)
{
clicon_debug(1, "%s", __FUNCTION__);
FCGX_FPrintF(r->out, "Content-Type: application/xrd+xml\r\n");
FCGX_FPrintF(r->out, "\r\n");
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>\r\n");
FCGX_FPrintF(r->out, " <Link rel='restconf' href='/restconf'/>\r\n");
FCGX_FPrintF(r->out, "</XRD>\r\n");
return 0;
}
/*! Retrieve the Top-Level API Resource
* @param[in] h Clicon handle
* @param[in] r Fastcgi request handle
* @note Only returns null for operations and data,...
* See RFC8040 3.3
*/
static int
api_root(clicon_handle h,
@ -181,9 +208,11 @@ api_root(clicon_handle h,
media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp);
if (strcmp(media_accept, "application/yang-data+xml")==0)
use_xml++;
clicon_debug(1, "%s use-xml:%d media-accept:%s", __FUNCTION__, use_xml, media_accept);
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
FCGX_FPrintF(r->out, "\r\n");
if (xml_parse_string("<restconf><data></data><operations></operations><yang-library-version>2016-06-21</yang-library-version></restconf>", NULL, &xt) < 0)
goto done;
if ((cb = cbuf_new()) == NULL){
@ -298,6 +327,8 @@ api_restconf(clicon_handle h,
retval = notfound(r);
goto done;
}
test(r, 1);
if (pn == 2){
retval = api_root(h, r);
goto done;
@ -319,7 +350,6 @@ api_restconf(clicon_handle h,
if (str2cvec(data, '&', '=', &dvec) < 0)
goto done;
test(r, 1);
/* If present, check credentials. See "plugin_credentials" in plugin
* See RFC 8040 section 2.5
*/
@ -367,23 +397,6 @@ api_restconf(clicon_handle h,
return retval;
}
/*! Process a FastCGI request
* @param[in] r Fastcgi request handle
*/
static int
api_well_known(clicon_handle h,
FCGX_Request *r)
{
clicon_debug(1, "%s", __FUNCTION__);
FCGX_FPrintF(r->out, "Content-Type: application/xrd+xml\r\n");
FCGX_FPrintF(r->out, "\r\n");
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>\r\n");
FCGX_FPrintF(r->out, " <Link rel='restconf' href='/restconf'/>\r\n");
FCGX_FPrintF(r->out, "</XRD>\r\n");
return 0;
}
static int
restconf_terminate(clicon_handle h)
@ -547,7 +560,7 @@ main(int argc,
if (strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)) == 0)
api_restconf(h, r); /* This is the function */
else if (strncmp(path, RESTCONF_WELL_KNOWN, strlen(RESTCONF_WELL_KNOWN)) == 0) {
api_well_known(h, r); /* This is the function */
api_well_known(h, r); /* */
}
else{
clicon_debug(1, "top-level %s not found", path);

View file

@ -195,6 +195,7 @@ api_data_get_err(clicon_handle h,
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] username Authenticated user
* @param[in] head If 1 is HEAD, otherwise GET
* @code
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
@ -332,6 +333,7 @@ api_data_get2(clicon_handle h,
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] username Authenticated user
The HEAD method is sent by the client to retrieve just the header fields
that would be returned for the comparable GET method, without the
response message-body.
@ -355,6 +357,7 @@ api_data_head(clicon_handle h,
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] username Authenticated user
* @code
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
* @endcode
@ -390,6 +393,8 @@ api_data_get(clicon_handle h,
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
* @param[in] username Authenticated user
* @note restconf POST is mapped to edit-config create.
POST:
target resource type is datastore --> create a top-level resource
@ -461,7 +466,7 @@ api_data_post(clicon_handle h,
goto done;
}
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0)
if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0)
goto done;
/* Parse input data as json or xml into xml */
if (parse_xml){
@ -586,6 +591,7 @@ match_list_keys(yang_stmt *y,
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
* @param[in] username Authenticated user
* @note restconf PUT is mapped to edit-config replace.
* @example
curl -X PUT -d '{"enabled":"false"}' http://127.0.0.1/restconf/data/interfaces/interface=eth1
@ -651,7 +657,7 @@ api_data_put(clicon_handle h,
if (xml_value_set(xu, username) < 0)
goto done;
}
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0)
if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0)
goto done;
/* Parse input data as json or xml into xml */
if (parse_xml){
@ -749,6 +755,7 @@ api_data_put(clicon_handle h,
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
* @param[in] username Authenticated user
* Netconf: <edit-config> (nc:operation="merge")
*/
int
@ -769,6 +776,7 @@ api_data_patch(clicon_handle h,
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] username Authenticated user
* @param[in] pi Offset, where path starts
* Example:
* curl -X DELETE http://127.0.0.1/restconf/data/interfaces/interface=eth0
@ -812,7 +820,7 @@ api_data_delete(clicon_handle h,
if (xml_value_set(xu, username) < 0)
goto done;
}
if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0)
if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0)
goto done;
if ((xa = xml_new("operation", xbot, NULL)) == NULL)
goto done;
@ -851,10 +859,27 @@ api_data_delete(clicon_handle h,
return retval;
}
/*! NYI
/*! GET restconf/operations resource
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @param[in] path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
* @param[in] username Authenticated user
*
* @code
* curl -G http://localhost/restconf/operations
* @endcode
* RFC8040 Sec 3.3.2:
* This optional resource is a container that provides access to the
* data-model-specific RPC operations supported by the server. The
* server MAY omit this resource if no data-model-specific RPC
* operations are advertised.
*/
int
api_operation_get(clicon_handle h,
api_operations_get(clicon_handle h,
FCGX_Request *r,
char *path,
cvec *pcvec,
@ -863,23 +888,87 @@ api_operation_get(clicon_handle h,
char *data,
char *username)
{
notimplemented(r);
return 0;
int retval = -1;
int pretty;
char *media_accept;
int use_xml = 0; /* By default use JSON */
yang_spec *yspec;
yang_stmt *ym;
yang_stmt *yc;
yang_stmt *yprefix;
char *prefix;
cbuf *cbx = NULL;
cxobj *xt = NULL;
clicon_debug(1, "%s", __FUNCTION__);
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp);
if (strcmp(media_accept, "application/yang-data+xml")==0)
use_xml++;
yspec = clicon_dbspec_yang(h);
if ((cbx = cbuf_new()) == NULL)
goto done;
cprintf(cbx, "<operations>");
ym = NULL;
while ((ym = yn_each((yang_node*)yspec, ym)) != NULL) {
if ((yprefix = yang_find((yang_node*)ym, Y_PREFIX, NULL)) != NULL)
prefix = yprefix->ys_argument;
else
continue;
yc = NULL;
while ((yc = yn_each((yang_node*)ym, yc)) != NULL) {
if (yc->ys_keyword != Y_RPC)
continue;
cprintf(cbx, "<%s:%s />", prefix, yc->ys_argument);
}
}
cprintf(cbx, "</operations>");
clicon_debug(1, "%s xml:%s", __FUNCTION__, cbuf_get(cbx));
if (xml_parse_string(cbuf_get(cbx), yspec, &xt) < 0)
goto done;
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
cbuf_reset(cbx); /* reuse same cbuf */
if (use_xml){
if (clicon_xml2cbuf(cbx, xt, 0, pretty) < 0) /* Dont print top object? */
goto done;
}
else{
if (xml2json_cbuf(cbx, xt, pretty) < 0)
goto done;
}
clicon_debug(1, "%s ret:%s", __FUNCTION__, cbuf_get(cbx));
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
FCGX_FPrintF(r->out, "\r\n");
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
FCGX_FPrintF(r->out, "\r\n\r\n");
// ok:
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cbx)
cbuf_free(cbx);
if (xt)
xml_free(xt);
return retval;
}
/*! REST operation POST method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
* @note We map post to edit-config create.
* @param[in] username Authenticated user
* @note We map post to edit-config create.
POST {+restconf}/operations/<operation>
*/
int
api_operation_post(clicon_handle h,
api_operations_post(clicon_handle h,
FCGX_Request *r,
char *path,
cvec *pcvec,
@ -956,7 +1045,7 @@ api_operation_post(clicon_handle h,
}
/* XXX: something strange for rpc user */
if (api_path2xml(oppath, yspec, xtop, 1, &xbot, &y) < 0)
if (api_path2xml(oppath, yspec, xtop, YC_SCHEMANODE, &xbot, &y) < 0)
goto done;
#if 1
{

View file

@ -61,13 +61,13 @@ int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path,
int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi,
char *username);
int api_operation_get(clicon_handle h, FCGX_Request *r,
char *path,
cvec *pcvec, int pi, cvec *qvec, char *data, char *username);
int api_operation_post(clicon_handle h, FCGX_Request *r,
int api_operations_get(clicon_handle h, FCGX_Request *r,
char *path,
cvec *pcvec, int pi, cvec *qvec, char *data,
char *username);
cvec *pcvec, int pi, cvec *qvec, char *data, char *username);
int api_operations_post(clicon_handle h, FCGX_Request *r,
char *path,
cvec *pcvec, int pi, cvec *qvec, char *data,
char *username);
#endif /* _RESTCONF_METHODS_H_ */