Adds StorageDriverFactory, unifying creation of StorageDrivers

Custom storage drivers can register a factory to create the driver by
name, similar to the database/sql package's Register and Open
factory.Create returns an in-process driver if registered or an IPC
driver if one can be found, erroring otherwise
This standardizes parameter passing for creation of storage drivers

Also adds documentation for storagedriver package and children
This commit is contained in:
Brian Bland 2014-10-28 18:15:40 -07:00
parent ff81f3a719
commit ca0084fad1
15 changed files with 290 additions and 79 deletions

View file

@ -15,6 +15,7 @@ import (
"github.com/docker/libchan/spdy"
)
// Storage Driver implementation using a managed child process communicating over IPC
type StorageDriverClient struct {
subprocess *exec.Cmd
socket *os.File
@ -22,6 +23,13 @@ type StorageDriverClient struct {
sender libchan.Sender
}
// Constructs a new out-of-process storage driver using the driver name and configuration parameters
// Must call Start() on this driver client before remote method calls can be made
//
// Looks for drivers in the following locations in order:
// - Storage drivers directory (to be determined, yet not implemented)
// - $GOPATH/bin
// - $PATH
func NewDriverClient(name string, parameters map[string]string) (*StorageDriverClient, error) {
paramsBytes, err := json.Marshal(parameters)
if err != nil {
@ -46,6 +54,7 @@ func NewDriverClient(name string, parameters map[string]string) (*StorageDriverC
}, nil
}
// Starts the designated child process storage driver and binds a socket to this process for IPC
func (driver *StorageDriverClient) Start() error {
fileDescriptors, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM, 0)
if err != nil {
@ -93,6 +102,8 @@ func (driver *StorageDriverClient) Start() error {
return nil
}
// Stops the child process storage driver
// storagedriver.StorageDriver methods called after Stop() will fail
func (driver *StorageDriverClient) Stop() error {
closeSenderErr := driver.sender.Close()
closeTransportErr := driver.transport.Close()
@ -109,6 +120,8 @@ func (driver *StorageDriverClient) Stop() error {
return killErr
}
// Implement the storagedriver.StorageDriver interface over IPC
func (driver *StorageDriverClient) GetContent(path string) ([]byte, error) {
receiver, remoteSender := libchan.Pipe()

View file

@ -10,12 +10,16 @@ import (
"github.com/docker/libchan"
)
// Defines a remote method call request
// A return value struct is to be sent over the ResponseChannel
type Request struct {
Type string
Parameters map[string]interface{}
ResponseChannel libchan.Sender
}
// A simple wrapper around an io.ReadCloser that implements the io.ReadWriteCloser interface
// Writes are disallowed and will return an error if ever called
type noWriteReadWriteCloser struct {
io.ReadCloser
}
@ -24,6 +28,8 @@ func (r noWriteReadWriteCloser) Write(p []byte) (n int, err error) {
return 0, errors.New("Write unsupported")
}
// Wraps an io.Reader as an io.ReadWriteCloser with a nop Close and unsupported Write method
// Has no effect when an io.ReadWriteCloser is passed in
func WrapReader(reader io.Reader) io.ReadWriteCloser {
if readWriteCloser, ok := reader.(io.ReadWriteCloser); ok {
return readWriteCloser
@ -39,6 +45,7 @@ type responseError struct {
Message string
}
// Wraps an error in a serializable struct containing the error's type and message
func ResponseError(err error) *responseError {
if err == nil {
return nil
@ -53,29 +60,37 @@ func (err *responseError) Error() string {
return fmt.Sprintf("%s: %s", err.Type, err.Message)
}
// IPC method call response object definitions
// Response for a ReadStream request
type ReadStreamResponse struct {
Reader io.ReadWriteCloser
Error *responseError
}
// Response for a WriteStream request
type WriteStreamResponse struct {
Error *responseError
}
// Response for a ResumeWritePosition request
type ResumeWritePositionResponse struct {
Position uint64
Error *responseError
}
// Response for a List request
type ListResponse struct {
Keys []string
Error *responseError
}
// Response for a Move request
type MoveResponse struct {
Error *responseError
}
// Response for a Delete request
type DeleteResponse struct {
Error *responseError
}

View file

@ -6,13 +6,18 @@ import (
"io/ioutil"
"net"
"os"
"reflect"
"github.com/docker/docker-registry/storagedriver"
"github.com/docker/libchan"
"github.com/docker/libchan/spdy"
)
func Server(driver storagedriver.StorageDriver) error {
// Construct a new IPC server handling requests for the given storagedriver.StorageDriver
// This explicitly uses file descriptor 3 for IPC communication, as storage drivers are spawned in client.go
//
// To create a new out-of-process driver, create a main package which calls StorageDriverServer with a storagedriver.StorageDriver
func StorageDriverServer(driver storagedriver.StorageDriver) error {
childSocket := os.NewFile(3, "childSocket")
defer childSocket.Close()
conn, err := net.FileConn(childSocket)
@ -34,6 +39,9 @@ func Server(driver storagedriver.StorageDriver) error {
}
}
// Receives new storagedriver.StorageDriver method requests and creates a new goroutine to handle each request
//
// Requests are expected to be of type ipc.Request as the parameters are unknown until the request type is deserialized
func receive(driver storagedriver.StorageDriver, receiver libchan.Receiver) {
for {
var request Request
@ -45,6 +53,8 @@ func receive(driver storagedriver.StorageDriver, receiver libchan.Receiver) {
}
}
// Handles storagedriver.StorageDriver method requests as defined in client.go
// Responds to requests using the Request.ResponseChannel
func handleRequest(driver storagedriver.StorageDriver, request Request) {
switch request.Type {
case "GetContent":
@ -76,14 +86,9 @@ func handleRequest(driver storagedriver.StorageDriver, request Request) {
panic(err)
}
case "ReadStream":
var offset uint64
path, _ := request.Parameters["Path"].(string)
offset, ok := request.Parameters["Offset"].(uint64)
if !ok {
offsetSigned, _ := request.Parameters["Offset"].(int64)
offset = uint64(offsetSigned)
}
// Depending on serialization method, Offset may be convereted to any int/uint type
offset := reflect.ValueOf(request.Parameters["Offset"]).Convert(reflect.TypeOf(uint64(0))).Uint()
reader, err := driver.ReadStream(path, offset)
var response ReadStreamResponse
if err != nil {
@ -96,19 +101,11 @@ func handleRequest(driver storagedriver.StorageDriver, request Request) {
panic(err)
}
case "WriteStream":
var offset uint64
path, _ := request.Parameters["Path"].(string)
offset, ok := request.Parameters["Offset"].(uint64)
if !ok {
offsetSigned, _ := request.Parameters["Offset"].(int64)
offset = uint64(offsetSigned)
}
size, ok := request.Parameters["Size"].(uint64)
if !ok {
sizeSigned, _ := request.Parameters["Size"].(int64)
size = uint64(sizeSigned)
}
// Depending on serialization method, Offset may be convereted to any int/uint type
offset := reflect.ValueOf(request.Parameters["Offset"]).Convert(reflect.TypeOf(uint64(0))).Uint()
// Depending on serialization method, Size may be convereted to any int/uint type
size := reflect.ValueOf(request.Parameters["Size"]).Convert(reflect.TypeOf(uint64(0))).Uint()
reader, _ := request.Parameters["Reader"].(io.ReadCloser)
err := driver.WriteStream(path, offset, size, reader)
response := WriteStreamResponse{