diff --git a/internal/provider/openconfig/dns.go b/internal/provider/openconfig/dns.go new file mode 100644 index 000000000..f3a1ebccc --- /dev/null +++ b/internal/provider/openconfig/dns.go @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package openconfig + +import ( + "context" + "fmt" + + "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.DNSProvider = (*Provider)(nil) + +func (p *Provider) EnsureDNS(ctx context.Context, req *provider.EnsureDNSRequest) error { + spec := req.DNS.Spec + if err := validateDNSSpec(spec); err != nil { + return err + } + + d := &DNS{ + Config: &DNSConfig{ + Search: []string{spec.Domain}, + }, + } + + if len(spec.Servers) > 0 { + d.Servers = &DNSServers{} + for _, s := range spec.Servers { + d.Servers.Server.Set(&DNSServer{ + Address: s.Address, + Config: &DNSServerConfig{Address: s.Address}, + }) + } + } + + return p.client.Update(ctx, d) +} + +func (p *Provider) DeleteDNS(ctx context.Context) error { + return p.client.Delete(ctx, &DNS{}) +} + +func validateDNSSpec(spec v1alpha1.DNSSpec) error { + var violations []apistatus.FieldViolation + if spec.AdminState == v1alpha1.AdminStateDown { + violations = append(violations, apistatus.FieldViolation{ + Field: "spec.adminState", + Description: "adminState Down is not supported by the OpenConfig DNS model", + }) + } + if spec.SourceInterfaceName != "" { + violations = append(violations, apistatus.FieldViolation{ + Field: "spec.sourceInterfaceName", + Description: "sourceInterfaceName is not supported by the OpenConfig DNS model", + }) + } + for _, s := range spec.Servers { + if s.VrfName != "" { + violations = append(violations, apistatus.FieldViolation{ + Field: fmt.Sprintf("spec.servers[%s].vrfName", s.Address), + Description: "vrfName is not supported by the OpenConfig DNS model", + }) + } + } + if len(violations) > 0 { + return apistatus.NewUnsupportedFieldError(violations...) + } + return nil +} + +// Compile-time assertions. +var _ gnmiext.DataElement = (*DNS)(nil) + +// DNS represents the OpenConfig /system/dns container. +type DNS struct { + Config *DNSConfig `json:"config,omitempty"` + Servers *DNSServers `json:"servers,omitempty"` +} + +func (*DNS) XPath() string { return "openconfig-system:system/dns" } + +// DNSConfig holds the config container for DNS. +type DNSConfig struct { + Search []string `json:"search,omitempty"` +} + +// DNSServers holds the servers container for DNS. +type DNSServers struct { + Server gnmiext.List[string, *DNSServer] `json:"server,omitempty"` +} + +// DNSServer represents a single DNS server entry. +type DNSServer struct { + Address string `json:"address"` + Config *DNSServerConfig `json:"config,omitempty"` +} + +func (s *DNSServer) Key() string { return s.Address } + +// DNSServerConfig holds the config container for a DNS server. +type DNSServerConfig struct { + Address string `json:"address"` +} diff --git a/internal/provider/openconfig/dns_test.go b/internal/provider/openconfig/dns_test.go new file mode 100644 index 000000000..6f57e1b54 --- /dev/null +++ b/internal/provider/openconfig/dns_test.go @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package openconfig + +import ( + "bytes" + "encoding/json" + "os" + "testing" +) + +func Test_DNS_Payload(t *testing.T) { + servers := &DNSServers{} + address := "1.1.1.1" + servers.Server.Set(&DNSServer{Address: address, Config: &DNSServerConfig{Address: address}}) + + d := &DNS{ + Config: &DNSConfig{Search: []string{"example.com"}}, + Servers: servers, + } + + got, err := json.Marshal(d) + if err != nil { + t.Fatalf("json.Marshal() error = %v", err) + } + + data, err := os.ReadFile("testdata/dns.json") + if err != nil { + t.Fatalf("os.ReadFile() error = %v", err) + } + + var want bytes.Buffer + if err := json.Compact(&want, data); err != nil { + t.Fatalf("json.Compact() error = %v", err) + } + + if !bytes.Equal(want.Bytes(), got) { + t.Errorf("payload mismatch:\nwant: %s\n got: %s", want.Bytes(), got) + } +} diff --git a/internal/provider/openconfig/testdata/dns.json b/internal/provider/openconfig/testdata/dns.json new file mode 100644 index 000000000..4e69cfaea --- /dev/null +++ b/internal/provider/openconfig/testdata/dns.json @@ -0,0 +1,17 @@ +{ + "config": { + "search": [ + "example.com" + ] + }, + "servers": { + "server": [ + { + "address": "1.1.1.1", + "config": { + "address": "1.1.1.1" + } + } + ] + } +} diff --git a/test/gnmi/testdata/openconfig/dns.txt b/test/gnmi/testdata/openconfig/dns.txt new file mode 100644 index 000000000..31f77c9c7 --- /dev/null +++ b/test/gnmi/testdata/openconfig/dns.txt @@ -0,0 +1,42 @@ +# DNS Configuration +-- dns/dns -- +apiVersion: networking.metal.ironcore.dev/v1alpha1 +kind: DNS +metadata: + name: dns + namespace: default +spec: + deviceRef: + name: device + domain: example.com + servers: + - address: 8.8.8.8 + - address: 1.1.1.1 +-- state -- +{ + "openconfig-system:system": { + "dns": { + "config": { + "search": [ + "example.com" + ] + }, + "servers": { + "server": [ + { + "address": "8.8.8.8", + "config": { + "address": "8.8.8.8" + } + }, + { + "address": "1.1.1.1", + "config": { + "address": "1.1.1.1" + } + } + ] + } + } + } +}