Welcome to ntc-rosetta-conf’s documentation!¶
ntc-rosetta-conf
is a RESTCONF interface for ntc-rosetta. This RESTCONF interface allows you to manipulate a candidate and running databases using supported models by ntc-rosetta
and it also exposes a few RPC endpoints to translate, parse and merge configurations.
Contents¶
Intro¶
ntc-rosetta-conf
is a RESTCONF interface for ntc-rosetta. This RESTCONF interface allows you to manipulate a candidate and running databases using supported models by ntc-rosetta
and it also exposes a few RPC endpoints to translate, parse and merge configurations.
Installing¶
This python package is available through pip so you can install with the following command:
pip install ntc-rosetta-conf
Starting the restconf interface¶
$ ntc-rosetta-conf serve \
--datamodel openconfig \
--pid-file /tmp/ntc-rosetta-conf-demo.pid \
--log-level debug \
--data-file data.json \
--port 8443 \
--ssl-crt pki_auto_generated_dir/server_ca/certs/rtr00.lab.local.crt \
--ssl-key pki_auto_generated_dir/server_ca/keys/rtr00.lab.local.key \
--ca-crt pki_auto_generated_dir/client_ca/certs/client_ca.crt
Consuming the restconf interface¶
$ curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
https://localhost:8443/restconf/data/openconfig-interfaces:interfaces
{
"openconfig-interfaces:interfaces": {
"interface": [
{
"name": "eth0",
"config": {
"name": "eth0",
"description": "an interface description",
"type": "iana-if-type:ethernetCsmacd"
}
},
{
"name": "eth1",
"config": {
"name": "eth1",
"description": "another interface",
"type": "iana-if-type:ethernetCsmacd"
}
}
]
}
}
$ curl -s --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @docs/tutorials/4_translate/translate_running.json \
$BASE_URL/restconf/operations/ntc-rosetta-conf:translate | jq -r ".native"
interface eth0
description an interface description
exit
!
interface eth1
description another interface
exit
!
CLI¶
To start ntc-rosetta-cpnf
you can use its command line:
$ ntc-rosetta-conf serve --help
Usage: ntc-rosetta-conf serve [OPTIONS]
Options:
--datamodel [openconfig|ntc] Datamodel to use
--pid-file TEXT PID file
--log-level [debug|info|warning|error]
Logging level
--data-file TEXT Path to json file to load data from and save
on commit
--listen-on-localhost-only Listen on localhost only
--port INTEGER Port to listen to
--ssl-crt TEXT SSL Certificate for the webserver
--ssl-key TEXT Private key for the webserver
--ca-crt TEXT CA certificate used to sign client
certificates
--help Show this message and exit.
Tutorials¶
Jetconf/Restconf basics¶
In this demo we are going to see how jetconf works from an end user perspective and some of its capabilities.
Starting things up¶
Run:
make start-dev-containers
Now, let’s export some environment variables we need:
[1]:
export USER_CERT=../../tests/certs/test_user_curl.pem
export BASE_URL=https://ntc-rosetta-conf:8443
Basics¶
Jetconf adheres to RFC8040, with the exception of having support for candidate/running configuration database (which we will explore later).
Methods supported¶
Path | Child type | Method | Use |
---|---|---|---|
container | list | POST | Creates a list element (doesn’t fail if exists, but will fail on commit) |
container | leaf | POST | Creates a leaf (fails if exists) |
container | container | POST | Creates container (fails if exists) |
container | N/A | PUT | Replaces the container object targetted in the path |
container | N/A | DELETE | Deletes container targetted in the path |
leaf | N/A | PUT | Replaces existing leaf |
leaf | N/A | DELETE | Deletes existing leaf |
list | N/A | PUT | Replaces existing element |
list | N/A | DELETE | Deletes existing element |
It’s important to understand the difference between path
and child
in this context, which only makes sense when the path points to a container and the method is a POST. The path is basically the URL you are querying, which might end in a container, list element or leaf. For instance:
- container -
/interfaces
or/interfaces/interface=eth0
- list -
/interfaces/interfaces
- leaf -
/interfaces/interfaces=eth0/name
The child type is basically the object type you are POSTing. When working with containers the object can either be a child object, which can be of either type, or iselt. We will see this more clearly as we progress with examples for each method.
Adding operations and hooks¶
Finally, users can define their own operations. We will see examples of that in a later notebook when we explore napalm’s integration. Hooks can also be defined; hooks are action that can be attached to actions like “creating an interface”, “removing a vlan”, “updating a particular object or field”, etc…
Examples¶
Now, let’s explore the methods supported to deal with objects by example.
Here we are targing a container (openconfig-interfaces:interfaces
) but the object inside will contain a list element (openconfig-interfaces:interface: {...}
). You can use this to create new list elements:
[2]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces
{
"openconfig-interfaces:interfaces": {
"interface": []
}
}
[3]:
cat 1_jetconf_basics/add_interface_eth0.json
{
"openconfig-interfaces:interface": {
"name": "eth0",
"config": {
"name": "eth0",
"description": "a test interface",
"type": "iana-if-type:ethernetCsmacd"
}
}
}
[4]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @1_jetconf_basics/add_interface_eth0.json \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces
[5]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces
{
"openconfig-interfaces:interfaces": {
"interface": [
{
"name": "eth0",
"config": {
"name": "eth0",
"description": "a test interface",
"type": "iana-if-type:ethernetCsmacd"
}
}
]
}
}
Now we are targetting a different container but the object inside is a leaf. You can use this to create new leaves in a container. For instance, let’s add the mtu field to the configuration, which is missing:
[6]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface=eth0/config
{
"openconfig-interfaces:config": {
"name": "eth0",
"description": "a test interface",
"type": "iana-if-type:ethernetCsmacd"
}
}
[7]:
cat 1_jetconf_basics/add_mtu.json
{
"openconfig-interfaces:mtu": 9000
}
[8]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @1_jetconf_basics/add_mtu.json \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface=eth0/config
[9]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface=eth0/config
{
"openconfig-interfaces:config": {
"name": "eth0",
"description": "a test interface",
"type": "iana-if-type:ethernetCsmacd",
"mtu": 9000
}
}
You can POST a container object in a container object to create it. For instance, let’s create the hold-time
container under the interface itself, which is missing:
[10]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface=eth0/hold-time
{
"ietf-restconf:errors": {
"error": [
{
"error-type": "protocol",
"error-tag": "invalid-value",
"error-path": "/openconfig-interfaces:interfaces/interface/0",
"error-message": "NonexistentInstance: member 'hold-time'"
}
]
}
}
[11]:
cat 1_jetconf_basics/add_hold_time.json
{
"openconfig-interfaces:hold-time": {}
}
[12]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @1_jetconf_basics/add_hold_time.json \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface=eth0
[13]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface=eth0/hold-time
{
"openconfig-interfaces:hold-time": {}
}
Note: In this example we added an empty container, but you can add an already populated one
When doing a PUT in a container, the object in the paylod is itself. You can use this to do replace the container:
[14]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface=eth0/config
{
"openconfig-interfaces:config": {
"name": "eth0",
"description": "a test interface",
"type": "iana-if-type:ethernetCsmacd",
"mtu": 9000
}
}
[15]:
cat 1_jetconf_basics/change_config.json
{
"openconfig-interfaces:config": {
"name": "eth0",
"description": "a new interface description",
"type": "iana-if-type:ethernetCsmacd"
}
}
[16]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X PUT \
-d @1_jetconf_basics/change_config.json \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface=eth0/config
[17]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface=eth0/config
{
"openconfig-interfaces:config": {
"name": "eth0",
"description": "a new interface description",
"type": "iana-if-type:ethernetCsmacd"
}
}
Use it to delete a container:
[18]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface=eth0
{
"openconfig-interfaces:interface": [
{
"name": "eth0",
"hold-time": {},
"config": {
"name": "eth0",
"description": "a new interface description",
"type": "iana-if-type:ethernetCsmacd"
}
}
]
}
[19]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X DELETE \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface=eth0/hold-time
[20]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface=eth0
{
"openconfig-interfaces:interface": [
{
"name": "eth0",
"config": {
"name": "eth0",
"description": "a new interface description",
"type": "iana-if-type:ethernetCsmacd"
}
}
]
}
You can use it to change a configuration element:
[21]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface=eth0/config/description
{
"openconfig-interfaces:description": "a new interface description"
}
[22]:
cat 1_jetconf_basics/change_description.json
{
"openconfig-interfaces:description": "yet another changed description"
}
[23]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X PUT \
-d @1_jetconf_basics/change_description.json \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface=eth0/config/description
[24]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface=eth0/config/description
{
"openconfig-interfaces:description": "yet another changed description"
}
This is useful to remove configuration elements:
[25]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface=eth0/config
{
"openconfig-interfaces:config": {
"name": "eth0",
"type": "iana-if-type:ethernetCsmacd",
"description": "yet another changed description"
}
}
[26]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X DELETE \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface=eth0/config/description
[27]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface=eth0/config
{
"openconfig-interfaces:config": {
"name": "eth0",
"type": "iana-if-type:ethernetCsmacd"
}
}
You can use this to replace the entire list:
[28]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface
{
"openconfig-interfaces:interface": [
{
"name": "eth0",
"config": {
"name": "eth0",
"type": "iana-if-type:ethernetCsmacd"
}
}
]
}
[29]:
cat 1_jetconf_basics/replace_interfaces.json
{
"openconfig-interfaces:interface": [
{
"name": "eth1",
"config": {
"name": "eth1",
"type": "iana-if-type:ethernetCsmacd"
}
},
{
"name": "eth2",
"config": {
"name": "eth2",
"type": "iana-if-type:ethernetCsmacd"
}
}
]
}
[30]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X PUT \
-d @1_jetconf_basics/replace_interfaces.json \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface
[31]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface
{
"openconfig-interfaces:interface": [
{
"name": "eth1",
"config": {
"name": "eth1",
"type": "iana-if-type:ethernetCsmacd"
}
},
{
"name": "eth2",
"config": {
"name": "eth2",
"type": "iana-if-type:ethernetCsmacd"
}
}
]
}
You can use this to delete the entire list (although you will need to reinitialize it with a POST, much better to replace the list with an empty one instead):
[32]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces
{
"openconfig-interfaces:interfaces": {
"interface": [
{
"name": "eth1",
"config": {
"name": "eth1",
"type": "iana-if-type:ethernetCsmacd"
}
},
{
"name": "eth2",
"config": {
"name": "eth2",
"type": "iana-if-type:ethernetCsmacd"
}
}
]
}
}
[33]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X DELETE \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface
[34]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces
{
"openconfig-interfaces:interfaces": {}
}
[35]:
# ignore me, this discards the changes so the notebook can be rerun
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
$BASE_URL/restconf/operations/jetconf:conf-reset
{
"status": "OK"
}
Candidate/Running config database¶
In this demo we are going to see how jetconf let’s you manage the
Starting things up¶
Run:
make start-dev-containers
Now, let’s export some environment variables we need:
[1]:
export USER_CERT=../../tests/certs/test_user_curl.pem
export BASE_URL=https://ntc-rosetta-conf:8443
Basics¶
Even though it’s not part of the specification, jetconf implements a netconf-like workflow where changes are made against a candidate database and commited in a single transaction into the running database. There are a few notes to be made here.
Even though changes are always made against the candidate database, the GET
method can be used against the running configuration. To support this the URL target will be slightly different:
- Use
$BASE_URL/restconf/data/...
to target the candidate database. - Use
$BASE_URL/restconf_running/data/...
to target the running database.
To operate the candidata database, jetconf supports the following operational endpoints:
Path | Method | Use |
---|---|---|
/restconf/operations/jetconf:conf-reset | POST | Discard candidate configuration |
/restconf/operations/jetconf:conf-commit | POST | commit candidate configuration |
/restconf/operations/jetconf:get-schema-digest | POST | retrieve supported schema |
/restconf/operations/jetconf:conf-status | POST | retrieve status of the configuration |
Let’s see a few examples, let’s start by adding an interface:
[2]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces
{
"openconfig-interfaces:interfaces": {
"interface": []
}
}
[3]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @2_candidate_config/add_interface_eth0.json \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces
Now, let’s verify the candidate database:
[4]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces
{
"openconfig-interfaces:interfaces": {
"interface": [
{
"name": "eth0",
"config": {
"name": "eth0",
"description": "a test interface",
"type": "iana-if-type:ethernetCsmacd"
}
}
]
}
}
The changes are there, let’s look at the running database:
[5]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf_running/data/openconfig-interfaces:interfaces
{
"openconfig-interfaces:interfaces": {
"interface": []
}
}
The changes are not yet present there, let’s do a couple of other changes; let’s add another interface:
[6]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @2_candidate_config/add_interface_eth1.json \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces
Now, let’s verify the different configuration databases:
[7]:
# candidate:devices
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces
{
"openconfig-interfaces:interfaces": {
"interface": [
{
"name": "eth0",
"config": {
"name": "eth0",
"description": "a test interface",
"type": "iana-if-type:ethernetCsmacd"
}
},
{
"name": "eth1",
"config": {
"name": "eth1",
"description": "another test interface",
"type": "iana-if-type:ethernetCsmacd"
}
}
]
}
}
[8]:
# running:devices
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf_running/data/openconfig-interfaces:interfaces
{
"openconfig-interfaces:interfaces": {
"interface": []
}
}
Pretty much what we expected. Now we can do three things:
/restconf/operations/jetconf:conf-reset
- Discard the changes/restconf/operations/jetconf:conf-commit
- Commit the changesrestconf/operations/jetconf:conf-status
- Verify the status of the configuration
Let’s start by verifying the status (should report that there is a transaction opened), commit the changes and then verify the status again (should report the transaction is not closed):
[9]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
$BASE_URL/restconf/operations/jetconf:conf-status
{
"status": "OK",
"transaction-opened": true
}
[10]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
$BASE_URL/restconf/operations/jetconf:conf-commit
{
"status": "OK",
"conf-changed": true
}
[11]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
$BASE_URL/restconf/operations/jetconf:conf-status
{
"status": "OK",
"transaction-opened": false
}
Now, let’s verify the running config:
[12]:
# running:interfaces
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf_running/data/openconfig-interfaces:interfaces
{
"openconfig-interfaces:interfaces": {
"interface": [
{
"name": "eth0",
"config": {
"name": "eth0",
"description": "a test interface",
"type": "iana-if-type:ethernetCsmacd"
}
},
{
"name": "eth1",
"config": {
"name": "eth1",
"description": "another test interface",
"type": "iana-if-type:ethernetCsmacd"
}
}
]
}
}
[13]:
# ignore me, this deletes the data so the notebook can be rerun
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X PUT \
-d @../../tests/data/interfaces_empty.json \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
$BASE_URL/restconf/operations/jetconf:conf-commit
{
"status": "OK",
"conf-changed": true
}
Errors¶
In this demo we are going to see how jetconf deals with errors in the data.
Starting things up¶
Run:
make build_container
docker run \
-p 8443:8443 \
-e ONLINE_MODE=0 \
rosetta -c /rosetta/tests/config.yaml
Now, let’s export some environment variables we need:
[1]:
export USER_CERT=../../tests/certs/test_user_curl.pem
export BASE_URL=https://ntc-rosetta-conf:8443
Types of errors¶
There are three type of errors you can encounter:
- Simple schema validation errors - For instance, a leaf being assigned a wrong type. This are performed on each operation.
- Complex schema validation errors - Wrong identity value or value. This are performed on commit only.
- Semantic errors - A duplicated key in a list, a missing leaf-ref, etc. This are performed on commit only.
Simple schema validation errors¶
We are going to start trying to create a device with a number as name:
[2]:
cat 3_errors/add_interface_eth0_bad_description.json
{
"openconfig-interfaces:interface": {
"name": "eth0",
"config": {
"name": "eth0",
"description": 0,
"type": "iana-if-type:ethernetCsmacd"
}
}
}
[3]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @3_errors/add_interface_eth0_bad_description.json \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces
{
"ietf-restconf:errors": {
"error": [
{
"error-type": "protocol",
"error-tag": "invalid-value",
"error-path": "/openconfig-interfaces:interfaces/interface/1/config/description",
"error-message": "RawTypeError: expected string value"
}
]
}
}
[4]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
$BASE_URL/restconf/operations/jetconf:conf-reset
{
"status": "OK"
}
Complex schema validation errors¶
Now, let’s try creating an interface with a very large MTU:
[5]:
cat 3_errors/add_interface_eth0_large_mtu.json
{
"openconfig-interfaces:interface": {
"name": "eth0",
"config": {
"name": "eth0",
"type": "iana-if-type:ethernetCsmacd",
"mtu": 5465464564564645
}
}
}
[6]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @3_errors/add_interface_eth0_large_mtu.json \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces
[7]:
# previous command succeeded, however, the commit will fail
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
$BASE_URL/restconf/operations/jetconf:conf-commit
{
"ietf-restconf:errors": {
"error": [
{
"error-type": "protocol",
"error-tag": "operation-failed",
"error-app-tag": "invalid-type",
"error-path": "/openconfig-interfaces:interfaces/interface/0/config/mtu",
"error-message": "YangTypeError: expected uint16"
}
]
}
}
[8]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
$BASE_URL/restconf/operations/jetconf:conf-reset
{
"status": "OK"
}
Semantic errors¶
Now we are going to try to create a two devices with the same name. On commit it will complain the key is not unique (it doesn’t matter if one of the element was previously commited or not):
[9]:
cat 3_errors/add_interface_eth0.json
{
"openconfig-interfaces:interface": {
"name": "eth0",
"config": {
"name": "eth0",
"type": "iana-if-type:ethernetCsmacd"
}
}
}
[10]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @3_errors/add_interface_eth0.json \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces
[11]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @3_errors/add_interface_eth0.json \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces
[12]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces
{
"openconfig-interfaces:interfaces": {
"interface": [
{
"name": "eth0",
"config": {
"name": "eth0",
"type": "iana-if-type:ethernetCsmacd"
}
},
{
"name": "eth0",
"config": {
"name": "eth0",
"type": "iana-if-type:ethernetCsmacd"
}
}
]
}
}
[13]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
$BASE_URL/restconf/operations/jetconf:conf-commit
{
"ietf-restconf:errors": {
"error": [
{
"error-type": "protocol",
"error-tag": "invalid-value",
"error-app-tag": "non-unique-key",
"error-path": "/openconfig-interfaces:interfaces/interface",
"error-message": "SemanticError: 'eth0'"
}
]
}
}
[14]:
# ignore me, this discards the changes so the notebook can be rerun
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
$BASE_URL/restconf/operations/jetconf:conf-reset
{
"status": "OK"
}
Using rosetta-conf to generate native configuration¶
Starting things up¶
Run:
make start-dev-containers
Now, let’s export some environment variables we need:
[1]:
export USER_CERT=../../tests/certs/test_user_curl.pem
export BASE_URL=https://ntc-rosetta-conf:8443
Configuration¶
First we need to configure the platform of the device, this configuration is part of the model.
[2]:
cat 4_translate/configuration.json
{
"ntc-rosetta-conf:device": {
"config": {
"platform": "ios"
}
}
}
[3]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @4_translate/configuration.json \
$BASE_URL/restconf/data/
[4]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
$BASE_URL/restconf/operations/jetconf:conf-commit
{
"status": "OK",
"conf-changed": true
}
[5]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf_running/data/ntc-rosetta-conf:device
{
"ntc-rosetta-conf:device": {
"config": {
"platform": "ntc-rosetta-conf:ios"
}
}
}
Translating¶
The first thing we can do is translate the data into native format. Let’s start by adding a couple of interfaces:
[6]:
cat 4_translate/add_interface_eth0.json
{
"openconfig-interfaces:interface": {
"name": "eth0",
"config": {
"name": "eth0",
"description": "an interface description",
"type": "iana-if-type:ethernetCsmacd"
}
}
}
[7]:
cat 4_translate/add_interface_eth1.json
{
"openconfig-interfaces:interface": {
"name": "eth1",
"config": {
"name": "eth1",
"description": "another interface",
"type": "iana-if-type:ethernetCsmacd"
}
}
}
[8]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @4_translate/add_interface_eth0.json \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces
[9]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @4_translate/add_interface_eth1.json \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces
[10]:
# candidate database
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces
{
"openconfig-interfaces:interfaces": {
"interface": [
{
"name": "eth0",
"config": {
"name": "eth0",
"description": "an interface description",
"type": "iana-if-type:ethernetCsmacd"
}
},
{
"name": "eth1",
"config": {
"name": "eth1",
"description": "another interface",
"type": "iana-if-type:ethernetCsmacd"
}
}
]
}
}
[11]:
# running database
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf_running/data/openconfig-interfaces:interfaces
{
"openconfig-interfaces:interfaces": {
"interface": []
}
}
Now we can translate either the “candidate” or “running” databases into native configuration:
[12]:
cat 4_translate/translate_candidate.json
{
"ntc-rosetta-conf:input": {
"database": "candidate"
}
}
[13]:
curl -s --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @4_translate/translate_candidate.json \
$BASE_URL/restconf/operations/ntc-rosetta-conf:translate | jq -r ".native"
interface eth0
description an interface description
exit
!
interface eth1
description another interface
exit
!
[14]:
# running was empty so no data there
curl -s --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @4_translate/translate_running.json \
$BASE_URL/restconf/operations/ntc-rosetta-conf:translate | jq -r ".native"
[15]:
# now we are happy with it we can commit the configuration
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
$BASE_URL/restconf/operations/jetconf:conf-commit
{
"status": "OK",
"conf-changed": true
}
More changes¶
We can now apply more changes to the candidate database and keep comparing the candidate and running databases of the device using their native representation:
[16]:
cat 4_translate/change_interface_eth0.json
{
"openconfig-interfaces:description": "a changed description"
}
[17]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X PUT \
-d @4_translate/change_interface_eth0.json \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces/interface=eth0/config/description
[18]:
curl -s --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @4_translate/translate_candidate.json \
$BASE_URL/restconf/operations/ntc-rosetta-conf:translate | jq -r ".native"
interface eth0
description a changed description
exit
!
interface eth1
description another interface
exit
!
[19]:
curl -s --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @4_translate/translate_running.json \
$BASE_URL/restconf/operations/ntc-rosetta-conf:translate | jq -r ".native"
interface eth0
description an interface description
exit
!
interface eth1
description another interface
exit
!
At this point you could grab both “native” results and diff them:
[20]:
curl -s --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @4_translate/translate_candidate.json \
$BASE_URL/restconf/operations/ntc-rosetta-conf:translate | jq -r ".native" > /tmp/candidate
curl -s --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @4_translate/translate_running.json \
$BASE_URL/restconf/operations/ntc-rosetta-conf:translate | jq -r ".native" > /tmp/running
diff -W 100 --side-by-side /tmp/candidate /tmp/running || echo -n
interface eth0 interface eth0
description a changed description | description an interface description
exit exit
! !
interface eth1 interface eth1
description another interface description another interface
exit exit
! !
Merging¶
Alterntatively, you can call the merge endpoint and get a list of commands that will make the configurations converge:
[21]:
curl -s --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @4_translate/merge.json \
$BASE_URL/restconf/operations/ntc-rosetta-conf:merge | jq -r ".native"
interface eth0
description a changed description
exit
!
[22]:
# ignore me, this deletes the data so the notebook can be rerun
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
$BASE_URL/restconf/operations/jetconf:conf-reset
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X PUT \
-d @../../tests/data/interfaces_empty.json \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X DELETE \
$BASE_URL/restconf/data/ntc-rosetta-conf:device
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
$BASE_URL/restconf/operations/jetconf:conf-commit
{
"status": "OK"
}{
"status": "OK",
"conf-changed": true
}
Parsing¶
Starting things up¶
Run:
make start-dev-containers
Now, let’s export some environment variables we need:
[1]:
export USER_CERT=../../tests/certs/test_user_curl.pem
export BASE_URL=https://ntc-rosetta-conf:8443
Configuration¶
First we need to configure the platform of the device, this configuration is part of the model.
[2]:
cat 5_parse/configuration.json
{
"ntc-rosetta-conf:device": {
"config": {
"platform": "ios"
}
}
}
[3]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @5_parse/configuration.json \
$BASE_URL/restconf/data/
[4]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
$BASE_URL/restconf/operations/jetconf:conf-commit
{
"status": "OK",
"conf-changed": true
}
[5]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf_running/data/ntc-rosetta-conf:device
{
"ntc-rosetta-conf:device": {
"config": {
"platform": "ntc-rosetta-conf:ios"
}
}
}
Parsing¶
Now let’s see how we can parse native configuration, let’s start by checking configuration is empty:
[6]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf_running/data/openconfig-interfaces:interfaces
{
"openconfig-interfaces:interfaces": {
"interface": []
}
}
Now we need to a json object with the following structure:
{
"ntc-rosetta-conf:input": {
"validate": true,
"native": "string with configuration in native format (ios_style/xml/etc)"
}
}
[7]:
cat 5_parse/parse.json
{
"ntc-rosetta-conf:input": {
"validate": true,
"native": "interface GigabitEthernet0\n description an interface description\n exit\n!\ninterface GigabitEthernet1\n description another interface\n exit\n!\n"
}
}
Now we call the RPC ntc-rosetta-conf:parse
:
[8]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
-d @5_parse/parse.json \
$BASE_URL/restconf/operations/ntc-rosetta-conf:parse
{
"result": "ntc-rosetta-config:success"
}
Configuration is parsed and loaded into the running database:
[9]:
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X GET \
$BASE_URL/restconf_running/data/openconfig-interfaces:interfaces
{
"openconfig-interfaces:interfaces": {
"interface": [
{
"name": "GigabitEthernet0",
"config": {
"name": "GigabitEthernet0",
"type": "iana-if-type:ethernetCsmacd",
"description": "an interface description",
"enabled": true
}
},
{
"name": "GigabitEthernet1",
"config": {
"name": "GigabitEthernet1",
"type": "iana-if-type:ethernetCsmacd",
"description": "another interface",
"enabled": true
}
}
]
}
}
[10]:
# ignore me, this deletes the data so the notebook can be rerun
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X PUT \
-d @../../tests/data/interfaces_empty.json \
$BASE_URL/restconf/data/openconfig-interfaces:interfaces
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X DELETE \
$BASE_URL/restconf/data/ntc-rosetta-conf:device
curl --http2 -k --cert-type PEM -E $USER_CERT \
-X POST \
$BASE_URL/restconf/operations/jetconf:conf-commit
{
"status": "OK",
"conf-changed": true
}
Mutual TLS authentication¶
ntc-rosetta-conf
utilizes mutual TLS (mTLS) for authentication purposes. In this document we are going to quickly see how mutual TLS authentication works in the context of ntc-rosetta-conf
Intro¶
This introduction mTLS doesn’t aim to be an in-depth explanation of how it works, there are plenty of resources out there that aim to do that. Here we are going to just introduce the basic concepts so we have a basic understanding to operate ntc-rosetta-conf
The basic idea of mTLS is that both the client and the server will validate the other party by looking at their certificate. This is done by checking that the certificate authority that signed the other party is (a) the one we expect, (b) cert hasn’t expired and (c) cert hasn’t been revoked. Let’s see an illustration:
+--------------------------------------------+ +--------------------------------------------+
| client CA | | server CA |
|(certificate authority to sign client certs)| |(certificate authority to sign server certs)|
+--------------------------------------------+ +--------------------------------------------+
____/ | \____ ____/ | \____
| | | | | |
+---------+ +---------+ +---------+ +---------+ +---------+ +---------+
| client1 | | client2 | | client3 | | server1 | | server2 | | server3 |
+---------+ +---------+ +---------+ +---------+ +---------+ +---------+
mTLS Authentication Overview
==============================================================================
Client| 1. Start tls handshake |Server
| ---------------------------------------------------------------------------> |
| A. Send server certificate and request one to the client |
| <--------------------------------------------------------------------------- |
| 2. Validate server cert is valid by checking it was signed by "server CA" |
| 3. Send client certificate |
| ---------------------------------------------------------------------------> |
| B. Validate client cert is valid by checking it was signed by "client CA" |
==============================================================================
Note
This is gross oversimplification of how TLS and mTLS work. Refer to more in-depth guides/documentation if you are interested in knowing how it works.
Tutorial¶
Let’s test the theory. Before we start, if you want to follow this tutorial you need:
- Docker. Alternatively, you can install easypki and use it outside the docker container.
- Create folder
pki_auto_generated_dir
in your current path:mkdir pki_auto_generated_dir
Clients¶
First, we need to provide our users with client certificates, so let’s create a couple.
Creating the client CA¶
To generate client certificates we are going to need a CA. Ideally, such CA would be an intermediate CA signed by a valid CA but as this is dev we are going to just create a root CA:
$ docker run -v $PWD:/certs -w /certs \
creatdevsolutions/easypki create \
--ca \
--filename client_ca \
--expire 365 \
--private-key-size 2048 \
--organization "NTC" \
--organizational-unit "Eng" \
--locality "Stockholm" \
--country "Sweden" \
--province "Stockholm" \
ntc-rosetta-conf-client-authority
Now, you can find under pki_auto_generated_dir/client_ca/{certs,keys}
our CA cert and key:
$ ls pki_auto_generated_dir/client_ca/{certs,keys}
pki_auto_generated_dir/client_ca/certs:
client_ca.crt
pki_auto_generated_dir/client_ca/keys:
client_ca.key
Creating client certificates¶
Now that we have a CA to generate client certificates let’s create a couple for Jane and John:
$ docker run -v $PWD:/certs -w /certs \
creatdevsolutions/easypki create \
--client \
--ca-name "client_ca" \
--expire 365 \
--private-key-size 2048 \
--organization "NTC" \
--organizational-unit "Eng" \
--locality "Stockholm" \
--country "Sweden" \
--province "Stockholm" \
--email "jane@networktocode.com" \
jane@networktocode.com
$ docker run -v $PWD:/certs -w /certs \
creatdevsolutions/easypki create \
--client \
--ca-name "client_ca" \
--expire 365 \
--private-key-size 2048 \
--organization "NTC" \
--organizational-unit "Eng" \
--locality "Stockholm" \
--country "Sweden" \
--province "Stockholm" \
--email "john@networktocode.com" \
john@networktocode.com
Generated certs and keys will be under the same client_ca
folder from before:
$ls pki_auto_generated_dir/client_ca/{certs,keys}
pki_auto_generated_dir/client_ca/certs:
client_ca.crt jane@networktocode.com.crt john@networktocode.com.crt
pki_auto_generated_dir/client_ca/keys:
client_ca.key jane@networktocode.com.key john@networktocode.com.key
Server certificates¶
Now we are going to need a server certificate for each instance of ntc-rosetta-conf
.
Server CA¶
As with the client CA ideally you’d use an intermediate CA signed by a valid CA but, as this is dev, we are going to create our own root CA:
$ docker run -v $PWD:/certs -w /certs \
creatdevsolutions/easypki create \
--ca \
--filename server_ca \
--expire 365 \
--private-key-size 2048 \
--organization "NTC" \
--organizational-unit "Eng" \
--locality "Stockholm" \
--country "Sweden" \
--province "Stockholm" \
ntc-rosetta-conf-server-authority
This time, we will find certs and keys under pki_auto_generated_dir/server_ca/{certs,keys}
:
$ ls pki_auto_generated_dir/server_ca/{certs,keys}
pki_auto_generated_dir/server_ca/certs:
server_ca.crt
pki_auto_generated_dir/server_ca/keys:
server_ca.key
Creating server certificates¶
Now it’s time to generate the certificates for our ntc-rosetta-conf
instances:
$ docker run -v $PWD:/certs -w /certs \
creatdevsolutions/easypki create \
--ca-name "server_ca" \
--expire 365 \
--private-key-size 2048 \
--organization "NTC" \
--organizational-unit "Eng" \
--locality "Stockholm" \
--country "Sweden" \
--province "Stockholm" \
rtr00.lab.local
$ docker run -v $PWD:/certs -w /certs \
creatdevsolutions/easypki create \
--ca-name "server_ca" \
--expire 365 \
--private-key-size 2048 \
--organization "NTC" \
--organizational-unit "Eng" \
--locality "Stockholm" \
--country "Sweden" \
--province "Stockholm" \
rtr01.lab.local
Certs and keys will be under the same path as the server CA:
$ ls pki_auto_generated_dir/server_ca/{certs,keys}
pki_auto_generated_dir/server_ca/certs:
rtr00.lab.local.crt rtr01.lab.local.crt server_ca.crt
pki_auto_generated_dir/server_ca/keys:
rtr00.lab.local.key rtr01.lab.local.key server_ca.key
Starting ntc-rosetta-conf¶
Now that everything is ready, let’s start ntc-rosetta-conf
. Note the options for --ssl-crt
(server cert), --ssl-key
(server key) and --ca-crt
(client CA cert):
$ ntc-rosetta-conf serve \
--datamodel openconfig \
--pid-file /tmp/ntc-rosetta-conf-demo.pid \
--log-level debug \
--data-file data.json \
--port 8443 \
--ssl-crt pki_auto_generated_dir/server_ca/certs/rtr00.lab.local.crt \
--ssl-key pki_auto_generated_dir/server_ca/keys/rtr00.lab.local.key \
--ca-crt pki_auto_generated_dir/client_ca/certs/client_ca.crt
2019-07-17 11:54:59,599 INFO NTC Rosetta Conf version TBD
2019-07-17 11:54:59,601 INFO Using config:
GLOBAL:
DATA_JSON_FILE: data.json
LOGFILE: '-'
LOG_DBG_MODULES:
- '*'
LOG_LEVEL: debug
PERSISTENT_CHANGES: true
PIDFILE: /tmp/ntc-rosetta-conf-demo.pid
TIMEZONE: GMT
VALIDATE_TRANSACTIONS: true
YANG_LIB_DIR: asda
HTTP_SERVER:
API_ROOT: /restconf
API_ROOT_RUNNING: /restconf_running
CA_CERT: pki_auto_generated_dir/client_ca/certs/client_ca.crt
DBG_DISABLE_CERTS: false
DOC_DEFAULT_NAME: index.html
DOC_ROOT: doc-root
LISTEN_LOCALHOST_ONLY: false
PORT: 8443
SERVER_NAME: jetconf-h2
SERVER_SSL_CERT: pki_auto_generated_dir/server_ca/certs/rtr00.lab.local.crt
SERVER_SSL_PRIVKEY: pki_auto_generated_dir/server_ca/keys/rtr00.lab.local.key
UPLOAD_SIZE_LIMIT: 1
NACM:
ALLOWED_USERS: []
ENABLED: true
2019-07-17 11:55:00,571 INFO Server started on ('::', 8443, 0, 0)
Client¶
Now we can use curl to query ntc-rosetta-conf
. Let’s start by trying to use curl without using any client cert:
$ curl \
--cacert pki_auto_generated_dir/server_ca/certs/server_ca.crt \
-X GET \
https://rtr00.lab.local:8443/restconf/data/openconfig-interfaces:interfaces
curl: (56) OpenSSL SSL_read: SSL_ERROR_SYSCALL, errno 104
We got an SSL error. This is because the handshake failed as the server requested a client certificate. Let’s try passing our client cert and key this time:
$ curl \
--cacert pki_auto_generated_dir/server_ca/certs/server_ca.crt \
--cert pki_auto_generated_dir/client_ca/certs/jane@networktocode.com.crt \
--key pki_auto_generated_dir/client_ca/keys/jane@networktocode.com.key \
-X GET \
https://rtr00.lab.local:8443/restconf/data/openconfig-interfaces:interfaces
{
"openconfig-interfaces:interfaces": {
"interface": []
}
}
Now we managed to authenticate ourselves with the server.
Note
make sure that rtr00.lab.local
resolves the correct IP. You can do that for the sake of testing by editing /etc/hosts
.
Note
You probably noticed the line --cacert pki_auto_generated_dir/server_ca/certs/server_ca.crt
. We need that option because we are using self-signed certificates.
Architecture¶
This document shows a reference architecture for ntc-rosetta-conf
. Note that you are free to deploy it in any way that works for you though.
First thing to bear in mind when deploying ntc-rosetta-conf
is that each instance represents a single device. This means that if you have 100 routers you will need 100 instances. This might sound a bit cumbersome but it has the advantage you limit your blast radius in case an instance fails for some reason and it also helps scaling out the solution by spreading the instances across many servers.
To avoid having to run all those instances manually, having to manage all the different ports to avoid collisions and having to remember which port belongs to which router, we recommend running this behind some dockerized solution and behind a load balancer.
A continuation you can see an example of such type of deployment using docker-compose
and haproxy
.
haproxy¶
Let’s start looking at the configuration file:
global
maxconn 2048
ulimit-n 51200
tune.ssl.default-dh-param 2048
defaults
timeout connect 5000
timeout client 50000
timeout server 50000
option http-server-close
option httpclose
mode http
balance roundrobin
frontend https-in
mode http
# listen on port 65443 and enable mTLS and http/2
bind 0.0.0.0:65443 ssl crt /etc/haproxy/rosetta.pem ca-file /etc/haproxy/ca.pem verify optional alpn h2
# forward SSL headers to rosetta
http-request set-header X-SSL %[ssl_fc]
http-request set-header X-SSL-Client-Verify %[ssl_c_verify]
http-request set-header X-SSL-Client-DN %{+Q}[ssl_c_s_dn]
http-request set-header X-SSL-Client-CN %{+Q}[ssl_c_s_dn(cn)]
http-request set-header X-SSL-Issuer %{+Q}[ssl_c_i_dn]
http-request set-header X-SSL-Client-Not-Before %{+Q}[ssl_c_notbefore]
http-request set-header X-SSL-Client-Not-After %{+Q}[ssl_c_notafter]
# configure rules to forward requests to the different instances of rosetta
use_backend rtr00 if { path -i -m beg /rtr00 }
use_backend rtr01 if { path -i -m beg /rtr01 }
backend rtr00
mode http
# remove /rtr0x from the url
reqrep ^([^\ ]*\ /)rtr00[/]?(.*) \1\2
server rtr00 172.21.33.100:8443 proto h2
backend rtr01
mode http
# remove /rtr0x from the url
reqrep ^([^\ ]*\ /)rtr01[/]?(.*) \1\2
server rtr00 172.21.33.101:8443 proto h2
Let’s try to summarize what’s going on:
- First we have some globals and defaults, we can ignore those.
- Next we define a
frontend
, this is what we are going to consume from the outside. The frontend is going to be responsible of terminating TLS and enforcing mTLS and forwarding SSL headers to the different instances instances ofntc-rosetta-conf
. Finally, thefrontend
is going to look at the URL path, look for/rtr0{0,1}
and forward the requests to the corresponding instance ofntc-rosetta-conf
. - Finally, we are going to define a backend per instance of
ntc-rosetta-conf
. In this example we have two of them. The backend needs to specify how to connect to it and also it needs to remove the/rtr0x
bit from the URL as that’s not part of our service.
docker-compose¶
docker-compose
is going to be responsible of instantiating the loadbalancer and both instances of ntc-rosetta-conf
. There isn’t a lot of magic here. Just mount the volumes with the configuration for haproxy, the data directories for each instance of ntc-rosetta-conf
and disable ssl on them as it will be terminated on the loadbalancer:
---
version: '2.2'
services:
loadbalancer:
image: haproxy:2.0-alpine
volumes:
- ./haproxy:/etc/haproxy
command: [
"haproxy",
"-f", "/etc/haproxy/haproxy.cfg",
]
ports:
- 65443:65443
networks:
net1:
ipv4_address: 172.21.33.10
ipv6_address: 2001:db8:33::10
rtr00:
build:
context: ../../..
dockerfile: Dockerfile
args:
PYTHON: 3.6
networks:
net1:
ipv4_address: 172.21.33.100
ipv6_address: 2001:db8:33::100
volumes:
- ./data/rtr00:/data
command: [
"ntc-rosetta-conf",
"serve",
"--datamodel", "openconfig",
"--pid-file", "/tmp/ntc-rosetta-conf-demo.pid",
"--log-level", "debug",
"--data-file", "/data/data.json",
"--port", "8443",
"--disable-ssl",
]
rtr01:
build:
context: ../../..
dockerfile: Dockerfile
args:
PYTHON: 3.6
networks:
net1:
ipv4_address: 172.21.33.101
ipv6_address: 2001:db8:33::101
volumes:
- ./data/rtr01:/data
command: [
"ntc-rosetta-conf",
"serve",
"--datamodel", "openconfig",
"--pid-file", "/tmp/ntc-rosetta-conf-demo.pid",
"--log-level", "debug",
"--data-file", "/data/data.json",
"--port", "8443",
"--disable-ssl",
]
networks:
net1:
driver: bridge
enable_ipv6: true
ipam:
config:
- subnet: 172.21.33.0/24
- subnet: 2001:db8:33::/64
After everything is up now you should be able to access each particular instance via /rtr00
and /rtr01
respectively. For instance; https://rosetta:65443/rtr00/restconf/data/openconfig-interfaces:interfaces
This can look like it’s going to be a lot if you have hundreds or thousands of devices but as you probably figured already these two configuration files are very easy to template and automate.
YANG models¶
ntc-rosetta-conf
supports same YANG models that ntc-rosetta
does so refer to its documentation for details on those.
In addition, RESTCONF operations require the following models:
ietf-yang-library¶
This module contains monitoring information about the YANG modules and submodules that are used within a YANG-based server. Copyright (c) 2016 IETF Trust and the persons identified as authors of the code. All rights reserved. Redistribution and use in source and binary forms, with or without modification, is permitted pursuant to, and subject to the license terms contained in, the Simplified BSD License set forth in Section 4.c of the IETF Trust’s Legal Provisions Relating to IETF Documents (http://trustee.ietf.org/license-info). This version of this YANG module is part of RFC 7895; see the RFC itself for full legal notices.
Types¶
revision-identifier¶
Represents a specific date in YYYY-MM-DD format.
type: string
pattern: \d{4}-\d{2}-\d{2}
Data nodes¶
/modules-state/module-set-id¶
Contains a server-specific identifier representing the current set of modules and submodules. The server MUST change the value of this leaf if the information represented by the ‘module’ list instances has changed.
nodetype: leaf
Type: string
/modules-state/module¶
Each entry represents one revision of one module currently supported by the server.
nodetype: list
/modules-state/module/name¶
The YANG module or submodule name.
nodetype: leaf
(list key)
Type: yang:yang-identifier
/modules-state/module/revision¶
The YANG module or submodule revision date. A zero-length string is used if no revision statement is present in the YANG module or submodule.
nodetype: leaf
(list key)
Type: union
- Type:
revision-identifier
- Type:
string
/modules-state/module/schema¶
Contains a URL that represents the YANG schema resource for this module or submodule. This leaf will only be present if there is a URL available for retrieval of the schema for this entry.
nodetype: leaf
Type: inet:uri
/modules-state/module/namespace¶
The XML namespace identifier for this module.
nodetype: leaf
Type: inet:uri
/modules-state/module/feature¶
List of YANG feature names from this module that are supported by the server, regardless of whether they are defined in the module or any included submodule.
nodetype: leaf-list
Type: yang:yang-identifier
/modules-state/module/deviation¶
List of YANG deviation module names and revisions used by this server to modify the conformance of the module associated with this entry. Note that the same module can be used for deviations for multiple modules, so the same entry MAY appear within multiple ‘module’ entries. The deviation module MUST be present in the ‘module’ list, with the same name and revision values. The ‘conformance-type’ value will be ‘implement’ for the deviation module.
nodetype: list
/modules-state/module/deviation/name¶
The YANG module or submodule name.
nodetype: leaf
(list key)
Type: yang:yang-identifier
/modules-state/module/deviation/revision¶
The YANG module or submodule revision date. A zero-length string is used if no revision statement is present in the YANG module or submodule.
nodetype: leaf
(list key)
Type: union
- Type:
revision-identifier
- Type:
string
/modules-state/module/conformance-type¶
Indicates the type of conformance the server is claiming for the YANG module identified by this entry.
nodetype: leaf
Type: enumeration
implement
: Indicates that the server implements one or more
protocol-accessible objects defined in the YANG module identified in this entry. This includes deviation statements defined in the module. For YANG version 1.1 modules, there is at most one module entry with conformance type ‘implement’ for a particular module name, since YANG 1.1 requires that, at most, one revision of a module is implemented. For YANG version 1 modules, there SHOULD NOT be more than one module entry for a particular module name.
import
: Indicates that the server imports reusable definitions
from the specified revision of the module but does not implement any protocol-accessible objects from this revision. Multiple module entries for the same module name MAY exist. This can occur if multiple modules import the same module but specify different revision dates in the import statements.
/modules-state/module/submodule¶
Each entry represents one submodule within the parent module.
nodetype: list
/modules-state/module/submodule/name¶
The YANG module or submodule name.
nodetype: leaf
(list key)
Type: yang:yang-identifier
/modules-state/module/submodule/revision¶
The YANG module or submodule revision date. A zero-length string is used if no revision statement is present in the YANG module or submodule.
nodetype: leaf
(list key)
Type: union
- Type:
revision-identifier
- Type:
string
/modules-state/module/submodule/schema¶
Contains a URL that represents the YANG schema resource for this module or submodule. This leaf will only be present if there is a URL available for retrieval of the schema for this entry.
nodetype: leaf
Type: inet:uri
/yang-library-change¶
Generated when the set of modules and submodules supported by the server has changed.
nodetype: notification
/yang-library-change/module-set-id¶
Contains the module-set-id value representing the set of modules and submodules supported at the server at the time the notification is generated.
nodetype: leaf
Type: leafref
- path reference:
/modules-state/module-set-id
ntc-rosetta-conf¶
This module describes all the operations that ntc-rosetta-conf can perform.
Identities¶
base: RESULT¶
Base identity for results
base: PLATFORM¶
Base identity for device’s platform
Data nodes¶
/device/config/platform¶
Device platform. Some RPC methods will require you to set this
nodetype: leaf
Type: identityref
- base:
PLATFORM
/ping/input¶
nodetype: input
/ping/output¶
nodetype: output
/ping/output/result¶
Whether the operation succeeded or not
nodetype: leaf
Type: identityref
- base:
ntc-rosetta-conf:RESULT
/ping/output/error-message¶
If the operation failed, message describing the error
nodetype: leaf
Type: string
/merge¶
Call ntc-rosetta merge method. Visit https://ntc-rosetta.readthedocs.io/en/latest/tutorials/ios_merging.html for details
nodetype: rpc
/merge/input¶
nodetype: input
/merge/output¶
nodetype: output
/merge/output/result¶
Whether the operation succeeded or not
nodetype: leaf
Type: identityref
- base:
ntc-rosetta-conf:RESULT
/merge/output/error-message¶
If the operation failed, message describing the error
nodetype: leaf
Type: string
/parse¶
Call ntc-rosetta parse method. Visit https://ntc-rosetta.readthedocs.io/en/latest/tutorials/ios_parsing.html for detauls. Loads the parsed object into the candidate database.
nodetype: rpc
/parse/input¶
nodetype: input
/parse/input/validate¶
Valiadte the configuration prior to load it into the candidate databbase
nodetype: leaf
Type: boolean
/parse/output¶
nodetype: output
/parse/output/result¶
Whether the operation succeeded or not
nodetype: leaf
Type: identityref
- base:
ntc-rosetta-conf:RESULT
/parse/output/error-message¶
If the operation failed, message describing the error
nodetype: leaf
Type: string
/translate¶
Call ntc-rosetta parse method. Visit https://ntc-rosetta.readthedocs.io/en/latest/tutorials/ios_translate.html
nodetype: rpc
/translate/input¶
nodetype: input
/translate/output¶
nodetype: output
/translate/output/result¶
Whether the operation succeeded or not
nodetype: leaf
Type: identityref
- base:
ntc-rosetta-conf:RESULT
/translate/output/error-message¶
If the operation failed, message describing the error
nodetype: leaf
Type: string
CONTRIBUTING¶
All contributions are welcome and even though there are no strict or formal requirements to contribute there are some basic rules contributors need to follow:
- Make sure your contribution is original and it doesn’t violate anybody’s copyright
- Make sure tests pass
- Make sure your contribution is tested
Below you can find more information depending on what you are trying to contribute, in case of doubt don’t hesitate to open a PR with your contribution and ask for help.
Running tests¶
To run tests you need docker
and GNU Make
. If you meet the requirements all you need to do is execute make tests
. All the tests will run inside docker containers you don’t need to worry about the environment.
Adding documentation¶
If you want to contribute documentation you need to be sligthly familiar with sphinx as that’s the framework used in this project (and most python projects) to build the documentation.
In addition, if you want to contribute with tutorials or code examples you need to be familiar with jupyter. The advantage of using jupyter notebooks over just plain text is that notebooks can be tested. This means code examples and tutorials will be tested by the CI and ensure they stay relevant and work.
The easiest way of working with jupyter is by executing make jupyter
in your local machine and pointing your browser to http://localhost:8888/notebooks/docs. If you are adding a new notebook don’t forget to add it to sphinx’s documentation.
Adding new features¶
New features need to come with tests and a tutorial in the form of a jupyter notebook so it can be tested.
Indices and tables