Skip to content

Commit 2f9805a

Browse files
authored
Add helper function and example for extensible accounting (#28)
1 parent 1376694 commit 2f9805a

File tree

9 files changed

+230
-18
lines changed

9 files changed

+230
-18
lines changed

Dockerfile

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,20 @@
1919
#___INFO__MARK_END_NEW__
2020

2121
#FROM hpcgridware/clusterscheduler-latest-ubuntu2204:latest
22-
FROM hpcgridware/clusterscheduler-latest-ubuntu2204:V901_TAG
22+
#FROM hpcgridware/clusterscheduler-latest-ubuntu2204:V901_TAG
23+
#FROM hpcgridware/ocs-ubuntu2204:9.0.2
24+
FROM hpcgridware/ocs-ubuntu2204-nightly:20250203
2325

2426
RUN mkdir -p /opt/helpers
2527

2628
COPY autoinstall.template /opt/helpers/
2729
COPY installer.sh /opt/helpers/
2830
COPY entrypoint.sh /entrypoint.sh
2931

30-
ARG GOLANG_VERSION=1.23.1
32+
ARG GOLANG_VERSION=1.23.5
3133

3234
RUN apt-get update && \
33-
apt-get install -y curl wget git gcc make vim libhwloc-dev hwloc software-properties-common && \
35+
apt-get install -y curl wget git gcc make vim libhwloc-dev hwloc software-properties-common man-db && \
3436
add-apt-repository -y ppa:apptainer/ppa && \
3537
apt-get update && \
3638
apt-get install -y apptainer

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ CONTAINER_NAME = $(IMAGE_NAME)
2727
.PHONY: build
2828
build:
2929
@echo "Building the Open Cluster Scheduler image..."
30-
docker build -t $(IMAGE_NAME):$(IMAGE_TAG) .
30+
docker build --platform=linux/amd64 -t $(IMAGE_NAME):$(IMAGE_TAG) .
3131

3232
# Running apptainers in containers requires more permissions. You can drop
3333
# the --privileged flag and the --cap-add SYS_ADMIN flag if you don't need
@@ -43,7 +43,7 @@ run: build
4343
@echo "Running the Open Cluster Scheduler container..."
4444
@echo "For a new installation, you need to remove the ./installation subdirectory first."
4545
mkdir -p ./installation
46-
docker run -p 7070:7070 -p 9464:9464 --rm -it -h master --name $(CONTAINER_NAME) -v ./installation:/opt/cs-install -v ./:/root/go/src/github.com/hpc-gridware/go-clusterscheduler $(IMAGE_NAME):$(IMAGE_TAG) /bin/bash
46+
docker run --platform=linux/amd64 -p 7070:7070 -p 9464:9464 --rm -it -h master --name $(CONTAINER_NAME) -v ./installation:/opt/cs-install -v ./:/root/go/src/github.com/hpc-gridware/go-clusterscheduler $(IMAGE_NAME):$(IMAGE_TAG) /bin/bash
4747

4848
# Running apptainers in containers requires more permissions. You can drop
4949
# the --privileged flag and the --cap-add SYS_ADMIN flag if you don't need
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/hpc-gridware/go-clusterscheduler/pkg/accounting"
7+
"golang.org/x/exp/rand"
8+
)
9+
10+
// This is an example epilog which adds arbitrary accounting records for
11+
// jobs to the system. In order to use the example additional accounting records
12+
// for the "test" namespace - a namespace is just a subsection in the JSON
13+
// accounting file - (with usage values with the "tst prefix) needs to
14+
// be enabled:
15+
//
16+
// qconf -mconf
17+
// ..
18+
// reporting_params ... usage_patterns=test:tst*
19+
//
20+
// Compile this example (go build) and copy the binary to your cluster
21+
// scheduler installation directory.
22+
//
23+
// Then configure your queue epilog script with sgeadmin@/path/to/flexibleaccounting
24+
// Ensure that the binary is executable. The sgeadmin is the correct user
25+
// to use for this (check the owner of $SGE_ROOT).
26+
//
27+
// When configured correctly the following command should output the
28+
// accounting records:
29+
//
30+
// qacct -j <job_id>
31+
//
32+
// You should see the two tst_random* records in the qacct after your job
33+
// has finished.
34+
//
35+
// tst_random1 351.000
36+
// tst_random2 121.000
37+
func main() {
38+
usageFilePath, err := accounting.GetUsageFilePath()
39+
if err != nil {
40+
fmt.Printf("Failed to get usage file path: %v\n", err)
41+
return
42+
}
43+
err = accounting.AppendToAccounting(usageFilePath, []accounting.Record{
44+
{
45+
AccountingKey: "tst_random1",
46+
AccountingValue: rand.Intn(1000),
47+
},
48+
{
49+
AccountingKey: "tst_random2",
50+
AccountingValue: rand.Intn(1000),
51+
},
52+
})
53+
if err != nil {
54+
fmt.Printf("Failed to append to accounting: %v\n", err)
55+
}
56+
}

go.mod

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ require (
1515
go.opentelemetry.io/otel/sdk v1.33.0
1616
go.opentelemetry.io/otel/sdk/log v0.9.0
1717
go.opentelemetry.io/otel/sdk/metric v1.33.0
18+
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c
1819
)
1920

