Skip to content

Commit

Permalink
Add node Metadata fragment query to client (#283)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
rizzza authored Nov 29, 2023
1 parent 780f19f commit 3d091d9
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 10 deletions.
46 changes: 45 additions & 1 deletion pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func WithHTTPClient(cli *http.Client) Option {
}

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

// NodeMetadata return the metadata-api subgraph node for a load balancer.
// Once a load balancer is deleted, it is gone. There are no soft-deletes.
// However, it's metadata remains to query via the node-resolver metadata-api subgraph.
// TODO: Move this to a supergraph client
func (c Client) NodeMetadata(ctx context.Context, id string) (*Metadata, error) {
// query {
// node(id:"loadbal-example") {
// ... on MetadataNode {
// metadata {
// statuses {
// totalCount
// edges {
// node {
// data
// }
// }
// }
// }
// }
// }
// }
_, err := gidx.Parse(id)
if err != nil {
return nil, err
}

vars := map[string]interface{}{
"id": graphql.ID(id),
}

var q GetMetadataNode
if err := c.gqlCli.Query(ctx, &q, vars); err != nil {
return nil, translateGQLErr(err)
}

if q.MetadataNode.Metadata.ID == "" || q.MetadataNode.Metadata.Statuses.TotalCount == 0 {
return nil, ErrMetadataStatusNotFound
}

return &q.MetadataNode.Metadata, nil
}

func translateGQLErr(err error) error {
switch {
case strings.Contains(err.Error(), "load_balancer not found"):
Expand All @@ -72,6 +114,8 @@ func translateGQLErr(err error) error {
return ErrUnauthorized
case strings.Contains(err.Error(), "subject doesn't have access"):
return ErrPermissionDenied
case strings.Contains(err.Error(), "internal server error"):
return ErrInternalServerError
}

return err
Expand Down
58 changes: 58 additions & 0 deletions pkg/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,64 @@ func TestGetLoadBalancer(t *testing.T) {
})
}

func TestNodeMetadata(t *testing.T) {
cli := Client{}

t.Run("bad prefix", func(t *testing.T) {
md, err := cli.NodeMetadata(context.Background(), "badprefix-test")
require.Error(t, err)
require.Nil(t, md)
assert.ErrorContains(t, err, "invalid id")
})

t.Run("successful query", func(t *testing.T) {
respJSON := `{
"data": {
"node": {
"metadata": {
"id": "metadat-testing",
"nodeID": "loadbal-testing",
"statuses": {
"totalCount": 1,
"edges": [
{
"node": {
"source": "loadbalancer-api",
"statusNamespaceID": "metasns-testing",
"id": "metasts-testing",
"data": {
"status": "creating"
}
}
}
]
}
}
}
}
}`
cli.gqlCli = mustNewGQLTestClient(respJSON, http.StatusOK)
md, err := cli.NodeMetadata(context.Background(), "loadbal-testing")
require.NoError(t, err)
require.NotNil(t, md)
})

t.Run("metadata not found", func(t *testing.T) {
respJSON := `{
"data": {
"node": {
"metadata": null
}
}
}`
cli.gqlCli = mustNewGQLTestClient(respJSON, http.StatusOK)
md, err := cli.NodeMetadata(context.Background(), "loadbal-testing")
require.Error(t, err)
require.Nil(t, md)
assert.ErrorIs(t, err, ErrMetadataStatusNotFound)
})
}

func mustNewGQLTestClient(respJSON string, respCode int) *graphql.Client {
mux := http.NewServeMux()
mux.HandleFunc("/query", func(w http.ResponseWriter, req *http.Request) {
Expand Down
6 changes: 6 additions & 0 deletions pkg/client/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,10 @@ var (

// ErrHTTPError returned when the http response is an error
ErrHTTPError = errors.New("loadbalancer api http error")

// ErrInternalServerError returned when the server returns an internal server error
ErrInternalServerError = errors.New("internal server error")

// ErrMetadataStatusNotFound returned when the status data is invalid
ErrMetadataStatusNotFound = errors.New("metadata status not found")
)
18 changes: 17 additions & 1 deletion pkg/client/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ type MetadataStatusEdges struct {

// MetadataStatuses is a struct that represents the Metadata statuses GraphQL type
type MetadataStatuses struct {
Edges []MetadataStatusEdges `graphql:"edges" json:"edges"`
TotalCount int `graphql:"totalCount" json:"totalCount"`
Edges []MetadataStatusEdges `graphql:"edges" json:"edges"`
}

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

// MetadataNodeFragment is a struct that represents the MetadataNodeFragment GraphQL fragment
type MetadataNodeFragment struct {
Metadata Metadata `graphql:"metadata" json:"metadata"`
}

// MetadataNode is a struct that represents the MetadataNode GraphQL type
type MetadataNode struct {
MetadataNodeFragment `graphql:"... on MetadataNode"`
}

// GetMetadataNode is a struct that represents the node-resolver subgraph query
type GetMetadataNode struct {
MetadataNode MetadataNode `graphql:"node(id: $id)"`
}

// Readable version of the above:
// type GetLoadBalancer struct {
// LoadBalancer struct {
Expand Down
16 changes: 9 additions & 7 deletions pkg/metadata/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,17 @@ type LoadBalancerStatus struct {

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

if err := json.Unmarshal(s.Node.Data, status); err != nil {
return nil, fmt.Errorf("%w: %s", ErrInvalidStatusData, err)
}
if err := json.Unmarshal(s.Node.Data, status); err != nil {
return nil, fmt.Errorf("%w: %s", ErrInvalidStatusData, err)
}

return status, nil
return status, nil
}
}
}

Expand Down
6 changes: 5 additions & 1 deletion pkg/metadata/metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
func TestGetLoadbalancerStatus(t *testing.T) {
t.Run("valid status", func(t *testing.T) {
statuses := client.MetadataStatuses{
TotalCount: 2,
Edges: []client.MetadataStatusEdges{
{
Node: client.MetadataStatusNode{
Expand All @@ -36,6 +37,7 @@ func TestGetLoadbalancerStatus(t *testing.T) {

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

t.Run("status not found", func(t *testing.T) {
statuses := client.MetadataStatuses{
Edges: []client.MetadataStatusEdges{},
TotalCount: 0,
Edges: []client.MetadataStatusEdges{},
}

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

t.Run("no status data", func(t *testing.T) {
statuses := client.MetadataStatuses{
TotalCount: 1,
Edges: []client.MetadataStatusEdges{
{
Node: client.MetadataStatusNode{
Expand Down

0 comments on commit 3d091d9

Please sign in to comment.