You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by la...@apache.org on 2026/09/01 00:23:51 UTC

(airavata-custos) 01/02: Decode SLURM TRES counts as fractional values

This is an automated email from the ASF dual-hosted git repository.

lahirujayathilake pushed a commit to branch billing-tres
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git

commit 8ac79417cb829bc8ed1e85c2e581ac7159a0efc9
Author: lahiruj <la...@gmail.com>
AuthorDate: Thu Aug 13 01:04:52 2026 -0400

    Decode SLURM TRES counts as fractional values
---
 .../internal/subscribers/association_writer.go            |  6 +++---
 connectors/SLURM/Rest-Client/pkg/client/tres.go           |  4 ++--
 connectors/SLURM/Rest-Client/pkg/client/types.go          |  8 +++++---
 connectors/SLURM/Rest-Client/pkg/client/types_test.go     | 15 +++++++++++++++
 4 files changed, 25 insertions(+), 8 deletions(-)

diff --git a/connectors/SLURM/Association-Mapper/internal/subscribers/association_writer.go b/connectors/SLURM/Association-Mapper/internal/subscribers/association_writer.go
index 5a14265ce..af5676d02 100644
--- a/connectors/SLURM/Association-Mapper/internal/subscribers/association_writer.go
+++ b/connectors/SLURM/Association-Mapper/internal/subscribers/association_writer.go
@@ -33,9 +33,9 @@ import (
 // and name; a plain resource like "cpu" has no name.
 func tresFor(resourceType string, count int64) client.TRES {
 	if typ, name, ok := strings.Cut(resourceType, "/"); ok {
-		return client.TRES{Type: typ, Name: name, Count: count}
+		return client.TRES{Type: typ, Name: name, Count: float64(count)}
 	}
-	return client.TRES{Type: resourceType, Count: count}
+	return client.TRES{Type: resourceType, Count: float64(count)}
 }
 
 // errNotProvisioned means the account does not exist on the cluster yet, so
@@ -66,7 +66,7 @@ func sameTRES(want, got []client.TRES) bool {
 	if len(want) != len(got) {
 		return false
 	}
-	counts := make(map[string]int64, len(want))
+	counts := make(map[string]float64, len(want))
 	for _, t := range want {
 		counts[t.Type] = t.Count
 	}
diff --git a/connectors/SLURM/Rest-Client/pkg/client/tres.go b/connectors/SLURM/Rest-Client/pkg/client/tres.go
index 9a7120398..4c8154a0b 100644
--- a/connectors/SLURM/Rest-Client/pkg/client/tres.go
+++ b/connectors/SLURM/Rest-Client/pkg/client/tres.go
@@ -38,9 +38,9 @@ func ParseTRES(s string) ([]TRES, error) {
 		if len(kv) != 2 {
 			return nil, fmt.Errorf("malformed TRES entry %q (want key=value)", p)
 		}
-		n, err := strconv.ParseInt(strings.TrimSpace(kv[1]), 10, 64)
+		n, err := strconv.ParseFloat(strings.TrimSpace(kv[1]), 64)
 		if err != nil {
-			return nil, fmt.Errorf("TRES count %q not an integer", kv[1])
+			return nil, fmt.Errorf("TRES count %q not a number", kv[1])
 		}
 		t := TRES{Count: n}
 		key := strings.TrimSpace(kv[0])
diff --git a/connectors/SLURM/Rest-Client/pkg/client/types.go b/connectors/SLURM/Rest-Client/pkg/client/types.go
index e15945bed..1b96cb14e 100644
--- a/connectors/SLURM/Rest-Client/pkg/client/types.go
+++ b/connectors/SLURM/Rest-Client/pkg/client/types.go
@@ -33,10 +33,12 @@ type ErrorResponse struct {
 	} `json:"warnings"`
 }
 
+// Billing weights scale a count down to the share of a node the job held, so
+// a count arrives fractional and an integer field fails the whole decode.
 type TRES struct {
-	Type  string `json:"type"`
-	Name  string `json:"name,omitempty"`
-	Count int64  `json:"count"`
+	Type  string  `json:"type"`
+	Name  string  `json:"name,omitempty"`
+	Count float64 `json:"count"`
 }
 
 type Account struct {
diff --git a/connectors/SLURM/Rest-Client/pkg/client/types_test.go b/connectors/SLURM/Rest-Client/pkg/client/types_test.go
index 8f26f9737..7572af603 100644
--- a/connectors/SLURM/Rest-Client/pkg/client/types_test.go
+++ b/connectors/SLURM/Rest-Client/pkg/client/types_test.go
@@ -41,3 +41,18 @@ func TestJobInfoDecodes(t *testing.T) {
 		t.Fatalf("core fields: %+v", j)
 	}
 }
+
+// An integer count field fails the whole decode, dropping every job in the
+// response, not just this one.
+func TestJobInfoDecodesFractionalTresCount(t *testing.T) {
+	raw := `{"account":"proj-a","user":"user-a","partition":"gpu","job_id":8,
+        "time":{"start":1000,"end":4600},
+        "tres":{"allocated":[{"type":"cpu","count":90},{"type":"billing","count":2.8125}]}}`
+	var j JobInfo
+	if err := json.Unmarshal([]byte(raw), &j); err != nil {
+		t.Fatalf("decode: %v", err)
+	}
+	if got := j.Tres.Allocated[1].Count; got != 2.8125 {
+		t.Fatalf("billing count: want 2.8125, got %v", got)
+	}
+}