Skip to content

Commit 44012e2

Browse files
kvapsclaude
andcommitted
feat(rest): honour ?nodes= and ?resources= filters on /v1/view/resources
linstor-csi polls /v1/view/resources with these query params during reconciliation; we previously returned the whole list and let the client filter. golinstor relies on Java LINSTOR's set-membership semantics — case-insensitive, comma-separated. Match that so ControllerPublishVolume's "is this resource on this node?" lookup sees an empty list when the answer is no, instead of "everything". Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
1 parent 4455dcd commit 44012e2

1 file changed

Lines changed: 52 additions & 3 deletions

File tree

pkg/rest/resources.go

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package rest
1818

1919
import (
2020
"net/http"
21+
"strings"
2122

2223
apiv1 "github.com/cozystack/blockstor/pkg/api/v1"
2324
)
@@ -36,13 +37,61 @@ func (s *Server) handleResourcesView(w http.ResponseWriter, r *http.Request) {
3637
return
3738
}
3839

39-
// Wrap each Resource in ResourceWithVolumes so the wire shape matches
40-
// upstream golinstor's expectation. Volumes will be populated once the
41-
// satellite reports them in Phase 3.
40+
// Optional filters golinstor sends: ?nodes=a,b&resources=rd1,rd2.
41+
// Java LINSTOR honours both as case-insensitive set-membership; we
42+
// match that so linstor-csi's "is this resource on this node?"
43+
// poll returns a non-empty list when the answer is yes.
44+
nodeFilter := splitCSV(r.URL.Query().Get("nodes"))
45+
rdFilter := splitCSV(r.URL.Query().Get("resources"))
46+
4247
out := make([]apiv1.ResourceWithVolumes, 0, len(resList))
48+
4349
for i := range resList {
50+
if !matchAnyFold(nodeFilter, resList[i].NodeName) {
51+
continue
52+
}
53+
54+
if !matchAnyFold(rdFilter, resList[i].Name) {
55+
continue
56+
}
57+
4458
out = append(out, apiv1.ResourceWithVolumes{Resource: resList[i]})
4559
}
4660

4761
writeJSON(w, http.StatusOK, out)
4862
}
63+
64+
// splitCSV parses the comma-separated query value, trimming whitespace
65+
// and dropping empty segments. Empty input means no filter.
66+
func splitCSV(value string) []string {
67+
if value == "" {
68+
return nil
69+
}
70+
71+
var out []string
72+
73+
for s := range strings.SplitSeq(value, ",") {
74+
s = strings.TrimSpace(s)
75+
if s != "" {
76+
out = append(out, s)
77+
}
78+
}
79+
80+
return out
81+
}
82+
83+
// matchAnyFold reports whether candidate matches any of needles
84+
// case-insensitively. Empty needles means "no filter — accept".
85+
func matchAnyFold(needles []string, candidate string) bool {
86+
if len(needles) == 0 {
87+
return true
88+
}
89+
90+
for _, n := range needles {
91+
if strings.EqualFold(n, candidate) {
92+
return true
93+
}
94+
}
95+
96+
return false
97+
}

0 commit comments

Comments
 (0)