istio

Форк
0
/
conditions.go 
365 строк · 14.4 Кб
1
// Copyright Istio Authors
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
package gateway
16

17
import (
18
	"fmt"
19
	"sort"
20

21
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22
	k8sv1 "sigs.k8s.io/gateway-api/apis/v1"
23
	k8s "sigs.k8s.io/gateway-api/apis/v1alpha2"
24

25
	"istio.io/istio/pilot/pkg/features"
26
	"istio.io/istio/pilot/pkg/model/kstatus"
27
	"istio.io/istio/pkg/config"
28
	"istio.io/istio/pkg/config/schema/gvk"
29
	"istio.io/istio/pkg/maps"
30
	"istio.io/istio/pkg/ptr"
31
	"istio.io/istio/pkg/slices"
32
	"istio.io/istio/pkg/util/sets"
33
)
34

35
// RouteParentResult holds the result of a route for a specific parent
36
type RouteParentResult struct {
37
	// OriginalReference contains the original reference
38
	OriginalReference k8s.ParentReference
39
	// DeniedReason, if present, indicates why the reference was not valid
40
	DeniedReason *ParentError
41
	// RouteError, if present, indicates why the reference was not valid
42
	RouteError *ConfigError
43
}
44

45
func createRouteStatus(parentResults []RouteParentResult, obj config.Config, currentParents []k8s.RouteParentStatus) []k8s.RouteParentStatus {
46
	parents := make([]k8s.RouteParentStatus, 0, len(parentResults))
47
	// Fill in all the gateways that are already present but not owned by us. This is non-trivial as there may be multiple
48
	// gateway controllers that are exposing their status on the same route. We need to attempt to manage ours properly (including
49
	// removing gateway references when they are removed), without mangling other Controller's status.
50
	for _, r := range currentParents {
51
		if r.ControllerName != k8sv1.GatewayController(features.ManagedGatewayController) {
52
			// We don't own this status, so keep it around
53
			parents = append(parents, r)
54
		}
55
	}
56
	// Collect all of our unique parent references. There may be multiple when we have a route without section name,
57
	// but reference a parent with multiple sections.
58
	// While we process these internally for-each sectionName, in the status we are just supposed to report one merged entry
59
	seen := map[k8s.ParentReference][]RouteParentResult{}
60
	seenReasons := sets.New[ParentErrorReason]()
61
	successCount := map[k8s.ParentReference]int{}
62
	for _, incoming := range parentResults {
63
		// We will append it if it is our first occurrence, or the existing one has an error. This means
64
		// if *any* section has no errors, we will declare Admitted
65
		if incoming.DeniedReason == nil {
66
			successCount[incoming.OriginalReference]++
67
		}
68
		seen[incoming.OriginalReference] = append(seen[incoming.OriginalReference], incoming)
69
		if incoming.DeniedReason != nil {
70
			seenReasons.Insert(incoming.DeniedReason.Reason)
71
		} else {
72
			seenReasons.Insert(ParentNoError)
73
		}
74
	}
75
	reasonRanking := []ParentErrorReason{
76
		// No errors is preferred
77
		ParentNoError,
78
		// All route level errors
79
		ParentErrorNotAllowed,
80
		ParentErrorNoHostname,
81
		ParentErrorParentRefConflict,
82
		// Failures to match the Port or SectionName. These are last so that if we bind to 1 listener we
83
		// just report errors for that 1 listener instead of for all sections we didn't bind to
84
		ParentErrorNotAccepted,
85
	}
86
	// Next we want to collapse these. We need to report 1 type of error, or none.
87
	report := map[k8s.ParentReference]RouteParentResult{}
88
	for _, wantReason := range reasonRanking {
89
		if !seenReasons.Contains(wantReason) {
90
			continue
91
		}
92
		// We found our highest priority ranking, now we need to collapse this into a single message
93
		for k, refs := range seen {
94
			for _, ref := range refs {
95
				reason := ParentNoError
96
				if ref.DeniedReason != nil {
97
					reason = ref.DeniedReason.Reason
98
				}
99
				if wantReason != reason {
100
					// Skip this one, it is for a less relevant reason
101
					continue
102
				}
103
				exist, f := report[k]
104
				if f {
105
					if ref.DeniedReason != nil {
106
						if exist.DeniedReason != nil {
107
							// join the error
108
							exist.DeniedReason.Message += "; " + ref.DeniedReason.Message
109
						} else {
110
							exist.DeniedReason = ref.DeniedReason
111
						}
112
					}
113
				} else {
114
					exist = ref
115
				}
116
				report[k] = exist
117
			}
118
		}
119
		// Once we find the best reason, do not consider any others
120
		break
121
	}
122

123
	// Now we fill in all the parents we do own
124
	for k, gw := range report {
125
		msg := "Route was valid"
126
		if successCount[k] > 1 {
127
			msg = fmt.Sprintf("Route was valid, bound to %d parents", successCount[k])
128
		}
129
		conds := map[string]*condition{
130
			string(k8s.RouteConditionAccepted): {
131
				reason:  string(k8s.RouteReasonAccepted),
132
				message: msg,
133
			},
134
			string(k8s.RouteConditionResolvedRefs): {
135
				reason:  string(k8s.RouteReasonResolvedRefs),
136
				message: "All references resolved",
137
			},
138
		}
139
		if gw.RouteError != nil {
140
			// Currently, the spec is not clear on where errors should be reported. The provided resources are:
141
			// * Accepted - used to describe errors binding to parents
142
			// * ResolvedRefs - used to describe errors about binding to objects
143
			// But no general errors
144
			// For now, we will treat all general route errors as "Ref" errors.
145
			conds[string(k8s.RouteConditionResolvedRefs)].error = gw.RouteError
146
		}
147
		if gw.DeniedReason != nil {
148
			conds[string(k8s.RouteConditionAccepted)].error = &ConfigError{
149
				Reason:  ConfigErrorReason(gw.DeniedReason.Reason),
150
				Message: gw.DeniedReason.Message,
151
			}
152
		}
153

154
		var currentConditions []metav1.Condition
155
		currentStatus := slices.FindFunc(currentParents, func(s k8sv1.RouteParentStatus) bool {
156
			return parentRefString(s.ParentRef) == parentRefString(gw.OriginalReference)
157
		})
158
		if currentStatus != nil {
159
			currentConditions = currentStatus.Conditions
160
		}
161
		parents = append(parents, k8s.RouteParentStatus{
162
			ParentRef:      gw.OriginalReference,
163
			ControllerName: k8sv1.GatewayController(features.ManagedGatewayController),
164
			Conditions:     setConditions(obj.Generation, currentConditions, conds),
165
		})
166
	}
167
	// Ensure output is deterministic.
168
	// TODO: will we fight over other controllers doing similar (but not identical) ordering?
169
	sort.SliceStable(parents, func(i, j int) bool {
170
		return parentRefString(parents[i].ParentRef) > parentRefString(parents[j].ParentRef)
171
	})
172
	return parents
173
}
174

