Skip to content

Commit 4bc22ff

Browse files
committed
Support custom list of services to be added to /etc/hosts in cluster DNS operator - RFE-4145
1 parent 6598130 commit 4bc22ff

File tree

5 files changed

+252
-6
lines changed

5 files changed

+252
-6
lines changed

manifests/0000_70_dns-operator_00.crd.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,35 @@ spec:
177177
type: object
178178
type: array
179179
type: object
180+
nodeServices:
181+
description: |-
182+
nodeServices specifies K8s services for which entries should be added
183+
to /etc/hosts by the node resolver. These services will be added in addition to
184+
the default "image-registry.openshift-image-registry.svc" service.
185+
For each service reference, entries will be created using the format "<name>.<namespace>.svc"
186+
and an alias with the CLUSTER_DOMAIN suffix will also be added.
187+
items:
188+
description: DNSNodeService represents a Kubernetes service by name
189+
and namespace for node services.
190+
properties:
191+
name:
192+
description: name is the name of the service.
193+
maxLength: 63
194+
minLength: 1
195+
pattern: ^[a-z]([-a-z0-9]*[a-z0-9])?$
196+
type: string
197+
namespace:
198+
description: namespace is the namespace of the service.
199+
maxLength: 63
200+
minLength: 1
201+
pattern: ^[a-z]([-a-z0-9]*[a-z0-9])?$
202+
type: string
203+
required:
204+
- name
205+
- namespace
206+
type: object
207+
maxItems: 20
208+
type: array
180209
operatorLogLevel:
181210
default: Normal
182211
description: 'operatorLogLevel controls the logging level of the DNS

