From d340d14619fd90e16d217058d5bf36a95eb9457e Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Thu, 23 Jul 2026 23:11:19 +0200 Subject: [PATCH 1/2] fix(lib-cloudinfo): type CloudProduct.gpusPerVm as Float to keep fractional-GPU counts CloudInfo reports fractional GPU counts for fractional-GPU instance families (e.g. AWS g6f.2xlarge reports gpusPerVm=0.25). The Integer field truncated these to 0 on JSON decode, making genuine GPU instances indistinguishable from CPU-only ones for consumers that check gpusPerVm > 0. Type gpusPerVm as Float (consistent with memPerVm/onDemandPrice) so fractional counts survive; > 0 then correctly flags any accelerator. Fixes #97 Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Paolo Di Tommaso --- .../io/seqera/cloudinfo/api/CloudProduct.java | 15 ++++++++++++--- .../cloudinfo/api/CloudProductTest.groovy | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/lib-cloudinfo/src/main/java/io/seqera/cloudinfo/api/CloudProduct.java b/lib-cloudinfo/src/main/java/io/seqera/cloudinfo/api/CloudProduct.java index 5338927..33aef52 100644 --- a/lib-cloudinfo/src/main/java/io/seqera/cloudinfo/api/CloudProduct.java +++ b/lib-cloudinfo/src/main/java/io/seqera/cloudinfo/api/CloudProduct.java @@ -37,7 +37,16 @@ public class CloudProduct { private String category; private Integer cpusPerVm; private Float memPerVm; - private Integer gpusPerVm; + /** + * Number of GPUs (accelerators) per instance, as reported by CloudInfo. + * Modelled as a {@link Float} because fractional-GPU families report a + * fraction of a physical accelerator (e.g. AWS {@code g6f.2xlarge} reports + * {@code 0.25}); an integer field would truncate such values to {@code 0} + * and misclassify the instance as CPU-only. A value {@code > 0} (including + * a fraction) means the instance carries an accelerator. Null on legacy + * responses that do not populate it. + */ + private Float gpusPerVm; private Boolean currentGen; private String ntwPerf; private String ntwPerfCategory; @@ -101,11 +110,11 @@ public void setMemPerVm(Float memPerVm) { this.memPerVm = memPerVm; } - public Integer getGpusPerVm() { + public Float getGpusPerVm() { return gpusPerVm; } - public void setGpusPerVm(Integer gpusPerVm) { + public void setGpusPerVm(Float gpusPerVm) { this.gpusPerVm = gpusPerVm; } diff --git a/lib-cloudinfo/src/test/groovy/io/seqera/cloudinfo/api/CloudProductTest.groovy b/lib-cloudinfo/src/test/groovy/io/seqera/cloudinfo/api/CloudProductTest.groovy index 9f2049c..b66ea4e 100644 --- a/lib-cloudinfo/src/test/groovy/io/seqera/cloudinfo/api/CloudProductTest.groovy +++ b/lib-cloudinfo/src/test/groovy/io/seqera/cloudinfo/api/CloudProductTest.groovy @@ -42,6 +42,25 @@ class CloudProductTest extends Specification { decoded.features == ['a100', 'gpu', 'nvidia', 'sched', 'ssd', 'x86'] } + def 'fractional gpusPerVm survives Jackson decode (does not truncate to 0)'() { + given: 'a GPU instance reporting a fractional accelerator, e.g. AWS g6f.2xlarge' + def json = '{"type":"g6f.2xlarge","category":"GPU instance","cpusPerVm":8,"gpusPerVm":0.25}' + + when: + def decoded = ENCODER.decode(json) + + then: + decoded.type == 'g6f.2xlarge' + decoded.gpusPerVm == 0.25f + decoded.gpusPerVm > 0 + } + + def 'whole and absent gpusPerVm decode correctly'() { + expect: + ENCODER.decode('{"type":"p4d.24xlarge","gpusPerVm":8}').gpusPerVm == 8.0f + ENCODER.decode('{"type":"m5.large","cpusPerVm":2}').gpusPerVm == null + } + def 'features field defaults to null on a freshly-constructed product'() { when: def product = new CloudProduct() From e84ffa6959d85782c063be8fa97df8c862a24aff Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Fri, 24 Jul 2026 08:36:46 +0200 Subject: [PATCH 2/2] Address review: bump lib-cloudinfo to 2.0.0 for breaking gpusPerVm type change - VERSION 1.2.2 -> 2.0.0 (breaking Integer->Float on published getter/setter) - changelog entry flagged BREAKING - README dependency snippet -> 2.0.0 - test: pin encode direction (whole counts serialise as 8.0) Co-Authored-By: Claude Opus 4.8 (1M context) --- lib-cloudinfo/README.md | 2 +- lib-cloudinfo/VERSION | 2 +- lib-cloudinfo/changelog.txt | 3 +++ .../io/seqera/cloudinfo/api/CloudProductTest.groovy | 10 ++++++++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib-cloudinfo/README.md b/lib-cloudinfo/README.md index e18c7ed..3d2ed7c 100644 --- a/lib-cloudinfo/README.md +++ b/lib-cloudinfo/README.md @@ -10,7 +10,7 @@ Add the dependency to your `build.gradle`: ```gradle dependencies { - implementation 'io.seqera:lib-cloudinfo:1.2.2' + implementation 'io.seqera:lib-cloudinfo:2.0.0' } ``` diff --git a/lib-cloudinfo/VERSION b/lib-cloudinfo/VERSION index 23aa839..227cea2 100644 --- a/lib-cloudinfo/VERSION +++ b/lib-cloudinfo/VERSION @@ -1 +1 @@ -1.2.2 +2.0.0 diff --git a/lib-cloudinfo/changelog.txt b/lib-cloudinfo/changelog.txt index 7221586..e84dd88 100644 --- a/lib-cloudinfo/changelog.txt +++ b/lib-cloudinfo/changelog.txt @@ -1,5 +1,8 @@ # lib-cloudinfo changelog +2.0.0 - 24 Jul 2026 +- BREAKING: type CloudProduct.gpusPerVm as Float instead of Integer. CloudInfo reports fractional GPU counts for fractional-GPU instance families (e.g. AWS g6f.2xlarge reports gpusPerVm=0.25), which the Integer field truncated to 0, making a real GPU instance indistinguishable from a CPU-only one for any `gpusPerVm > 0` check. Consumers that auto-unbox getGpusPerVm() to int (notably sched) must consume Float and treat `> 0` as GPU. Whole counts now serialise as 8.0 instead of 8 (PR #98, closes #97). + 1.2.2 - 6 Jul 2026 - Add AcceleratorFeatures: provider-agnostic helpers over the accelerator-related CloudProduct.features tokens (hasGpu, isNvidia, hasFpga) plus the token constants (gpu, nvidia, amd, habana, fpga, xilinx). Centralises classification of CloudInfo-returned accelerator data with the model instead of duplicating it in consumers. diff --git a/lib-cloudinfo/src/test/groovy/io/seqera/cloudinfo/api/CloudProductTest.groovy b/lib-cloudinfo/src/test/groovy/io/seqera/cloudinfo/api/CloudProductTest.groovy index b66ea4e..8e14357 100644 --- a/lib-cloudinfo/src/test/groovy/io/seqera/cloudinfo/api/CloudProductTest.groovy +++ b/lib-cloudinfo/src/test/groovy/io/seqera/cloudinfo/api/CloudProductTest.groovy @@ -61,6 +61,16 @@ class CloudProductTest extends Specification { ENCODER.decode('{"type":"m5.large","cpusPerVm":2}').gpusPerVm == null } + def 'whole gpusPerVm now serialises as a float (8.0, not 8)'() { + given: + def product = new CloudProduct() + product.type = 'p4d.24xlarge' + product.gpusPerVm = 8.0f + + expect: + ENCODER.encode(product).contains('"gpusPerVm":8.0') + } + def 'features field defaults to null on a freshly-constructed product'() { when: def product = new CloudProduct()