IETF Interfaces
Warning
Pydantify is still in an alpha state and many parts can hopefully be improved in future versions.
YANG Model
Using pyang the model can be validated and displayed as a tree.
module: ietf-interfaces
+--rw interfaces
| +--rw interface* [name]
| +--rw name string
| +--rw description? string
| +--rw type identityref
| +--rw enabled? boolean
| +--rw link-up-down-trap-enable? enumeration {if-mib}?
+--ro interfaces-state
+--ro interface* [name]
+--ro name string
+--ro type identityref
+--ro admin-status enumeration {if-mib}?
+--ro oper-status enumeration
+--ro last-change? yang:date-and-time
+--ro if-index int32 {if-mib}?
+--ro phys-address? yang:phys-address
+--ro higher-layer-if* interface-state-ref
+--ro lower-layer-if* interface-state-ref
+--ro speed? yang:gauge64
+--ro statistics
+--ro discontinuity-time yang:date-and-time
+--ro in-octets? yang:counter64
+--ro in-unicast-pkts? yang:counter64
+--ro in-broadcast-pkts? yang:counter64
+--ro in-multicast-pkts? yang:counter64
+--ro in-discards? yang:counter32
+--ro in-errors? yang:counter32
+--ro in-unknown-protos? yang:counter32
+--ro out-octets? yang:counter64
+--ro out-unicast-pkts? yang:counter64
+--ro out-broadcast-pkts? yang:counter64
+--ro out-multicast-pkts? yang:counter64
+--ro out-discards? yang:counter32
+--ro out-errors? yang:counter32
Note
Only the model ietf-interfaces is used without any models like ietf-ip, which augment the ietf-interfaces model.
Create pydantic model
To focus only on the configuration part of the model, the model path can be trimmed to the tree branch ietf-interfaces:interfaces/
.
$ pydantify -t=ietf-interfaces/interfaces ietf-interfaces.yang
[INFO] /workspaces/pydantify/pydantify/plugins/pydantic_plugin.py:41 (emit): Output model generated in 0.063s.
The generated module will be in the file out/out.py
. We can move and rename it to ietf_interfaces.py
.
ietf_interfaces.py
from __future__ import annotations
from enum import Enum
from typing import Any, List, Optional
from pydantic import BaseModel, ConfigDict, Field, RootModel
from typing_extensions import Annotated
class EnumerationEnum(Enum):
enabled = 'enabled'
disabled = 'disabled'
class InterfaceListEntry(BaseModel):
"""
The list of configured interfaces on the device.
The operational state of an interface is available in the
/interfaces-state/interface list. If the configuration of a
system-controlled interface cannot be used by the system
(e.g., the interface hardware present does not match the
interface type), then the configuration is not applied to
the system-controlled interface shown in the
/interfaces-state/interface list. If the configuration
of a user-controlled interface cannot be used by the system,
the configured interface is not instantiated in the
/interfaces-state/interface list.
"""
model_config = ConfigDict(
populate_by_name=True,
regex_engine="python-re",
)
name: Annotated[
Optional[str], Field(alias='ietf-interfaces:name', title='NameLeaf')
] = None
"""
The name of the interface.
A device MAY restrict the allowed values for this leaf,
possibly depending on the type of the interface.
For system-controlled interfaces, this leaf is the
device-specific name of the interface. The 'config false'
list /interfaces-state/interface contains the currently
existing interfaces on the device.
If a client tries to create configuration for a
system-controlled interface that is not present in the
/interfaces-state/interface list, the server MAY reject
the request if the implementation does not support
pre-provisioning of interfaces or if the name refers to
an interface that can never exist in the system. A
NETCONF server MUST reply with an rpc-error with the
error-tag 'invalid-value' in this case.
If the device supports pre-provisioning of interface
configuration, the 'pre-provisioning' feature is
advertised.
If the device allows arbitrarily named user-controlled
interfaces, the 'arbitrary-names' feature is advertised.
When a configured user-controlled interface is created by
the system, it is instantiated with the same name in the
/interface-state/interface list.
"""
description: Annotated[
Optional[str],
Field(alias='ietf-interfaces:description', title='DescriptionLeaf'),
] = None
"""
A textual description of the interface.
A server implementation MAY map this leaf to the ifAlias
MIB object. Such an implementation needs to use some
mechanism to handle the differences in size and characters
allowed between this leaf and ifAlias. The definition of
such a mechanism is outside the scope of this document.
Since ifAlias is defined to be stored in non-volatile
storage, the MIB implementation MUST map ifAlias to the
value of 'description' in the persistently stored
datastore.
Specifically, if the device supports ':startup', when
ifAlias is read the device MUST return the value of
'description' in the 'startup' datastore, and when it is
written, it MUST be written to the 'running' and 'startup'
datastores. Note that it is up to the implementation to
decide whether to modify this single leaf in 'startup' or
perform an implicit copy-config from 'running' to
'startup'.
If the device does not support ':startup', ifAlias MUST
be mapped to the 'description' leaf in the 'running'
datastore.
"""
type: Annotated[Any, Field(alias='ietf-interfaces:type', title='TypeLeaf')]
"""
The type of the interface.
When an interface entry is created, a server MAY
initialize the type leaf with a valid value, e.g., if it
is possible to derive the type from the name of the
interface.
If a client tries to set the type of an interface to a
value that can never be used by the system, e.g., if the
type is not supported or if the type does not match the
name of the interface, the server MUST reject the request.
A NETCONF server MUST reply with an rpc-error with the
error-tag 'invalid-value' in this case.
"""
enabled: Annotated[
Optional[bool], Field(alias='ietf-interfaces:enabled', title='EnabledLeaf')
] = True
"""
This leaf contains the configured, desired state of the
interface.
Systems that implement the IF-MIB use the value of this
leaf in the 'running' datastore to set
IF-MIB.ifAdminStatus to 'up' or 'down' after an ifEntry
has been initialized, as described in RFC 2863.
Changes in this leaf in the 'running' datastore are
reflected in ifAdminStatus, but if ifAdminStatus is
changed over SNMP, this leaf is not affected.
"""
link_up_down_trap_enable: Annotated[
Optional[EnumerationEnum],
Field(
alias='ietf-interfaces:link-up-down-trap-enable',
title='Link-up-down-trap-enableLeaf',
),
] = None
"""
Controls whether linkUp/linkDown SNMP notifications
should be generated for this interface.
If this node is not configured, the value 'enabled' is
operationally used by the server for interfaces that do
not operate on top of any other interface (i.e., there are
no 'lower-layer-if' entries), and 'disabled' otherwise.
"""
class InterfacesContainer(BaseModel):
"""
Interface configuration parameters.
"""
model_config = ConfigDict(
populate_by_name=True,
regex_engine="python-re",
)
interface: Annotated[
Optional[List[InterfaceListEntry]], Field(alias='ietf-interfaces:interface')
] = None
class Model(BaseModel):
"""
Initialize an instance of this class and serialize it to JSON; this results in a RESTCONF payload.
## Tips
Initialization:
- all values have to be set via keyword arguments
- if a class contains only a `root` field, it can be initialized as follows:
- `member=MyNode(root=<value>)`
- `member=<value>`
Serialziation:
- `exclude_defaults=True` omits fields set to their default value (recommended)
- `by_alias=True` ensures qualified names are used (necessary)
"""
model_config = ConfigDict(
populate_by_name=True,
regex_engine="python-re",
)
interfaces: Annotated[
Optional[InterfacesContainer], Field(alias='ietf-interfaces:interfaces')
] = None
if __name__ == "__main__":
model = Model(
# <Initialize model here>
)
restconf_payload = model.model_dump_json(
exclude_defaults=True, by_alias=True, indent=2
)
print(f"Generated output: {restconf_payload}")
# Send config to network device:
# from pydantify.utility import restconf_patch_request
# restconf_patch_request(url='...', user_pw_auth=('usr', 'pw'), data=restconf_payload)
Parse JSON data into the model
Using requests, or any other HTTP library, the data can be retrieved in JSON format.
restconf.py | |
---|---|
The output depends on the network device. This example uses a Cisco CSR1k with three interfaces.
{
"ietf-interfaces:interfaces": {
"interface": [
{
"name": "GigabitEthernet1",
"description": "MANAGEMENT INTERFACE - DON'T TOUCH ME",
"type": "iana-if-type:ethernetCsmacd",
"enabled": true,
"ietf-ip:ipv4": {
"address": [
{
"ip": "10.10.20.48",
"netmask": "255.255.255.0"
}
]
},
"ietf-ip:ipv6": {}
},
{
"name": "GigabitEthernet2",
"description": "https://pydantify.github.io/pydantify/",
"type": "iana-if-type:ethernetCsmacd",
"enabled": true,
"ietf-ip:ipv4": {},
"ietf-ip:ipv6": {}
},
{
"name": "GigabitEthernet3",
"description": "Configured and Merged by Ansible Network",
"type": "iana-if-type:ethernetCsmacd",
"enabled": false,
"ietf-ip:ipv4": {},
"ietf-ip:ipv6": {}
}
]
}
}
The received response from the device includes data from the ietf-ip model, which augment the ietf-interfaces model. Because the build model does not have these fields, the model configuration must be set to ignore extra fields (default). Pydantic Configuration extra
restconf.py | |
---|---|
Now the model is filled with the received data. By using the option by_alias=True
, all keys contain the model prefix in the output.
{
"ietf-interfaces:interfaces": {
"ietf-interfaces:interface": [
{
"ietf-interfaces:name": "GigabitEthernet1",
"ietf-interfaces:description": "MANAGEMENT INTERFACE - DON'T TOUCH ME",
"ietf-interfaces:type": "iana-if-type:ethernetCsmacd",
"ietf-interfaces:enabled": true
},
{
"ietf-interfaces:name": "GigabitEthernet2",
"ietf-interfaces:description": "Configured and Merged by Ansible Network",
"ietf-interfaces:type": "iana-if-type:ethernetCsmacd",
"ietf-interfaces:enabled": false
},
{
"ietf-interfaces:name": "GigabitEthernet3",
"ietf-interfaces:description": "Configured and Merged by Ansible Network",
"ietf-interfaces:type": "iana-if-type:ethernetCsmacd",
"ietf-interfaces:enabled": false
}
]
}
}
As with all Python objects, you can access them and make evaluations.
restconf.py | |
---|---|
The first interface is the management interface and is enabled; the other two are disabled.
Interface GigabitEthernet1 is enabled
Interface GigabitEthernet2 is disabled
Interface GigabitEthernet3 is disabled
Update/change model
This example takes the second interface, changes the interface to enable, and updates the description.
restconf.py | |
---|---|
Taking a look at only this level of the tree, the generated output contains the changes.
{
"ietf-interfaces:name": "GigabitEthernet2",
"ietf-interfaces:description": "https://pydantify.github.io/pydantify/",
"ietf-interfaces:type": "iana-if-type:ethernetCsmacd",
"ietf-interfaces:enabled": true
}
For an easy configuration update, a new model can be created only containing the changed interface.
restconf.py | |
---|---|
Now the output contains all layers of the YANG tree starting at the root interfaces field.
{
"ietf-interfaces:interfaces": {
"ietf-interfaces:interface": [
{
"ietf-interfaces:name": "GigabitEthernet2",
"ietf-interfaces:description": "https://pydantify.github.io/pydantify/",
"ietf-interfaces:type": "iana-if-type:ethernetCsmacd",
"ietf-interfaces:enabled": true
}
]
}
}
Update device configuration
Using the newly created model from the top, a PATCH request can be sent to the device using the root URL of the model (same URL was used to get the data).
exclude_defaults=True
It is recommended to use the option exlude_defaults=True
not to send unnecessary data.
restconf.py | |
---|---|
To address the interface directly using the URL, to not only update but also be able to replace the configuration, the data structure needs not a map containing a list of interfaces but a map containing a map looking like this:
{
"ietf-interfaces:interface": {
"ietf-interfaces:name": "GigabitEthernet2",
"ietf-interfaces:description": "https://pydantify.github.io/pydantify/",
"ietf-interfaces:type": "iana-if-type:ethernetCsmacd",
"ietf-interfaces:enabled": true,
}
}
Now the URL must include the interface like ietf-interfaces:interfaces/interface=GigabitEthernet2
.