commit
						642075f42c
					
				| 
						 | 
					@ -100,6 +100,17 @@ storage:
 | 
				
			||||||
  gcs:
 | 
					  gcs:
 | 
				
			||||||
    bucket: bucketname
 | 
					    bucket: bucketname
 | 
				
			||||||
    keyfile: /path/to/keyfile
 | 
					    keyfile: /path/to/keyfile
 | 
				
			||||||
 | 
					    credentials:
 | 
				
			||||||
 | 
					      type: service_account
 | 
				
			||||||
 | 
					      project_id: project_id_string
 | 
				
			||||||
 | 
					      private_key_id: private_key_id_string
 | 
				
			||||||
 | 
					      private_key: private_key_string
 | 
				
			||||||
 | 
					      client_email: client@example.com
 | 
				
			||||||
 | 
					      client_id: client_id_string
 | 
				
			||||||
 | 
					      auth_uri: http://example.com/auth_uri
 | 
				
			||||||
 | 
					      token_uri: http://example.com/token_uri
 | 
				
			||||||
 | 
					      auth_provider_x509_cert_url: http://example.com/provider_cert_url
 | 
				
			||||||
 | 
					      client_x509_cert_url: http://example.com/client_cert_url
 | 
				
			||||||
    rootdirectory: /gcs/object/name/prefix
 | 
					    rootdirectory: /gcs/object/name/prefix
 | 
				
			||||||
    chunksize: 5242880
 | 
					    chunksize: 5242880
 | 
				
			||||||
  s3:
 | 
					  s3:
 | 
				
			||||||
| 
						 | 
					@ -389,6 +400,17 @@ storage:
 | 
				
			||||||
  gcs:
 | 
					  gcs:
 | 
				
			||||||
    bucket: bucketname
 | 
					    bucket: bucketname
 | 
				
			||||||
    keyfile: /path/to/keyfile
 | 
					    keyfile: /path/to/keyfile
 | 
				
			||||||
 | 
					    credentials:
 | 
				
			||||||
 | 
					      type: service_account
 | 
				
			||||||
 | 
					      project_id: project_id_string
 | 
				
			||||||
 | 
					      private_key_id: private_key_id_string
 | 
				
			||||||
 | 
					      private_key: private_key_string
 | 
				
			||||||
 | 
					      client_email: client@example.com
 | 
				
			||||||
 | 
					      client_id: client_id_string
 | 
				
			||||||
 | 
					      auth_uri: http://example.com/auth_uri
 | 
				
			||||||
 | 
					      token_uri: http://example.com/token_uri
 | 
				
			||||||
 | 
					      auth_provider_x509_cert_url: http://example.com/provider_cert_url
 | 
				
			||||||
 | 
					      client_x509_cert_url: http://example.com/client_cert_url
 | 
				
			||||||
    rootdirectory: /gcs/object/name/prefix
 | 
					    rootdirectory: /gcs/object/name/prefix
 | 
				
			||||||
  s3:
 | 
					  s3:
 | 
				
			||||||
    accesskey: awsaccesskey
 | 
					    accesskey: awsaccesskey
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,10 @@ package base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	storagedriver "github.com/docker/distribution/registry/storage/driver"
 | 
						storagedriver "github.com/docker/distribution/registry/storage/driver"
 | 
				
			||||||
