| OLD | NEW |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. | 1 // Copyright 2016 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package gaeconfig | 5 package gaeconfig |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "errors" | 8 "errors" |
| 9 "fmt" | 9 "fmt" |
| 10 "net/url" | 10 "net/url" |
| 11 "strconv" | 11 "strconv" |
| 12 "time" | 12 "time" |
| 13 | 13 |
| 14 "golang.org/x/net/context" | 14 "golang.org/x/net/context" |
| 15 | 15 |
| 16 "github.com/luci/luci-go/server/settings" | 16 "github.com/luci/luci-go/server/settings" |
| 17 ) | 17 ) |
| 18 | 18 |
| 19 // DefaultExpire is a reasonable default expiration value. | 19 // DefaultExpire is a reasonable default expiration value. |
| 20 const DefaultExpire = 10 * time.Minute | 20 const DefaultExpire = 10 * time.Minute |
| 21 | 21 |
| 22 type dsCacheMode string |
| 23 |
| 24 const ( |
| 25 dsCacheDisabled dsCacheMode = "" |
| 26 dsCacheEnabled dsCacheMode = "Enabled" |
| 27 dsCacheStrict dsCacheMode = "Strict" |
| 28 ) |
| 29 |
| 30 const dsCacheDisabledSetting = "Disabled" |
| 31 |
| 22 // Settings are stored in the datastore via appengine/gaesettings package. | 32 // Settings are stored in the datastore via appengine/gaesettings package. |
| 23 type Settings struct { | 33 type Settings struct { |
| 24 // ConfigServiceURL is URL of luci-config service to fetch configs from. | 34 // ConfigServiceURL is URL of luci-config service to fetch configs from. |
| 25 ConfigServiceURL string `json:"config_service_url"` | 35 ConfigServiceURL string `json:"config_service_url"` |
| 26 | 36 |
| 27 // CacheExpirationSec is how long to hold configs in local cache. | 37 // CacheExpirationSec is how long to hold configs in local cache. |
| 28 CacheExpirationSec int `json:"cache_expiration_sec"` | 38 CacheExpirationSec int `json:"cache_expiration_sec"` |
| 39 |
| 40 // DatastoreCacheMode, is the datastore caching mode. |
| 41 DatastoreCacheMode dsCacheMode `json:"datastore_enabled"` |
| 29 } | 42 } |
| 30 | 43 |
| 31 // FetchCachedSettings fetches Settings from the settings store. | 44 // FetchCachedSettings fetches Settings from the settings store. |
| 32 // | 45 // |
| 33 // Uses in-process global cache to avoid hitting datastore often. The cache | 46 // Uses in-process global cache to avoid hitting datastore often. The cache |
| 34 // expiration time is 1 min (see gaesettings.expirationTime), meaning | 47 // expiration time is 1 min (see gaesettings.expirationTime), meaning |
| 35 // the instance will refetch settings once a minute (blocking only one unlucky | 48 // the instance will refetch settings once a minute (blocking only one unlucky |
| 36 // request to do so). | 49 // request to do so). |
| 37 // | 50 // |
| 38 // Returns errors only if there's no cached value (i.e. it is the first call | 51 // Returns errors only if there's no cached value (i.e. it is the first call |
| 39 // to this function in this process ever) and datastore operation fails. | 52 // to this function in this process ever) and datastore operation fails. |
| 40 func FetchCachedSettings(c context.Context) (Settings, error) { | 53 func FetchCachedSettings(c context.Context) (Settings, error) { |
| 41 s := Settings{} | 54 s := Settings{} |
| 42 switch err := settings.Get(c, settingsKey, &s); err { | 55 switch err := settings.Get(c, settingsKey, &s); err { |
| 43 case nil: | 56 case nil: |
| 44 return s, nil | 57 return s, nil |
| 45 case settings.ErrNoSettings: | 58 case settings.ErrNoSettings: |
| 46 return DefaultSettings(), nil | 59 return DefaultSettings(), nil |
| 47 default: | 60 default: |
| 48 return Settings{}, err | 61 return Settings{}, err |
| 49 } | 62 } |
| 50 } | 63 } |
| 51 | 64 |
| 65 func mustFetchCachedSettings(c context.Context) *Settings { |
| 66 settings, err := FetchCachedSettings(c) |
| 67 if err != nil { |
| 68 panic(err) |
| 69 } |
| 70 return &settings |
| 71 } |
| 72 |
| 52 // DefaultSettings returns Settings to use if setting store is empty. | 73 // DefaultSettings returns Settings to use if setting store is empty. |
| 53 func DefaultSettings() Settings { | 74 func DefaultSettings() Settings { |
| 54 return Settings{ | 75 return Settings{ |
| 55 CacheExpirationSec: int(DefaultExpire.Seconds()), | 76 CacheExpirationSec: int(DefaultExpire.Seconds()), |
| 77 DatastoreCacheMode: dsCacheDisabled, |
| 56 } | 78 } |
| 57 } | 79 } |
| 58 | 80 |
| 59 //////////////////////////////////////////////////////////////////////////////// | 81 //////////////////////////////////////////////////////////////////////////////// |
| 60 // UI for settings. | 82 // UI for settings. |
| 61 | 83 |
| 62 // settingsKey is used internally to identify gaeconfig settings in settings | 84 // settingsKey is used internally to identify gaeconfig settings in settings |
| 63 // store. | 85 // store. |
| 64 const settingsKey = "gaeconfig" | 86 const settingsKey = "gaeconfig" |
| 65 | 87 |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 104 Validator: func(v string) error { | 126 Validator: func(v string) error { |
| 105 if i, err := strconv.Atoi(v); err != nil || i <
0 { | 127 if i, err := strconv.Atoi(v); err != nil || i <
0 { |
| 106 return errors.New("expecting a non-negat
ive integer") | 128 return errors.New("expecting a non-negat
ive integer") |
| 107 } | 129 } |
| 108 return nil | 130 return nil |
| 109 }, | 131 }, |
| 110 Help: `<p>For better performance configuration files fet
ched from remote | 132 Help: `<p>For better performance configuration files fet
ched from remote |
| 111 service are cached in memcache for specified amount of time. Set it to 0 to | 133 service are cached in memcache for specified amount of time. Set it to 0 to |
| 112 disable local cache.</p>`, | 134 disable local cache.</p>`, |
| 113 }, | 135 }, |
| 136 { |
| 137 ID: "DatastoreCacheMode", |
| 138 Title: "Enable datastore-backed config caching", |
| 139 Type: settings.UIFieldChoice, |
| 140 ChoiceVariants: []string{ |
| 141 dsCacheDisabledSetting, |
| 142 string(dsCacheEnabled), |
| 143 string(dsCacheStrict), |
| 144 }, |
| 145 Help: `<p>For better performance and resilience against
configuration |
| 146 service outages, the local datastore can be used as a backing cache. When |
| 147 enabled, an additional <strong>Strict</strong> mode is available where expired |
| 148 datastore entries will error instead of fail-open to the config service.</p>`, |
| 149 }, |
| 114 }, nil | 150 }, nil |
| 115 } | 151 } |
| 116 | 152 |
| 117 func (settingsUIPage) ReadSettings(c context.Context) (map[string]string, error)
{ | 153 func (settingsUIPage) ReadSettings(c context.Context) (map[string]string, error)
{ |
| 118 s := DefaultSettings() | 154 s := DefaultSettings() |
| 119 err := settings.GetUncached(c, settingsKey, &s) | 155 err := settings.GetUncached(c, settingsKey, &s) |
| 120 if err != nil && err != settings.ErrNoSettings { | 156 if err != nil && err != settings.ErrNoSettings { |
| 121 return nil, err | 157 return nil, err |
| 122 } | 158 } |
| 159 |
| 160 cacheMode := string(s.DatastoreCacheMode) |
| 161 if cacheMode == string(dsCacheDisabled) { |
| 162 cacheMode = dsCacheDisabledSetting |
| 163 } |
| 164 |
| 123 return map[string]string{ | 165 return map[string]string{ |
| 124 "ConfigServiceURL": s.ConfigServiceURL, | 166 "ConfigServiceURL": s.ConfigServiceURL, |
| 125 "CacheExpirationSec": strconv.Itoa(s.CacheExpirationSec), | 167 "CacheExpirationSec": strconv.Itoa(s.CacheExpirationSec), |
| 168 "DatastoreCacheMode": cacheMode, |
| 126 }, nil | 169 }, nil |
| 127 } | 170 } |
| 128 | 171 |
| 129 func (settingsUIPage) WriteSettings(c context.Context, values map[string]string,
who, why string) error { | 172 func (settingsUIPage) WriteSettings(c context.Context, values map[string]string,
who, why string) error { |
| 130 » modified := Settings{} | 173 » dsMode := dsCacheMode(values["DatastoreCacheMode"]) |
| 131 » modified.ConfigServiceURL = values["ConfigServiceURL"] | 174 » if dsMode == dsCacheDisabledSetting { |
| 175 » » dsMode = dsCacheDisabled |
| 176 » } |
| 177 |
| 178 » modified := Settings{ |
| 179 » » ConfigServiceURL: values["ConfigServiceURL"], |
| 180 » » DatastoreCacheMode: dsMode, |
| 181 » } |
| 132 | 182 |
| 133 var err error | 183 var err error |
| 134 modified.CacheExpirationSec, err = strconv.Atoi(values["CacheExpirationS
ec"]) | 184 modified.CacheExpirationSec, err = strconv.Atoi(values["CacheExpirationS
ec"]) |
| 135 if err != nil { | 185 if err != nil { |
| 136 return err | 186 return err |
| 137 } | 187 } |
| 138 | 188 |
| 139 return settings.SetIfChanged(c, settingsKey, &modified, who, why) | 189 return settings.SetIfChanged(c, settingsKey, &modified, who, why) |
| 140 } | 190 } |
| 141 | 191 |
| 142 func init() { | 192 func init() { |
| 143 settings.RegisterUIPage(settingsKey, settingsUIPage{}) | 193 settings.RegisterUIPage(settingsKey, settingsUIPage{}) |
| 144 } | 194 } |
| OLD | NEW |