diff --git a/apps/provider-inventory/src/repositories/bid-screening/bid-screening.aggregator.spec.ts b/apps/provider-inventory/src/repositories/bid-screening/bid-screening.aggregator.spec.ts index 1a700986a..0c01befe2 100644 --- a/apps/provider-inventory/src/repositories/bid-screening/bid-screening.aggregator.spec.ts +++ b/apps/provider-inventory/src/repositories/bid-screening/bid-screening.aggregator.spec.ts @@ -54,21 +54,65 @@ describe(aggregateCriteria.name, () => { expect(c.totalEphemeralStorage).toBe(1000n); }); - it("skips ram-class storage from totalMemory (issue 4 territory)", () => { + it("rolls ram-class volumes into totalMemory and leaves storage totals untouched", () => { const c = aggregateCriteria( [ makeUnit({ memory: 100n, - count: 1, - storage: [{ name: "shm", quantity: 99_999n, attributes: [{ key: "class", value: "ram" }] }] + count: 3, + storage: [{ name: "shm", quantity: 50n, attributes: [{ key: "class", value: "ram" }] }] }) ], makeRequirements() ); - expect(c.totalMemory).toBe(100n); + expect(c.totalMemory).toBe((100n + 50n) * 3n); expect(c.totalEphemeralStorage).toBe(0n); expect(c.totalPersistentStorage).toBe(0n); }); + + it("sums multiple ram-class volumes on a single unit into totalMemory", () => { + const c = aggregateCriteria( + [ + makeUnit({ + memory: 100n, + count: 1, + storage: [ + { name: "shm1", quantity: 25n, attributes: [{ key: "class", value: "ram" }] }, + { name: "shm2", quantity: 75n, attributes: [{ key: "class", value: "ram" }] } + ] + }) + ], + makeRequirements() + ); + expect(c.totalMemory).toBe(200n); + }); + + it("partitions ram, ephemeral, and persistent volumes on the same unit without double-counting", () => { + const c = aggregateCriteria( + [ + makeUnit({ + memory: 100n, + count: 2, + storage: [ + { name: "shm", quantity: 50n, attributes: [{ key: "class", value: "ram" }] }, + { name: "scratch", quantity: 500n, attributes: [] }, + { + name: "data", + quantity: 1000n, + attributes: [ + { key: "persistent", value: "true" }, + { key: "class", value: "beta2" } + ] + } + ] + }) + ], + makeRequirements() + ); + expect(c.totalMemory).toBe((100n + 50n) * 2n); + expect(c.totalEphemeralStorage).toBe(500n * 2n); + expect(c.totalPersistentStorage).toBe(1000n * 2n); + }); }); describe("maxPerReplica", () => { @@ -88,6 +132,21 @@ describe(aggregateCriteria.name, () => { expect(c.maxPerReplicaMemory).toBe(50n); }); + it("includes ram-class volume sizes when picking maxPerReplicaMemory", () => { + const c = aggregateCriteria( + [ + makeUnit({ memory: 100n, count: 1 }), + makeUnit({ + memory: 30n, + count: 1, + storage: [{ name: "shm", quantity: 200n, attributes: [{ key: "class", value: "ram" }] }] + }) + ], + makeRequirements() + ); + expect(c.maxPerReplicaMemory).toBe(230n); + }); + it("picks max of unit.gpu across units", () => { const c = aggregateCriteria([makeUnit({ gpu: 1n, count: 8 }), makeUnit({ gpu: 4n, count: 1 })], makeRequirements()); expect(c.maxPerReplicaGpu).toBe(4n); diff --git a/apps/provider-inventory/src/repositories/bid-screening/bid-screening.aggregator.ts b/apps/provider-inventory/src/repositories/bid-screening/bid-screening.aggregator.ts index 82ae673e5..ab11ec10e 100644 --- a/apps/provider-inventory/src/repositories/bid-screening/bid-screening.aggregator.ts +++ b/apps/provider-inventory/src/repositories/bid-screening/bid-screening.aggregator.ts @@ -37,27 +37,28 @@ export function aggregateCriteria(resourceUnits: RequestedResourceUnit[], requir for (const unit of resourceUnits) { const count = BigInt(unit.count); const cpu = unit.resources.cpu.units; - const memory = unit.resources.memory.quantity; const gpu = unit.resources.gpu.units; - totalCpu += cpu * count; - totalMemory += memory * count; - totalGpu += gpu * count; - - if (cpu > maxPerReplicaCpu) maxPerReplicaCpu = cpu; - if (memory > maxPerReplicaMemory) maxPerReplicaMemory = memory; - if (gpu > maxPerReplicaGpu) maxPerReplicaGpu = gpu; - + let effectiveMemory = unit.resources.memory.quantity; for (const vol of unit.resources.storage) { const parsed = parseStorageAttributes(vol.attributes); if (parsed.classification === "persistent") { totalPersistentStorage += vol.quantity * count; } else if (parsed.classification === "ephemeral") { totalEphemeralStorage += vol.quantity * count; + } else if (parsed.classification === "ram") { + effectiveMemory += vol.quantity; } - // ram volumes intentionally skipped — issue 4 will add them to totalMemory } + totalCpu += cpu * count; + totalMemory += effectiveMemory * count; + totalGpu += gpu * count; + + if (cpu > maxPerReplicaCpu) maxPerReplicaCpu = cpu; + if (effectiveMemory > maxPerReplicaMemory) maxPerReplicaMemory = effectiveMemory; + if (gpu > maxPerReplicaGpu) maxPerReplicaGpu = gpu; + units.push({ gpuTokens: collectGpuTokens(unit.resources.gpu), persistentClasses: collectPersistentStorageTokens(unit.resources.storage) diff --git a/apps/provider-inventory/src/repositories/bid-screening/bid-screening.repository.integration.ts b/apps/provider-inventory/src/repositories/bid-screening/bid-screening.repository.integration.ts index 6fd60dfc6..71ebbc5e6 100644 --- a/apps/provider-inventory/src/repositories/bid-screening/bid-screening.repository.integration.ts +++ b/apps/provider-inventory/src/repositories/bid-screening/bid-screening.repository.integration.ts @@ -38,6 +38,37 @@ describe(BidScreeningRepository.name, () => { expect(owners(rows)).toEqual(["akash1roomy"]); }); + + it("excludes providers whose memory headroom covers the bare unit but not the combined memory + ram-class volume demand", async () => { + // Workload requests 100 bytes of memory + a 200-byte ram-class volume → 300 bytes per replica. + const replicaMemory = 100n; + const ramVolume = 200n; + const effectivePerReplica = replicaMemory + ramVolume; + + await seed({ + owner: "akash1tightOnMemory", + totalAvailableMemory: effectivePerReplica - 1n, + maxNodeFreeMemory: effectivePerReplica - 1n + }); + await seed({ + owner: "akash1fits", + totalAvailableMemory: effectivePerReplica, + maxNodeFreeMemory: effectivePerReplica + }); + + const rows = await repository.findCandidates( + [ + unit({ + memory: replicaMemory, + count: 1, + storage: [{ name: "shm", quantity: ramVolume, attributes: [{ key: "class", value: "ram" }] }] + }) + ], + requirements() + ); + + expect(owners(rows)).toEqual(["akash1fits"]); + }); }); describe("signedBy", () => {