diff --git a/Dockerfile b/Dockerfile index 9382940..2320a7b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,9 @@ #___INFO__MARK_END_NEW__ #FROM hpcgridware/clusterscheduler-latest-ubuntu2204:latest -FROM hpcgridware/clusterscheduler-latest-ubuntu2204:V901_TAG +#FROM hpcgridware/clusterscheduler-latest-ubuntu2204:V901_TAG +#FROM hpcgridware/ocs-ubuntu2204:9.0.2 +FROM hpcgridware/ocs-ubuntu2204-nightly:20250203 RUN mkdir -p /opt/helpers @@ -27,10 +29,10 @@ COPY autoinstall.template /opt/helpers/ COPY installer.sh /opt/helpers/ COPY entrypoint.sh /entrypoint.sh -ARG GOLANG_VERSION=1.23.1 +ARG GOLANG_VERSION=1.23.5 RUN apt-get update && \ - apt-get install -y curl wget git gcc make vim libhwloc-dev hwloc software-properties-common && \ + apt-get install -y curl wget git gcc make vim libhwloc-dev hwloc software-properties-common man-db && \ add-apt-repository -y ppa:apptainer/ppa && \ apt-get update && \ apt-get install -y apptainer diff --git a/Makefile b/Makefile index 9459924..fe07999 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ CONTAINER_NAME = $(IMAGE_NAME) .PHONY: build build: @echo "Building the Open Cluster Scheduler image..." - docker build -t $(IMAGE_NAME):$(IMAGE_TAG) . + docker build --platform=linux/amd64 -t $(IMAGE_NAME):$(IMAGE_TAG) . # Running apptainers in containers requires more permissions. You can drop # the --privileged flag and the --cap-add SYS_ADMIN flag if you don't need @@ -43,7 +43,7 @@ run: build @echo "Running the Open Cluster Scheduler container..." @echo "For a new installation, you need to remove the ./installation subdirectory first." mkdir -p ./installation - 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 + 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 # Running apptainers in containers requires more permissions. You can drop # the --privileged flag and the --cap-add SYS_ADMIN flag if you don't need diff --git a/examples/flexableaccounting/flexibleaccounting.go b/examples/flexableaccounting/flexibleaccounting.go new file mode 100644 index 0000000..676e1fc --- /dev/null +++ b/examples/flexableaccounting/flexibleaccounting.go @@ -0,0 +1,56 @@ +package main + +import ( + "fmt" + + "github.com/hpc-gridware/go-clusterscheduler/pkg/accounting" + "golang.org/x/exp/rand" +) + +// This is an example epilog which adds arbitrary accounting records for +// jobs to the system. In order to use the example additional accounting records +// for the "test" namespace - a namespace is just a subsection in the JSON +// accounting file - (with usage values with the "tst prefix) needs to +// be enabled: +// +// qconf -mconf +// .. +// reporting_params ... usage_patterns=test:tst* +// +// Compile this example (go build) and copy the binary to your cluster +// scheduler installation directory. +// +// Then configure your queue epilog script with sgeadmin@/path/to/flexibleaccounting +// Ensure that the binary is executable. The sgeadmin is the correct user +// to use for this (check the owner of $SGE_ROOT). +// +// When configured correctly the following command should output the +// accounting records: +// +// qacct -j +// +// You should see the two tst_random* records in the qacct after your job +// has finished. +// +// tst_random1 351.000 +// tst_random2 121.000 +func main() { + usageFilePath, err := accounting.GetUsageFilePath() + if err != nil { + fmt.Printf("Failed to get usage file path: %v\n", err) + return + } + err = accounting.AppendToAccounting(usageFilePath, []accounting.Record{ + { + AccountingKey: "tst_random1", + AccountingValue: rand.Intn(1000), + }, + { + AccountingKey: "tst_random2", + AccountingValue: rand.Intn(1000), + }, + }) + if err != nil { + fmt.Printf("Failed to append to accounting: %v\n", err) + } +} diff --git a/go.mod b/go.mod index 55ad560..36e9147 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( go.opentelemetry.io/otel/sdk v1.33.0 go.opentelemetry.io/otel/sdk/log v0.9.0 go.opentelemetry.io/otel/sdk/metric v1.33.0 + golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c ) require ( @@ -27,9 +28,9 @@ require ( go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel/metric v1.33.0 // indirect go.opentelemetry.io/otel/trace v1.33.0 // indirect - golang.org/x/net v0.30.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/text v0.19.0 // indirect - golang.org/x/tools v0.26.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/tools v0.29.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3862a1a..8b73944 100644 --- a/go.sum +++ b/go.sum @@ -53,14 +53,16 @@ go.opentelemetry.io/otel/sdk/metric v1.33.0 h1:Gs5VK9/WUJhNXZgn8MR6ITatvAmKeIuCt go.opentelemetry.io/otel/sdk/metric v1.33.0/go.mod h1:dL5ykHZmm1B1nVRk9dDjChwDmt81MjVp3gLkQRwKf/Q= go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc= +golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/installer.sh b/installer.sh index e40ea82..2df6ec6 100755 --- a/installer.sh +++ b/installer.sh @@ -38,7 +38,11 @@ fi echo "Open Cluster Scheduler is not yet installed in ${MOUNT_DIR}. Starting installation." # Copy unpacked Open Cluster Scheduler package to ${MOUNT_DIR} -cp -r /opt/cs/* ${MOUNT_DIR} +if [ -d /opt/ocs ]; then + cp -r /opt/ocs/* "${MOUNT_DIR}" +else + cp -r /opt/cs/* "${MOUNT_DIR}" +fi cd ${MOUNT_DIR} diff --git a/pkg/accounting/accounting.go b/pkg/accounting/accounting.go new file mode 100644 index 0000000..8f0835b --- /dev/null +++ b/pkg/accounting/accounting.go @@ -0,0 +1,65 @@ +/*___INFO__MARK_BEGIN__*/ +/************************************************************************* +* Copyright 2025 HPC-Gridware GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +************************************************************************/ +/*___INFO__MARK_END__*/ + +package accounting + +import ( + "fmt" + "os" + "path/filepath" +) + +// Record is a key-value pair representing an accounting record. +type Record struct { + AccountingKey string + AccountingValue int +} + +// AppendToAccounting appends accounting records to the usage file so +// that it gets send to the execution daemon. Typically sgeadmin user +// (Cluster Scheduler install user). +// Hence you need to prefix your epilog script with sgeadmin@/path/to/epilog. +func AppendToAccounting(usageFilePath string, records []Record) error { + f, err := os.OpenFile(usageFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer f.Close() + + var nl string + + for _, record := range records { + nl += fmt.Sprintf("%s=%d\n", + record.AccountingKey, + record.AccountingValue) + } + + _, err = f.WriteString(nl) + return err +} + +// GetUsageFilePath returns the path to the job usage file in the +// job spool directory. +func GetUsageFilePath() (string, error) { + jobSpoolDir := os.Getenv("SGE_JOB_SPOOL_DIR") + if jobSpoolDir == "" { + return "", fmt.Errorf("SGE_JOB_SPOOL_DIR is not set") + } + return filepath.Join(jobSpoolDir, "usage"), nil +} diff --git a/pkg/accounting/accounting_suite_test.go b/pkg/accounting/accounting_suite_test.go new file mode 100644 index 0000000..4ca4b12 --- /dev/null +++ b/pkg/accounting/accounting_suite_test.go @@ -0,0 +1,32 @@ +/*___INFO__MARK_BEGIN__*/ +/************************************************************************* +* Copyright 2025 HPC-Gridware GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +************************************************************************/ +/*___INFO__MARK_END__*/ + +package accounting_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestAccounting(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Accounting Suite") +} diff --git a/pkg/accounting/accounting_test.go b/pkg/accounting/accounting_test.go new file mode 100644 index 0000000..9f54c6e --- /dev/null +++ b/pkg/accounting/accounting_test.go @@ -0,0 +1,50 @@ +/*___INFO__MARK_BEGIN__*/ +/************************************************************************* +* Copyright 2025 HPC-Gridware GmbH +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +************************************************************************/ +/*___INFO__MARK_END__*/ + +package accounting_test + +import ( + "os" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/hpc-gridware/go-clusterscheduler/pkg/accounting" +) + +var _ = Describe("Accounting", func() { + + Context("GetUsageFilePath", func() { + + It("should return the usage file path", func() { + os.Unsetenv("SGE_JOB_SPOOL_DIR") + usageFilePath, err := accounting.GetUsageFilePath() + Expect(err).To(HaveOccurred()) + Expect(usageFilePath).To(Equal("")) + }) + + It("should return the usage file path", func() { + os.Setenv("SGE_JOB_SPOOL_DIR", "/var/spool/gridengine/job_spool") + usageFilePath, err := accounting.GetUsageFilePath() + Expect(err).NotTo(HaveOccurred()) + Expect(usageFilePath).To(Equal("/var/spool/gridengine/job_spool/usage")) + }) + + }) +})