Adds versioning for out-of-process storage driver
The registry currently only accepts storage driver versions with the same major version and an equal or lower minor version as its own current storage driver api version, but this may be changed in the future if we decide to implement specific version cross-compatibility.
This commit is contained in:
parent
f02cfee950
commit
1ae5485998
5 changed files with 106 additions and 17 deletions
|
@ -40,6 +40,8 @@ Storage drivers should call `factory.Register` with their driver name in an `ini
|
||||||
### Out-of-process drivers
|
### Out-of-process drivers
|
||||||
As many users will run the registry as a pre-constructed docker container, storage drivers should also be distributable as IPC server executables. Drivers written in go should model the main method provided in `main/storagedriver/filesystem/filesystem.go`. Parameters to IPC drivers will be provided as a JSON-serialized map in the first argument to the process. These parameters should be validated and then a blocking call to `ipc.StorageDriverServer` should be made with a new storage driver.
|
As many users will run the registry as a pre-constructed docker container, storage drivers should also be distributable as IPC server executables. Drivers written in go should model the main method provided in `main/storagedriver/filesystem/filesystem.go`. Parameters to IPC drivers will be provided as a JSON-serialized map in the first argument to the process. These parameters should be validated and then a blocking call to `ipc.StorageDriverServer` should be made with a new storage driver.
|
||||||
|
|
||||||
|
Out-of-process drivers must also implement the `ipc.IPCStorageDriver` interface, which exposes a `Version` check for the storage driver. This is used to validate storage driver api compatibility at driver load-time.
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
Storage driver test suites are provided in `storagedriver/testsuites/testsuites.go` and may be used for any storage driver written in go. Two methods are provided for registering test suites, `RegisterInProcessSuite` and `RegisterIPCSuite`, which run the same set of tests for the driver imported or managed over IPC respectively.
|
Storage driver test suites are provided in `storagedriver/testsuites/testsuites.go` and may be used for any storage driver written in go. Two methods are provided for registering test suites, `RegisterInProcessSuite` and `RegisterIPCSuite`, which run the same set of tests for the driver imported or managed over IPC respectively.
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/docker/docker-registry/storagedriver"
|
||||||
"github.com/docker/libchan"
|
"github.com/docker/libchan"
|
||||||
"github.com/docker/libchan/spdy"
|
"github.com/docker/libchan/spdy"
|
||||||
)
|
)
|
||||||
|
@ -22,6 +23,7 @@ type StorageDriverClient struct {
|
||||||
socket *os.File
|
socket *os.File
|
||||||
transport *spdy.Transport
|
transport *spdy.Transport
|
||||||
sender libchan.Sender
|
sender libchan.Sender
|
||||||
|
version storagedriver.Version
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDriverClient constructs a new out-of-process storage driver using the driver name and
|
// NewDriverClient constructs a new out-of-process storage driver using the driver name and
|
||||||
|
@ -65,42 +67,62 @@ func (driver *StorageDriverClient) Start() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
childSocket := os.NewFile(uintptr(fileDescriptors[0]), "childSocket")
|
childSocket := os.NewFile(uintptr(fileDescriptors[0]), "childSocket")
|
||||||
parentSocket := os.NewFile(uintptr(fileDescriptors[1]), "parentSocket")
|
driver.socket = os.NewFile(uintptr(fileDescriptors[1]), "parentSocket")
|
||||||
|
|
||||||
driver.subprocess.Stdout = os.Stdout
|
driver.subprocess.Stdout = os.Stdout
|
||||||
driver.subprocess.Stderr = os.Stderr
|
driver.subprocess.Stderr = os.Stderr
|
||||||
driver.subprocess.ExtraFiles = []*os.File{childSocket}
|
driver.subprocess.ExtraFiles = []*os.File{childSocket}
|
||||||
|
|
||||||
if err = driver.subprocess.Start(); err != nil {
|
if err = driver.subprocess.Start(); err != nil {
|
||||||
parentSocket.Close()
|
driver.Stop()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = childSocket.Close(); err != nil {
|
if err = childSocket.Close(); err != nil {
|
||||||
parentSocket.Close()
|
driver.Stop()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
connection, err := net.FileConn(parentSocket)
|
connection, err := net.FileConn(driver.socket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
parentSocket.Close()
|
driver.Stop()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
transport, err := spdy.NewClientTransport(connection)
|
driver.transport, err = spdy.NewClientTransport(connection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
parentSocket.Close()
|
driver.Stop()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sender, err := transport.NewSendChannel()
|
driver.sender, err = driver.transport.NewSendChannel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
transport.Close()
|
driver.Stop()
|
||||||
parentSocket.Close()
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
driver.socket = parentSocket
|
// Check the driver's version to determine compatibility
|
||||||
driver.transport = transport
|
receiver, remoteSender := libchan.Pipe()
|
||||||
driver.sender = sender
|
err = driver.sender.Send(&Request{Type: "Version", ResponseChannel: remoteSender})
|
||||||
|
if err != nil {
|
||||||
|
driver.Stop()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var response VersionResponse
|
||||||
|
err = receiver.Receive(&response)
|
||||||
|
if err != nil {
|
||||||
|
driver.Stop()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.Error != nil {
|
||||||
|
return response.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
driver.version = response.Version
|
||||||
|
|
||||||
|
if driver.version.Major() != storagedriver.CurrentVersion.Major() || driver.version.Minor() > storagedriver.CurrentVersion.Minor() {
|
||||||
|
return IncompatibleVersionError{driver.version}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -108,10 +130,20 @@ func (driver *StorageDriverClient) Start() error {
|
||||||
// Stop stops the child process storage driver
|
// Stop stops the child process storage driver
|
||||||
// storagedriver.StorageDriver methods called after Stop will fail
|
// storagedriver.StorageDriver methods called after Stop will fail
|
||||||
func (driver *StorageDriverClient) Stop() error {
|
func (driver *StorageDriverClient) Stop() error {
|
||||||
closeSenderErr := driver.sender.Close()
|
var closeSenderErr, closeTransportErr, closeSocketErr, killErr error
|
||||||
closeTransportErr := driver.transport.Close()
|
|
||||||
closeSocketErr := driver.socket.Close()
|
if driver.sender != nil {
|
||||||
killErr := driver.subprocess.Process.Kill()
|
closeSenderErr = driver.sender.Close()
|
||||||
|
}
|
||||||
|
if driver.transport != nil {
|
||||||
|
closeTransportErr = driver.transport.Close()
|
||||||
|
}
|
||||||
|
if driver.socket != nil {
|
||||||
|
closeSocketErr = driver.socket.Close()
|
||||||
|
}
|
||||||
|
if driver.subprocess != nil {
|
||||||
|
killErr = driver.subprocess.Process.Kill()
|
||||||
|
}
|
||||||
|
|
||||||
if closeSenderErr != nil {
|
if closeSenderErr != nil {
|
||||||
return closeSenderErr
|
return closeSenderErr
|
||||||
|
|
|
@ -5,9 +5,29 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/docker/docker-registry/storagedriver"
|
||||||
"github.com/docker/libchan"
|
"github.com/docker/libchan"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IPCStorageDriver is the interface which IPC storage drivers must implement. As external storage
|
||||||
|
// drivers may be defined to use a different version of the storagedriver.StorageDriver interface,
|
||||||
|
// we use an additional version check to determine compatiblity.
|
||||||
|
type IPCStorageDriver interface {
|
||||||
|
// Version returns the storagedriver.StorageDriver interface version which this storage driver
|
||||||
|
// implements, which is used to determine driver compatibility
|
||||||
|
Version() (storagedriver.Version, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncompatibleVersionError is returned when a storage driver is using an incompatible version of
|
||||||
|
// the storagedriver.StorageDriver api
|
||||||
|
type IncompatibleVersionError struct {
|
||||||
|
version storagedriver.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e IncompatibleVersionError) Error() string {
|
||||||
|
return fmt.Sprintf("Incompatible storage driver version: %s", e.version)
|
||||||
|
}
|
||||||
|
|
||||||
// Request defines a remote method call request
|
// Request defines a remote method call request
|
||||||
// A return value struct is to be sent over the ResponseChannel
|
// A return value struct is to be sent over the ResponseChannel
|
||||||
type Request struct {
|
type Request struct {
|
||||||
|
@ -38,6 +58,12 @@ func (err *responseError) Error() string {
|
||||||
|
|
||||||
// IPC method call response object definitions
|
// IPC method call response object definitions
|
||||||
|
|
||||||
|
// VersionResponse is a response for a Version request
|
||||||
|
type VersionResponse struct {
|
||||||
|
Version storagedriver.Version
|
||||||
|
Error *responseError
|
||||||
|
}
|
||||||
|
|
||||||
// ReadStreamResponse is a response for a ReadStream request
|
// ReadStreamResponse is a response for a ReadStream request
|
||||||
type ReadStreamResponse struct {
|
type ReadStreamResponse struct {
|
||||||
Reader io.ReadCloser
|
Reader io.ReadCloser
|
||||||
|
|
|
@ -61,6 +61,11 @@ func receive(driver storagedriver.StorageDriver, receiver libchan.Receiver) {
|
||||||
// Responds to requests using the Request.ResponseChannel
|
// Responds to requests using the Request.ResponseChannel
|
||||||
func handleRequest(driver storagedriver.StorageDriver, request Request) {
|
func handleRequest(driver storagedriver.StorageDriver, request Request) {
|
||||||
switch request.Type {
|
switch request.Type {
|
||||||
|
case "Version":
|
||||||
|
err := request.ResponseChannel.Send(&VersionResponse{Version: storagedriver.CurrentVersion})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
case "GetContent":
|
case "GetContent":
|
||||||
path, _ := request.Parameters["Path"].(string)
|
path, _ := request.Parameters["Path"].(string)
|
||||||
content, err := driver.GetContent(path)
|
content, err := driver.GetContent(path)
|
||||||
|
|
|
@ -3,8 +3,32 @@ package storagedriver
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Version is a string representing the storage driver version, of the form Major.Minor.
|
||||||
|
// The registry must accept storage drivers with equal major version and greater minor version,
|
||||||
|
// but may not be compatible with older storage driver versions.
|
||||||
|
type Version string
|
||||||
|
|
||||||
|
// Major returns the major (primary) component of a version
|
||||||
|
func (version Version) Major() uint {
|
||||||
|
majorPart := strings.Split(string(version), ".")[0]
|
||||||
|
major, _ := strconv.ParseUint(majorPart, 10, 0)
|
||||||
|
return uint(major)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minor returns the minor (secondary) component of a version
|
||||||
|
func (version Version) Minor() uint {
|
||||||
|
minorPart := strings.Split(string(version), ".")[1]
|
||||||
|
minor, _ := strconv.ParseUint(minorPart, 10, 0)
|
||||||
|
return uint(minor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentVersion is the current storage driver Version
|
||||||
|
const CurrentVersion Version = "0.1"
|
||||||
|
|
||||||
// StorageDriver defines methods that a Storage Driver must implement for a filesystem-like
|
// StorageDriver defines methods that a Storage Driver must implement for a filesystem-like
|
||||||
// key/value object storage
|
// key/value object storage
|
||||||
type StorageDriver interface {
|
type StorageDriver interface {
|
||||||
|
|
Loading…
Reference in a new issue