* Revised RESTCONF->NETCONF insert/point translation

* Netconf edit-config "operation" attribute namespace check is enforced
This commit is contained in:
Olof hagsand 2019-08-02 17:03:17 +02:00
parent c97346921b
commit b73348e0cd
6 changed files with 79 additions and 35 deletions

View file

@ -5,6 +5,7 @@
### Major New features
* Restconf RFC 8040 increased feature compliance
* RESTCONF "insert" and "point" query parameters supported
* Applies to ordered-by-user leaf and leaf-lists
* RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns:
* `201 Created` for created resources
* `204 No Content` for replaced resources.
@ -22,6 +23,9 @@
* The main example explains how to implement a Yang extension in a backend plugin.
### API changes on existing features (you may need to change your code)
* Netconf edit-config "operation" attribute namespace check is enforced
* This is enforced: `<a xmlns="uri:example" nc:operation="merge" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
* This was previously allowed: `<a xmlns="uri:example" operation="merge">
* RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns:
* `201 Created` for created resources
* `204 No Content` for replaced resources.

View file

@ -77,8 +77,7 @@ Clixon interaction is best done posting issues, pull requests, or joining the
Clixon provides a core system and can be used as-is using available
Yang specifications. However, an application very quickly needs to
specialize functions. Clixon is extended by writing
plugins for cli and backend. Extensions for netconf and restconf
are also available.
plugins for cli, backend, netconf and restconf.
Plugins are written in C and easiest is to look at
[example](example/README.md) or consulting the [FAQ](doc/FAQ.md).
@ -100,12 +99,12 @@ However, the following YANG syntax modules are not implemented (reference to RFC
- action (7.15)
- augment in a uses sub-clause (7.17) (module-level augment is implemented)
- require-instance
- instance-identifier type
- instance-identifier type (9.13)
- status (7.21.2)
- YIN (13)
- Yang extended Xpath functions: re-match(), deref)(), derived-from(), derived-from-or-self(), enum-value(), bit-is-set() (10.2-10.6)
- Default values on leaf-lists are not supported (7.7.2)
- instance-identifier type
- Lists without keys (non-config lists may lack keys)
### Yang patterns
Yang type patterns use regexps defined in [W3C XML XSD](http://www.w3.org/TR/2004/REC-xmlschema-2-20041028). XSD regexp:s are

View file

@ -558,6 +558,22 @@ restconf_terminate(clicon_handle h)
* @param[in] qvec Query parameters (eg where insert/point should be)
* @retval 0 OK
* @retval -1 Error
* A difficulty is that RESTCONF 'point' is a complete identity-identifier encoded
* as an uri-path
* wheras NETCONF key and value are:
* The value of the "key" attribute is the key predicates of the full instance identifier
* the "value" attribute MUST also be used to specify an existing entry in the
* leaf-list.
* This means that api-path is first translated to xpath, and then strip everything
* except the key values (or leaf-list values).
* Example:
* RESTCONF URI: point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D1
* Leaf example:
* instance-id: /ex:system/ex:service[ex:name='foo'][ex:enabled='']
* NETCONF: yang:key="[ex:name='foo'][ex:enabled='']
* Leaf-list example:
* instance-id: /ex:system/ex:service[.='foo']
* NETCONF: yang:value="foo"
*/
int
restconf_insert_attributes(cxobj *xdata,
@ -570,6 +586,10 @@ restconf_insert_attributes(cxobj *xdata,
yang_stmt *y;
char *attrname;
int ret;
char *xpath = NULL;
char *namespace = NULL;
cbuf *cb = NULL;
char *p;
y = xml_spec(xdata);
if ((instr = cvec_find_str(qvec, "insert")) != NULL){
@ -591,9 +611,6 @@ restconf_insert_attributes(cxobj *xdata,
goto done;
}
if ((pstr = cvec_find_str(qvec, "point")) != NULL){
char *xpath = NULL;
char *namespace = NULL;
cbuf *cb = NULL;
if (y == NULL){
clicon_err(OE_YANG, 0, "Cannot yang resolve %s", xml_name(xdata));
goto done;
@ -614,16 +631,36 @@ restconf_insert_attributes(cxobj *xdata,
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "/%s", xpath); /* XXX: also prefix/namespace? */
if (yang_keyword_get(y) == Y_LIST){
/* translate /../x[] --> []*/
if ((p = rindex(xpath,'/')) == NULL)
p = xpath;
p = index(p, '[');
cprintf(cb, "%s", p);
}
else{ /* LEAF_LIST */
/* translate /../x[.='x'] --> x */
if ((p = rindex(xpath,'\'')) == NULL){
clicon_err(OE_YANG, 0, "Translated api->xpath %s->%s not on leaf-list canonical form: ../[.='x']", pstr, xpath);
goto done;
}
*p = '\0';
if ((p = rindex(xpath,'\'')) == NULL){
clicon_err(OE_YANG, 0, "Translated api->xpath %s->%s not on leaf-list canonical form: ../[.='x']", pstr, xpath);
goto done;
}
p++;
cprintf(cb, "%s", p);
}
if (xml_value_set(xa, cbuf_get(cb)) < 0)
goto done;
if (xpath)
free(xpath);
if (cb)
cbuf_free(cb);
}
retval = 0;
done:
if (xpath)
free(xpath);
if (cb)
cbuf_free(cb);
return retval;
}

View file

@ -487,6 +487,15 @@ xml_search(cxobj *xp,
* @param[in] key_val Key if LIST and ins is before/after, val if LEAF_LIST
* @retval i Order where xn should be inserted into xp:s children
* @retval -1 Error
* LIST: RFC 7950 7.8.6:
* The value of the "key" attribute is the key predicates of the
* full instance identifier (see Section 9.13) for the list entry.
* This means the value can be [x='a'] but the full instance-id should be prepended,
* such as /ex:system/ex:services[x='a']
*
* LEAF-LIST: RFC7950 7.7.9
* yang:insert="after"
* yang:value="3des-cbc">blowfish-cbc</cipher>)
*/
static int
@ -501,7 +510,6 @@ xml_insert_userorder(cxobj *xp,
int i;
cxobj *xc;
yang_stmt *yc;
char *kludge = ""; /* Cant get instance-id generic of [.. and /.. case */
switch (ins){
case INS_FIRST:
@ -534,7 +542,7 @@ xml_insert_userorder(cxobj *xp,
else{
switch (yang_keyword_get(yn)){
case Y_LEAF_LIST:
if ((xc = xpath_first(xp, "%s", key_val)) == NULL)
if ((xc = xpath_first(xp, "%s[.='%s']", xml_name(xn),key_val)) == NULL)
clicon_err(OE_YANG, 0, "bad-attribute: value, missing-instance: %s", key_val);
else {
if ((i = xml_child_order(xp, xc)) < 0)
@ -544,10 +552,8 @@ xml_insert_userorder(cxobj *xp,
}
break;
case Y_LIST:
if (strlen(key_val) && key_val[0] == '[')
kludge = xml_name(xn);
if ((xc = xpath_first(xp, "%s%s", kludge, key_val)) == NULL)
clicon_err(OE_YANG, 0, "bad-attribute: key, missing-instance: %s%s", xml_name(xn), key_val);
if ((xc = xpath_first(xp, "%s%s", xml_name(xn), key_val)) == NULL)
clicon_err(OE_YANG, 0, "bad-attribute: key, missing-instance: %s", key_val);
else {
if ((i = xml_child_order(xp, xc)) < 0)
clicon_err(OE_YANG, 0, "internal error xpath found but not in child list");

View file

@ -324,17 +324,17 @@ expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><candidate/></s
# before and after and value attribute
new "add one leaf-list entry 71 before b"
XML="<rpc><edit-config><target><candidate/></target><config><y0 xmlns=\"urn:example:order\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:insert=\"before\" yang:value=\"y0[.='b']\">71</y0></config></edit-config></rpc>]]>]]>"
XML="<rpc><edit-config><target><candidate/></target><config><y0 xmlns=\"urn:example:order\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:insert=\"before\" yang:value=\"b\">71</y0></config></edit-config></rpc>]]>]]>"
expecteof "$clixon_netconf -qf $cfg" 0 "$XML" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "add one entry 42 after b"
XML="<rpc><edit-config><target><candidate/></target><config><y0 xmlns=\"urn:example:order\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:insert=\"after\" yang:value=\"y0[.='b']\">42</y0></config></edit-config></rpc>]]>]]>"
XML="<rpc><edit-config><target><candidate/></target><config><y0 xmlns=\"urn:example:order\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:insert=\"after\" yang:value=\"b\">42</y0></config></edit-config></rpc>]]>]]>"
expecteof "$clixon_netconf -qf $cfg" 0 "$XML" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# XXX actually not right error message, should be as RFC7950 Sec 15.7
new "add one entry 99 after Q (not found, error)"
XML="<rpc><edit-config><target><candidate/></target><config><y0 xmlns=\"urn:example:order\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:insert=\"after\" yang:value=\"y0[.='Q']\">99</y0></config></edit-config></rpc>]]>]]>"
RES="^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>bad-attribute: value, missing-instance: y0\[.='Q'\]</error-message></rpc-error></rpc-reply>]]>]]>$"
XML="<rpc><edit-config><target><candidate/></target><config><y0 xmlns=\"urn:example:order\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:insert=\"after\" yang:value=\"Q\">99</y0></config></edit-config></rpc>]]>]]>"
RES="^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>bad-attribute: value, missing-instance: Q</error-message></rpc-error></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "$XML" "$RES"
new "check ordered-by-user: e,a,71,b,42,c,d"
@ -384,7 +384,7 @@ expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></
# XXX actually not right error message, should be as RFC7950 Sec 15.7
new "add one entry key 99 after Q (not found, error)"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config><y2 xmlns=\"urn:example:order\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:insert=\"after\" yang:key=\"[k='Q']\"><k>99</k><a>bar</a></y2></config></edit-config></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>bad-attribute: key, missing-instance: y2\[k='Q'\]</error-message></rpc-error></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config><y2 xmlns=\"urn:example:order\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:insert=\"after\" yang:key=\"Q\"><k>99</k><a>bar</a></y2></config></edit-config></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>operation-failed</error-tag><error-severity>error</error-severity><error-message>bad-attribute: key, missing-instance: Q</error-message></rpc-error></rpc-reply>]]>]]>$"
new "check ordered-by-user: e,a,71,b,42,c,d"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>' '^<rpc-reply><data><y2 xmlns="urn:example:order"><k>e</k><a>bar</a></y2><y2 xmlns="urn:example:order"><k>a</k><a>foo</a></y2><y2 xmlns="urn:example:order"><k>71</k><a>fie</a></y2><y2 xmlns="urn:example:order"><k>b</k><a>bar</a></y2><y2 xmlns="urn:example:order"><k>42</k><a>fum</a></y2><y2 xmlns="urn:example:order"><k>c</k><a>foo</a></y2><y2 xmlns="urn:example:order"><k>d</k><a>fie</a></y2></data></rpc-reply>]]>]]>$'

View file

@ -360,11 +360,11 @@ expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 ""
new 'B.3.4. "insert" Parameter'
JSON="{\"example-jukebox:song\":[{\"index\":1,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Rope']\"}]}"
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created"
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=1'
new 'B.3.4. "insert" Parameter first (RFC example says after)'
JSON="{\"example-jukebox:song\":[{\"index\":0,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Bridge Burning']\"}]}"
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created"
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=first -d "$JSON")" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=0'
new 'B.3.4. "insert" Parameter check order'
RES="<playlist xmlns=\"http://example.com/ns/example-jukebox\"><name>Foo-One</name><song><index>0</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>1</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]</id></song></playlist>"
@ -372,14 +372,12 @@ expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:j
new 'B.3.5. "point" Parameter (before for more interesting order: 0,2,1)'
JSON="{\"example-jukebox:song\":[{\"index\":2,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Bridge Burning']\"}]}"
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' -d "$JSON" http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=before\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D1 )" 0 "HTTP/1.1 201 Created"
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' -d "$JSON" http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One?insert=before\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D1 )" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=2'
new 'B.3.5. "point" check order (0,2,1)'
RES="<playlist xmlns=\"http://example.com/ns/example-jukebox\"><name>Foo-One</name><song><index>0</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>2</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Bridge Burning'\]</id></song><song><index>1</index><id>/example-jukebox:jukebox/library/artist\[name='Foo Fighters'\]/album\[name='Wasting Light'\]/song\[name='Rope'\]</id></song></playlist>"
expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' "$RES"
#XXX 'Location: https://example.com/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=2'
new 'B.3.5. "point" Parameter 3 after 2 (using PUT)'
JSON="{\"example-jukebox:song\":[{\"index\":3,\"id\":\"/example-jukebox:jukebox/library/artist[name='Foo Fighters']/album[name='Wasting Light']/song[name='Something else']\"}]}"
expectpart "$(curl -s -i -X PUT -H 'Content-Type: application/yang-data+json' -d "$JSON" http://localhost/restconf/data/example-jukebox:jukebox/playlist=Foo-One/song=3?insert=after\&point=%2Fexample-jukebox%3Ajukebox%2Fplaylist%3DFoo-One%2Fsong%3D2 )" 0 "HTTP/1.1 201 Created"
@ -392,23 +390,23 @@ new "restconf DELETE whole datastore"
expectfn 'curl -s -X DELETE http://localhost/restconf/data' 0 ""
new 'B.3.4. "insert/point" leaf-list 3 (not in RFC)'
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"3"}')" 0 "HTTP/1.1 201 Created"
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"3"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=3'
new 'B.3.4. "insert/point" leaf-list 2 first'
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=first -d '{"example-jukebox:extra":"2"}')" 0 "HTTP/1.1 201 Created"
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=first -d '{"example-jukebox:extra":"2"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=2'
new 'B.3.4. "insert/point" leaf-list 1 last'
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created"
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=1'
#new 'B.3.4. "insert/point" move leaf-list 1 last'
#- restconf cannot move a leaf-list(list?) item
#expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created"
#expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' http://localhost/restconf/data?insert=last -d '{"example-jukebox:extra":"1"}')" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=1'
new 'B.3.5. "insert/point" leaf-list check order (2,3,1)'
expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<extra xmlns="http://example.com/ns/example-jukebox">2</extra><extra xmlns="http://example.com/ns/example-jukebox">3</extra><extra xmlns="http://example.com/ns/example-jukebox">1</extra>'
new 'B.3.5. "point" Parameter leaf-list 4 before 3'
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' -d '{"example-jukebox:extra":"4"}' http://localhost/restconf/data?insert=before\&point=%2Fexample-jukebox%3Aextra%3D3 )" 0 "HTTP/1.1 201 Created"
expectpart "$(curl -s -i -X POST -H 'Content-Type: application/yang-data+json' -d '{"example-jukebox:extra":"4"}' http://localhost/restconf/data?insert=before\&point=%2Fexample-jukebox%3Aextra%3D3 )" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=4'
new 'B.3.5. "insert/point" leaf-list check order (2,4,3,1)'
expectpart "$(curl -s -i -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '<extra xmlns="http://example.com/ns/example-jukebox">2</extra><extra xmlns="http://example.com/ns/example-jukebox">4</extra><extra xmlns="http://example.com/ns/example-jukebox">3</extra><extra xmlns="http://example.com/ns/example-jukebox">1</extra>'