Skip to content

Commit 5f0d2fa

Browse files
Add admin server tests and adapters
1 parent a8cfbf4 commit 5f0d2fa

3 files changed

Lines changed: 189 additions & 3 deletions

File tree

admin/ops.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/fabricekabongo/loggerhead/clustering"
1313
"github.com/fabricekabongo/loggerhead/config"
14+
"github.com/hashicorp/memberlist"
1415
"github.com/prometheus/client_golang/prometheus/promhttp"
1516
)
1617

@@ -35,11 +36,43 @@ func init() {
3536
}
3637

3738
type OpsServer struct {
38-
cluster *clustering.Cluster
39+
cluster Cluster
3940
cfg config.Config
4041
}
4142

42-
func NewOpsServer(cluster *clustering.Cluster, cfg config.Config) *OpsServer {
43+
type Cluster interface {
44+
MemberList() MemberListProvider
45+
Broadcasts() BroadcastQueue
46+
}
47+
48+
type MemberListProvider interface {
49+
LocalNode() *memberlist.Node
50+
NumMembers() int
51+
Members() []*memberlist.Node
52+
GetHealthScore() int
53+
}
54+
55+
type BroadcastQueue interface {
56+
NumQueued() int
57+
}
58+
59+
type clusterAdapter struct {
60+
cluster *clustering.Cluster
61+
}
62+
63+
func NewClusterAdapter(cluster *clustering.Cluster) Cluster {
64+
return clusterAdapter{cluster: cluster}
65+
}
66+
67+
func (c clusterAdapter) MemberList() MemberListProvider {
68+
return c.cluster.MemberList()
69+
}
70+
71+
func (c clusterAdapter) Broadcasts() BroadcastQueue {
72+
return c.cluster.Broadcasts()
73+
}
74+
75+
func NewOpsServer(cluster Cluster, cfg config.Config) *OpsServer {
4376
return &OpsServer{
4477
cluster: cluster,
4578
cfg: cfg,

admin/ops_test.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package admin
2+
3+
import (
4+
"encoding/json"
5+
"net"
6+
"net/http"
7+
"net/http/httptest"
8+
"testing"
9+
10+
"github.com/fabricekabongo/loggerhead/config"
11+
"github.com/hashicorp/memberlist"
12+
)
13+
14+
type mockMemberList struct {
15+
local *memberlist.Node
16+
members []*memberlist.Node
17+
health int
18+
}
19+
20+
func (m *mockMemberList) LocalNode() *memberlist.Node { return m.local }
21+
func (m *mockMemberList) NumMembers() int { return len(m.members) }
22+
func (m *mockMemberList) Members() []*memberlist.Node { return m.members }
23+
func (m *mockMemberList) GetHealthScore() int { return m.health }
24+
25+
type mockQueue struct{ queued int }
26+
27+
func (m mockQueue) NumQueued() int { return m.queued }
28+
29+
type mockCluster struct {
30+
ml MemberListProvider
31+
queue BroadcastQueue
32+
}
33+
34+
func (m mockCluster) MemberList() MemberListProvider { return m.ml }
35+
func (m mockCluster) Broadcasts() BroadcastQueue { return m.queue }
36+
37+
func TestAdminData(t *testing.T) {
38+
localNode := &memberlist.Node{
39+
Name: "node-a",
40+
Addr: net.ParseIP("127.0.0.1"),
41+
State: memberlist.StateAlive,
42+
}
43+
44+
cluster := mockCluster{
45+
ml: &mockMemberList{
46+
local: localNode,
47+
members: []*memberlist.Node{localNode},
48+
health: 2,
49+
},
50+
queue: mockQueue{queued: 3},
51+
}
52+
53+
server := NewOpsServer(cluster, configForTests())
54+
55+
rr := httptest.NewRecorder()
56+
req := httptest.NewRequest(http.MethodGet, "/admin-data", nil)
57+
server.AdminData().ServeHTTP(rr, req)
58+
59+
if rr.Code != http.StatusOK {
60+
t.Fatalf("expected status 200, got %d", rr.Code)
61+
}
62+
63+
if got := rr.Header().Get("Content-Type"); got != "application/json" {
64+
t.Fatalf("expected application/json content type, got %s", got)
65+
}
66+
67+
var data Data
68+
if err := json.NewDecoder(rr.Body).Decode(&data); err != nil {
69+
t.Fatalf("failed to decode response: %v", err)
70+
}
71+
72+
if data.Name != "node-a" {
73+
t.Fatalf("expected node name to be propagated, got %s", data.Name)
74+
}
75+
76+
if data.Address != "127.0.0.1" {
77+
t.Fatalf("expected address to match node address, got %s", data.Address)
78+
}
79+
80+
if data.NodesAlive != 1 {
81+
t.Fatalf("expected one node alive, got %d", data.NodesAlive)
82+
}
83+
84+
if data.Health != 2 {
85+
t.Fatalf("expected health score from cluster, got %d", data.Health)
86+
}
87+
88+
if data.QueueCount != 3 {
89+
t.Fatalf("expected queue count to be included, got %d", data.QueueCount)
90+
}
91+
}
92+
93+
func TestAdminDataProxySkip(t *testing.T) {
94+
localNode := &memberlist.Node{
95+
Name: "node-a",
96+
Addr: net.ParseIP("127.0.0.1"),
97+
State: memberlist.StateAlive,
98+
}
99+
remoteNode := &memberlist.Node{
100+
Name: "node-b",
101+
Addr: net.ParseIP("127.0.0.2"),
102+
State: memberlist.StateAlive,
103+
}
104+
105+
cluster := mockCluster{
106+
ml: &mockMemberList{
107+
local: localNode,
108+
members: []*memberlist.Node{localNode, remoteNode},
109+
health: 1,
110+
},
111+
queue: mockQueue{queued: 0},
112+
}
113+
114+
server := NewOpsServer(cluster, configForTests())
115+
116+
rr := httptest.NewRecorder()
117+
req := httptest.NewRequest(http.MethodGet, "/admin-data?proxy=true", nil)
118+
server.AdminData().ServeHTTP(rr, req)
119+
120+
var data Data
121+
if err := json.NewDecoder(rr.Body).Decode(&data); err != nil {
122+
t.Fatalf("failed to decode response: %v", err)
123+
}
124+
125+
if len(data.Others) != 0 {
126+
t.Fatalf("expected proxy mode to skip other members, got %d entries", len(data.Others))
127+
}
128+
}
129+
130+
func TestAdminUI(t *testing.T) {
131+
cluster := mockCluster{
132+
ml: &mockMemberList{local: &memberlist.Node{}, members: []*memberlist.Node{{}}, health: 0},
133+
queue: mockQueue{queued: 0},
134+
}
135+
136+
server := NewOpsServer(cluster, configForTests())
137+
138+
rr := httptest.NewRecorder()
139+
req := httptest.NewRequest(http.MethodGet, "/", nil)
140+
server.AdminUI().ServeHTTP(rr, req)
141+
142+
if rr.Code != http.StatusOK {
143+
t.Fatalf("expected status 200 from admin UI, got %d", rr.Code)
144+
}
145+
146+
if rr.Body.Len() == 0 {
147+
t.Fatal("expected template to render some content")
148+
}
149+
}
150+
151+
func configForTests() config.Config {
152+
return config.Config{}
153+
}

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func main() {
4949
ClusterCtx, concel := context.WithCancel(ctx)
5050
clusterEngine := clustering.NewEngineDecorator(ClusterCtx, cluster, writeEngine)
5151

52-
opsServer := admin.NewOpsServer(cluster, cfg)
52+
opsServer := admin.NewOpsServer(admin.NewClusterAdapter(cluster), cfg)
5353
go opsServer.Start()
5454

5555
writer := server.NewListener(cfg.WritePort, cfg.MaxConnections, cfg.MaxEOFWait, clusterEngine) // This is the writer listener (for writes and broadcasts)

0 commit comments

Comments
 (0)