| 
						 | 
					@ -15,6 +18,46 @@ type regulator struct {
 | 
				
			||||||
	available uint64
 | 
						available uint64
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetLimitFromParameter takes an interface type as decoded from the YAML
 | 
				
			||||||
 | 
					// configuration and returns a uint64 representing the maximum number of
 | 
				
			||||||
 | 
					// concurrent calls given a minimum limit and default.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// If the parameter supplied is of an invalid type this returns an error.
 | 
				
			||||||
 | 
					func GetLimitFromParameter(param interface{}, min, def uint64) (uint64, error) {
 | 
				
			||||||
 | 
						limit := def
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch v := param.(type) {
 | 
				
			||||||
 | 
						case string:
 | 
				
			||||||
 | 
							var err error
 | 
				
			||||||
 | 
							if limit, err = strconv.ParseUint(v, 0, 64); err != nil {
 | 
				
			||||||
 | 
								return limit, fmt.Errorf("parameter must be an integer, '%v' invalid", param)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case uint64:
 | 
				
			||||||
 | 
							limit = v
 | 
				
			||||||
 | 
						case int, int32, int64:
 | 
				
			||||||
 | 
							val := reflect.ValueOf(v).Convert(reflect.TypeOf(param)).Int()
 | 
				
			||||||
 | 
							// if param is negative casting to uint64 will wrap around and
 | 
				
			||||||
 | 
							// give you the hugest thread limit ever. Let's be sensible, here
 | 
				
			||||||
 | 
							if val > 0 {
 | 
				
			||||||
 | 
								limit = uint64(val)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								limit = min
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case uint, uint32:
 | 
				
			||||||
 | 
							limit = reflect.ValueOf(v).Convert(reflect.TypeOf(param)).Uint()
 | 
				
			||||||
 | 
						case nil:
 | 
				
			||||||
 | 
							// use the default
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return 0, fmt.Errorf("invalid value '%#v'", param)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if limit < min {
 | 
				
			||||||
 | 
							return min, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return limit, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewRegulator wraps the given driver and is used to regulate concurrent calls
 | 
					// NewRegulator wraps the given driver and is used to regulate concurrent calls
 | 
				
			||||||
// to the given storage driver to a maximum of the given limit. This is useful
 | 
					// to the given storage driver to a maximum of the given limit. This is useful
 | 
				
			||||||
// for storage drivers that would otherwise create an unbounded number of OS
 | 
					// for storage drivers that would otherwise create an unbounded number of OS
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
package base
 | 
					package base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
| 
						 | 
					@ -65,3 +66,33 @@ func TestRegulatorEnterExit(t *testing.T) {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestGetLimitFromParameter(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							Input    interface{}
 | 
				
			||||||
 | 
							Expected uint64
 | 
				
			||||||
 | 
							Min      uint64
 | 
				
			||||||
 | 
							Default  uint64
 | 
				
			||||||
 | 
							Err      error
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{"foo", 0, 5, 5, fmt.Errorf("parameter must be an integer, 'foo' invalid")},
 | 
				
			||||||
 | 
							{"50", 50, 5, 5, nil},
 | 
				
			||||||
 | 
							{"5", 25, 25, 50, nil}, // lower than Min returns Min
 | 
				
			||||||
 | 
							{nil, 50, 25, 50, nil}, // nil returns default
 | 
				
			||||||
 | 
							{812, 812, 25, 50, nil},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, item := range tests {
 | 
				
			||||||
 | 
							t.Run(fmt.Sprint(item.Input), func(t *testing.T) {
 | 
				
			||||||
 | 
								actual, err := GetLimitFromParameter(item.Input, item.Min, item.Default)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err != nil && item.Err != nil && err.Error() != item.Err.Error() {
 | 
				
			||||||
 | 
									t.Fatalf("GetLimitFromParameter error, expected %#v got %#v", item.Err, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if actual != item.Expected {
 | 
				
			||||||
 | 
									t.Fatalf("GetLimitFromParameter result error, expected %d got %d", item.Expected, actual)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,8 +9,6 @@ import (
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
	"reflect"
 | 
					 | 
				
			||||||
	"strconv"
 | 
					 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	storagedriver "github.com/docker/distribution/registry/storage/driver"
 | 
						storagedriver "github.com/docker/distribution/registry/storage/driver"
 | 
				
			||||||
| 
						 | 
					@ -85,33 +83,9 @@ func fromParametersImpl(parameters map[string]interface{}) (*DriverParameters, e
 | 
				
			||||||
			rootDirectory = fmt.Sprint(rootDir)
 | 
								rootDirectory = fmt.Sprint(rootDir)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Get maximum number of threads for blocking filesystem operations,
 | 
							maxThreads, err = base.GetLimitFromParameter(parameters["maxthreads"], minThreads, defaultMaxThreads)
 | 
				
			||||||
		// if specified
 | 
							if err != nil {
 | 
				
			||||||
		threads := parameters["maxthreads"]
 | 
								return nil, fmt.Errorf("maxthreads config error: %s", err.Error())
 | 
				
			||||||
		switch v := threads.(type) {
 | 
					 | 
				
			||||||
		case string:
 | 
					 | 
				
			||||||
			if maxThreads, err = strconv.ParseUint(v, 0, 64); err != nil {
 | 
					 | 
				
			||||||
				return nil, fmt.Errorf("maxthreads parameter must be an integer, %v invalid", threads)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		case uint64:
 | 
					 | 
				
			||||||
			maxThreads = v
 | 
					 | 
				
			||||||
		case int, int32, int64:
 | 
					 | 
				
			||||||
			val := reflect.ValueOf(v).Convert(reflect.TypeOf(threads)).Int()
 | 
					 | 
				
			||||||
			// If threads is negative casting to uint64 will wrap around and
 | 
					 | 
				
			||||||
			// give you the hugest thread limit ever. Let's be sensible, here
 | 
					 | 
				
			||||||
			if val > 0 {
 | 
					 | 
				
			||||||
				maxThreads = uint64(val)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		case uint, uint32:
 | 
					 | 
				
			||||||
			maxThreads = reflect.ValueOf(v).Convert(reflect.TypeOf(threads)).Uint()
 | 
					 | 
				
			||||||
		case nil:
 | 
					 | 
				
			||||||
			// do nothing
 | 
					 | 
				
			||||||
		default:
 | 
					 | 
				
			||||||
			return nil, fmt.Errorf("invalid value for maxthreads: %#v", threads)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if maxThreads < minThreads {
 | 
					 | 
				
			||||||
			maxThreads = minThreads
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,7 @@ package gcs
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
| 
						 | 
					@ -49,6 +50,8 @@ const (
 | 
				
			||||||
	uploadSessionContentType = "application/x-docker-upload-session"
 | 
						uploadSessionContentType = "application/x-docker-upload-session"
 | 
				
			||||||
	minChunkSize             = 256 * 1024
 | 
						minChunkSize             = 256 * 1024
 | 
				
			||||||
	defaultChunkSize         = 20 * minChunkSize
 | 
						defaultChunkSize         = 20 * minChunkSize
 | 
				
			||||||
 | 
						defaultMaxConcurrency    = 50
 | 
				
			||||||
 | 
						minConcurrency           = 25
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	maxTries = 5
 | 
						maxTries = 5
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -64,6 +67,12 @@ type driverParameters struct {
 | 
				
			||||||
	client        *http.Client
 | 
						client        *http.Client
 | 
				
			||||||
	rootDirectory string
 | 
						rootDirectory string
 | 
				
			||||||
	chunkSize     int
 | 
						chunkSize     int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// maxConcurrency limits the number of concurrent driver operations
 | 
				
			||||||
 | 
						// to GCS, which ultimately increases reliability of many simultaneous
 | 
				
			||||||
 | 
						// pushes by ensuring we aren't DoSing our own server with many
 | 
				
			||||||
 | 
						// connections.
 | 
				
			||||||
 | 
						maxConcurrency uint64
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
| 
						 | 
					@ -89,6 +98,16 @@ type driver struct {
 | 
				
			||||||
	chunkSize     int
 | 
						chunkSize     int
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Wrapper wraps `driver` with a throttler, ensuring that no more than N
 | 
				
			||||||
 | 
					// GCS actions can occur concurrently. The default limit is 75.
 | 
				
			||||||
 | 
					type Wrapper struct {
 | 
				
			||||||
 | 
						baseEmbed
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type baseEmbed struct {
 | 
				
			||||||
 | 
						base.Base
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// FromParameters constructs a new Driver with a given parameters map
 | 
					// FromParameters constructs a new Driver with a given parameters map
 | 
				
			||||||
// Required parameters:
 | 
					// Required parameters:
 | 
				
			||||||
// - bucket
 | 
					// - bucket
 | 
				
			||||||
| 
						 | 
					@ -140,6 +159,31 @@ func FromParameters(parameters map[string]interface{}) (storagedriver.StorageDri
 | 
				
			||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		ts = jwtConf.TokenSource(context.Background())
 | 
							ts = jwtConf.TokenSource(context.Background())
 | 
				
			||||||
 | 
						} else if credentials, ok := parameters["credentials"]; ok {
 | 
				
			||||||
 | 
							credentialMap, ok := credentials.(map[interface{}]interface{})
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("The credentials were not specified in the correct format")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							stringMap := map[string]interface{}{}
 | 
				
			||||||
 | 
							for k, v := range credentialMap {
 | 
				
			||||||
 | 
								key, ok := k.(string)
 | 
				
			||||||
 | 
								if !ok {
 | 
				
			||||||
 | 
									return nil, fmt.Errorf("One of the credential keys was not a string: %s", fmt.Sprint(k))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								stringMap[key] = v
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							data, err := json.Marshal(stringMap)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("Failed to marshal gcs credentials to json")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							jwtConf, err = google.JWTConfigFromJSON(data, storage.ScopeFullControl)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ts = jwtConf.TokenSource(context.Background())
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		var err error
 | 
							var err error
 | 
				
			||||||
		ts, err = google.DefaultTokenSource(context.Background(), storage.ScopeFullControl)
 | 
							ts, err = google.DefaultTokenSource(context.Background(), storage.ScopeFullControl)
 | 
				
			||||||
| 
						 | 
					@ -148,6 +192,11 @@ func FromParameters(parameters map[string]interface{}) (storagedriver.StorageDri
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						maxConcurrency, err := base.GetLimitFromParameter(parameters["maxconcurrency"], minConcurrency, defaultMaxConcurrency)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("maxconcurrency config error: %s", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	params := driverParameters{
 | 
						params := driverParameters{
 | 
				
			||||||
		bucket:         fmt.Sprint(bucket),
 | 
							bucket:         fmt.Sprint(bucket),
 | 
				
			||||||
		rootDirectory:  fmt.Sprint(rootDirectory),
 | 
							rootDirectory:  fmt.Sprint(rootDirectory),
 | 
				
			||||||
| 
						 | 
					@ -155,6 +204,7 @@ func FromParameters(parameters map[string]interface{}) (storagedriver.StorageDri
 | 
				
			||||||
		privateKey:     jwtConf.PrivateKey,
 | 
							privateKey:     jwtConf.PrivateKey,
 | 
				
			||||||
		client:         oauth2.NewClient(context.Background(), ts),
 | 
							client:         oauth2.NewClient(context.Background(), ts),
 | 
				
			||||||
		chunkSize:      chunkSize,
 | 
							chunkSize:      chunkSize,
 | 
				
			||||||
 | 
							maxConcurrency: maxConcurrency,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return New(params)
 | 
						return New(params)
 | 
				
			||||||
| 
						 | 
					@ -178,8 +228,12 @@ func New(params driverParameters) (storagedriver.StorageDriver, error) {
 | 
				
			||||||
		chunkSize:     params.chunkSize,
 | 
							chunkSize:     params.chunkSize,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return &base.Base{
 | 
						return &Wrapper{
 | 
				
			||||||
		StorageDriver: d,
 | 
							baseEmbed: baseEmbed{
 | 
				
			||||||
 | 
								Base: base.Base{
 | 
				
			||||||
 | 
									StorageDriver: base.NewRegulator(d, params.maxConcurrency),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	}, nil
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -864,7 +918,7 @@ func (d *driver) context(context context.Context) context.Context {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (d *driver) pathToKey(path string) string {
 | 
					func (d *driver) pathToKey(path string) string {
 | 
				
			||||||
	return strings.TrimRight(d.rootDirectory+strings.TrimLeft(path, "/"), "/")
 | 
						return strings.TrimSpace(strings.TrimRight(d.rootDirectory+strings.TrimLeft(path, "/"), "/"))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (d *driver) pathToDirKey(path string) string {
 | 
					func (d *driver) pathToDirKey(path string) string {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue