Skip to content

Commit bbc78ba

Browse files
fix(api): keep integral spellings valid and refuse doubles past int64
The schema validator accepts 2.0 and 2e0 as integers, but they decoded as float64 and failed the integer check, so a valid spec was refused for how its numbers were written. A double result past the int64 range in millis saturated on conversion, so 1e100 became a successful reservation far smaller than the one declared. Assisted-by: LLM Signed-off-by: Mattia Eleuteri <[email protected]>
1 parent b56bc70 commit bbc78ba

2 files changed

Lines changed: 55 additions & 2 deletions

File tree

‎pkg/reservation/evaluator.go‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,14 @@ func toQuantity(v ref.Val) (resource.Quantity, error) {
312312
if f < 0 {
313313
return resource.Quantity{}, fmt.Errorf("negative reservation %v", f)
314314
}
315-
return *resource.NewMilliQuantity(int64(math.Ceil(f*1000)), resource.DecimalSI), nil
315+
// float64(math.MaxInt64) rounds up to 2^63, so the bound is
316+
// exclusive; past it the conversion would saturate into a smaller
317+
// figure.
318+
milli := math.Ceil(f * 1000)
319+
if milli >= math.MaxInt64 {
320+
return resource.Quantity{}, fmt.Errorf("%v overflows a quantity", f)
321+
}
322+
return *resource.NewMilliQuantity(int64(milli), resource.DecimalSI), nil
316323
case types.String:
317324
q, err := resource.ParseQuantity(string(t))
318325
if err != nil {
@@ -429,8 +436,10 @@ func normalizeNumbers(v any) any {
429436
if i, err := t.Int64(); err == nil {
430437
return i
431438
}
439+
// 2.0 and 2e0 are integers to the schema validator; spelled that
440+
// way they fail Int64 and are classified as a float64 would be.
432441
f, _ := t.Float64()
433-
return f
442+
return normalizeNumbers(f)
434443
case float64:
435444
if t == math.Trunc(t) && math.Abs(t) < 1<<53 {
436445
return int64(t)

‎pkg/reservation/evaluator_test.go‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,27 @@ func TestEvaluateFailsClosed(t *testing.T) {
277277
if _, err := tiny.EvaluateJSON(nil, env); err == nil {
278278
t.Error("small negative reservation: expected an error, not a zero")
279279
}
280+
281+
// A double past the range of an int64 in millis must not be clamped into
282+
// a smaller figure.
283+
for _, expr := range []string{`1e100`, `1e308`, `9.3e15`} {
284+
huge, err := Compile(&Declaration{Resources: map[string]string{"cpu": expr}}, nil)
285+
if err != nil {
286+
t.Fatal(err)
287+
}
288+
if got, err := huge.EvaluateJSON(nil, env); err == nil {
289+
t.Errorf("%s: expected an error, got %v", expr, got)
290+
}
291+
}
292+
finite, err := Compile(&Declaration{Resources: map[string]string{"cpu": `1e3`, "memory": `0.25`}}, nil)
293+
if err != nil {
294+
t.Fatal(err)
295+
}
296+
got, err := finite.EvaluateJSON(nil, env)
297+
if err != nil {
298+
t.Fatal(err)
299+
}
300+
assertList(t, "finite doubles", got, rl("cpu", "1k", "memory", "250m"))
280301
}
281302

282303
func TestEvaluateRejectsValuesOfTheWrongType(t *testing.T) {
@@ -323,6 +344,29 @@ func TestEvaluateRejectsValuesOfTheWrongType(t *testing.T) {
323344
}
324345
}
325346

347+
func TestEvaluateAcceptsIntegralSpellings(t *testing.T) {
348+
env := testEnvironment(t)
349+
// The Kubernetes schema validator accepts an integral number however it
350+
// is spelled, so the evaluator must too.
351+
ev, err := Compile(&Declaration{Resources: map[string]string{"cpu": `values.replicas`}}, shippedSchema(t, "postgres"))
352+
if err != nil {
353+
t.Fatal(err)
354+
}
355+
for _, raw := range []string{`{"replicas": 2}`, `{"replicas": 2.0}`, `{"replicas": 2e0}`, `{"replicas": 0.2e1}`} {
356+
got, err := ev.EvaluateJSON([]byte(raw), env)
357+
if err != nil {
358+
t.Errorf("%s: %v", raw, err)
359+
continue
360+
}
361+
assertList(t, raw, got, rl("cpu", "2"))
362+
}
363+
for _, raw := range []string{`{"replicas": 2.5}`, `{"replicas": 25e-1}`} {
364+
if got, err := ev.EvaluateJSON([]byte(raw), env); err == nil {
365+
t.Errorf("%s: expected an error, got %v", raw, got)
366+
}
367+
}
368+
}
369+
326370
func TestEvaluateChecksUntypedSchemasByShape(t *testing.T) {
327371
env := testEnvironment(t)
328372
// BuildSchema does not require a structural schema to be typed, and

0 commit comments

Comments
 (0)