diff --git a/main/storagedriver/filesystem/filesystem.go b/main/storagedriver/filesystem/filesystem.go index 8c0e2677..8a5fc93c 100644 --- a/main/storagedriver/filesystem/filesystem.go +++ b/main/storagedriver/filesystem/filesystem.go @@ -8,19 +8,14 @@ import ( "github.com/docker/docker-registry/storagedriver/ipc" ) +// An out-of-process filesystem driver, intended to be run by ipc.NewDriverClient func main() { parametersBytes := []byte(os.Args[1]) - var parameters map[string]interface{} + var parameters map[string]string err := json.Unmarshal(parametersBytes, ¶meters) if err != nil { panic(err) } - rootDirectory := "/tmp/registry" - if parameters != nil { - rootDirParam, ok := parameters["RootDirectory"].(string) - if ok && rootDirParam != "" { - rootDirectory = rootDirParam - } - } - ipc.Server(filesystem.NewDriver(rootDirectory)) + + ipc.StorageDriverServer(filesystem.FromParameters(parameters)) } diff --git a/main/storagedriver/inmemory/inmemory.go b/main/storagedriver/inmemory/inmemory.go index f55c8d5f..999c05d7 100644 --- a/main/storagedriver/inmemory/inmemory.go +++ b/main/storagedriver/inmemory/inmemory.go @@ -5,6 +5,8 @@ import ( "github.com/docker/docker-registry/storagedriver/ipc" ) +// An out-of-process inmemory driver, intended to be run by ipc.NewDriverClient +// This exists primarily for example and testing purposes func main() { - ipc.Server(inmemory.NewDriver()) + ipc.StorageDriverServer(inmemory.New()) } diff --git a/main/storagedriver/s3/s3.go b/main/storagedriver/s3/s3.go index 0fbc376c..aa5a1180 100644 --- a/main/storagedriver/s3/s3.go +++ b/main/storagedriver/s3/s3.go @@ -3,55 +3,24 @@ package main import ( "encoding/json" "os" - "strconv" - "github.com/crowdmob/goamz/aws" "github.com/docker/docker-registry/storagedriver/ipc" "github.com/docker/docker-registry/storagedriver/s3" ) +// An out-of-process S3 driver, intended to be run by ipc.NewDriverClient func main() { parametersBytes := []byte(os.Args[1]) - var parameters map[string]interface{} + var parameters map[string]string err := json.Unmarshal(parametersBytes, ¶meters) if err != nil { panic(err) } - accessKey, ok := parameters["accessKey"].(string) - if !ok || accessKey == "" { - panic("No accessKey parameter") - } - - secretKey, ok := parameters["secretKey"].(string) - if !ok || secretKey == "" { - panic("No secretKey parameter") - } - - region, ok := parameters["region"].(string) - if !ok || region == "" { - panic("No region parameter") - } - - bucket, ok := parameters["bucket"].(string) - if !ok || bucket == "" { - panic("No bucket parameter") - } - - encrypt, ok := parameters["encrypt"].(string) - if !ok { - panic("No encrypt parameter") - } - - encryptBool, err := strconv.ParseBool(encrypt) + driver, err := s3.FromParameters(parameters) if err != nil { panic(err) } - driver, err := s3.NewDriver(accessKey, secretKey, aws.GetRegion(region), encryptBool, bucket) - if err != nil { - panic(err) - } - - ipc.Server(driver) + ipc.StorageDriverServer(driver) } diff --git a/storagedriver/factory/factory.go b/storagedriver/factory/factory.go new file mode 100644 index 00000000..c13c6c1e --- /dev/null +++ b/storagedriver/factory/factory.go @@ -0,0 +1,64 @@ +package factory + +import ( + "fmt" + + "github.com/docker/docker-registry/storagedriver" + "github.com/docker/docker-registry/storagedriver/ipc" +) + +// Internal mapping between storage driver names and their respective factories +var driverFactories = make(map[string]StorageDriverFactory) + +// Factory interface for the storagedriver.StorageDriver interface +// Storage drivers should call Register() with a factory to make the driver available by name +type StorageDriverFactory interface { + // Creates and returns a new storagedriver.StorageDriver with the given parameters + // Parameters will vary by driver and may be ignored + // Each parameter key must only consist of lowercase letters and numbers + Create(parameters map[string]string) (storagedriver.StorageDriver, error) +} + +// Register makes a storage driver available by the provided name. +// If Register is called twice with the same name or if driver factory is nil, it panics. +func Register(name string, factory StorageDriverFactory) { + if factory == nil { + panic("Must not provide nil StorageDriverFactory") + } + _, registered := driverFactories[name] + if registered { + panic(fmt.Sprintf("StorageDriverFactory named %s already registered", name)) + } + + driverFactories[name] = factory +} + +// Create a new storagedriver.StorageDriver with the given name and parameters +// To run in-process, the StorageDriverFactory must first be registered with the given name +// If no in-process drivers are found with the given name, this attempts to create an IPC driver +// If no in-process or external drivers are found, an InvalidStorageDriverError is returned +func Create(name string, parameters map[string]string) (storagedriver.StorageDriver, error) { + driverFactory, ok := driverFactories[name] + if !ok { + // No registered StorageDriverFactory found, try ipc + driverClient, err := ipc.NewDriverClient(name, parameters) + if err != nil { + return nil, InvalidStorageDriverError{name} + } + err = driverClient.Start() + if err != nil { + return nil, err + } + return driverClient, nil + } + return driverFactory.Create(parameters) +} + +// Error returned when attempting to construct an unregistered storage driver +type InvalidStorageDriverError struct { + Name string +} + +func (err InvalidStorageDriverError) Error() string { + return fmt.Sprintf("StorageDriver not registered: %s", err.Name) +} diff --git a/storagedriver/filesystem/filesystem.go b/storagedriver/filesystem/filesystem.go index 79106e37..27ffcf7a 100644 --- a/storagedriver/filesystem/filesystem.go +++ b/storagedriver/filesystem/filesystem.go @@ -8,13 +8,45 @@ import ( "strings" "github.com/docker/docker-registry/storagedriver" + "github.com/docker/docker-registry/storagedriver/factory" ) +const DriverName = "filesystem" +const DefaultRootDirectory = "/tmp/registry/storage" + +func init() { + factory.Register(DriverName, &filesystemDriverFactory{}) +} + +// Implements the factory.StorageDriverFactory interface +type filesystemDriverFactory struct{} + +func (factory *filesystemDriverFactory) Create(parameters map[string]string) (storagedriver.StorageDriver, error) { + return FromParameters(parameters), nil +} + +// Storage Driver backed by a local filesystem +// All provided paths will be subpaths of the RootDirectory type FilesystemDriver struct { rootDirectory string } -func NewDriver(rootDirectory string) *FilesystemDriver { +// Constructs a new FilesystemDriver with a given parameters map +// Optional Parameters: +// - rootdirectory +func FromParameters(parameters map[string]string) *FilesystemDriver { + var rootDirectory = DefaultRootDirectory + if parameters != nil { + rootDir, ok := parameters["rootdirectory"] + if ok { + rootDirectory = rootDir + } + } + return New(rootDirectory) +} + +// Constructs a new FilesystemDriver with a given rootDirectory +func New(rootDirectory string) *FilesystemDriver { return &FilesystemDriver{rootDirectory} } @@ -22,6 +54,8 @@ func (d *FilesystemDriver) subPath(subPath string) string { return path.Join(d.rootDirectory, subPath) } +// Implement the storagedriver.StorageDriver interface + func (d *FilesystemDriver) GetContent(path string) ([]byte, error) { contents, err := ioutil.ReadFile(d.subPath(path)) if err != nil { diff --git a/storagedriver/filesystem/filesystem_test.go b/storagedriver/filesystem/filesystem_test.go index 15ef9663..7eb4024c 100644 --- a/storagedriver/filesystem/filesystem_test.go +++ b/storagedriver/filesystem/filesystem_test.go @@ -17,8 +17,8 @@ func init() { os.RemoveAll(rootDirectory) filesystemDriverConstructor := func() (storagedriver.StorageDriver, error) { - return NewDriver(rootDirectory), nil + return New(rootDirectory), nil } testsuites.RegisterInProcessSuite(filesystemDriverConstructor, testsuites.NeverSkip) - testsuites.RegisterIPCSuite("filesystem", map[string]string{"RootDirectory": rootDirectory}, testsuites.NeverSkip) + testsuites.RegisterIPCSuite(DriverName, map[string]string{"rootdirectory": rootDirectory}, testsuites.NeverSkip) } diff --git a/storagedriver/inmemory/inmemory.go b/storagedriver/inmemory/inmemory.go index ea44bb39..2cf1b9f4 100644 --- a/storagedriver/inmemory/inmemory.go +++ b/storagedriver/inmemory/inmemory.go @@ -10,14 +10,31 @@ import ( "sync" "github.com/docker/docker-registry/storagedriver" + "github.com/docker/docker-registry/storagedriver/factory" ) +const DriverName = "inmemory" + +func init() { + factory.Register(DriverName, &inMemoryDriverFactory{}) +} + +// Implements the factory.StorageDriverFactory interface +type inMemoryDriverFactory struct{} + +func (factory *inMemoryDriverFactory) Create(parameters map[string]string) (storagedriver.StorageDriver, error) { + return New(), nil +} + +// InMemory Storage Driver backed by a map +// Intended solely for example and testing purposes type InMemoryDriver struct { storage map[string][]byte mutex sync.RWMutex } -func NewDriver() *InMemoryDriver { +// Constructs a new InMemoryDriver +func New() *InMemoryDriver { return &InMemoryDriver{storage: make(map[string][]byte)} } diff --git a/storagedriver/inmemory/inmemory_test.go b/storagedriver/inmemory/inmemory_test.go index accbb5f8..feea5eab 100644 --- a/storagedriver/inmemory/inmemory_test.go +++ b/storagedriver/inmemory/inmemory_test.go @@ -13,8 +13,8 @@ func Test(t *testing.T) { TestingT(t) } func init() { inmemoryDriverConstructor := func() (storagedriver.StorageDriver, error) { - return NewDriver(), nil + return New(), nil } testsuites.RegisterInProcessSuite(inmemoryDriverConstructor, testsuites.NeverSkip) - testsuites.RegisterIPCSuite("inmemory", nil, testsuites.NeverSkip) + testsuites.RegisterIPCSuite(DriverName, nil, testsuites.NeverSkip) } diff --git a/storagedriver/ipc/client.go b/storagedriver/ipc/client.go index 0025d2bc..6327b156 100644 --- a/storagedriver/ipc/client.go +++ b/storagedriver/ipc/client.go @@ -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() diff --git a/storagedriver/ipc/ipc.go b/storagedriver/ipc/ipc.go index 89b0cf20..4e7e65c7 100644 --- a/storagedriver/ipc/ipc.go +++ b/storagedriver/ipc/ipc.go @@ -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 } diff --git a/storagedriver/ipc/server.go b/storagedriver/ipc/server.go index 0d39a31b..0af41d0a 100644 --- a/storagedriver/ipc/server.go +++ b/storagedriver/ipc/server.go @@ -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{ diff --git a/storagedriver/s3/s3.go b/storagedriver/s3/s3.go index a73e5e3d..0c301126 100644 --- a/storagedriver/s3/s3.go +++ b/storagedriver/s3/s3.go @@ -2,6 +2,7 @@ package s3 import ( "bytes" + "fmt" "io" "net/http" "strconv" @@ -9,21 +10,82 @@ import ( "github.com/crowdmob/goamz/aws" "github.com/crowdmob/goamz/s3" "github.com/docker/docker-registry/storagedriver" + "github.com/docker/docker-registry/storagedriver/factory" ) -/* Chunks need to be at least 5MB to store with a multipart upload on S3 */ +const DriverName = "s3" + +// Chunks need to be at least 5MB to store with a multipart upload on S3 const minChunkSize = uint64(5 * 1024 * 1024) -/* The largest amount of parts you can request from S3 */ +// The largest amount of parts you can request from S3 const listPartsMax = 1000 +func init() { + factory.Register(DriverName, &s3DriverFactory{}) +} + +// Implements the factory.StorageDriverFactory interface +type s3DriverFactory struct{} + +func (factory *s3DriverFactory) Create(parameters map[string]string) (storagedriver.StorageDriver, error) { + return FromParameters(parameters) +} + +// Storage Driver backed by Amazon S3 +// Objects are stored at absolute keys in the provided bucket type S3Driver struct { S3 *s3.S3 Bucket *s3.Bucket Encrypt bool } -func NewDriver(accessKey string, secretKey string, region aws.Region, encrypt bool, bucketName string) (*S3Driver, error) { +// Constructs a new S3Driver with a given parameters map +// Required parameters: +// - accesskey +// - secretkey +// - region +// - bucket +// - encrypt +func FromParameters(parameters map[string]string) (*S3Driver, error) { + accessKey, ok := parameters["accesskey"] + if !ok || accessKey == "" { + return nil, fmt.Errorf("No accesskey parameter provided") + } + + secretKey, ok := parameters["secretkey"] + if !ok || secretKey == "" { + return nil, fmt.Errorf("No secretkey parameter provided") + } + + regionName, ok := parameters["region"] + if !ok || regionName == "" { + return nil, fmt.Errorf("No region parameter provided") + } + region := aws.GetRegion(regionName) + if region.Name == "" { + return nil, fmt.Errorf("Invalid region provided: %s", region) + } + + bucket, ok := parameters["bucket"] + if !ok || bucket == "" { + return nil, fmt.Errorf("No bucket parameter provided") + } + + encrypt, ok := parameters["encrypt"] + if !ok { + return nil, fmt.Errorf("No encrypt parameter provided") + } + + encryptBool, err := strconv.ParseBool(encrypt) + if err != nil { + return nil, fmt.Errorf("Unable to parse the encrypt parameter: %v", err) + } + return New(accessKey, secretKey, region, encryptBool, bucket) +} + +// Constructs a new S3Driver with the given AWS credentials, region, encryption flag, and bucketName +func New(accessKey string, secretKey string, region aws.Region, encrypt bool, bucketName string) (*S3Driver, error) { auth := aws.Auth{AccessKey: accessKey, SecretKey: secretKey} s3obj := s3.New(auth, region) bucket := s3obj.Bucket(bucketName) diff --git a/storagedriver/s3/s3_test.go b/storagedriver/s3/s3_test.go index b6862ab9..576c3623 100644 --- a/storagedriver/s3/s3_test.go +++ b/storagedriver/s3/s3_test.go @@ -2,6 +2,7 @@ package s3 import ( "os" + "strconv" "testing" "github.com/crowdmob/goamz/aws" @@ -14,23 +15,34 @@ import ( func Test(t *testing.T) { TestingT(t) } func init() { - accessKey := os.Getenv("ACCESS_KEY") - secretKey := os.Getenv("SECRET_KEY") + accessKey := os.Getenv("AWS_ACCESS_KEY") + secretKey := os.Getenv("AWS_SECRET_KEY") region := os.Getenv("AWS_REGION") bucket := os.Getenv("S3_BUCKET") encrypt := os.Getenv("S3_ENCRYPT") s3DriverConstructor := func() (storagedriver.StorageDriver, error) { - return NewDriver(accessKey, secretKey, aws.GetRegion(region), true, bucket) + shouldEncrypt, err := strconv.ParseBool(encrypt) + if err != nil { + return nil, err + } + return New(accessKey, secretKey, aws.GetRegion(region), shouldEncrypt, bucket) } + // Skip S3 storage driver tests if environment variable parameters are not provided skipCheck := func() string { if accessKey == "" || secretKey == "" || region == "" || bucket == "" || encrypt == "" { - return "Must set ACCESS_KEY, SECRET_KEY, AWS_REGION, S3_BUCKET, and S3_ENCRYPT to run S3 tests" + return "Must set AWS_ACCESS_KEY, AWS_SECRET_KEY, AWS_REGION, S3_BUCKET, and S3_ENCRYPT to run S3 tests" } return "" } testsuites.RegisterInProcessSuite(s3DriverConstructor, skipCheck) - testsuites.RegisterIPCSuite("s3", map[string]string{"accessKey": accessKey, "secretKey": secretKey, "region": region, "bucket": bucket, "encrypt": encrypt}, skipCheck) + testsuites.RegisterIPCSuite(DriverName, map[string]string{ + "accesskey": accessKey, + "secretkey": secretKey, + "region": region, + "bucket": bucket, + "encrypt": encrypt, + }, skipCheck) } diff --git a/storagedriver/storagedriver.go b/storagedriver/storagedriver.go index bfbfc110..55596cd6 100644 --- a/storagedriver/storagedriver.go +++ b/storagedriver/storagedriver.go @@ -5,17 +5,41 @@ import ( "io" ) +// Defines methods that a Storage Driver must implement for a filesystem-like key/value object storage type StorageDriver interface { + // Retrieve the content stored at "path" as a []byte + // Should primarily be used for small objects GetContent(path string) ([]byte, error) + + // Store the []byte content at a location designated by "path" + // Should primarily be used for small objects PutContent(path string, content []byte) error + + // Retrieve an io.ReadCloser for the content stored at "path" with a given byte offset + // May be used to resume reading a stream by providing a nonzero offset ReadStream(path string, offset uint64) (io.ReadCloser, error) + + // Store the contents of the provided io.ReadCloser at a location designated by "path" + // The driver will know it has received the full contents when it has read "size" bytes + // May be used to resume writing a stream by providing a nonzero offset + // The offset must be no larger than the number of bytes already written to this path WriteStream(path string, offset, size uint64, readCloser io.ReadCloser) error + + // Retrieve the byte offset at which it is safe to continue writing at "path" ResumeWritePosition(path string) (uint64, error) + + // Recursively lists the objects stored at a subpath of the given prefix List(prefix string) ([]string, error) + + // Moves an object stored at sourcePath to destPath, removing the original object + // Note: This may be no more efficient than a copy followed by a delete for many implementations Move(sourcePath string, destPath string) error + + // Recursively deletes all objects stored at "path" and its subpaths Delete(path string) error } +// Error returned when operating on a nonexistent path type PathNotFoundError struct { Path string } @@ -24,6 +48,7 @@ func (err PathNotFoundError) Error() string { return fmt.Sprintf("Path not found: %s", err.Path) } +// Error returned when attempting to read or write from an invalid offset type InvalidOffsetError struct { Path string Offset uint64 diff --git a/storagedriver/testsuites/testsuites.go b/storagedriver/testsuites/testsuites.go index ff93b038..45c621d3 100644 --- a/storagedriver/testsuites/testsuites.go +++ b/storagedriver/testsuites/testsuites.go @@ -17,6 +17,9 @@ import ( // Hook up gocheck into the "go test" runner func Test(t *testing.T) { TestingT(t) } +// Registers an in-process storage driver test suite with the go test runner +// +// If skipCheck returns a non-empty skip reason, the suite is skipped with the given reason func RegisterInProcessSuite(driverConstructor DriverConstructor, skipCheck SkipCheck) { Suite(&DriverSuite{ Constructor: driverConstructor, @@ -24,6 +27,9 @@ func RegisterInProcessSuite(driverConstructor DriverConstructor, skipCheck SkipC }) } +// Registers a storage driver test suite which runs the named driver as a child process with the given parameters +// +// If skipCheck returns a non-empty skip reason, the suite is skipped with the given reason func RegisterIPCSuite(driverName string, ipcParams map[string]string, skipCheck SkipCheck) { suite := &DriverSuite{ Constructor: func() (storagedriver.StorageDriver, error) {