pkg/operator/controller/controller_dns_node_resolver_daemonset.go

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package controller
33
import (
44
"context"
55
"fmt"
6+
"strings"
67

78
"github.com/google/go-cmp/cmp"
89
"github.com/google/go-cmp/cmp/cmpopts"
@@ -23,11 +24,9 @@ import (
2324
)
2425

2526
const (
26-
// services is a comma- or space-delimited list of services for which
27-
// entries should be added to /etc/hosts. NOTE: For now, ensure these
28-
// are relative names; for each relative name, an alias with the
29-
// CLUSTER_DOMAIN suffix will also be added.
30-
services = "image-registry.openshift-image-registry.svc"
27+
// defaultService is the default service that should always be included
28+
// in the services list for the node resolver
29+
defaultService = "image-registry.openshift-image-registry.svc"
3130

3231
// workloadPartitioningManagement contains the management workload annotation
3332
workloadPartitioningManagement = "target.workload.openshift.io/management"
@@ -38,6 +37,27 @@ var (
3837
nodeResolverScript = manifests.NodeResolverScript()
3938
)
4039

40+
// buildServicesList combines the default service with additional services from the DNS spec.
41+
// The default service is always included first, followed by any additional services.
42+
func buildServicesList(dns *operatorv1.DNS) string {
43+
// Start with the default service
44+
services := []string{defaultService}
45+
46+
// Add any additional services from the spec
47+
if len(dns.Spec.NodeServices) > 0 {
48+
for _, service := range dns.Spec.NodeServices {
49+
// Build service name in format: name.namespace.svc
50+
if service.Name != "" && service.Namespace != "" {
51+
serviceName := service.Name + "." + service.Namespace + ".svc"
52+
services = append(services, serviceName)
53+
}
54+
}
55+
}
56+
57+
// Join all services with commas for the environment variable
58+
return strings.Join(services, ",")
59+
}
60+
4161
// ensureNodeResolverDaemonset ensures the node resolver daemonset exists if it
4262
// should or does not exist if it should not exist. Returns a Boolean
4363
// indicating whether the daemonset exists, the daemonset if it does exist, and
@@ -82,7 +102,7 @@ func desiredNodeResolverDaemonSet(dns *operatorv1.DNS, clusterIP, clusterDomain,
82102
maxUnavailable := intstr.FromString("33%")
83103
envs := []corev1.EnvVar{{
84104
Name: "SERVICES",
85-
Value: services,
105+
Value: buildServicesList(dns),
86106
}}
87107
if len(clusterIP) > 0 {
88108
envs = append(envs, corev1.EnvVar{

pkg/operator/controller/controller_dns_node_resolver_daemonset_test.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,71 @@ import (
1212
"k8s.io/apimachinery/pkg/util/intstr"
1313
)
1414

15+
// TestBuildServicesList tests the buildServicesList function with various DNS configurations
16+
func TestBuildServicesList(t *testing.T) {
17+
testCases := []struct {
18+
name string
19+
nodeServices []operatorv1.DNSNodeService
20+
expected string
21+
}{
22+
{
23+
name: "no additional services",
24+
nodeServices: nil,
25+
expected: "image-registry.openshift-image-registry.svc",
26+
},
27+
{
28+
name: "empty additional services slice",
29+
nodeServices: []operatorv1.DNSNodeService{},
30+
expected: "image-registry.openshift-image-registry.svc",
31+
},
32+
{
33+
name: "single additional service",
34+
nodeServices: []operatorv1.DNSNodeService{{Name: "my-service", Namespace: "my-namespace"}},
35+
expected: "image-registry.openshift-image-registry.svc,my-service.my-namespace.svc",
36+
},
37+
{
38+
name: "multiple additional services",
39+
nodeServices: []operatorv1.DNSNodeService{{Name: "service1", Namespace: "namespace1"}, {Name: "service2", Namespace: "namespace2"}, {Name: "service3", Namespace: "namespace3"}},
40+
expected: "image-registry.openshift-image-registry.svc,service1.namespace1.svc,service2.namespace2.svc,service3.namespace3.svc",
41+
},
42+
{
43+
name: "service with empty name is filtered out",
44+
nodeServices: []operatorv1.DNSNodeService{{Name: "", Namespace: "my-namespace"}},
45+
expected: "image-registry.openshift-image-registry.svc",
46+
},
47+
{
48+
name: "service with empty namespace is filtered out",
49+
nodeServices: []operatorv1.DNSNodeService{{Name: "my-service", Namespace: ""}},
50+
expected: "image-registry.openshift-image-registry.svc",
51+
},
52+
{
53+
name: "mixed valid and invalid services",
54+
nodeServices: []operatorv1.DNSNodeService{{Name: "service1", Namespace: "namespace1"}, {Name: "", Namespace: "namespace2"}, {Name: "service3", Namespace: "namespace3"}},
55+
expected: "image-registry.openshift-image-registry.svc,service1.namespace1.svc,service3.namespace3.svc",
56+
},
57+
{
58+
name: "all invalid services filtered out",
59+
nodeServices: []operatorv1.DNSNodeService{{Name: "", Namespace: "namespace1"}, {Name: "service2", Namespace: ""}},
60+
expected: "image-registry.openshift-image-registry.svc",
61+
},
62+
}
63+
64+
for _, tc := range testCases {
65+
t.Run(tc.name, func(t *testing.T) {
66+
dns := &operatorv1.DNS{
67+
Spec: operatorv1.DNSSpec{
68+
NodeServices: tc.nodeServices,
69+
},
70+
}
71+
72+
result := buildServicesList(dns)
73+
if result != tc.expected {
74+
t.Errorf("expected %q, got %q", tc.expected, result)
75+
}
76+
})
77+
}
78+
}
79+
1580
// TestDesiredNodeResolverDaemonset verifies that desiredNodeResolverDaemonSet
1681
// returns the expected daemonset.
1782
func TestDesiredNodeResolverDaemonset(t *testing.T) {
@@ -53,6 +118,79 @@ func TestDesiredNodeResolverDaemonset(t *testing.T) {
53118
} else if clusterDomain != domain {
54119
t.Errorf("expected CLUSTER_DOMAIN env for dns node resolver image %q, got %q", clusterDomain, domain)
55120
}
121+
services, ok := envs["SERVICES"]
122+
if !ok {
123+
t.Errorf("SERVICES env for dns node resolver image not found")
124+
} else if services != "image-registry.openshift-image-registry.svc" {
125+
t.Errorf("expected SERVICES env for dns node resolver image %q, got %q", "image-registry.openshift-image-registry.svc", services)
126+
}
127+
}
128+
}
129+
130+
// TestDesiredNodeResolverDaemonsetWithNodeServices verifies that desiredNodeResolverDaemonSet
131+
// correctly handles node services in the SERVICES environment variable.
132+
func TestDesiredNodeResolverDaemonsetWithNodeServices(t *testing.T) {
133+
clusterDomain := "cluster.local"
134+
clusterIP := "172.30.77.10"
135+
openshiftCLIImage := "openshift/origin-cli:test"
136+
137+
testCases := []struct {
138+
name string
139+
nodeServices []operatorv1.DNSNodeService
140+
expectedServices string
141+
}{
142+
{
143+
name: "with single node service",
144+
nodeServices: []operatorv1.DNSNodeService{{Name: "my-api", Namespace: "my-namespace"}},
145+
expectedServices: "image-registry.openshift-image-registry.svc,my-api.my-namespace.svc",
146+
},
147+
{
148+
name: "with multiple node services",
149+
nodeServices: []operatorv1.DNSNodeService{{Name: "service1", Namespace: "ns1"}, {Name: "service2", Namespace: "ns2"}},
150+
expectedServices: "image-registry.openshift-image-registry.svc,service1.ns1.svc,service2.ns2.svc",
151+
},
152+
{
153+
name: "with valid services only",
154+
nodeServices: []operatorv1.DNSNodeService{{Name: "clean-service", Namespace: "namespace"}, {Name: "another-service", Namespace: "ns"}},
155+
expectedServices: "image-registry.openshift-image-registry.svc,clean-service.namespace.svc,another-service.ns.svc",
156+
},
157+
}
158+
159+
for _, tc := range testCases {
160+
t.Run(tc.name, func(t *testing.T) {
161+
dns := &operatorv1.DNS{
162+
ObjectMeta: metav1.ObjectMeta{
163+
Name: DefaultDNSController,
164+
},
165+
Spec: operatorv1.DNSSpec{
166+
NodeServices: tc.nodeServices,
167+
},
168+
}
169+
170+
if want, ds, err := desiredNodeResolverDaemonSet(dns, clusterIP, clusterDomain, openshiftCLIImage); err != nil {
171+
t.Errorf("invalid node resolver daemonset: %v", err)
172+
} else if !want {
173+
t.Error("expected the node resolver daemonset desired to be true, got false")
174+
} else if len(ds.Spec.Template.Spec.Containers) != 1 {
175+
t.Errorf("expected number of daemonset containers 1, got %d", len(ds.Spec.Template.Spec.Containers))
176+
} else {
177+
c := ds.Spec.Template.Spec.Containers[0]
178+
179+
// Check environment variables
180+
envs := map[string]string{}
181+
for _, e := range c.Env {
182+
envs[e.Name] = e.Value
183+
}
184+
185+
// Verify SERVICES environment variable contains expected services
186+
services, ok := envs["SERVICES"]
187+
if !ok {
188+
t.Errorf("SERVICES env for dns node resolver image not found")
189+
} else if services != tc.expectedServices {
190+
t.Errorf("expected SERVICES env for dns node resolver image %q, got %q", tc.expectedServices, services)
191+
}
192+
}
193+
})
56194
}
57195
}
58196

vendor/github.com/openshift/api/operator/v1/types_dns.go

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/openshift/api/operator/v1/zz_generated.crd-manifests/0000_70_dns_00_dnses.crd.yaml

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)