Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions internal/provider/openconfig/dns.go
Original file line number Diff line number Diff line change
@@ -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"`
}
41 changes: 41 additions & 0 deletions internal/provider/openconfig/dns_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
17 changes: 17 additions & 0 deletions internal/provider/openconfig/testdata/dns.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"config": {
"search": [
"example.com"
]
},
"servers": {
"server": [
{
"address": "1.1.1.1",
"config": {
"address": "1.1.1.1"
}
}
]
}
}
42 changes: 42 additions & 0 deletions test/gnmi/testdata/openconfig/dns.txt
Original file line number Diff line number Diff line change
@@ -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"
}
}
]
}
}
}
}
Loading