175
type ParentErrorReason string
176

177
const (
178
	ParentErrorNotAccepted       = ParentErrorReason(k8sv1.RouteReasonNoMatchingParent)
179
	ParentErrorNotAllowed        = ParentErrorReason(k8s.RouteReasonNotAllowedByListeners)
180
	ParentErrorNoHostname        = ParentErrorReason(k8s.RouteReasonNoMatchingListenerHostname)
181
	ParentErrorParentRefConflict = ParentErrorReason("ParentRefConflict")
182
	ParentNoError                = ParentErrorReason("")
183
)
184

185
type ConfigErrorReason = string
186

187
const (
188
	// InvalidRefNotPermitted indicates a route was not permitted
189
	InvalidRefNotPermitted ConfigErrorReason = ConfigErrorReason(k8s.RouteReasonRefNotPermitted)
190
	// InvalidDestination indicates an issue with the destination
191
	InvalidDestination ConfigErrorReason = "InvalidDestination"
192
	InvalidAddress     ConfigErrorReason = ConfigErrorReason(k8sv1.GatewayReasonUnsupportedAddress)
193
	// InvalidDestinationPermit indicates a destination was not permitted
194
	InvalidDestinationPermit ConfigErrorReason = ConfigErrorReason(k8s.RouteReasonRefNotPermitted)
195
	// InvalidDestinationKind indicates an issue with the destination kind
196
	InvalidDestinationKind ConfigErrorReason = ConfigErrorReason(k8s.RouteReasonInvalidKind)
197
	// InvalidDestinationNotFound indicates a destination does not exist
198
	InvalidDestinationNotFound ConfigErrorReason = ConfigErrorReason(k8s.RouteReasonBackendNotFound)
199
	// InvalidParentRef indicates we could not refer to the parent we request
200
	InvalidParentRef ConfigErrorReason = "InvalidParentReference"
201
	// InvalidFilter indicates an issue with the filters
202
	InvalidFilter ConfigErrorReason = "InvalidFilter"
203
	// InvalidTLS indicates an issue with TLS settings
204
	InvalidTLS ConfigErrorReason = ConfigErrorReason(k8sv1.ListenerReasonInvalidCertificateRef)
205
	// InvalidListenerRefNotPermitted indicates a listener reference was not permitted
206
	InvalidListenerRefNotPermitted ConfigErrorReason = ConfigErrorReason(k8sv1.ListenerReasonRefNotPermitted)
207
	// InvalidConfiguration indicates a generic error for all other invalid configurations
208
	InvalidConfiguration ConfigErrorReason = "InvalidConfiguration"
209
	InvalidResources     ConfigErrorReason = ConfigErrorReason(k8sv1.GatewayReasonNoResources)
210
	DeprecateFieldUsage                    = "DeprecatedField"
211
)
212

