/kufuli

Kufuli is a centralized locking system for distributed, highly available systems.

Primary LanguageGoMIT LicenseMIT

kufuli

Go Report Card

Kufuli is a centralized locking system for distributed, highly available systems. Written in Go and powered by gRPC. Built as a proof of concept.

Installation

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

Configuration

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
max_retries: 10 retry_delay: "500ms" max_lock_span: "10s" default_cleanup_delay: "5s"

Writing clients

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.

Support for other languages

gRPC uses protocol buffers which allows you to generate client and server interfaces from a .proto file. A lot of major languages are supported.

Example: Generating a python client

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

Documentation

api.proto

Request

FieldTypeLabelDescription
resource string

serviceID string

Response

FieldTypeLabelDescription
success bool

error string

Api

Method Name Request TypeResponse Type Description
RequestLock Request Response
ReleaseLock Request Response

Scalar Value Types

.proto TypeNotesC++ TypeJava TypePython 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