From f15a8cf7e36d1a83854b5e3cbc00618e904a4eb8 Mon Sep 17 00:00:00 2001 From: Jan Eitzinger Date: Tue, 10 May 2022 11:20:03 +0200 Subject: [PATCH 1/3] Remove obsolete util dir --- utils/README.md | 1 - utils/add-job.mjs | 40 ---------------------------------------- utils/gen-keypair.go | 22 ---------------------- utils/systemd/README.md | 30 ------------------------------ 4 files changed, 93 deletions(-) delete mode 100644 utils/README.md delete mode 100644 utils/add-job.mjs delete mode 100644 utils/gen-keypair.go delete mode 100644 utils/systemd/README.md diff --git a/utils/README.md b/utils/README.md deleted file mode 100644 index fabc774..0000000 --- a/utils/README.md +++ /dev/null @@ -1 +0,0 @@ -# Helper scripts diff --git a/utils/add-job.mjs b/utils/add-job.mjs deleted file mode 100644 index dc14039..0000000 --- a/utils/add-job.mjs +++ /dev/null @@ -1,40 +0,0 @@ -import fetch from 'node-fetch' - -// Just for testing - -const job = { - jobId: 123, - user: 'lou', - project: 'testproj', - cluster: 'heidi', - partition: 'default', - arrayJobId: 0, - numNodes: 1, - numHwthreads: 8, - numAcc: 0, - exclusive: 1, - monitoringStatus: 1, - smt: 1, - jobState: 'running', - duration: 2*60*60, - tags: [], - resources: [ - { - hostname: 'heidi', - hwthreads: [0, 1, 2, 3, 4, 5, 6, 7] - } - ], - metaData: null, - startTime: 1641427200 -} - -fetch('http://localhost:8080/api/jobs/start_job/', { - method: 'POST', - body: JSON.stringify(job), - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Bearer eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc19hZG1pbiI6dHJ1ZSwiaXNfYXBpIjpmYWxzZSwic3ViIjoibG91In0.nY6dCgLSdm7zXz1xPkrb_3JnnUCgExXeXcrTlAAySs4p72VKJhmzzC1RxgkJE26l8tDYUilM-o-urzlaqK5aDA' - } - }) - .then(res => res.status == 200 ? res.json() : res.text()) - .then(res => console.log(res)) diff --git a/utils/gen-keypair.go b/utils/gen-keypair.go deleted file mode 100644 index 905817d..0000000 --- a/utils/gen-keypair.go +++ /dev/null @@ -1,22 +0,0 @@ -package main - -import ( - "crypto/ed25519" - "crypto/rand" - "encoding/base64" - "fmt" - "os" -) - -func main() { - // rand.Reader uses /dev/urandom on Linux - pub, priv, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - fmt.Fprintf(os.Stderr, "error: %s\n", err.Error()) - os.Exit(1) - } - - fmt.Fprintf(os.Stdout, "JWT_PUBLIC_KEY=%#v\nJWT_PRIVATE_KEY=%#v\n", - base64.StdEncoding.EncodeToString(pub), - base64.StdEncoding.EncodeToString(priv)) -} diff --git a/utils/systemd/README.md b/utils/systemd/README.md deleted file mode 100644 index ed715ab..0000000 --- a/utils/systemd/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# How to run this as a systemd deamon - -The files in this directory assume that you install the Golang version of ClusterCockpit to `/var/clustercockpit`. If you do not like that, you can choose any other location, but make sure to replace all paths that begin with `/var/clustercockpit` in the `clustercockpit.service` file! - -If you have not installed [yarn](https://yarnpkg.com/getting-started/install) and [go](https://go.dev/doc/install) already, do that (Golang is available in most package managers). - -The `config.json` can have the optional fields *user* and *group*. If provided, the application will call [setuid](https://man7.org/linux/man-pages/man2/setuid.2.html) and [setgid](https://man7.org/linux/man-pages/man2/setgid.2.html) after having read the config file and having bound to a TCP port (so that it can take a privileged port), but before it starts accepting any connections. This is good for security, but means that the directories `frontend/public`, `var/` and `templates/` must be readable by that user and `var/` writable as well (All paths relative to the repos root). The `.env` and `config.json` files might contain secrets and should not be readable by that user. If those files are changed, the server has to be restarted. - -```sh -# 1.: Clone this repository to /var/clustercockpit -git clone git@github.com:ClusterCockpit/cc-backend.git /var/clustercockpit - -# 2.: Install all dependencies and build everything -cd /var/clustercockpit -go get && go build && (cd ./frontend && yarn install && yarn build) - -# 3.: Modify the `./config.json` file from the directory which contains this README.md to your liking and put it in the repo root -cp ./utils/systemd/config.json ./config.json -vim ./config.json # do your thing... - -# 4.: Add the systemd service unit file -sudo ln -s /var/clustercockpit/utils/systemd/clustercockpit.service /etc/systemd/system/clustercockpit.service - -# 5.: Enable and start the server -sudo systemctl enable clustercockpit.service # optional (if done, (re-)starts automatically) -sudo systemctl start clustercockpit.service - -# Check whats going on: -sudo journalctl -u clustercockpit.service -``` From 515fdbd44cc5ee82837136f1eb1bd9c18b8febf7 Mon Sep 17 00:00:00 2001 From: Lou Knauer Date: Thu, 12 May 2022 09:20:38 +0200 Subject: [PATCH 2/3] SubCluster-specific thresholds --- frontend | 2 +- graph/generated/generated.go | 455 ++++++++++++++++++++++++++++++++--- graph/model/models_gen.go | 27 ++- graph/schema.graphqls | 17 +- 4 files changed, 452 insertions(+), 49 deletions(-) diff --git a/frontend b/frontend index 4b0d020..94ef11a 160000 --- a/frontend +++ b/frontend @@ -1 +1 @@ -Subproject commit 4b0d020dd416e6b6b2a70b476b158804e62b3d7c +Subproject commit 94ef11aa9fc3c194f1df497e3e06c60a7125883d diff --git a/graph/generated/generated.go b/graph/generated/generated.go index 5a7d81c..e1e5db4 100644 --- a/graph/generated/generated.go +++ b/graph/generated/generated.go @@ -150,6 +150,7 @@ type ComplexityRoot struct { Normal func(childComplexity int) int Peak func(childComplexity int) int Scope func(childComplexity int) int + SubClusters func(childComplexity int) int Timestep func(childComplexity int) int Unit func(childComplexity int) int } @@ -228,6 +229,14 @@ type ComplexityRoot struct { Topology func(childComplexity int) int } + SubClusterConfig struct { + Alert func(childComplexity int) int + Caution func(childComplexity int) int + Name func(childComplexity int) int + Normal func(childComplexity int) int + Peak func(childComplexity int) int + } + Tag struct { ID func(childComplexity int) int Name func(childComplexity int) int @@ -763,6 +772,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.MetricConfig.Scope(childComplexity), true + case "MetricConfig.subClusters": + if e.complexity.MetricConfig.SubClusters == nil { + break + } + + return e.complexity.MetricConfig.SubClusters(childComplexity), true + case "MetricConfig.timestep": if e.complexity.MetricConfig.Timestep == nil { break @@ -1181,6 +1197,41 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.SubCluster.Topology(childComplexity), true + case "SubClusterConfig.alert": + if e.complexity.SubClusterConfig.Alert == nil { + break + } + + return e.complexity.SubClusterConfig.Alert(childComplexity), true + + case "SubClusterConfig.caution": + if e.complexity.SubClusterConfig.Caution == nil { + break + } + + return e.complexity.SubClusterConfig.Caution(childComplexity), true + + case "SubClusterConfig.name": + if e.complexity.SubClusterConfig.Name == nil { + break + } + + return e.complexity.SubClusterConfig.Name(childComplexity), true + + case "SubClusterConfig.normal": + if e.complexity.SubClusterConfig.Normal == nil { + break + } + + return e.complexity.SubClusterConfig.Normal(childComplexity), true + + case "SubClusterConfig.peak": + if e.complexity.SubClusterConfig.Peak == nil { + break + } + + return e.complexity.SubClusterConfig.Peak(childComplexity), true + case "Tag.id": if e.complexity.Tag.ID == nil { break @@ -1413,16 +1464,25 @@ type Accelerator { model: String! } +type SubClusterConfig { + name: String! + peak: Float! + normal: Float! + caution: Float! + alert: Float! +} + type MetricConfig { name: String! unit: String! scope: MetricScope! aggregation: String timestep: Int! - peak: Float! - normal: Float! - caution: Float! - alert: Float! + peak: Float + normal: Float + caution: Float + alert: Float + subClusters: [SubClusterConfig] } type Tag { @@ -4352,14 +4412,11 @@ func (ec *executionContext) _MetricConfig_peak(ctx context.Context, field graphq return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } - res := resTmp.(float64) + res := resTmp.(*float64) fc.Result = res - return ec.marshalNFloat2float64(ctx, field.Selections, res) + return ec.marshalOFloat2ᚖfloat64(ctx, field.Selections, res) } func (ec *executionContext) _MetricConfig_normal(ctx context.Context, field graphql.CollectedField, obj *model.MetricConfig) (ret graphql.Marshaler) { @@ -4387,14 +4444,11 @@ func (ec *executionContext) _MetricConfig_normal(ctx context.Context, field grap return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } - res := resTmp.(float64) + res := resTmp.(*float64) fc.Result = res - return ec.marshalNFloat2float64(ctx, field.Selections, res) + return ec.marshalOFloat2ᚖfloat64(ctx, field.Selections, res) } func (ec *executionContext) _MetricConfig_caution(ctx context.Context, field graphql.CollectedField, obj *model.MetricConfig) (ret graphql.Marshaler) { @@ -4422,14 +4476,11 @@ func (ec *executionContext) _MetricConfig_caution(ctx context.Context, field gra return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } - res := resTmp.(float64) + res := resTmp.(*float64) fc.Result = res - return ec.marshalNFloat2float64(ctx, field.Selections, res) + return ec.marshalOFloat2ᚖfloat64(ctx, field.Selections, res) } func (ec *executionContext) _MetricConfig_alert(ctx context.Context, field graphql.CollectedField, obj *model.MetricConfig) (ret graphql.Marshaler) { @@ -4457,14 +4508,43 @@ func (ec *executionContext) _MetricConfig_alert(ctx context.Context, field graph return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } - res := resTmp.(float64) + res := resTmp.(*float64) fc.Result = res - return ec.marshalNFloat2float64(ctx, field.Selections, res) + return ec.marshalOFloat2ᚖfloat64(ctx, field.Selections, res) +} + +func (ec *executionContext) _MetricConfig_subClusters(ctx context.Context, field graphql.CollectedField, obj *model.MetricConfig) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "MetricConfig", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.SubClusters, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]*model.SubClusterConfig) + fc.Result = res + return ec.marshalOSubClusterConfig2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐSubClusterConfig(ctx, field.Selections, res) } func (ec *executionContext) _MetricFootprints_metric(ctx context.Context, field graphql.CollectedField, obj *model.MetricFootprints) (ret graphql.Marshaler) { @@ -6261,6 +6341,181 @@ func (ec *executionContext) _SubCluster_topology(ctx context.Context, field grap return ec.marshalNTopology2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐTopology(ctx, field.Selections, res) } +func (ec *executionContext) _SubClusterConfig_name(ctx context.Context, field graphql.CollectedField, obj *model.SubClusterConfig) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "SubClusterConfig", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _SubClusterConfig_peak(ctx context.Context, field graphql.CollectedField, obj *model.SubClusterConfig) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "SubClusterConfig", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Peak, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(float64) + fc.Result = res + return ec.marshalNFloat2float64(ctx, field.Selections, res) +} + +func (ec *executionContext) _SubClusterConfig_normal(ctx context.Context, field graphql.CollectedField, obj *model.SubClusterConfig) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "SubClusterConfig", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Normal, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(float64) + fc.Result = res + return ec.marshalNFloat2float64(ctx, field.Selections, res) +} + +func (ec *executionContext) _SubClusterConfig_caution(ctx context.Context, field graphql.CollectedField, obj *model.SubClusterConfig) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "SubClusterConfig", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Caution, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(float64) + fc.Result = res + return ec.marshalNFloat2float64(ctx, field.Selections, res) +} + +func (ec *executionContext) _SubClusterConfig_alert(ctx context.Context, field graphql.CollectedField, obj *model.SubClusterConfig) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "SubClusterConfig", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Alert, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(float64) + fc.Result = res + return ec.marshalNFloat2float64(ctx, field.Selections, res) +} + func (ec *executionContext) _Tag_id(ctx context.Context, field graphql.CollectedField, obj *schema.Tag) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -9217,9 +9472,6 @@ func (ec *executionContext) _MetricConfig(ctx context.Context, sel ast.Selection out.Values[i] = innerFunc(ctx) - if out.Values[i] == graphql.Null { - invalids++ - } case "normal": innerFunc := func(ctx context.Context) (res graphql.Marshaler) { return ec._MetricConfig_normal(ctx, field, obj) @@ -9227,9 +9479,6 @@ func (ec *executionContext) _MetricConfig(ctx context.Context, sel ast.Selection out.Values[i] = innerFunc(ctx) - if out.Values[i] == graphql.Null { - invalids++ - } case "caution": innerFunc := func(ctx context.Context) (res graphql.Marshaler) { return ec._MetricConfig_caution(ctx, field, obj) @@ -9237,9 +9486,6 @@ func (ec *executionContext) _MetricConfig(ctx context.Context, sel ast.Selection out.Values[i] = innerFunc(ctx) - if out.Values[i] == graphql.Null { - invalids++ - } case "alert": innerFunc := func(ctx context.Context) (res graphql.Marshaler) { return ec._MetricConfig_alert(ctx, field, obj) @@ -9247,9 +9493,13 @@ func (ec *executionContext) _MetricConfig(ctx context.Context, sel ast.Selection out.Values[i] = innerFunc(ctx) - if out.Values[i] == graphql.Null { - invalids++ + case "subClusters": + innerFunc := func(ctx context.Context) (res graphql.Marshaler) { + return ec._MetricConfig_subClusters(ctx, field, obj) } + + out.Values[i] = innerFunc(ctx) + default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -10081,6 +10331,77 @@ func (ec *executionContext) _SubCluster(ctx context.Context, sel ast.SelectionSe return out } +var subClusterConfigImplementors = []string{"SubClusterConfig"} + +func (ec *executionContext) _SubClusterConfig(ctx context.Context, sel ast.SelectionSet, obj *model.SubClusterConfig) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, subClusterConfigImplementors) + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("SubClusterConfig") + case "name": + innerFunc := func(ctx context.Context) (res graphql.Marshaler) { + return ec._SubClusterConfig_name(ctx, field, obj) + } + + out.Values[i] = innerFunc(ctx) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "peak": + innerFunc := func(ctx context.Context) (res graphql.Marshaler) { + return ec._SubClusterConfig_peak(ctx, field, obj) + } + + out.Values[i] = innerFunc(ctx) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "normal": + innerFunc := func(ctx context.Context) (res graphql.Marshaler) { + return ec._SubClusterConfig_normal(ctx, field, obj) + } + + out.Values[i] = innerFunc(ctx) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "caution": + innerFunc := func(ctx context.Context) (res graphql.Marshaler) { + return ec._SubClusterConfig_caution(ctx, field, obj) + } + + out.Values[i] = innerFunc(ctx) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "alert": + innerFunc := func(ctx context.Context) (res graphql.Marshaler) { + return ec._SubClusterConfig_alert(ctx, field, obj) + } + + out.Values[i] = innerFunc(ctx) + + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var tagImplementors = []string{"Tag"} func (ec *executionContext) _Tag(ctx context.Context, sel ast.SelectionSet, obj *schema.Tag) graphql.Marshaler { @@ -12214,6 +12535,22 @@ func (ec *executionContext) marshalOBoolean2ᚖbool(ctx context.Context, sel ast return res } +func (ec *executionContext) unmarshalOFloat2ᚖfloat64(ctx context.Context, v interface{}) (*float64, error) { + if v == nil { + return nil, nil + } + res, err := graphql.UnmarshalFloatContext(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOFloat2ᚖfloat64(ctx context.Context, sel ast.SelectionSet, v *float64) graphql.Marshaler { + if v == nil { + return graphql.Null + } + res := graphql.MarshalFloatContext(*v) + return graphql.WrapContextMarshaler(ctx, res) +} + func (ec *executionContext) unmarshalOFloatRange2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐFloatRange(ctx context.Context, v interface{}) (*model.FloatRange, error) { if v == nil { return nil, nil @@ -12627,6 +12964,54 @@ func (ec *executionContext) unmarshalOStringInput2ᚖgithubᚗcomᚋClusterCockp return &res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) marshalOSubClusterConfig2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐSubClusterConfig(ctx context.Context, sel ast.SelectionSet, v []*model.SubClusterConfig) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalOSubClusterConfig2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐSubClusterConfig(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + return ret +} + +func (ec *executionContext) marshalOSubClusterConfig2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋgraphᚋmodelᚐSubClusterConfig(ctx context.Context, sel ast.SelectionSet, v *model.SubClusterConfig) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._SubClusterConfig(ctx, sel, v) +} + func (ec *executionContext) unmarshalOTime2ᚖtimeᚐTime(ctx context.Context, v interface{}) (*time.Time, error) { if v == nil { return nil, nil diff --git a/graph/model/models_gen.go b/graph/model/models_gen.go index 34e84a4..ca8186e 100644 --- a/graph/model/models_gen.go +++ b/graph/model/models_gen.go @@ -97,15 +97,16 @@ type JobsStatistics struct { } type MetricConfig struct { - Name string `json:"name"` - Unit string `json:"unit"` - Scope schema.MetricScope `json:"scope"` - Aggregation *string `json:"aggregation"` - Timestep int `json:"timestep"` - Peak float64 `json:"peak"` - Normal float64 `json:"normal"` - Caution float64 `json:"caution"` - Alert float64 `json:"alert"` + Name string `json:"name"` + Unit string `json:"unit"` + Scope schema.MetricScope `json:"scope"` + Aggregation *string `json:"aggregation"` + Timestep int `json:"timestep"` + Peak *float64 `json:"peak"` + Normal *float64 `json:"normal"` + Caution *float64 `json:"caution"` + Alert *float64 `json:"alert"` + SubClusters []*SubClusterConfig `json:"subClusters"` } type MetricFootprints struct { @@ -150,6 +151,14 @@ type SubCluster struct { Topology *Topology `json:"topology"` } +type SubClusterConfig struct { + Name string `json:"name"` + Peak float64 `json:"peak"` + Normal float64 `json:"normal"` + Caution float64 `json:"caution"` + Alert float64 `json:"alert"` +} + type TimeRange struct { From *time.Time `json:"from"` To *time.Time `json:"to"` diff --git a/graph/schema.graphqls b/graph/schema.graphqls index bb4c68d..8652bed 100644 --- a/graph/schema.graphqls +++ b/graph/schema.graphqls @@ -68,16 +68,25 @@ type Accelerator { model: String! } +type SubClusterConfig { + name: String! + peak: Float! + normal: Float! + caution: Float! + alert: Float! +} + type MetricConfig { name: String! unit: String! scope: MetricScope! aggregation: String timestep: Int! - peak: Float! - normal: Float! - caution: Float! - alert: Float! + peak: Float + normal: Float + caution: Float + alert: Float + subClusters: [SubClusterConfig] } type Tag { From daa2fd638bc485c5a12cd7b8c48f4c86e5162094 Mon Sep 17 00:00:00 2001 From: Lou Knauer Date: Mon, 16 May 2022 09:10:55 +0200 Subject: [PATCH 3/3] make nodelist parser more slurm-like --- config/nodelist.go | 133 +++++++++++++++++++++++++--------------- config/nodelist_test.go | 28 +++++++-- 2 files changed, 107 insertions(+), 54 deletions(-) diff --git a/config/nodelist.go b/config/nodelist.go index 800e1ba..fb823df 100644 --- a/config/nodelist.go +++ b/config/nodelist.go @@ -8,6 +8,29 @@ import ( "github.com/ClusterCockpit/cc-backend/log" ) +type NodeList [][]interface { + consume(input string) (next string, ok bool) +} + +func (nl *NodeList) Contains(name string) bool { + var ok bool + for _, term := range *nl { + str := name + for _, expr := range term { + str, ok = expr.consume(str) + if !ok { + break + } + } + + if ok && str == "" { + return true + } + } + + return false +} + type NLExprString string func (nle NLExprString) consume(input string) (next string, ok bool) { @@ -18,6 +41,17 @@ func (nle NLExprString) consume(input string) (next string, ok bool) { return "", false } +type NLExprIntRanges []NLExprIntRange + +func (nles NLExprIntRanges) consume(input string) (next string, ok bool) { + for _, nle := range nles { + if next, ok := nle.consume(input); ok { + return next, ok + } + } + return "", false +} + type NLExprIntRange struct { start, end int64 zeroPadded bool @@ -51,36 +85,31 @@ func (nle NLExprIntRange) consume(input string) (next string, ok bool) { return "", false } -type NodeList [][]interface { - consume(input string) (next string, ok bool) -} - -func (nl *NodeList) Contains(name string) bool { - var ok bool - for _, term := range *nl { - str := name - for _, expr := range term { - str, ok = expr.consume(str) - if !ok { - break - } - } - - if ok && str == "" { - return true - } - } - - return false -} - func ParseNodeList(raw string) (NodeList, error) { - nl := NodeList{} - isLetter := func(r byte) bool { return ('a' <= r && r <= 'z') || ('A' <= r && r <= 'Z') } isDigit := func(r byte) bool { return '0' <= r && r <= '9' } - for _, rawterm := range strings.Split(raw, ",") { + rawterms := []string{} + prevterm := 0 + for i := 0; i < len(raw); i++ { + if raw[i] == '[' { + for i < len(raw) && raw[i] != ']' { + i++ + } + if i == len(raw) { + return nil, fmt.Errorf("node list: unclosed '['") + } + } else if raw[i] == ',' { + rawterms = append(rawterms, raw[prevterm:i]) + prevterm = i + 1 + } + } + if prevterm != len(raw) { + rawterms = append(rawterms, raw[prevterm:]) + } + + nl := NodeList{} + for _, rawterm := range rawterms { exprs := []interface { consume(input string) (next string, ok bool) }{} @@ -99,31 +128,37 @@ func ParseNodeList(raw string) (NodeList, error) { return nil, fmt.Errorf("node list: unclosed '['") } - minus := strings.Index(rawterm[i:i+end], "-") - if minus == -1 { - return nil, fmt.Errorf("node list: no '-' found inside '[...]'") + parts := strings.Split(rawterm[i+1:i+end], ",") + nles := NLExprIntRanges{} + for _, part := range parts { + minus := strings.Index(part, "-") + if minus == -1 { + return nil, fmt.Errorf("node list: no '-' found inside '[...]'") + } + + s1, s2 := part[0:minus], part[minus+1:] + if len(s1) != len(s2) || len(s1) == 0 { + return nil, fmt.Errorf("node list: %#v and %#v are not of equal length or of length zero", s1, s2) + } + + x1, err := strconv.ParseInt(s1, 10, 32) + if err != nil { + return nil, fmt.Errorf("node list: %w", err) + } + x2, err := strconv.ParseInt(s2, 10, 32) + if err != nil { + return nil, fmt.Errorf("node list: %w", err) + } + + nles = append(nles, NLExprIntRange{ + start: x1, + end: x2, + digits: len(s1), + zeroPadded: true, + }) } - s1, s2 := rawterm[i+1:i+minus], rawterm[i+minus+1:i+end] - if len(s1) != len(s2) || len(s1) == 0 { - return nil, fmt.Errorf("node list: %#v and %#v are not of equal length or of length zero", s1, s2) - } - - x1, err := strconv.ParseInt(s1, 10, 32) - if err != nil { - return nil, fmt.Errorf("node list: %w", err) - } - x2, err := strconv.ParseInt(s2, 10, 32) - if err != nil { - return nil, fmt.Errorf("node list: %w", err) - } - - exprs = append(exprs, NLExprIntRange{ - start: x1, - end: x2, - digits: len(s1), - zeroPadded: true, - }) + exprs = append(exprs, nles) i += end } else { return nil, fmt.Errorf("node list: invalid character: %#v", rune(c)) diff --git a/config/nodelist_test.go b/config/nodelist_test.go index 6768d59..b1f4a6f 100644 --- a/config/nodelist_test.go +++ b/config/nodelist_test.go @@ -10,11 +10,6 @@ func TestNodeList(t *testing.T) { t.Fatal(err) } - // fmt.Printf("terms\n") - // for i, term := range nl.terms { - // fmt.Printf("term %d: %#v\n", i, term) - // } - if nl.Contains("hello") || nl.Contains("woody") { t.Fail() } @@ -35,3 +30,26 @@ func TestNodeList(t *testing.T) { t.Fail() } } + +func TestNodeListCommasInBrackets(t *testing.T) { + nl, err := ParseNodeList("a[1000-2000,2010-2090,3000-5000]") + if err != nil { + t.Fatal(err) + } + + if nl.Contains("hello") || nl.Contains("woody") { + t.Fatal("1") + } + + if nl.Contains("a0") || nl.Contains("a0000") || nl.Contains("a5001") || nl.Contains("a2005") { + t.Fatal("2") + } + + if !nl.Contains("a1001") || !nl.Contains("a2000") { + t.Fatal("3") + } + + if !nl.Contains("a2042") || !nl.Contains("a4321") || !nl.Contains("a3000") { + t.Fatal("4") + } +}