diff --git a/CHANGELOG.md b/CHANGELOG.md index e505609..e65432f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ Lists the changes in the gocmlclient package. +## Version 0.0.22 + +- added HideLinks node property +- added another HTTP request error condition +- ran gofumpt over the code base + ## Version 0.0.20 and 0.0.21 - added more cases where an error results in ErrSystemNotReady diff --git a/apiclient.go b/apiclient.go index 7a45f41..3d335ca 100644 --- a/apiclient.go +++ b/apiclient.go @@ -54,7 +54,6 @@ func (c *Client) apiRequest(ctx context.Context, method string, path string, dat } func (c *Client) doAPI(ctx context.Context, req *http.Request, depth int32) ([]byte, error) { - if c.state.get() == stateInitial { c.state.set(stateCheckVersion) c.compatibilityErr = c.versionCheck(ctx, depth) @@ -79,7 +78,7 @@ retry: res, err := c.httpClient.Do(req) if err != nil { if urlError, ok := (err).(*url.Error); ok { - if urlError.Timeout() { + if urlError.Timeout() || urlError.Temporary() { return nil, ErrSystemNotReady } } @@ -166,7 +165,6 @@ func (c *Client) jsonDelete(ctx context.Context, api string, depth int32) error } func (c *Client) jsonReq(ctx context.Context, method, api string, data io.Reader, result any, depth int32) error { - // during initialization, the API client does API calls recursively which // leads to all sorts of nasty race problems. The below basically prevents // any additional API calls when recursion happens during initialization or diff --git a/apiclient_test.go b/apiclient_test.go index d065e4c..a516d32 100644 --- a/apiclient_test.go +++ b/apiclient_test.go @@ -49,7 +49,6 @@ func TestClient_compatibility(t *testing.T) { } func TestClient_putpatch(t *testing.T) { - putResponse := mr.MockRespList{ mr.MockResp{Code: 204}, } diff --git a/auth_test.go b/auth_test.go index 703a8c9..dfbc4c9 100644 --- a/auth_test.go +++ b/auth_test.go @@ -12,7 +12,6 @@ import ( ) func TestClient_authenticate(t *testing.T) { - tc := newTestAPIclient() tc.client.state.set(stateAuthRequired) tc.client.userpass = userPass{"qwe", "qwe"} @@ -76,7 +75,6 @@ func TestClient_authenticate(t *testing.T) { } func TestClient_token_auth(t *testing.T) { - tc := newAuthedTestAPIclient() tc.client.apiToken = "sometoken" tc.client.userpass = userPass{} @@ -183,7 +181,6 @@ func TestClient_SetCACert(t *testing.T) { } func TestClient_complete(t *testing.T) { - tc := newTestAPIclient() tc.mr.SetData(mr.MockRespList{ @@ -232,7 +229,6 @@ func TestClient_complete(t *testing.T) { } func TestClient_Race(t *testing.T) { - // GOFLAGS="-count=1000" time go test -race -parallel 2 . -run 'Race$' t.Parallel() diff --git a/cmd/ctest/main.go b/cmd/ctest/main.go index 09543a5..271ea3a 100644 --- a/cmd/ctest/main.go +++ b/cmd/ctest/main.go @@ -10,7 +10,6 @@ import ( ) func main() { - // address and lab id host, found := os.LookupEnv("CML_HOST") if !found { diff --git a/extconn.go b/extconn.go new file mode 100644 index 0000000..bd88e7c --- /dev/null +++ b/extconn.go @@ -0,0 +1,80 @@ +package cmlclient + +import ( + "context" + "fmt" +) + +/* +[ + { + "id": "58568fbb-e1f8-4b83-a1f8-148c656eed39", + "device_name": "virbr0", + "label": "NAT", + "protected": false, + "snooped": true, + "tags": [ + "NAT" + ], + "operational": { + "forwarding": "NAT", + "label": "NAT", + "mtu": 1500, + "exists": true, + "enabled": true, + "protected": false, + "snooped": true, + "stp": false, + "interface": null, + "ip_networks": [] + } + } +] +*/ + +// Operational data struct +type opdata struct { + Forwarding string `json:"forwarding"` + Label string `json:"label"` + MTU int `json:"mtu"` + Exists bool `json:"exits"` + Enabled bool `json:"enabled"` + Protected bool `json:"protected"` + Snooped bool `json:"snooped"` + STP bool `json:"stp"` + // Interface string `json:"interface"` + IPNetworks []string `json:"ip_networks"` +} + +// ExtConn defines the data structure for a CML external connector +type ExtConn struct { + ID string `json:"id"` + DeviceName string `json:"device_name"` + Label string `json:"label"` + Protected bool `json:"protected"` + Snooped bool `json:"snooped"` + Tags []string `json:"tags"` + Operational opdata `json:"operational"` +} + +// ExtConnGet returns the external connector specified by the ID given +func (c *Client) ExtConnGet(ctx context.Context, extConnID string) (*ExtConn, error) { + api := fmt.Sprintf("system/external_connectors/%s", extConnID) + extconn := &ExtConn{} + err := c.jsonGet(ctx, api, extconn, 0) + if err != nil { + return nil, err + } + return extconn, err +} + +// ExtConnectors returns all external connectors on the system +func (c *Client) ExtConnectors(ctx context.Context) ([]*ExtConn, error) { + api := fmt.Sprintf("system/external_connectors") + extconnlist := make([]*ExtConn, 0) + err := c.jsonGet(ctx, api, &extconnlist, 0) + if err != nil { + return nil, err + } + return extconnlist, err +} diff --git a/extconn_test.go b/extconn_test.go new file mode 100644 index 0000000..6e86cfb --- /dev/null +++ b/extconn_test.go @@ -0,0 +1,71 @@ +package cmlclient + +import ( + "testing" + + mr "github.com/rschmied/mockresponder" + "github.com/stretchr/testify/assert" +) + +func TestClient_ExtConns(t *testing.T) { + tc := newAuthedTestAPIclient() + + extConn := []byte(` + { + "id": "58568fbb-e1f8-4b83-a1f8-148c656eed39", + "device_name": "virbr0", + "label": "NAT", + "protected": false, + "snooped": true, + "tags": [ + "NAT" + ], + "operational": { + "forwarding": "NAT", + "label": "NAT", + "mtu": 1500, + "exists": true, + "enabled": true, + "protected": false, + "snooped": true, + "stp": false, + "interface": null, + "ip_networks": [] + } + } + `) + extConns := append([]byte(`[`), extConn...) + extConns = append(extConns, []byte(`]`)...) + + tests := []struct { + name string + data mr.MockRespList + wantErr bool + }{ + {"good", mr.MockRespList{mr.MockResp{Data: extConns}}, false}, + {"badjson", mr.MockRespList{mr.MockResp{Data: []byte(`///`)}}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tc.mr.SetData(tt.data) + got, err := tc.client.ExtConnectors(tc.ctx) + if err != nil { + if !tt.wantErr { + t.Error("unexpected error!", err) + } + return + } + assert.NoError(t, err) + assert.Len(t, got, 1) + assert.True(t, got[0].Label == "NAT") + }) + } + + t.Run("single", func(t *testing.T) { + tc.mr.SetData(mr.MockRespList{mr.MockResp{Data: extConn}}) + got, err := tc.client.ExtConnGet(tc.ctx, "58568fbb-e1f8-4b83-a1f8-148c656eed39") + assert.NoError(t, err) + assert.Equal(t, got.ID, "58568fbb-e1f8-4b83-a1f8-148c656eed39") + assert.True(t, got.Label == "NAT") + }) +} diff --git a/go.mod b/go.mod index 1d8097c..e6c9684 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Masterminds/semver/v3 v3.2.1 github.com/rschmied/mockresponder v1.0.4 github.com/stretchr/testify v1.8.2 - golang.org/x/sync v0.1.0 + golang.org/x/sync v0.5.0 ) require ( diff --git a/go.sum b/go.sum index 67299cd..2ddc45d 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/groups_test.go b/groups_test.go index 99c2d80..1946216 100644 --- a/groups_test.go +++ b/groups_test.go @@ -49,7 +49,6 @@ var ( ) func TestClient_GetGroups(t *testing.T) { - tc := newAuthedTestAPIclient() tests := []struct { @@ -109,7 +108,6 @@ func TestClient_GetGroups(t *testing.T) { } func TestClient_GetGroupByName(t *testing.T) { - tc := newAuthedTestAPIclient() tests := []struct { @@ -167,7 +165,6 @@ func TestClient_GetGroupByName(t *testing.T) { } func TestClient_GetGroup(t *testing.T) { - tc := newAuthedTestAPIclient() // ID of group1 from above @@ -243,7 +240,6 @@ func TestClient_GroupDestroy(t *testing.T) { } func TestClient_GroupCreate(t *testing.T) { - tc := newAuthedTestAPIclient() newGroup := Group{Name: "doesntmatter"} @@ -299,7 +295,6 @@ func TestClient_GroupCreate(t *testing.T) { } func TestClient_GroupUpdate(t *testing.T) { - tc := newAuthedTestAPIclient() newGroup := Group{Name: "doesntmatter"} diff --git a/iface.go b/iface.go index 4d09ab9..4b8074b 100644 --- a/iface.go +++ b/iface.go @@ -224,7 +224,6 @@ func (c *Client) getInterfacesForNode(ctx context.Context, node *Node) error { // InterfaceGet returns the interface identified by its `ID` (iface.ID). func (c *Client) InterfaceGet(ctx context.Context, iface *Interface) (*Interface, error) { - if iface, ok := c.getCachedIface(iface); ok { return iface, nil } @@ -238,7 +237,6 @@ func (c *Client) InterfaceGet(ctx context.Context, iface *Interface) (*Interface // is >= 0, the request creates all unallocated slots up to and including // that slot. Conversely, if the slot is < 0 (e.g. -1), the next free slot is used. func (c *Client) InterfaceCreate(ctx context.Context, labID, nodeID string, slot int) (*Interface, error) { - var slotPtr *int if slot >= 0 { diff --git a/iface_test.go b/iface_test.go index 7d1536e..b70d82c 100644 --- a/iface_test.go +++ b/iface_test.go @@ -158,7 +158,6 @@ func TestClient_IfaceDelete(t *testing.T) { } func Test_Race(t *testing.T) { - tc := newAuthedTestAPIclient() tc.client.useCache = true diff --git a/imagedef_test.go b/imagedef_test.go index 90199c4..1313141 100644 --- a/imagedef_test.go +++ b/imagedef_test.go @@ -269,7 +269,6 @@ import ( // ] func TestClient_GetImageDefs(t *testing.T) { - tc := newAuthedTestAPIclient() aimgdef := `{ diff --git a/lab.go b/lab.go index 1aed008..42f7b73 100644 --- a/lab.go +++ b/lab.go @@ -45,12 +45,14 @@ type LabGroup struct { Permission string `json:"permission"` } -type IDlist []string -type NodeMap map[string]*Node -type InterfaceList []*Interface -type nodeList []*Node -type linkList []*Link -type LabGroupList []*LabGroup +type ( + IDlist []string + NodeMap map[string]*Node + InterfaceList []*Interface + nodeList []*Node + linkList []*Link + LabGroupList []*LabGroup +) type labAlias struct { Lab @@ -185,7 +187,6 @@ func (c *Client) deleteCachedLab(id string, err error) error { // LabCreate creates a new lab on the controller. func (c *Client) LabCreate(ctx context.Context, lab Lab) (*Lab, error) { - // TODO: inconsistent attributes lab_title vs title, ... postAlias := labPatchPostAlias{ Title: lab.Title, @@ -212,7 +213,6 @@ func (c *Client) LabCreate(ctx context.Context, lab Lab) (*Lab, error) { // LabUpdate updates specific fields of a lab (title, description and notes). func (c *Client) LabUpdate(ctx context.Context, lab Lab) (*Lab, error) { - // TODO: inconsistent attributes lab_title vs title, ... patchAlias := labPatchPostAlias{ Title: lab.Title, @@ -290,7 +290,6 @@ func (c *Client) LabDestroy(ctx context.Context, id string) error { // LabGetByTitle returns the lab identified by its `title`. For the use of // `deep` see LabGet(). func (c *Client) LabGetByTitle(ctx context.Context, title string, deep bool) (*Lab, error) { - var data map[string]map[string]*labAlias err := c.jsonGet(ctx, "populate_lab_tiles", &data, 0) @@ -315,7 +314,6 @@ func (c *Client) LabGetByTitle(ctx context.Context, title string, deep bool) (*L // then the nodes, their interfaces and links are also fetched from the controller. // Also, with `deep`, the L3 IP address info is fetched for the given lab. func (c *Client) LabGet(ctx context.Context, id string, deep bool) (*Lab, error) { - if lab, ok := c.getCachedLab(id, deep); ok { return lab, nil } @@ -333,7 +331,6 @@ func (c *Client) LabGet(ctx context.Context, id string, deep bool) (*Lab, error) } func (c *Client) labFill(ctx context.Context, la *labAlias) (*Lab, error) { - var err error g, ctx := errgroup.WithContext(ctx) diff --git a/lab_test.go b/lab_test.go index ccea24b..bb0e388 100644 --- a/lab_test.go +++ b/lab_test.go @@ -307,7 +307,6 @@ func TestClient_ImportLabBadAuth(t *testing.T) { } func TestClient_NodeByLabel(t *testing.T) { - l := Lab{ Nodes: NodeMap{ "bla": &Node{ @@ -579,6 +578,7 @@ func TestClient_LabGetByTitle(t *testing.T) { }) } } + func TestClient_LabCreate(t *testing.T) { tc := newAuthedTestAPIclient() diff --git a/link.go b/link.go index 6ed800e..bbc77f7 100644 --- a/link.go +++ b/link.go @@ -95,10 +95,8 @@ func (c *Client) LinkGet(ctx context.Context, labID, linkID string, deep bool) ( link.LabID = labID if deep { - var ( - err error - // ifaceA, ifaceB *Interface - ) + var err error + // ifaceA, ifaceB *Interface ifaceA := &Interface{ ID: link.SrcID, diff --git a/node.go b/node.go index a0e48bb..7572888 100644 --- a/node.go +++ b/node.go @@ -69,6 +69,7 @@ type Node struct { Label string `json:"label"` X int `json:"x"` Y int `json:"y"` + HideLinks bool `json:"hide_links"` NodeDefinition string `json:"node_definition"` ImageDefinition string `json:"image_definition"` Configuration *string `json:"configuration"` @@ -92,6 +93,7 @@ type nodePatchPostAlias struct { Label string `json:"label,omitempty"` X int `json:"x"` Y int `json:"y"` + HideLinks bool `json:"hide_links"` NodeDefinition string `json:"node_definition,omitempty"` ImageDefinition string `json:"image_definition,omitempty"` Configuration *string `json:"configuration,omitempty"` @@ -109,6 +111,7 @@ func newNodeAlias(node *Node, update bool) nodePatchPostAlias { npp.Label = node.Label npp.X = node.X npp.Y = node.Y + npp.HideLinks = node.HideLinks npp.Tags = node.Tags // node tags can't be null, either the tag has to be omitted or it has @@ -156,6 +159,7 @@ func (c *Client) updateCachedNode(existingNode, node *Node) *Node { existingNode.X = node.X existingNode.Y = node.Y existingNode.Tags = node.Tags + existingNode.HideLinks = node.HideLinks // these can be changed but only when the node VM doesn't exist if node.State == NodeStateDefined { @@ -322,7 +326,6 @@ func (c *Client) NodeStop(ctx context.Context, node *Node) error { // NodeCreate creates a new node on the controller based on the data provided // in `node`. Label, node definition and image definition must be provided. func (c *Client) NodeCreate(ctx context.Context, node *Node) (*Node, error) { - // TODO: inconsistent attributes lab_title vs title, .. node.State = NodeStateDefined postAlias := newNodeAlias(node, false) @@ -383,7 +386,6 @@ func (c *Client) NodeCreate(ctx context.Context, node *Node) (*Node, error) { // NodeGet returns the node identified by its `ID` and `LabID` in the provided node. func (c *Client) NodeGet(ctx context.Context, node *Node, nocache bool) (*Node, error) { - if !nocache { if node, ok := c.getCachedNode(node.LabID, node.ID); ok { return node, nil diff --git a/nodedef_test.go b/nodedef_test.go index f29b381..5305d10 100644 --- a/nodedef_test.go +++ b/nodedef_test.go @@ -8,7 +8,6 @@ import ( ) func TestClient_GetNodeDefs(t *testing.T) { - tc := newAuthedTestAPIclient() nodeDefs := []byte(`[ diff --git a/system.go b/system.go index c464643..f138c76 100644 --- a/system.go +++ b/system.go @@ -31,7 +31,6 @@ func versionError(got string) error { } func (c *Client) versionCheck(ctx context.Context, depth int32) error { - c.compatibilityErr = nil sv := systemVersion{} if err := c.jsonGet(ctx, systeminfoAPI, &sv, depth); err != nil { diff --git a/system_test.go b/system_test.go index 70559e3..80f2c58 100644 --- a/system_test.go +++ b/system_test.go @@ -8,7 +8,6 @@ import ( ) func TestClient_VersionCheck(t *testing.T) { - c := New("https://bla.bla", true, useCache) mrClient, ctx := mr.NewMockResponder() c.httpClient = mrClient @@ -42,7 +41,6 @@ func TestClient_VersionCheck(t *testing.T) { } func TestClient_NotReady(t *testing.T) { - c := New("https://bla.bla", true, useCache) mrClient, ctx := mr.NewMockResponder() c.httpClient = mrClient @@ -66,7 +64,6 @@ func TestClient_Version(t *testing.T) { version string want string }{ - {"empty", "", ""}, {"some", "some", "some"}, } diff --git a/users_test.go b/users_test.go index 8c2bbc6..6e28d78 100644 --- a/users_test.go +++ b/users_test.go @@ -48,7 +48,6 @@ var ( ) func TestClient_GetUsers(t *testing.T) { - tc := newAuthedTestAPIclient() tests := []struct { @@ -108,7 +107,6 @@ func TestClient_GetUsers(t *testing.T) { } func TestClient_GetUserByName(t *testing.T) { - tc := newAuthedTestAPIclient() tests := []struct { @@ -169,7 +167,6 @@ func TestClient_GetUserByName(t *testing.T) { } func TestClient_GetUser(t *testing.T) { - tc := newAuthedTestAPIclient() // ID of user1 from above @@ -245,7 +242,6 @@ func TestClient_UserDestroy(t *testing.T) { } func TestClient_UserCreate(t *testing.T) { - tc := newAuthedTestAPIclient() newUser := User{Username: "doesntmatter"} @@ -300,7 +296,6 @@ func TestClient_UserCreate(t *testing.T) { } func TestClient_UserUpdate(t *testing.T) { - tc := newAuthedTestAPIclient() newUser := User{Username: "doesntmatter"} @@ -359,7 +354,6 @@ func TestClient_UserUpdate(t *testing.T) { } func TestClient_UserGroups(t *testing.T) { - tc := newAuthedTestAPIclient() tests := []struct { diff --git a/vendor/golang.org/x/sync/errgroup/errgroup.go b/vendor/golang.org/x/sync/errgroup/errgroup.go index cbee7a4..b18efb7 100644 --- a/vendor/golang.org/x/sync/errgroup/errgroup.go +++ b/vendor/golang.org/x/sync/errgroup/errgroup.go @@ -20,7 +20,7 @@ type token struct{} // A zero Group is valid, has no limit on the number of active goroutines, // and does not cancel on error. type Group struct { - cancel func() + cancel func(error) wg sync.WaitGroup @@ -43,7 +43,7 @@ func (g *Group) done() { // returns a non-nil error or the first time Wait returns, whichever occurs // first. func WithContext(ctx context.Context) (*Group, context.Context) { - ctx, cancel := context.WithCancel(ctx) + ctx, cancel := withCancelCause(ctx) return &Group{cancel: cancel}, ctx } @@ -52,7 +52,7 @@ func WithContext(ctx context.Context) (*Group, context.Context) { func (g *Group) Wait() error { g.wg.Wait() if g.cancel != nil { - g.cancel() + g.cancel(g.err) } return g.err } @@ -76,7 +76,7 @@ func (g *Group) Go(f func() error) { g.errOnce.Do(func() { g.err = err if g.cancel != nil { - g.cancel() + g.cancel(g.err) } }) } @@ -105,7 +105,7 @@ func (g *Group) TryGo(f func() error) bool { g.errOnce.Do(func() { g.err = err if g.cancel != nil { - g.cancel() + g.cancel(g.err) } }) } diff --git a/vendor/golang.org/x/sync/errgroup/go120.go b/vendor/golang.org/x/sync/errgroup/go120.go new file mode 100644 index 0000000..f93c740 --- /dev/null +++ b/vendor/golang.org/x/sync/errgroup/go120.go @@ -0,0 +1,13 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.20 + +package errgroup + +import "context" + +func withCancelCause(parent context.Context) (context.Context, func(error)) { + return context.WithCancelCause(parent) +} diff --git a/vendor/golang.org/x/sync/errgroup/pre_go120.go b/vendor/golang.org/x/sync/errgroup/pre_go120.go new file mode 100644 index 0000000..88ce334 --- /dev/null +++ b/vendor/golang.org/x/sync/errgroup/pre_go120.go @@ -0,0 +1,14 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !go1.20 + +package errgroup + +import "context" + +func withCancelCause(parent context.Context) (context.Context, func(error)) { + ctx, cancel := context.WithCancel(parent) + return ctx, func(error) { cancel() } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 2b60350..dad1024 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -13,8 +13,8 @@ github.com/rschmied/mockresponder # github.com/stretchr/testify v1.8.2 ## explicit; go 1.13 github.com/stretchr/testify/assert -# golang.org/x/sync v0.1.0 -## explicit +# golang.org/x/sync v0.5.0 +## explicit; go 1.18 golang.org/x/sync/errgroup # gopkg.in/yaml.v3 v3.0.1 ## explicit