Skip to content

Commit 86152ef

Browse files
committed
Ensure roundtrip success for Quantities
1 parent b0b6cf8 commit 86152ef

File tree

3 files changed

+207
-2
lines changed

3 files changed

+207
-2
lines changed

pkg/resources/requests.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,27 @@ func ResourceQuantity(name corev1.ResourceName, v int64) resource.Quantity {
108108
case corev1.ResourceCPU:
109109
return *resource.NewMilliQuantity(v, resource.DecimalSI)
110110
case corev1.ResourceMemory, corev1.ResourceEphemeralStorage:
111-
return *resource.NewQuantity(v, resource.BinarySI)
111+
return parseMaybeBinary(v)
112112
default:
113113
if strings.HasPrefix(string(name), corev1.ResourceHugePagesPrefix) {
114-
return *resource.NewQuantity(v, resource.BinarySI)
114+
return parseMaybeBinary(v)
115115
}
116116
return *resource.NewQuantity(v, resource.DecimalSI)
117117
}
118118
}
119119

120+
// Ensures that the resulting Quantity roundtrips.
121+
// For example, 1000000 will be decimal (1M), while 1048576 will be binary (1Mi)
122+
func parseMaybeBinary(v int64) resource.Quantity {
123+
binary := *resource.NewQuantity(v, resource.BinarySI)
124+
final, err := resource.ParseQuantity(binary.String())
125+
if err != nil {
126+
// Should never happen
127+
return binary
128+
}
129+
return final
130+
}
131+
120132
func ResourceQuantityString(name corev1.ResourceName, v int64) string {
121133
rq := ResourceQuantity(name, v)
122134
return rq.String()

pkg/resources/requests_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ limitations under the License.
1717
package resources
1818

1919
import (
20+
"encoding/json"
2021
"math"
2122
"testing"
2223

2324
corev1 "k8s.io/api/core/v1"
25+
"k8s.io/apimachinery/pkg/api/resource"
2426
)
2527

2628
func TestCountIn(t *testing.T) {
@@ -121,3 +123,76 @@ func TestCountIn(t *testing.T) {
121123
})
122124
}
123125
}
126+
127+
func TestResourceQuantityRoundTrips(t *testing.T) {
128+
cases := map[string]struct {
129+
resource corev1.ResourceName
130+
value int64
131+
expected string
132+
}{
133+
"1": {
134+
resource: corev1.ResourceMemory,
135+
value: 1,
136+
expected: "1",
137+
},
138+
"1k": {
139+
resource: corev1.ResourceMemory,
140+
value: 1000,
141+
expected: "1k",
142+
},
143+
"1M": {
144+
resource: corev1.ResourceMemory,
145+
value: 1000000,
146+
expected: "1M",
147+
},
148+
"1.5M": {
149+
resource: corev1.ResourceMemory,
150+
value: 1500000,
151+
expected: "1500k",
152+
},
153+
"1G": {
154+
resource: corev1.ResourceMemory,
155+
value: 1000000000,
156+
expected: "1G",
157+
},
158+
"1Ki": {
159+
resource: corev1.ResourceMemory,
160+
value: 1024,
161+
expected: "1Ki",
162+
},
163+
"1Mi": {
164+
resource: corev1.ResourceMemory,
165+
value: 1024 * 1024,
166+
expected: "1Mi",
167+
},
168+
"1.5Mi": {
169+
resource: corev1.ResourceMemory,
170+
value: 1024 * 1024 * 1.5,
171+
expected: "1536Ki",
172+
},
173+
"1Gi": {
174+
resource: corev1.ResourceMemory,
175+
value: 1024 * 1024 * 1024,
176+
expected: "1Gi",
177+
},
178+
}
179+
for name, tc := range cases {
180+
t.Run(name, func(t *testing.T) {
181+
quantity := ResourceQuantity(tc.resource, tc.value)
182+
initial := quantity.String()
183+
184+
if initial != tc.expected {
185+
t.Errorf("unexpected result, want=%s, got=%s", tc.expected, initial)
186+
}
187+
188+
serialized, _ := json.Marshal(quantity)
189+
var deserialized resource.Quantity
190+
_ = json.Unmarshal(serialized, &deserialized)
191+
roundtrip := deserialized.String()
192+
193+
if roundtrip != tc.expected {
194+
t.Errorf("unexpected result after roundtrip, want=%s, got=%s", tc.expected, roundtrip)
195+
}
196+
})
197+
}
198+
}

