/
selector.go
395 lines (354 loc) · 13.2 KB
/
selector.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
383
384
385
386
387
388
389
390
391
392
393
394
395
// Copyright 2016-2020 Authors of Cilium
//
// 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 api
import (
"encoding/json"
"fmt"
"strings"
k8sLbls "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/labels"
slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1"
validation "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1/validation"
"github.com/cilium/cilium/pkg/labels"
"github.com/cilium/cilium/pkg/logging"
"github.com/cilium/cilium/pkg/logging/logfields"
"github.com/cilium/cilium/pkg/metrics"
)
var log = logging.DefaultLogger.WithField(logfields.LogSubsys, "policy-api")
// EndpointSelector is a wrapper for k8s LabelSelector.
type EndpointSelector struct {
*slim_metav1.LabelSelector
// requirements provides a cache for a k8s-friendly format of the
// LabelSelector, which allows more efficient matching in Matches().
//
// Kept as a pointer to allow EndpointSelector to be used as a map key.
requirements *k8sLbls.Requirements
// cachedString is the cached representation of the LabelSelector for this
// EndpointSelector. It is populated when EndpointSelectors are created
// via `NewESFromMatchRequirements`. It is immutable after its creation.
cachedLabelSelectorString string
}
// LabelSelectorString returns a user-friendly string representation of
// EndpointSelector.
func (n *EndpointSelector) LabelSelectorString() string {
if n != nil && n.LabelSelector == nil {
return "<all>"
}
return slim_metav1.FormatLabelSelector(n.LabelSelector)
}
// String returns a string representation of EndpointSelector.
func (n EndpointSelector) String() string {
j, _ := n.MarshalJSON()
return string(j)
}
// CachedString returns the cached string representation of the LabelSelector
// for this EndpointSelector.
func (n EndpointSelector) CachedString() string {
return n.cachedLabelSelectorString
}
// UnmarshalJSON unmarshals the endpoint selector from the byte array.
func (n *EndpointSelector) UnmarshalJSON(b []byte) error {
n.LabelSelector = &slim_metav1.LabelSelector{}
err := json.Unmarshal(b, n.LabelSelector)
if err != nil {
return err
}
if n.MatchLabels != nil {
ml := map[string]string{}
for k, v := range n.MatchLabels {
ml[labels.GetExtendedKeyFrom(k)] = v
}
n.MatchLabels = ml
}
if n.MatchExpressions != nil {
newMatchExpr := make([]slim_metav1.LabelSelectorRequirement, len(n.MatchExpressions))
for i, v := range n.MatchExpressions {
v.Key = labels.GetExtendedKeyFrom(v.Key)
newMatchExpr[i] = v
}
n.MatchExpressions = newMatchExpr
}
n.requirements = labelSelectorToRequirements(n.LabelSelector)
n.cachedLabelSelectorString = n.LabelSelector.String()
return nil
}
// MarshalJSON returns a JSON representation of the byte array.
func (n EndpointSelector) MarshalJSON() ([]byte, error) {
ls := slim_metav1.LabelSelector{}
if n.LabelSelector == nil {
return json.Marshal(ls)
}
if n.MatchLabels != nil {
newLabels := map[string]string{}
for k, v := range n.MatchLabels {
newLabels[labels.GetCiliumKeyFrom(k)] = v
}
ls.MatchLabels = newLabels
}
if n.MatchExpressions != nil {
newMatchExpr := make([]slim_metav1.LabelSelectorRequirement, len(n.MatchExpressions))
for i, v := range n.MatchExpressions {
v.Key = labels.GetCiliumKeyFrom(v.Key)
newMatchExpr[i] = v
}
ls.MatchExpressions = newMatchExpr
}
return json.Marshal(ls)
}
// HasKeyPrefix checks if the endpoint selector contains the given key prefix in
// its MatchLabels map and MatchExpressions slice.
func (n EndpointSelector) HasKeyPrefix(prefix string) bool {
for k := range n.MatchLabels {
if strings.HasPrefix(k, prefix) {
return true
}
}
for _, v := range n.MatchExpressions {
if strings.HasPrefix(v.Key, prefix) {
return true
}
}
return false
}
// HasKey checks if the endpoint selector contains the given key in
// its MatchLabels map or in its MatchExpressions slice.
func (n EndpointSelector) HasKey(key string) bool {
if _, ok := n.MatchLabels[key]; ok {
return true
}
for _, v := range n.MatchExpressions {
if v.Key == key {
return true
}
}
return false
}
// GetMatch checks for a match on the specified key, and returns the value that
// the key must match, and true. If a match cannot be found, returns nil, false.
func (n EndpointSelector) GetMatch(key string) ([]string, bool) {
if value, ok := n.MatchLabels[key]; ok {
return []string{value}, true
}
for _, v := range n.MatchExpressions {
if v.Key == key && v.Operator == slim_metav1.LabelSelectorOpIn {
return v.Values, true
}
}
return nil, false
}
// labelSelectorToRequirements turns a kubernetes Selector into a slice of
// requirements equivalent to the selector. These are cached internally in the
// EndpointSelector to speed up Matches().
//
// This validates the labels, which can be expensive (and may fail..)
// If there's an error, the selector will be nil and the Matches()
// implementation will refuse to match any labels.
func labelSelectorToRequirements(labelSelector *slim_metav1.LabelSelector) *k8sLbls.Requirements {
selector, err := slim_metav1.LabelSelectorAsSelector(labelSelector)
if err != nil {
metrics.PolicyImportErrors.Inc()
log.WithError(err).WithField(logfields.EndpointLabelSelector,
logfields.Repr(labelSelector)).Error("unable to construct selector in label selector")
return nil
}
requirements, selectable := selector.Requirements()
if !selectable {
return nil
}
return &requirements
}
// NewESFromLabels creates a new endpoint selector from the given labels.
func NewESFromLabels(lbls ...labels.Label) EndpointSelector {
ml := map[string]string{}
for _, lbl := range lbls {
ml[lbl.GetExtendedKey()] = lbl.Value
}
return NewESFromMatchRequirements(ml, nil)
}
// NewESFromMatchRequirements creates a new endpoint selector from the given
// match specifications: An optional set of labels that must match, and
// an optional slice of LabelSelectorRequirements.
//
// If the caller intends to reuse 'matchLabels' or 'reqs' after creating the
// EndpointSelector, they must make a copy of the parameter.
func NewESFromMatchRequirements(matchLabels map[string]string, reqs []slim_metav1.LabelSelectorRequirement) EndpointSelector {
labelSelector := &slim_metav1.LabelSelector{
MatchLabels: matchLabels,
MatchExpressions: reqs,
}
return EndpointSelector{
LabelSelector: labelSelector,
requirements: labelSelectorToRequirements(labelSelector),
cachedLabelSelectorString: labelSelector.String(),
}
}
// SyncRequirementsWithLabelSelector ensures that the requirements within the
// specified EndpointSelector are in sync with the LabelSelector. This is
// because the LabelSelector has publicly accessible fields, which can be
// updated without concurrently updating the requirements, so the two fields can
// become out of sync.
func (n *EndpointSelector) SyncRequirementsWithLabelSelector() {
n.requirements = labelSelectorToRequirements(n.LabelSelector)
}
// newReservedEndpointSelector returns a selector that matches on all
// endpoints with the specified reserved label.
func newReservedEndpointSelector(ID string) EndpointSelector {
reservedLabels := labels.NewLabel(ID, "", labels.LabelSourceReserved)
return NewESFromLabels(reservedLabels)
}
var (
// WildcardEndpointSelector is a wildcard endpoint selector matching
// all endpoints that can be described with labels.
WildcardEndpointSelector = NewESFromLabels()
// ReservedEndpointSelectors map reserved labels to EndpointSelectors
// that will match those endpoints.
ReservedEndpointSelectors = map[string]EndpointSelector{
labels.IDNameHost: newReservedEndpointSelector(labels.IDNameHost),
labels.IDNameRemoteNode: newReservedEndpointSelector(labels.IDNameRemoteNode),
labels.IDNameWorld: newReservedEndpointSelector(labels.IDNameWorld),
}
)
// NewESFromK8sLabelSelector returns a new endpoint selector from the label
// where it the given srcPrefix will be encoded in the label's keys.
func NewESFromK8sLabelSelector(srcPrefix string, lss ...*slim_metav1.LabelSelector) EndpointSelector {
var (
matchLabels map[string]string
matchExpressions []slim_metav1.LabelSelectorRequirement
)
for _, ls := range lss {
if ls == nil {
continue
}
if ls.MatchLabels != nil {
if matchLabels == nil {
matchLabels = map[string]string{}
}
for k, v := range ls.MatchLabels {
matchLabels[srcPrefix+k] = v
}
}
if ls.MatchExpressions != nil {
if matchExpressions == nil {
matchExpressions = make([]slim_metav1.LabelSelectorRequirement, 0, len(ls.MatchExpressions))
}
for _, v := range ls.MatchExpressions {
v.Key = srcPrefix + v.Key
matchExpressions = append(matchExpressions, v)
}
}
}
return NewESFromMatchRequirements(matchLabels, matchExpressions)
}
// AddMatch adds a match for 'key' == 'value' to the endpoint selector.
func (n *EndpointSelector) AddMatch(key, value string) {
if n.MatchLabels == nil {
n.MatchLabels = map[string]string{}
}
n.MatchLabels[key] = value
n.requirements = labelSelectorToRequirements(n.LabelSelector)
n.cachedLabelSelectorString = n.LabelSelector.String()
}
// AddMatchExpression adds a match expression to label selector of the endpoint selector.
func (n *EndpointSelector) AddMatchExpression(key string, op slim_metav1.LabelSelectorOperator, values []string) {
n.MatchExpressions = append(n.MatchExpressions, slim_metav1.LabelSelectorRequirement{
Key: key,
Operator: op,
Values: values,
})
// Update cache of the EndopintSelector from the embedded label selector.
// This is to make sure we have updates caches containing the required selectors.
n.requirements = labelSelectorToRequirements(n.LabelSelector)
n.cachedLabelSelectorString = n.LabelSelector.String()
}
// Matches returns true if the endpoint selector Matches the `lblsToMatch`.
// Returns always true if the endpoint selector contains the reserved label for
// "all".
func (n *EndpointSelector) Matches(lblsToMatch k8sLbls.Labels) bool {
// Try to update cached requirements for this EndpointSelector if possible.
if n.requirements == nil {
n.requirements = labelSelectorToRequirements(n.LabelSelector)
// Nil indicates that requirements failed validation in some way,
// so we cannot parse the labels for matching purposes; thus, we cannot
// match if labels cannot be parsed, so return false.
if n.requirements == nil {
return false
}
}
for _, req := range *n.requirements {
if !req.Matches(lblsToMatch) {
return false
}
}
return true
}
// IsWildcard returns true if the endpoint selector selects all endpoints.
func (n *EndpointSelector) IsWildcard() bool {
return n.LabelSelector != nil &&
len(n.LabelSelector.MatchLabels)+len(n.LabelSelector.MatchExpressions) == 0
}
// ConvertToLabelSelectorRequirementSlice converts the MatchLabels and
// MatchExpressions within the specified EndpointSelector into a list of
// LabelSelectorRequirements.
func (n *EndpointSelector) ConvertToLabelSelectorRequirementSlice() []slim_metav1.LabelSelectorRequirement {
requirements := make([]slim_metav1.LabelSelectorRequirement, 0, len(n.MatchExpressions)+len(n.MatchLabels))
// Append already existing match expressions.
requirements = append(requirements, n.MatchExpressions...)
// Convert each MatchLables to LabelSelectorRequirement.
for key, value := range n.MatchLabels {
requirementFromMatchLabels := slim_metav1.LabelSelectorRequirement{
Key: key,
Operator: slim_metav1.LabelSelectorOpIn,
Values: []string{value},
}
requirements = append(requirements, requirementFromMatchLabels)
}
return requirements
}
// sanitize returns an error if the EndpointSelector's LabelSelector is invalid.
func (n *EndpointSelector) sanitize() error {
errList := validation.ValidateLabelSelector(n.LabelSelector, nil)
if len(errList) > 0 {
return fmt.Errorf("invalid label selector: %s", errList.ToAggregate().Error())
}
return nil
}
// EndpointSelectorSlice is a slice of EndpointSelectors that can be sorted.
type EndpointSelectorSlice []EndpointSelector
func (s EndpointSelectorSlice) Len() int { return len(s) }
func (s EndpointSelectorSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s EndpointSelectorSlice) Less(i, j int) bool {
strI := s[i].LabelSelectorString()
strJ := s[j].LabelSelectorString()
return strings.Compare(strI, strJ) < 0
}
// Matches returns true if any of the EndpointSelectors in the slice match the
// provided labels
func (s EndpointSelectorSlice) Matches(ctx labels.LabelArray) bool {
for _, selector := range s {
if selector.Matches(ctx) {
return true
}
}
return false
}
// SelectsAllEndpoints returns whether the EndpointSelectorSlice selects all
// endpoints, which is true if the wildcard endpoint selector is present in the
// slice.
func (s EndpointSelectorSlice) SelectsAllEndpoints() bool {
for _, selector := range s {
if selector.IsWildcard() {
return true
}
}
return false
}