213
// ParentError represents that a parent could not be referenced
214
type ParentError struct {
215
	Reason  ParentErrorReason
216
	Message string
217
}
218

219
// ConfigError represents an invalid configuration that will be reported back to the user.
220
type ConfigError struct {
221
	Reason  ConfigErrorReason
222
	Message string
223
}
224

225
type condition struct {
226
	// reason defines the reason to report on success. Ignored if error is set
227
	reason string
228
	// message defines the message to report on success. Ignored if error is set
229
	message string
230
	// status defines the status to report on success. The inverse will be set if error is set
231
	// If not set, will default to StatusTrue
232
	status metav1.ConditionStatus
233
	// error defines an error state; the reason and message will be replaced with that of the error and
234
	// the status inverted
235
	error *ConfigError
236
	// setOnce, if enabled, will only set the condition if it is not yet present or set to this reason
237
	setOnce string
238
}
239

240
// setConditions sets the existingConditions with the new conditions
241
func setConditions(generation int64, existingConditions []metav1.Condition, conditions map[string]*condition) []metav1.Condition {
242
	// Sort keys for deterministic ordering
243
	for _, k := range slices.Sort(maps.Keys(conditions)) {
244
		cond := conditions[k]
245
		setter := kstatus.UpdateConditionIfChanged
246
		if cond.setOnce != "" {
247
			setter = func(conditions []metav1.Condition, condition metav1.Condition) []metav1.Condition {
248
				return kstatus.CreateCondition(conditions, condition, cond.setOnce)
249
			}
250
		}
251
		// A condition can be "negative polarity" (ex: ListenerInvalid) or "positive polarity" (ex:
252
		// ListenerValid), so in order to determine the status we should set each `condition` defines its
253
		// default positive status. When there is an error, we will invert that. Example: If we have
254
		// condition ListenerInvalid, the status will be set to StatusFalse. If an error is reported, it
255
		// will be inverted to StatusTrue to indicate listeners are invalid. See
256
		// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties
257
		// for more information
258
		if cond.error != nil {
259
			existingConditions = setter(existingConditions, metav1.Condition{
260
				Type:               k,
261
				Status:             kstatus.InvertStatus(cond.status),
262
				ObservedGeneration: generation,
263
				LastTransitionTime: metav1.Now(),
264
				Reason:             cond.error.Reason,
265
				Message:            cond.error.Message,
266
			})
267
		} else {
268
			status := cond.status
269
			if status == "" {
270
				status = kstatus.StatusTrue
271
			}
272
			existingConditions = setter(existingConditions, metav1.Condition{
273
				Type:               k,
274
				Status:             status,
275
				ObservedGeneration: generation,
276
				LastTransitionTime: metav1.Now(),
277
				Reason:             cond.reason,
278
				Message:            cond.message,
279
			})
280
		}
281
	}
282
	return existingConditions
283
}
284