2021
require (
@@ -27,9 +28,9 @@ require (
2728
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
2829
go.opentelemetry.io/otel/metric v1.33.0 // indirect
2930
go.opentelemetry.io/otel/trace v1.33.0 // indirect
30-
golang.org/x/net v0.30.0 // indirect
31-
golang.org/x/sys v0.28.0 // indirect
32-
golang.org/x/text v0.19.0 // indirect
33-
golang.org/x/tools v0.26.0 // indirect
31+
golang.org/x/net v0.34.0 // indirect
32+
golang.org/x/sys v0.29.0 // indirect
33+
golang.org/x/text v0.21.0 // indirect
34+
golang.org/x/tools v0.29.0 // indirect
3435
gopkg.in/yaml.v3 v3.0.1 // indirect
3536
)

go.sum

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,16 @@ go.opentelemetry.io/otel/sdk/metric v1.33.0 h1:Gs5VK9/WUJhNXZgn8MR6ITatvAmKeIuCt
5353
go.opentelemetry.io/otel/sdk/metric v1.33.0/go.mod h1:dL5ykHZmm1B1nVRk9dDjChwDmt81MjVp3gLkQRwKf/Q=
5454
go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s=
5555
go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
56-
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
57-
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
58-
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
59-
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
60-
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
61-
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
62-
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
63-
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
56+
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
57+
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
58+
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
59+
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
60+
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
61+
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
62+
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
63+
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
64+
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
65+
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
6466
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
6567
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
6668
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

installer.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,11 @@ fi
3838
echo "Open Cluster Scheduler is not yet installed in ${MOUNT_DIR}. Starting installation."
3939

4040
# Copy unpacked Open Cluster Scheduler package to ${MOUNT_DIR}
41-
cp -r /opt/cs/* ${MOUNT_DIR}
41+
if [ -d /opt/ocs ]; then
42+
cp -r /opt/ocs/* "${MOUNT_DIR}"
43+
else
44+
cp -r /opt/cs/* "${MOUNT_DIR}"
45+
fi
4246

4347
cd ${MOUNT_DIR}
4448

pkg/accounting/accounting.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*___INFO__MARK_BEGIN__*/
2+
/*************************************************************************
3+
* Copyright 2025 HPC-Gridware GmbH
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
************************************************************************/
18+
/*___INFO__MARK_END__*/
19+
20+
package accounting
21+
22+
import (
23+
"fmt"
24+
"os"
25+
"path/filepath"
26+
)
27+
28+
// Record is a key-value pair representing an accounting record.
29+
type Record struct {
30+
AccountingKey string
31+
AccountingValue int
32+
}
33+
34+
// AppendToAccounting appends accounting records to the usage file so
35+
// that it gets send to the execution daemon. Typically sgeadmin user
36+
// (Cluster Scheduler install user).
37+
// Hence you need to prefix your epilog script with sgeadmin@/path/to/epilog.
38+
func AppendToAccounting(usageFilePath string, records []Record) error {
39+
f, err := os.OpenFile(usageFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
40+
if err != nil {
41+
return err
42+
}
43+
defer f.Close()
44+
45+
var nl string
46+
47+
for _, record := range records {
48+
nl += fmt.Sprintf("%s=%d\n",
49+
record.AccountingKey,
50+
record.AccountingValue)
51+
}
52+
53+
_, err = f.WriteString(nl)
54+
return err
55+
}
56+
57+
// GetUsageFilePath returns the path to the job usage file in the
58+
// job spool directory.
59+
func GetUsageFilePath() (string, error) {
60+
jobSpoolDir := os.Getenv("SGE_JOB_SPOOL_DIR")
61+
if jobSpoolDir == "" {
62+
return "", fmt.Errorf("SGE_JOB_SPOOL_DIR is not set")
63+
}
64+
return filepath.Join(jobSpoolDir, "usage"), nil
65+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*___INFO__MARK_BEGIN__*/
2+
/*************************************************************************
3+
* Copyright 2025 HPC-Gridware GmbH
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
************************************************************************/
18+
/*___INFO__MARK_END__*/
19+
20+
package accounting_test
21+
22+
import (
23+
"testing"
24+
25+
. "github.com/onsi/ginkgo/v2"
26+
. "github.com/onsi/gomega"
27+
)
28+
29+
func TestAccounting(t *testing.T) {
30+
RegisterFailHandler(Fail)
31+
RunSpecs(t, "Accounting Suite")
32+
}

pkg/accounting/accounting_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*___INFO__MARK_BEGIN__*/
2+
/*************************************************************************
3+
* Copyright 2025 HPC-Gridware GmbH
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
************************************************************************/
18+
/*___INFO__MARK_END__*/
19+
20+
package accounting_test
21+
22+
import (
23+
"os"
24+
25+
. "github.com/onsi/ginkgo/v2"
26+
. "github.com/onsi/gomega"
27+
28+
"github.com/hpc-gridware/go-clusterscheduler/pkg/accounting"
29+
)
30+
31+
var _ = Describe("Accounting", func() {
32+
33+
Context("GetUsageFilePath", func() {
34+
35+
It("should return the usage file path", func() {
36+
os.Unsetenv("SGE_JOB_SPOOL_DIR")
37+
usageFilePath, err := accounting.GetUsageFilePath()
38+
Expect(err).To(HaveOccurred())
39+
Expect(usageFilePath).To(Equal(""))
40+
})
41+
42+
It("should return the usage file path", func() {
43+
os.Setenv("SGE_JOB_SPOOL_DIR", "/var/spool/gridengine/job_spool")
44+
usageFilePath, err := accounting.GetUsageFilePath()
45+
Expect(err).NotTo(HaveOccurred())
46+
Expect(usageFilePath).To(Equal("/var/spool/gridengine/job_spool/usage"))
47+
})
48+
49+
})
50+
})

0 commit comments

Comments
 (0)