From c05634640077768bf3d7e90607e3ee74681ebc2c Mon Sep 17 00:00:00 2001 From: vsoch Date: Tue, 18 Jul 2023 17:18:35 -0600 Subject: [PATCH] reapi: updates for golang Problem: Go bindings can be more friendly to Go developers Solution: This includes docstring changes, the main module path to be under flux-framework, returning Go error instead of int, and updating tests to return nil (no error) instead of 0. The module path is fixed from a development variant to a flux-framework one. Finally, it fixes the Go bindings to use a struct instead of passing around a ctx variable. Our goal with these final changes to the Go module is to make the code more friendly to future go developers. Signed-off-by: vsoch --- .devcontainer/Dockerfile | 2 +- .devcontainer/devcontainer.json | 22 +- .gitignore | 3 + CMakeLists.txt | 6 + README.md | 26 +- cmake/GolangSimple.cmake | 31 ++ resource/reapi/bindings/CMakeLists.txt | 5 + resource/reapi/bindings/go/CMakeLists.txt | 1 + resource/reapi/bindings/go/go.mod | 3 + resource/reapi/bindings/go/src/CMakeLists.txt | 1 + resource/reapi/bindings/go/src/fluxcli/go.mod | 3 - .../bindings/go/src/fluxcli/reapi_cli.go | 321 +++++++++--------- .../bindings/go/src/fluxcli/reapi_cli_test.go | 4 +- .../go/src/fluxmodule/reapi_module.go | 136 +++++--- .../go/src/fluxmodule/reapi_module_test.go | 10 +- .../reapi/bindings/go/src/test/CMakeLists.txt | 17 + resource/reapi/bindings/go/src/test/main.go | 67 ++-- src/test/docker/focal-golang/Dockerfile | 31 +- src/test/generate-matrix.py | 7 - t/CMakeLists.txt | 2 + t/data/resource/expected/golang/001.R.out | 10 +- t/data/resource/expected/golang/002.R.out | 10 +- 22 files changed, 415 insertions(+), 303 deletions(-) create mode 100644 cmake/GolangSimple.cmake create mode 100644 resource/reapi/bindings/go/CMakeLists.txt create mode 100644 resource/reapi/bindings/go/go.mod create mode 100644 resource/reapi/bindings/go/src/CMakeLists.txt delete mode 100644 resource/reapi/bindings/go/src/fluxcli/go.mod create mode 100644 resource/reapi/bindings/go/src/test/CMakeLists.txt diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index fef32d376..edeba829b 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -25,7 +25,7 @@ RUN sudo apt-get -qq install -y --no-install-recommends \ curl # Assuming installing to /usr/local -ENV LD_LIBRARY_PATH=/usr/local/lib +ENV LD_LIBRARY_PATH=/usr/lib:/usr/local/lib RUN curl -s -L https://github.com/Kitware/CMake/releases/download/v3.26.4/cmake-3.26.4-linux-$(uname -m).sh > cmake.sh ;\ sudo bash cmake.sh --prefix=/usr/local --skip-license ;\ diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0f0daae1d..2014c67ea 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,15 +1,17 @@ { - "name": "Flux Sched Developer Environment", - "dockerFile": "Dockerfile", - "context": "../", + "name": "Flux Sched Developer Environment", + "dockerFile": "Dockerfile", + "context": "../", - "customizations": { - "vscode": { - "settings": { - "terminal.integrated.defaultProfile.linux": "bash" - } - } + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.defaultProfile.linux": "bash" + }, + "extensions": [ + "ms-vscode.cmake-tools" + ] } }, "postStartCommand": "git config --global --add safe.directory /workspaces/flux-sched" -} +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4f66700fa..cff963592 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,6 @@ compile_flags.txt # Rules to ignore auto-generated test harness scripts test_*.t !/src/bindings/python/test_commands/test_runner.t + +# Go +resource/reapi/bindings/go/src/test/main diff --git a/CMakeLists.txt b/CMakeLists.txt index 846b2a11f..3b80f149c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -174,6 +174,12 @@ if(ENABLE_COVERAGE) SET( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${COVERAGE_FLAGS}" ) endif() +# export WITH_GO=yes to build the go bindings and test binary +if(DEFINED ENV{WITH_GO}) + message(STATUS "WITH_GO detected in main CMakeLists.txt to build go bindings") + include(GolangSimple) +endif() + include_directories(.) add_subdirectory( etc ) add_subdirectory( src ) diff --git a/README.md b/README.md index 72737f09f..072892759 100644 --- a/README.md +++ b/README.md @@ -104,13 +104,37 @@ be set to the same prefix as was used to install the target flux-core. For example, if flux-core was installed in `$FLUX_CORE_PREFIX`: -``` +```bash ./configure --prefix=${FLUX_CORE_PREFIX} make make check make install ``` +To build go bindings, you will need go (tested with 1.19.10) available, and then: + +```bash +export WITH_GO=yes +./configure +make +``` + +To run just one test, you can cd into t + +```bash +$ ./t9001-golang-basic.t +ok 1 - match allocate 1 slot: 1 socket: 1 core (pol=default) +ok 2 - match allocate 2 slots: 2 sockets: 5 cores 1 gpu 6 memory +# passed all 2 test(s) +1..2 +``` + +To run full tests (more robust and mimics what happens in CI) you can do: + +```bash +make check +``` + ##### Flux Instance The examples below walk through exercising functioning flux-sched modules (i.e., diff --git a/cmake/GolangSimple.cmake b/cmake/GolangSimple.cmake new file mode 100644 index 000000000..c3711736b --- /dev/null +++ b/cmake/GolangSimple.cmake @@ -0,0 +1,31 @@ + +set(CUSTOM_GO_PATH "${CMAKE_SOURCE_DIR}/resource/reapi/bindings/go") +set(GOPATH "${CMAKE_CURRENT_BINARY_DIR}/go") + +# This probably isn't necessary, although if we could build fluxcli into it maybe +file(MAKE_DIRECTORY ${GOPATH}) + +# ADD_GO_INSTALLABLE_PROGRAM builds a custom go program (primarily for testing) +function(BUILD_GO_PROGRAM NAME MAIN_SRC CGO_CFLAGS CGO_LIBRARY_FLAGS) + message(STATUS "GOPATH: ${GOPATH}") + message(STATUS "CGO_LDFLAGS: ${CGO_LIBRARY_FLAGS}") + get_filename_component(MAIN_SRC_ABS ${MAIN_SRC} ABSOLUTE) + add_custom_target(${NAME}) + + # IMPORTANT: the trick to getting *spaces* to render in COMMAND is to convert them to ";" + # string(REPLACE ...) + STRING(REPLACE " " ";" CGO_LIBRARY_FLAGS ${CGO_LIBRARY_FLAGS}) + add_custom_command(TARGET ${NAME} + COMMAND GOPATH=${GOPATH}:${CUSTOM_GO_PATH} GOOS=linux G0111MODULE=off CGO_CFLAGS="${CGO_CFLAGS}" CGO_LDFLAGS='${CGO_LIBRARY_FLAGS}' go build -ldflags '-w' + -o "${CMAKE_CURRENT_SOURCE_DIR}/${NAME}" + ${CMAKE_GO_FLAGS} ${MAIN_SRC} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + DEPENDS ${MAIN_SRC_ABS} + COMMENT "Building Go library") + foreach(DEP ${ARGN}) + add_dependencies(${NAME} ${DEP}) + endforeach() + + add_custom_target(${NAME}_all ALL DEPENDS ${NAME}) + install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${NAME} DESTINATION bin) +endfunction(BUILD_GO_PROGRAM) \ No newline at end of file diff --git a/resource/reapi/bindings/CMakeLists.txt b/resource/reapi/bindings/CMakeLists.txt index c478213aa..30ed443db 100644 --- a/resource/reapi/bindings/CMakeLists.txt +++ b/resource/reapi/bindings/CMakeLists.txt @@ -18,3 +18,8 @@ add_library ( reapi_module STATIC target_link_libraries(reapi_module PRIVATE flux::core ) + +if(DEFINED ENV{WITH_GO}) + message(STATUS "WITH_GO is set to build go bindings") + add_subdirectory( go ) +endif() \ No newline at end of file diff --git a/resource/reapi/bindings/go/CMakeLists.txt b/resource/reapi/bindings/go/CMakeLists.txt new file mode 100644 index 000000000..39838feed --- /dev/null +++ b/resource/reapi/bindings/go/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory( src ) \ No newline at end of file diff --git a/resource/reapi/bindings/go/go.mod b/resource/reapi/bindings/go/go.mod new file mode 100644 index 000000000..659f614b3 --- /dev/null +++ b/resource/reapi/bindings/go/go.mod @@ -0,0 +1,3 @@ +module github.com/flux-framework/flux-sched/resource/reapi/bindings/go + +go 1.19 \ No newline at end of file diff --git a/resource/reapi/bindings/go/src/CMakeLists.txt b/resource/reapi/bindings/go/src/CMakeLists.txt new file mode 100644 index 000000000..4f50cacf2 --- /dev/null +++ b/resource/reapi/bindings/go/src/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory( test ) \ No newline at end of file diff --git a/resource/reapi/bindings/go/src/fluxcli/go.mod b/resource/reapi/bindings/go/src/fluxcli/go.mod deleted file mode 100644 index ad5560c48..000000000 --- a/resource/reapi/bindings/go/src/fluxcli/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/cmisale/flux-sched/resource/hlapi/bindings/go/src/fluxcli - -go 1.19 diff --git a/resource/reapi/bindings/go/src/fluxcli/reapi_cli.go b/resource/reapi/bindings/go/src/fluxcli/reapi_cli.go index 681a27fb2..9409f8e71 100644 --- a/resource/reapi/bindings/go/src/fluxcli/reapi_cli.go +++ b/resource/reapi/bindings/go/src/fluxcli/reapi_cli.go @@ -14,180 +14,202 @@ package fluxcli #include "reapi_cli.h" */ import "C" +import ( + "fmt" +) type ( ReapiCtx C.struct_reapi_cli_ctx_t -) -/*! Create and initialize reapi_cli context -reapi_cli_ctx_t *reapi_cli_new (); -*/ + // ReapiClient is a flux resource API client + // it holds a context that is required for most interactinos + ReapiClient struct { + ctx *ReapiCtx + } +) -func NewReapiCli() *ReapiCtx { - return (*ReapiCtx)(C.reapi_cli_new()) +// NewReapiCli creates a new resource API client +// reapi_cli_ctx_t *reapi_cli_new (); +func NewReapiClient() *ReapiClient { + ctx := (*ReapiCtx)(C.reapi_cli_new()) + return &ReapiClient{ctx: ctx} } -// func ReapiCliGetNode(ctx *ReapiCtx) string { -// return C.GoString(C.reapi_cli_get_node((*C.struct_reapi_cli_ctx)(ctx))) -// } - -/*! Destroy reapi cli context -* -* \param ctx reapi_cli_ctx_t context object -void reapi_cli_destroy (reapi_cli_ctx_t *ctx); -*/ - -func ReapiCliDestroy(ctx *ReapiCtx) { - C.reapi_cli_destroy((*C.struct_reapi_cli_ctx)(ctx)) +// Given an integer return code, convert to go error +// Also provide a meaningful string to the developer user +func retvalToError(code int, message string) error { + if code == 0 { + return nil + } + return fmt.Errorf(message+" %d", code) } -/* int reapi_cli_initialize (reapi_cli_ctx_t *ctx, const char *jgf); */ +// HasContext exposes the private ctx, telling the caller if it is set +func (cli *ReapiClient) HasContext() bool { + return cli.ctx != nil +} -func ReapiCliInit(ctx *ReapiCtx, jgf string, options string) (err int) { - err = (int)(C.reapi_cli_initialize((*C.struct_reapi_cli_ctx)(ctx), - C.CString(jgf), (C.CString(options)))) - return err +// Destroy destroys a resource API context +// void reapi_cli_destroy (reapi_cli_ctx_t *ctx); +func (cli *ReapiClient) Destroy() { + C.reapi_cli_destroy((*C.struct_reapi_cli_ctx)(cli.ctx)) } -/*! Match a jobspec to the "best" resources and either allocate -* orelse reserve them. The best resources are determined by -* the selected match policy. -* -* \param ctx reapi_cli_ctx_t context object -* \param orelse_reserve -* Boolean: if false, only allocate; otherwise, first try -* to allocate and if that fails, reserve. -* \param jobspec jobspec string. -* \param jobid jobid of the uint64_t type. -* \param reserved Boolean into which to return true if this job has been -* reserved instead of allocated. -* \param R String into which to return the resource set either -* allocated or reserved. -* \param at If allocated, 0 is returned; if reserved, actual time -* at which the job is reserved. -* \param ov Double into which to return performance overhead -* in terms of elapse time needed to complete -* the match operation. -* \return 0 on success; -1 on error. -int reapi_module_match_allocate (reapi_module_ctx_t *ctx, bool orelse_reserve, - const char *jobspec, const uint64_t jobid, - bool *reserved, - char **R, int64_t *at, double *ov); -*/ +// InitContext initializes Fluxion with resource graph +// \param rgraph string encoding the resource graph +// \param options json string initialization options +// int reapi_cli_initialize (reapi_cli_ctx_t *ctx, const char *jgf); +func (cli *ReapiClient) InitContext(jgf string, options string) (err error) { + + jobgraph := C.CString(jgf) + opts := C.CString(options) + fluxerr := (int)( + C.reapi_cli_initialize( + (*C.struct_reapi_cli_ctx)(cli.ctx), jobgraph, (opts), + ), + ) + + return retvalToError(fluxerr, "issue initializing resource api client") +} -func ReapiCliMatchAllocate(ctx *ReapiCtx, orelse_reserve bool, - jobspec string) (reserved bool, allocated string, at int64, overhead float64, jobid uint64, err int) { +// MatchAllocate matches a jobspec to the "best" resources and either allocate +// +// orelse reserve them. The best resources are determined by +// the selected match policy. +// +// \param orelse_reserve +// Boolean: if false, only allocate; otherwise, first try +// to allocate and if that fails, reserve. +// \param jobspec jobspec string. +// \param jobid jobid of the uint64_t type. +// \param reserved Boolean into which to return true if this job has been +// reserved instead of allocated. +// \param R String into which to return the resource set either +// allocated or reserved. +// \param at If allocated, 0 is returned; if reserved, actual time +// at which the job is reserved. +// \param ov Double into which to return performance overhead +// in terms of elapse time needed to complete +// the match operation. +// \return 0 on success; -1 on error. +func (cli *ReapiClient) MatchAllocate( + orelse_reserve bool, + jobspec string, +) (reserved bool, allocated string, at int64, overhead float64, jobid uint64, err error) { var r = C.CString("") - err = (int)(C.reapi_cli_match_allocate((*C.struct_reapi_cli_ctx)(ctx), + spec := C.CString(jobspec) + + fluxerr := (int)(C.reapi_cli_match_allocate((*C.struct_reapi_cli_ctx)(cli.ctx), (C.bool)(orelse_reserve), - C.CString(jobspec), + spec, (*C.ulong)(&jobid), (*C.bool)(&reserved), &r, (*C.long)(&at), (*C.double)(&overhead))) + allocated = C.GoString(r) - return reserved, allocated, at, overhead, - jobid, err -} + err = retvalToError(fluxerr, "issue resource api client matching allocate") + return reserved, allocated, at, overhead, jobid, err -/*! Update the resource state with R. - * - * \param ctx reapi_cli_ctx_t context object - * \param jobid jobid of the uint64_t type. - * \param R R string - * \param at return the scheduled time - * \param ov return the performance overhead - * in terms of elapse time needed to complete - * the match operation. - * \param R_out return the updated R string. - * \return 0 on success; -1 on error. - * - int reapi_cli_update_allocate (reapi_cli_ctx_t *ctx, - const uint64_t jobid, const char *R, int64_t *at, - double *ov, const char **R_out); -*/ +} -func ReapiCliUpdateAllocate(ctx *ReapiCtx, jobid int, r string) (at int64, overhead float64, r_out string, err int) { +// UpdateAllocate updates the resource state with R. +// +// \param jobid jobid of the uint64_t type. +// \param R R string +// \param at return the scheduled time +// \param ov return the performance overhead +// in terms of elapse time needed to complete +// the match operation. +// \param R_out return the updated R string. +// \return 0 on success; -1 on error. +// +// int reapi_cli_update_allocate (reapi_cli_ctx_t *ctx, +// +// const uint64_t jobid, const char *R, int64_t *at, +// double *ov, const char **R_out); +func (cli *ReapiClient) UpdateAllocate(jobid int, r string) (at int64, overhead float64, r_out string, err error) { var tmp_rout = C.CString("") - err = (int)(C.reapi_cli_update_allocate((*C.struct_reapi_cli_ctx)(ctx), + var resource = C.CString(r) + + fluxerr := (int)(C.reapi_cli_update_allocate((*C.struct_reapi_cli_ctx)(cli.ctx), (C.ulong)(jobid), - C.CString(r), + resource, (*C.long)(&at), (*C.double)(&overhead), &tmp_rout)) + r_out = C.GoString(tmp_rout) + + err = retvalToError(fluxerr, "issue resource api client updating allocate") return at, overhead, r_out, err } -/*! Cancel the allocation or reservation corresponding to jobid. - * - * \param ctx reapi_cli_ctx_t context object - * \param jobid jobid of the uint64_t type. - * \param noent_ok don't return an error on nonexistent jobid - * \return 0 on success; -1 on error. - - int reapi_cli_cancel (reapi_cli_ctx_t *ctx, - const uint64_t jobid, bool noent_ok);*/ - -func ReapiCliCancel(ctx *ReapiCtx, jobid int64, noent_ok bool) (err int) { - err = (int)(C.reapi_cli_cancel((*C.struct_reapi_cli_ctx)(ctx), +// Cancel cancels the allocation or reservation corresponding to jobid. +// +// \param jobid jobid of the uint64_t type. +// \param noent_ok don't return an error on nonexistent jobid +// \return 0 on success; -1 on error. +// +// int reapi_cli_cancel (reapi_cli_ctx_t *ctx, +// +// const uint64_t jobid, bool noent_ok); +func (cli *ReapiClient) Cancel(jobid int64, noent_ok bool) (err error) { + fluxerr := (int)(C.reapi_cli_cancel((*C.struct_reapi_cli_ctx)(cli.ctx), (C.ulong)(jobid), (C.bool)(noent_ok))) - return err + return retvalToError(fluxerr, "issue resource api client cancel") } -/*! Get the information on the allocation or reservation corresponding - * to jobid. - * - * \param ctx reapi_cli_ctx_t context object - * \param jobid const jobid of the uint64_t type. - * \param reserved Boolean into which to return true if this job has been - * reserved instead of allocated. - * \param at If allocated, 0 is returned; if reserved, actual time - * at which the job is reserved. - * \param ov Double into which to return performance overhead - * in terms of elapse time needed to complete - * the match operation. - * \return 0 on success; -1 on error. - - int reapi_cli_info (reapi_cli_ctx_t *ctx, const uint64_t jobid, - bool *reserved, int64_t *at, double *ov); -*/ - -func ReapiCliInfo(ctx *ReapiCtx, jobid int64) (reserved bool, at int64, overhead float64, mode string, err int) { +// Info gets the information on the allocation or reservation corresponding to jobid +// +// \param jobid const jobid of the uint64_t type. +// \param reserved Boolean into which to return true if this job has been +// reserved instead of allocated. +// \param at If allocated, 0 is returned; if reserved, actual time +// at which the job is reserved. +// \param ov Double into which to return performance overhead +// in terms of elapse time needed to complete +// the match operation. +// \return 0 on success; -1 on error. +// +// int reapi_cli_info (reapi_cli_ctx_t *ctx, const uint64_t jobid, +// +// bool *reserved, int64_t *at, double *ov); +func (cli *ReapiClient) Info(jobid int64) (reserved bool, at int64, overhead float64, mode string, err error) { var tmp_mode = C.CString("") - err = (int)(C.reapi_cli_info((*C.struct_reapi_cli_ctx)(ctx), + + fluxerr := (int)(C.reapi_cli_info((*C.struct_reapi_cli_ctx)(cli.ctx), (C.ulong)(jobid), (&tmp_mode), (*C.bool)(&reserved), (*C.long)(&at), (*C.double)(&overhead))) + + err = retvalToError(fluxerr, "issue resource api client info") return reserved, at, overhead, C.GoString(tmp_mode), err } -/* -! Get the performance information about the resource infrastructure. - - * - * \param ctx reapi_cli_ctx_t context object - * \param V Number of resource vertices - * \param E Number of edges - * \param J Number of jobs - * \param load Graph load time - * \param min Min match time - * \param max Max match time - * \param avg Avg match time - * \return 0 on success; -1 on error. - - int reapi_cli_stat (reapi_cli_ctx_t *ctx, int64_t *V, int64_t *E, - int64_t *J, double *load, - double *min, double *max, double *avg); -*/ -func ReapiCliStat(ctx *ReapiCtx) (v int64, e int64, - jobs int64, load float64, min float64, max float64, avg float64, err int) { - err = (int)(C.reapi_cli_stat((*C.struct_reapi_cli_ctx)(ctx), +// Stat gets the performance information about the resource infrastructure. +// +// \param V Number of resource vertices +// \param E Number of edges +// \param J Number of jobs +// \param load Graph load time +// \param min Min match time +// \param max Max match time +// \param avg Avg match time +// \return 0 on success; -1 on error. +// +// int reapi_cli_stat (reapi_cli_ctx_t *ctx, int64_t *V, int64_t *E, +// +// int64_t *J, double *load, +// double *min, double *max, double *avg); +func (cli *ReapiClient) Stat() (v int64, e int64, + jobs int64, load float64, min float64, max float64, avg float64, err error) { + fluxerr := (int)(C.reapi_cli_stat((*C.struct_reapi_cli_ctx)(cli.ctx), (*C.long)(&v), (*C.long)(&e), (*C.long)(&jobs), @@ -195,45 +217,18 @@ func ReapiCliStat(ctx *ReapiCtx) (v int64, e int64, (*C.double)(&min), (*C.double)(&max), (*C.double)(&avg))) + + err = retvalToError(fluxerr, "issue resource api client stat") return v, e, jobs, load, min, max, avg, err } -func ReapiCliGetErrMsg(ctx *ReapiCtx) string { - errmsg := C.reapi_cli_get_err_msg((*C.struct_reapi_cli_ctx)(ctx)) +// GetErrMsg returns a string error message from the resource api +func (cli *ReapiClient) GetErrMsg() string { + errmsg := C.reapi_cli_get_err_msg((*C.struct_reapi_cli_ctx)(cli.ctx)) return C.GoString(errmsg) } -func ReapiCliClearErrMsg(ctx *ReapiCtx) { - C.reapi_cli_clear_err_msg((*C.struct_reapi_cli_ctx)(ctx)) -} - -/*! Set the opaque handle to the reapi cli context. -* -* \param ctx reapi_cli_ctx_t context object -* \param h Opaque handle. How it is used is an implementation -* detail. However, when it is used within a Flux's -* service cli, it is expected to be a pointer -* to a flux_t object. -* \return 0 on success; -1 on error. -* -int reapi_cli_set_handle (reapi_cli_ctx_t *ctx, void *handle); -*/ - -func ReapiCliSetHandle(ctx *ReapiCtx) int { - return -1 -} - -/* -! Set the opaque handle to the reapi cli context. - - * - * \param ctx reapi_cli_ctx_t context object - * \return handle - * - -void *reapi_cli_get_handle (reapi_cli_ctx_t *ctx); -*/ -func ReapiCliGetHandle(ctx *ReapiCtx) int { - return -1 - +// ClearErrMsg clears error messages +func (cli *ReapiClient) ClearErrMsg() { + C.reapi_cli_clear_err_msg((*C.struct_reapi_cli_ctx)(cli.ctx)) } diff --git a/resource/reapi/bindings/go/src/fluxcli/reapi_cli_test.go b/resource/reapi/bindings/go/src/fluxcli/reapi_cli_test.go index 4dc5d5d5f..7bf8fa3c6 100644 --- a/resource/reapi/bindings/go/src/fluxcli/reapi_cli_test.go +++ b/resource/reapi/bindings/go/src/fluxcli/reapi_cli_test.go @@ -13,8 +13,8 @@ package fluxcli import "testing" func TestFluxcli(t *testing.T) { - ctx := NewReapiCli() - if ctx == nil { + cli := NewReapiClient() + if !cli.HasContext() { t.Errorf("Context is null") } diff --git a/resource/reapi/bindings/go/src/fluxmodule/reapi_module.go b/resource/reapi/bindings/go/src/fluxmodule/reapi_module.go index 317902150..668ae2a4e 100644 --- a/resource/reapi/bindings/go/src/fluxmodule/reapi_module.go +++ b/resource/reapi/bindings/go/src/fluxmodule/reapi_module.go @@ -12,91 +12,115 @@ package fluxcli // #include "reapi_module.h" import "C" +import ( + "fmt" + "unsafe" +) type ( - ReapiCtx C.struct_reapi_module_ctx_t + ReapiCtx C.struct_reapi_module_ctx_t + ReapiModule struct { + ctx ReapiCtx + } ) -/*! Create and initialize reapi_module context -reapi_module_ctx_t *reapi_module_new (); -*/ - -func NewReapiModule() *ReapiCtx { - return (*ReapiCtx)(C.reapi_module_new()) +// NewReapiModule creates a new resource API module +// reapi_module_ctx_t *reapi_module_new (); +func NewReapiModule() *ReapiModule { + ctx := (*ReapiCtx)(C.reapi_module_new()) + return &ReapiModule{ctx: ctx} } -/*! Destroy reapi cli context -* -* \param ctx reapi_module_ctx_t context object -void reapi_module_destroy (reapi_module_ctx_t *ctx); -*/ +// HasContext exposes the private ctx, telling the caller if it is set +func (m *ReapiModule) HasContext() bool { + return m.ctx != nil +} -func ReapiModuleDestroy(ctx *ReapiCtx) { - C.reapi_module_destroy((*C.struct_reapi_module_ctx)(ctx)) +// Given an integer return code, convert to go error +// Also provide a meaningful string to the developer user +func retvalToError(code int, message string) error { + if code == 0 { + return nil + } + return fmt.Errorf(message+" %d", code) } -/* int reapi_module_match_allocate (reapi_module_ctx_t *ctx, bool orelse_reserve, - const char *jobspec, const uint64_t jobid, - bool *reserved, - char **R, int64_t *at, double *ov);*/ +// Destroy destroys the resource API context +// void reapi_module_destroy (reapi_module_ctx_t *ctx); +func (m *ReapiModule) Destroy() { + C.reapi_module_destroy((*C.struct_reapi_module_ctx)(m.ctx)) +} -func ReapiModuleMatchAllocate(ctx *ReapiCtx, orelse_reserve bool, - jobspec string, jobid int) (reserved bool, allocated string, at int64, overhead float64, err int) { +// MatchAllocate matches and allocates resources +// int reapi_module_match_allocate (reapi_module_ctx_t *ctx, bool orelse_reserve, +// at: is the scheduled time "at" +func (m *ReapiModule) MatchAllocate( + orelse_reserve bool, + jobspec string, + jobid int, +) (reserved bool, allocated string, at int64, overhead float64, err error) { // var atlong C.long = (C.long)(at) var r = C.CString("teststring") - err = (int)(C.reapi_module_match_allocate((*C.struct_reapi_module_ctx)(ctx), + // Jobspec as a CString + spec := C.CString(jobspec) + + fluxerr := (int)(C.reapi_module_match_allocate((*C.struct_reapi_module_ctx)(m.ctx), (C.bool)(orelse_reserve), - C.CString(jobspec), + spec, (C.ulong)(jobid), (*C.bool)(&reserved), &r, (*C.long)(&at), (*C.double)(&overhead))) + allocated = C.GoString(r) + + err = retvalToError(fluxerr, "issue matching allocation for resource api") return reserved, allocated, at, overhead, err } -/*! Get the information on the allocation or reservation corresponding - * to jobid. - * - * \param ctx reapi_module_ctx_t context object - * \param jobid const jobid of the uint64_t type. - * \param reserved Boolean into which to return true if this job has been - * reserved instead of allocated. - * \param at If allocated, 0 is returned; if reserved, actual time - * at which the job is reserved. - * \param ov Double into which to return performance overhead - * in terms of elapse time needed to complete - * the match operation. - * \return 0 on success; -1 on error. - - int reapi_module_info (reapi_module_ctx_t *ctx, const uint64_t jobid, - bool *reserved, int64_t *at, double *ov); -*/ - -func ReapiModuleInfo(ctx *ReapiCtx, jobid int64) (reserved bool, at int64, overhead float64, err int) { - err = (int)(C.reapi_module_info((*C.struct_reapi_module_ctx)(ctx), +// Info gets the information on the allocation or reservation corresponding +// to jobid. +// +// \param ctx reapi_module_ctx_t context object +// \param jobid const jobid of the uint64_t type. +// \param reserved Boolean into which to return true if this job has been +// reserved instead of allocated. +// \param at If allocated, 0 is returned; if reserved, actual time +// at which the job is reserved. +// \param ov Double into which to return performance overhead +// in terms of elapse time needed to complete +// the match operation. +// \return 0 on success; -1 on error. +// +// int reapi_module_info (reapi_module_ctx_t *ctx, const uint64_t jobid, +// +// bool *reserved, int64_t *at, double *ov); +func (m *ReapiModule) Info(ctx *ReapiCtx, jobid int64) (reserved bool, at int64, overhead float64, err error) { + fluxerr := (int)(C.reapi_module_info((*C.struct_reapi_module_ctx)(m.ctx), (C.ulong)(jobid), (*C.bool)(&reserved), (*C.long)(&at), (*C.double)(&overhead))) + + err = retvalToError(fluxerr, "issue getting module info") return reserved, at, overhead, err } -/*! Cancel the allocation or reservation corresponding to jobid. - * - * \param ctx reapi_module_ctx_t context object - * \param jobid jobid of the uint64_t type. - * \param noent_ok don't return an error on nonexistent jobid - * \return 0 on success; -1 on error. - - int reapi_module_cancel (reapi_module_ctx_t *ctx, - const uint64_t jobid, bool noent_ok);*/ - -func ReapiModuleCancel(ctx *ReapiCtx, jobid int64, noent_ok bool) (err int) { - err = (int)(C.reapi_module_cancel((*C.struct_reapi_module_ctx)(ctx), +// Cancel cancels the allocation or reservation corresponding to jobid. +// +// \param ctx reapi_module_ctx_t context object +// \param jobid jobid of the uint64_t type. +// \param noent_ok don't return an error on nonexistent jobid +// \return 0 on success; -1 on error. +// +// int reapi_module_cancel (reapi_module_ctx_t *ctx, +// +// const uint64_t jobid, bool noent_ok);*/ +func (m *ReapiModule) Cancel(jobid int64, noent_ok bool) (err error) { + fluxerr := (int)(C.reapi_module_cancel((*C.struct_reapi_module_ctx)(m.ctx), (C.ulong)(jobid), (C.bool)(noent_ok))) - return -} + return retvalToError(fluxerr, "issue with cancel") +} \ No newline at end of file diff --git a/resource/reapi/bindings/go/src/fluxmodule/reapi_module_test.go b/resource/reapi/bindings/go/src/fluxmodule/reapi_module_test.go index 65ddde534..7177d7fd1 100644 --- a/resource/reapi/bindings/go/src/fluxmodule/reapi_module_test.go +++ b/resource/reapi/bindings/go/src/fluxmodule/reapi_module_test.go @@ -12,17 +12,17 @@ package fluxmodule import ( "fmt" - "io/ioutil" + "os" "testing" ) func TestFluxmodule(t *testing.T) { - ctx := NewReapiModule() - jobspec, err := ioutil.ReadFile("/root/flux-sched/t/data/resource/jobspecs/basics/test001.yaml") - if ctx == nil { + mod := NewReapiModule() + jobspec, err := os.ReadFile("/root/flux-sched/t/data/resource/jobspecs/basics/test001.yaml") + if !mod.HasContext() { t.Errorf("Context is null") } - reserved, allocated, at, overhead, err1 := ReapiModuleMatchAllocate(ctx, false, string(jobspec), 4) + reserved, allocated, at, overhead, err1 := mod.MatchAllocate(false, string(jobspec), 4) fmt.Printf("%t, %s, %d, %f, %d, %s", reserved, allocated, at, overhead, err1, err) } diff --git a/resource/reapi/bindings/go/src/test/CMakeLists.txt b/resource/reapi/bindings/go/src/test/CMakeLists.txt new file mode 100644 index 000000000..ff4a0d5df --- /dev/null +++ b/resource/reapi/bindings/go/src/test/CMakeLists.txt @@ -0,0 +1,17 @@ +set(TARGET main) +set(SRCS main.go) +set(CGO_CFLAGS "-I${CMAKE_BINARY_DIR}/resource/reapi/bindings/c") + +# This is currently passed but not used because when passed into add_custom_command the spaces are escaped +set(CGO_LIBRARY_FLAGS "-Wl,-R ${CMAKE_BINARY_DIR}/resource -L${CMAKE_BINARY_DIR}/resource/reapi/bindings -L${CMAKE_BINARY_DIR}/resource/libjobspec -ljobspec_conv -lreapi_cli -L${CMAKE_BINARY_DIR}/resource -lresource -lflux-idset -lstdc++ -lczmq -ljansson -lhwloc -lboost_system -lflux-hostlist -lboost_graph -lyaml-cpp") + +# This ensures the main binary is cleaned up +set_directory_properties( + PROPERTIES + ADDITIONAL_CLEAN_FILES "${CMAKE_CURRENT_BINARY_DIR}/main" + ) + +# GO_GET(go_redis github.com/hoisie/redis) +# main main.go +# We add the dependencies at the end so the build can run in parallel and we don't try to build this before they are ready! +BUILD_GO_PROGRAM(${TARGET} ${SRCS} "${CGO_CFLAGS}" "${CGO_LIBRARY_FLAGS}" jobspec_conv reapi_module reapi_cli resource flux::core) \ No newline at end of file diff --git a/resource/reapi/bindings/go/src/test/main.go b/resource/reapi/bindings/go/src/test/main.go index c9bacb03f..797984475 100644 --- a/resource/reapi/bindings/go/src/test/main.go +++ b/resource/reapi/bindings/go/src/test/main.go @@ -12,75 +12,76 @@ package main import ( "flag" - "fluxcli" "fmt" - "io/ioutil" + "os" + + "github.com/flux-framework/flux-sched/resource/reapi/bindings/go/src/fluxcli" ) func main() { - ctx := fluxcli.NewReapiCli() jgfPtr := flag.String("jgf", "", "path to jgf") jobspecPtr := flag.String("jobspec", "", "path to jobspec") reserve := flag.Bool("reserve", false, "or else reserve?") flag.Parse() - jgf, err := ioutil.ReadFile(*jgfPtr) + jgf, err := os.ReadFile(*jgfPtr) if err != nil { fmt.Println("Error reading JGF file") return } - fluxerr := fluxcli.ReapiCliInit(ctx, string(jgf), "{}") - if fluxerr < 0 { - fmt.Println("Error init ReapiCli") + cli := fluxcli.NewReapiClient() + err = cli.InitContext(string(jgf), "{}") + if err != nil { + fmt.Printf("Error initializing jobspec context for ReapiClient: %v\n", err) return } - fmt.Printf("Errors so far: %s\n", fluxcli.ReapiCliGetErrMsg(ctx)) + fmt.Printf("Errors so far: %s\n", cli.GetErrMsg()) - jobspec, err := ioutil.ReadFile(*jobspecPtr) + jobspec, err := os.ReadFile(*jobspecPtr) if err != nil { - fmt.Println("Error reading jobspec file") + fmt.Printf("Error reading jobspec file: %v\n", err) return } fmt.Printf("Jobspec:\n %s\n", jobspec) - reserved, allocated, at, overhead, jobid, fluxerr := fluxcli.ReapiCliMatchAllocate(ctx, *reserve, string(jobspec)) - if fluxerr != 0 { - fmt.Println("Error in ReapiCliMatchAllocate") + reserved, allocated, at, overhead, jobid, err := cli.MatchAllocate(*reserve, string(jobspec)) + if err != nil { + fmt.Printf("Error in ReapiClient MatchAllocate: %v\n", err) return } - printOutput(reserved, allocated, at, jobid, fluxerr) - reserved, allocated, at, overhead, jobid, fluxerr = fluxcli.ReapiCliMatchAllocate(ctx, *reserve, string(jobspec)) - fmt.Println("Errors so far: \n", fluxcli.ReapiCliGetErrMsg(ctx)) + printOutput(reserved, allocated, at, jobid, err) + reserved, allocated, at, overhead, jobid, err = cli.MatchAllocate(*reserve, string(jobspec)) + fmt.Println("Errors so far: \n", cli.GetErrMsg()) - if fluxerr != 0 { - fmt.Println("Error in ReapiCliMatchAllocate") + if err != nil { + fmt.Printf("Error in ReapiClient MatchAllocate: %v\n", err) return } - printOutput(reserved, allocated, at, jobid, fluxerr) - fluxerr = fluxcli.ReapiCliCancel(ctx, 1, false) - if fluxerr != 0 { - fmt.Println("Error in ReapiCliCancel") + printOutput(reserved, allocated, at, jobid, err) + err = cli.Cancel(1, false) + if err != nil { + fmt.Printf("Error in ReapiClient Cancel: %v\n", err) return } - fmt.Printf("Cancel output: %d\n", fluxerr) + fmt.Printf("Cancel output: %v\n", err) - reserved, at, overhead, mode, fluxerr := fluxcli.ReapiCliInfo(ctx, 1) - if fluxerr != 0 { - fmt.Println("Error in ReapiCliInfo") + reserved, at, overhead, mode, err := cli.Info(1) + if err != nil { + fmt.Printf("Error in ReapiClient Info: %v\n", err) return } - fmt.Printf("Info output jobid 1: %t, %d, %f, %s, %d\n", reserved, at, overhead, mode, fluxerr) + fmt.Printf("Info output jobid 1: %t, %d, %f, %s, %v\n", reserved, at, overhead, mode, err) - reserved, at, overhead, mode, fluxerr = fluxcli.ReapiCliInfo(ctx, 2) - if fluxerr != 0 { - fmt.Println("Error in ReapiCliInfo") + reserved, at, overhead, mode, err = cli.Info(2) + if err != nil { + fmt.Println("Error in ReapiClient Info: %v\n", err) return } - fmt.Printf("Info output jobid 2: %t, %d, %f, %d\n", reserved, at, overhead, fluxerr) + fmt.Printf("Info output jobid 2: %t, %d, %f, %v\n", reserved, at, overhead, err) } -func printOutput(reserved bool, allocated string, at int64, jobid uint64, fluxerr int) { +func printOutput(reserved bool, allocated string, at int64, jobid uint64, err error) { fmt.Println("\n\t----Match Allocate output---") - fmt.Printf("jobid: %d\nreserved: %t\nallocated: %s\nat: %d\nerror: %d\n", jobid, reserved, allocated, at, fluxerr) + fmt.Printf("jobid: %d\nreserved: %t\nallocated: %s\nat: %d\nerror: %v\n", jobid, reserved, allocated, at, err) } diff --git a/src/test/docker/focal-golang/Dockerfile b/src/test/docker/focal-golang/Dockerfile index 880380d7b..9c0340a28 100644 --- a/src/test/docker/focal-golang/Dockerfile +++ b/src/test/docker/focal-golang/Dockerfile @@ -6,19 +6,26 @@ ARG UID=1000 # Install extra buildrequires for flux-sched: RUN sudo apt-get update RUN sudo apt-get -qq install -y --no-install-recommends \ - libboost-graph-dev \ - libboost-system-dev \ - libboost-filesystem-dev \ - libboost-regex-dev \ - python-yaml \ - libyaml-cpp-dev \ - libedit-dev + libboost-graph-dev \ + libboost-system-dev \ + libboost-filesystem-dev \ + libboost-regex-dev \ + python-yaml \ + libyaml-cpp-dev \ + ninja-build \ + curl \ + libedit-dev -# Install Golang for binding tests -RUN wget "https://go.dev/dl/go1.19.9.linux-amd64.tar.gz" +# Install cmake for new build system +RUN curl -s -L https://github.com/Kitware/CMake/releases/download/v3.26.4/cmake-3.26.4-linux-$(uname -m).sh > cmake.sh ;\ + sudo bash cmake.sh --prefix=/usr/local --skip-license ;\ + rm cmake.sh + +# Install Golang 1.19.10 for binding tests +RUN wget https://go.dev/dl/go1.19.10.linux-amd64.tar.gz RUN sudo rm -rf /usr/local/go \ - && sudo tar -C /usr/local -xzf go1.19.9.linux-amd64.tar.gz \ - && rm -f go1.19.9.linux-amd64.tar.gz + && sudo tar -C /usr/local -xzf go1.19.10.linux-amd64.tar.gz \ + && rm -f go1.19.10.linux-amd64.tar.gz # Add configured user to image with sudo access: # @@ -32,4 +39,4 @@ RUN \ USER $USER WORKDIR /home/$USER -ENV PATH=$PATH:/usr/local/go/bin +ENV PATH=$PATH:/usr/local/go/bin \ No newline at end of file diff --git a/src/test/generate-matrix.py b/src/test/generate-matrix.py index 54cb0d5d6..3b6131de8 100755 --- a/src/test/generate-matrix.py +++ b/src/test/generate-matrix.py @@ -196,13 +196,6 @@ def __str__(self): ), ) -# RHEL7 clone -matrix.add_build( - name="el7", - image="el7", - docker_tag=True, -) - # RHEL8 clone matrix.add_build( name="el8", diff --git a/t/CMakeLists.txt b/t/CMakeLists.txt index dbb8559f2..330e51798 100644 --- a/t/CMakeLists.txt +++ b/t/CMakeLists.txt @@ -1,6 +1,7 @@ # NOTE: skipping tree tests, since they're long broken # t2000-tree-basic.t # t2001-tree-real.t + set(ALL_TESTS t0000-sharness.t t1001-qmanager-basic.t @@ -81,6 +82,7 @@ set(ALL_TESTS t6002-graph-hwloc.t t7000-shell-datastaging.t t8001-util-ion-R.t + t9001-golang-basic.t ) foreach(test ${ALL_TESTS}) flux_add_test(NAME ${test} diff --git a/t/data/resource/expected/golang/001.R.out b/t/data/resource/expected/golang/001.R.out index 25604292e..7a9f056cf 100644 --- a/t/data/resource/expected/golang/001.R.out +++ b/t/data/resource/expected/golang/001.R.out @@ -38,7 +38,7 @@ reserved: false allocated: {"graph": {"nodes": [{"id": "79", "metadata": {"type": "core", "basename": "core", "name": "core35", "id": 35, "uniq_id": 79, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node1/socket1/core35"}}}, {"id": "7", "metadata": {"type": "socket", "basename": "socket", "name": "socket1", "id": 1, "uniq_id": 7, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node1/socket1"}}}, {"id": "3", "metadata": {"type": "node", "basename": "node", "name": "node1", "id": 1, "uniq_id": 3, "rank": -1, "exclusive": false, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node1"}}}, {"id": "1", "metadata": {"type": "rack", "basename": "rack", "name": "rack0", "id": 0, "uniq_id": 1, "rank": -1, "exclusive": false, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0"}}}, {"id": "0", "metadata": {"type": "cluster", "basename": "tiny", "name": "tiny0", "id": 0, "uniq_id": 0, "rank": -1, "exclusive": false, "unit": "", "size": 1, "paths": {"containment": "/tiny0"}}}], "edges": [{"source": "7", "target": "79", "metadata": {"name": {"containment": "contains"}}}, {"source": "3", "target": "7", "metadata": {"name": {"containment": "contains"}}}, {"source": "1", "target": "3", "metadata": {"name": {"containment": "contains"}}}, {"source": "0", "target": "1", "metadata": {"name": {"containment": "contains"}}}]}} at: 0 -error: 0 +error: Errors so far: @@ -48,7 +48,7 @@ reserved: false allocated: {"graph": {"nodes": [{"id": "43", "metadata": {"type": "core", "basename": "core", "name": "core35", "id": 35, "uniq_id": 43, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node0/socket1/core35"}}}, {"id": "5", "metadata": {"type": "socket", "basename": "socket", "name": "socket1", "id": 1, "uniq_id": 5, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node0/socket1"}}}, {"id": "2", "metadata": {"type": "node", "basename": "node", "name": "node0", "id": 0, "uniq_id": 2, "rank": -1, "exclusive": false, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node0"}}}, {"id": "1", "metadata": {"type": "rack", "basename": "rack", "name": "rack0", "id": 0, "uniq_id": 1, "rank": -1, "exclusive": false, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0"}}}, {"id": "0", "metadata": {"type": "cluster", "basename": "tiny", "name": "tiny0", "id": 0, "uniq_id": 0, "rank": -1, "exclusive": false, "unit": "", "size": 1, "paths": {"containment": "/tiny0"}}}], "edges": [{"source": "5", "target": "43", "metadata": {"name": {"containment": "contains"}}}, {"source": "2", "target": "5", "metadata": {"name": {"containment": "contains"}}}, {"source": "1", "target": "2", "metadata": {"name": {"containment": "contains"}}}, {"source": "0", "target": "1", "metadata": {"name": {"containment": "contains"}}}]}} at: 0 -error: 0 -Cancel output: 0 -Info output jobid 1: false, 0, 0.000000, CANCELED, 0 -Info output jobid 2: false, 0, 0.000000, 0 +error: +Cancel output: +Info output jobid 1: false, 0, 0.000000, CANCELED, +Info output jobid 2: false, 0, 0.000000, diff --git a/t/data/resource/expected/golang/002.R.out b/t/data/resource/expected/golang/002.R.out index aaaf99aa4..7917bce3d 100644 --- a/t/data/resource/expected/golang/002.R.out +++ b/t/data/resource/expected/golang/002.R.out @@ -36,7 +36,7 @@ reserved: false allocated: {"graph": {"nodes": [{"id": "21", "metadata": {"type": "core", "basename": "core", "name": "core13", "id": 13, "uniq_id": 21, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node0/socket0/core13"}}}, {"id": "22", "metadata": {"type": "core", "basename": "core", "name": "core14", "id": 14, "uniq_id": 22, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node0/socket0/core14"}}}, {"id": "23", "metadata": {"type": "core", "basename": "core", "name": "core15", "id": 15, "uniq_id": 23, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node0/socket0/core15"}}}, {"id": "24", "metadata": {"type": "core", "basename": "core", "name": "core16", "id": 16, "uniq_id": 24, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node0/socket0/core16"}}}, {"id": "25", "metadata": {"type": "core", "basename": "core", "name": "core17", "id": 17, "uniq_id": 25, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node0/socket0/core17"}}}, {"id": "80", "metadata": {"type": "gpu", "basename": "gpu", "name": "gpu0", "id": 0, "uniq_id": 80, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node0/socket0/gpu0"}}}, {"id": "85", "metadata": {"type": "memory", "basename": "memory", "name": "memory1", "id": 1, "uniq_id": 85, "rank": -1, "exclusive": true, "unit": "", "size": 2, "paths": {"containment": "/tiny0/rack0/node0/socket0/memory1"}}}, {"id": "86", "metadata": {"type": "memory", "basename": "memory", "name": "memory2", "id": 2, "uniq_id": 86, "rank": -1, "exclusive": true, "unit": "", "size": 2, "paths": {"containment": "/tiny0/rack0/node0/socket0/memory2"}}}, {"id": "87", "metadata": {"type": "memory", "basename": "memory", "name": "memory3", "id": 3, "uniq_id": 87, "rank": -1, "exclusive": true, "unit": "", "size": 2, "paths": {"containment": "/tiny0/rack0/node0/socket0/memory3"}}}, {"id": "4", "metadata": {"type": "socket", "basename": "socket", "name": "socket0", "id": 0, "uniq_id": 4, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node0/socket0"}}}, {"id": "39", "metadata": {"type": "core", "basename": "core", "name": "core31", "id": 31, "uniq_id": 39, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node0/socket1/core31"}}}, {"id": "40", "metadata": {"type": "core", "basename": "core", "name": "core32", "id": 32, "uniq_id": 40, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node0/socket1/core32"}}}, {"id": "41", "metadata": {"type": "core", "basename": "core", "name": "core33", "id": 33, "uniq_id": 41, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node0/socket1/core33"}}}, {"id": "42", "metadata": {"type": "core", "basename": "core", "name": "core34", "id": 34, "uniq_id": 42, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node0/socket1/core34"}}}, {"id": "43", "metadata": {"type": "core", "basename": "core", "name": "core35", "id": 35, "uniq_id": 43, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node0/socket1/core35"}}}, {"id": "81", "metadata": {"type": "gpu", "basename": "gpu", "name": "gpu1", "id": 1, "uniq_id": 81, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node0/socket1/gpu1"}}}, {"id": "89", "metadata": {"type": "memory", "basename": "memory", "name": "memory5", "id": 5, "uniq_id": 89, "rank": -1, "exclusive": true, "unit": "", "size": 2, "paths": {"containment": "/tiny0/rack0/node0/socket1/memory5"}}}, {"id": "90", "metadata": {"type": "memory", "basename": "memory", "name": "memory6", "id": 6, "uniq_id": 90, "rank": -1, "exclusive": true, "unit": "", "size": 2, "paths": {"containment": "/tiny0/rack0/node0/socket1/memory6"}}}, {"id": "91", "metadata": {"type": "memory", "basename": "memory", "name": "memory7", "id": 7, "uniq_id": 91, "rank": -1, "exclusive": true, "unit": "", "size": 2, "paths": {"containment": "/tiny0/rack0/node0/socket1/memory7"}}}, {"id": "5", "metadata": {"type": "socket", "basename": "socket", "name": "socket1", "id": 1, "uniq_id": 5, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node0/socket1"}}}, {"id": "2", "metadata": {"type": "node", "basename": "node", "name": "node0", "id": 0, "uniq_id": 2, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node0"}}}, {"id": "57", "metadata": {"type": "core", "basename": "core", "name": "core13", "id": 13, "uniq_id": 57, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node1/socket0/core13"}}}, {"id": "58", "metadata": {"type": "core", "basename": "core", "name": "core14", "id": 14, "uniq_id": 58, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node1/socket0/core14"}}}, {"id": "59", "metadata": {"type": "core", "basename": "core", "name": "core15", "id": 15, "uniq_id": 59, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node1/socket0/core15"}}}, {"id": "60", "metadata": {"type": "core", "basename": "core", "name": "core16", "id": 16, "uniq_id": 60, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node1/socket0/core16"}}}, {"id": "61", "metadata": {"type": "core", "basename": "core", "name": "core17", "id": 17, "uniq_id": 61, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node1/socket0/core17"}}}, {"id": "82", "metadata": {"type": "gpu", "basename": "gpu", "name": "gpu0", "id": 0, "uniq_id": 82, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node1/socket0/gpu0"}}}, {"id": "93", "metadata": {"type": "memory", "basename": "memory", "name": "memory1", "id": 1, "uniq_id": 93, "rank": -1, "exclusive": true, "unit": "", "size": 2, "paths": {"containment": "/tiny0/rack0/node1/socket0/memory1"}}}, {"id": "94", "metadata": {"type": "memory", "basename": "memory", "name": "memory2", "id": 2, "uniq_id": 94, "rank": -1, "exclusive": true, "unit": "", "size": 2, "paths": {"containment": "/tiny0/rack0/node1/socket0/memory2"}}}, {"id": "95", "metadata": {"type": "memory", "basename": "memory", "name": "memory3", "id": 3, "uniq_id": 95, "rank": -1, "exclusive": true, "unit": "", "size": 2, "paths": {"containment": "/tiny0/rack0/node1/socket0/memory3"}}}, {"id": "6", "metadata": {"type": "socket", "basename": "socket", "name": "socket0", "id": 0, "uniq_id": 6, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node1/socket0"}}}, {"id": "75", "metadata": {"type": "core", "basename": "core", "name": "core31", "id": 31, "uniq_id": 75, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node1/socket1/core31"}}}, {"id": "76", "metadata": {"type": "core", "basename": "core", "name": "core32", "id": 32, "uniq_id": 76, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node1/socket1/core32"}}}, {"id": "77", "metadata": {"type": "core", "basename": "core", "name": "core33", "id": 33, "uniq_id": 77, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node1/socket1/core33"}}}, {"id": "78", "metadata": {"type": "core", "basename": "core", "name": "core34", "id": 34, "uniq_id": 78, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node1/socket1/core34"}}}, {"id": "79", "metadata": {"type": "core", "basename": "core", "name": "core35", "id": 35, "uniq_id": 79, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node1/socket1/core35"}}}, {"id": "83", "metadata": {"type": "gpu", "basename": "gpu", "name": "gpu1", "id": 1, "uniq_id": 83, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node1/socket1/gpu1"}}}, {"id": "97", "metadata": {"type": "memory", "basename": "memory", "name": "memory5", "id": 5, "uniq_id": 97, "rank": -1, "exclusive": true, "unit": "", "size": 2, "paths": {"containment": "/tiny0/rack0/node1/socket1/memory5"}}}, {"id": "98", "metadata": {"type": "memory", "basename": "memory", "name": "memory6", "id": 6, "uniq_id": 98, "rank": -1, "exclusive": true, "unit": "", "size": 2, "paths": {"containment": "/tiny0/rack0/node1/socket1/memory6"}}}, {"id": "99", "metadata": {"type": "memory", "basename": "memory", "name": "memory7", "id": 7, "uniq_id": 99, "rank": -1, "exclusive": true, "unit": "", "size": 2, "paths": {"containment": "/tiny0/rack0/node1/socket1/memory7"}}}, {"id": "7", "metadata": {"type": "socket", "basename": "socket", "name": "socket1", "id": 1, "uniq_id": 7, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node1/socket1"}}}, {"id": "3", "metadata": {"type": "node", "basename": "node", "name": "node1", "id": 1, "uniq_id": 3, "rank": -1, "exclusive": true, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0/node1"}}}, {"id": "1", "metadata": {"type": "rack", "basename": "rack", "name": "rack0", "id": 0, "uniq_id": 1, "rank": -1, "exclusive": false, "unit": "", "size": 1, "paths": {"containment": "/tiny0/rack0"}}}, {"id": "0", "metadata": {"type": "cluster", "basename": "tiny", "name": "tiny0", "id": 0, "uniq_id": 0, "rank": -1, "exclusive": false, "unit": "", "size": 1, "paths": {"containment": "/tiny0"}}}], "edges": [{"source": "4", "target": "21", "metadata": {"name": {"containment": "contains"}}}, {"source": "4", "target": "22", "metadata": {"name": {"containment": "contains"}}}, {"source": "4", "target": "23", "metadata": {"name": {"containment": "contains"}}}, {"source": "4", "target": "24", "metadata": {"name": {"containment": "contains"}}}, {"source": "4", "target": "25", "metadata": {"name": {"containment": "contains"}}}, {"source": "4", "target": "80", "metadata": {"name": {"containment": "contains"}}}, {"source": "4", "target": "85", "metadata": {"name": {"containment": "contains"}}}, {"source": "4", "target": "86", "metadata": {"name": {"containment": "contains"}}}, {"source": "4", "target": "87", "metadata": {"name": {"containment": "contains"}}}, {"source": "2", "target": "4", "metadata": {"name": {"containment": "contains"}}}, {"source": "5", "target": "39", "metadata": {"name": {"containment": "contains"}}}, {"source": "5", "target": "40", "metadata": {"name": {"containment": "contains"}}}, {"source": "5", "target": "41", "metadata": {"name": {"containment": "contains"}}}, {"source": "5", "target": "42", "metadata": {"name": {"containment": "contains"}}}, {"source": "5", "target": "43", "metadata": {"name": {"containment": "contains"}}}, {"source": "5", "target": "81", "metadata": {"name": {"containment": "contains"}}}, {"source": "5", "target": "89", "metadata": {"name": {"containment": "contains"}}}, {"source": "5", "target": "90", "metadata": {"name": {"containment": "contains"}}}, {"source": "5", "target": "91", "metadata": {"name": {"containment": "contains"}}}, {"source": "2", "target": "5", "metadata": {"name": {"containment": "contains"}}}, {"source": "1", "target": "2", "metadata": {"name": {"containment": "contains"}}}, {"source": "6", "target": "57", "metadata": {"name": {"containment": "contains"}}}, {"source": "6", "target": "58", "metadata": {"name": {"containment": "contains"}}}, {"source": "6", "target": "59", "metadata": {"name": {"containment": "contains"}}}, {"source": "6", "target": "60", "metadata": {"name": {"containment": "contains"}}}, {"source": "6", "target": "61", "metadata": {"name": {"containment": "contains"}}}, {"source": "6", "target": "82", "metadata": {"name": {"containment": "contains"}}}, {"source": "6", "target": "93", "metadata": {"name": {"containment": "contains"}}}, {"source": "6", "target": "94", "metadata": {"name": {"containment": "contains"}}}, {"source": "6", "target": "95", "metadata": {"name": {"containment": "contains"}}}, {"source": "3", "target": "6", "metadata": {"name": {"containment": "contains"}}}, {"source": "7", "target": "75", "metadata": {"name": {"containment": "contains"}}}, {"source": "7", "target": "76", "metadata": {"name": {"containment": "contains"}}}, {"source": "7", "target": "77", "metadata": {"name": {"containment": "contains"}}}, {"source": "7", "target": "78", "metadata": {"name": {"containment": "contains"}}}, {"source": "7", "target": "79", "metadata": {"name": {"containment": "contains"}}}, {"source": "7", "target": "83", "metadata": {"name": {"containment": "contains"}}}, {"source": "7", "target": "97", "metadata": {"name": {"containment": "contains"}}}, {"source": "7", "target": "98", "metadata": {"name": {"containment": "contains"}}}, {"source": "7", "target": "99", "metadata": {"name": {"containment": "contains"}}}, {"source": "3", "target": "7", "metadata": {"name": {"containment": "contains"}}}, {"source": "1", "target": "3", "metadata": {"name": {"containment": "contains"}}}, {"source": "0", "target": "1", "metadata": {"name": {"containment": "contains"}}}]}} at: 0 -error: 0 +error: Errors so far: @@ -45,7 +45,7 @@ jobid: 2 reserved: false allocated: at: 0 -error: 0 -Cancel output: 0 -Info output jobid 1: false, 0, 0.000000, CANCELED, 0 -Info output jobid 2: false, 0, 0.000000, 0 +error: +Cancel output: +Info output jobid 1: false, 0, 0.000000, CANCELED, +Info output jobid 2: false, 0, 0.000000,