@@ -70,8 +70,9 @@ type ClusterCelSelector struct {
70
70
A ManagedClusterLib will be added.
71
71
- Variable ` managedCluster ` will be added.
72
72
- Function ` scores ` will be added.
73
- - Function ` versionIsGreaterThan ` , ` versionIsLessThan ` will be added.
74
- - Function ` quantityIsGreaterThan ` , ` quantityIsLessThan ` will be added.
73
+ - Function ` parseJSON ` will be added.
74
+ - Kubernetes semver library is included.
75
+ - Kubernetes quantity library is included.
75
76
76
77
``` go
77
78
// ManagedClusterLib defines the CEL library for ManagedCluster evaluation.
@@ -96,72 +97,59 @@ A ManagedClusterLib will be added.
96
97
// - name: string - The name of the score
97
98
// - value: int - The numeric score value
98
99
// - quantity: number|string - The quantity value, represented as:
99
- // * number: for pure decimal values (e.g., 3)
100
- // * string: for values with units or decimal places (e.g., "300Mi", "1.5Gi")
100
+ // - number: for pure decimal values (e.g., 3)
101
+ // - string: for values with units or decimal places (e.g., "300Mi", "1.5Gi")
101
102
//
102
103
// Examples:
103
104
//
104
- // managedCluster.scores("cpu-memory") // returns [{name: "cpu", value: 3, quantity: 3}, {name: "memory", value: 4, quantity: "300Mi"}]
105
+ // managedCluster.scores("cpu-memory") // returns [{name: "cpu", value: 3, quantity: 3" }, {name: "memory", value: 4, quantity: "300Mi"}]
105
106
//
106
- // Version Comparisons:
107
+ // parseJSON
107
108
//
108
- // versionIsGreaterThan
109
+ // Parses a JSON string into a CEL-compatible map or list.
109
110
//
110
- // Returns true if the first version string is greater than the second version string.
111
- // The version must follow Semantic Versioning specification (http://semver.org/).
112
- // It can be with or without 'v' prefix (eg, "1.14.3" or "v1.14.3").
111
+ // parseJSON(<string>) <dyn>
113
112
//
114
- // versionIsGreaterThan(<string>, <string>) <bool>
113
+ // Takes a single string argument, attempts to parse it as JSON, and returns the resulting
114
+ // data structure as a CEL-compatible value. If the input is not a valid JSON string, it returns an error.
115
115
//
116
116
// Examples:
117
117
//
118
- // versionIsGreaterThan("1.25.0", "1.24.0") // returns true
119
- // versionIsGreaterThan("1.24.0", "1.25.0") // returns false
118
+ // parseJSON("{\"key\": \"value\"}") // returns a map with key-value pairs
120
119
//
121
- // versionIsLessThan
120
+ // Semver Library:
122
121
//
123
- // Returns true if the first version string is less than the second version string.
124
- // The version must follow Semantic Versioning specification (http://semver.org/).
125
- // It can be with or without 'v' prefix (eg, "1.14.3" or "v1.14.3").
126
- //
127
- // versionIsLessThan(<string>, <string>) <bool>
122
+ // Semver provides a CEL function library extension for [semver.Version].
123
+ // Upstream enhancement to support v is a prefix https://github.com/kubernetes/kubernetes/pull/130648.
128
124
//
129
125
// Examples:
130
126
//
131
- // versionIsLessThan("1.24.0", "1.25.0") // returns true
132
- // versionIsLessThan("1.25.0", "1.24.0") // returns false
133
- //
134
- // Quantity Comparisons:
135
- //
136
- // quantityIsGreaterThan
127
+ // semver("1.2.3").isLessThan(semver("1.2.4")) // returns true
128
+ // semver("1.2.3").isGreaterThan(semver("1.2.4")) // returns false
137
129
//
138
- // Returns true if the first quantity string is greater than the second quantity string.
130
+ // Quantity Library:
139
131
//
140
- // quantityIsGreaterThan(<string>, <string>) <bool>
132
+ // Quantity provides a CEL function library extension of Kubernetes resource.
141
133
//
142
134
// Examples:
143
135
//
144
- // quantityIsGreaterThan("2Gi", "1Gi") // returns true
145
- // quantityIsGreaterThan("1Gi", "2Gi") // returns false
146
- // quantityIsGreaterThan("1000Mi", "1Gi") // returns false
147
- //
148
- // quantityIsLessThan
149
- //
150
- // Returns true if the first quantity string is less than the second quantity string.
151
- //
152
- // quantityIsLessThan(<string>, <string>) <bool>
153
- //
154
- // Examples:
155
- //
156
- // quantityIsLessThan("1Gi", "2Gi") // returns true
157
- // quantityIsLessThan("2Gi", "1Gi") // returns false
158
- // quantityIsLessThan("1000Mi", "1Gi") // returns true
136
+ // quantity("100Mi").isLessThan(quantity("200Mi")) // returns true
137
+ // quantity("100Mi").isGreaterThan(quantity("200Mi")) // returns false
159
138
160
139
type ManagedClusterLib struct {}
161
140
162
141
// CompileOptions implements cel.Library interface to provide compile-time options.
163
142
func (ManagedClusterLib ) CompileOptions () []cel .EnvOption {
164
143
return []cel.EnvOption {
144
+ // Add the extended strings library
145
+ ext.Strings (),
146
+
147
+ // Add the kubernetes semver library
148
+ library.SemverLib (),
149
+
150
+ // Add the kubernetes quantity library
151
+ library.Quantity (),
152
+
165
153
// The input types may either be instances of `proto.Message` or `ref.Type`.
166
154
// Here we use func ConvertManagedCluster() to convert ManagedCluster to a Map.
167
155
cel.Variable (" managedCluster" , cel.MapType (cel.StringType , cel.DynType )),
@@ -174,36 +162,12 @@ func (ManagedClusterLib) CompileOptions() []cel.EnvOption {
174
162
cel.FunctionBinding (clusterScores)),
175
163
),
176
164
177
- cel.Function (" versionIsGreaterThan" ,
178
- cel.MemberOverload (
179
- " version_is_greater_than" ,
180
- []*cel.Type {cel.StringType , cel.StringType },
181
- cel.BoolType ,
182
- cel.FunctionBinding (versionIsGreaterThan)),
183
- ),
184
-
185
- cel.Function (" versionIsLessThan" ,
186
- cel.MemberOverload (
187
- " version_is_less_than" ,
188
- []*cel.Type {cel.StringType , cel.StringType },
189
- cel.BoolType ,
190
- cel.FunctionBinding (versionIsLessThan)),
191
- ),
192
-
193
- cel.Function (" quantityIsGreaterThan" ,
194
- cel.MemberOverload (
195
- " quantity_is_greater_than" ,
196
- []*cel.Type {cel.StringType , cel.StringType },
197
- cel.BoolType ,
198
- cel.FunctionBinding (quantityIsGreaterThan)),
199
- ),
200
-
201
- cel.Function (" quantityIsLessThan" ,
202
- cel.MemberOverload (
203
- " quantity_is_less_than" ,
204
- []*cel.Type {cel.StringType , cel.StringType },
205
- cel.BoolType ,
206
- cel.FunctionBinding (quantityIsLessThan)),
165
+ cel.Function (" parseJSON" ,
166
+ cel.MemberOverload (" parse_json_string" ,
167
+ []*cel.Type {cel.StringType },
168
+ cel.DynType ,
169
+ cel.FunctionBinding (l.parseJSON ),
170
+ ),
207
171
),
208
172
}
209
173
}
@@ -223,6 +187,15 @@ func NewEvaluator() (*Evaluator, error) {
223
187
}
224
188
```
225
189
190
+ ### Metrics for time spent evaluating CEL at runtime.
191
+ - TBD
192
+
193
+ ### Cost budget
194
+ - TBD
195
+
196
+ ### CEL validation error message
197
+ - TBD
198
+
226
199
### Examples
227
200
228
201
1 . The user can select clusters by Kubernetes version listed in ` managedCluster.Status.version.kubernetes ` .
@@ -234,14 +207,11 @@ metadata:
234
207
name : placement1
235
208
namespace : default
236
209
spec :
237
- numberOfClusters : 3
238
- clusterSets :
239
- - prod
240
210
predicates :
241
211
- requiredClusterSelector :
242
212
celSelector :
243
213
celExpressions :
244
- - managedCluster.Status .version.kubernetes == "v1.30.0"
214
+ - managedCluster.status .version.kubernetes == "v1.30.0"
245
215
` ` `
246
216
2. The user can use CEL [Standard macros](https://github.com/google/cel-spec/blob/master/doc/langdef.md#macros) and [Standard functions](https://github.com/google/cel-spec/blob/master/doc/langdef.md#standard-definitions) on the ` managedCluster` fields.
247
217
@@ -254,9 +224,6 @@ metadata:
254
224
name: placement1
255
225
namespace: default
256
226
spec:
257
- numberOfClusters: 3
258
- clusterSets:
259
- - prod
260
227
predicates:
261
228
- requiredClusterSelector:
262
229
celSelector:
@@ -273,9 +240,6 @@ metadata:
273
240
name: placement1
274
241
namespace: default
275
242
spec:
276
- numberOfClusters: 3
277
- clusterSets:
278
- - prod
279
243
predicates:
280
244
- requiredClusterSelector:
281
245
celSelector:
@@ -284,7 +248,7 @@ spec:
284
248
` ` `
285
249
286
250
287
- 3. The user can use CEL customized functions `versionIsGreaterThan ` and `versionIsLessThan ` to select clusters by version comparison. For example, select clusters whose kubernetes version > v1.13.0.
251
+ 3. The user can use Kubernetes semver library functions `isLessThan ` and `isGreaterThan ` to select clusters by version comparison. For example, select clusters whose kubernetes version > v1.13.0.
288
252
289
253
290
254
` ` ` yaml
@@ -294,18 +258,17 @@ metadata:
294
258
name: placement1
295
259
namespace: default
296
260
spec:
297
- numberOfClusters: 3
298
- clusterSets:
299
- - prod
300
261
predicates:
301
262
- requiredClusterSelector:
302
263
celSelector:
303
264
celExpressions:
304
- - managedCluster.Status.version.kubernetes.versionIsGreaterThan("v1.30.0")
265
+ # Upstream enhancement to support v is a prefix https://github.com/kubernetes/kubernetes/pull/130648.
266
+ - semver(managedCluster.status.version.kubernetes, true).isGreaterThan(semver("v1.30.0", true))
305
267
` ` `
306
268
307
269
308
- 4. The user can use CEL customized function `score` to select clusters by `AddonPlacementScore`. For example, select clusters whose cpuAvailable score < 2.
270
+ 4. The user can use CEL customized function `scores` to select clusters by `AddonPlacementScore`. And use Kubernetes quantity library function `isLessThan` and `isGreaterThan` to compare quantities.
271
+ For example, select clusters whose cpuAvailable quantity > 4 and memAvailable quantity > 100Mi.
309
272
310
273
311
274
` ` ` yaml
@@ -315,17 +278,62 @@ metadata:
315
278
name: placement1
316
279
namespace: default
317
280
spec:
318
- numberOfClusters: 3
319
- clusterSets:
320
- - prod
321
281
predicates:
322
282
- requiredClusterSelector:
323
283
celSelector:
324
284
celExpressions:
325
285
- managedCluster.scores("default").filter(s, s.name == 'cpuAvailable').all(e, e.quantity > 4)
326
- - managedCluster.scores("default").filter(s, s.name == 'memAvailable').all(e, e.quantity.quantityIsGreaterThan( "100Mi"))
286
+ - managedCluster.scores("default").filter(s, s.name == 'memAvailable').all(e, quantity( e.quantity).isGreaterThan(quantity( "100Mi") ))
327
287
` ` `
328
288
289
+ 5. The user can use CEL to prase properties with format like, a comma separated string or a json format string.
290
+
291
+ ` ` ` yaml
292
+ apiVersion: multicluster.x-k8s.io/v1alpha1
293
+ kind: ClusterProfile
294
+ metadata:
295
+ name: bravelion
296
+ namespace: ...
297
+ ...
298
+ status:
299
+ properties:
300
+ - name: sku.node.k8s.io
301
+ value: g6.xlarge,Standard_NC48ads_H100,m3-ultramem-32
302
+ - name: sku.gpu.kubernetes-fleet.io
303
+ value: |
304
+ {
305
+ "H100": [
306
+ {
307
+ "Standard_NC48ads_H100_v4": 10
308
+ }
309
+ {
310
+ "Standard_NC96ads_H100_v4": 2
311
+ }
312
+ ]
313
+ "A100": [
314
+ {
315
+ "Standard_NC48ads_A100_v4": 50
316
+ }
317
+ {
318
+ "Standard_NC96ads_A100_v4": 20
319
+ }
320
+ ]
321
+ }
322
+ ...
323
+ ` ` `
324
+
325
+ ` ` ` yaml
326
+ apiVersion: cluster.open-cluster-management.io/v1beta1
327
+ kind: Placement
328
+ …
329
+ spec:
330
+ predicates:
331
+ - requiredClusterSelector:
332
+ celSelector:
333
+ celExpressions:
334
+ - managedCluster.status.properties.exists(c, c.name == "sku.node.k8s.io" && c.value.split(",").exists(e, e == "g6.xlarge"))
335
+ - managedCluster.status.properties.exists(c, c.name == "sku.gpu.kubernetes-fleet.io" && c.value.parseJSON().H100.exists(e, e.Standard_NC96ads_H100_v4 == 2))
336
+ ` ` `
329
337
330
338
# ## Test Plan
331
339
0 commit comments