blob: 31024cd82003a1bb238c045e5d018bb1d5b5a9c0 [file] [log] [blame]
package ds
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import (
"bytes"
"errors"
"fmt"
"log"
"math"
"regexp"
"testing"
tc_log "github.com/apache/trafficcontrol/lib/go-log"
"github.com/apache/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/traffic_monitor/cache"
"github.com/apache/trafficcontrol/traffic_monitor/dsdata"
"github.com/apache/trafficcontrol/traffic_monitor/health"
"github.com/apache/trafficcontrol/traffic_monitor/peer"
"github.com/apache/trafficcontrol/traffic_monitor/threadsafe"
"github.com/apache/trafficcontrol/traffic_monitor/todata"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/test"
)
func checkLogOutput(t *testing.T, buffer *bytes.Buffer, toData todata.TOData, caches []tc.CacheName) {
output := buffer.String()
for _, cacheName := range caches {
match, err := regexp.MatchString(string(cacheName)+".*not found in DeliveryServices", output)
if err != nil {
t.Fatalf("cannot match cache name %s against output", cacheName)
}
if toData.ServerTypes[cacheName] != tc.CacheTypeMid && !match {
t.Fatalf("expected to find info-level message about cache %s with cache type %s not being found in DeliveryServices, no message found", cacheName, toData.ServerTypes[cacheName])
}
if toData.ServerTypes[cacheName] == tc.CacheTypeMid && match {
t.Fatalf("expected not to find info-level message about cache %s with cache type %s not being found in DeliveryServices, but message was found", cacheName, toData.ServerTypes[cacheName])
}
}
}
func TestCreateStats(t *testing.T) {
toData := getMockTOData()
combinedCRStates := peer.NewCRStatesThreadsafe()
lastStatsThs := threadsafe.NewLastStats()
maxEvents := uint64(4)
events := health.NewThreadsafeEvents(maxEvents)
localCRStates := peer.NewCRStatesThreadsafe()
dses := []tc.DeliveryServiceName{}
for ds, _ := range toData.DeliveryServiceServers {
dses = append(dses, ds)
}
caches := []tc.CacheName{}
for cache, _ := range toData.ServerDeliveryServices {
caches = append(caches, cache)
}
for _, cache := range caches {
combinedCRStates.AddCache(cache, tc.IsAvailable{IsAvailable: true})
localCRStates.AddCache(cache, tc.IsAvailable{IsAvailable: true})
}
precomputeds := randCachesPrecomputedData(caches, toData)
monitorConfig := getMockMonitorConfig(dses)
lastStatsVal := lastStatsThs.Get()
lastStatsCopy := lastStatsVal.Copy()
dsStats := CreateStats(precomputeds, toData, combinedCRStates.Get(), lastStatsCopy, monitorConfig, events, localCRStates)
serverDeliveryServices := toData.ServerDeliveryServices
toData.ServerDeliveryServices = map[tc.CacheName][]tc.DeliveryServiceName{} // temporarily unassign servers to generate warnings about caches not assigned to delivery services
buffer := bytes.NewBuffer(make([]byte, 0, 10000))
tc_log.Info = log.New(buffer, "TestAddAvailabilityDataNotFoundInDeliveryService", log.Lshortfile)
_ = CreateStats(precomputeds, toData, combinedCRStates.Get(), lastStatsCopy, monitorConfig, events, localCRStates)
checkLogOutput(t, buffer, toData, caches)
toData.ServerDeliveryServices = serverDeliveryServices
lastStatsThs.Set(*lastStatsCopy)
cgMap := map[tc.CacheGroupName]struct{}{}
for _, cg := range toData.ServerCachegroups {
cgMap[cg] = struct{}{}
}
tpMap := map[tc.CacheType]struct{}{}
for _, tp := range toData.ServerTypes {
tpMap[tp] = struct{}{}
}
caMap := map[tc.CacheName]struct{}{}
for ca, _ := range toData.ServerDeliveryServices {
caMap[ca] = struct{}{}
}
for dsName, dsStat := range dsStats.DeliveryService {
for cgName, cgStat := range dsStat.CacheGroups {
if _, ok := cgMap[cgName]; !ok {
t.Fatalf("CreateStats cachegroup expected: %+v, actual: %+v", cgMap, cgName)
}
var cgExpected cache.DSStat
for pCache, pData := range precomputeds {
if toData.ServerCachegroups[pCache] != cgName {
continue
}
if pDataDS, ok := pData.DeliveryServiceStats[string(dsName)]; ok {
cgExpected.InBytes += pDataDS.InBytes
cgExpected.OutBytes += pDataDS.OutBytes
cgExpected.Status2xx += pDataDS.Status2xx
cgExpected.Status3xx += pDataDS.Status3xx
cgExpected.Status4xx += pDataDS.Status4xx
cgExpected.Status5xx += pDataDS.Status5xx
}
}
if errStr := compareAStatToStatCacheStats(&cgExpected, cgStat); errStr != "" {
t.Fatalf("CreateStats cachegroup " + string(cgName) + ": " + errStr)
}
}
for tpName, tpStat := range dsStat.Types {
if _, ok := tpMap[tpName]; !ok {
t.Fatalf("CreateStats type expected: %+v, actual: %+v", tpMap, tpName)
}
var tpExpected cache.DSStat
for pCache, pData := range precomputeds {
if toData.ServerTypes[pCache] != tpName {
continue
}
if pDataDS, ok := pData.DeliveryServiceStats[string(dsName)]; ok {
tpExpected.InBytes += pDataDS.InBytes
tpExpected.OutBytes += pDataDS.OutBytes
tpExpected.Status2xx += pDataDS.Status2xx
tpExpected.Status3xx += pDataDS.Status3xx
tpExpected.Status4xx += pDataDS.Status4xx
tpExpected.Status5xx += pDataDS.Status5xx
}
}
if errStr := compareAStatToStatCacheStats(&tpExpected, tpStat); errStr != "" {
t.Fatalf("CreateStats type " + string(tpName) + ": " + errStr)
}
}
for caName, caStat := range dsStat.Caches {
if _, ok := caMap[caName]; !ok {
t.Fatalf("CreateStats cache expected: %+v, actual: %+v", caMap, caName)
}
var caExpected cache.DSStat
for pCache, pData := range precomputeds {
if pCache != caName {
continue
}
if pDataDS, ok := pData.DeliveryServiceStats[string(dsName)]; ok {
caExpected.InBytes += pDataDS.InBytes
caExpected.OutBytes += pDataDS.OutBytes
caExpected.Status2xx += pDataDS.Status2xx
caExpected.Status3xx += pDataDS.Status3xx
caExpected.Status4xx += pDataDS.Status4xx
caExpected.Status5xx += pDataDS.Status5xx
}
}
if errStr := compareAStatToStatCacheStats(&caExpected, caStat); errStr != "" {
t.Fatalf("CreateStats cache " + string(caName) + ": " + errStr)
}
}
{
cmStat := dsStat.CommonStats
if int(cmStat.CachesConfiguredNum.Value) != len(toData.DeliveryServiceServers[dsName]) {
t.Fatalf("CreateStats CommonStats.CachesConfiguredNum expected: %+v actual: %+v", len(toData.DeliveryServiceServers[dsName]), dsStat.CommonStats.CachesConfiguredNum.Value)
}
for caName, reporting := range cmStat.CachesReporting {
if _, ok := caMap[caName]; !ok {
t.Fatalf("CreateStats CommonStats.CachesReporting '%+v' not in test caches", caName)
}
if !reporting {
t.Fatalf("CreateStats len(CommonStats.CachesReporting[%+v] expected: true actual: false", caName)
}
}
if cmStat.ErrorStr.Value != "" {
t.Fatalf("CreateStats CommonStats.ErrorStr expected: '' actual: %+v", cmStat.ErrorStr.Value)
}
if cmStat.StatusStr.Value != "" {
t.Fatalf("CreateStats CommonStats.StatusStr expected: '' actual: '%+v'", cmStat.StatusStr.Value)
}
}
}
if len(lastStatsCopy.DeliveryServices) != len(toData.DeliveryServiceServers) {
t.Fatalf("CreateStats len(LastStats.DeliveryServices) expected: %+v actual: %+v", len(toData.DeliveryServiceServers), len(lastStatsCopy.DeliveryServices))
}
if len(lastStatsCopy.Caches) != len(toData.ServerDeliveryServices) {
t.Fatalf("CreateStats len(LastStats.Caches) expected: %+v actual: %+v", len(toData.ServerDeliveryServices), len(lastStatsCopy.Caches))
}
}
// compareAStatToStatCacheStats compares the two stats, and returns an error string, which is empty of both are equal.
// The fields in StatCacheStats but not AStat are ignored.
func compareAStatToStatCacheStats(expected *cache.DSStat, actual *dsdata.StatCacheStats) string {
if actual.InBytes.Value != float64(expected.InBytes) {
return fmt.Sprintf("InBytes expected: \n%+v, actual: \n%+v", expected.InBytes, actual.InBytes.Value)
}
if actual.OutBytes.Value != int64(expected.OutBytes) {
return fmt.Sprintf("OutBytes expected: \n%+v, actual: \n%+v", expected.OutBytes, actual.OutBytes.Value)
}
if actual.Status2xx.Value != int64(expected.Status2xx) {
return fmt.Sprintf("Status2xx expected: \n%+v, actual: \n%+v", expected.Status2xx, actual.Status2xx.Value)
}
if actual.Status3xx.Value != int64(expected.Status3xx) {
return fmt.Sprintf("Status3xx expected: \n%+v, actual: \n%+v", expected.Status3xx, actual.Status3xx.Value)
}
if actual.Status4xx.Value != int64(expected.Status4xx) {
return fmt.Sprintf("Status4xx expected: \n%+v, actual: \n%+v", expected.Status4xx, actual.Status4xx.Value)
}
if actual.Status5xx.Value != int64(expected.Status5xx) {
return fmt.Sprintf("Status5xx expected: \n%+v, actual: \n%+v", expected.Status5xx, actual.Status5xx.Value)
}
if actual.ErrorString.Value != "" {
return fmt.Sprintf("ErrorString expected: empty, actual: %+v", actual.ErrorString.Value)
}
return ""
}
func getMockMonitorDSNoThresholds(name tc.DeliveryServiceName) tc.TMDeliveryService {
return tc.TMDeliveryService{
XMLID: string(name),
TotalTPSThreshold: math.MaxInt64,
ServerStatus: string(tc.CacheStatusReported),
TotalKbpsThreshold: math.MaxInt64,
}
}
func getMockMonitorDSLowThresholds(name tc.DeliveryServiceName) tc.TMDeliveryService {
return tc.TMDeliveryService{
XMLID: string(name),
TotalTPSThreshold: 1,
ServerStatus: string(tc.CacheStatusReported),
TotalKbpsThreshold: 1,
}
}
func getMockMonitorConfig(dses []tc.DeliveryServiceName) tc.TrafficMonitorConfigMap {
mc := tc.TrafficMonitorConfigMap{
TrafficServer: map[string]tc.TrafficServer{},
CacheGroup: map[string]tc.TMCacheGroup{},
Config: map[string]interface{}{},
TrafficMonitor: map[string]tc.TrafficMonitor{},
DeliveryService: map[string]tc.TMDeliveryService{},
Profile: map[string]tc.TMProfile{},
}
tmDSes := map[string]tc.TMDeliveryService{}
for _, ds := range dses {
tmDSes[string(ds)] = getMockMonitorDSNoThresholds(ds)
}
mc.DeliveryService = tmDSes
return mc
}
func getMockTOData() todata.TOData {
numCaches := 100
numDSes := 100
numCacheDSes := numDSes / 3
numCGs := 20
types := []tc.CacheType{tc.CacheTypeEdge, tc.CacheTypeEdge, tc.CacheTypeEdge, tc.CacheTypeEdge, tc.CacheTypeEdge, tc.CacheTypeMid}
caches := []tc.CacheName{}
for i := 0; i < numCaches; i++ {
caches = append(caches, tc.CacheName(test.RandStr()))
}
dses := []tc.DeliveryServiceName{}
for i := 0; i < numDSes; i++ {
dses = append(dses, tc.DeliveryServiceName(test.RandStr()))
}
cgs := []tc.CacheGroupName{}
for i := 0; i < numCGs; i++ {
cgs = append(cgs, tc.CacheGroupName(test.RandStr()))
}
serverDSes := map[tc.CacheName][]tc.DeliveryServiceName{}
for _, ca := range caches {
for i := 0; i < numCacheDSes; i++ {
serverDSes[ca] = append(serverDSes[ca], dses[test.RandIntn(len(dses))])
}
}
dsServers := map[tc.DeliveryServiceName][]tc.CacheName{}
for server, dses := range serverDSes {
for _, ds := range dses {
dsServers[ds] = append(dsServers[ds], server)
}
}
serverCGs := map[tc.CacheName]tc.CacheGroupName{}
for _, cache := range caches {
serverCGs[cache] = cgs[test.RandIntn(len(cgs))]
}
serverTypes := map[tc.CacheName]tc.CacheType{}
for _, cache := range caches {
serverTypes[cache] = types[test.RandIntn(len(types))]
}
tod := todata.New()
tod.DeliveryServiceServers = dsServers
tod.ServerDeliveryServices = serverDSes
tod.ServerTypes = serverTypes
tod.ServerCachegroups = serverCGs
return *tod
}
func randCachesPrecomputedData(caches []tc.CacheName, toData todata.TOData) map[tc.CacheName]cache.PrecomputedData {
prc := map[tc.CacheName]cache.PrecomputedData{}
for _, ca := range caches {
prc[ca] = randPrecomputedData(toData)
}
return prc
}
func randPrecomputedData(toData todata.TOData) cache.PrecomputedData {
dsStats := randDsStats(toData)
dsTotal := uint64(0)
for _, stat := range dsStats {
dsTotal += stat.OutBytes
}
return cache.PrecomputedData{
DeliveryServiceStats: dsStats,
OutBytes: dsTotal,
MaxKbps: test.RandInt64(),
Errors: randErrs(),
Reporting: true,
}
}
func randDsStats(toData todata.TOData) map[string]*cache.DSStat {
a := map[string]*cache.DSStat{}
for ds, _ := range toData.DeliveryServiceServers {
a[string(ds)] = randAStat()
}
return a
}
func randAStat() *cache.DSStat {
return &cache.DSStat{
InBytes: uint64(test.RandIntn(1000)),
OutBytes: uint64(test.RandIntn(1000)),
Status2xx: uint64(test.RandIntn(1000)),
Status3xx: uint64(test.RandIntn(1000)),
Status4xx: uint64(test.RandIntn(1000)),
Status5xx: uint64(test.RandIntn(1000)),
}
}
func randErrs() []error {
if test.RandBool() {
return []error{}
}
num := 5
errs := []error{}
for i := 0; i < num; i++ {
errs = append(errs, errors.New(test.RandStr()))
}
return errs
}
func TestAddLastStatsToStatCacheStatsNilVals(t *testing.T) {
// test that addLastStatsToStatCacheStats doesn't panic with nil values
addLastStatsToStatCacheStats(nil, nil)
addLastStatsToStatCacheStats(&dsdata.StatCacheStats{}, nil)
addLastStatsToStatCacheStats(nil, &dsdata.LastStatsData{})
}