Skip to content

Commit 3d091d9

Browse files
authored
Add node Metadata fragment query to client (#283)
* metadata node Signed-off-by: Matt Siwiec <[email protected]> * add client query for node metadata Signed-off-by: Matt Siwiec <[email protected]> --------- Signed-off-by: Matt Siwiec <[email protected]>
1 parent 780f19f commit 3d091d9

File tree

6 files changed

+140
-10
lines changed

6 files changed

+140
-10
lines changed

pkg/client/client.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func WithHTTPClient(cli *http.Client) Option {
4646
}
4747

4848
// GetLoadBalancer returns a load balancer by id
49-
func (c *Client) GetLoadBalancer(ctx context.Context, id string) (*LoadBalancer, error) {
49+
func (c Client) GetLoadBalancer(ctx context.Context, id string) (*LoadBalancer, error) {
5050
_, err := gidx.Parse(id)
5151
if err != nil {
5252
return nil, err
@@ -64,6 +64,48 @@ func (c *Client) GetLoadBalancer(ctx context.Context, id string) (*LoadBalancer,
6464
return &q.LoadBalancer, nil
6565
}
6666

67+
// NodeMetadata return the metadata-api subgraph node for a load balancer.
68+
// Once a load balancer is deleted, it is gone. There are no soft-deletes.
69+
// However, it's metadata remains to query via the node-resolver metadata-api subgraph.
70+
// TODO: Move this to a supergraph client
71+
func (c Client) NodeMetadata(ctx context.Context, id string) (*Metadata, error) {
72+
// query {
73+
// node(id:"loadbal-example") {
74+
// ... on MetadataNode {
75+
// metadata {
76+
// statuses {
77+
// totalCount
78+
// edges {
79+
// node {
80+
// data
81+
// }
82+
// }
83+
// }
84+
// }
85+
// }
86+
// }
87+
// }
88+
_, err := gidx.Parse(id)
89+
if err != nil {
90+
return nil, err
91+
}
92+
93+
vars := map[string]interface{}{
94+
"id": graphql.ID(id),
95+
}
96+
97+
var q GetMetadataNode
98+
if err := c.gqlCli.Query(ctx, &q, vars); err != nil {
99+
return nil, translateGQLErr(err)
100+
}
101+
102+
if q.MetadataNode.Metadata.ID == "" || q.MetadataNode.Metadata.Statuses.TotalCount == 0 {
103+
return nil, ErrMetadataStatusNotFound
104+
}
105+
106+
return &q.MetadataNode.Metadata, nil
107+
}
108+
67109
func translateGQLErr(err error) error {
68110
switch {
69111
case strings.Contains(err.Error(), "load_balancer not found"):
@@ -72,6 +114,8 @@ func translateGQLErr(err error) error {
72114
return ErrUnauthorized
73115
case strings.Contains(err.Error(), "subject doesn't have access"):
74116
return ErrPermissionDenied
117+
case strings.Contains(err.Error(), "internal server error"):
118+
return ErrInternalServerError
75119
}
76120

77121
return err

pkg/client/client_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,64 @@ func TestGetLoadBalancer(t *testing.T) {
217217
})
218218
}
219219

220+
func TestNodeMetadata(t *testing.T) {
221+
cli := Client{}
222+
223+
t.Run("bad prefix", func(t *testing.T) {
224+
md, err := cli.NodeMetadata(context.Background(), "badprefix-test")
225+
require.Error(t, err)
226+
require.Nil(t, md)
227+
assert.ErrorContains(t, err, "invalid id")
228+
})
229+
230+
t.Run("successful query", func(t *testing.T) {
231+
respJSON := `{
232+
"data": {
233+
"node": {
234+
"metadata": {
235+
"id": "metadat-testing",
236+
"nodeID": "loadbal-testing",
237+
"statuses": {
238+
"totalCount": 1,
239+
"edges": [
240+
{
241+
"node": {
242+
"source": "loadbalancer-api",
243+
"statusNamespaceID": "metasns-testing",
244+
"id": "metasts-testing",
245+
"data": {
246+
"status": "creating"
247+
}
248+
}
249+
}
250+
]
251+
}
252+
}
253+
}
254+
}
255+
}`
256+
cli.gqlCli = mustNewGQLTestClient(respJSON, http.StatusOK)
257+
md, err := cli.NodeMetadata(context.Background(), "loadbal-testing")
258+
require.NoError(t, err)
259+
require.NotNil(t, md)
260+
})
261+
262+
t.Run("metadata not found", func(t *testing.T) {
263+
respJSON := `{
264+
"data": {
265+
"node": {
266+
"metadata": null
267+
}
268+
}
269+
}`
270+
cli.gqlCli = mustNewGQLTestClient(respJSON, http.StatusOK)
271+
md, err := cli.NodeMetadata(context.Background(), "loadbal-testing")
272+
require.Error(t, err)
273+
require.Nil(t, md)
274+
assert.ErrorIs(t, err, ErrMetadataStatusNotFound)
275+
})
276+
}
277+
220278
func mustNewGQLTestClient(respJSON string, respCode int) *graphql.Client {
221279
mux := http.NewServeMux()
222280
mux.HandleFunc("/query", func(w http.ResponseWriter, req *http.Request) {

pkg/client/errors.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,10 @@ var (
1616

1717
// ErrHTTPError returned when the http response is an error
1818
ErrHTTPError = errors.New("loadbalancer api http error")
19+
20+
// ErrInternalServerError returned when the server returns an internal server error
21+
ErrInternalServerError = errors.New("internal server error")
22+
23+
// ErrMetadataStatusNotFound returned when the status data is invalid
24+
ErrMetadataStatusNotFound = errors.New("metadata status not found")
1925
)

pkg/client/types.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ type MetadataStatusEdges struct {
9696

9797
// MetadataStatuses is a struct that represents the Metadata statuses GraphQL type
9898
type MetadataStatuses struct {
99-
Edges []MetadataStatusEdges `graphql:"edges" json:"edges"`
99+
TotalCount int `graphql:"totalCount" json:"totalCount"`
100+
Edges []MetadataStatusEdges `graphql:"edges" json:"edges"`
100101
}
101102

102103
// Metadata is a struct that represents the metadata GraphQL type
@@ -106,6 +107,21 @@ type Metadata struct {
106107
Statuses MetadataStatuses `graphql:"statuses" json:"statuses"`
107108
}
108109

110+
// MetadataNodeFragment is a struct that represents the MetadataNodeFragment GraphQL fragment
111+
type MetadataNodeFragment struct {
112+
Metadata Metadata `graphql:"metadata" json:"metadata"`
113+
}
114+
115+
// MetadataNode is a struct that represents the MetadataNode GraphQL type
116+
type MetadataNode struct {
117+
MetadataNodeFragment `graphql:"... on MetadataNode"`
118+
}
119+
120+
// GetMetadataNode is a struct that represents the node-resolver subgraph query
121+
type GetMetadataNode struct {
122+
MetadataNode MetadataNode `graphql:"node(id: $id)"`
123+
}
124+
109125
// Readable version of the above:
110126
// type GetLoadBalancer struct {
111127
// LoadBalancer struct {

pkg/metadata/metadata.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,17 @@ type LoadBalancerStatus struct {
2828

2929
// GetLoadbalancerStatus returns the status of a load balancer
3030
func GetLoadbalancerStatus(metadataStatuses client.MetadataStatuses, statusNamespaceID gidx.PrefixedID) (*LoadBalancerStatus, error) {
31-
for _, s := range metadataStatuses.Edges {
32-
if s.Node.StatusNamespaceID == statusNamespaceID.String() {
33-
status := &LoadBalancerStatus{}
31+
if metadataStatuses.TotalCount > 0 {
32+
for _, s := range metadataStatuses.Edges {
33+
if s.Node.StatusNamespaceID == statusNamespaceID.String() {
34+
status := &LoadBalancerStatus{}
3435

35-
if err := json.Unmarshal(s.Node.Data, status); err != nil {
36-
return nil, fmt.Errorf("%w: %s", ErrInvalidStatusData, err)
37-
}
36+
if err := json.Unmarshal(s.Node.Data, status); err != nil {
37+
return nil, fmt.Errorf("%w: %s", ErrInvalidStatusData, err)
38+
}
3839

39-
return status, nil
40+
return status, nil
41+
}
4042
}
4143
}
4244

pkg/metadata/metadata_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
func TestGetLoadbalancerStatus(t *testing.T) {
1414
t.Run("valid status", func(t *testing.T) {
1515
statuses := client.MetadataStatuses{
16+
TotalCount: 2,
1617
Edges: []client.MetadataStatusEdges{
1718
{
1819
Node: client.MetadataStatusNode{
@@ -36,6 +37,7 @@ func TestGetLoadbalancerStatus(t *testing.T) {
3637

3738
t.Run("bad json data", func(t *testing.T) {
3839
statuses := client.MetadataStatuses{
40+
TotalCount: 1,
3941
Edges: []client.MetadataStatusEdges{
4042
{
4143
Node: client.MetadataStatusNode{
@@ -54,7 +56,8 @@ func TestGetLoadbalancerStatus(t *testing.T) {
5456

5557
t.Run("status not found", func(t *testing.T) {
5658
statuses := client.MetadataStatuses{
57-
Edges: []client.MetadataStatusEdges{},
59+
TotalCount: 0,
60+
Edges: []client.MetadataStatusEdges{},
5861
}
5962

6063
status, err := GetLoadbalancerStatus(statuses, "metasns-loadbalancer-status")
@@ -65,6 +68,7 @@ func TestGetLoadbalancerStatus(t *testing.T) {
6568

6669
t.Run("no status data", func(t *testing.T) {
6770
statuses := client.MetadataStatuses{
71+
TotalCount: 1,
6872
Edges: []client.MetadataStatusEdges{
6973
{
7074
Node: client.MetadataStatusNode{

0 commit comments

Comments
 (0)