-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
BKTCLT-19 new bindings for raft session admin routes
Add two new functions binding the admin routes: - AdminGetBucketSessionID (`GET /_/buckets/my-bucket/id`) - AdminGetSessionInfo/AdminGetAllSessionsInfo (`GET /_/raft_sessions`)
- Loading branch information
1 parent
962ceb4
commit 4f32cbf
Showing
4 changed files
with
267 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package bucketclient | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"strconv" | ||
) | ||
|
||
// AdminGetBucketSessionID returns the raft session ID of the given bucket. | ||
// Returns -1 and an error if the bucket doesn't exist, or if a request error occurs. | ||
func (client *BucketClient) AdminGetBucketSessionID(ctx context.Context, bucketName string) (int, error) { | ||
resource := fmt.Sprintf("/_/buckets/%s/id", bucketName) | ||
responseBody, err := client.Request(ctx, "AdminGetBucketSessionID", "GET", resource) | ||
if err != nil { | ||
return -1, err | ||
} | ||
sessionId, err := strconv.ParseInt(string(responseBody), 10, 0) | ||
if err != nil { | ||
return -1, &BucketClientError{ | ||
"AdminGetBucketSessionID", "GET", client.Endpoint, resource, 0, "", | ||
fmt.Errorf("bucketd did not return a valid session ID in response body"), | ||
} | ||
} | ||
return int(sessionId), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package bucketclient_test | ||
|
||
import ( | ||
. "github.com/onsi/ginkgo/v2" | ||
. "github.com/onsi/gomega" | ||
|
||
"github.com/jarcoal/httpmock" | ||
|
||
"github.com/scality/bucketclient/go" | ||
) | ||
|
||
var _ = Describe("AdminGetBucketSessionId()", Ordered, func() { | ||
BeforeEach(func() { | ||
httpmock.Reset() | ||
}) | ||
|
||
var client *bucketclient.BucketClient | ||
BeforeAll(func() { | ||
httpmock.Activate() | ||
client = bucketclient.New("http://localhost:9000") | ||
}) | ||
AfterAll(func() { | ||
httpmock.DeactivateAndReset() | ||
}) | ||
It("return the raft session ID hosting a bucket", func(ctx SpecContext) { | ||
httpmock.RegisterResponder( | ||
"GET", "http://localhost:9000/_/buckets/my-bucket/id", | ||
httpmock.NewStringResponder(200, "42"), | ||
) | ||
Expect(client.AdminGetBucketSessionID(ctx, "my-bucket")).To(Equal(42)) | ||
}) | ||
It("return an error if the bucket doesn't exist", func(ctx SpecContext) { | ||
httpmock.RegisterResponder( | ||
"GET", "http://localhost:9000/_/buckets/nosuchbucket/id", | ||
httpmock.NewStringResponder(404, ""), | ||
) | ||
_, err := client.AdminGetBucketSessionID(ctx, "nosuchbucket") | ||
Expect(err).To(MatchError(ContainSubstring("bucketd returned HTTP status 404"))) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package bucketclient | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
) | ||
|
||
type MemberInfo struct { | ||
ID int `json:"id"` | ||
Name string `json:"name"` | ||
DisplayName string `json:"display_name"` | ||
Host string `json:"host"` | ||
Port int `json:"port"` | ||
AdminPort int `json:"adminPort"` | ||
MDClusterId string `json:"mdClusterId"` | ||
} | ||
|
||
type SessionInfo struct { | ||
ID int `json:"id"` | ||
RaftMembers []MemberInfo `json:"raftMembers"` | ||
ConnectedToLeader bool `json:"connectedToLeader"` | ||
} | ||
|
||
// AdminGetAllSessionsInfo returns raft session info for all Metadata | ||
// raft sessions available on the S3C deployment. | ||
func (client *BucketClient) AdminGetAllSessionsInfo(ctx context.Context) ([]SessionInfo, error) { | ||
responseBody, err := client.Request(ctx, "AdminGetAllSessionsInfo", "GET", "/_/raft_sessions") | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var parsedInfo []SessionInfo | ||
jsonErr := json.Unmarshal(responseBody, &parsedInfo) | ||
if jsonErr != nil { | ||
return nil, ErrorMalformedResponse("AdminGetAllSessionsInfo", | ||
"GET", client.Endpoint, "/_/raft_sessions", jsonErr) | ||
} | ||
return parsedInfo, nil | ||
} | ||
|
||
// AdminGetSessionInfo returns raft session info for the given raft session ID. | ||
// Returns nil and an error if the raft session doesn't exist, or if a request | ||
// error occurs. | ||
func (client *BucketClient) AdminGetSessionInfo(ctx context.Context, | ||
sessionId int) (*SessionInfo, error) { | ||
// When querying /_/raft_sessions/X/info, bucketd returns a | ||
// status 500 if the raft session doesn't exist, which is hard | ||
// to distinguish from a generic type of failure. For this | ||
// reason, instead, we fetch the info for all raft sessions | ||
// then lookup the one we want. | ||
sessionsInfo, err := client.AdminGetAllSessionsInfo(ctx) | ||
if err != nil { | ||
return nil, err | ||
} | ||
for _, sessionInfo := range sessionsInfo { | ||
if sessionInfo.ID == sessionId { | ||
return &sessionInfo, nil | ||
} | ||
} | ||
// raft session does not exist: return a 404 status as if coming from bucketd | ||
return nil, &BucketClientError{ | ||
"AdminGetSessionInfo", "GET", client.Endpoint, "/_/raft_sessions", | ||
404, "RaftSessionNotFound", | ||
fmt.Errorf("no such raft session: %d", sessionId), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package bucketclient_test | ||
|
||
import ( | ||
. "github.com/onsi/ginkgo/v2" | ||
. "github.com/onsi/gomega" | ||
|
||
"github.com/jarcoal/httpmock" | ||
|
||
"github.com/scality/bucketclient/go" | ||
) | ||
|
||
var bucketdResponse = `[ | ||
{ | ||
"id": 1, | ||
"raftMembers": [ | ||
{ | ||
"id": 10, | ||
"name": "md1-cluster1", | ||
"display_name": "127.0.0.1 (md1-cluster1)", | ||
"host": "127.0.0.1", | ||
"port": 4201, | ||
"adminPort": 4251, | ||
"mdClusterId": "1" | ||
} | ||
], | ||
"connectedToLeader": true | ||
}, | ||
{ | ||
"id": 2, | ||
"raftMembers": [ | ||
{ | ||
"id": 20, | ||
"name": "md1-cluster1", | ||
"display_name": "127.0.0.1 (md1-cluster1)", | ||
"host": "127.0.0.1", | ||
"port": 4202, | ||
"adminPort": 4252, | ||
"mdClusterId": "1" | ||
} | ||
], | ||
"connectedToLeader": false | ||
} | ||
] | ||
` | ||
|
||
var _ = Describe("AdminGetSessionInfo()/AdminGetAllSessionsInfo()", Ordered, func() { | ||
BeforeEach(func() { | ||
httpmock.Reset() | ||
}) | ||
|
||
var client *bucketclient.BucketClient | ||
BeforeAll(func() { | ||
httpmock.Activate() | ||
client = bucketclient.New("http://localhost:9000") | ||
}) | ||
AfterAll(func() { | ||
httpmock.DeactivateAndReset() | ||
}) | ||
|
||
Describe("AdminGetAllSessionsInfo()", func() { | ||
It("return info about all raft sessions", func(ctx SpecContext) { | ||
httpmock.RegisterResponder( | ||
"GET", "http://localhost:9000/_/raft_sessions", | ||
httpmock.NewStringResponder(200, bucketdResponse), | ||
) | ||
Expect(client.AdminGetAllSessionsInfo(ctx)).To(Equal([]bucketclient.SessionInfo{ | ||
{ | ||
ID: 1, | ||
RaftMembers: []bucketclient.MemberInfo{ | ||
{ | ||
ID: 10, | ||
Name: "md1-cluster1", | ||
DisplayName: "127.0.0.1 (md1-cluster1)", | ||
Host: "127.0.0.1", | ||
Port: 4201, | ||
AdminPort: 4251, | ||
MDClusterId: "1", | ||
}, | ||
}, | ||
ConnectedToLeader: true, | ||
}, | ||
{ | ||
ID: 2, | ||
RaftMembers: []bucketclient.MemberInfo{ | ||
{ | ||
ID: 20, | ||
Name: "md1-cluster1", | ||
DisplayName: "127.0.0.1 (md1-cluster1)", | ||
Host: "127.0.0.1", | ||
Port: 4202, | ||
AdminPort: 4252, | ||
MDClusterId: "1", | ||
}, | ||
}, | ||
ConnectedToLeader: false, | ||
}, | ||
})) | ||
}) | ||
}) | ||
|
||
Describe("AdminGetSessionInfo()", func() { | ||
It("return info about a particular raft session", func(ctx SpecContext) { | ||
httpmock.RegisterResponder( | ||
"GET", "http://localhost:9000/_/raft_sessions", | ||
httpmock.NewStringResponder(200, bucketdResponse), | ||
) | ||
Expect(client.AdminGetSessionInfo(ctx, 2)).To(Equal(&bucketclient.SessionInfo{ | ||
ID: 2, | ||
RaftMembers: []bucketclient.MemberInfo{ | ||
{ | ||
ID: 20, | ||
Name: "md1-cluster1", | ||
DisplayName: "127.0.0.1 (md1-cluster1)", | ||
Host: "127.0.0.1", | ||
Port: 4202, | ||
AdminPort: 4252, | ||
MDClusterId: "1", | ||
}, | ||
}, | ||
ConnectedToLeader: false, | ||
})) | ||
}) | ||
It("return an error if the session doesn't exist", func(ctx SpecContext) { | ||
httpmock.RegisterResponder( | ||
"GET", "http://localhost:9000/_/raft_sessions", | ||
httpmock.NewStringResponder(200, bucketdResponse), | ||
) | ||
_, err := client.AdminGetSessionInfo(ctx, 3) | ||
bcErr, ok := err.(*bucketclient.BucketClientError) | ||
Expect(ok).To(BeTrue()) | ||
Expect(bcErr.StatusCode).To(Equal(404)) | ||
Expect(bcErr.ErrorType).To(Equal("RaftSessionNotFound")) | ||
}) | ||
}) | ||
}) |