Add defaults for cache headers, and add tests to ensure that default configs can successfully be parsed

Signed-off-by: Ying Li <ying.li@docker.com>
This commit is contained in:
Ying Li 2016-03-16 10:58:06 -07:00
parent 66a39cb30c
commit f1d78f8d6e
4 changed files with 157 additions and 94 deletions

View File

@ -7,10 +7,10 @@ import (
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/health"
_ "github.com/docker/distribution/registry/auth/htpasswd"
_ "github.com/docker/distribution/registry/auth/token"
"github.com/docker/go-connections/tlsconfig"
"github.com/docker/notary"
"github.com/docker/notary/server/storage"
"github.com/docker/notary/signer/client"
"github.com/docker/notary/tuf/data"
@ -59,7 +59,7 @@ func grpcTLS(configuration *viper.Viper) (*tls.Config, error) {
}
// parses the configuration and returns a backing store for the TUF files
func getStore(configuration *viper.Viper, allowedBackends []string) (
func getStore(configuration *viper.Viper, allowedBackends []string, hRegister healthRegister) (
storage.MetaStore, error) {
storeConfig, err := utils.ParseStorage(configuration, allowedBackends)
@ -76,7 +76,7 @@ func getStore(configuration *viper.Viper, allowedBackends []string) (
if err != nil {
return nil, fmt.Errorf("Error starting DB driver: %s", err.Error())
}
health.RegisterPeriodicFunc(
hRegister(
"DB operational", store.CheckHealth, time.Second*60)
return store, nil
}
@ -135,29 +135,36 @@ func getTrustService(configuration *viper.Viper, sFactory signerFactory,
return notarySigner, keyAlgo, nil
}
// Gets the cache configuration for GET-ting current and checksummed metadata
// This is mainly the max-age (an integer in seconds, just like in the
// Cache-Control header) for consistent (content-addressable) downloads and
// current (latest version) downloads. The max-age must be between 0 and 31536000
// (one year in seconds, which is the recommended maximum time data is cached),
// else parsing will return an error. A max-age of 0 will disable caching for
// that type of download (consistent or current).
func getCacheConfig(configuration *viper.Viper) (utils.CacheControlConfig, utils.CacheControlConfig, error) {
var cccs []utils.CacheControlConfig
types := []string{"current_metadata", "metadata_by_checksum"}
// Parse the cache configurations for GET-ting current and checksummed metadata,
// returning the configuration for current (non-content-addressed) metadata
// first, then the configuration for consistent (content-addressed) metadata
// second. The configuration consists mainly of the max-age (an integer in seconds,
// just like in the Cache-Control header) for each type of metadata.
// The max-age must be between 0 and 31536000 (one year in seconds, which is
// the recommended maximum time data is cached), else parsing will return an error.
// A max-age of 0 will disable caching for that type of download (consistent or current).
func getCacheConfig(configuration *viper.Viper) (current, consistent utils.CacheControlConfig, err error) {
cccs := make(map[string]utils.CacheControlConfig)
currentOpt, consistentOpt := "current_metadata", "consistent_metadata"
for _, optionName := range types {
m := configuration.GetString(fmt.Sprintf("caching.max_age.%s", optionName))
if m == "" {
continue
}
seconds, err := strconv.Atoi(m)
if err != nil || seconds < 0 || seconds > maxMaxAge {
return nil, nil, fmt.Errorf(
"must specify a cache-control max-age between 0 and %v", maxMaxAge)
}
cccs = append(cccs, utils.NewCacheControlConfig(seconds, optionName == "current_metadata"))
defaults := map[string]int{
currentOpt: int(notary.CurrentMetadataCacheMaxAge.Seconds()),
consistentOpt: int(notary.ConsistentMetadataCacheMaxAge.Seconds()),
}
return cccs[0], cccs[1], nil
maxMaxAge := int(notary.CacheMaxAgeLimit.Seconds())
for optionName, seconds := range defaults {
m := configuration.GetString(fmt.Sprintf("caching.max_age.%s", optionName))
if m != "" {
seconds, err = strconv.Atoi(m)
if err != nil || seconds < 0 || seconds > maxMaxAge {
return nil, nil, fmt.Errorf(
"must specify a cache-control max-age between 0 and %v", maxMaxAge)
}
}
cccs[optionName] = utils.NewCacheControlConfig(seconds, optionName == currentOpt)
}
current = cccs[currentOpt]
consistent = cccs[consistentOpt]
return
}

View File

@ -23,10 +23,6 @@ import (
const (
jsonLogFormat = "json"
DebugAddress = "localhost:8080"
// This is the generally recommended maximum age for Cache-Control headers
// (one year, in seconds, since one year is forever in terms of internet
// content)
maxMaxAge = 60 * 60 * 24 * 365
)
var (
@ -34,11 +30,9 @@ var (
logFormat string
configFile string
envPrefix = "NOTARY_SERVER"
mainViper = viper.New()
)
func init() {
utils.SetupViper(mainViper, envPrefix)
// Setup flags
flag.StringVar(&configFile, "config", "", "Path to configuration file")
flag.BoolVar(&debug, "debug", false, "Enable the debugging server on localhost:8080")
@ -50,6 +44,64 @@ func init() {
}
}
func parseServerConfig(configFilePath string, hRegister healthRegister) (context.Context, server.Config, error) {
config := viper.New()
utils.SetupViper(config, envPrefix)
// parse viper config
if err := utils.ParseViper(config, configFilePath); err != nil {
return nil, server.Config{}, err
}
ctx := context.Background()
// default is error level
lvl, err := utils.ParseLogLevel(config, logrus.ErrorLevel)
if err != nil {
return nil, server.Config{}, err
}
logrus.SetLevel(lvl)
// parse bugsnag config
bugsnagConf, err := utils.ParseBugsnag(config)
if err != nil {
return ctx, server.Config{}, err
}
utils.SetUpBugsnag(bugsnagConf)
trust, keyAlgo, err := getTrustService(config, client.NewNotarySigner, hRegister)
if err != nil {
return nil, server.Config{}, err
}
ctx = context.WithValue(ctx, "keyAlgorithm", keyAlgo)
store, err := getStore(config, []string{utils.MySQLBackend, utils.MemoryBackend}, hRegister)
if err != nil {
return nil, server.Config{}, err
}
ctx = context.WithValue(ctx, "metaStore", store)
currentCache, consistentCache, err := getCacheConfig(config)
if err != nil {
return nil, server.Config{}, err
}
httpAddr, tlsConfig, err := getAddrAndTLSConfig(config)
if err != nil {
return nil, server.Config{}, err
}
return ctx, server.Config{
Addr: httpAddr,
TLSConfig: tlsConfig,
Trust: trust,
AuthMethod: config.GetString("auth.type"),
AuthOpts: config.Get("auth.options"),
CurrentCacheControlConfig: currentCache,
ConsistentCacheControlConfig: consistentCache,
}, nil
}
func main() {
flag.Usage = usage
flag.Parse()
@ -61,63 +113,13 @@ func main() {
// when the server starts print the version for debugging and issue logs later
logrus.Infof("Version: %s, Git commit: %s", version.NotaryVersion, version.GitCommit)
ctx := context.Background()
// parse viper config
if err := utils.ParseViper(mainViper, configFile); err != nil {
logrus.Fatal(err.Error())
}
// default is error level
lvl, err := utils.ParseLogLevel(mainViper, logrus.ErrorLevel)
if err != nil {
logrus.Fatal(err.Error())
}
logrus.SetLevel(lvl)
// parse bugsnag config
bugsnagConf, err := utils.ParseBugsnag(mainViper)
if err != nil {
logrus.Fatal(err.Error())
}
utils.SetUpBugsnag(bugsnagConf)
trust, keyAlgo, err := getTrustService(mainViper,
client.NewNotarySigner, health.RegisterPeriodicFunc)
if err != nil {
logrus.Fatal(err.Error())
}
ctx = context.WithValue(ctx, "keyAlgorithm", keyAlgo)
store, err := getStore(mainViper, []string{utils.MySQLBackend, utils.MemoryBackend})
if err != nil {
logrus.Fatal(err.Error())
}
ctx = context.WithValue(ctx, "metaStore", store)
currentCache, consistentCache, err := getCacheConfig(mainViper)
if err != nil {
logrus.Fatal(err.Error())
}
httpAddr, tlsConfig, err := getAddrAndTLSConfig(mainViper)
ctx, serverConfig, err := parseServerConfig(configFile, health.RegisterPeriodicFunc)
if err != nil {
logrus.Fatal(err.Error())
}
logrus.Info("Starting Server")
err = server.Run(
ctx,
server.Config{
Addr: httpAddr,
TLSConfig: tlsConfig,
Trust: trust,
AuthMethod: mainViper.GetString("auth.type"),
AuthOpts: mainViper.Get("auth.options"),
CurrentCacheControlConfig: currentCache,
ConsistentCacheControlConfig: consistentCache,
},
)
err = server.Run(ctx, serverConfig)
logrus.Error(err.Error())
return

View File

@ -11,6 +11,7 @@ import (
"testing"
"time"
"github.com/docker/notary"
"github.com/docker/notary/server/storage"
"github.com/docker/notary/signer/client"
"github.com/docker/notary/tuf/data"
@ -301,8 +302,16 @@ func TestGetTrustServiceTLSFailure(t *testing.T) {
func TestGetStoreInvalid(t *testing.T) {
config := `{"storage": {"backend": "asdf", "db_url": "/tmp/1234"}}`
_, err := getStore(configure(config), []string{"mysql"})
var registerCalled = 0
var fakeRegister = func(_ string, _ func() error, _ time.Duration) {
registerCalled++
}
_, err := getStore(configure(config), []string{"mysql"}, fakeRegister)
assert.Error(t, err)
// no health function ever registered
assert.Equal(t, 0, registerCalled)
}
func TestGetStoreDBStore(t *testing.T) {
@ -314,36 +323,74 @@ func TestGetStoreDBStore(t *testing.T) {
config := fmt.Sprintf(`{"storage": {"backend": "%s", "db_url": "%s"}}`,
utils.SqliteBackend, tmpFile.Name())
store, err := getStore(configure(config), []string{utils.SqliteBackend})
var registerCalled = 0
var fakeRegister = func(_ string, _ func() error, _ time.Duration) {
registerCalled++
}
store, err := getStore(configure(config), []string{utils.SqliteBackend}, fakeRegister)
assert.NoError(t, err)
_, ok := store.(*storage.SQLStorage)
assert.True(t, ok)
// health function registered
assert.Equal(t, 1, registerCalled)
}
func TestGetMemoryStore(t *testing.T) {
var registerCalled = 0
var fakeRegister = func(_ string, _ func() error, _ time.Duration) {
registerCalled++
}
config := fmt.Sprintf(`{"storage": {"backend": "%s"}}`, utils.MemoryBackend)
store, err := getStore(configure(config),
[]string{utils.MySQLBackend, utils.MemoryBackend})
[]string{utils.MySQLBackend, utils.MemoryBackend}, fakeRegister)
assert.NoError(t, err)
_, ok := store.(*storage.MemStorage)
assert.True(t, ok)
// no health function ever registered
assert.Equal(t, 0, registerCalled)
}
func TestGetCacheConfig(t *testing.T) {
valid := `{"caching": {"max_age": {"current_metadata": 0, "metadata_by_checksum": 31536000}}}`
defaults := `{}`
valid := `{"caching": {"max_age": {"current_metadata": 0, "consistent_metadata": 31536000}}}`
invalids := []string{
`{"caching": {"max_age": {"current_metadata": 0, "metadata_by_checksum": 31539000}}}`,
`{"caching": {"max_age": {"current_metadata": -1, "metadata_by_checksum": 300}}}`,
`{"caching": {"max_age": {"current_metadata": "hello", "metadata_by_checksum": 300}}}`,
`{"caching": {"max_age": {"current_metadata": 0, "consistent_metadata": 31539000}}}`,
`{"caching": {"max_age": {"current_metadata": -1, "consistent_metadata": 300}}}`,
`{"caching": {"max_age": {"current_metadata": "hello", "consistent_metadata": 300}}}`,
}
current, consistent, err := getCacheConfig(configure(valid))
current, consistent, err := getCacheConfig(configure(defaults))
assert.NoError(t, err)
assert.IsType(t, utils.NoCacheControl{}, current)
assert.IsType(t, utils.PublicCacheControl{}, consistent)
assert.Equal(t,
utils.PublicCacheControl{MaxAgeInSeconds: int(notary.CurrentMetadataCacheMaxAge.Seconds()),
MustReValidate: true}, current)
assert.Equal(t,
utils.PublicCacheControl{MaxAgeInSeconds: int(notary.ConsistentMetadataCacheMaxAge.Seconds())}, consistent)
current, consistent, err = getCacheConfig(configure(valid))
assert.NoError(t, err)
assert.Equal(t, utils.NoCacheControl{}, current)
assert.Equal(t, utils.PublicCacheControl{MaxAgeInSeconds: 31536000}, consistent)
for _, invalid := range invalids {
_, _, err := getCacheConfig(configure(invalid))
assert.Error(t, err)
}
}
// For sanity, make sure we can always parse the sample config
func TestSampleConfig(t *testing.T) {
var registerCalled = 0
var fakeRegister = func(_ string, _ func() error, _ time.Duration) {
registerCalled++
}
_, _, err := parseServerConfig("../../fixtures/server-config.json", fakeRegister)
assert.NoError(t, err)
// once for the DB, once for the trust service
assert.Equal(t, registerCalled, 2)
}

View File

@ -42,6 +42,13 @@ const (
NotaryTargetsExpiry = 3 * Year
NotarySnapshotExpiry = 3 * Year
NotaryTimestampExpiry = 14 * Day
ConsistentMetadataCacheMaxAge = 30 * Day
CurrentMetadataCacheMaxAge = 5 * time.Minute
// CacheMaxAgeLimit is the generally recommended maximum age for Cache-Control headers
// (one year, in seconds, since one year is forever in terms of internet
// content)
CacheMaxAgeLimit = 1 * Year
)
// NotaryDefaultExpiries is the construct used to configure the default expiry times of