Skip to content

Commit 5083ae8

Browse files
Add /v2/dqlite/remove endpoint (#55)
1 parent 664ceaa commit 5083ae8

File tree

11 files changed

+231
-49
lines changed

11 files changed

+231
-49
lines changed

pkg/api/v2/register.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,25 @@ func (a *API) RegisterServer(server *http.ServeMux, middleware func(f http.Handl
5353
}
5454
httputil.Response(w, map[string]string{"status": "OK"})
5555
}))
56+
57+
// POST v2/dqlite/remove
58+
server.HandleFunc(fmt.Sprintf("%s/dqlite/remove", HTTPPrefix), middleware(func(w http.ResponseWriter, r *http.Request) {
59+
if r.Method != http.MethodPost {
60+
w.WriteHeader(http.StatusMethodNotAllowed)
61+
return
62+
}
63+
64+
req := RemoveFromDqliteRequest{}
65+
if err := httputil.UnmarshalJSON(r, &req); err != nil {
66+
httputil.Error(w, http.StatusBadRequest, fmt.Errorf("failed to unmarshal JSON: %w", err))
67+
return
68+
}
69+
70+
if rc, err := a.RemoveFromDqlite(r.Context(), req); err != nil {
71+
httputil.Error(w, rc, fmt.Errorf("failed to remove from dqlite: %w", err))
72+
return
73+
}
74+
75+
httputil.Response(w, nil)
76+
}))
5677
}

pkg/api/v2/remove.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package v2
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
8+
snaputil "github.com/canonical/microk8s-cluster-agent/pkg/snap/util"
9+
)
10+
11+
// RemoveFromDqliteRequest represents a request to remove a node from the dqlite cluster.
12+
type RemoveFromDqliteRequest struct {
13+
// RemoveEndpoint is the endpoint of the node to remove from the dqlite cluster.
14+
RemoveEndpoint string `json:"removeEndpoint"`
15+
}
16+
17+
// RemoveFromDqlite implements the "POST /v2/dqlite/remove" endpoint and removes a node from the dqlite cluster.
18+
func (a *API) RemoveFromDqlite(ctx context.Context, req RemoveFromDqliteRequest) (int, error) {
19+
if err := snaputil.RemoveNodeFromDqlite(ctx, a.Snap, req.RemoveEndpoint); err != nil {
20+
return http.StatusInternalServerError, fmt.Errorf("failed to remove node from dqlite: %w", err)
21+
}
22+
23+
return http.StatusOK, nil
24+
}

pkg/api/v2/remove_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package v2_test
2+
3+
import (
4+
"context"
5+
"errors"
6+
"net/http"
7+
"testing"
8+
9+
. "github.com/onsi/gomega"
10+
11+
v2 "github.com/canonical/microk8s-cluster-agent/pkg/api/v2"
12+
"github.com/canonical/microk8s-cluster-agent/pkg/snap/mock"
13+
)
14+
15+
func TestRemove(t *testing.T) {
16+
t.Run("RemoveFails", func(t *testing.T) {
17+
cmdErr := errors.New("failed to run command")
18+
apiv2 := &v2.API{
19+
Snap: &mock.Snap{
20+
RunCommandErr: cmdErr,
21+
},
22+
}
23+
24+
rc, err := apiv2.RemoveFromDqlite(context.Background(), v2.RemoveFromDqliteRequest{RemoveEndpoint: "1.1.1.1:1234"})
25+
26+
g := NewWithT(t)
27+
g.Expect(err).To(MatchError(cmdErr))
28+
g.Expect(rc).To(Equal(http.StatusInternalServerError))
29+
})
30+
31+
t.Run("RemovesSuccessfully", func(t *testing.T) {
32+
apiv2 := &v2.API{
33+
Snap: &mock.Snap{},
34+
}
35+
36+
rc, err := apiv2.RemoveFromDqlite(context.Background(), v2.RemoveFromDqliteRequest{RemoveEndpoint: "1.1.1.1:1234"})
37+
38+
g := NewWithT(t)
39+
g.Expect(err).ToNot(HaveOccurred())
40+
g.Expect(rc).To(Equal(http.StatusOK))
41+
})
42+
}

pkg/snap/interface.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ import (
77

88
// Snap is how the cluster agent interacts with the snap.
99
type Snap interface {
10+
// GetSnapPath returns the path to a file or directory in the snap directory.
11+
GetSnapPath(parts ...string) string
12+
// GetSnapDataPath returns the path to a file or directory in the snap's data directory.
13+
GetSnapDataPath(parts ...string) string
14+
// GetSnapCommonPath returns the path to a file or directory in the snap's common directory.
15+
GetSnapCommonPath(parts ...string) string
16+
17+
// RunCommand runs a shell command.
18+
RunCommand(ctx context.Context, commands ...string) error
19+
1020
// GetGroupName is the group microk8s is using.
1121
// The group name is "microk8s" for classic snaps and "snap_microk8s" for strict snaps.
1222
GetGroupName() string

pkg/snap/mock/mock.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"io"
7+
"path/filepath"
78
"strings"
89

910
"github.com/canonical/microk8s-cluster-agent/pkg/snap"
@@ -23,8 +24,20 @@ type JoinClusterCall struct {
2324
Worker bool
2425
}
2526

27+
// RunCommandCall contains the arguments passed to a specific call of the RunCommand method.
28+
type RunCommandCall struct {
29+
Commands []string
30+
}
31+
2632
// Snap is a generic mock for the snap.Snap interface.
2733
type Snap struct {
34+
SnapDir string
35+
SnapDataDir string
36+
SnapCommonDir string
37+
38+
RunCommandCalledWith []RunCommandCall
39+
RunCommandErr error
40+
2841
GroupName string
2942

3043
EnableAddonCalledWith []string
@@ -88,6 +101,27 @@ type Snap struct {
88101
EtcdCA, EtcdCert, EtcdKey string
89102
}
90103

104+
// GetSnapPath is a mock implementation for the snap.Snap interface.
105+
func (s *Snap) GetSnapPath(parts ...string) string {
106+
return filepath.Join(append([]string{s.SnapDir}, parts...)...)
107+
}
108+
109+
// GetSnapDataPath is a mock implementation for the snap.Snap interface.
110+
func (s *Snap) GetSnapDataPath(parts ...string) string {
111+
return filepath.Join(append([]string{s.SnapDataDir}, parts...)...)
112+
}
113+
114+
// GetSnapCommonPath is a mock implementation for the snap.Snap interface.
115+
func (s *Snap) GetSnapCommonPath(parts ...string) string {
116+
return filepath.Join(append([]string{s.SnapCommonDir}, parts...)...)
117+
}
118+
119+
// RunCommand is a mock implementation for the snap.Snap interface.
120+
func (s *Snap) RunCommand(_ context.Context, commands ...string) error {
121+
s.RunCommandCalledWith = append(s.RunCommandCalledWith, RunCommandCall{Commands: commands})
122+
return s.RunCommandErr
123+
}
124+
91125
// GetGroupName is a mock implementation for the snap.Snap interface.
92126
func (s *Snap) GetGroupName() string {
93127
return s.GroupName

0 commit comments

Comments
 (0)