/
config.go
382 lines (331 loc) · 13.3 KB
/
config.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
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
// Copyright Istio 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 model
import (
"hash/crc32"
"net"
"sort"
"strings"
udpa "github.com/cncf/udpa/go/udpa/type/v1"
networking "istio.io/api/networking/v1alpha3"
"istio.io/istio/pkg/config"
"istio.io/istio/pkg/config/host"
"istio.io/istio/pkg/config/labels"
"istio.io/istio/pkg/config/schema/collection"
"istio.io/istio/pkg/config/schema/gvk"
)
// Statically link protobuf descriptors from UDPA
var _ = udpa.TypedStruct{}
// ConfigKey describe a specific config item.
// In most cases, the name is the config's name. However, for ServiceEntry it is service's FQDN.
type ConfigKey struct {
Kind config.GroupVersionKind
Name string
Namespace string
}
func (key ConfigKey) HashCode() uint32 {
var result uint32
result = 31*result + crc32.ChecksumIEEE([]byte(key.Kind.Kind))
result = 31*result + crc32.ChecksumIEEE([]byte(key.Kind.Version))
result = 31*result + crc32.ChecksumIEEE([]byte(key.Kind.Group))
result = 31*result + crc32.ChecksumIEEE([]byte(key.Namespace))
result = 31*result + crc32.ChecksumIEEE([]byte(key.Name))
return result
}
// ConfigsOfKind extracts configs of the specified kind.
func ConfigsOfKind(configs map[ConfigKey]struct{}, kind config.GroupVersionKind) map[ConfigKey]struct{} {
ret := make(map[ConfigKey]struct{})
for conf := range configs {
if conf.Kind == kind {
ret[conf] = struct{}{}
}
}
return ret
}
// ConfigNamesOfKind extracts config names of the specified kind.
func ConfigNamesOfKind(configs map[ConfigKey]struct{}, kind config.GroupVersionKind) map[string]struct{} {
ret := make(map[string]struct{})
for conf := range configs {
if conf.Kind == kind {
ret[conf.Name] = struct{}{}
}
}
return ret
}
// ConfigStore describes a set of platform agnostic APIs that must be supported
// by the underlying platform to store and retrieve Istio configuration.
//
// Configuration key is defined to be a combination of the type, name, and
// namespace of the configuration object. The configuration key is guaranteed
// to be unique in the store.
//
// The storage interface presented here assumes that the underlying storage
// layer supports _Get_ (list), _Update_ (update), _Create_ (create) and
// _Delete_ semantics but does not guarantee any transactional semantics.
//
// _Update_, _Create_, and _Delete_ are mutator operations. These operations
// are asynchronous, and you might not see the effect immediately (e.g. _Get_
// might not return the object by key immediately after you mutate the store.)
// Intermittent errors might occur even though the operation succeeds, so you
// should always check if the object store has been modified even if the
// mutating operation returns an error. Objects should be created with
// _Create_ operation and updated with _Update_ operation.
//
// Resource versions record the last mutation operation on each object. If a
// mutation is applied to a different revision of an object than what the
// underlying storage expects as defined by pure equality, the operation is
// blocked. The client of this interface should not make assumptions about the
// structure or ordering of the revision identifier.
//
// Object references supplied and returned from this interface should be
// treated as read-only. Modifying them violates thread-safety.
type ConfigStore interface {
// Schemas exposes the configuration type schema known by the config store.
// The type schema defines the bidrectional mapping between configuration
// types and the protobuf encoding schema.
Schemas() collection.Schemas
// Get retrieves a configuration element by a type and a key
Get(typ config.GroupVersionKind, name, namespace string) *config.Config
// List returns objects by type and namespace.
// Use "" for the namespace to list across namespaces.
List(typ config.GroupVersionKind, namespace string) ([]config.Config, error)
// Create adds a new configuration object to the store. If an object with the
// same name and namespace for the type already exists, the operation fails
// with no side effects.
Create(config config.Config) (revision string, err error)
// Update modifies an existing configuration object in the store. Update
// requires that the object has been created. Resource version prevents
// overriding a value that has been changed between prior _Get_ and _Put_
// operation to achieve optimistic concurrency. This method returns a new
// revision if the operation succeeds.
Update(config config.Config) (newRevision string, err error)
UpdateStatus(config config.Config) (newRevision string, err error)
// Patch applies only the modifications made in the PatchFunc rather than doing a full replace. Useful to avoid
// read-modify-write conflicts when there are many concurrent-writers to the same resource.
Patch(orig config.Config, patchFn config.PatchFunc) (string, error)
// Delete removes an object from the store by key
// For k8s, resourceVersion must be fulfilled before a deletion is carried out.
// If not possible, a 409 Conflict status will be returned.
Delete(typ config.GroupVersionKind, name, namespace string, resourceVersion *string) error
}
// ConfigStoreCache is a local fully-replicated cache of the config store. The
// cache actively synchronizes its local state with the remote store and
// provides a notification mechanism to receive update events. As such, the
// notification handlers must be registered prior to calling _Run_, and the
// cache requires initial synchronization grace period after calling _Run_.
//
// Update notifications require the following consistency guarantee: the view
// in the cache must be AT LEAST as fresh as the moment notification arrives, but
// MAY BE more fresh (e.g. if _Delete_ cancels an _Add_ event).
//
// Handlers execute on the single worker queue in the order they are appended.
// Handlers receive the notification event and the associated object. Note
// that all handlers must be registered before starting the cache controller.
type ConfigStoreCache interface {
ConfigStore
// RegisterEventHandler adds a handler to receive config update events for a
// configuration type
RegisterEventHandler(kind config.GroupVersionKind, handler func(config.Config, config.Config, Event))
// Run until a signal is received
Run(stop <-chan struct{})
// HasSynced returns true after initial cache synchronization is complete
HasSynced() bool
}
// IstioConfigStore is a specialized interface to access config store using
// Istio configuration types
// nolint
type IstioConfigStore interface {
ConfigStore
// ServiceEntries lists all service entries
ServiceEntries() []config.Config
// Gateways lists all gateways bound to the specified workload labels
Gateways(workloadLabels labels.Collection) []config.Config
// AuthorizationPolicies selects AuthorizationPolicies in the specified namespace.
AuthorizationPolicies(namespace string) []config.Config
}
const (
// NamespaceAll is a designated symbol for listing across all namespaces
NamespaceAll = ""
)
// ResolveShortnameToFQDN uses metadata information to resolve a reference
// to shortname of the service to FQDN
func ResolveShortnameToFQDN(hostname string, meta config.Meta) host.Name {
out := hostname
// Treat the wildcard hostname as fully qualified. Any other variant of a wildcard hostname will contain a `.` too,
// and skip the next if, so we only need to check for the literal wildcard itself.
if hostname == "*" {
return host.Name(out)
}
// if the hostname is a valid ipv4 or ipv6 address, do not append domain or namespace
if net.ParseIP(hostname) != nil {
return host.Name(out)
}
// if FQDN is specified, do not append domain or namespace to hostname
if !strings.Contains(hostname, ".") {
if meta.Namespace != "" {
out = out + "." + meta.Namespace
}
// FIXME this is a gross hack to hardcode a service's domain name in kubernetes
// BUG this will break non kubernetes environments if they use shortnames in the
// rules.
if meta.Domain != "" {
out = out + ".svc." + meta.Domain
}
}
return host.Name(out)
}
// resolveGatewayName uses metadata information to resolve a reference
// to shortname of the gateway to FQDN
func resolveGatewayName(gwname string, meta config.Meta) string {
out := gwname
// New way of binding to a gateway in remote namespace
// is ns/name. Old way is either FQDN or short name
if !strings.Contains(gwname, "/") {
if !strings.Contains(gwname, ".") {
// we have a short name. Resolve to a gateway in same namespace
out = meta.Namespace + "/" + gwname
} else {
// parse namespace from FQDN. This is very hacky, but meant for backward compatibility only
// This is a legacy FQDN format. Transform name.ns.svc.cluster.local -> ns/name
i := strings.Index(gwname, ".")
fqdn := strings.Index(gwname[i+1:], ".")
if fqdn == -1 {
out = gwname[i+1:] + "/" + gwname[:i]
} else {
out = gwname[i+1:i+1+fqdn] + "/" + gwname[:i]
}
}
} else {
// remove the . from ./gateway and substitute it with the namespace name
i := strings.Index(gwname, "/")
if gwname[:i] == "." {
out = meta.Namespace + "/" + gwname[i+1:]
}
}
return out
}
// MostSpecificHostMatch compares the elements of the stack to the needle, and returns the longest stack element
// matching the needle, or false if no element in the stack matches the needle.
func MostSpecificHostMatch(needle host.Name, m map[host.Name]struct{}, stack []host.Name) (host.Name, bool) {
matches := []host.Name{}
// exact match first
if m != nil {
if _, ok := m[needle]; ok {
return needle, true
}
} else {
for _, h := range stack {
if h == needle {
return needle, true
}
}
}
if needle.IsWildCarded() {
// slice has better loop performance than map, so use stack to range
// and stack is ordered before
for _, h := range stack {
// both needle and h are wildcards
if h.IsWildCarded() {
if len(needle) < len(h) {
continue
}
if strings.HasSuffix(string(needle[1:]), string(h[1:])) {
matches = append(matches, h)
}
}
}
} else {
for _, h := range stack {
// only n is wildcard
if h.IsWildCarded() {
if strings.HasSuffix(string(needle), string(h[1:])) {
matches = append(matches, h)
}
}
}
}
if len(matches) > 0 {
// TODO: return closest match out of all non-exact matching hosts
return matches[0], true
}
return "", false
}
// istioConfigStore provides a simple adapter for Istio configuration types
// from the generic config registry
type istioConfigStore struct {
ConfigStore
}
// MakeIstioStore creates a wrapper around a store.
// In pilot it is initialized with a ConfigStoreCache, tests only use
// a regular ConfigStore.
func MakeIstioStore(store ConfigStore) IstioConfigStore {
return &istioConfigStore{store}
}
func (store *istioConfigStore) ServiceEntries() []config.Config {
serviceEntries, err := store.List(gvk.ServiceEntry, NamespaceAll)
if err != nil {
return nil
}
// To ensure the ip allocation logic deterministically
// allocates the same IP to a service entry.
sortConfigByCreationTime(serviceEntries)
return serviceEntries
}
// sortConfigByCreationTime sorts the list of config objects in ascending order by their creation time (if available).
func sortConfigByCreationTime(configs []config.Config) {
sort.SliceStable(configs, func(i, j int) bool {
// If creation time is the same, then behavior is nondeterministic. In this case, we can
// pick an arbitrary but consistent ordering based on name and namespace, which is unique.
// CreationTimestamp is stored in seconds, so this is not uncommon.
if configs[i].CreationTimestamp == configs[j].CreationTimestamp {
in := configs[i].Name + "." + configs[i].Namespace
jn := configs[j].Name + "." + configs[j].Namespace
return in < jn
}
return configs[i].CreationTimestamp.Before(configs[j].CreationTimestamp)
})
}
func (store *istioConfigStore) Gateways(workloadLabels labels.Collection) []config.Config {
configs, err := store.List(gvk.Gateway, NamespaceAll)
if err != nil {
return nil
}
sortConfigByCreationTime(configs)
out := make([]config.Config, 0)
for _, cfg := range configs {
gateway := cfg.Spec.(*networking.Gateway)
if gateway.GetSelector() == nil {
// no selector. Applies to all workloads asking for the gateway
out = append(out, cfg)
} else {
gatewaySelector := labels.Instance(gateway.GetSelector())
if workloadLabels.IsSupersetOf(gatewaySelector) {
out = append(out, cfg)
}
}
}
return out
}
// key creates a key from a reference's name and namespace.
func key(name, namespace string) string {
return name + "/" + namespace
}
func (store *istioConfigStore) AuthorizationPolicies(namespace string) []config.Config {
authorizationPolicies, err := store.List(gvk.AuthorizationPolicy, namespace)
if err != nil {
log.Errorf("failed to get AuthorizationPolicy in namespace %s: %v", namespace, err)
return nil
}
return authorizationPolicies
}