Skip to content

Commit 61941d0

Browse files
committed
feat: add support for NVLink domain discovery in NetQ provider
Signed-off-by: Dmitry Shmulevich <[email protected]>
1 parent b9c8013 commit 61941d0

File tree

4 files changed

+141
-2
lines changed

4 files changed

+141
-2
lines changed

pkg/providers/netq/netq.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525
const (
2626
LoginURL = "auth/v1/login"
2727
OpIdURL = "auth/v1/select/opid"
28-
TopologyURL = "telemetry/v1/object/topologygraph/fetch-topology"
28+
TopologyURL = "api/netq/telemetry/v1/object/topologygraph/fetch-topology"
2929
)
3030

3131
type NetqResponse struct {

pkg/providers/netq/netq_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,9 @@ func TestGetURL(t *testing.T) {
163163
t.Run(tc.name, func(t *testing.T) {
164164
u, err := getURL(tc.baseURL, tc.query, tc.paths...)
165165
if len(tc.err) != 0 {
166+
require.NotNil(t, err)
166167
require.EqualError(t, err, tc.err)
167168
} else {
168-
169169
require.Nil(t, err)
170170
require.Equal(t, tc.url, u)
171171
}

pkg/providers/netq/nmx.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright 2025 NVIDIA CORPORATION
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package netq
7+
8+
import (
9+
"context"
10+
"encoding/base64"
11+
"encoding/json"
12+
"fmt"
13+
"net/http"
14+
15+
"k8s.io/klog/v2"
16+
17+
"github.com/NVIDIA/topograph/internal/httperr"
18+
"github.com/NVIDIA/topograph/internal/httpreq"
19+
"github.com/NVIDIA/topograph/pkg/topology"
20+
)
21+
22+
const (
23+
ComputeURL = "nmx/v1/compute-nodes"
24+
)
25+
26+
type ComputeNode struct {
27+
Id string `json:"ID"`
28+
Name string `json:"Name"`
29+
DomainUUID string `json:"DomainUUID"`
30+
}
31+
32+
func (p *Provider) getComputeNodes(ctx context.Context) (topology.DomainMap, *httperr.Error) {
33+
url, headers, httpErr := p.getComputeUrl()
34+
if httpErr != nil {
35+
return nil, httpErr
36+
}
37+
38+
klog.V(4).Infof("Fetching %s", url)
39+
f := getRequestFunc(ctx, "GET", url, headers, nil)
40+
resp, data, err := httpreq.DoRequest(f)
41+
if err != nil {
42+
return nil, httperr.NewError(resp.StatusCode, err.Error())
43+
}
44+
45+
return parseComputeNodes(data)
46+
}
47+
48+
func (p *Provider) getComputeUrl() (string, map[string]string, *httperr.Error) {
49+
auth := p.cred.user + ":" + p.cred.passwd
50+
authHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
51+
headers := map[string]string{"Authorization": authHeader}
52+
53+
url, err := getURL(p.params.ApiURL, nil, ComputeURL)
54+
return url, headers, err
55+
}
56+
57+
func parseComputeNodes(data []byte) (topology.DomainMap, *httperr.Error) {
58+
var computeNodes []ComputeNode
59+
err := json.Unmarshal(data, &computeNodes)
60+
if err != nil {
61+
return nil, httperr.NewError(http.StatusBadGateway, fmt.Sprintf("nmx output read failed: %v", err))
62+
}
63+
64+
domainMap := topology.NewDomainMap()
65+
for _, node := range computeNodes {
66+
domainMap.AddHost(node.DomainUUID, node.Name, node.Name)
67+
}
68+
69+
return domainMap, nil
70+
}

pkg/providers/netq/nmx_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2025 NVIDIA CORPORATION
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package netq
7+
8+
import (
9+
"os"
10+
"testing"
11+
12+
"github.com/NVIDIA/topograph/pkg/topology"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
func TestGetComputeUrl(t *testing.T) {
17+
p := &Provider{
18+
params: &ProviderParams{},
19+
cred: &Credentials{user: "user", passwd: "passwd"},
20+
}
21+
22+
testCases := []struct {
23+
name string
24+
serverURL string
25+
headers map[string]string
26+
computeUrl string
27+
err string
28+
}{
29+
{
30+
name: "Case 1: invalid URL",
31+
serverURL: `:///server`,
32+
err: `parse ":///server": missing protocol scheme`,
33+
},
34+
{
35+
name: "Case 2: valid input",
36+
serverURL: `https://server.com`,
37+
headers: map[string]string{"Authorization": "Basic dXNlcjpwYXNzd2Q="},
38+
computeUrl: `https://server.com/nmx/v1/compute-nodes`,
39+
},
40+
}
41+
42+
for _, tc := range testCases {
43+
t.Run(tc.name, func(t *testing.T) {
44+
p.params.ApiURL = tc.serverURL
45+
url, headers, err := p.getComputeUrl()
46+
if len(tc.err) != 0 {
47+
require.NotNil(t, err)
48+
require.EqualError(t, err, tc.err)
49+
} else {
50+
require.Nil(t, err)
51+
require.Equal(t, tc.computeUrl, url)
52+
require.Equal(t, tc.headers, headers)
53+
}
54+
})
55+
}
56+
}
57+
58+
func TestParseComputeNodes(t *testing.T) {
59+
data, err := os.ReadFile("../../../tests/output/netq/computeNodes.json")
60+
require.NoError(t, err)
61+
62+
domains, err := parseComputeNodes(data)
63+
require.Nil(t, err)
64+
65+
expected := topology.NewDomainMap()
66+
expected.AddHost("5e273675-31ef-4aec-a818-969207e0fdef", "", "")
67+
68+
require.Equal(t, expected, domains)
69+
}

0 commit comments

Comments
 (0)