forked from TrueCloudLab/distribution
ca0084fad1
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
304 lines
7.3 KiB
Go
304 lines
7.3 KiB
Go
package ipc
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"io"
|
|
"io/ioutil"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"syscall"
|
|
|
|
"github.com/docker/libchan"
|
|
"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
|
|
transport *spdy.Transport
|
|
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 {
|
|
return nil, err
|
|
}
|
|
|
|
driverPath := os.ExpandEnv(path.Join("$GOPATH", "bin", name))
|
|
if _, err := os.Stat(driverPath); os.IsNotExist(err) {
|
|
driverPath = path.Join(path.Dir(os.Args[0]), name)
|
|
}
|
|
if _, err := os.Stat(driverPath); os.IsNotExist(err) {
|
|
driverPath, err = exec.LookPath(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
command := exec.Command(driverPath, string(paramsBytes))
|
|
|
|
return &StorageDriverClient{
|
|
subprocess: command,
|
|
}, 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 {
|
|
return err
|
|
}
|
|
|
|
childSocket := os.NewFile(uintptr(fileDescriptors[0]), "childSocket")
|
|
parentSocket := os.NewFile(uintptr(fileDescriptors[1]), "parentSocket")
|
|
|
|
driver.subprocess.Stdout = os.Stdout
|
|
driver.subprocess.Stderr = os.Stderr
|
|
driver.subprocess.ExtraFiles = []*os.File{childSocket}
|
|
|
|
if err = driver.subprocess.Start(); err != nil {
|
|
parentSocket.Close()
|
|
return err
|
|
}
|
|
|
|
if err = childSocket.Close(); err != nil {
|
|
parentSocket.Close()
|
|
return err
|
|
}
|
|
|
|
connection, err := net.FileConn(parentSocket)
|
|
if err != nil {
|
|
parentSocket.Close()
|
|
return err
|
|
}
|
|
transport, err := spdy.NewClientTransport(connection)
|
|
if err != nil {
|
|
parentSocket.Close()
|
|
return err
|
|
}
|
|
sender, err := transport.NewSendChannel()
|
|
if err != nil {
|
|
transport.Close()
|
|
parentSocket.Close()
|
|
return err
|
|
}
|
|
|
|
driver.socket = parentSocket
|
|
driver.transport = transport
|
|
driver.sender = sender
|
|
|
|
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()
|
|
closeSocketErr := driver.socket.Close()
|
|
killErr := driver.subprocess.Process.Kill()
|
|
|
|
if closeSenderErr != nil {
|
|
return closeSenderErr
|
|
} else if closeTransportErr != nil {
|
|
return closeTransportErr
|
|
} else if closeSocketErr != nil {
|
|
return closeSocketErr
|
|
}
|
|
return killErr
|
|
}
|
|
|
|
// Implement the storagedriver.StorageDriver interface over IPC
|
|
|
|
func (driver *StorageDriverClient) GetContent(path string) ([]byte, error) {
|
|
receiver, remoteSender := libchan.Pipe()
|
|
|
|
params := map[string]interface{}{"Path": path}
|
|
err := driver.sender.Send(&Request{Type: "GetContent", Parameters: params, ResponseChannel: remoteSender})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var response ReadStreamResponse
|
|
err = receiver.Receive(&response)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if response.Error != nil {
|
|
return nil, response.Error
|
|
}
|
|
|
|
defer response.Reader.Close()
|
|
contents, err := ioutil.ReadAll(response.Reader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return contents, nil
|
|
}
|
|
|
|
func (driver *StorageDriverClient) PutContent(path string, contents []byte) error {
|
|
receiver, remoteSender := libchan.Pipe()
|
|
|
|
params := map[string]interface{}{"Path": path, "Reader": WrapReader(bytes.NewReader(contents))}
|
|
err := driver.sender.Send(&Request{Type: "PutContent", Parameters: params, ResponseChannel: remoteSender})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var response WriteStreamResponse
|
|
err = receiver.Receive(&response)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if response.Error != nil {
|
|
return response.Error
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (driver *StorageDriverClient) ReadStream(path string, offset uint64) (io.ReadCloser, error) {
|
|
receiver, remoteSender := libchan.Pipe()
|
|
|
|
params := map[string]interface{}{"Path": path, "Offset": offset}
|
|
err := driver.sender.Send(&Request{Type: "ReadStream", Parameters: params, ResponseChannel: remoteSender})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var response ReadStreamResponse
|
|
err = receiver.Receive(&response)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if response.Error != nil {
|
|
return nil, response.Error
|
|
}
|
|
|
|
return response.Reader, nil
|
|
}
|
|
|
|
func (driver *StorageDriverClient) WriteStream(path string, offset, size uint64, reader io.ReadCloser) error {
|
|
receiver, remoteSender := libchan.Pipe()
|
|
|
|
params := map[string]interface{}{"Path": path, "Offset": offset, "Size": size, "Reader": WrapReader(reader)}
|
|
err := driver.sender.Send(&Request{Type: "WriteStream", Parameters: params, ResponseChannel: remoteSender})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var response WriteStreamResponse
|
|
err = receiver.Receive(&response)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if response.Error != nil {
|
|
return response.Error
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (driver *StorageDriverClient) ResumeWritePosition(path string) (uint64, error) {
|
|
receiver, remoteSender := libchan.Pipe()
|
|
|
|
params := map[string]interface{}{"Path": path}
|
|
err := driver.sender.Send(&Request{Type: "ResumeWritePosition", Parameters: params, ResponseChannel: remoteSender})
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
var response ResumeWritePositionResponse
|
|
err = receiver.Receive(&response)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if response.Error != nil {
|
|
return 0, response.Error
|
|
}
|
|
|
|
return response.Position, nil
|
|
}
|
|
|
|
func (driver *StorageDriverClient) List(prefix string) ([]string, error) {
|
|
receiver, remoteSender := libchan.Pipe()
|
|
|
|
params := map[string]interface{}{"Prefix": prefix}
|
|
err := driver.sender.Send(&Request{Type: "List", Parameters: params, ResponseChannel: remoteSender})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var response ListResponse
|
|
err = receiver.Receive(&response)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if response.Error != nil {
|
|
return nil, response.Error
|
|
}
|
|
|
|
return response.Keys, nil
|
|
}
|
|
|
|
func (driver *StorageDriverClient) Move(sourcePath string, destPath string) error {
|
|
receiver, remoteSender := libchan.Pipe()
|
|
|
|
params := map[string]interface{}{"SourcePath": sourcePath, "DestPath": destPath}
|
|
err := driver.sender.Send(&Request{Type: "Move", Parameters: params, ResponseChannel: remoteSender})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var response MoveResponse
|
|
err = receiver.Receive(&response)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if response.Error != nil {
|
|
return response.Error
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (driver *StorageDriverClient) Delete(path string) error {
|
|
receiver, remoteSender := libchan.Pipe()
|
|
|
|
params := map[string]interface{}{"Path": path}
|
|
err := driver.sender.Send(&Request{Type: "Delete", Parameters: params, ResponseChannel: remoteSender})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var response DeleteResponse
|
|
err = receiver.Receive(&response)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if response.Error != nil {
|
|
return response.Error
|
|
}
|
|
|
|
return nil
|
|
}
|