Skip to content

Commit 6bf1db7

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

File tree

5 files changed

+208
-6
lines changed

5 files changed

+208
-6
lines changed

manifests/0000_70_dns-operator_00.crd.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,18 @@ 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+
Each service should be a relative name following the format "<service>.<namespace>.svc";
186+
for each relative name, an alias with the CLUSTER_DOMAIN suffix will also be added.
187+
items:
188+
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?\.[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.svc)?$
189+
type: string
190+
maxItems: 20
191+
type: array
180192
operatorLogLevel:
181193
default: Normal
182194
description: 'operatorLogLevel controls the logging level of the DNS

pkg/operator/controller/controller_dns_node_resolver_daemonset.go

Lines changed: 25 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,26 @@ 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+
// Trim whitespace and add non-empty services
50+
if trimmed := strings.TrimSpace(service); trimmed != "" {
51+
services = append(services, trimmed)
52+
}
53+
}
54+
}
55+
56+
// Join all services with commas for the environment variable
57+
return strings.Join(services, ",")
58+
}
59+
4160
// ensureNodeResolverDaemonset ensures the node resolver daemonset exists if it
4261
// should or does not exist if it should not exist. Returns a Boolean
4362
// indicating whether the daemonset exists, the daemonset if it does exist, and
@@ -82,7 +101,7 @@ func desiredNodeResolverDaemonSet(dns *operatorv1.DNS, clusterIP, clusterDomain,
82101
maxUnavailable := intstr.FromString("33%")
83102
envs := []corev1.EnvVar{{
84103
Name: "SERVICES",
85-
Value: services,
104+
Value: buildServicesList(dns),
86105
}}
87106
if len(clusterIP) > 0 {
88107
envs = append(envs, corev1.EnvVar{

pkg/operator/controller/controller_dns_node_resolver_daemonset_test.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,81 @@ 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 []string
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: []string{},
30+
expected: "image-registry.openshift-image-registry.svc",
31+
},
32+
{
33+
name: "single additional service",
34+
nodeServices: []string{"my-service.my-namespace.svc"},
35+
expected: "image-registry.openshift-image-registry.svc,my-service.my-namespace.svc",
36+
},
37+
{
38+
name: "multiple additional services",
39+
nodeServices: []string{"service1.namespace1.svc", "service2.namespace2.svc", "service3.namespace3.svc"},
40+
expected: "image-registry.openshift-image-registry.svc,service1.namespace1.svc,service2.namespace2.svc,service3.namespace3.svc",
41+
},
42+
{
43+
name: "service with whitespace gets trimmed",
44+
nodeServices: []string{" my-service.my-namespace.svc "},
45+
expected: "image-registry.openshift-image-registry.svc,my-service.my-namespace.svc",
46+
},
47+
{
48+
name: "mixed clean and whitespace services",
49+
nodeServices: []string{"service1.namespace1.svc", " service2.namespace2.svc ", "service3.namespace3.svc"},
50+
expected: "image-registry.openshift-image-registry.svc,service1.namespace1.svc,service2.namespace2.svc,service3.namespace3.svc",
51+
},
52+
{
53+
name: "empty string services are filtered out",
54+
nodeServices: []string{"service1.namespace1.svc", "", "service2.namespace2.svc"},
55+
expected: "image-registry.openshift-image-registry.svc,service1.namespace1.svc,service2.namespace2.svc",
56+
},
57+
{
58+
name: "whitespace-only services are filtered out",
59+
nodeServices: []string{"service1.namespace1.svc", " ", "service2.namespace2.svc"},
60+
expected: "image-registry.openshift-image-registry.svc,service1.namespace1.svc,service2.namespace2.svc",
61+
},
62+
{
63+
name: "all whitespace/empty services filtered out",
64+
nodeServices: []string{"", " ", "\t"},
65+
expected: "image-registry.openshift-image-registry.svc",
66+
},
67+
{
68+
name: "service without .svc suffix",
69+
nodeServices: []string{"my-service.my-namespace"},
70+
expected: "image-registry.openshift-image-registry.svc,my-service.my-namespace",
71+
},
72+
}
73+
74+
for _, tc := range testCases {
75+
t.Run(tc.name, func(t *testing.T) {
76+
dns := &operatorv1.DNS{
77+
Spec: operatorv1.DNSSpec{
78+
NodeServices: tc.nodeServices,
79+
},
80+
}
81+
82+
result := buildServicesList(dns)
83+
if result != tc.expected {
84+
t.Errorf("expected %q, got %q", tc.expected, result)
85+
}
86+
})
87+
}
88+
}
89+
1590
// TestDesiredNodeResolverDaemonset verifies that desiredNodeResolverDaemonSet
1691
// returns the expected daemonset.
1792
func TestDesiredNodeResolverDaemonset(t *testing.T) {
@@ -53,6 +128,79 @@ func TestDesiredNodeResolverDaemonset(t *testing.T) {
53128
} else if clusterDomain != domain {
54129
t.Errorf("expected CLUSTER_DOMAIN env for dns node resolver image %q, got %q", clusterDomain, domain)
55130
}
131+
services, ok := envs["SERVICES"]
132+
if !ok {
133+
t.Errorf("SERVICES env for dns node resolver image not found")
134+
} else if services != "image-registry.openshift-image-registry.svc" {
135+
t.Errorf("expected SERVICES env for dns node resolver image %q, got %q", "image-registry.openshift-image-registry.svc", services)
136+
}
137+
}
138+
}
139+
140+
// TestDesiredNodeResolverDaemonsetWithNodeServices verifies that desiredNodeResolverDaemonSet
141+
// correctly handles node services in the SERVICES environment variable.
142+
func TestDesiredNodeResolverDaemonsetWithNodeServices(t *testing.T) {
143+
clusterDomain := "cluster.local"
144+
clusterIP := "172.30.77.10"
145+
openshiftCLIImage := "openshift/origin-cli:test"
146+
147+
testCases := []struct {
148+
name string
149+
nodeServices []string
150+
expectedServices string
151+
}{
152+
{
153+
name: "with single node service",
154+
nodeServices: []string{"my-api.my-namespace.svc"},
155+
expectedServices: "image-registry.openshift-image-registry.svc,my-api.my-namespace.svc",
156+
},
157+
{
158+
name: "with multiple node services",
159+
nodeServices: []string{"service1.ns1.svc", "service2.ns2.svc"},
160+
expectedServices: "image-registry.openshift-image-registry.svc,service1.ns1.svc,service2.ns2.svc",
161+
},
162+
{
163+
name: "with services containing whitespace",
164+
nodeServices: []string{" clean-service.namespace.svc ", "another-service.ns.svc"},
165+
expectedServices: "image-registry.openshift-image-registry.svc,clean-service.namespace.svc,another-service.ns.svc",
166+
},
167+
}
168+
169+
for _, tc := range testCases {
170+
t.Run(tc.name, func(t *testing.T) {
171+
dns := &operatorv1.DNS{
172+
ObjectMeta: metav1.ObjectMeta{
173+
Name: DefaultDNSController,
174+
},
175+
Spec: operatorv1.DNSSpec{
176+
NodeServices: tc.nodeServices,
177+
},
178+
}
179+
180+
if want, ds, err := desiredNodeResolverDaemonSet(dns, clusterIP, clusterDomain, openshiftCLIImage); err != nil {
181+
t.Errorf("invalid node resolver daemonset: %v", err)
182+
} else if !want {
183+
t.Error("expected the node resolver daemonset desired to be true, got false")
184+
} else if len(ds.Spec.Template.Spec.Containers) != 1 {
185+
t.Errorf("expected number of daemonset containers 1, got %d", len(ds.Spec.Template.Spec.Containers))
186+
} else {
187+
c := ds.Spec.Template.Spec.Containers[0]
188+
189+
// Check environment variables
190+
envs := map[string]string{}
191+
for _, e := range c.Env {
192+
envs[e.Name] = e.Value
193+
}
194+
195+
// Verify SERVICES environment variable contains expected services
196+
services, ok := envs["SERVICES"]
197+
if !ok {
198+
t.Errorf("SERVICES env for dns node resolver image not found")
199+
} else if services != tc.expectedServices {
200+
t.Errorf("expected SERVICES env for dns node resolver image %q, got %q", tc.expectedServices, services)
201+
}
202+
}
203+
})
56204
}
57205
}
58206

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

Lines changed: 11 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: 12 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)