-
Notifications
You must be signed in to change notification settings - Fork 672
/
accessors.go
366 lines (308 loc) · 10.2 KB
/
accessors.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
// Copyright Project Contour Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dag
import (
"fmt"
"strconv"
"strings"
"github.com/projectcontour/contour/internal/annotation"
"github.com/projectcontour/contour/internal/xds"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
)
// EnsureService looks for a Kubernetes service in the cache matching the provided
// namespace, name and port, and returns a DAG service for it. If a matching service
// cannot be found in the cache, an error is returned.
func (d *DAG) EnsureService(meta types.NamespacedName, port int, healthPort int, cache *KubernetesCache, enableExternalNameSvc bool) (*Service, error) {
svc, svcPort, err := cache.LookupService(meta, intstr.FromInt(port))
if err != nil {
return nil, err
}
healthSvcPort := svcPort
if healthPort != 0 && healthPort != port {
_, healthSvcPort, err = cache.LookupService(meta, intstr.FromInt(healthPort))
if err != nil {
return nil, err
}
}
err = validateExternalName(svc, enableExternalNameSvc)
if err != nil {
return nil, err
}
// There's no need to walk the DAG to look for a matching
// existing Service here. They're terminal nodes in the DAG
// so nothing is getting attached to them, and when used
// to generate an Envoy cluster any copy of this info will
// do. Doing a DAG walk to look for them is also very
// inefficient and can cause performance isuses at scale
// (see https://github.com/projectcontour/contour/issues/4058).
return &Service{
Weighted: WeightedService{
ServiceName: svc.Name,
ServiceNamespace: svc.Namespace,
ServicePort: svcPort,
HealthPort: healthSvcPort,
Weight: 1,
},
Protocol: upstreamProtocol(svc, svcPort),
MaxConnections: annotation.MaxConnections(svc),
MaxPendingRequests: annotation.MaxPendingRequests(svc),
MaxRequests: annotation.MaxRequests(svc),
MaxRetries: annotation.MaxRetries(svc),
ExternalName: externalName(svc),
}, nil
}
func validateExternalName(svc *v1.Service, enableExternalNameSvc bool) error {
// If this isn't an ExternalName Service, we're all good here.
en := externalName(svc)
if en == "" {
return nil
}
// If ExternalNames are disabled, then we don't want to add this to the DAG.
if !enableExternalNameSvc {
return fmt.Errorf("%s/%s is an ExternalName service, these are not currently enabled. See the config.enableExternalNameService config file setting", svc.Namespace, svc.Name)
}
// Check against a list of known localhost names, using a map to approximate a set.
// TODO(youngnick) This is a very porous hack, and we should probably look into doing a DNS
// lookup to check what the externalName resolves to, but I'm worried about the
// performance impact of doing one or more DNS lookups per DAG run, so we're
// going to go with a specific blocklist for now.
localhostNames := map[string]struct{}{
"localhost": {},
"localhost.localdomain": {},
"local.projectcontour.io": {},
}
_, localhost := localhostNames[en]
if localhost {
return fmt.Errorf("%s/%s is an ExternalName service that points to localhost, this is not allowed", svc.Namespace, svc.Name)
}
return nil
}
func upstreamProtocol(svc *v1.Service, port v1.ServicePort) string {
up := annotation.ParseUpstreamProtocols(svc.Annotations)
protocol := up[port.Name]
if protocol == "" {
protocol = up[strconv.Itoa(int(port.Port))]
}
return protocol
}
func externalName(svc *v1.Service) string {
if svc.Spec.Type != v1.ServiceTypeExternalName {
return ""
}
return svc.Spec.ExternalName
}
// GetSingleListener returns the sole listener with the specified protocol,
// or an error if there is not exactly one listener with that protocol.
func (d *DAG) GetSingleListener(protocol string) (*Listener, error) {
var res *Listener
for _, listener := range d.Listeners {
if listener.Protocol != protocol {
continue
}
if res != nil {
return nil, fmt.Errorf("more than one %s listener configured", strings.ToUpper(protocol))
}
res = listener
}
if res == nil {
return nil, fmt.Errorf("no %s listener configured", strings.ToUpper(protocol))
}
return res, nil
}
// GetSecureVirtualHost returns the secure virtual host in the DAG that
// matches the provided name, or nil if no matching secure virtual host
// is found.
func (d *DAG) GetSecureVirtualHost(listener, hostname string) *SecureVirtualHost {
return d.Listeners[listener].svhostsByName[hostname]
}
// EnsureSecureVirtualHost adds a secure virtual host with the provided
// name to the DAG if it does not already exist, and returns it.
func (d *DAG) EnsureSecureVirtualHost(listener, hostname string) *SecureVirtualHost {
if svh := d.GetSecureVirtualHost(listener, hostname); svh != nil {
return svh
}
svh := &SecureVirtualHost{
VirtualHost: VirtualHost{
Name: hostname,
},
}
d.Listeners[listener].SecureVirtualHosts = append(d.Listeners[listener].SecureVirtualHosts, svh)
d.Listeners[listener].svhostsByName[svh.Name] = svh
return svh
}
// GetVirtualHost returns the virtual host in the DAG that matches the
// provided name, or nil if no matching virtual host is found.
func (d *DAG) GetVirtualHost(listener, hostname string) *VirtualHost {
return d.Listeners[listener].vhostsByName[hostname]
}
// EnsureVirtualHost adds a virtual host with the provided name to the
// DAG if it does not already exist, and returns it.
func (d *DAG) EnsureVirtualHost(listener, hostname string) *VirtualHost {
if vhost := d.GetVirtualHost(listener, hostname); vhost != nil {
return vhost
}
vhost := &VirtualHost{
Name: hostname,
}
d.Listeners[listener].VirtualHosts = append(d.Listeners[listener].VirtualHosts, vhost)
d.Listeners[listener].vhostsByName[vhost.Name] = vhost
return vhost
}
func (d *DAG) GetClusters() []*Cluster {
var res []*Cluster
for _, listener := range d.Listeners {
if listener.TCPProxy != nil {
res = append(res, listener.TCPProxy.Clusters...)
}
for _, vhost := range listener.VirtualHosts {
for _, route := range vhost.Routes {
res = append(res, route.Clusters...)
for _, mp := range route.MirrorPolicies {
if mp.Cluster != nil {
res = append(res, mp.Cluster)
}
}
}
}
for _, vhost := range listener.SecureVirtualHosts {
for _, route := range vhost.Routes {
res = append(res, route.Clusters...)
for _, mp := range route.MirrorPolicies {
if mp.Cluster != nil {
res = append(res, mp.Cluster)
}
}
}
if vhost.TCPProxy != nil {
res = append(res, vhost.TCPProxy.Clusters...)
}
}
}
return res
}
func (d *DAG) GetDNSNameClusters() []*DNSNameCluster {
var res []*DNSNameCluster
for _, listener := range d.Listeners {
for _, svhost := range listener.SecureVirtualHosts {
for _, provider := range svhost.JWTProviders {
provider := provider
res = append(res, &provider.RemoteJWKS.Cluster)
}
}
}
return res
}
func (d *DAG) GetServiceClusters() []*ServiceCluster {
var res []*ServiceCluster
for _, cluster := range d.GetClusters() {
// We do not use EDS with clusters configured for ExternalName
// Services, so skip over returning these. We do not want to
// return extra endpoint resources in a snapshot that Envoy
// does not request. Especially with ADS, this is discouraged.
if len(cluster.Upstream.ExternalName) > 0 {
continue
}
// A Service has only one WeightedService entry. Fake up a
// ServiceCluster so that the visitor can pretend to not
// know this.
c := &ServiceCluster{
ClusterName: xds.ClusterLoadAssignmentName(
types.NamespacedName{
Name: cluster.Upstream.Weighted.ServiceName,
Namespace: cluster.Upstream.Weighted.ServiceNamespace,
},
cluster.Upstream.Weighted.ServicePort.Name),
Services: []WeightedService{
cluster.Upstream.Weighted,
},
}
res = append(res, c)
}
for _, ec := range d.ExtensionClusters {
res = append(res, &ec.Upstream)
}
return res
}
// GetExtensionClusters returns all extension clusters in the DAG.
func (d *DAG) GetExtensionClusters() map[string]*ExtensionCluster {
// TODO for DAG consumers, this should iterate
// over Listeners.SecureVirtualHosts and get
// AuthorizationServices.
res := map[string]*ExtensionCluster{}
for _, ec := range d.ExtensionClusters {
res[ec.Name] = ec
}
return res
}
func (d *DAG) GetSecrets() []*Secret {
var res []*Secret
for _, l := range d.Listeners {
for _, svh := range l.SecureVirtualHosts {
if svh.Secret != nil {
res = append(res, svh.Secret)
}
if svh.FallbackCertificate != nil {
res = append(res, svh.FallbackCertificate)
}
}
}
for _, c := range d.GetClusters() {
if c.ClientCertificate != nil {
res = append(res, c.ClientCertificate)
}
}
return res
}
// GetExtensionCluster returns the extension cluster in the DAG that
// matches the provided name, or nil if no matching extension cluster
// is found.
func (d *DAG) GetExtensionCluster(name string) *ExtensionCluster {
for _, ec := range d.ExtensionClusters {
if ec.Name == name {
return ec
}
}
return nil
}
func (d *DAG) GetVirtualHostRoutes() map[*VirtualHost][]*Route {
res := map[*VirtualHost][]*Route{}
for _, l := range d.Listeners {
for _, vhost := range l.VirtualHosts {
var routes []*Route
for _, r := range vhost.Routes {
routes = append(routes, r)
}
if len(routes) > 0 {
res[vhost] = routes
}
}
}
return res
}
func (d *DAG) GetSecureVirtualHostRoutes() map[*SecureVirtualHost][]*Route {
res := map[*SecureVirtualHost][]*Route{}
for _, l := range d.Listeners {
for _, vhost := range l.SecureVirtualHosts {
var routes []*Route
for _, r := range vhost.Routes {
routes = append(routes, r)
}
if len(routes) > 0 {
res[vhost] = routes
}
}
}
return res
}