Skip to content

Commit da29f7f

Browse files
committed
Find and Invoke Slurm APIs using reflect to avoid having version specific code
1 parent fdcde52 commit da29f7f

File tree

2 files changed

+143
-75
lines changed

2 files changed

+143
-75
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package slurm
2+
3+
// Method names used in the Slurm API
4+
const (
5+
methodPostNode = "PostNode"
6+
methodPing = "Ping"
7+
)
8+
9+
// withExecuteSuffix appends "Execute" to the given method name.
10+
// For example, "PostNode" becomes "PostNodeExecute".
11+
func withExecuteSuffix(method string) string {
12+
return method + "Execute"
13+
}

redfish-exporter/slurm/slurm.go

Lines changed: 130 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import (
66
"log"
77
"math"
88
"net/http"
9+
"reflect"
10+
"regexp"
11+
"strings"
912
"time"
1013

1114
"github.com/nod-ai/ADA/redfish-exporter/api/generated/slurmrestdapi"
@@ -50,11 +53,13 @@ type SlurmServerConfig struct {
5053

5154
type Client struct {
5255
apiClient *slurmrestdapi.APIClient // slurm URL to client mapping
56+
helper map[string]reflect.Method
5357
}
5458

55-
var apiCl *Client // singleton client
59+
var singletonAPICl *Client // singleton client
5660

5761
func NewClient(slurmControlNode, slurmUser, slurmToken string) (*Client, error) {
62+
c := &Client{}
5863
slConfig := &SlurmServerConfig{
5964
URL: slurmControlNode,
6065
Username: defaultSlurmUsername,
@@ -64,125 +69,175 @@ func NewClient(slurmControlNode, slurmUser, slurmToken string) (*Client, error)
6469
slConfig.Username = slurmUser
6570
}
6671
cl := createRestClient(slConfig)
67-
c := &Client{apiClient: cl}
72+
73+
// populate the methods required for ping and node update operations
74+
t := reflect.TypeOf(cl.SlurmAPI)
75+
postNodeRe := regexp.MustCompile(fmt.Sprintf(`%s$`, methodPostNode))
76+
pingRe := regexp.MustCompile(fmt.Sprintf(`%s$`, methodPing))
77+
for i := 0; i < t.NumMethod(); i++ {
78+
method := t.Method(i)
79+
if postNodeRe.MatchString(method.Name) {
80+
postNodeExecuteMethod, found := t.MethodByName(withExecuteSuffix(method.Name))
81+
if !found {
82+
return nil, fmt.Errorf("could not find PostNodeExecute method from Slurm REST APIs")
83+
}
84+
85+
if _, found := c.helper[methodPostNode]; !found {
86+
c.helper[methodPostNode] = method
87+
c.helper[withExecuteSuffix(methodPostNode)] = postNodeExecuteMethod
88+
}
89+
} else if pingRe.MatchString(method.Name) {
90+
pingExecuteMethod, found := t.MethodByName(withExecuteSuffix(method.Name))
91+
if !found {
92+
return nil, fmt.Errorf("could not find PingExecute method from Slurm REST APIs")
93+
}
94+
95+
if _, found := c.helper[methodPing]; !found {
96+
c.helper[methodPing] = method
97+
c.helper[withExecuteSuffix(methodPing)] = pingExecuteMethod
98+
}
99+
}
100+
}
101+
102+
c.apiClient = cl
68103

69104
log.Printf("[slurm] created slurm client for node: %v\n", slurmControlNode)
70105
err := c.getConnectionStatus()
71106
if err != nil {
72107
log.Printf("[slurm] error in getting the connection status of the slurm node: %v, err: %+v\n", slurmControlNode, err)
73108
}
74109

75-
apiCl = c
110+
singletonAPICl = c
76111
return c, err
77112
}
78113

79114
func GetClient() *Client {
80-
return apiCl
115+
return singletonAPICl
81116
}
82117

83118
func (c *Client) ResumeNode(nodeName string) error {
84-
apiCall := func() (interface{}, *http.Response, error) {
85-
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
86-
jreq := c.apiClient.SlurmAPI.SlurmV0040PostNode(ctx, nodeName)
87-
req := slurmrestdapi.V0040UpdateNodeMsg{State: []string{"resume"}}
88-
jreq = jreq.V0040UpdateNodeMsg(req)
89-
res, resp, err := c.apiClient.SlurmAPI.SlurmV0040PostNodeExecute(jreq)
90-
cancel()
91-
if err != nil {
92-
return res, resp, err
93-
} else if resp.StatusCode != 200 {
94-
return res, resp, fmt.Errorf("invalid status code: %v", resp.StatusCode)
95-
}
96-
return res, resp, nil
97-
}
98-
99-
_, resp, err := CallWithRetry(apiCall, maxRetries, baseDelay)
100-
if err != nil {
101-
return err
102-
}
103-
defer resp.Body.Close()
104-
105-
return nil
119+
return c.updateNodeState(nodeName, "resume")
106120
}
107121

108122
func (c *Client) DrainNode(nodeName string) error {
123+
return c.updateNodeState(nodeName, "drain")
124+
}
125+
126+
func (c *Client) getConnectionStatus() error {
109127
apiCall := func() (interface{}, *http.Response, error) {
110128
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
111-
jreq := c.apiClient.SlurmAPI.SlurmV0040PostNode(ctx, nodeName)
112-
req := slurmrestdapi.V0040UpdateNodeMsg{State: []string{"drain"}}
113-
jreq = jreq.V0040UpdateNodeMsg(req)
114-
res, resp, err := c.apiClient.SlurmAPI.SlurmV0040PostNodeExecute(jreq)
115-
cancel()
116-
if err != nil {
117-
return res, resp, err
118-
} else if resp.StatusCode != 200 {
119-
return res, resp, fmt.Errorf("invalid status code: %v", resp.StatusCode)
129+
defer cancel()
130+
131+
// Step 1: Call the Ping method using reflection
132+
pingVals := c.helper["Ping"].Func.Call([]reflect.Value{
133+
reflect.ValueOf(c.apiClient.SlurmAPI),
134+
reflect.ValueOf(ctx),
135+
})
136+
137+
// Check if the call produced results
138+
if len(pingVals) == 0 {
139+
return nil, nil, fmt.Errorf("Ping call returned no values")
120140
}
121-
return res, resp, nil
141+
142+
// Step 2: Execute the Ping method with the request
143+
pingResp := c.helper["PingExecute"].Func.Call([]reflect.Value{
144+
reflect.ValueOf(c.apiClient.SlurmAPI),
145+
pingVals[0],
146+
})
147+
148+
// Extract and return the response and error
149+
resp, _ := pingResp[1].Interface().(*http.Response)
150+
err, _ := pingResp[2].Interface().(error)
151+
return pingResp[0].Interface(), resp, err
122152
}
123153

124154
_, resp, err := CallWithRetry(apiCall, maxRetries, baseDelay)
125155
if err != nil {
126-
return err
156+
return nil
127157
}
128158
defer resp.Body.Close()
129159

160+
log.Printf("[slurm] ping success: %v\n", resp.StatusCode)
130161
return nil
131162
}
132163

133-
func (c *Client) GetNodes() ([]string, error) {
134-
var nodes []string
164+
func (c *Client) updateNodeState(nodeName, state string) error {
135165
apiCall := func() (interface{}, *http.Response, error) {
136166
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
137-
jreq := c.apiClient.SlurmAPI.SlurmV0039GetNodes(ctx)
138-
res, resp, err := c.apiClient.SlurmAPI.SlurmV0039GetNodesExecute(jreq)
139-
cancel()
140-
if err != nil {
141-
return res, resp, err
142-
} else if resp.StatusCode != 200 {
143-
return res, resp, fmt.Errorf("invalid status code: %v", resp.StatusCode)
167+
defer cancel()
168+
169+
// Step 1: Call the PostNode method using reflection
170+
postNodeVals := c.helper["PostNode"].Func.Call([]reflect.Value{
171+
reflect.ValueOf(c.apiClient.SlurmAPI),
172+
reflect.ValueOf(ctx),
173+
reflect.ValueOf(nodeName),
174+
})
175+
176+
// Check if the call produced results
177+
if len(postNodeVals) == 0 {
178+
return nil, nil, fmt.Errorf("PostNode call returned no values")
144179
}
145-
return res, resp, nil
146-
}
147-
148-
res, resp, err := CallWithRetry(apiCall, maxRetries, baseDelay)
149-
if err != nil {
150-
return nodes, err
151-
}
152-
defer resp.Body.Close()
153-
154-
log.Printf("[slurm] get nodes: %+v\n", nodes)
155-
temp := res.(*slurmrestdapi.V0039NodesResponse)
156-
for _, node := range temp.GetNodes() {
157-
nodes = append(nodes, *node.Name)
158-
}
159-
return nodes, nil
160-
}
161180

162-
func (c *Client) getConnectionStatus() error {
163-
apiCall := func() (interface{}, *http.Response, error) {
164-
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
165-
jreq := c.apiClient.SlurmAPI.SlurmV0039Ping(ctx)
166-
res, resp, err := c.apiClient.SlurmAPI.SlurmV0039PingExecute(jreq)
167-
cancel()
168-
if err != nil {
169-
return res, resp, err
170-
} else if resp.StatusCode != 200 {
171-
return res, resp, fmt.Errorf("invalid status code: %v", resp.StatusCode)
181+
// Step 2: Find and call the UpdateNodeMsg method on the request object
182+
newInstance := reflect.New(postNodeVals[0].Type()).Elem()
183+
instanceType := newInstance.Type()
184+
185+
for i := 0; i < instanceType.NumMethod(); i++ {
186+
method := instanceType.Method(i)
187+
if strings.Contains(method.Name, "UpdateNodeMsg") {
188+
// Create a new UpdateNodeMsg request
189+
updateNodeMsgReq := createUpdateNodeMsgRequest(method.Type)
190+
if updateNodeMsgReq.IsValid() {
191+
updateNodeMsgReq.FieldByName("State").Set(reflect.ValueOf([]string{state}))
192+
}
193+
194+
// Step 3: Call UpdateNodeMsg with the request
195+
updatedNodeVals := method.Func.Call([]reflect.Value{postNodeVals[0], updateNodeMsgReq})
196+
if len(updatedNodeVals) == 0 {
197+
return nil, nil, fmt.Errorf("UpdateNodeMsg call returned no values")
198+
}
199+
200+
// Step 4: Execute the PostNode method with the updated request
201+
postNodeResp := c.helper["PostNodeExecute"].Func.Call([]reflect.Value{
202+
reflect.ValueOf(c.apiClient.SlurmAPI),
203+
updatedNodeVals[0],
204+
})
205+
206+
if len(postNodeResp) < 3 {
207+
return nil, nil, fmt.Errorf("PostNodeExecute call returned insufficient values")
208+
}
209+
210+
// Extract and return the response and error
211+
resp, _ := postNodeResp[1].Interface().(*http.Response)
212+
err, _ := postNodeResp[2].Interface().(error)
213+
return postNodeResp[0].Interface(), resp, err
214+
}
172215
}
173-
return res, resp, nil
216+
217+
return nil, nil, fmt.Errorf("no suitable UpdateNodeMsg method found")
174218
}
175219

220+
// Retry the API call
176221
_, resp, err := CallWithRetry(apiCall, maxRetries, baseDelay)
177222
if err != nil {
178-
return nil
223+
return err
179224
}
180225
defer resp.Body.Close()
181226

182-
log.Printf("[slurm] ping success: %v\n", resp.StatusCode)
183227
return nil
184228
}
185229

230+
// Helper function to create an UpdateNodeMsg request using reflection
231+
func createUpdateNodeMsgRequest(methodType reflect.Type) reflect.Value {
232+
for j := 1; j < methodType.NumIn(); j++ { // Start from 1 to skip the receiver
233+
paramType := methodType.In(j)
234+
if strings.Contains(paramType.Name(), "UpdateNodeMsg") {
235+
return reflect.New(paramType).Elem()
236+
}
237+
}
238+
return reflect.Value{}
239+
}
240+
186241
func createRestClient(c *SlurmServerConfig) *slurmrestdapi.APIClient {
187242
cfg := slurmrestdapi.NewConfiguration()
188243
cfg.HTTPClient = &http.Client{Timeout: slurmRestClientTimeout}

0 commit comments

Comments
 (0)