test/integration/singlecluster/controller/admissionchecks/provisioning/provisioning_test.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1886,5 +1886,123 @@ var _ = ginkgo.Describe("Provisioning with scheduling", ginkgo.Ordered, ginkgo.C
18861886
}, util.Timeout, util.Interval).Should(gomega.Succeed())
18871887
})
18881888
})
1889+
1890+
ginkgo.It("Should be successfully re-admitted on another flavor with a decimal memory request", func() {
1891+
ginkgo.By("Set up ClusterQueue and LocalQueue", func() {
1892+
cq = utiltestingapi.MakeClusterQueue("cluster-queue").
1893+
Preemption(kueue.ClusterQueuePreemption{
1894+
WithinClusterQueue: kueue.PreemptionPolicyLowerPriority,
1895+
}).
1896+
ResourceGroup(
1897+
*utiltestingapi.MakeFlavorQuotas(rf1.Name).
1898+
Resource(corev1.ResourceCPU, "0.75").
1899+
Resource(corev1.ResourceMemory, "5G").
1900+
Obj(),
1901+
*utiltestingapi.MakeFlavorQuotas(rf2.Name).
1902+
Resource(corev1.ResourceCPU, "0.5").
1903+
Resource(corev1.ResourceMemory, "5G").
1904+
Obj(),
1905+
).
1906+
AdmissionCheckStrategy(kueue.AdmissionCheckStrategyRule{
1907+
Name: ac1Ref,
1908+
OnFlavors: []kueue.ResourceFlavorReference{flavor1Ref},
1909+
}).
1910+
Obj()
1911+
util.MustCreate(ctx, k8sClient, cq)
1912+
util.ExpectClusterQueuesToBeActive(ctx, k8sClient, cq)
1913+
1914+
lq = utiltestingapi.MakeLocalQueue("queue", ns.Name).ClusterQueue(cq.Name).Obj()
1915+
util.MustCreate(ctx, k8sClient, lq)
1916+
util.ExpectLocalQueuesToBeActive(ctx, k8sClient, lq)
1917+
})
1918+
1919+
ginkgo.By("submit the Job", func() {
1920+
job1 := testingjob.MakeJob("job1", ns.Name).
1921+
Queue(kueue.LocalQueueName(lq.Name)).
1922+
Request(corev1.ResourceCPU, "500m").
1923+
Request(corev1.ResourceMemory, "220M").
1924+
Obj()
1925+
util.MustCreate(ctx, k8sClient, job1)
1926+
ginkgo.DeferCleanup(func() {
1927+
util.ExpectObjectToBeDeleted(ctx, k8sClient, job1, true)
1928+
})
1929+
wl1Key = types.NamespacedName{Name: workloadjob.GetWorkloadNameForJob(job1.Name, job1.UID), Namespace: ns.Name}
1930+
})
1931+
1932+
ginkgo.By("await for wl1 to be created", func() {
1933+
gomega.Eventually(func(g gomega.Gomega) {
1934+
g.Expect(k8sClient.Get(ctx, wl1Key, &wlObj)).Should(gomega.Succeed())
1935+
}, util.Timeout, util.Interval).Should(gomega.Succeed())
1936+
})
1937+
1938+
ginkgo.By("await for wl1 to have QuotaReserved on flavor-1", func() {
1939+
gomega.Eventually(func(g gomega.Gomega) {
1940+
gomega.Expect(k8sClient.Get(ctx, wl1Key, &wlObj)).Should(gomega.Succeed())
1941+
g.Expect(workload.Status(&wlObj)).To(gomega.Equal(workload.StatusQuotaReserved))
1942+
psa := wlObj.Status.Admission.PodSetAssignments
1943+
g.Expect(psa).Should(gomega.HaveLen(1))
1944+
g.Expect(psa[0].Flavors).To(gomega.Equal(map[corev1.ResourceName]kueue.ResourceFlavorReference{
1945+
corev1.ResourceCPU: flavor1Ref,
1946+
corev1.ResourceMemory: flavor1Ref,
1947+
}))
1948+
}, util.Timeout, util.Interval).Should(gomega.Succeed())
1949+
})
1950+
1951+
ginkgo.By("await for the ProvisioningRequest on flavor-1 to be created", func() {
1952+
provReqKey = types.NamespacedName{
1953+
Namespace: wl1Key.Namespace,
1954+
Name: provisioning.ProvisioningRequestName(wl1Key.Name, ac1Ref, 1),
1955+
}
1956+
1957+
gomega.Eventually(func(g gomega.Gomega) {
1958+
g.Expect(k8sClient.Get(ctx, provReqKey, &createdRequest)).Should(gomega.Succeed())
1959+
}, util.Timeout, util.Interval).Should(gomega.Succeed())
1960+
})
1961+
1962+
ginkgo.By("set the ProvisioningRequest on flavor-1 as Provisioned", func() {
1963+
gomega.Eventually(func(g gomega.Gomega) {
1964+
g.Expect(k8sClient.Get(ctx, provReqKey, &createdRequest)).Should(gomega.Succeed())
1965+
apimeta.SetStatusCondition(&createdRequest.Status.Conditions, metav1.Condition{
1966+
Type: autoscaling.Provisioned,
1967+
Status: metav1.ConditionTrue,
1968+
Reason: autoscaling.Provisioned,
1969+
})
1970+
g.Expect(k8sClient.Status().Update(ctx, &createdRequest)).Should(gomega.Succeed())
1971+
}, util.Timeout, util.Interval).Should(gomega.Succeed())
1972+
})
1973+
1974+
ginkgo.By("await for wl1 to be Admitted", func() {
1975+
gomega.Eventually(func(g gomega.Gomega) {
1976+
gomega.Expect(k8sClient.Get(ctx, wl1Key, &wlObj)).Should(gomega.Succeed())
1977+
g.Expect(workload.Status(&wlObj)).To(gomega.Equal(workload.StatusAdmitted))
1978+
}, util.Timeout, util.Interval).Should(gomega.Succeed())
1979+
})
1980+
1981+
ginkgo.By("submit a high-priority job2", func() {
1982+
job2 := testingjob.MakeJob("job2", ns.Name).
1983+
Queue(kueue.LocalQueueName(lq.Name)).
1984+
WorkloadPriorityClass(priorityClassName).
1985+
Request(corev1.ResourceCPU, "750m").
1986+
Obj()
1987+
util.MustCreate(ctx, k8sClient, job2)
1988+
ginkgo.DeferCleanup(func() {
1989+
util.ExpectObjectToBeDeleted(ctx, k8sClient, job2, true)
1990+
})
1991+
wl2Key = types.NamespacedName{Name: workloadjob.GetWorkloadNameForJob(job2.Name, job2.UID), Namespace: ns.Name}
1992+
})
1993+
1994+
ginkgo.By("await for wl1 to be Admitted on flavor-2", func() {
1995+
gomega.Eventually(func(g gomega.Gomega) {
1996+
gomega.Expect(k8sClient.Get(ctx, wl1Key, &wlObj)).Should(gomega.Succeed())
1997+
g.Expect(workload.Status(&wlObj)).To(gomega.Equal(workload.StatusAdmitted))
1998+
psa := wlObj.Status.Admission.PodSetAssignments
1999+
g.Expect(psa).Should(gomega.HaveLen(1))
2000+
g.Expect(psa[0].Flavors).To(gomega.Equal(map[corev1.ResourceName]kueue.ResourceFlavorReference{
2001+
corev1.ResourceCPU: flavor2Ref,
2002+
corev1.ResourceMemory: flavor2Ref,
2003+
}))
2004+
}, util.Timeout, util.Interval).Should(gomega.Succeed())
2005+
})
2006+
})
18892007
})
18902008
})

0 commit comments

Comments
 (0)