Kufuli is a centralized locking system for distributed, highly available systems. Written in Go and powered by gRPC. Built as a proof of concept.
There's currently no build system for this project, you'll have to build from source.
Ensure you have gPRC installed:
go get -u google.golang.org/grpc
Clone this repo and install project dependencies:
git clone git@github.com:ishuah/kufuli.git
dep ensure
You can now build the project:
go build
Run the instance:
./kufuli
This repo contains a sample configuration file. To use it, copy the contents of sample.config.yaml
to config.yaml
and edit the values to your liking. If you don't create the config.yaml
file, kufuli will fall back to default values defined in config/config.go
.
The configurations are as follows
Field | Description | Type | Default |
max_retries | Maximum number of retries when a client tries to obtain a lock | int | 16 |
retry_delay | The interval length between retries | string | 500ms |
max_lock_span | Maximum duration a client can hold a lock | string | 10s |
default_cleanup_delay | Kufuli runs background services that release locks that have exceeded `max_lock_span`. This config dictates the interval between the checks. | string | 5s |
Clients in Go are supported using the github.com/ishuah/kufuli/api
package.
A simple example(examples/client.go
):
package main
import (
"log"
"os"
"github.com/ishuah/kufuli/api"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
func main() {
args := os.Args[1:]
if len(args) < 3 {
log.Fatal("You need to provide three arguments")
}
var conn *grpc.ClientConn
conn, err := grpc.Dial(":7777", grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %s", err)
}
defer conn.Close()
c := api.NewApiClient(conn)
action := args[0]
var response *api.Response
switch action {
case "lock":
response, err = c.RequestLock(context.Background(), &api.Request{Resource: args[1], ServiceID: args[2]})
case "release":
response, err = c.ReleaseLock(context.Background(), &api.Request{Resource: args[1], ServiceID: args[2]})
}
if err != nil {
log.Fatalf("Error when calling %s: %s", action, err)
}
log.Printf("Response from server, success: %t, error: %s", response.Success, response.Error)
}
You can run the above example as follows:
go run client.go lock disk2 sync
You create a new client instance via api.NewApiClient
. The client exposes two function, RequestLock
and ReleaseLock
. Both functions accept an api.Request
pointer and return an api.Response
and an error
. Further documentation below.
gRPC uses protocol buffers which allows you to generate client and server interfaces from a .proto
file. A lot of major languages are supported.
Create a new directory, e.g kufuli-client-py
.
In this new directory, create a new directory called api
.
Copy the api/api.proto file to kufuli-client-py/api
.
Install the python gPRC package:
pip install grpcio-tools
Generate the python code:
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. api/api.proto
Create a new file, kufuli-client-py/client.py
and copy the following code into it:
import grpc
import sys
from api import api_pb2, api_pb2_grpc
def run(args):
channel = grpc.insecure_channel('localhost:7777')
stub = api_pb2_grpc.ApiStub(channel)
action = args[0]
request = api_pb2.Request(resource=args[1], serviceID=args[2])
try:
if action == "lock":
response = stub.RequestLock(request)
elif action == "release":
response = stub.ReleaseLock(request)
else:
print "unsupported action {}".format(action)
sys.exit(0)
print "Response from server: {}".format(response)
except grpc.RpcError as e:
print "error when requesting {}: {}".format(action, e)
if __name__ == '__main__':
args = sys.argv[1:]
if len(args) < 3:
print "You need to provide three arguments"
sys.exit(0)
run(args)
Finally, run the client (make sure the server instance is running):
python client.py lock disk0 sync
Field | Type | Label | Description |
resource | string |
|
|
serviceID | string |
|
Field | Type | Label | Description |
success | bool |
|
|
error | string |
|
Method Name | Request Type | Response Type | Description |
RequestLock | Request | Response | |
ReleaseLock | Request | Response |
.proto Type | Notes | C++ Type | Java Type | Python Type |
double | double | double | float | |
float | float | float | float | |
int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int |
int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long |
uint32 | Uses variable-length encoding. | uint32 | int | int/long |
uint64 | Uses variable-length encoding. | uint64 | long | int/long |
sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int |
sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long |
fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int |
fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long |
sfixed32 | Always four bytes. | int32 | int | int |
sfixed64 | Always eight bytes. | int64 | long | int/long |
bool | bool | boolean | boolean | |
string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode |
bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str |