• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubernetes-sigs / external-dns / 14923095442

09 May 2025 06:43AM UTC coverage: 72.681% (+0.2%) from 72.504%
14923095442

Pull #5368

github

ivankatliarchuk
fix(log testing): re-use logger library testing functionality

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>
Pull Request #5368: fix(log testing): re-use logger library testing functionality

0 of 31 new or added lines in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

14877 of 20469 relevant lines covered (72.68%)

690.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.3
/source/openshift_route.go
1
/*
2
Copyright 2017 The Kubernetes Authors.
3

4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    http://www.apache.org/licenses/LICENSE-2.0
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package source
18

19
import (
20
        "context"
21
        "fmt"
22
        "sort"
23
        "text/template"
24
        "time"
25

26
        routev1 "github.com/openshift/api/route/v1"
27
        versioned "github.com/openshift/client-go/route/clientset/versioned"
28
        extInformers "github.com/openshift/client-go/route/informers/externalversions"
29
        routeInformer "github.com/openshift/client-go/route/informers/externalversions/route/v1"
30
        log "github.com/sirupsen/logrus"
31
        corev1 "k8s.io/api/core/v1"
32
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
33
        "k8s.io/apimachinery/pkg/labels"
34
        "k8s.io/client-go/tools/cache"
35

36
        "sigs.k8s.io/external-dns/endpoint"
37
)
38

39
// ocpRouteSource is an implementation of Source for OpenShift Route objects.
40
// The Route implementation will use the Route spec.host field for the hostname,
41
// and the Route status' canonicalHostname field as the target.
42
// The targetAnnotationKey can be used to explicitly set an alternative
43
// endpoint, if desired.
44
type ocpRouteSource struct {
45
        client                   versioned.Interface
46
        namespace                string
47
        annotationFilter         string
48
        fqdnTemplate             *template.Template
49
        combineFQDNAnnotation    bool
50
        ignoreHostnameAnnotation bool
51
        routeInformer            routeInformer.RouteInformer
52
        labelSelector            labels.Selector
53
        ocpRouterName            string
54
}
55

56
// NewOcpRouteSource creates a new ocpRouteSource with the given config.
57
func NewOcpRouteSource(
58
        ctx context.Context,
59
        ocpClient versioned.Interface,
60
        namespace string,
61
        annotationFilter string,
62
        fqdnTemplate string,
63
        combineFQDNAnnotation bool,
64
        ignoreHostnameAnnotation bool,
65
        labelSelector labels.Selector,
66
        ocpRouterName string,
67
) (Source, error) {
16✔
68
        tmpl, err := parseTemplate(fqdnTemplate)
16✔
69
        if err != nil {
17✔
70
                return nil, err
1✔
71
        }
1✔
72

73
        // Use a shared informer to listen for add/update/delete of Routes in the specified namespace.
74
        // Set resync period to 0, to prevent processing when nothing has changed.
75
        informerFactory := extInformers.NewFilteredSharedInformerFactory(ocpClient, 0*time.Second, namespace, nil)
15✔
76
        informer := informerFactory.Route().V1().Routes()
15✔
77

15✔
78
        // Add default resource event handlers to properly initialize informer.
15✔
79
        informer.Informer().AddEventHandler(
15✔
80
                cache.ResourceEventHandlerFuncs{
15✔
81
                        AddFunc: func(obj interface{}) {
26✔
82
                        },
11✔
83
                },
84
        )
85

86
        informerFactory.Start(ctx.Done())
15✔
87

15✔
88
        // wait for the local cache to be populated.
15✔
89
        if err := waitForCacheSync(context.Background(), informerFactory); err != nil {
15✔
90
                return nil, err
×
91
        }
×
92

93
        return &ocpRouteSource{
15✔
94
                client:                   ocpClient,
15✔
95
                namespace:                namespace,
15✔
96
                annotationFilter:         annotationFilter,
15✔
97
                fqdnTemplate:             tmpl,
15✔
98
                combineFQDNAnnotation:    combineFQDNAnnotation,
15✔
99
                ignoreHostnameAnnotation: ignoreHostnameAnnotation,
15✔
100
                routeInformer:            informer,
15✔
101
                labelSelector:            labelSelector,
15✔
102
                ocpRouterName:            ocpRouterName,
15✔
103
        }, nil
15✔
104
}
105

106
func (ors *ocpRouteSource) AddEventHandler(ctx context.Context, handler func()) {
×
107
        log.Debug("Adding event handler for openshift route")
×
108

×
109
        // Right now there is no way to remove event handler from informer, see:
×
110
        // https://github.com/kubernetes/kubernetes/issues/79610
×
111
        ors.routeInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
×
112
}
×
113

114
// Endpoints returns endpoint objects for each host-target combination that should be processed.
115
// Retrieves all OpenShift Route resources on all namespaces, unless an explicit namespace
116
// is specified in ocpRouteSource.
117
func (ors *ocpRouteSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) {
11✔
118
        ocpRoutes, err := ors.routeInformer.Lister().Routes(ors.namespace).List(ors.labelSelector)
11✔
119
        if err != nil {
11✔
120
                return nil, err
×
121
        }
×
122

123
        ocpRoutes, err = ors.filterByAnnotations(ocpRoutes)
11✔
124
        if err != nil {
11✔
125
                return nil, err
×
126
        }
×
127

128
        endpoints := []*endpoint.Endpoint{}
11✔
129

11✔
130
        for _, ocpRoute := range ocpRoutes {
20✔
131
                // Check controller annotation to see if we are responsible.
9✔
132
                controller, ok := ocpRoute.Annotations[controllerAnnotationKey]
9✔
133
                if ok && controller != controllerAnnotationValue {
10✔
134
                        log.Debugf("Skipping OpenShift Route %s/%s because controller value does not match, found: %s, required: %s",
1✔
135
                                ocpRoute.Namespace, ocpRoute.Name, controller, controllerAnnotationValue)
1✔
136
                        continue
1✔
137
                }
138

139
                orEndpoints := ors.endpointsFromOcpRoute(ocpRoute, ors.ignoreHostnameAnnotation)
8✔
140

8✔
141
                // apply template if host is missing on OpenShift Route
8✔
142
                if (ors.combineFQDNAnnotation || len(orEndpoints) == 0) && ors.fqdnTemplate != nil {
10✔
143
                        oEndpoints, err := ors.endpointsFromTemplate(ocpRoute)
2✔
144
                        if err != nil {
2✔
145
                                return nil, err
×
146
                        }
×
147

148
                        if ors.combineFQDNAnnotation {
2✔
149
                                orEndpoints = append(orEndpoints, oEndpoints...)
×
150
                        } else {
2✔
151
                                orEndpoints = oEndpoints
2✔
152
                        }
2✔
153
                }
154

155
                if len(orEndpoints) == 0 {
10✔
156
                        log.Debugf("No endpoints could be generated from OpenShift Route %s/%s", ocpRoute.Namespace, ocpRoute.Name)
2✔
157
                        continue
2✔
158
                }
159

160
                log.Debugf("Endpoints generated from OpenShift Route: %s/%s: %v", ocpRoute.Namespace, ocpRoute.Name, orEndpoints)
6✔
161
                endpoints = append(endpoints, orEndpoints...)
6✔
162
        }
163

164
        for _, ep := range endpoints {
17✔
165
                sort.Sort(ep.Targets)
6✔
166
        }
6✔
167

168
        return endpoints, nil
11✔
169
}
170

171
func (ors *ocpRouteSource) endpointsFromTemplate(ocpRoute *routev1.Route) ([]*endpoint.Endpoint, error) {
2✔
172
        hostnames, err := execTemplate(ors.fqdnTemplate, ocpRoute)
2✔
173
        if err != nil {
2✔
174
                return nil, err
×
175
        }
×
176

177
        resource := fmt.Sprintf("route/%s/%s", ocpRoute.Namespace, ocpRoute.Name)
2✔
178

2✔
179
        ttl := getTTLFromAnnotations(ocpRoute.Annotations, resource)
2✔
180

2✔
181
        targets := getTargetsFromTargetAnnotation(ocpRoute.Annotations)
2✔
182
        if len(targets) == 0 {
4✔
183
                targetsFromRoute, _ := ors.getTargetsFromRouteStatus(ocpRoute.Status)
2✔
184
                targets = targetsFromRoute
2✔
185
        }
2✔
186

187
        providerSpecific, setIdentifier := getProviderSpecificAnnotations(ocpRoute.Annotations)
2✔
188

2✔
189
        var endpoints []*endpoint.Endpoint
2✔
190
        for _, hostname := range hostnames {
4✔
191
                endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific, setIdentifier, resource)...)
2✔
192
        }
2✔
193
        return endpoints, nil
2✔
194
}
195

196
func (ors *ocpRouteSource) filterByAnnotations(ocpRoutes []*routev1.Route) ([]*routev1.Route, error) {
11✔
197
        labelSelector, err := metav1.ParseToLabelSelector(ors.annotationFilter)
11✔
198
        if err != nil {
11✔
199
                return nil, err
×
200
        }
×
201
        selector, err := metav1.LabelSelectorAsSelector(labelSelector)
11✔
202
        if err != nil {
11✔
203
                return nil, err
×
204
        }
×
205

206
        // empty filter returns original list
207
        if selector.Empty() {
22✔
208
                return ocpRoutes, nil
11✔
209
        }
11✔
210

211
        filteredList := []*routev1.Route{}
×
212

×
213
        for _, ocpRoute := range ocpRoutes {
×
214
                // convert the Route's annotations to an equivalent label selector
×
215
                annotations := labels.Set(ocpRoute.Annotations)
×
216

×
217
                // include ocpRoute if its annotations match the selector
×
218
                if selector.Matches(annotations) {
×
219
                        filteredList = append(filteredList, ocpRoute)
×
220
                }
×
221
        }
222

223
        return filteredList, nil
×
224
}
225

226
// endpointsFromOcpRoute extracts the endpoints from a OpenShift Route object
227
func (ors *ocpRouteSource) endpointsFromOcpRoute(ocpRoute *routev1.Route, ignoreHostnameAnnotation bool) []*endpoint.Endpoint {
8✔
228
        var endpoints []*endpoint.Endpoint
8✔
229

8✔
230
        resource := fmt.Sprintf("route/%s/%s", ocpRoute.Namespace, ocpRoute.Name)
8✔
231

8✔
232
        ttl := getTTLFromAnnotations(ocpRoute.Annotations, resource)
8✔
233

8✔
234
        targets := getTargetsFromTargetAnnotation(ocpRoute.Annotations)
8✔
235
        targetsFromRoute, host := ors.getTargetsFromRouteStatus(ocpRoute.Status)
8✔
236

8✔
237
        if len(targets) == 0 {
14✔
238
                targets = targetsFromRoute
6✔
239
        }
6✔
240

241
        providerSpecific, setIdentifier := getProviderSpecificAnnotations(ocpRoute.Annotations)
8✔
242

8✔
243
        if host != "" {
14✔
244
                endpoints = append(endpoints, endpointsForHostname(host, targets, ttl, providerSpecific, setIdentifier, resource)...)
6✔
245
        }
6✔
246

247
        // Skip endpoints if we do not want entries from annotations
248
        if !ignoreHostnameAnnotation {
16✔
249
                hostnameList := getHostnamesFromAnnotations(ocpRoute.Annotations)
8✔
250
                for _, hostname := range hostnameList {
8✔
251
                        endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific, setIdentifier, resource)...)
×
252
                }
×
253
        }
254
        return endpoints
8✔
255
}
256

257
// getTargetsFromRouteStatus returns the router's canonical hostname and host
258
// either for the given router if it admitted the route
259
// or for the first (in the status list) router that admitted the route.
260
func (ors *ocpRouteSource) getTargetsFromRouteStatus(status routev1.RouteStatus) (endpoint.Targets, string) {
10✔
261
        for _, ing := range status.Ingress {
25✔
262
                // if this Ingress didn't admit the route or it doesn't have the canonical hostname, then ignore it
15✔
263
                if ingressConditionStatus(&ing, routev1.RouteAdmitted) != corev1.ConditionTrue || ing.RouterCanonicalHostname == "" {
22✔
264
                        continue
7✔
265
                }
266

267
                // if the router name is specified for the Route source and it matches the route's ingress name, then return it
268
                if ors.ocpRouterName != "" && ors.ocpRouterName == ing.RouterName {
10✔
269
                        return endpoint.Targets{ing.RouterCanonicalHostname}, ing.Host
2✔
270
                }
2✔
271

272
                // if the router name is not specified in the Route source then return the first ingress
273
                if ors.ocpRouterName == "" {
10✔
274
                        return endpoint.Targets{ing.RouterCanonicalHostname}, ing.Host
4✔
275
                }
4✔
276
        }
277
        return endpoint.Targets{}, ""
4✔
278
}
279

280
func ingressConditionStatus(ingress *routev1.RouteIngress, t routev1.RouteIngressConditionType) corev1.ConditionStatus {
15✔
281
        for _, condition := range ingress.Conditions {
30✔
282
                if t != condition.Type {
15✔
283
                        continue
×
284
                }
285
                return condition.Status
15✔
286
        }
UNCOV
287
        return corev1.ConditionUnknown
×
288
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc