From 7728a8a62a5304f43a7ad38fe9b306b5b49e4433 Mon Sep 17 00:00:00 2001 From: Robert Gildein Date: Thu, 6 Aug 2026 16:01:53 +0200 Subject: [PATCH 1/2] feat(openconfig): Implement ManagementAccess provider Add EnsureManagementAccess and DeleteManagementAccess to the OpenConfig provider using openconfig-system YANG paths: - gRPC server: /system/grpc-servers/grpc-server[name=gnmi]/config - SSH server: /system/ssh-server/config Unsupported fields (spec.grpc.gnmi, spec.ssh.sessionLimit) are rejected with a terminal UnsupportedFieldError, following the DNS provider pattern. Co-authored-by: Claude Signed-off-by: Robert Gildein --- .../provider/openconfig/managementaccess.go | 92 +++++++++++++++++++ .../testdata/openconfig/managementaccess.txt | 47 ++++++++++ 2 files changed, 139 insertions(+) create mode 100644 internal/provider/openconfig/managementaccess.go create mode 100644 test/gnmi/testdata/openconfig/managementaccess.txt diff --git a/internal/provider/openconfig/managementaccess.go b/internal/provider/openconfig/managementaccess.go new file mode 100644 index 000000000..022b1ac29 --- /dev/null +++ b/internal/provider/openconfig/managementaccess.go @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package openconfig + +import ( + "context" + "fmt" + "time" + + "github.com/ironcore-dev/network-operator/api/core/v1alpha1" + "github.com/ironcore-dev/network-operator/internal/apistatus" + "github.com/ironcore-dev/network-operator/internal/provider" + "github.com/ironcore-dev/network-operator/internal/transport/gnmiext" +) + +var _ provider.ManagementAccessProvider = (*Provider)(nil) + +const grpcServerName = "gnmi" + +func (p *Provider) EnsureManagementAccess(ctx context.Context, req *provider.EnsureManagementAccessRequest) error { + ma := req.ManagementAccess + if err := validateManagementAccessSpec(ma.Spec); err != nil { + return err + } + grpcServer := &GRPCServer{ + Name: grpcServerName, + Enable: ma.Spec.GRPC.Enabled, + Port: uint16(ma.Spec.GRPC.Port), + CertificateID: ma.Spec.GRPC.CertificateID, + NetworkInstance: ma.Spec.GRPC.VrfName, + } + sshServer := &SSHServer{ + Enable: ma.Spec.SSH.Enabled, + Timeout: uint32(ma.Spec.SSH.Timeout.Duration.Seconds()), + } + return p.client.Update(ctx, grpcServer, sshServer) +} + +func (p *Provider) DeleteManagementAccess(ctx context.Context) error { + return p.client.Delete(ctx, &GRPCServer{Name: grpcServerName}, &SSHServer{}) +} + +func validateManagementAccessSpec(spec v1alpha1.ManagementAccessSpec) error { + var violations []apistatus.FieldViolation + gnmi := spec.GRPC.GNMI + if gnmi.MaxConcurrentCall != 8 || gnmi.KeepAliveTimeout.Duration != 10*time.Minute { + violations = append(violations, apistatus.FieldViolation{ + Field: "spec.grpc.gnmi", + Description: "gnmi configuration is not supported by the OpenConfig gRPC server model", + }) + } + if spec.SSH.SessionLimit != 32 { + violations = append(violations, apistatus.FieldViolation{ + Field: "spec.ssh.sessionLimit", + Description: "sessionLimit is not supported by the OpenConfig SSH server model", + }) + } + if len(violations) > 0 { + return apistatus.NewUnsupportedFieldError(violations...) + } + return nil +} + +// Compile-time assertions. +var ( + _ gnmiext.DataElement = (*GRPCServer)(nil) + _ gnmiext.DataElement = (*SSHServer)(nil) +) + +// GRPCServer targets the OpenConfig grpc-server list item config container. +type GRPCServer struct { + Name string `json:"name"` + Enable bool `json:"enable"` + Port uint16 `json:"port"` + CertificateID string `json:"certificate-id,omitempty"` + NetworkInstance string `json:"network-instance,omitempty"` +} + +func (g *GRPCServer) XPath() string { + return fmt.Sprintf("openconfig-system:system/grpc-servers/grpc-server[name=%s]/config", g.Name) +} + +// SSHServer targets the OpenConfig ssh-server config container. +type SSHServer struct { + Enable bool `json:"enable"` + Timeout uint32 `json:"timeout,omitempty"` +} + +func (s *SSHServer) XPath() string { + return "openconfig-system:system/ssh-server/config" +} diff --git a/test/gnmi/testdata/openconfig/managementaccess.txt b/test/gnmi/testdata/openconfig/managementaccess.txt new file mode 100644 index 000000000..a66b7c11b --- /dev/null +++ b/test/gnmi/testdata/openconfig/managementaccess.txt @@ -0,0 +1,47 @@ +# ManagementAccess +-- managementaccesses/managementaccess -- +apiVersion: networking.metal.ironcore.dev/v1alpha1 +kind: ManagementAccess +metadata: + name: managementaccess + namespace: default +spec: + deviceRef: + name: device + grpc: + enabled: true + port: 9339 + certificateId: mytrustpoint + vrfName: mgmt + gnmi: + maxConcurrentCall: 16 + keepAliveTimeout: 600s + ssh: + enabled: true + timeout: 120s + sessionLimit: 10 +-- state -- +{ + "openconfig-system:system": { + "grpc-servers": { + "grpc-server": [ + { + "name": "gnmi", + "config": { + "name": "gnmi", + "enable": true, + "port": 9339, + "certificate-id": "mytrustpoint", + "network-instance": "mgmt" + } + } + ] + }, + "ssh-server": { + "config": { + "enable": true, + "timeout": 120 + } + } + } +} From 1f0191ccc0867ae5fbb420b4e45e1653a6ec5b22 Mon Sep 17 00:00:00 2001 From: Robert Gildein Date: Thu, 6 Aug 2026 17:05:38 +0200 Subject: [PATCH 2/2] fix unnecessary conversion from int32 to uint16 + small fixes Signed-off-by: Robert Gildein --- internal/provider/openconfig/managementaccess.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/provider/openconfig/managementaccess.go b/internal/provider/openconfig/managementaccess.go index 022b1ac29..ec1ba36a1 100644 --- a/internal/provider/openconfig/managementaccess.go +++ b/internal/provider/openconfig/managementaccess.go @@ -26,13 +26,13 @@ func (p *Provider) EnsureManagementAccess(ctx context.Context, req *provider.Ens grpcServer := &GRPCServer{ Name: grpcServerName, Enable: ma.Spec.GRPC.Enabled, - Port: uint16(ma.Spec.GRPC.Port), + Port: ma.Spec.GRPC.Port, CertificateID: ma.Spec.GRPC.CertificateID, NetworkInstance: ma.Spec.GRPC.VrfName, } sshServer := &SSHServer{ Enable: ma.Spec.SSH.Enabled, - Timeout: uint32(ma.Spec.SSH.Timeout.Duration.Seconds()), + Timeout: uint32(ma.Spec.SSH.Timeout.Seconds()), } return p.client.Update(ctx, grpcServer, sshServer) } @@ -72,7 +72,7 @@ var ( type GRPCServer struct { Name string `json:"name"` Enable bool `json:"enable"` - Port uint16 `json:"port"` + Port int32 `json:"port"` CertificateID string `json:"certificate-id,omitempty"` NetworkInstance string `json:"network-instance,omitempty"` } @@ -87,6 +87,6 @@ type SSHServer struct { Timeout uint32 `json:"timeout,omitempty"` } -func (s *SSHServer) XPath() string { +func (*SSHServer) XPath() string { return "openconfig-system:system/ssh-server/config" }