diff --git a/api/schema.graphqls b/api/schema.graphqls index d05c658..3a4e3ea 100644 --- a/api/schema.graphqls +++ b/api/schema.graphqls @@ -361,7 +361,7 @@ type Query { from: Time! to: Time! ): [NodeMetrics!]! - + nodeMetricsList( cluster: String! subCluster: String! diff --git a/go.mod b/go.mod index 0725a30..d830d1c 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,12 @@ module github.com/ClusterCockpit/cc-backend -go 1.23.5 +go 1.24.0 toolchain go1.24.1 require ( github.com/99designs/gqlgen v0.17.78 - github.com/ClusterCockpit/cc-lib v0.8.0 + github.com/ClusterCockpit/cc-lib v0.9.1 github.com/Masterminds/squirrel v1.5.4 github.com/coreos/go-oidc/v3 v3.12.0 github.com/expr-lang/expr v1.17.6 @@ -34,7 +34,7 @@ require ( github.com/vektah/gqlparser/v2 v2.5.30 golang.org/x/crypto v0.41.0 golang.org/x/oauth2 v0.30.0 - golang.org/x/time v0.12.0 + golang.org/x/time v0.13.0 ) require ( @@ -89,7 +89,7 @@ require ( golang.org/x/mod v0.26.0 // indirect golang.org/x/net v0.43.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.35.0 // indirect + golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.28.0 // indirect golang.org/x/tools v0.35.0 // indirect google.golang.org/protobuf v1.36.8 // indirect diff --git a/go.sum b/go.sum index 81ae22b..ea23aa5 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/ClusterCockpit/cc-lib v0.8.0 h1:kQRMOx30CJCy+Q6TgCK9rarJnJ/CKZPWlIEdIXYlxoA= -github.com/ClusterCockpit/cc-lib v0.8.0/go.mod h1:5xTwONu9pSp15mJ9CjBKGU9I3Jad8NfhrVHJZl50/yI= +github.com/ClusterCockpit/cc-lib v0.9.1 h1:pcUbpcbD1o4u7gILiBFVnO9DyQpji/Lgq+pIQ/CwxQo= +github.com/ClusterCockpit/cc-lib v0.9.1/go.mod h1:RRud94Y5qXAvosww0LxbdBFKXndVN4FnwgS1PxVTKbc= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= @@ -341,8 +341,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -363,8 +363,8 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= +golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/gqlgen.yml b/gqlgen.yml index c4b3faf..186aaa1 100644 --- a/gqlgen.yml +++ b/gqlgen.yml @@ -63,11 +63,11 @@ models: fields: partitions: resolver: true - Node: - model: "github.com/ClusterCockpit/cc-lib/schema.Node" - fields: - metaData: - resolver: true + # Node: + # model: "github.com/ClusterCockpit/cc-lib/schema.Node" + # fields: + # metaData: + # resolver: true NullableFloat: { model: "github.com/ClusterCockpit/cc-lib/schema.Float" } MetricScope: { model: "github.com/ClusterCockpit/cc-lib/schema.MetricScope" } MetricValue: { model: "github.com/ClusterCockpit/cc-lib/schema.MetricValue" } @@ -81,7 +81,7 @@ models: Resource: { model: "github.com/ClusterCockpit/cc-lib/schema.Resource" } JobState: { model: "github.com/ClusterCockpit/cc-lib/schema.JobState" } MonitoringState: - { model: "github.com/ClusterCockpit/cc-lib/schema.NodeState" } + { model: "github.com/ClusterCockpit/cc-lib/schema.SchedulerState" } HealthState: { model: "github.com/ClusterCockpit/cc-lib/schema.MonitoringState" } JobMetric: { model: "github.com/ClusterCockpit/cc-lib/schema.JobMetric" } diff --git a/internal/api/node.go b/internal/api/node.go index 19dad39..310e22b 100644 --- a/internal/api/node.go +++ b/internal/api/node.go @@ -16,14 +16,12 @@ import ( ) type Node struct { - Name string `json:"hostname"` + Hostname string `json:"hostname"` States []string `json:"states"` CpusAllocated int `json:"cpusAllocated"` - CpusTotal int `json:"cpusTotal"` MemoryAllocated int `json:"memoryAllocated"` - MemoryTotal int `json:"memoryTotal"` GpusAllocated int `json:"gpusAllocated"` - GpusTotal int `json:"gpusTotal"` + JobsRunning int `json:"jobsRunning"` } type UpdateNodeStatesRequest struct { @@ -32,7 +30,7 @@ type UpdateNodeStatesRequest struct { } // this routine assumes that only one of them exists per node -func determineState(states []string) schema.NodeState { +func determineState(states []string) schema.SchedulerState { for _, state := range states { switch strings.ToLower(state) { case "allocated": @@ -77,15 +75,15 @@ func (api *RestApi) updateNodeStates(rw http.ResponseWriter, r *http.Request) { for _, node := range req.Nodes { state := determineState(node.States) - nodeState := schema.Node{ + nodeState := schema.NodeState{ TimeStamp: time.Now().Unix(), NodeState: state, - Hostname: node.Name, Cluster: req.Cluster, - CpusAllocated: node.CpusAllocated, CpusTotal: node.CpusTotal, - MemoryAllocated: node.MemoryAllocated, MemoryTotal: node.MemoryTotal, - GpusAllocated: node.GpusAllocated, GpusTotal: node.GpusTotal, - HealthState: schema.MonitoringStateFull, + CpusAllocated: node.CpusAllocated, + MemoryAllocated: node.MemoryAllocated, + GpusAllocated: node.GpusAllocated, + HealthState: schema.MonitoringStateFull, + JobsRunning: node.JobsRunning, } - repo.InsertNodeState(&nodeState) + repo.UpdateNodeState(node.Hostname, req.Cluster, &nodeState) } } diff --git a/internal/graph/generated/generated.go b/internal/graph/generated/generated.go index 766b748..fe1894c 100644 --- a/internal/graph/generated/generated.go +++ b/internal/graph/generated/generated.go @@ -447,10 +447,9 @@ type MutationResolver interface { UpdateConfiguration(ctx context.Context, name string, value string) (*string, error) } type NodeResolver interface { - RunningJobs(ctx context.Context, obj *schema.Node) (int, error) - NodeState(ctx context.Context, obj *schema.Node) (string, error) - HealthState(ctx context.Context, obj *schema.Node) (schema.NodeState, error) - MetaData(ctx context.Context, obj *schema.Node) (any, error) + NodeState(ctx context.Context, obj *model.Node) (string, error) + HealthState(ctx context.Context, obj *model.Node) (schema.SchedulerState, error) + MetaData(ctx context.Context, obj *model.Node) (any, error) } type QueryResolver interface { Clusters(ctx context.Context) ([]*schema.Cluster, error) @@ -458,7 +457,7 @@ type QueryResolver interface { GlobalMetrics(ctx context.Context) ([]*schema.GlobalMetricListItem, error) User(ctx context.Context, username string) (*model.User, error) AllocatedNodes(ctx context.Context, cluster string) ([]*model.Count, error) - Node(ctx context.Context, id string) (*schema.Node, error) + Node(ctx context.Context, id string) (*model.Node, error) Nodes(ctx context.Context, filter []*model.NodeFilter, order *model.OrderByInput) (*model.NodeStateResultList, error) NodeStates(ctx context.Context, filter []*model.NodeFilter) ([]*model.NodeStates, error) Job(ctx context.Context, id string) (*schema.Job, error) @@ -2700,7 +2699,7 @@ type Query { from: Time! to: Time! ): [NodeMetrics!]! - + nodeMetricsList( cluster: String! subCluster: String! @@ -9680,7 +9679,7 @@ func (ec *executionContext) fieldContext_NamedStatsWithScope_stats(_ context.Con return fc, nil } -func (ec *executionContext) _Node_id(ctx context.Context, field graphql.CollectedField, obj *schema.Node) (ret graphql.Marshaler) { +func (ec *executionContext) _Node_id(ctx context.Context, field graphql.CollectedField, obj *model.Node) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Node_id(ctx, field) if err != nil { return graphql.Null @@ -9724,7 +9723,7 @@ func (ec *executionContext) fieldContext_Node_id(_ context.Context, field graphq return fc, nil } -func (ec *executionContext) _Node_hostname(ctx context.Context, field graphql.CollectedField, obj *schema.Node) (ret graphql.Marshaler) { +func (ec *executionContext) _Node_hostname(ctx context.Context, field graphql.CollectedField, obj *model.Node) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Node_hostname(ctx, field) if err != nil { return graphql.Null @@ -9768,7 +9767,7 @@ func (ec *executionContext) fieldContext_Node_hostname(_ context.Context, field return fc, nil } -func (ec *executionContext) _Node_cluster(ctx context.Context, field graphql.CollectedField, obj *schema.Node) (ret graphql.Marshaler) { +func (ec *executionContext) _Node_cluster(ctx context.Context, field graphql.CollectedField, obj *model.Node) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Node_cluster(ctx, field) if err != nil { return graphql.Null @@ -9812,7 +9811,7 @@ func (ec *executionContext) fieldContext_Node_cluster(_ context.Context, field g return fc, nil } -func (ec *executionContext) _Node_subCluster(ctx context.Context, field graphql.CollectedField, obj *schema.Node) (ret graphql.Marshaler) { +func (ec *executionContext) _Node_subCluster(ctx context.Context, field graphql.CollectedField, obj *model.Node) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Node_subCluster(ctx, field) if err != nil { return graphql.Null @@ -9856,7 +9855,7 @@ func (ec *executionContext) fieldContext_Node_subCluster(_ context.Context, fiel return fc, nil } -func (ec *executionContext) _Node_runningJobs(ctx context.Context, field graphql.CollectedField, obj *schema.Node) (ret graphql.Marshaler) { +func (ec *executionContext) _Node_runningJobs(ctx context.Context, field graphql.CollectedField, obj *model.Node) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Node_runningJobs(ctx, field) if err != nil { return graphql.Null @@ -9870,7 +9869,7 @@ func (ec *executionContext) _Node_runningJobs(ctx context.Context, field graphql }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Node().RunningJobs(rctx, obj) + return obj.RunningJobs, nil }) if err != nil { ec.Error(ctx, err) @@ -9891,8 +9890,8 @@ func (ec *executionContext) fieldContext_Node_runningJobs(_ context.Context, fie fc = &graphql.FieldContext{ Object: "Node", Field: field, - IsMethod: true, - IsResolver: true, + IsMethod: false, + IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { return nil, errors.New("field of type Int does not have child fields") }, @@ -9900,7 +9899,7 @@ func (ec *executionContext) fieldContext_Node_runningJobs(_ context.Context, fie return fc, nil } -func (ec *executionContext) _Node_nodeState(ctx context.Context, field graphql.CollectedField, obj *schema.Node) (ret graphql.Marshaler) { +func (ec *executionContext) _Node_nodeState(ctx context.Context, field graphql.CollectedField, obj *model.Node) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Node_nodeState(ctx, field) if err != nil { return graphql.Null @@ -9944,7 +9943,7 @@ func (ec *executionContext) fieldContext_Node_nodeState(_ context.Context, field return fc, nil } -func (ec *executionContext) _Node_healthState(ctx context.Context, field graphql.CollectedField, obj *schema.Node) (ret graphql.Marshaler) { +func (ec *executionContext) _Node_healthState(ctx context.Context, field graphql.CollectedField, obj *model.Node) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Node_healthState(ctx, field) if err != nil { return graphql.Null @@ -9970,9 +9969,9 @@ func (ec *executionContext) _Node_healthState(ctx context.Context, field graphql } return graphql.Null } - res := resTmp.(schema.NodeState) + res := resTmp.(schema.SchedulerState) fc.Result = res - return ec.marshalNMonitoringState2githubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐNodeState(ctx, field.Selections, res) + return ec.marshalNMonitoringState2githubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐSchedulerState(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Node_healthState(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -9988,7 +9987,7 @@ func (ec *executionContext) fieldContext_Node_healthState(_ context.Context, fie return fc, nil } -func (ec *executionContext) _Node_metaData(ctx context.Context, field graphql.CollectedField, obj *schema.Node) (ret graphql.Marshaler) { +func (ec *executionContext) _Node_metaData(ctx context.Context, field graphql.CollectedField, obj *model.Node) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Node_metaData(ctx, field) if err != nil { return graphql.Null @@ -10195,9 +10194,9 @@ func (ec *executionContext) _NodeStateResultList_items(ctx context.Context, fiel } return graphql.Null } - res := resTmp.([]*schema.Node) + res := resTmp.([]*model.Node) fc.Result = res - return ec.marshalNNode2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐNodeᚄ(ctx, field.Selections, res) + return ec.marshalNNode2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐNodeᚄ(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_NodeStateResultList_items(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -10923,9 +10922,9 @@ func (ec *executionContext) _Query_node(ctx context.Context, field graphql.Colle if resTmp == nil { return graphql.Null } - res := resTmp.(*schema.Node) + res := resTmp.(*model.Node) fc.Result = res - return ec.marshalONode2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐNode(ctx, field.Selections, res) + return ec.marshalONode2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐNode(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Query_node(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -16704,7 +16703,7 @@ func (ec *executionContext) unmarshalInputNodeFilter(ctx context.Context, obj an it.NodeState = data case "healthState": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("healthState")) - data, err := ec.unmarshalOMonitoringState2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐNodeState(ctx, v) + data, err := ec.unmarshalOMonitoringState2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐSchedulerState(ctx, v) if err != nil { return it, err } @@ -18726,7 +18725,7 @@ func (ec *executionContext) _NamedStatsWithScope(ctx context.Context, sel ast.Se var nodeImplementors = []string{"Node"} -func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj *schema.Node) graphql.Marshaler { +func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj *model.Node) graphql.Marshaler { fields := graphql.CollectFields(ec.OperationContext, sel, nodeImplementors) out := graphql.NewFieldSet(fields) @@ -18756,41 +18755,10 @@ func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj atomic.AddUint32(&out.Invalids, 1) } case "runningJobs": - field := field - - innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - } - }() - res = ec._Node_runningJobs(ctx, field, obj) - if res == graphql.Null { - atomic.AddUint32(&fs.Invalids, 1) - } - return res + out.Values[i] = ec._Node_runningJobs(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) } - - if field.Deferrable != nil { - dfs, ok := deferred[field.Deferrable.Label] - di := 0 - if ok { - dfs.AddField(field) - di = len(dfs.Values) - 1 - } else { - dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) - deferred[field.Deferrable.Label] = dfs - } - dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { - return innerFunc(ctx, dfs) - }) - - // don't run the out.Concurrently() call below - out.Values[i] = graphql.Null - continue - } - - out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) case "nodeState": field := field @@ -21654,13 +21622,13 @@ func (ec *executionContext) marshalNMetricValue2githubᚗcomᚋClusterCockpitᚋ return ec._MetricValue(ctx, sel, &v) } -func (ec *executionContext) unmarshalNMonitoringState2githubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐNodeState(ctx context.Context, v any) (schema.NodeState, error) { +func (ec *executionContext) unmarshalNMonitoringState2githubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐSchedulerState(ctx context.Context, v any) (schema.SchedulerState, error) { tmp, err := graphql.UnmarshalString(v) - res := schema.NodeState(tmp) + res := schema.SchedulerState(tmp) return res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) marshalNMonitoringState2githubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐNodeState(ctx context.Context, sel ast.SelectionSet, v schema.NodeState) graphql.Marshaler { +func (ec *executionContext) marshalNMonitoringState2githubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐSchedulerState(ctx context.Context, sel ast.SelectionSet, v schema.SchedulerState) graphql.Marshaler { _ = sel res := graphql.MarshalString(string(v)) if res == graphql.Null { @@ -21779,7 +21747,7 @@ func (ec *executionContext) marshalNNamedStatsWithScope2ᚖgithubᚗcomᚋCluste return ec._NamedStatsWithScope(ctx, sel, v) } -func (ec *executionContext) marshalNNode2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐNodeᚄ(ctx context.Context, sel ast.SelectionSet, v []*schema.Node) graphql.Marshaler { +func (ec *executionContext) marshalNNode2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐNodeᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.Node) graphql.Marshaler { ret := make(graphql.Array, len(v)) var wg sync.WaitGroup isLen1 := len(v) == 1 @@ -21803,7 +21771,7 @@ func (ec *executionContext) marshalNNode2ᚕᚖgithubᚗcomᚋClusterCockpitᚋc if !isLen1 { defer wg.Done() } - ret[i] = ec.marshalNNode2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐNode(ctx, sel, v[i]) + ret[i] = ec.marshalNNode2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐNode(ctx, sel, v[i]) } if isLen1 { f(i) @@ -21823,7 +21791,7 @@ func (ec *executionContext) marshalNNode2ᚕᚖgithubᚗcomᚋClusterCockpitᚋc return ret } -func (ec *executionContext) marshalNNode2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐNode(ctx context.Context, sel ast.SelectionSet, v *schema.Node) graphql.Marshaler { +func (ec *executionContext) marshalNNode2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐNode(ctx context.Context, sel ast.SelectionSet, v *model.Node) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { ec.Errorf(ctx, "the requested element is null which the schema does not allow") @@ -23249,16 +23217,16 @@ func (ec *executionContext) marshalOMetricStatistics2githubᚗcomᚋClusterCockp return ec._MetricStatistics(ctx, sel, &v) } -func (ec *executionContext) unmarshalOMonitoringState2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐNodeState(ctx context.Context, v any) (*schema.NodeState, error) { +func (ec *executionContext) unmarshalOMonitoringState2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐSchedulerState(ctx context.Context, v any) (*schema.SchedulerState, error) { if v == nil { return nil, nil } tmp, err := graphql.UnmarshalString(v) - res := schema.NodeState(tmp) + res := schema.SchedulerState(tmp) return &res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) marshalOMonitoringState2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐNodeState(ctx context.Context, sel ast.SelectionSet, v *schema.NodeState) graphql.Marshaler { +func (ec *executionContext) marshalOMonitoringState2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐSchedulerState(ctx context.Context, sel ast.SelectionSet, v *schema.SchedulerState) graphql.Marshaler { if v == nil { return graphql.Null } @@ -23268,7 +23236,7 @@ func (ec *executionContext) marshalOMonitoringState2ᚖgithubᚗcomᚋClusterCoc return res } -func (ec *executionContext) marshalONode2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑlibᚋschemaᚐNode(ctx context.Context, sel ast.SelectionSet, v *schema.Node) graphql.Marshaler { +func (ec *executionContext) marshalONode2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐNode(ctx context.Context, sel ast.SelectionSet, v *model.Node) graphql.Marshaler { if v == nil { return graphql.Null } diff --git a/internal/graph/model/models.go b/internal/graph/model/models.go index c943700..00ebef5 100644 --- a/internal/graph/model/models.go +++ b/internal/graph/model/models.go @@ -2,4 +2,22 @@ // All rights reserved. This file is part of cc-backend. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. + package model + +import ( + "github.com/ClusterCockpit/cc-lib/schema" +) + +type Node struct { + ID int64 + Hostname string `json:"hostname"` + Cluster string `json:"cluster"` + SubCluster string `json:"subCluster"` + RunningJobs int `json:"jobsRunning"` + CpusAllocated int `json:"cpusAllocated"` + MemoryAllocated int `json:"memoryAllocated"` + GpusAllocated int `json:"gpusAllocated"` + NodeState schema.NodeState `json:"nodeState"` + HealthState schema.MonitoringState `json:"healthState"` +} diff --git a/internal/graph/model/models_gen.go b/internal/graph/model/models_gen.go index 9b87864..a8f099c 100644 --- a/internal/graph/model/models_gen.go +++ b/internal/graph/model/models_gen.go @@ -171,11 +171,11 @@ type NamedStatsWithScope struct { } type NodeFilter struct { - Hostname *StringInput `json:"hostname,omitempty"` - Cluster *StringInput `json:"cluster,omitempty"` - Subcluster *StringInput `json:"subcluster,omitempty"` - NodeState *string `json:"nodeState,omitempty"` - HealthState *schema.NodeState `json:"healthState,omitempty"` + Hostname *StringInput `json:"hostname,omitempty"` + Cluster *StringInput `json:"cluster,omitempty"` + Subcluster *StringInput `json:"subcluster,omitempty"` + NodeState *string `json:"nodeState,omitempty"` + HealthState *schema.SchedulerState `json:"healthState,omitempty"` } type NodeMetrics struct { @@ -185,8 +185,8 @@ type NodeMetrics struct { } type NodeStateResultList struct { - Items []*schema.Node `json:"items"` - Count *int `json:"count,omitempty"` + Items []*Node `json:"items"` + Count *int `json:"count,omitempty"` } type NodeStates struct { diff --git a/internal/graph/schema.resolvers.go b/internal/graph/schema.resolvers.go index cbe3650..04cd7f2 100644 --- a/internal/graph/schema.resolvers.go +++ b/internal/graph/schema.resolvers.go @@ -305,24 +305,18 @@ func (r *mutationResolver) UpdateConfiguration(ctx context.Context, name string, return nil, nil } -// RunningJobs is the resolver for the runningJobs field. -func (r *nodeResolver) RunningJobs(ctx context.Context, obj *schema.Node) (int, error) { - panic(fmt.Errorf("not implemented: RunningJobs - runningJobs")) -} - // NodeState is the resolver for the nodeState field. -func (r *nodeResolver) NodeState(ctx context.Context, obj *schema.Node) (string, error) { - return string(obj.NodeState), nil +func (r *nodeResolver) NodeState(ctx context.Context, obj *model.Node) (string, error) { + panic(fmt.Errorf("not implemented: NodeState - nodeState")) } // HealthState is the resolver for the healthState field. -func (r *nodeResolver) HealthState(ctx context.Context, obj *schema.Node) (schema.NodeState, error) { - // FIXME: Why is Output of schema.NodeState Type? +func (r *nodeResolver) HealthState(ctx context.Context, obj *model.Node) (schema.SchedulerState, error) { panic(fmt.Errorf("not implemented: HealthState - healthState")) } // MetaData is the resolver for the metaData field. -func (r *nodeResolver) MetaData(ctx context.Context, obj *schema.Node) (any, error) { +func (r *nodeResolver) MetaData(ctx context.Context, obj *model.Node) (any, error) { panic(fmt.Errorf("not implemented: MetaData - metaData")) } @@ -366,44 +360,18 @@ func (r *queryResolver) AllocatedNodes(ctx context.Context, cluster string) ([]* } // Node is the resolver for the node field. -func (r *queryResolver) Node(ctx context.Context, id string) (*schema.Node, error) { - repo := repository.GetNodeRepository() - numericId, err := strconv.ParseInt(id, 10, 64) - if err != nil { - cclog.Warn("Error while parsing job id") - return nil, err - } - return repo.GetNode(numericId, false) +func (r *queryResolver) Node(ctx context.Context, id string) (*model.Node, error) { + panic(fmt.Errorf("not implemented: Node - node")) } // Nodes is the resolver for the nodes field. func (r *queryResolver) Nodes(ctx context.Context, filter []*model.NodeFilter, order *model.OrderByInput) (*model.NodeStateResultList, error) { - repo := repository.GetNodeRepository() - nodes, err := repo.QueryNodes(ctx, filter, order) - count := len(nodes) - return &model.NodeStateResultList{Items: nodes, Count: &count}, err + panic(fmt.Errorf("not implemented: Nodes - nodes")) } // NodeStates is the resolver for the nodeStates field. func (r *queryResolver) NodeStates(ctx context.Context, filter []*model.NodeFilter) ([]*model.NodeStates, error) { - repo := repository.GetNodeRepository() - - stateCounts, serr := repo.CountNodeStates(ctx, filter) - if serr != nil { - cclog.Warnf("Error while counting nodeStates: %s", serr.Error()) - return nil, serr - } - - healthCounts, herr := repo.CountHealthStates(ctx, filter) - if herr != nil { - cclog.Warnf("Error while counting healthStates: %s", herr.Error()) - return nil, herr - } - - allCounts := make([]*model.NodeStates, 0) - allCounts = append(stateCounts, healthCounts...) - - return allCounts, nil + panic(fmt.Errorf("not implemented: NodeStates - nodeStates")) } // Job is the resolver for the job field. @@ -854,15 +822,3 @@ type mutationResolver struct{ *Resolver } type nodeResolver struct{ *Resolver } type queryResolver struct{ *Resolver } type subClusterResolver struct{ *Resolver } - -// !!! WARNING !!! -// The code below was going to be deleted when updating resolvers. It has been copied here so you have -// one last chance to move it out of harms way if you want. There are two reasons this happens: -// - When renaming or deleting a resolver the old code will be put in here. You can safely delete -// it when you're done. -// - You have helper methods in this file. Move them out to keep these resolver files clean. -/* - func (r *jobResolver) Exclusive(ctx context.Context, obj *schema.Job) (int, error) { - panic(fmt.Errorf("not implemented: Exclusive - exclusive")) -} -*/ diff --git a/internal/repository/migrations/sqlite3/10_node-table.up.sql b/internal/repository/migrations/sqlite3/10_node-table.up.sql index 2291aea..39656f9 100644 --- a/internal/repository/migrations/sqlite3/10_node-table.up.sql +++ b/internal/repository/migrations/sqlite3/10_node-table.up.sql @@ -1,5 +1,3 @@ --- sqlfluff:dialect:sqlite --- CREATE TABLE "node" ( id INTEGER PRIMARY KEY, hostname VARCHAR(255) NOT NULL, @@ -13,9 +11,6 @@ CREATE TABLE "node_state" ( id INTEGER PRIMARY KEY, time_stamp INTEGER NOT NULL, jobs_running INTEGER DEFAULT 0 NOT NULL, - cpus_total INTEGER DEFAULT 0 NOT NULL, - memory_total INTEGER DEFAULT 0 NOT NULL, - gpus_total INTEGER DEFAULT 0 NOT NULL, cpus_allocated INTEGER DEFAULT 0 NOT NULL, memory_allocated INTEGER DEFAULT 0 NOT NULL, gpus_allocated INTEGER DEFAULT 0 NOT NULL, @@ -35,10 +30,6 @@ CREATE TABLE "node_state" ( -- Add Indices For New Node Table VARCHAR Fields CREATE INDEX IF NOT EXISTS nodes_cluster ON node (cluster); CREATE INDEX IF NOT EXISTS nodes_cluster_subcluster ON node (cluster, subcluster); -CREATE INDEX IF NOT EXISTS nodes_state ON node (node_state); -CREATE INDEX IF NOT EXISTS nodes_cluster_state ON node (cluster, node_state); -CREATE INDEX IF NOT EXISTS nodes_health ON node (health_state); -CREATE INDEX IF NOT EXISTS nodes_cluster_health ON node (cluster, health_state); -- Add Indices For Increased Amounts of Tags CREATE INDEX IF NOT EXISTS tags_jobid ON jobtag (job_id); diff --git a/internal/repository/node.go b/internal/repository/node.go index 3a18625..1331fd8 100644 --- a/internal/repository/node.go +++ b/internal/repository/node.go @@ -2,6 +2,7 @@ // All rights reserved. This file is part of cc-backend. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. + package repository import ( @@ -49,12 +50,6 @@ func GetNodeRepository() *NodeRepository { return nodeRepoInstance } -var nodeColumns []string = []string{ - // "node.id," - "node.hostname", "node.cluster", "node.subcluster", - "node.node_state", "node.health_state", // "node.meta_data", -} - func (r *NodeRepository) FetchMetadata(node *schema.Node) (map[string]string, error) { start := time.Now() cachekey := fmt.Sprintf("metadata:%d", node.ID) @@ -121,24 +116,24 @@ func (r *NodeRepository) UpdateMetadata(node *schema.Node, key, val string) (err func (r *NodeRepository) GetNode(id int64, withMeta bool) (*schema.Node, error) { node := &schema.Node{} - if err := sq.Select("id", "hostname", "cluster", "subcluster", "node_state", - "health_state").From("node"). - Where("node.id = ?", id).RunWith(r.DB). - QueryRow().Scan(&node.ID, &node.Hostname, &node.Cluster, &node.SubCluster, &node.NodeState, - &node.HealthState); err != nil { - cclog.Warnf("Error while querying node '%v' from database", id) - return nil, err - } - - if withMeta { - var err error - var meta map[string]string - if meta, err = r.FetchMetadata(node); err != nil { - cclog.Warnf("Error while fetching metadata for node '%v'", id) - return nil, err - } - node.MetaData = meta - } + // if err := sq.Select("id", "hostname", "cluster", "subcluster", "node_state", + // "health_state").From("node"). + // Where("node.id = ?", id).RunWith(r.DB). + // QueryRow().Scan(&node.ID, &node.Hostname, &node.Cluster, &node.SubCluster, &node.NodeState, + // &node.HealthState); err != nil { + // cclog.Warnf("Error while querying node '%v' from database", id) + // return nil, err + // } + // + // if withMeta { + // var err error + // var meta map[string]string + // if meta, err = r.FetchMetadata(node); err != nil { + // cclog.Warnf("Error while fetching metadata for node '%v'", id) + // return nil, err + // } + // node.MetaData = meta + // } return node, nil } @@ -192,8 +187,9 @@ func (r *NodeRepository) InsertNodeState(nodeState *schema.Node) error { } const NamedNodeStateInsert string = ` -INSERT INTO node (hostname, cluster, subcluster) - VALUES (:hostname, :cluster, :subcluster);` +INSERT INTO node_state (time_stamp, node_state, health_state, cpus_allocated, + memory_allocated, gpus_allocated, jobs_running, node_id) + VALUES (:time_stamp, :node_state, :health_state, :cpus_allocated, :memory_allocated, :gpus_allocated, :jobs_running, :node_id);` func (r *NodeRepository) UpdateNodeState(hostname string, cluster string, nodeState *schema.NodeState) error { var id int64 @@ -208,8 +204,7 @@ func (r *NodeRepository) UpdateNodeState(hostname string, cluster string, nodeSt return err } node := schema.Node{ - Hostname: hostname, Cluster: cluster, SubCluster: subcluster, NodeState: *nodeState, - HealthState: schema.MonitoringStateFull, + Hostname: hostname, Cluster: cluster, SubCluster: subcluster, } id, err = r.AddNode(&node) if err != nil { @@ -225,11 +220,12 @@ func (r *NodeRepository) UpdateNodeState(hostname string, cluster string, nodeSt } } - if _, err := sq.Insert("node_state").Set("node_state", nodeState).Where("node.id = ?", id).RunWith(r.DB).Exec(); err != nil { - cclog.Errorf("error while updating node '%s'", hostname) + _, err := r.DB.NamedExec(NamedNodeStateInsert, nodeState) + if err != nil { + cclog.Errorf("Error while adding node state for '%v' to database", hostname) return err } - cclog.Infof("Updated node '%s' in database", hostname) + cclog.Infof("Updated node state for '%s' in database", hostname) return nil } @@ -252,13 +248,15 @@ func (r *NodeRepository) DeleteNode(id int64) error { return nil } -// TODO: Implement order by +// QueryNodes returns a list of nodes based on a node filter. It always operates +// on the last state (largest timestamp). func (r *NodeRepository) QueryNodes( ctx context.Context, filters []*model.NodeFilter, order *model.OrderByInput, // Currently unused! -) ([]*schema.Node, error) { - query, qerr := AccessCheck(ctx, sq.Select(nodeColumns...).From("node")) +) ([]*model.Node, error) { + query, qerr := AccessCheck(ctx, + sq.Select("node.hostname", "node.cluster", "node.subcluster", "node_state.node_state", "node_state.health_state", "MAX(node_state.time_stamp)").From("node_state").Join("node ON nodes_state.node_id = node.id").GroupBy("node_state.node_id")) if qerr != nil { return nil, qerr } @@ -288,9 +286,9 @@ func (r *NodeRepository) QueryNodes( return nil, err } - nodes := make([]*schema.Node, 0, 50) + nodes := make([]*model.Node, 0, 50) for rows.Next() { - node := schema.Node{} + node := model.Node{} if err := rows.Scan(&node.Hostname, &node.Cluster, &node.SubCluster, &node.NodeState, &node.HealthState); err != nil { @@ -304,128 +302,104 @@ func (r *NodeRepository) QueryNodes( return nodes, nil } -func (r *NodeRepository) ListNodes(cluster string) ([]*schema.Node, error) { - q := sq.Select("hostname", "cluster", "subcluster", "node_state", - "health_state").From("node").Where("node.cluster = ?", cluster).OrderBy("node.hostname ASC") - - rows, err := q.RunWith(r.DB).Query() - if err != nil { - cclog.Warn("Error while querying user list") - return nil, err - } - nodeList := make([]*schema.Node, 0, 100) - defer rows.Close() - for rows.Next() { - node := &schema.Node{} - if err := rows.Scan(&node.Hostname, &node.Cluster, - &node.SubCluster, &node.NodeState, &node.HealthState); err != nil { - cclog.Warn("Error while scanning node list") - return nil, err - } - - nodeList = append(nodeList, node) - } - - return nodeList, nil -} - -func (r *NodeRepository) CountNodeStates(ctx context.Context, filters []*model.NodeFilter) ([]*model.NodeStates, error) { - query, qerr := AccessCheck(ctx, sq.Select("node_state AS state", "count(*) AS count").From("node")) - if qerr != nil { - return nil, qerr - } - - for _, f := range filters { - if f.Hostname != nil { - query = buildStringCondition("node.hostname", f.Hostname, query) - } - if f.Cluster != nil { - query = buildStringCondition("node.cluster", f.Cluster, query) - } - if f.Subcluster != nil { - query = buildStringCondition("node.subcluster", f.Subcluster, query) - } - if f.NodeState != nil { - query = query.Where("node.node_state = ?", f.NodeState) - } - if f.HealthState != nil { - query = query.Where("node.health_state = ?", f.HealthState) - } - } - - // Add Group and Order - query = query.GroupBy("state").OrderBy("count DESC") - - rows, err := query.RunWith(r.stmtCache).Query() - if err != nil { - queryString, queryVars, _ := query.ToSql() - cclog.Errorf("Error while running query '%s' %v: %v", queryString, queryVars, err) - return nil, err - } - - nodes := make([]*model.NodeStates, 0) - for rows.Next() { - node := model.NodeStates{} - - if err := rows.Scan(&node.State, &node.Count); err != nil { - rows.Close() - cclog.Warn("Error while scanning rows (NodeStates)") - return nil, err - } - nodes = append(nodes, &node) - } - - return nodes, nil -} - -func (r *NodeRepository) CountHealthStates(ctx context.Context, filters []*model.NodeFilter) ([]*model.NodeStates, error) { - query, qerr := AccessCheck(ctx, sq.Select("health_state AS state", "count(*) AS count").From("node")) - if qerr != nil { - return nil, qerr - } - - for _, f := range filters { - if f.Hostname != nil { - query = buildStringCondition("node.hostname", f.Hostname, query) - } - if f.Cluster != nil { - query = buildStringCondition("node.cluster", f.Cluster, query) - } - if f.Subcluster != nil { - query = buildStringCondition("node.subcluster", f.Subcluster, query) - } - if f.NodeState != nil { - query = query.Where("node.node_state = ?", f.NodeState) - } - if f.HealthState != nil { - query = query.Where("node.health_state = ?", f.HealthState) - } - } - - // Add Group and Order - query = query.GroupBy("state").OrderBy("count DESC") - - rows, err := query.RunWith(r.stmtCache).Query() - if err != nil { - queryString, queryVars, _ := query.ToSql() - cclog.Errorf("Error while running query '%s' %v: %v", queryString, queryVars, err) - return nil, err - } - - nodes := make([]*model.NodeStates, 0) - for rows.Next() { - node := model.NodeStates{} - - if err := rows.Scan(&node.State, &node.Count); err != nil { - rows.Close() - cclog.Warn("Error while scanning rows (NodeStates)") - return nil, err - } - nodes = append(nodes, &node) - } - - return nodes, nil -} +// +// func (r *NodeRepository) CountNodeStates(ctx context.Context, filters []*model.NodeFilter) ([]*model.NodeStates, error) { +// query, qerr := AccessCheck(ctx, sq.Select("node_state AS state", "count(*) AS count").From("node")) +// if qerr != nil { +// return nil, qerr +// } +// +// for _, f := range filters { +// if f.Hostname != nil { +// query = buildStringCondition("node.hostname", f.Hostname, query) +// } +// if f.Cluster != nil { +// query = buildStringCondition("node.cluster", f.Cluster, query) +// } +// if f.Subcluster != nil { +// query = buildStringCondition("node.subcluster", f.Subcluster, query) +// } +// if f.NodeState != nil { +// query = query.Where("node.node_state = ?", f.NodeState) +// } +// if f.HealthState != nil { +// query = query.Where("node.health_state = ?", f.HealthState) +// } +// } +// +// // Add Group and Order +// query = query.GroupBy("state").OrderBy("count DESC") +// +// rows, err := query.RunWith(r.stmtCache).Query() +// if err != nil { +// queryString, queryVars, _ := query.ToSql() +// cclog.Errorf("Error while running query '%s' %v: %v", queryString, queryVars, err) +// return nil, err +// } +// +// nodes := make([]*model.NodeStates, 0) +// for rows.Next() { +// node := model.NodeStates{} +// +// if err := rows.Scan(&node.State, &node.Count); err != nil { +// rows.Close() +// cclog.Warn("Error while scanning rows (NodeStates)") +// return nil, err +// } +// nodes = append(nodes, &node) +// } +// +// return nodes, nil +// } +// +// func (r *NodeRepository) CountHealthStates(ctx context.Context, filters []*model.NodeFilter) ([]*model.NodeStates, error) { +// query, qerr := AccessCheck(ctx, sq.Select("health_state AS state", "count(*) AS count").From("node")) +// if qerr != nil { +// return nil, qerr +// } +// +// for _, f := range filters { +// if f.Hostname != nil { +// query = buildStringCondition("node.hostname", f.Hostname, query) +// } +// if f.Cluster != nil { +// query = buildStringCondition("node.cluster", f.Cluster, query) +// } +// if f.Subcluster != nil { +// query = buildStringCondition("node.subcluster", f.Subcluster, query) +// } +// if f.NodeState != nil { +// query = query.Where("node.node_state = ?", f.NodeState) +// } +// if f.HealthState != nil { +// query = query.Where("node.health_state = ?", f.HealthState) +// } +// } +// +// // Add Group and Order +// query = query.GroupBy("state").OrderBy("count DESC") +// +// rows, err := query.RunWith(r.stmtCache).Query() +// if err != nil { +// queryString, queryVars, _ := query.ToSql() +// cclog.Errorf("Error while running query '%s' %v: %v", queryString, queryVars, err) +// return nil, err +// } +// +// nodes := make([]*model.NodeStates, 0) +// for rows.Next() { +// node := model.NodeStates{} +// +// if err := rows.Scan(&node.State, &node.Count); err != nil { +// rows.Close() +// cclog.Warn("Error while scanning rows (NodeStates)") +// return nil, err +// } +// nodes = append(nodes, &node) +// } +// +// return nodes, nil +// } func AccessCheck(ctx context.Context, query sq.SelectBuilder) (sq.SelectBuilder, error) { user := GetUserFromContext(ctx)