-
Notifications
You must be signed in to change notification settings - Fork 303
/
url_maps.go
266 lines (245 loc) · 7.86 KB
/
url_maps.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
/*
Copyright 2018 The Kubernetes 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 loadbalancers
import (
"crypto/md5"
"encoding/hex"
"fmt"
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud"
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/ingress-gce/pkg/composite"
"k8s.io/ingress-gce/pkg/utils"
"k8s.io/klog"
)
const (
// The gce api uses the name of a path rule to match a host rule.
hostRulePrefix = "host"
)
// ensureComputeURLMap retrieves the current URLMap and overwrites it if incorrect. If the resource
// does not exist, the map is created.
func (l *L7) ensureComputeURLMap() error {
if l.runtimeInfo.UrlMap == nil {
return fmt.Errorf("cannot create urlmap without internal representation")
}
// Every update replaces the entire urlmap.
// Use an empty name parameter since we only care about the scope
// TODO: (shance) refactor this so we don't need an empty arg
key, err := l.CreateKey("")
if err != nil {
return err
}
expectedMap := toCompositeURLMap(l.Name, l.runtimeInfo.UrlMap, l.namer, key)
key.Name = expectedMap.Name
expectedMap.Version = l.Versions().UrlMap
currentMap, err := composite.GetUrlMap(l.cloud, key, expectedMap.Version)
if utils.IgnoreHTTPNotFound(err) != nil {
return err
}
if currentMap == nil {
klog.V(3).Infof("Creating URLMap %q", expectedMap.Name)
if err := composite.CreateUrlMap(l.cloud, key, expectedMap); err != nil {
return fmt.Errorf("CreateUrlMap: %v", err)
}
l.um = expectedMap
return nil
}
if mapsEqual(currentMap, expectedMap) {
klog.V(4).Infof("URLMap for %q is unchanged", l.Name)
l.um = currentMap
return nil
}
klog.V(3).Infof("Updating URLMap for %q", l.Name)
expectedMap.Fingerprint = currentMap.Fingerprint
if err := composite.UpdateUrlMap(l.cloud, key, expectedMap); err != nil {
return fmt.Errorf("UpdateURLMap: %v", err)
}
l.um = expectedMap
return nil
}
// getBackendNames returns the names of backends in this L7 urlmap.
func getBackendNames(computeURLMap *composite.UrlMap) ([]string, error) {
beNames := sets.NewString()
for _, pathMatcher := range computeURLMap.PathMatchers {
name, err := utils.KeyName(pathMatcher.DefaultService)
if err != nil {
return nil, err
}
beNames.Insert(name)
for _, pathRule := range pathMatcher.PathRules {
name, err = utils.KeyName(pathRule.Service)
if err != nil {
return nil, err
}
beNames.Insert(name)
}
}
// The default Service recorded in the urlMap is a link to the backend.
// Note that this can either be user specified, or the L7 controller's
// global default.
name, err := utils.KeyName(computeURLMap.DefaultService)
if err != nil {
return nil, err
}
beNames.Insert(name)
return beNames.List(), nil
}
// mapsEqual compares the structure of two compute.UrlMaps.
// The service strings are parsed and compared as resource paths (such as
// "global/backendServices/my-service") to ignore variables: endpoint, version, and project.
func mapsEqual(a, b *composite.UrlMap) bool {
if !utils.EqualResourcePaths(a.DefaultService, b.DefaultService) {
return false
}
if len(a.HostRules) != len(b.HostRules) {
return false
}
for i := range a.HostRules {
a := a.HostRules[i]
b := b.HostRules[i]
if a.Description != b.Description {
return false
}
if len(a.Hosts) != len(b.Hosts) {
return false
}
for i := range a.Hosts {
if a.Hosts[i] != b.Hosts[i] {
return false
}
}
if a.PathMatcher != b.PathMatcher {
return false
}
}
if len(a.PathMatchers) != len(b.PathMatchers) {
return false
}
for i := range a.PathMatchers {
a := a.PathMatchers[i]
b := b.PathMatchers[i]
if !utils.EqualResourcePaths(a.DefaultService, b.DefaultService) {
return false
}
if a.Description != b.Description {
return false
}
if a.Name != b.Name {
return false
}
if len(a.PathRules) != len(b.PathRules) {
return false
}
for i := range a.PathRules {
a := a.PathRules[i]
b := b.PathRules[i]
if len(a.Paths) != len(b.Paths) {
return false
}
for i := range a.Paths {
if a.Paths[i] != b.Paths[i] {
return false
}
}
if !utils.EqualResourcePaths(a.Service, b.Service) {
return false
}
}
}
return true
}
// toCompositeURLMap translates the given hostname: endpoint->port mapping into a gce url map.
//
// HostRule: Conceptually contains all PathRules for a given host.
// PathMatcher: Associates a path rule with a host rule. Mostly an optimization.
// PathRule: Maps a single path regex to a backend.
//
// The GCE url map allows multiple hosts to share url->backend mappings without duplication, eg:
// Host: foo(PathMatcher1), bar(PathMatcher1,2)
// PathMatcher1:
// /a -> b1
// /b -> b2
// PathMatcher2:
// /c -> b1
// This leads to a lot of complexity in the common case, where all we want is a mapping of
// host->{/path: backend}.
//
// Consider some alternatives:
// 1. Using a single backend per PathMatcher:
// Host: foo(PathMatcher1,3) bar(PathMatcher1,2,3)
// PathMatcher1:
// /a -> b1
// PathMatcher2:
// /c -> b1
// PathMatcher3:
// /b -> b2
// 2. Using a single host per PathMatcher:
// Host: foo(PathMatcher1)
// PathMatcher1:
// /a -> b1
// /b -> b2
// Host: bar(PathMatcher2)
// PathMatcher2:
// /a -> b1
// /b -> b2
// /c -> b1
// In the context of kubernetes services, 2 makes more sense, because we
// rarely want to lookup backends (service:nodeport). When a service is
// deleted, we need to find all host PathMatchers that have the backend
// and remove the mapping. When a new path is added to a host (happens
// more frequently than service deletion) we just need to lookup the 1
// pathmatcher of the host.
func toCompositeURLMap(lbName string, g *utils.GCEURLMap, namer *utils.Namer, key *meta.Key) *composite.UrlMap {
defaultBackendName := g.DefaultBackend.BackendName(namer)
key.Name = defaultBackendName
resourceID := cloud.ResourceID{ProjectID: "", Resource: "backendServices", Key: key}
m := &composite.UrlMap{
Name: namer.UrlMap(lbName),
DefaultService: resourceID.ResourcePath(),
}
for _, hostRule := range g.HostRules {
// Create a host rule
// Create a path matcher
// Add all given endpoint:backends to pathRules in path matcher
pmName := getNameForPathMatcher(hostRule.Hostname)
m.HostRules = append(m.HostRules, &composite.HostRule{
Hosts: []string{hostRule.Hostname},
PathMatcher: pmName,
})
pathMatcher := &composite.PathMatcher{
Name: pmName,
DefaultService: m.DefaultService,
PathRules: []*composite.PathRule{},
}
// GCE ensures that matched rule with longest prefix wins.
for _, rule := range hostRule.Paths {
beName := rule.Backend.BackendName(namer)
key.Name = beName
resourceID := cloud.ResourceID{ProjectID: "", Resource: "backendServices", Key: key}
beLink := resourceID.ResourcePath()
pathMatcher.PathRules = append(pathMatcher.PathRules, &composite.PathRule{
Paths: []string{rule.Path},
Service: beLink,
})
}
m.PathMatchers = append(m.PathMatchers, pathMatcher)
}
return m
}
// getNameForPathMatcher returns a name for a pathMatcher based on the given host rule.
// The host rule can be a regex, the path matcher name used to associate the 2 cannot.
func getNameForPathMatcher(hostRule string) string {
hasher := md5.New()
hasher.Write([]byte(hostRule))
return fmt.Sprintf("%v%v", hostRulePrefix, hex.EncodeToString(hasher.Sum(nil)))
}