285
func reportListenerAttachedRoutes(index int, obj config.Config, i int32) {
286
	obj.Status.(*kstatus.WrappedStatus).Mutate(func(s config.Status) config.Status {
287
		gs := s.(*k8s.GatewayStatus)
288
		for index >= len(gs.Listeners) {
289
			gs.Listeners = append(gs.Listeners, k8s.ListenerStatus{})
290
		}
291
		status := gs.Listeners[index]
292
		status.AttachedRoutes = i
293
		gs.Listeners[index] = status
294
		return gs
295
	})
296
}
297

298
func reportListenerCondition(index int, l k8s.Listener, obj config.Config, conditions map[string]*condition) {
299
	obj.Status.(*kstatus.WrappedStatus).Mutate(func(s config.Status) config.Status {
300
		gs := s.(*k8s.GatewayStatus)
301
		for index >= len(gs.Listeners) {
302
			gs.Listeners = append(gs.Listeners, k8s.ListenerStatus{})
303
		}
304
		cond := gs.Listeners[index].Conditions
305
		supported, valid := generateSupportedKinds(l)
306
		if !valid {
307
			conditions[string(k8sv1.ListenerConditionResolvedRefs)] = &condition{
308
				reason:  string(k8sv1.ListenerReasonInvalidRouteKinds),
309
				status:  metav1.ConditionFalse,
310
				message: "Invalid route kinds",
311
			}
312
		}
313
		gs.Listeners[index] = k8s.ListenerStatus{
314
			Name:           l.Name,
315
			AttachedRoutes: 0, // this will be reported later
316
			SupportedKinds: supported,
317
			Conditions:     setConditions(obj.Generation, cond, conditions),
318
		}
319
		return gs
320
	})
321
}
322

323
func generateSupportedKinds(l k8s.Listener) ([]k8s.RouteGroupKind, bool) {
324
	supported := []k8s.RouteGroupKind{}
325
	switch l.Protocol {
326
	case k8sv1.HTTPProtocolType, k8sv1.HTTPSProtocolType:
327
		// Only terminate allowed, so its always HTTP
328
		supported = []k8s.RouteGroupKind{
329
			{Group: (*k8s.Group)(ptr.Of(gvk.HTTPRoute.Group)), Kind: k8s.Kind(gvk.HTTPRoute.Kind)},
330
			{Group: (*k8s.Group)(ptr.Of(gvk.GRPCRoute.Group)), Kind: k8s.Kind(gvk.GRPCRoute.Kind)},
331
		}
332
	case k8sv1.TCPProtocolType:
333
		supported = []k8s.RouteGroupKind{{Group: (*k8s.Group)(ptr.Of(gvk.TCPRoute.Group)), Kind: k8s.Kind(gvk.TCPRoute.Kind)}}
334
	case k8sv1.TLSProtocolType:
335
		if l.TLS != nil && l.TLS.Mode != nil && *l.TLS.Mode == k8sv1.TLSModePassthrough {
336
			supported = []k8s.RouteGroupKind{{Group: (*k8s.Group)(ptr.Of(gvk.TLSRoute.Group)), Kind: k8s.Kind(gvk.TLSRoute.Kind)}}
337
		} else {
338
			supported = []k8s.RouteGroupKind{{Group: (*k8s.Group)(ptr.Of(gvk.TCPRoute.Group)), Kind: k8s.Kind(gvk.TCPRoute.Kind)}}
339
		}
340
		// UDP route note support
341
	}
342
	if l.AllowedRoutes != nil && len(l.AllowedRoutes.Kinds) > 0 {
343
		// We need to filter down to only ones we actually support
344
		intersection := []k8s.RouteGroupKind{}
345
		for _, s := range supported {
346
			for _, kind := range l.AllowedRoutes.Kinds {
347
				if routeGroupKindEqual(s, kind) {
348
					intersection = append(intersection, s)
349
					break
350
				}
351
			}
352
		}
353
		return intersection, len(intersection) == len(l.AllowedRoutes.Kinds)
354
	}
355
	return supported, true
356
}
357

358
// This and the following function really belongs in some gateway-api lib
359
func routeGroupKindEqual(rgk1, rgk2 k8s.RouteGroupKind) bool {
360
	return rgk1.Kind == rgk2.Kind && getGroup(rgk1) == getGroup(rgk2)
361
}
362

363
func getGroup(rgk k8s.RouteGroupKind) k8s.Group {
364
	return ptr.OrDefault(rgk.Group, k8s.Group(gvk.KubernetesGateway.Group))
365
}
366

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.