Implement Openconfig NTP provider - #517
Conversation
a355b07 to
e1c927a
Compare
| Prefer bool `json:"prefer,omitempty"` | ||
|
|
||
| // The name of the vrf used to communicate with the NTP server. | ||
| // Maps to NetworkInstance in Openconfig. If empty, will be set to `mgmt`. |
There was a problem hiding this comment.
We try to avoid such vendor specifics in our core apis, as these are meant to be vendor-agnostic and work across all vendors.
Also note that the management vrf is called differently on different platforms, so it might not always be called "mgmt".
There was a problem hiding this comment.
This was done specifically for Nokia, but you're right that it should be as much vendor-agnostic as possible. Especially since we will likely be implementing the Nokia provider soon and therefore the Openconfig implementation should remain as pure as possible. I'll remove this.
On the other hand, the name vrfName is already vendor-specific :)
There was a problem hiding this comment.
I wouldn't say that vrfName is vendor specific. The term VRF is quite universally known, the term is even used in the linux kernel (https://docs.kernel.org/networking/vrf.html). And it also refers to the VRF resource type that we have defined (https://github.com/ironcore-dev/network-operator/blob/main/api/core/v1alpha1/vrf_types.go) within it's spec.name.
6974f26 to
edf5a26
Compare
- Add OpenConfig provider implementation for the NTP resource (`internal/provider/openconfig/ntp.go`) and a gnmi testdata file (`test/gnmi/testdata/openconfig/ntp.txtar`) - Get the IP address of the `NTPSpec.SourceInterface` from the device using the GRPC client and use it to populate the `source-address` field in Openconfig - Add a simple Containerlab setup with one SRLinux node + the associated network-operator `NTP` resource If the management interface is used for NTP we can't obtain the address from an `Interface` resource, because we don't create such resource for the management interface. Therefore the provider reads the interface state directly from the device over GRPC and uses its IP address to populate the `source-address` field. Signed-off-by: Adam Trizuljak <adam.trizuljak@sap.com>
edf5a26 to
2ab090d
Compare
Merging this branch will decrease overall coverage
Coverage by fileChanged files (no unit tests)
Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code. |
nikatza
left a comment
There was a problem hiding this comment.
I have some minor comments, happy to discuss them if needed :) 🚀
| Prefer bool `json:"prefer,omitempty"` | ||
|
|
||
| // The name of the vrf used to communicate with the NTP server. | ||
| // Maps to network-instance in Openconfig |
There was a problem hiding this comment.
Do we need this comment here? It does seem to bring any relevant information for the API user at first. May be the commit message or the provider implementation are more appropriate place.
| @@ -0,0 +1,19 @@ | |||
| apiVersion: networking.metal.ironcore.dev/v1alpha1 | |||
There was a problem hiding this comment.
I fear these new files included in this folder as they don't really fit well here in this PR. I think the examples folder is reserved to more complex scenarios. If that is OK for you we could remove them and clarify in our weekly. What do you think?
| } | ||
|
|
||
| func (a *interfaceAddrs) XPath() string { | ||
| return fmt.Sprintf("openconfig-interfaces:interfaces/interface[name=%s]/subinterfaces/subinterface[index=0]/openconfig-if-ip:ipv4/addresses", a.ifName) |
There was a problem hiding this comment.
We have two hard-coded elements here: the index (0) and the AFI (IPv4). I think we should be able to fetch IPv6 or indicate the address family we want. Same goes for the index. What we could do is add these fields in interfaceAddrs (with tag json:"-") and build our xpath using those values.
If so the provider should be changed accordingly, e.g., use index 0 when the interface name matches some constraints (or just document why 0 is hard-coded). For the IPv4 and IPv6 AFI: you could make that dependent on the AFI of the NTPServers in the spec.
| } | ||
|
|
||
| // interfaceIPAddr retrieves the first IPv4 address from the state of the named interface. | ||
| func (p *Provider) interfaceIPAddr(ctx context.Context, name string) (string, error) { |
There was a problem hiding this comment.
We should be able to get IPv6 addresses too :)
| addrs := &interfaceAddrs{ifName: name} | ||
| if err := p.client.GetState(ctx, addrs); err != nil { | ||
| if errors.Is(err, gnmiext.ErrNil) { | ||
| return "", apistatus.NewFailedPreconditionError(fmt.Sprintf("interface %q has no IPv4 address A", name)) |
There was a problem hiding this comment.
"IPv4 address A" --> "IPv4 address"?
| k8s_yaml('./examples/openconfig-containerlab/kubernetes/01-devices/v1alpha1-leaf1-clab-ntp.yaml') | ||
| k8s_resource(new_name='ntp-clab', objects=['ntp-clab:ntp'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False, labels=['containerlab']) |
There was a problem hiding this comment.
I would propose to leave the existing resource from samples and keep the tilt file compact.
| Prefer bool `json:"prefer,omitempty"` | ||
|
|
||
| // The name of the vrf used to communicate with the NTP server. | ||
| // Maps to network-instance in Openconfig |
There was a problem hiding this comment.
| // Maps to network-instance in Openconfig | |
| // Maps to network-instance in Openconfig. |
| labels: | ||
| app.kubernetes.io/name: network-operator | ||
| app.kubernetes.io/managed-by: kustomize | ||
| networking.metal.ironcore.dev/device-name: leaf1-clab |
There was a problem hiding this comment.
| networking.metal.ironcore.dev/device-name: leaf1-clab | |
| networking.metal.ironcore.dev/device-name: leaf1 |
| addrs := &interfaceAddrs{ifName: name} | ||
| if err := p.client.GetState(ctx, addrs); err != nil { | ||
| if errors.Is(err, gnmiext.ErrNil) { | ||
| return "", apistatus.NewFailedPreconditionError(fmt.Sprintf("interface %q has no IPv4 address A", name)) |
There was a problem hiding this comment.
What is "IPv4 address A" and "IPv4 address B" in this file?
| // DNS represents the OpenConfig /system/ntp container. | ||
| type NTP struct { | ||
| Config *NTPConfig `json:"config"` | ||
| Servers *NTPServers `json:"servers"` |
There was a problem hiding this comment.
| Servers *NTPServers `json:"servers"` | |
| Servers *NTPServers `json:"servers,omitempty"` |
This field is optional and may be nil in which case I think we don't want to serialize "null" into the JSON payload. As we do an update (gnmi replace), omitting this field is also fine.
| k8s_resource(new_name='route-prefix', objects=['route-prefix:probe'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False, labels=['samples']) | ||
| k8s_resource(new_name='vtep-peers', objects=['vtep-peers:probe'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False, labels=['samples']) | ||
|
|
||
| k8s_yaml('./examples/openconfig-containerlab/kubernetes/01-devices/v1alpha1-leaf1-clab-ntp.yaml') |
There was a problem hiding this comment.
The naming here is a bit weird. This is nothing really containerlab specific in the sample.
| // NTPServerConfig holds the config container for a NTP server. | ||
| type NTPServerConfig struct { | ||
| Address string `json:"address"` | ||
| Prefer bool `json:"prefer,omitempty"` |
There was a problem hiding this comment.
| Prefer bool `json:"prefer,omitempty"` | |
| Prefer bool `json:"prefer"` |
I think we don't want to omit a value of false (default).
Work in progress
internal/provider/openconfig/ntp.go) and a gnmi testdata file (test/gnmi/testdata/openconfig/ntp.txtar).Add optional fieldNTPSpec.SourceAddressand validation logic (see below)NTPSpec.SourceInterfacefrom the device using the GRPC client and use it to populate thesource-addressfield in OpenconfigDeviceandNTPresourcesApplying examples/openconfig-containerlab/kubernetes/01-devices/v1alpha1-leaf1-clab-ntp.yaml produces the following config in SRLinux:
Cisco vs Openconfig compatibility issue (solved)
NTPSpecdefines theSourceInterfaceNamefield https://github.com/ironcore-dev/network-operator/blob/main/api/core/v1alpha1/ntp_types.go#L36. Openconfig has something similarsource-addresshttps://openconfig.net/projects/models/schemadocs/yangdoc/openconfig-system.html#system-ntp-servers-server-config-source-addressA way to handle it would be to look up the interface given by SourceInterfaceName , get its IPv4 address and put it into
source-address. Does it make sense? Do you usually do such operations, or do you keep the provider code clean from reading any additional k8s resources?Other way would be to just ignore
SourceInterfaceNameand not pass it into the Openconfig struct, or even throw aapistatus.FieldViolationsaying that it's unsupportedSolution
After discussion we decided to do the original proposal and obtain the IP address from the interface. However, if the management interface is used for NTP we can't obtain the address from an
Interfaceresource, because we don't create such resource for the management interface. Therefore the provider reads the interface state directly from the device over GRPC and uses its IP address to populate thesource-addressfield.This solution does not change the NTPSpec and should work with any
SourceInterfaceNamethat has an IP address (either manually configured, or obtained from DHCP).Previous solution - abandoned
Discussed with @nikatza and @rgildeinNTPSpec.SourceInterfaceNameas optionalNTPSpec.SourceAddressSourceInterfaceNameis definedSince both fields are optional, the change will be backwards-compatible with the previous NTPSpec and the generated custom resources.