forked from Seagate/cortx-motr
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add example of REST API server based on go bindings. Signed-off-by: kiwionly2 <gheewooi.ong@seagate.com>
- Loading branch information
Showing
12 changed files
with
859 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,67 @@ | ||
|
||
## Motr REST API server | ||
|
||
Motr REST API Server is a simple REST API Server which based on motr go bindings. | ||
|
||
It included a REST API server and a http client. | ||
|
||
The API implemented as below: | ||
|
||
``` | ||
/api/kv/put | ||
/api/kv/get | ||
/api/kv/delete | ||
/api/object/read | ||
/api/object/write | ||
``` | ||
|
||
#### Server | ||
|
||
To start REST API server, first you need to run motr server. | ||
|
||
Follow motr [quick start guide](/doc/Quick-Start-Guide.rst) to build motr. | ||
|
||
To start motr in development mode, open a new terminal, go to `%cortx_motr_dir%/motr/examples` at `motr` root directory. | ||
|
||
```sh | ||
cd %cortx_home%/motr/examples | ||
./setup_a_running_motr_system.sh | ||
``` | ||
|
||
After motr server start, you should see the connection information at the end of terminal. | ||
|
||
At this directory, open `motr.toml`, this is where `motr` connection configure, modify the value accordingly and save the file. | ||
|
||
Open another terminal and run the REST API server : | ||
``` | ||
> go mod tidy | ||
> export MOTR_HOME=motr_root_diretory | ||
> CGO_CFLAGS="-I/$MOTR_HOME -I/usr/include/motr -DM0_EXTERN=extern -DM0_INTERNAL= -Wno-attributes" CGO_LDFLAGS="-L$MOTR_HOME/motr/.libs -Wl,-rpath=$MOTR_HOME/motr/.libs -lmotr" go run . | ||
``` | ||
|
||
You should see the server start and running at port `8081`. | ||
|
||
|
||
#### Client | ||
|
||
To interect with the REST API server, you could use http client under `http_client` folder: | ||
|
||
```go | ||
client := NewClient("localhost", 8081) | ||
|
||
// read from file and write to motr | ||
response, err := client.Write(IndexID, "black_hole.png") | ||
|
||
// read from motr and write to file | ||
err := client.Read(IndexID, objectSize, "out.png") | ||
``` | ||
|
||
Open another terminal and run the tests to see if the http client work as expected. | ||
``` | ||
> cd http_client | ||
> go test -v | ||
``` | ||
|
||
You could refer to test cases for more sample of client usage. | ||
|
||
|
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,108 @@ | ||
package api | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"io/ioutil" | ||
"net/url" | ||
"strconv" | ||
"time" | ||
|
||
"github.com/gin-gonic/gin" | ||
) | ||
|
||
func get(ctx *gin.Context, name string, required bool, defaultValue interface{}, errs *[]error) interface{} { | ||
|
||
var val string | ||
|
||
if ctx.Request.Method == "POST" { | ||
val = ctx.PostForm(name) | ||
|
||
// if POST binary content, get from query | ||
if val == "" { | ||
val = ctx.Query(name) | ||
} | ||
|
||
} else { | ||
val = ctx.Query(name) | ||
} | ||
|
||
if required && val == "" { | ||
toError(errs, name+" is required") | ||
return defaultValue | ||
} else if val == "" { | ||
return defaultValue | ||
} | ||
|
||
val, err := url.QueryUnescape(val) | ||
|
||
if err != nil { | ||
toError(errs, err.Error()) | ||
} | ||
|
||
return val | ||
} | ||
|
||
func getString(ctx *gin.Context, name string, required bool, defaultValue interface{}, errs *[]error) string { | ||
val := get(ctx, name, required, defaultValue, errs) | ||
return toString(val) | ||
} | ||
|
||
func getInt(ctx *gin.Context, name string, required bool, defaultValue interface{}, errs *[]error) int { | ||
val := get(ctx, name, required, defaultValue, errs) | ||
c := toI32(val) | ||
return int(c) | ||
} | ||
|
||
func getBytes(ctx *gin.Context, errs *[]error) []byte { | ||
ByteBody, err := ioutil.ReadAll(ctx.Request.Body) | ||
|
||
if err != nil { | ||
toError(errs, err.Error()) | ||
return nil | ||
} | ||
|
||
return ByteBody | ||
} | ||
|
||
func toError(errs *[]error, msg string) { | ||
*errs = append(*errs, errors.New(msg)) | ||
} | ||
|
||
func hasError(errs []error) error { | ||
|
||
for _, err := range errs { | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func createError(err error) map[string]interface{} { | ||
|
||
errs := make(map[string]interface{}) | ||
errs["error"] = err.Error() | ||
|
||
return errs | ||
} | ||
|
||
func toTime(end time.Duration) string { | ||
return fmt.Sprintf("%v", end) | ||
} | ||
|
||
func toString(t interface{}) string { | ||
return fmt.Sprintf("%+v", t) | ||
} | ||
|
||
func toI32(t interface{}) int32 { | ||
s := toString(t) | ||
i, err := strconv.Atoi(s) | ||
|
||
if err != nil { | ||
return 0 | ||
} | ||
|
||
return int32(i) | ||
} |
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,125 @@ | ||
package api | ||
|
||
import ( | ||
"fmt" | ||
"go_motr_api_server/v2/motr" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/gin-gonic/gin" | ||
) | ||
|
||
func Status(ctx *gin.Context) { | ||
|
||
json := make(map[string]interface{}) | ||
json["status"] = "ok" | ||
|
||
ctx.JSON(http.StatusOK, json) | ||
} | ||
|
||
func GetKey(ctx *gin.Context) { | ||
|
||
start := time.Now() | ||
|
||
errs := &[]error{} | ||
|
||
indexID := getString(ctx, "index_id", true, nil, errs) | ||
key := getString(ctx, "key", true, nil, errs) | ||
|
||
if err := hasError(*errs); err != nil { | ||
ctx.AbortWithStatusJSON(http.StatusBadRequest, createError(err)) | ||
return | ||
} | ||
|
||
value, err := motr.Get(indexID, key) | ||
|
||
if err != nil { | ||
ctx.AbortWithStatusJSON(http.StatusBadRequest, createError(err)) | ||
return | ||
} | ||
|
||
end := time.Since(start) | ||
|
||
meta := make(map[string]interface{}) | ||
meta["time"] = toTime(end) | ||
|
||
json := make(map[string]interface{}) | ||
json["meta"] = meta | ||
json["index_id"] = indexID | ||
json["key"] = key | ||
json["value"] = value | ||
|
||
ctx.JSON(http.StatusOK, json) | ||
} | ||
|
||
func DeleteKey(ctx *gin.Context) { | ||
|
||
start := time.Now() | ||
|
||
errs := &[]error{} | ||
|
||
indexID := getString(ctx, "index_id", true, nil, errs) | ||
key := getString(ctx, "key", true, nil, errs) | ||
|
||
if err := hasError(*errs); err != nil { | ||
ctx.AbortWithStatusJSON(http.StatusBadRequest, createError(err)) | ||
return | ||
} | ||
|
||
err := motr.Delete(indexID, key) | ||
|
||
if err != nil { | ||
ctx.AbortWithStatusJSON(http.StatusBadRequest, createError(err)) | ||
return | ||
} | ||
|
||
end := time.Since(start) | ||
|
||
meta := make(map[string]interface{}) | ||
meta["time"] = toTime(end) | ||
|
||
json := make(map[string]interface{}) | ||
json["meta"] = meta | ||
json["index_id"] = indexID | ||
json["key"] = key | ||
|
||
ctx.JSON(http.StatusOK, json) | ||
} | ||
|
||
func PutKeyValue(ctx *gin.Context) { | ||
|
||
start := time.Now() | ||
|
||
errs := &[]error{} | ||
|
||
indexID := getString(ctx, "index_id", true, nil, errs) | ||
key := getString(ctx, "key", true, nil, errs) | ||
value := getString(ctx, "value", true, nil, errs) | ||
|
||
if err := hasError(*errs); err != nil { | ||
ctx.AbortWithStatusJSON(http.StatusBadRequest, createError(err)) | ||
return | ||
} | ||
|
||
fmt.Println(indexID, key, value) | ||
|
||
err := motr.Put(indexID, key, value) | ||
|
||
if err != nil { | ||
ctx.AbortWithStatusJSON(http.StatusBadRequest, createError(err)) | ||
return | ||
} | ||
|
||
end := time.Since(start) | ||
|
||
meta := make(map[string]interface{}) | ||
meta["time"] = toTime(end) | ||
|
||
json := make(map[string]interface{}) | ||
json["meta"] = meta | ||
json["index_id"] = indexID | ||
json["key"] = key | ||
json["value"] = value | ||
|
||
ctx.JSON(http.StatusOK, json) | ||
} |
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,72 @@ | ||
package api | ||
|
||
import ( | ||
"bytes" | ||
"go_motr_api_server/v2/motr" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/gin-gonic/gin" | ||
) | ||
|
||
func Write(ctx *gin.Context) { | ||
|
||
start := time.Now() | ||
|
||
errs := &[]error{} | ||
|
||
indexID := getString(ctx, "index_id", true, nil, errs) | ||
size := getInt(ctx, "size", true, nil, errs) | ||
byteBody := getBytes(ctx, errs) | ||
|
||
if err := hasError(*errs); err != nil { | ||
ctx.AbortWithStatusJSON(http.StatusBadRequest, createError(err)) | ||
return | ||
} | ||
|
||
err := motr.Write("", indexID, uint64(size), bytes.NewReader(byteBody)) | ||
|
||
if err != nil { | ||
ctx.AbortWithStatusJSON(http.StatusBadRequest, createError(err)) | ||
return | ||
} | ||
|
||
end := time.Since(start) | ||
|
||
meta := make(map[string]interface{}) | ||
meta["time"] = toTime(end) | ||
|
||
json := make(map[string]interface{}) | ||
json["meta"] = meta | ||
json["indexID"] = indexID | ||
json["size"] = size | ||
|
||
ctx.JSON(http.StatusOK, json) | ||
} | ||
|
||
func Read(ctx *gin.Context) { | ||
|
||
start := time.Now() | ||
|
||
errs := &[]error{} | ||
|
||
indexID := getString(ctx, "index_id", true, nil, errs) | ||
size := getInt(ctx, "size", true, nil, errs) | ||
|
||
if err := hasError(*errs); err != nil { | ||
ctx.AbortWithStatusJSON(http.StatusBadRequest, createError(err)) | ||
return | ||
} | ||
|
||
byteContent, err := motr.Read("", indexID, uint64(size)) | ||
|
||
if err != nil { | ||
ctx.AbortWithStatusJSON(http.StatusBadRequest, createError(err)) | ||
return | ||
} | ||
|
||
end := time.Since(start) | ||
|
||
ctx.Header("time", toTime(end)) | ||
ctx.Data(http.StatusOK, "application/octet-stream", byteContent) | ||
} |
Oops, something went wrong.