blob: 847d82931affe0176364d5bf171b9b18fcce489f [file] [log] [blame]
package health
/*
* 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 (
"strings"
"testing"
"time"
"github.com/apache/trafficcontrol/traffic_monitor/cache"
"github.com/apache/trafficcontrol/traffic_monitor/config"
"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/lib/go-tc"
)
// TestNoMonitoredInterfacesGetVitals assures that GetVitals
// does not fail even if no interfaces are marked to be monitored
func TestNoMonitoredInterfacesGetVitals(t *testing.T) {
serverID := "no-monitored"
fakeRequestTime := time.Now()
zeroValueVitals := cache.Vitals{}
// Interfaces to monitor are marked true (none)
tmcm := tc.TrafficMonitorConfigMap{
TrafficServer: map[string]tc.TrafficServer{
serverID: {
Interfaces: []tc.ServerInterfaceInfo{
{
Name: "bond0",
Monitor: false,
},
{
Name: "bond1",
Monitor: false,
},
{
Name: "lo",
Monitor: false,
},
},
},
},
}
// multiple interfaces, plus extra
firstResult := cache.Result{
ID: serverID,
Error: nil,
Miscellaneous: map[string]interface{}{},
Statistics: cache.Statistics{
Interfaces: map[string]cache.Interface{
"bond0": {
Speed: 100000,
BytesIn: 570791700709,
BytesOut: 4212211168526,
},
"bond1": {
Speed: 100000,
BytesIn: 1989352297218,
BytesOut: 10630690813,
},
"lo": {
Speed: 0,
BytesIn: 181882394,
BytesOut: 181882394,
},
"em5": {
Speed: 0,
BytesIn: 0,
BytesOut: 0,
},
},
},
Time: fakeRequestTime,
RequestTime: time.Second,
Vitals: cache.Vitals{},
InterfaceVitals: nil,
PollID: 42,
PollFinished: make(chan uint64, 1),
PrecomputedData: cache.PrecomputedData{},
Available: true,
UsingIPv4: false,
}
GetVitals(&firstResult, nil, &tmcm)
// No interfaces were selected to be monitored so none
// should have been added later
if len(firstResult.InterfaceVitals) > 0 {
t.Errorf("InterfaceVitals map should be empty. expected: %v actual: %v:", 0, len(firstResult.InterfaceVitals))
}
// No interfaces were selected to be monitored so no vitals
// should have been calculated
if firstResult.Vitals != zeroValueVitals {
t.Errorf("Vitals should have zero values. expected: %v actual: %v:", zeroValueVitals, firstResult.Vitals)
}
secondResult := firstResult
secondResult.Time = fakeRequestTime.Add(5 * time.Second)
GetVitals(&secondResult, &firstResult, &tmcm)
// No interfaces were selected to be monitored so none
// should have been added later
if len(secondResult.InterfaceVitals) > 0 {
t.Errorf("InterfaceVitals map should be empty. expected: %v actual: %v:", 0, len(secondResult.InterfaceVitals))
}
// No interfaces were selected to be monitored so no vitals
// should have been calculated
if secondResult.Vitals != zeroValueVitals {
t.Errorf("Vitals should have zero values. expected: %v actual: %v:", zeroValueVitals, secondResult.Vitals)
}
// The previous results should not have been impacted
if firstResult.Vitals != zeroValueVitals {
t.Errorf("Vitals should have zero values. expected: %v actual: %v:", zeroValueVitals, firstResult.Vitals)
}
}
// TestDualHomingMonitoredInterfacesGetVitals ensures cache servers
// with multiple interfaces correctly calculate bandwidth based on
// whether the interfaces are marked as "Monitor this interface"
func TestDualHomingMonitoredInterfacesGetVitals(t *testing.T) {
serverID := "dual-homed"
fakeRequestTime := time.Now()
// Interfaces to monitor are marked true
tmcm := tc.TrafficMonitorConfigMap{
TrafficServer: map[string]tc.TrafficServer{
serverID: {
Interfaces: []tc.ServerInterfaceInfo{
{
Name: "bond0",
Monitor: true,
},
{
Name: "bond1",
Monitor: true,
},
{
Name: "lo",
Monitor: false,
},
},
},
},
}
// multiple interfaces, plus extras
firstResult := cache.Result{
ID: serverID,
Error: nil,
Miscellaneous: map[string]interface{}{},
Statistics: cache.Statistics{
Interfaces: map[string]cache.Interface{
"bond0": {
Speed: 100000,
BytesIn: 570791700709,
BytesOut: 4212211168526,
},
"bond1": {
Speed: 100000,
BytesIn: 1989352297218,
BytesOut: 10630690813,
},
"p1p1": {
Speed: 100000,
BytesIn: 570793589545,
BytesOut: 4212220919951,
},
"p3p1": {
Speed: 100000,
BytesIn: 1989354450479,
BytesOut: 10630690813,
},
"lo": {
Speed: 0,
BytesIn: 181882394,
BytesOut: 181882394,
},
"em5": {
Speed: 0,
BytesIn: 0,
BytesOut: 0,
},
"em6": {
Speed: 0,
BytesIn: 0,
BytesOut: 0,
},
},
},
Time: fakeRequestTime,
RequestTime: time.Second,
Vitals: cache.Vitals{},
InterfaceVitals: nil,
PollID: 42,
PollFinished: make(chan uint64, 1),
PrecomputedData: cache.PrecomputedData{},
Available: true,
UsingIPv4: false,
}
GetVitals(&firstResult, nil, &tmcm)
// Two interfaces were selected to be monitored so they
// should have been added later
if len(firstResult.InterfaceVitals) != 2 {
t.Errorf("InterfaceVitals map should not be empty. expected: %v actual: %v:", 2, len(firstResult.InterfaceVitals))
}
expectedFirstVitals := cache.Vitals{
LoadAvg: 0,
BytesIn: 2560143997927,
BytesOut: 4222841859339,
KbpsOut: 0,
MaxKbpsOut: 200000000,
}
// Only two interfaces were selected to be monitored so vitals
// should have been calculated based on those two (bond0 and bond1)
if firstResult.Vitals != expectedFirstVitals {
t.Errorf("Vitals do not match expected output. expected: %v actual: %v:", expectedFirstVitals, firstResult.Vitals)
}
secondResult := firstResult
secondResult.Statistics.Interfaces = map[string]cache.Interface{
"bond0": {
Speed: 100000,
BytesIn: 572608907987,
BytesOut: 4227149141326,
},
"bond1": {
Speed: 100000,
BytesIn: 1996376171468,
BytesOut: 10630696953,
},
"p1p1": {
Speed: 100000,
BytesIn: 572609282353,
BytesOut: 4227157881921,
},
"p3p1": {
Speed: 100000,
BytesIn: 1996378204692,
BytesOut: 10630696953,
},
"lo": {
Speed: 0,
BytesIn: 181882394,
BytesOut: 181882394,
},
"em5": {
Speed: 0,
BytesIn: 0,
BytesOut: 0,
},
"em6": {
Speed: 0,
BytesIn: 0,
BytesOut: 0,
},
}
secondResult.Time = fakeRequestTime.Add(5 * time.Second)
secondResult.Vitals = cache.Vitals{}
GetVitals(&secondResult, &firstResult, &tmcm)
// Two interfaces were selected to be monitored so they
// should have been added later
if len(secondResult.InterfaceVitals) != 2 {
t.Errorf("InterfaceVitals map should not be empty. expected: %v actual: %v:", 2, len(secondResult.InterfaceVitals))
}
expectedSecondVitals := cache.Vitals{
LoadAvg: 0,
BytesIn: 2568985079455,
BytesOut: 4237779838279,
KbpsOut: 23900766,
MaxKbpsOut: 200000000,
}
// Only two interfaces were selected to be monitored so vitals
// should have been calculated based on those two (bond0 and bond1)
if secondResult.Vitals != expectedSecondVitals {
t.Errorf("Vitals do not match expected output. expected: %v actual: %v:", expectedSecondVitals, secondResult.Vitals)
}
// Previous result values should have been altered
if firstResult.Vitals != expectedFirstVitals {
t.Errorf("Vitals do not match expected output. expected: %v actual: %v:", expectedFirstVitals, firstResult.Vitals)
}
}
func TestCalcAvailabilityThresholds(t *testing.T) {
resultID := "myCacheName"
mc := tc.TrafficMonitorConfigMap{
TrafficServer: map[string]tc.TrafficServer{
string(resultID): {
ServerStatus: string(tc.CacheStatusReported),
Profile: "myProfileName",
Interfaces: []tc.ServerInterfaceInfo{
{
Name: "bond0",
Monitor: true,
},
{
Name: "eth0",
Monitor: true,
},
{
Name: "lo",
Monitor: false,
},
},
},
},
Profile: map[string]tc.TMProfile{},
}
result := cache.Result{
ID: resultID,
Error: nil,
Miscellaneous: map[string]interface{}{},
Statistics: cache.Statistics{
Loadavg: cache.Loadavg{
One: 5.43,
Five: 4.32,
Fifteen: 3.21,
CurrentProcesses: 3,
TotalProcesses: 1234,
LatestPID: 32109,
},
Interfaces: map[string]cache.Interface{
"bond0": {
Speed: 20000,
BytesIn: 1234567891011121,
BytesOut: 12345678910111213,
},
"eth0": {
Speed: 30000,
BytesIn: 1234567891011121,
BytesOut: 12345678910111213,
},
},
NotAvailable: false,
},
Time: time.Now(),
RequestTime: time.Second,
Vitals: cache.Vitals{},
InterfaceVitals: map[string]cache.Vitals{},
PollID: 42,
PollFinished: make(chan uint64, 1),
PrecomputedData: cache.PrecomputedData{},
Available: true,
UsingIPv4: false,
}
GetVitals(&result, nil, &mc)
totalBytesOut := result.Statistics.Interfaces["bond0"].BytesOut + result.Statistics.Interfaces["eth0"].BytesOut
if totalBytesOut != result.Vitals.BytesOut {
t.Errorf("Incorrect calculated BytesOut; expected: %d, got: %d", totalBytesOut, result.Vitals.BytesOut)
}
prevIV := map[string]cache.Vitals{}
prevIV["eth0"] = cache.Vitals{BytesOut: result.Vitals.BytesOut - 1250000000} // 10 gigabits
prevIV["bond0"] = cache.Vitals{BytesOut: result.Vitals.BytesOut - 1250000000} // 10 gigabits
prevResult := cache.Result{
Time: result.Time.Add(time.Second * -1),
Vitals: cache.Vitals{BytesOut: result.Vitals.BytesOut - 1250000000}, // 10 gigabits
InterfaceVitals: prevIV,
}
GetVitals(&result, &prevResult, &mc)
mc.Profile[mc.TrafficServer[string(result.ID)].Profile] = tc.TMProfile{
Name: mc.TrafficServer[string(result.ID)].Profile,
Parameters: tc.TMParameters{
Thresholds: map[string]tc.HealthThreshold{
"availableBandwidthInKbps": {
Val: 15000000,
Comparator: ">",
},
},
},
}
toData := todata.TOData{
ServerTypes: map[tc.CacheName]tc.CacheType{},
DeliveryServiceServers: map[tc.DeliveryServiceName][]tc.CacheName{},
ServerCachegroups: map[tc.CacheName]tc.CacheGroupName{},
}
toData.ServerTypes[tc.CacheName(result.ID)] = tc.CacheTypeEdge
toData.ServerCachegroups[tc.CacheName(result.ID)] = "myCG"
localCacheStatusThreadsafe := threadsafe.NewCacheAvailableStatus()
localStates := peer.NewCRStatesThreadsafe()
localStates.SetDeliveryService("myDS", tc.CRStatesDeliveryService{})
events := NewThreadsafeEvents(200)
// test that a normal stat poll over the kbps threshold marks down
pollerName := "stat"
results := []cache.Result{result}
// Ensure that if the interfaces haven't been reported yet that CalcAvailability doesn't panic
original := results[0].Statistics.Interfaces
statResultHistory := (*threadsafe.ResultStatHistory)(nil)
results[0].Statistics.Interfaces = make(map[string]cache.Interface)
CalcAvailability(results, pollerName, statResultHistory, mc, toData, localCacheStatusThreadsafe, localStates, events, config.Both)
results[0].Statistics.Interfaces = original
CalcAvailability(results, pollerName, statResultHistory, mc, toData, localCacheStatusThreadsafe, localStates, events, config.Both)
// ensure that the DisabledLocations is an empty, non-nil slice
for _, ds := range localStates.GetDeliveryServices() {
if ds.DisabledLocations == nil {
t.Error("expected: non-nil DisabledLocations, actual: nil")
}
if len(ds.DisabledLocations) > 0 {
t.Errorf("expected: empty DisabledLocations, actual: %d", len(ds.DisabledLocations))
}
}
localCacheStatuses := localCacheStatusThreadsafe.Get()
localCacheStatus, ok := localCacheStatuses[result.ID]
if !ok {
t.Fatalf("expected: localCacheStatus[cacheName], actual: missing")
}
if !strings.Contains(localCacheStatus.Why, "availableBandwidthInKbps too low") {
t.Errorf("localCacheStatus.Why expected 'availableBandwidthInKbps too low' actual %s", localCacheStatus.Why)
} else if !strings.HasPrefix(localCacheStatus.UnavailableStat, "availableBandwidthInKbps") { // only check prefix because we don't care about the specific interfaces right now
t.Errorf("localCacheStatus.UnavailableStat expected it to start with: 'availableBandwidthInKbps', actual: '%s'", localCacheStatus.UnavailableStat)
}
if localCacheStatus.Available.IPv4 {
t.Errorf("localCacheStatus.Available.IPv4 over kbps threshold expected: false, actual: true")
}
if localCacheStatus.Available.IPv6 {
t.Error("localCacheStatus.Available.IPv6 over kbps threshold expected: false, actual: true")
}
if localCacheStatus.Status != string(tc.CacheStatusReported) {
t.Errorf("localCacheStatus.Status expected: 'todo', actual: '%s'", localCacheStatus.Status)
}
if localCacheStatus.Poller != pollerName {
t.Errorf("localCacheStatus.Poller expected '%s' actual '%s'", pollerName, localCacheStatus.Poller)
}
// test that the health poll didn't override the stat poll threshold markdown and mark available
// https://github.com/apache/trafficcontrol/issues/3646
healthResult := result
healthResultInf := result.Statistics.Interfaces["bond0"]
healthResultInf.BytesOut = 12345680160111212
healthResult.Statistics.Interfaces["bond0"] = healthResultInf
GetVitals(&healthResult, &result, nil)
healthPollerName := "health"
healthResults := []cache.Result{healthResult}
CalcAvailability(healthResults, healthPollerName, nil, mc, toData, localCacheStatusThreadsafe, localStates, events, config.Both)
localCacheStatuses = localCacheStatusThreadsafe.Get()
if _, ok := localCacheStatuses[result.ID]; !ok {
t.Fatalf("expected: localCacheStatus[cacheName], actual: missing")
}
localCacheStatus = localCacheStatuses[result.ID]
if !strings.Contains(localCacheStatus.Why, "availableBandwidthInKbps too low") {
t.Errorf("localCacheStatus.Why expected: 'availableBandwidthInKbps too low' actual: '%s'", localCacheStatus.Why)
} else if !strings.HasPrefix(localCacheStatus.UnavailableStat, "availableBandwidthInKbps") { // only check prefix because we don't care about the specific interfaces right now
t.Errorf("localCacheStatus.UnavailableStat expected it to start with: 'availableBandwidthInKbps', actual: '%s'", localCacheStatus.UnavailableStat)
}
if localCacheStatus.Available.IPv4 {
t.Fatal("localCacheStatus.Available.IPv4 over kbps threshold expected: false, actual: true")
} else if localCacheStatus.Available.IPv6 {
t.Fatal("localCacheStatus.Available.IPv6 over kbps threshold expected: false, actual: true")
} else if localCacheStatus.Status != string(tc.CacheStatusReported) {
t.Fatalf("localCacheStatus.Status expected: 'todo' actual: '%s'", localCacheStatus.Status)
} else if localCacheStatus.Poller != healthPollerName {
t.Fatalf("localCacheStatus.Poller expected: '%s' actual '%s'", healthPollerName, localCacheStatus.Poller)
}
}
func TestEvalInterface(t *testing.T) {
result := cache.ResultInfo{
Available: true,
Error: nil,
ID: "test",
PollID: 1,
RequestTime: time.Second,
Statistics: cache.Statistics{},
Time: time.Now(),
UsingIPv4: true,
Vitals: cache.Vitals{},
InterfaceVitals: map[string]cache.Vitals{},
}
var infMaxKbps uint64 = 200
mc := tc.TrafficMonitorConfigMap{
Profile: map[string]tc.TMProfile{
"testProfile": {},
},
TrafficServer: map[string]tc.TrafficServer{
"test": {
Profile: "testProfile",
Interfaces: []tc.ServerInterfaceInfo{
{
Monitor: true,
MaxBandwidth: &infMaxKbps,
Name: "testInterface",
IPAddresses: []tc.ServerIPAddress{
{
Address: "::1",
ServiceAddress: true,
},
},
},
},
},
},
}
available, why := EvalInterface(result.InterfaceVitals, mc.TrafficServer["test"].Interfaces[0])
if available {
t.Error("Expected unpolled interface to be unavailable, but it wasn't")
}
if why != "not found in polled data" {
t.Errorf("Incorrect reason for unpolled interface's availability; expected: 'not found in polled data', got: '%s'", why)
}
result.InterfaceVitals["testInterface"] = cache.Vitals{
KbpsOut: 199,
}
available, why = EvalInterface(result.InterfaceVitals, mc.TrafficServer["test"].Interfaces[0])
if !available {
t.Error("Expected polled interface within threshold to be available, but it wasn't")
}
if why != "" {
t.Errorf("Expected no reason why polled interface within threshold is unavailable, got: '%s'", why)
}
result.InterfaceVitals["testInterface"] = cache.Vitals{
KbpsOut: 201,
}
available, why = EvalInterface(result.InterfaceVitals, mc.TrafficServer["test"].Interfaces[0])
if available {
t.Error("Expected interface exceeding threshold to be unavailable, but it wasn't")
}
if why != "maximum bandwidth exceeded" {
t.Errorf("Incorrect reason for interface exceeding threshold to be unavailable; expected: 'maximum bandwidth exceeded', got: '%s'", why)
}
}