Add TM2 CRConfig API caching
Project: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/commit/84750094 Tree: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/tree/84750094 Diff: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/diff/84750094 Branch: refs/heads/master Commit: 84750094adcfbda77705ab56806a10a468cc2be8 Parents: ea5a046 Author: Robert Butts <robert.o.bu...@gmail.com> Authored: Thu Feb 16 14:57:29 2017 -0700 Committer: Dave Neuman <neu...@apache.org> Committed: Sun Feb 19 18:56:45 2017 -0700 ---------------------------------------------------------------------- .../traffic_monitor/manager/datarequest.go | 21 ++++++-- .../trafficopswrapper/trafficopswrapper.go | 56 ++++++++++++++++++-- .../traffic_monitor/version.go | 2 +- 3 files changed, 68 insertions(+), 11 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/84750094/traffic_monitor_golang/traffic_monitor/manager/datarequest.go ---------------------------------------------------------------------- diff --git a/traffic_monitor_golang/traffic_monitor/manager/datarequest.go b/traffic_monitor_golang/traffic_monitor/manager/datarequest.go index fd6ad9e..86ebe01 100644 --- a/traffic_monitor_golang/traffic_monitor/manager/datarequest.go +++ b/traffic_monitor_golang/traffic_monitor/manager/datarequest.go @@ -547,15 +547,26 @@ func WrapParams(f SrvFunc, contentType string) http.HandlerFunc { } } -func srvTRConfig(opsConfig OpsConfigThreadsafe, toSession towrap.ITrafficOpsSession) ([]byte, error) { +func WrapAgeErr(errorCount threadsafe.Uint, f func() ([]byte, time.Time, error), contentType string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + bytes, contentTime, err := f() + _, code := WrapErrCode(errorCount, r.URL.EscapedPath(), bytes, err) + w.Header().Set("Content-Type", contentType) + w.Header().Set("Age", fmt.Sprintf("%.0f", time.Since(contentTime).Seconds())) + w.WriteHeader(code) + log.Write(w, bytes, r.URL.EscapedPath()) + } +} + +func srvTRConfig(opsConfig OpsConfigThreadsafe, toSession towrap.ITrafficOpsSession) ([]byte, time.Time, error) { cdnName := opsConfig.Get().CdnName if toSession == nil { - return nil, fmt.Errorf("Unable to connect to Traffic Ops") + return nil, time.Time{}, fmt.Errorf("Unable to connect to Traffic Ops") } if cdnName == "" { - return nil, fmt.Errorf("No CDN Configured") + return nil, time.Time{}, fmt.Errorf("No CDN Configured") } - return toSession.CRConfigRaw(cdnName) + return toSession.LastCRConfig(cdnName) } func srvTRState(params url.Values, localStates peer.CRStatesThreadsafe, combinedStates peer.CRStatesThreadsafe) ([]byte, error) { @@ -725,7 +736,7 @@ func MakeDispatchMap( } dispatchMap := map[string]http.HandlerFunc{ - "/publish/CrConfig": wrap(WrapErr(errorCount, func() ([]byte, error) { + "/publish/CrConfig": wrap(WrapAgeErr(errorCount, func() ([]byte, time.Time, error) { return srvTRConfig(opsConfig, toSession) }, ContentTypeJSON)), "/publish/CrStates": wrap(WrapParams(func(params url.Values, path string) ([]byte, int) { http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/84750094/traffic_monitor_golang/traffic_monitor/trafficopswrapper/trafficopswrapper.go ---------------------------------------------------------------------- diff --git a/traffic_monitor_golang/traffic_monitor/trafficopswrapper/trafficopswrapper.go b/traffic_monitor_golang/traffic_monitor/trafficopswrapper/trafficopswrapper.go index b86d844..a9d9e07 100644 --- a/traffic_monitor_golang/traffic_monitor/trafficopswrapper/trafficopswrapper.go +++ b/traffic_monitor_golang/traffic_monitor/trafficopswrapper/trafficopswrapper.go @@ -23,6 +23,7 @@ import ( "encoding/json" "fmt" "sync" + "time" "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/common/log" "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/crconfig" @@ -32,6 +33,7 @@ import ( // ITrafficOpsSession provides an interface to the Traffic Ops client, so it may be wrapped or mocked. type ITrafficOpsSession interface { CRConfigRaw(cdn string) ([]byte, error) + LastCRConfig(cdn string) ([]byte, time.Time, error) TrafficMonitorConfigMap(cdn string) (*to.TrafficMonitorConfigMap, error) Set(session *to.Session) URL() (string, error) @@ -45,15 +47,46 @@ type ITrafficOpsSession interface { var ErrNilSession = fmt.Errorf("nil session") +type ByteTime struct { + bytes []byte + time time.Time +} + +type ByteMapCache struct { + cache *map[string]ByteTime + m *sync.RWMutex +} + +func NewByteMapCache() ByteMapCache { + return ByteMapCache{m: &sync.RWMutex{}, cache: &map[string]ByteTime{}} +} + +func (c ByteMapCache) Set(key string, newBytes []byte) { + c.m.Lock() + defer c.m.Unlock() + (*c.cache)[key] = ByteTime{bytes: newBytes, time: time.Now()} +} + +func (c ByteMapCache) Get(key string) ([]byte, time.Time) { + c.m.RLock() + defer c.m.RUnlock() + if byteTime, ok := (*c.cache)[key]; !ok { + return nil, time.Time{} + } else { + return byteTime.bytes, byteTime.time + } +} + // TrafficOpsSessionThreadsafe provides access to the Traffic Ops client safe for multiple goroutines. This fulfills the ITrafficOpsSession interface. type TrafficOpsSessionThreadsafe struct { - session **to.Session // pointer-to-pointer, because we're given a pointer from the Traffic Ops package, and we don't want to copy it. - m *sync.Mutex + session **to.Session // pointer-to-pointer, because we're given a pointer from the Traffic Ops package, and we don't want to copy it. + m *sync.Mutex + lastCRConfig ByteMapCache } // NewTrafficOpsSessionThreadsafe returns a new threadsafe TrafficOpsSessionThreadsafe wrapping the given `Session`. func NewTrafficOpsSessionThreadsafe(s *to.Session) TrafficOpsSessionThreadsafe { - return TrafficOpsSessionThreadsafe{&s, &sync.Mutex{}} + return TrafficOpsSessionThreadsafe{session: &s, m: &sync.Mutex{}, lastCRConfig: NewByteMapCache()} } // Set sets the internal Traffic Ops session. This is safe for multiple goroutines, being aware they will race. @@ -95,8 +128,21 @@ func (s TrafficOpsSessionThreadsafe) CRConfigRaw(cdn string) ([]byte, error) { if ss == nil { return nil, ErrNilSession } - b, _, e := ss.GetCRConfig(cdn) - return b, e + b, _, err := ss.GetCRConfig(cdn) + if err == nil { + s.lastCRConfig.Set(cdn, b) + } + return b, err +} + +// LastCRConfig returns the last CRConfig requested from CRConfigRaw, and the time it was returned. This is designed to be used in conjunction with a poller which regularly calls CRConfigRaw. If no last CRConfig exists, because CRConfigRaw has never been called successfully, this calls CRConfigRaw once to try to get the CRConfig from Traffic Ops. +func (s TrafficOpsSessionThreadsafe) LastCRConfig(cdn string) ([]byte, time.Time, error) { + crConfig, crConfigTime := s.lastCRConfig.Get(cdn) + if crConfig == nil { + b, err := s.CRConfigRaw(cdn) + return b, time.Now(), err + } + return crConfig, crConfigTime, nil } // TrafficMonitorConfigMapRaw returns the Traffic Monitor config map from the Traffic Ops, directly from the monitoring.json endpoint. This is not usually what is needed, rather monitoring needs the snapshotted CRConfig data, which is filled in by `TrafficMonitorConfigMap`. This is safe for multiple goroutines. http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/84750094/traffic_monitor_golang/traffic_monitor/version.go ---------------------------------------------------------------------- diff --git a/traffic_monitor_golang/traffic_monitor/version.go b/traffic_monitor_golang/traffic_monitor/version.go index 31b0e57..31569de 100644 --- a/traffic_monitor_golang/traffic_monitor/version.go +++ b/traffic_monitor_golang/traffic_monitor/version.go @@ -20,4 +20,4 @@ package main */ // Version is the current version of the app, in string form. -var Version = "2.0.4" +var Version = "2.0.5"