Skip to content

Commit

Permalink
BKTCLT-18 add API methods
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathan-gramain committed Sep 4, 2024
1 parent 078b47b commit 2249f07
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 0 deletions.
23 changes: 23 additions & 0 deletions go-client/pkg/bucketclient/admin-get-bucket-session-id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package bucketclient

import (
"fmt"
"strconv"
)

func (client *BucketClient) GetBucketSessionID(bucketName string) (int, error) {
url := fmt.Sprintf("/_/buckets/%s/id", bucketName)
responseBody, err := client.Request("GetBucketSessionID", "GET", url, nil, "")
if err != nil {
requestErr := err.(*BucketClientError)
if requestErr.StatusCode == 404 {
return -1, fmt.Errorf("bucket '%s' does not exist", bucketName)
}
return -1, err
}
sessionId, err := strconv.ParseInt(string(responseBody), 10, 0)
if err != nil {
return -1, fmt.Errorf("bucketd did not return a valid session ID in response body")
}
return int(sessionId), nil
}
54 changes: 54 additions & 0 deletions go-client/pkg/bucketclient/admin-get-session-info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package bucketclient

import (
"encoding/json"
)

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"`
}

func (client *BucketClient) GetAllSessionsInfo() ([]SessionInfo, error) {
responseBody, err := client.Request("GetAllSessionsInfo", "GET", "/_/raft_sessions", nil, "")
if err != nil {
return nil, err
}

var parsedInfo []SessionInfo
jsonErr := json.Unmarshal(responseBody, &parsedInfo)
if jsonErr != nil {
return nil, ErrorMalformedResponse("GetAllSessionsInfo", jsonErr)
}
return parsedInfo, nil
}

func (client *BucketClient) GetSessionInfo(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.GetAllSessionsInfo()
if err != nil {
return nil, err
}
for _, sessionInfo := range sessionsInfo {
if sessionInfo.ID == sessionId {
return &sessionInfo, nil
}
}
// raft session does not exist
return nil, nil
}
28 changes: 28 additions & 0 deletions go-client/pkg/bucketclient/bucketclient-error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package bucketclient

import (
"fmt"
)

type BucketClientError struct {
ApiMethod string
StatusCode int
ErrorType string
Err error
}

func (e *BucketClientError) Error() string {
if e.StatusCode > 0 {
return fmt.Sprintf("error in %s: bucketd returned HTTP status %d",
e.ApiMethod, e.StatusCode)
} else {
return fmt.Sprintf("error in %s: HTTP request to bucketd failed: %v",
e.ApiMethod, e.Err)
}
}

func ErrorMalformedResponse(apiMethod string, err error) error {
return &BucketClientError{apiMethod, 0, "",
fmt.Errorf("bucketd returned a malformed response body: %w", err),
}
}
55 changes: 55 additions & 0 deletions go-client/pkg/bucketclient/bucketclient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package bucketclient

import (
"bytes"
"fmt"
"net/http"
"io"
"strings"
)

type BucketClient struct {
URL string
}

func New(bucketdURL string) *BucketClient {
return &BucketClient{bucketdURL}
}


func (client *BucketClient) Request(apiMethod string, httpMethod string, path string,
requestBody []byte, requestBodyContentType string) ([]byte, error) {

url := fmt.Sprintf("%s%s", client.URL, path)
var response *http.Response
var err error = nil

switch httpMethod {
case "GET":
response, err = http.Get(url)
case "POST":
response, err = http.Post(url, requestBodyContentType, bytes.NewReader(requestBody))
default:
err = fmt.Errorf("unsupported HTTP method %s", httpMethod)
}
if err != nil {
return nil, &BucketClientError{apiMethod, 0, "", err}
}
defer response.Body.Close()

if response.StatusCode / 100 != 2 {
splitStatus := strings.Split(response.Status, " ")
errorType := ""
if len(splitStatus) == 2 {
errorType = splitStatus[1]
}
return nil, &BucketClientError{apiMethod, response.StatusCode, errorType, nil}
}
responseBody, err := io.ReadAll(response.Body)
if err != nil {
return nil, &BucketClientError{
apiMethod, 0, "", fmt.Errorf("error reading response body: %w", err),
}
}
return responseBody, nil
}
18 changes: 18 additions & 0 deletions go-client/pkg/bucketclient/create-bucket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package bucketclient

import (
"fmt"
)

func (client *BucketClient) CreateBucket(bucketName string, bucketAttributes []byte) error {
url := fmt.Sprintf("/default/bucket/%s", bucketName)
_, err := client.Request("CreateBucket", "POST", url, bucketAttributes, "application/json")
return err
}

func (client *BucketClient) CreateBucketOnSession(bucketName string, sessionId int,
bucketAttributes []byte) error {
url := fmt.Sprintf("/default/bucket/%s?raftsession=%d", bucketName, sessionId)
_, err := client.Request("CreateBucket", "POST", url, bucketAttributes, "application/json")
return err
}
10 changes: 10 additions & 0 deletions go-client/pkg/bucketclient/get-bucket-attributes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package bucketclient

import (
"fmt"
)

func (client *BucketClient) GetBucketAttributes(bucketName string) ([]byte, error) {
path := fmt.Sprintf("/default/attributes/%s", bucketName)
return client.Request("GetBucketAttributes", "GET", path, nil, "")
}
38 changes: 38 additions & 0 deletions go-client/pkg/bucketclient/list-object-versions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package bucketclient

import (
"encoding/json"
"fmt"
"net/url"
)

type ListObjectVersionsEntry struct {
Key string `json:"key"`
VersionId string `json:"versionId"`
Value string `json:"value"`
}

type ListObjectVersionsResponse struct {
Versions []ListObjectVersionsEntry
CommonPrefixes []string
IsTruncated bool
NextKeyMarker string
NextVersionIdMarker string
}

func (client *BucketClient) ListObjectVersions(bucketName string,
keyMarker string, versionIdMarker string, maxKeys int) (*ListObjectVersionsResponse, error) {
path := fmt.Sprintf(
"/default/bucket/%s?listingType=DelimiterVersions&keyMarker=%s&versionIdMarker=%s&maxKeys=%d",
bucketName, url.QueryEscape(keyMarker), url.QueryEscape(versionIdMarker), maxKeys)
responseBody, reqErr := client.Request("ListObjectVersions", "GET", path, nil, "")
if reqErr != nil {
return nil, reqErr
}
var parsedResponse = new(ListObjectVersionsResponse)
jsonErr := json.Unmarshal(responseBody, parsedResponse)
if jsonErr != nil {
return nil, ErrorMalformedResponse("ListObjectVersions", jsonErr)
}
return parsedResponse, nil
}
12 changes: 12 additions & 0 deletions go-client/pkg/bucketclient/put-bucket-attributes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package bucketclient

import (
"fmt"
)

func (client *BucketClient) PutBucketAttributes(bucketName string, bucketAttributes []byte) error {
path := fmt.Sprintf("/default/attributes/%s", bucketName)
_, err := client.Request("PutBucketAttributes", "POST", path,
bucketAttributes, "application/json")
return err
}

0 comments on commit 2249f07

Please sign in to comment.