github.com/prebid/prebid-server/v2@v2.18.0/config/stored_requests.go (about) 1 package config 2 3 import ( 4 "fmt" 5 "strings" 6 "time" 7 8 "github.com/golang/glog" 9 ) 10 11 // DataType constants 12 type DataType string 13 14 const ( 15 RequestDataType DataType = "Request" 16 CategoryDataType DataType = "Category" 17 VideoDataType DataType = "Video" 18 AMPRequestDataType DataType = "AMP Request" 19 AccountDataType DataType = "Account" 20 ResponseDataType DataType = "Response" 21 ) 22 23 // Section returns the config section this type is defined in 24 func (dataType DataType) Section() string { 25 return map[DataType]string{ 26 RequestDataType: "stored_requests", 27 CategoryDataType: "categories", 28 VideoDataType: "stored_video_req", 29 AMPRequestDataType: "stored_amp_req", 30 AccountDataType: "accounts", 31 ResponseDataType: "stored_responses", 32 }[dataType] 33 } 34 35 // Section returns the config section 36 func (sr *StoredRequests) Section() string { 37 return sr.dataType.Section() 38 } 39 40 // DataType returns the DataType associated with this config 41 func (sr *StoredRequests) DataType() DataType { 42 return sr.dataType 43 } 44 45 // SetDataType sets the DataType on this config. Needed for tests. 46 func (sr *StoredRequests) SetDataType(dataType DataType) { 47 sr.dataType = dataType 48 } 49 50 // StoredRequests struct defines options for stored requests for each data type 51 // including some amp stored_requests options 52 type StoredRequests struct { 53 // dataType is a tag pushed from upstream indicating the type of object fetched here 54 dataType DataType 55 // Files should be used if Stored Requests should be loaded from the filesystem. 56 // Fetchers are in stored_requests/backends/file_system/fetcher.go 57 Files FileFetcherConfig `mapstructure:"filesystem"` 58 // Database configures Fetchers and EventProducers which read from a Database. 59 // Fetchers are in stored_requests/backends/db_fetcher/fetcher.go 60 // EventProducers are in stored_requests/events/database 61 Database DatabaseConfig `mapstructure:"database"` 62 // HTTP configures an instance of stored_requests/backends/http/http_fetcher.go. 63 // If non-nil, Stored Requests will be fetched from the endpoint described there. 64 HTTP HTTPFetcherConfig `mapstructure:"http"` 65 // InMemoryCache configures an instance of stored_requests/caches/memory/cache.go. 66 // If non-nil, Stored Requests will be saved in an in-memory cache. 67 InMemoryCache InMemoryCache `mapstructure:"in_memory_cache"` 68 // CacheEvents configures an instance of stored_requests/events/api/api.go. 69 // This is a sub-object containing the endpoint name to use for this API endpoint. 70 CacheEvents CacheEventsConfig `mapstructure:"cache_events"` 71 // HTTPEvents configures an instance of stored_requests/events/http/http.go. 72 // If non-nil, the server will use those endpoints to populate and update the cache. 73 HTTPEvents HTTPEventsConfig `mapstructure:"http_events"` 74 } 75 76 // HTTPEventsConfig configures stored_requests/events/http/http.go 77 type HTTPEventsConfig struct { 78 Endpoint string `mapstructure:"endpoint"` 79 RefreshRate int64 `mapstructure:"refresh_rate_seconds"` 80 Timeout int `mapstructure:"timeout_ms"` 81 AmpEndpoint string `mapstructure:"amp_endpoint"` 82 } 83 84 func (cfg HTTPEventsConfig) TimeoutDuration() time.Duration { 85 return time.Duration(cfg.Timeout) * time.Millisecond 86 } 87 88 func (cfg HTTPEventsConfig) RefreshRateDuration() time.Duration { 89 return time.Duration(cfg.RefreshRate) * time.Second 90 } 91 92 // CacheEventsConfig configured stored_requests/events/api/api.go 93 type CacheEventsConfig struct { 94 // Enabled should be true to enable the events api endpoint 95 Enabled bool `mapstructure:"enabled"` 96 // Endpoint is the url path exposed for this stored requests events api 97 Endpoint string `mapstructure:"endpoint"` 98 } 99 100 // FileFetcherConfig configures a stored_requests/backends/file_fetcher/fetcher.go 101 type FileFetcherConfig struct { 102 // Enabled should be true if Stored Requests should be loaded from the filesystem. 103 Enabled bool `mapstructure:"enabled"` 104 // Path to the directory this file fetcher gets data from. 105 Path string `mapstructure:"directorypath"` 106 } 107 108 // HTTPFetcherConfig configures a stored_requests/backends/http_fetcher/fetcher.go 109 type HTTPFetcherConfig struct { 110 Endpoint string `mapstructure:"endpoint"` 111 AmpEndpoint string `mapstructure:"amp_endpoint"` 112 } 113 114 // Migrate combined stored_requests+amp configuration to separate simple config sections 115 func resolvedStoredRequestsConfig(cfg *Configuration) { 116 sr := &cfg.StoredRequests 117 amp := &cfg.StoredRequestsAMP 118 119 sr.CacheEvents.Endpoint = "/storedrequests/openrtb2" // why is this here and not SetDefault ? 120 121 // Amp uses the same config but some fields get replaced by Amp* version of similar fields 122 cfg.StoredRequestsAMP = cfg.StoredRequests 123 amp.Database.FetcherQueries.QueryTemplate = sr.Database.FetcherQueries.AmpQueryTemplate 124 amp.Database.CacheInitialization.Query = sr.Database.CacheInitialization.AmpQuery 125 amp.Database.PollUpdates.Query = sr.Database.PollUpdates.AmpQuery 126 amp.HTTP.Endpoint = sr.HTTP.AmpEndpoint 127 amp.CacheEvents.Endpoint = "/storedrequests/amp" 128 amp.HTTPEvents.Endpoint = sr.HTTPEvents.AmpEndpoint 129 130 // Set data types for each section 131 cfg.StoredRequests.dataType = RequestDataType 132 cfg.StoredRequestsAMP.dataType = AMPRequestDataType 133 cfg.StoredVideo.dataType = VideoDataType 134 cfg.CategoryMapping.dataType = CategoryDataType 135 cfg.Accounts.dataType = AccountDataType 136 cfg.StoredResponses.dataType = ResponseDataType 137 } 138 139 func (cfg *StoredRequests) validate(errs []error) []error { 140 if cfg.DataType() == AccountDataType && cfg.Database.ConnectionInfo.Database != "" { 141 errs = append(errs, fmt.Errorf("%s.database: retrieving accounts via database not available, use accounts.files", cfg.Section())) 142 } else { 143 errs = cfg.Database.validate(cfg.DataType(), errs) 144 } 145 146 // Categories do not use cache so none of the following checks apply 147 if cfg.DataType() == CategoryDataType { 148 return errs 149 } 150 151 if cfg.InMemoryCache.Type == "none" { 152 if cfg.CacheEvents.Enabled { 153 errs = append(errs, fmt.Errorf("%s: cache_events must be disabled if in_memory_cache=none", cfg.Section())) 154 } 155 156 if cfg.HTTPEvents.RefreshRate != 0 { 157 errs = append(errs, fmt.Errorf("%s: http_events.refresh_rate_seconds must be 0 if in_memory_cache=none", cfg.Section())) 158 } 159 160 if cfg.Database.PollUpdates.Query != "" { 161 errs = append(errs, fmt.Errorf("%s: database.poll_for_updates.query must be empty if in_memory_cache=none", cfg.Section())) 162 } 163 if cfg.Database.CacheInitialization.Query != "" { 164 errs = append(errs, fmt.Errorf("%s: database.initialize_caches.query must be empty if in_memory_cache=none", cfg.Section())) 165 } 166 } 167 errs = cfg.InMemoryCache.validate(cfg.DataType(), errs) 168 return errs 169 } 170 171 // DatabaseConfig configures the Stored Request ecosystem to use Database. This must include a Fetcher, 172 // and may optionally include some EventProducers to populate and refresh the caches. 173 type DatabaseConfig struct { 174 ConnectionInfo DatabaseConnection `mapstructure:"connection"` 175 FetcherQueries DatabaseFetcherQueries `mapstructure:"fetcher"` 176 CacheInitialization DatabaseCacheInitializer `mapstructure:"initialize_caches"` 177 PollUpdates DatabaseUpdatePolling `mapstructure:"poll_for_updates"` 178 } 179 180 func (cfg *DatabaseConfig) validate(dataType DataType, errs []error) []error { 181 if cfg.ConnectionInfo.Database == "" { 182 return errs 183 } 184 185 errs = cfg.CacheInitialization.validate(dataType, errs) 186 errs = cfg.PollUpdates.validate(dataType, errs) 187 return errs 188 } 189 190 // DatabaseConnection has options which put types to the Database Connection string. See: 191 // https://godoc.org/github.com/lib/pq#hdr-Connection_String_Parameters 192 type DatabaseConnection struct { 193 Driver string `mapstructure:"driver"` 194 Database string `mapstructure:"dbname"` 195 Host string `mapstructure:"host"` 196 Port int `mapstructure:"port"` 197 Username string `mapstructure:"user"` 198 Password string `mapstructure:"password"` 199 QueryString string `mapstructure:"query_string"` 200 TLS TLS `mapstructure:"tls"` 201 } 202 203 type TLS struct { 204 RootCert string `mapstructure:"root_cert"` 205 ClientCert string `mapstructure:"client_cert"` 206 ClientKey string `mapstructure:"client_key"` 207 } 208 209 type DatabaseFetcherQueries struct { 210 // QueryTemplate is the Database Query which can be used to fetch configs from the database. 211 // It is a Template, rather than a full Query, because a single HTTP request may reference multiple Stored Requests. 212 // 213 // In the simplest case, this could be something like: 214 // SELECT id, requestData, 'request' as type 215 // FROM stored_requests 216 // WHERE id in $REQUEST_ID_LIST 217 // UNION ALL 218 // SELECT id, impData, 'imp' as type 219 // FROM stored_imps 220 // WHERE id in $IMP_ID_LIST 221 // 222 // The MakeQuery function will transform this query into: 223 // SELECT id, requestData, 'request' as type 224 // FROM stored_requests 225 // WHERE id in ($1) 226 // UNION ALL 227 // SELECT id, impData, 'imp' as type 228 // FROM stored_imps 229 // WHERE id in ($2, $3, $4, ...) 230 // 231 // ... where the number of "$x" args depends on how many IDs are nested within the HTTP request. 232 QueryTemplate string `mapstructure:"query"` 233 234 // AmpQueryTemplate is the same as QueryTemplate, but used in the `/openrtb2/amp` endpoint. 235 AmpQueryTemplate string `mapstructure:"amp_query"` 236 } 237 238 type DatabaseCacheInitializer struct { 239 Timeout int `mapstructure:"timeout_ms"` 240 // Query should be something like: 241 // 242 // SELECT id, requestData, 'request' AS type FROM stored_requests 243 // UNION ALL 244 // SELECT id, impData, 'imp' AS type FROM stored_imps 245 // 246 // This query will be run once on startup to fetch _all_ known Stored Request data from the database. 247 // 248 // For more details on the expected format of requestData and impData, see stored_requests/events/database/database.go 249 Query string `mapstructure:"query"` 250 // AmpQuery is just like Query, but for AMP Stored Requests 251 AmpQuery string `mapstructure:"amp_query"` 252 } 253 254 func (cfg *DatabaseCacheInitializer) validate(dataType DataType, errs []error) []error { 255 section := dataType.Section() 256 if cfg.Query == "" { 257 return errs 258 } 259 if cfg.Timeout <= 0 { 260 errs = append(errs, fmt.Errorf("%s: database.initialize_caches.timeout_ms must be positive", section)) 261 } 262 if strings.Contains(cfg.Query, "$") { 263 errs = append(errs, fmt.Errorf("%s: database.initialize_caches.query should not contain any wildcards denoted by $ (e.g. $LAST_UPDATED)", section)) 264 } 265 return errs 266 } 267 268 type DatabaseUpdatePolling struct { 269 // RefreshRate determines how frequently the Query and AmpQuery are run. 270 RefreshRate int `mapstructure:"refresh_rate_seconds"` 271 272 // Timeout is the amount of time before a call to the database is aborted. 273 Timeout int `mapstructure:"timeout_ms"` 274 275 // An example UpdateQuery is: 276 // 277 // SELECT id, requestData, 'request' AS type 278 // FROM stored_requests 279 // WHERE last_updated > $LAST_UPDATED 280 // UNION ALL 281 // SELECT id, impData, 'imp' AS type 282 // FROM stored_imps 283 // WHERE last_updated > $LAST_UPDATED 284 // 285 // The code will be run periodically to fetch updates from the database. 286 Query string `mapstructure:"query"` 287 // AmpQuery is the same as Query, but used for the `/openrtb2/amp` endpoint. 288 AmpQuery string `mapstructure:"amp_query"` 289 } 290 291 func (cfg *DatabaseUpdatePolling) validate(dataType DataType, errs []error) []error { 292 section := dataType.Section() 293 if cfg.Query == "" { 294 return errs 295 } 296 297 if cfg.RefreshRate <= 0 { 298 errs = append(errs, fmt.Errorf("%s: database.poll_for_updates.refresh_rate_seconds must be > 0", section)) 299 } 300 301 if cfg.Timeout <= 0 { 302 errs = append(errs, fmt.Errorf("%s: database.poll_for_updates.timeout_ms must be > 0", section)) 303 } 304 if !strings.Contains(cfg.Query, "$LAST_UPDATED") { 305 errs = append(errs, fmt.Errorf("%s: database.poll_for_updates.query must contain $LAST_UPDATED parameter", section)) 306 } 307 308 return errs 309 } 310 311 type InMemoryCache struct { 312 // Identify the type of memory cache. "none", "unbounded", "lru" 313 Type string `mapstructure:"type"` 314 // TTL is the maximum number of seconds that an unused value will stay in the cache. 315 // TTL <= 0 can be used for "no ttl". Elements will still be evicted based on the Size. 316 TTL int `mapstructure:"ttl_seconds"` 317 // Size is the max total cache size allowed for single caches 318 Size int `mapstructure:"size_bytes"` 319 // RequestCacheSize is the max number of bytes allowed in the cache for Stored Requests. Values <= 0 will have no limit 320 RequestCacheSize int `mapstructure:"request_cache_size_bytes"` 321 // ImpCacheSize is the max number of bytes allowed in the cache for Stored Imps. Values <= 0 will have no limit 322 ImpCacheSize int `mapstructure:"imp_cache_size_bytes"` 323 // ResponsesCacheSize is the max number of bytes allowed in the cache for Stored Responses. Values <= 0 will have no limit 324 RespCacheSize int `mapstructure:"resp_cache_size_bytes"` 325 } 326 327 func (cfg *InMemoryCache) validate(dataType DataType, errs []error) []error { 328 section := dataType.Section() 329 switch cfg.Type { 330 case "none": 331 // No errors for no config options 332 case "unbounded": 333 if cfg.TTL != 0 { 334 errs = append(errs, fmt.Errorf("%s: in_memory_cache.ttl_seconds is not supported for unbounded caches. Got %d", section, cfg.TTL)) 335 } 336 if dataType == AccountDataType { 337 // single cache 338 if cfg.Size != 0 { 339 errs = append(errs, fmt.Errorf("%s: in_memory_cache.size_bytes is not supported for unbounded caches. Got %d", section, cfg.Size)) 340 } 341 } else { 342 // dual (request and imp) caches 343 if cfg.RequestCacheSize != 0 { 344 errs = append(errs, fmt.Errorf("%s: in_memory_cache.request_cache_size_bytes is not supported for unbounded caches. Got %d", section, cfg.RequestCacheSize)) 345 } 346 if cfg.ImpCacheSize != 0 { 347 errs = append(errs, fmt.Errorf("%s: in_memory_cache.imp_cache_size_bytes is not supported for unbounded caches. Got %d", section, cfg.ImpCacheSize)) 348 } 349 if cfg.RespCacheSize != 0 { 350 errs = append(errs, fmt.Errorf("%s: in_memory_cache.resp_cache_size_bytes is not supported for unbounded caches. Got %d", section, cfg.RespCacheSize)) 351 } 352 } 353 case "lru": 354 if dataType == AccountDataType { 355 // single cache 356 if cfg.Size <= 0 { 357 errs = append(errs, fmt.Errorf("%s: in_memory_cache.size_bytes must be >= 0 when in_memory_cache.type=lru. Got %d", section, cfg.Size)) 358 } 359 if cfg.RequestCacheSize > 0 || cfg.ImpCacheSize > 0 || cfg.RespCacheSize > 0 { 360 glog.Warningf("%s: in_memory_cache.request_cache_size_bytes, imp_cache_size_bytes and resp_cache_size_bytes do not apply to this section and will be ignored", section) 361 } 362 } else { 363 // dual (request and imp) caches 364 if cfg.RequestCacheSize <= 0 { 365 errs = append(errs, fmt.Errorf("%s: in_memory_cache.request_cache_size_bytes must be >= 0 when in_memory_cache.type=lru. Got %d", section, cfg.RequestCacheSize)) 366 } 367 if cfg.ImpCacheSize <= 0 { 368 errs = append(errs, fmt.Errorf("%s: in_memory_cache.imp_cache_size_bytes must be >= 0 when in_memory_cache.type=lru. Got %d", section, cfg.ImpCacheSize)) 369 } 370 if cfg.RespCacheSize <= 0 { 371 errs = append(errs, fmt.Errorf("%s: in_memory_cache.resp_cache_size_bytes must be >= 0 when in_memory_cache.type=lru. Got %d", section, cfg.RespCacheSize)) 372 } 373 if cfg.Size > 0 { 374 glog.Warningf("%s: in_memory_cache.size_bytes does not apply in this section and will be ignored", section) 375 } 376 } 377 default: 378 errs = append(errs, fmt.Errorf("%s: in_memory_cache.type %s is invalid", section, cfg.Type)) 379 } 380 return errs 381 }