You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by sr...@apache.org on 2022/05/26 16:19:45 UTC
[trafficcontrol] branch master updated: Add TO option to cache server update status in memory (#6838)
This is an automated email from the ASF dual-hosted git repository.
srijeet0406 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git
The following commit(s) were added to refs/heads/master by this push:
new 31205d568c Add TO option to cache server update status in memory (#6838)
31205d568c is described below
commit 31205d568ce80a3dc8eeb18209f46814768e52f6
Author: Rawlin Peters <ra...@apache.org>
AuthorDate: Thu May 26 10:19:41 2022 -0600
Add TO option to cache server update status in memory (#6838)
* Add TO option to cache server update status in memory
To reduce load on the Traffic Ops database, add an option to
periodically read all server update status data from the database and
serve all requests to /servers/{hostname}/update_status from memory.
With this option enabled, `t3c` runs on cache servers that don't have
updates pending don't cause any TODB queries, which means TODB has more
capacity for actually propagating changes.
The tradeoff is that it can take up to
`server_update_status_cache_refresh_interval_sec` seconds for cache
servers to see updates/revalidations pending. However, this is better
because it allows a cache server to get updates sooner than it would
otherwise (due to being able to run `t3c` more frequently).
* Address review comments
* Add defaults to CIAB cdn.conf
---
CHANGELOG.md | 1 +
docs/source/admin/traffic_ops.rst | 6 +
.../ansible/roles/traffic_ops/defaults/main.yml | 1 +
.../roles/traffic_ops/templates/cdn.conf.j2 | 1 +
infrastructure/cdn-in-a-box/traffic_ops/config.sh | 2 +
lib/go-tc/enum.go | 5 +
traffic_ops/app/conf/cdn.conf | 1 +
traffic_ops/traffic_ops_golang/auth/usercache.go | 14 +-
traffic_ops/traffic_ops_golang/config/config.go | 58 ++---
.../traffic_ops_golang/config/config_test.go | 6 +-
.../server/servers_update_status.go | 260 ++++++++++++++++++++-
.../server/servers_update_status_test.go | 131 ++++++++++-
.../traffic_ops_golang/traffic_ops_golang.go | 14 +-
13 files changed, 452 insertions(+), 48 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8ec525f362..532c07e57c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
- Traffic Monitor config option `distributed_polling` which enables the ability for Traffic Monitor to poll a subset of the CDN and divide into "local peer groups" and "distributed peer groups". Traffic Monitors in the same group are local peers, while Traffic Monitors in other groups are distibuted peers. Each TM group polls the same set of cachegroups and gets availability data for the other cachegroups from other TM groups. This allows each TM to be responsible for polling a subset of [...]
- Added support for a new Traffic Ops GLOBAL profile parameter -- `tm_query_status_override` -- to override which status of Traffic Monitors to query (default: ONLINE).
- Traffic Ops: added new `cdn.conf` option -- `user_cache_refresh_interval_sec` -- which enables an in-memory users cache to improve performance. Default: 0 (disabled).
+- Traffic Ops: added new `cdn.conf` option -- `server_update_status_cache_refresh_interval_sec` -- which enables an in-memory server update status cache to improve performance. Default: 0 (disabled).
- Traffic Router: Add support for `file`-protocol URLs for the `geolocation.polling.url` for the Geolocation database.
- Replaces all Traffic Portal Tenant select boxes with a novel tree select box [#6427](https://github.com/apache/trafficcontrol/issues/6427).
- Traffic Monitor: Add support for `access.log` to TM.
diff --git a/docs/source/admin/traffic_ops.rst b/docs/source/admin/traffic_ops.rst
index 6ea3cbf479..0a3c28c66c 100644
--- a/docs/source/admin/traffic_ops.rst
+++ b/docs/source/admin/traffic_ops.rst
@@ -511,6 +511,12 @@ This file deals with the configuration parameters of running Traffic Ops itself.
.. versionadded:: 7.0
+:server_update_status_cache_refresh_interval_sec: This optional integer value specifies the interval (in seconds) between refreshing the in-memory server update status cache. Default: 0 (disabled).
+
+ .. warning:: Enabling the server update status cache improves performance by reducing the number of queries made to the Traffic Ops database, but it means that it may take up to this many seconds before any server updates or revalidations are reflected in the :ref:`to-api-servers-hostname-update_status` API.
+
+ .. versionadded:: 7.0
+
Example cdn.conf
''''''''''''''''
diff --git a/infrastructure/ansible/roles/traffic_ops/defaults/main.yml b/infrastructure/ansible/roles/traffic_ops/defaults/main.yml
index bec28e6916..c368069358 100644
--- a/infrastructure/ansible/roles/traffic_ops/defaults/main.yml
+++ b/infrastructure/ansible/roles/traffic_ops/defaults/main.yml
@@ -45,6 +45,7 @@ to_disable_auto_cert_deletion: false
to_use_ims: true
to_use_rbp: true
to_user_cache_refresh_interval_sec: 0
+to_server_update_status_cache_refresh_interval_sec: 0
to_heartbeat_timeout: 20
to_hypnotoad_number_of_workers: 12
to_cors_access_control_allow_origin: "http://localhost:8080"
diff --git a/infrastructure/ansible/roles/traffic_ops/templates/cdn.conf.j2 b/infrastructure/ansible/roles/traffic_ops/templates/cdn.conf.j2
index 4244b7fcc9..5dfeaccfca 100644
--- a/infrastructure/ansible/roles/traffic_ops/templates/cdn.conf.j2
+++ b/infrastructure/ansible/roles/traffic_ops/templates/cdn.conf.j2
@@ -50,6 +50,7 @@
"address" : "{{ to_smtp_address }}"
},
"user_cache_refresh_interval_sec": {{ to_user_cache_refresh_interval_sec }},
+ "server_update_status_cache_refresh_interval_sec": {{ to_server_update_status_cache_refresh_interval_sec }},
"disable_auto_cert_deletion": {{ to_disable_auto_cert_deletion | bool | lower }},
"use_ims": {{ to_use_ims | bool | lower }},
"role_based_permissions": {{ to_use_rbp | bool | lower }},
diff --git a/infrastructure/cdn-in-a-box/traffic_ops/config.sh b/infrastructure/cdn-in-a-box/traffic_ops/config.sh
index d31393fd96..c44525a839 100755
--- a/infrastructure/cdn-in-a-box/traffic_ops/config.sh
+++ b/infrastructure/cdn-in-a-box/traffic_ops/config.sh
@@ -81,6 +81,8 @@ cdn_conf=/opt/traffic_ops/app/conf/cdn.conf
},
"disable_auto_cert_deletion": false,
"use_ims": true,
+ "server_update_status_cache_refresh_interval_sec": 0,
+ "user_cache_refresh_interval_sec": 0,
"role_based_permissions": true,
"traffic_ops_golang" : {
"traffic_vault_backend": "$TV_BACKEND",
diff --git a/lib/go-tc/enum.go b/lib/go-tc/enum.go
index 8d100fa683..453270e751 100644
--- a/lib/go-tc/enum.go
+++ b/lib/go-tc/enum.go
@@ -107,6 +107,11 @@ func CacheTypeFromString(s string) CacheType {
return CacheTypeInvalid
}
+// IsValidCacheType returns true if the given string represents a valid cache type.
+func IsValidCacheType(s string) bool {
+ return CacheTypeFromString(s) != CacheTypeInvalid
+}
+
// InterfaceName is the name of a server interface.
type InterfaceName string
diff --git a/traffic_ops/app/conf/cdn.conf b/traffic_ops/app/conf/cdn.conf
index eb3ce5778e..9eceeefb35 100644
--- a/traffic_ops/app/conf/cdn.conf
+++ b/traffic_ops/app/conf/cdn.conf
@@ -45,6 +45,7 @@
},
"disable_auto_cert_deletion": false,
"user_cache_refresh_interval_sec": 0,
+ "server_update_status_cache_refresh_interval_sec": 0,
"use_ims": false,
"role_based_permissions": true,
"cors" : {
diff --git a/traffic_ops/traffic_ops_golang/auth/usercache.go b/traffic_ops/traffic_ops_golang/auth/usercache.go
index 95520bbbb9..1cfebef9ab 100644
--- a/traffic_ops/traffic_ops_golang/auth/usercache.go
+++ b/traffic_ops/traffic_ops_golang/auth/usercache.go
@@ -127,17 +127,17 @@ func startUsersCacheRefresher(interval time.Duration, db *sql.DB, timeout time.D
}
func refreshUsersCache(db *sql.DB, timeout time.Duration) {
- usersCache.Lock()
- defer usersCache.Unlock()
newUsers, err := getUsers(db, timeout)
if err != nil {
log.Errorf("refreshing users cache: %s", err.Error())
- } else {
- usersCache.userMap = newUsers
- usersCache.usernamesByToken = createTokenToUsernameMap(newUsers)
- usersCache.initialized = true
- log.Infof("refreshed users cache (len = %d)", len(usersCache.userMap))
+ return
}
+ usersCache.Lock()
+ defer usersCache.Unlock()
+ usersCache.userMap = newUsers
+ usersCache.usernamesByToken = createTokenToUsernameMap(newUsers)
+ usersCache.initialized = true
+ log.Infof("refreshed users cache (len = %d)", len(usersCache.userMap))
}
func createTokenToUsernameMap(users map[string]user) map[string]string {
diff --git a/traffic_ops/traffic_ops_golang/config/config.go b/traffic_ops/traffic_ops_golang/config/config.go
index 521da1d9bd..37cffbe7c2 100644
--- a/traffic_ops/traffic_ops_golang/config/config.go
+++ b/traffic_ops/traffic_ops_golang/config/config.go
@@ -67,33 +67,34 @@ type BackendConfig struct {
// Config reflects the structure of the cdn.conf file
type Config struct {
- URL *url.URL `json:"-"`
- CertPath string `json:"-"`
- KeyPath string `json:"-"`
- ConfigHypnotoad `json:"hypnotoad"`
- ConfigTrafficOpsGolang `json:"traffic_ops_golang"`
- ConfigTO *ConfigTO `json:"to"`
- SMTP *ConfigSMTP `json:"smtp"`
- ConfigPortal `json:"portal"`
- ConfigLetsEncrypt `json:"lets_encrypt"`
- ConfigAcmeRenewal `json:"acme_renewal"`
- AcmeAccounts []ConfigAcmeAccount `json:"acme_accounts"`
- DB ConfigDatabase `json:"db"`
- Secrets []string `json:"secrets"`
- TrafficVaultEnabled bool
- ConfigLDAP *ConfigLDAP
- UserCacheRefreshIntervalSec int `json:"user_cache_refresh_interval_sec"`
- LDAPEnabled bool
- LDAPConfPath string `json:"ldap_conf_location"`
- ConfigInflux *ConfigInflux
- InfluxEnabled bool
- InfluxDBConfPath string `json:"influxdb_conf_path"`
- Version string
- DisableAutoCertDeletion bool `json:"disable_auto_cert_deletion"`
- UseIMS bool `json:"use_ims"`
- RoleBasedPermissions bool `json:"role_based_permissions"`
- DefaultCertificateInfo *DefaultCertificateInfo `json:"default_certificate_info"`
- Cdni *CdniConf `json:"cdni"`
+ URL *url.URL `json:"-"`
+ CertPath string `json:"-"`
+ KeyPath string `json:"-"`
+ ConfigHypnotoad `json:"hypnotoad"`
+ ConfigTrafficOpsGolang `json:"traffic_ops_golang"`
+ ConfigTO *ConfigTO `json:"to"`
+ SMTP *ConfigSMTP `json:"smtp"`
+ ConfigPortal `json:"portal"`
+ ConfigLetsEncrypt `json:"lets_encrypt"`
+ ConfigAcmeRenewal `json:"acme_renewal"`
+ AcmeAccounts []ConfigAcmeAccount `json:"acme_accounts"`
+ DB ConfigDatabase `json:"db"`
+ Secrets []string `json:"secrets"`
+ TrafficVaultEnabled bool
+ ConfigLDAP *ConfigLDAP
+ UserCacheRefreshIntervalSec int `json:"user_cache_refresh_interval_sec"`
+ ServerUpdateStatusCacheRefreshIntervalSec int `json:"server_update_status_cache_refresh_interval_sec"`
+ LDAPEnabled bool
+ LDAPConfPath string `json:"ldap_conf_location"`
+ ConfigInflux *ConfigInflux
+ InfluxEnabled bool
+ InfluxDBConfPath string `json:"influxdb_conf_path"`
+ Version string
+ DisableAutoCertDeletion bool `json:"disable_auto_cert_deletion"`
+ UseIMS bool `json:"use_ims"`
+ RoleBasedPermissions bool `json:"role_based_permissions"`
+ DefaultCertificateInfo *DefaultCertificateInfo `json:"default_certificate_info"`
+ Cdni *CdniConf `json:"cdni"`
}
// ConfigHypnotoad carries http setting for hypnotoad (mojolicious) server
@@ -490,6 +491,9 @@ func ParseConfig(cfg Config) (Config, error) {
if cfg.UserCacheRefreshIntervalSec < 0 {
cfg.UserCacheRefreshIntervalSec = 0
}
+ if cfg.ServerUpdateStatusCacheRefreshIntervalSec < 0 {
+ cfg.ServerUpdateStatusCacheRefreshIntervalSec = 0
+ }
invalidTOURLStr := ""
var err error
diff --git a/traffic_ops/traffic_ops_golang/config/config_test.go b/traffic_ops/traffic_ops_golang/config/config_test.go
index 70c03d1f41..ee01de555b 100644
--- a/traffic_ops/traffic_ops_golang/config/config_test.go
+++ b/traffic_ops/traffic_ops_golang/config/config_test.go
@@ -99,6 +99,7 @@ const (
"workers" : 12
},
"user_cache_refresh_interval_sec": 30,
+ "server_update_status_cache_refresh_interval_sec": 15,
"disable_auto_cert_deletion": true,
"traffic_ops_golang" : {
"port" : "443",
@@ -239,7 +240,10 @@ func TestLoadConfig(t *testing.T) {
t.Errorf("expected traffic_vault_backend to be 'something', actual: '%s'", cfg.TrafficVaultBackend)
}
if cfg.UserCacheRefreshIntervalSec != 30 {
- t.Errorf("expected user_refresh_interval_sec: 30, actual: %d", cfg.UserCacheRefreshIntervalSec)
+ t.Errorf("expected user_cache_refresh_interval_sec: 30, actual: %d", cfg.UserCacheRefreshIntervalSec)
+ }
+ if cfg.ServerUpdateStatusCacheRefreshIntervalSec != 15 {
+ t.Errorf("expected server_update_status_cache_refresh_interval_sec: 15, actual: %d", cfg.ServerUpdateStatusCacheRefreshIntervalSec)
}
tvConfig := make(map[string]string)
err = json.Unmarshal(cfg.TrafficVaultConfig, &tvConfig)
diff --git a/traffic_ops/traffic_ops_golang/server/servers_update_status.go b/traffic_ops/traffic_ops_golang/server/servers_update_status.go
index bdc2e9aa0d..589d643f6f 100644
--- a/traffic_ops/traffic_ops_golang/server/servers_update_status.go
+++ b/traffic_ops/traffic_ops_golang/server/servers_update_status.go
@@ -20,16 +20,18 @@ package server
*/
import (
+ "context"
"database/sql"
"fmt"
"net/http"
-
- "github.com/lib/pq"
+ "sync"
+ "time"
"github.com/apache/trafficcontrol/lib/go-log"
"github.com/apache/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
- "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/config"
+
+ "github.com/lib/pq"
)
func GetServerUpdateStatusHandler(w http.ResponseWriter, r *http.Request) {
@@ -40,7 +42,7 @@ func GetServerUpdateStatusHandler(w http.ResponseWriter, r *http.Request) {
}
defer inf.Close()
- serverUpdateStatuses, err, _ := getServerUpdateStatus(inf.Tx.Tx, inf.Config, inf.Params["host_name"])
+ serverUpdateStatuses, err, _ := getServerUpdateStatus(inf.Tx.Tx, inf.Params["host_name"])
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, err)
return
@@ -56,7 +58,10 @@ func GetServerUpdateStatusHandler(w http.ResponseWriter, r *http.Request) {
}
}
-func getServerUpdateStatus(tx *sql.Tx, cfg *config.Config, hostName string) ([]tc.ServerUpdateStatusV40, error, error) {
+func getServerUpdateStatus(tx *sql.Tx, hostName string) ([]tc.ServerUpdateStatusV40, error, error) {
+ if serverUpdateStatusCacheIsInitialized() {
+ return getServerUpdateStatusFromCache(hostName), nil, nil
+ }
updateStatuses := []tc.ServerUpdateStatusV40{}
@@ -175,3 +180,248 @@ ORDER BY s.id
}
return updateStatuses, nil, nil
}
+
+type serverUpdateStatuses struct {
+ serverMap map[string][]tc.ServerUpdateStatusV40
+ *sync.RWMutex
+ initialized bool
+ enabled bool // note: enabled is only written to once at startup, before serving requests, so it doesn't need synchronized access
+}
+
+var serverUpdateStatusCache = serverUpdateStatuses{RWMutex: &sync.RWMutex{}}
+
+func serverUpdateStatusCacheIsInitialized() bool {
+ if serverUpdateStatusCache.enabled {
+ serverUpdateStatusCache.RLock()
+ defer serverUpdateStatusCache.RUnlock()
+ return serverUpdateStatusCache.initialized
+ }
+ return false
+}
+
+func getServerUpdateStatusFromCache(hostname string) []tc.ServerUpdateStatusV40 {
+ serverUpdateStatusCache.RLock()
+ defer serverUpdateStatusCache.RUnlock()
+ return serverUpdateStatusCache.serverMap[hostname]
+}
+
+var once = sync.Once{}
+
+func InitServerUpdateStatusCache(interval time.Duration, db *sql.DB, timeout time.Duration) {
+ once.Do(func() {
+ if interval <= 0 {
+ return
+ }
+ serverUpdateStatusCache.enabled = true
+ refreshServerUpdateStatusCache(db, timeout)
+ startServerUpdateStatusCacheRefresher(interval, db, timeout)
+ })
+}
+
+func startServerUpdateStatusCacheRefresher(interval time.Duration, db *sql.DB, timeout time.Duration) {
+ go func() {
+ for {
+ time.Sleep(interval)
+ refreshServerUpdateStatusCache(db, timeout)
+ }
+ }()
+}
+
+func refreshServerUpdateStatusCache(db *sql.DB, timeout time.Duration) {
+ newServerUpdateStatuses, err := getServerUpdateStatuses(db, timeout)
+ if err != nil {
+ log.Errorf("refreshing server update status cache: %s", err.Error())
+ return
+ }
+ serverUpdateStatusCache.Lock()
+ defer serverUpdateStatusCache.Unlock()
+ serverUpdateStatusCache.serverMap = newServerUpdateStatuses
+ serverUpdateStatusCache.initialized = true
+ log.Infof("refreshed server update status cache (len = %d)", len(serverUpdateStatusCache.serverMap))
+}
+
+type serverInfo struct {
+ id int
+ hostName string
+ typeName string
+ cdnId int
+ status string
+ cachegroup int
+ configUpdateTime *time.Time
+ configApplyTime *time.Time
+ revalUpdateTime *time.Time
+ revalApplyTime *time.Time
+}
+
+const getUseRevalPendingQuery = `
+ SELECT value::BOOLEAN
+ FROM parameter
+ WHERE name = 'use_reval_pending' AND config_file = 'global'
+ UNION ALL SELECT FALSE FETCH FIRST 1 ROW ONLY
+`
+
+const getServerInfoQuery = `
+ SELECT
+ s.id,
+ s.host_name,
+ t.name,
+ s.cdn_id,
+ st.name,
+ s.cachegroup,
+ s.config_update_time,
+ s.config_apply_time,
+ s.revalidate_update_time,
+ s.revalidate_apply_time
+ FROM server s
+ JOIN type t ON t.id = s.type
+ JOIN status st ON st.id = s.status
+`
+
+const getCacheGroupsQuery = `
+ SELECT
+ c.id,
+ c.parent_cachegroup_id,
+ c.secondary_parent_cachegroup_id
+ FROM cachegroup c
+`
+
+const getTopologyCacheGroupParentsQuery = `
+ SELECT
+ cg_child.id,
+ ARRAY_AGG(DISTINCT cg_parent.id)
+ FROM topology_cachegroup_parents tcp
+ JOIN topology_cachegroup tc_child ON tc_child.id = tcp.child
+ JOIN cachegroup cg_child ON cg_child.name = tc_child.cachegroup
+ JOIN topology_cachegroup tc_parent ON tc_parent.id = tcp.parent
+ JOIN cachegroup cg_parent ON cg_parent.name = tc_parent.cachegroup
+ GROUP BY cg_child.id
+`
+
+func getServerUpdateStatuses(db *sql.DB, timeout time.Duration) (map[string][]tc.ServerUpdateStatusV40, error) {
+ dbCtx, dbClose := context.WithTimeout(context.Background(), timeout)
+ defer dbClose()
+ serversByID := make(map[int]serverInfo)
+ updatePendingByCDNCachegroup := make(map[int]map[int]bool)
+ revalPendingByCDNCachegroup := make(map[int]map[int]bool)
+ tx, err := db.BeginTx(dbCtx, nil)
+ if err != nil {
+ return nil, fmt.Errorf("beginning server update status transaction: %w", err)
+ }
+ defer func() {
+ if err := tx.Commit(); err != nil && err != sql.ErrTxDone {
+ log.Errorln("committing server update status transaction: " + err.Error())
+ }
+ }()
+
+ useRevalPending := false
+ if err := tx.QueryRowContext(dbCtx, getUseRevalPendingQuery).Scan(&useRevalPending); err != nil {
+ return nil, fmt.Errorf("querying use_reval_pending param: %w", err)
+ }
+
+ // get all servers and build map of update/revalPending by cachegroup+CDN
+ serverRows, err := tx.QueryContext(dbCtx, getServerInfoQuery)
+ if err != nil {
+ return nil, fmt.Errorf("querying servers: %w", err)
+ }
+ defer log.Close(serverRows, "closing server rows")
+ for serverRows.Next() {
+ s := serverInfo{}
+ if err := serverRows.Scan(&s.id, &s.hostName, &s.typeName, &s.cdnId, &s.status, &s.cachegroup, &s.configUpdateTime, &s.configApplyTime, &s.revalUpdateTime, &s.revalApplyTime); err != nil {
+ return nil, fmt.Errorf("scanning servers: %w", err)
+ }
+ serversByID[s.id] = s
+ if _, ok := updatePendingByCDNCachegroup[s.cdnId]; !ok {
+ updatePendingByCDNCachegroup[s.cdnId] = make(map[int]bool)
+ }
+ if _, ok := revalPendingByCDNCachegroup[s.cdnId]; !ok {
+ revalPendingByCDNCachegroup[s.cdnId] = make(map[int]bool)
+ }
+ status := tc.CacheStatusFromString(s.status)
+ if tc.IsValidCacheType(s.typeName) && (status == tc.CacheStatusOnline || status == tc.CacheStatusReported || status == tc.CacheStatusAdminDown) {
+ if s.configUpdateTime.After(*s.configApplyTime) {
+ updatePendingByCDNCachegroup[s.cdnId][s.cachegroup] = true
+ }
+ if s.revalUpdateTime.After(*s.revalApplyTime) {
+ revalPendingByCDNCachegroup[s.cdnId][s.cachegroup] = true
+ }
+ }
+ }
+ if err := serverRows.Err(); err != nil {
+ return nil, fmt.Errorf("iterating over server rows: %w", err)
+ }
+
+ // get all legacy cachegroup parents
+ cacheGroupParents := make(map[int]map[int]struct{})
+ cacheGroupRows, err := tx.QueryContext(dbCtx, getCacheGroupsQuery)
+ if err != nil {
+ return nil, fmt.Errorf("querying cachegroups: %w", err)
+ }
+ defer log.Close(cacheGroupRows, "closing cachegroup rows")
+ for cacheGroupRows.Next() {
+ id := 0
+ parentID := new(int)
+ secondaryParentID := new(int)
+ if err := cacheGroupRows.Scan(&id, &parentID, &secondaryParentID); err != nil {
+ return nil, fmt.Errorf("scanning cachegroups: %w", err)
+ }
+ cacheGroupParents[id] = make(map[int]struct{})
+ if parentID != nil {
+ cacheGroupParents[id][*parentID] = struct{}{}
+ }
+ if secondaryParentID != nil {
+ cacheGroupParents[id][*secondaryParentID] = struct{}{}
+ }
+ }
+ if err := cacheGroupRows.Err(); err != nil {
+ return nil, fmt.Errorf("iterating over cachegroup rows: %w", err)
+ }
+
+ // get all topology-based cachegroup parents
+ topologyCachegroupRows, err := tx.QueryContext(dbCtx, getTopologyCacheGroupParentsQuery)
+ if err != nil {
+ return nil, fmt.Errorf("querying topology cachegroups: %w", err)
+ }
+ defer log.Close(topologyCachegroupRows, "closing topology cachegroup rows")
+ for topologyCachegroupRows.Next() {
+ id := 0
+ parents := []int32{}
+ if err := topologyCachegroupRows.Scan(&id, pq.Array(&parents)); err != nil {
+ return nil, fmt.Errorf("scanning topology cachegroup rows: %w", err)
+ }
+ for _, p := range parents {
+ cacheGroupParents[id][int(p)] = struct{}{}
+ }
+ }
+ if err = topologyCachegroupRows.Err(); err != nil {
+ return nil, fmt.Errorf("iterating over topology cachegroup rows: %w", err)
+ }
+
+ serverUpdateStatuses := make(map[string][]tc.ServerUpdateStatusV40, len(serversByID))
+ for serverID, server := range serversByID {
+ updateStatus := tc.ServerUpdateStatusV40{
+ HostName: server.hostName,
+ UpdatePending: server.configUpdateTime.After(*server.configApplyTime),
+ RevalPending: server.revalUpdateTime.After(*server.revalApplyTime),
+ UseRevalPending: useRevalPending,
+ HostId: serverID,
+ Status: server.status,
+ ParentPending: getParentPending(cacheGroupParents[server.cachegroup], updatePendingByCDNCachegroup[server.cdnId]),
+ ParentRevalPending: getParentPending(cacheGroupParents[server.cachegroup], revalPendingByCDNCachegroup[server.cdnId]),
+ ConfigUpdateTime: server.configUpdateTime,
+ ConfigApplyTime: server.configApplyTime,
+ RevalidateUpdateTime: server.revalUpdateTime,
+ RevalidateApplyTime: server.revalApplyTime,
+ }
+ serverUpdateStatuses[server.hostName] = append(serverUpdateStatuses[server.hostName], updateStatus)
+ }
+ return serverUpdateStatuses, nil
+}
+
+func getParentPending(parents map[int]struct{}, pendingByCacheGroup map[int]bool) bool {
+ for parent := range parents {
+ if pendingByCacheGroup[parent] {
+ return true
+ }
+ }
+ return false
+}
diff --git a/traffic_ops/traffic_ops_golang/server/servers_update_status_test.go b/traffic_ops/traffic_ops_golang/server/servers_update_status_test.go
index bdcbf4e91a..0d0ce5e7ef 100644
--- a/traffic_ops/traffic_ops_golang/server/servers_update_status_test.go
+++ b/traffic_ops/traffic_ops_golang/server/servers_update_status_test.go
@@ -26,7 +26,7 @@ import (
"time"
"github.com/apache/trafficcontrol/lib/go-tc"
- "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/config"
+
"github.com/jmoiron/sqlx"
sqlmock "gopkg.in/DATA-DOG/go-sqlmock.v1"
)
@@ -58,7 +58,7 @@ func TestGetServerUpdateStatus(t *testing.T) {
}
defer tx.Commit()
- result, err, _ := getServerUpdateStatus(tx, &config.Config{ConfigTrafficOpsGolang: config.ConfigTrafficOpsGolang{DBQueryTimeoutSeconds: 20}}, "host_name_1")
+ result, err, _ := getServerUpdateStatus(tx, "host_name_1")
if err != nil {
t.Errorf("getServerUpdateStatus: %v", err)
}
@@ -75,3 +75,130 @@ func TestGetServerUpdateStatus(t *testing.T) {
reflect.DeepEqual(expected, result)
}
+
+func TestGetServerUpdateStatuses(t *testing.T) {
+ mockDB, mock, err := sqlmock.New()
+ if err != nil {
+ t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
+ }
+ defer mockDB.Close()
+
+ mock.ExpectBegin()
+ revalPendingRows := sqlmock.NewRows([]string{"value"})
+ revalPendingRows.AddRow(true)
+ mock.ExpectQuery("SELECT").WillReturnRows(revalPendingRows)
+
+ serverInfoRows := sqlmock.NewRows([]string{"id", "host_name", "type", "cdn_id", "status",
+ "cachegroup", "config_update_time", "config_apply_time", "revalidate_update_time",
+ "revalidate_apply_time"})
+ tenSecAfter := time.UnixMilli(10000)
+ epoch := time.UnixMilli(0)
+ serverInfoRows.AddRow(1, "edge1", tc.CacheTypeEdge.String(), 1, tc.CacheStatusReported.String(), 1, tenSecAfter, tenSecAfter, tenSecAfter, tenSecAfter)
+ serverInfoRows.AddRow(2, "mid1", tc.CacheTypeMid.String(), 1, tc.CacheStatusReported.String(), 2, tenSecAfter, epoch, tenSecAfter, tenSecAfter)
+ serverInfoRows.AddRow(3, "edge2", tc.CacheTypeEdge.String(), 2, tc.CacheStatusReported.String(), 1, tenSecAfter, tenSecAfter, tenSecAfter, tenSecAfter)
+ serverInfoRows.AddRow(4, "mid2", tc.CacheTypeMid.String(), 2, tc.CacheStatusReported.String(), 2, tenSecAfter, tenSecAfter, tenSecAfter, tenSecAfter)
+ serverInfoRows.AddRow(5, "mid3", tc.CacheTypeMid.String(), 2, tc.CacheStatusReported.String(), 3, tenSecAfter, tenSecAfter, tenSecAfter, epoch)
+ mock.ExpectQuery("SELECT").WillReturnRows(serverInfoRows)
+
+ cachegroupRows := sqlmock.NewRows([]string{"id", "parent_cachegroup_id", "secondary_parent_cachegroup_id"})
+ cachegroupRows.AddRow(1, 2, nil)
+ cachegroupRows.AddRow(2, nil, nil)
+ cachegroupRows.AddRow(3, nil, nil)
+ mock.ExpectQuery("SELECT").WillReturnRows(cachegroupRows)
+
+ topologyCachegroupRows := sqlmock.NewRows([]string{"id", "array_agg"})
+ topologyCachegroupRows.AddRow(1, "{3}")
+ mock.ExpectQuery("SELECT").WillReturnRows(topologyCachegroupRows)
+
+ mock.ExpectCommit()
+
+ expected := map[string][]tc.ServerUpdateStatusV40{
+ "edge1": {
+ {
+ HostName: "edge1",
+ UpdatePending: false,
+ RevalPending: false,
+ UseRevalPending: true,
+ HostId: 1,
+ Status: tc.CacheStatusReported.String(),
+ ParentPending: true,
+ ParentRevalPending: false,
+ ConfigUpdateTime: &tenSecAfter,
+ ConfigApplyTime: &tenSecAfter,
+ RevalidateUpdateTime: &tenSecAfter,
+ RevalidateApplyTime: &tenSecAfter,
+ },
+ },
+ "mid1": {
+ {
+ HostName: "mid1",
+ UpdatePending: true,
+ RevalPending: false,
+ UseRevalPending: true,
+ HostId: 2,
+ Status: tc.CacheStatusReported.String(),
+ ParentPending: false,
+ ParentRevalPending: false,
+ ConfigUpdateTime: &tenSecAfter,
+ ConfigApplyTime: &epoch,
+ RevalidateUpdateTime: &tenSecAfter,
+ RevalidateApplyTime: &tenSecAfter,
+ },
+ },
+ "edge2": {
+ {
+ HostName: "edge2",
+ UpdatePending: false,
+ RevalPending: false,
+ UseRevalPending: true,
+ HostId: 3,
+ Status: tc.CacheStatusReported.String(),
+ ParentPending: false,
+ ParentRevalPending: true,
+ ConfigUpdateTime: &tenSecAfter,
+ ConfigApplyTime: &tenSecAfter,
+ RevalidateUpdateTime: &tenSecAfter,
+ RevalidateApplyTime: &tenSecAfter,
+ },
+ },
+ "mid2": {
+ {
+ HostName: "mid2",
+ UpdatePending: false,
+ RevalPending: false,
+ UseRevalPending: true,
+ HostId: 4,
+ Status: tc.CacheStatusReported.String(),
+ ParentPending: false,
+ ParentRevalPending: false,
+ ConfigUpdateTime: &tenSecAfter,
+ ConfigApplyTime: &tenSecAfter,
+ RevalidateUpdateTime: &tenSecAfter,
+ RevalidateApplyTime: &tenSecAfter,
+ },
+ },
+ "mid3": {
+ {
+ HostName: "mid3",
+ UpdatePending: false,
+ RevalPending: true,
+ UseRevalPending: true,
+ HostId: 5,
+ Status: tc.CacheStatusReported.String(),
+ ParentPending: false,
+ ParentRevalPending: false,
+ ConfigUpdateTime: &tenSecAfter,
+ ConfigApplyTime: &tenSecAfter,
+ RevalidateUpdateTime: &tenSecAfter,
+ RevalidateApplyTime: &epoch,
+ },
+ },
+ }
+ actual, err := getServerUpdateStatuses(mockDB, 20*time.Second)
+ if err != nil {
+ t.Fatalf("unexpected error getting server update statuses: %s", err)
+ }
+ if !reflect.DeepEqual(expected, actual) {
+ t.Errorf("getting server update statuses - expected: %+v, actual: %+v", expected, actual)
+ }
+}
diff --git a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
index 42a3acf45e..60b9b4c2ad 100644
--- a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
+++ b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
@@ -41,6 +41,7 @@ import (
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/config"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/plugin"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/routing"
+ "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/server"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/trafficvault"
_ "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/trafficvault/backends" // init traffic vault backends
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/trafficvault/backends/disabled"
@@ -146,6 +147,7 @@ func main() {
db.SetConnMaxLifetime(time.Duration(cfg.DBConnMaxLifetimeSeconds) * time.Second)
auth.InitUsersCache(time.Duration(cfg.UserCacheRefreshIntervalSec)*time.Second, db.DB, time.Duration(cfg.DBQueryTimeoutSeconds)*time.Second)
+ server.InitServerUpdateStatusCache(time.Duration(cfg.ServerUpdateStatusCacheRefreshIntervalSec)*time.Second, db.DB, time.Duration(cfg.DBQueryTimeoutSeconds)*time.Second)
trafficVault := setupTrafficVault(*riakConfigFileName, &cfg)
@@ -186,7 +188,7 @@ func main() {
log.Infof("Listening on " + cfg.Port)
- server := &http.Server{
+ httpServer := &http.Server{
Addr: ":" + cfg.Port,
TLSConfig: cfg.TLSConfig,
ReadTimeout: time.Duration(cfg.ReadTimeout) * time.Second,
@@ -195,11 +197,11 @@ func main() {
IdleTimeout: time.Duration(cfg.IdleTimeout) * time.Second,
ErrorLog: log.Error,
}
- if server.TLSConfig == nil {
- server.TLSConfig = &tls.Config{}
+ if httpServer.TLSConfig == nil {
+ httpServer.TLSConfig = &tls.Config{}
}
// Deprecated in 5.0
- server.TLSConfig.InsecureSkipVerify = cfg.Insecure
+ httpServer.TLSConfig.InsecureSkipVerify = cfg.Insecure
// end deprecated block
go func() {
@@ -226,8 +228,8 @@ func main() {
} else {
file.Close()
}
- server.Handler = mux
- if err := server.ListenAndServeTLS(cfg.CertPath, cfg.KeyPath); err != nil {
+ httpServer.Handler = mux
+ if err := httpServer.ListenAndServeTLS(cfg.CertPath, cfg.KeyPath); err != nil {
log.Errorf("stopping server: %v\n", err)
os.Exit(1)
}