This is an automated email from the ASF dual-hosted git repository.
ocket8888 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 f5048b3 /cdns/capacity returns 500 error: capacity was zero (#4892)
f5048b3 is described below
commit f5048b36e6fa26c1aa48bae7e3f73c38916592c1
Author: Srijeet Chatterjee <[email protected]>
AuthorDate: Tue Jul 28 16:00:54 2020 -0600
/cdns/capacity returns 500 error: capacity was zero (#4892)
* initial commit to parse cache stats properly
* Remove unused code
* Remove unneeded log lines
* Adding changelog entry
* Code review fixes
* Fixing changelog to address review comment
* Code review fixes to run the query only once
* Code review fixes
---
CHANGELOG.md | 1 +
lib/go-tc/enum.go | 3 +
traffic_ops/traffic_ops_golang/cdn/capacity.go | 74 ++++++++++++++++---
.../traffic_ops_golang/cdn/capacity_test.go | 85 ++++++++++++++++++++++
.../trafficstats/deliveryservice.go | 1 -
5 files changed, 152 insertions(+), 12 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8eae060..1ad25fe 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -29,6 +29,7 @@ The format is based on [Keep a
Changelog](http://keepachangelog.com/en/1.0.0/).
- Added the ability to view Hash ID field (aka xmppID) on Traffic Portals'
server summary page
### Fixed
+- Fixed #4848 - `GET /api/x/cdns/capacity` gives back 500, with the message
`capacity was zero`
- Fixed #2156 - Renaming a host in TC, does not impact xmpp_id and thereby
hashid [Related github
issue](https://github.com/apache/trafficcontrol/issues/2156)
- Fixed #3661 - Anonymous Proxy ipv4 whitelist does not work
- Fixed the `GET /api/x/jobs` and `GET /api/x/jobs/:id` Traffic Ops API routes
to allow falling back to Perl via the routing blacklist
diff --git a/lib/go-tc/enum.go b/lib/go-tc/enum.go
index f9614e7..83c7f46 100644
--- a/lib/go-tc/enum.go
+++ b/lib/go-tc/enum.go
@@ -55,6 +55,9 @@ type DeliveryServiceName string
// CacheType is the type (or tier) of a CDN cache.
type CacheType string
+// InterfaceName is the name of the server interface
+type InterfaceName string
+
const OriginLocationType = "ORG_LOC"
const (
diff --git a/traffic_ops/traffic_ops_golang/cdn/capacity.go
b/traffic_ops/traffic_ops_golang/cdn/capacity.go
index c68ab31..8daf490 100644
--- a/traffic_ops/traffic_ops_golang/cdn/capacity.go
+++ b/traffic_ops/traffic_ops_golang/cdn/capacity.go
@@ -57,7 +57,9 @@ func getCapacity(tx *sql.Tx) (CapacityResp, error) {
if err != nil {
return CapacityResp{}, errors.New("getting monitors: " +
err.Error())
}
-
+ if len(monitors) == 0 {
+ return CapacityResp{}, errors.New("no monitors found")
+ }
return getMonitorsCapacity(tx, monitors)
}
@@ -102,7 +104,7 @@ func getMonitorsCapacity(tx *sql.Tx, monitors
map[tc.CDNName][]string) (Capacity
return CapacityResp{}, errors.New("getting profile thresholds:
" + err.Error())
}
- cap, err := getCapacityData(monitors, thresholds, client)
+ cap, err := getCapacityData(monitors, thresholds, client, tx)
if err != nil {
return CapacityResp{}, errors.New("getting capacity from
monitors: " + err.Error())
} else if cap.Capacity == 0 {
@@ -120,7 +122,7 @@ func getMonitorsCapacity(tx *sql.Tx, monitors
map[tc.CDNName][]string) (Capacity
// getCapacityData attempts to get the CDN capacity from each monitor. If one
fails, it tries the next.
// The first monitor for which all data requests succeed is used.
// Only if all monitors for a CDN fail is an error returned, from the last
monitor tried.
-func getCapacityData(monitors map[tc.CDNName][]string, thresholds
map[string]float64, client *http.Client) (CapData, error) {
+func getCapacityData(monitors map[tc.CDNName][]string, thresholds
map[string]float64, client *http.Client, tx *sql.Tx) (CapData, error) {
cap := CapData{}
for cdn, monitorFQDNs := range monitors {
err := error(nil)
@@ -143,7 +145,8 @@ func getCapacityData(monitors map[tc.CDNName][]string,
thresholds map[string]flo
log.Warnln("getCapacity failed to get
CacheStats from cdn '" + string(cdn) + " monitor '" + monitorFQDN + "', trying
next monitor: " + err.Error())
continue
}
- cap = addCapacity(cap, cacheStats, crStates, crConfig,
thresholds)
+
+ cap = addCapacity(cap, cacheStats, crStates, crConfig,
thresholds, tx)
break
}
if err != nil {
@@ -153,7 +156,12 @@ func getCapacityData(monitors map[tc.CDNName][]string,
thresholds map[string]flo
return cap, nil
}
-func addCapacity(cap CapData, cacheStats CacheStats, crStates tc.CRStates,
crConfig tc.CRConfig, thresholds map[string]float64) CapData {
+func addCapacity(cap CapData, cacheStats CacheStats, crStates tc.CRStates,
crConfig tc.CRConfig, thresholds map[string]float64, tx *sql.Tx) CapData {
+ serviceInterfaces, err := getServiceInterfaces(tx)
+ if err != nil {
+ log.Errorf("couldn't get the service interfaces for servers.
err: %v", err.Error())
+ return cap
+ }
for cacheName, stats := range cacheStats.Caches {
cache, ok := crConfig.ContentServers[string(cacheName)]
if !ok {
@@ -166,25 +174,69 @@ func addCapacity(cap CapData, cacheStats CacheStats,
crStates tc.CRStates, crCon
if !strings.HasPrefix(*cache.ServerType,
string(tc.CacheTypeEdge)) {
continue
}
- if len(stats.KBPS) < 1 || len(stats.MaxKBPS) < 1 {
+ if _, ok := serviceInterfaces[string(cacheName)]; !ok {
+ log.Errorf("no service interface found for server with
host name %v", cacheName)
+ continue
+ }
+ kbps, maxKbps, err :=
getStatsFromServiceInterface(stats[tc.InterfaceName(serviceInterfaces[string(cacheName)])])
+ if err != nil {
+ log.Errorf("couldn't get service interface stats for
%v. err: %v", cacheName, err.Error())
continue
}
if string(*cache.ServerStatus) ==
string(tc.CacheStatusReported) || string(*cache.ServerStatus) ==
string(tc.CacheStatusOnline) {
if crStates.Caches[cacheName].IsAvailable {
- cap.Available += float64(stats.KBPS[0].Value)
+ cap.Available += kbps
} else {
- cap.Unavailable += float64(stats.KBPS[0].Value)
+ cap.Unavailable += kbps
}
} else if string(*cache.ServerStatus) ==
string(tc.CacheStatusAdminDown) {
- cap.Maintenance += float64(stats.KBPS[0].Value)
+ cap.Maintenance += kbps
} else {
continue // don't add capacity for OFFLINE or other
statuses
}
- cap.Capacity += float64(stats.MaxKBPS[0].Value) -
thresholds[*cache.Profile]
+ cap.Capacity += maxKbps - thresholds[*cache.Profile]
}
return cap
}
+func getStatsFromServiceInterface(stats CacheStat) (float64, float64, error) {
+ var kbps, maxKbps float64
+ if len(stats.KBPS) < 1 ||
+ len(stats.MaxKBPS) < 1 {
+ return kbps, maxKbps, errors.New("no kbps/ maxKbps stats to
return")
+ }
+ kbps = stats.KBPS[0].Value
+ maxKbps = stats.MaxKBPS[0].Value
+ return kbps, maxKbps, nil
+}
+
+func getServiceInterfaces(tx *sql.Tx) (map[string]string, error) {
+ query := `
+SELECT s.host_name, i.interface FROM ip_address i
+JOIN server s ON s.id = i.server
+WHERE i.service_address=true
+`
+ rows, err := tx.Query(query)
+ if err != nil {
+ log.Errorf("couldn't get service interfaces %v", err.Error())
+ return nil, err
+ }
+ defer rows.Close()
+
+ resultMap := make(map[string]string)
+ for rows.Next() {
+ hostname := ""
+ serviceinterface := ""
+ err = rows.Scan(&hostname, &serviceinterface)
+ if err != nil {
+ log.Errorf("error unmarshalling response %v",
err.Error())
+ continue
+ }
+ resultMap[hostname] = serviceinterface
+ }
+ return resultMap, nil
+}
+
func getEdgeProfileHealthThresholdBandwidth(tx *sql.Tx) (map[string]float64,
error) {
rows, err := tx.Query(`
SELECT pr.name as profile, pa.name, pa.config_file, pa.value
@@ -221,7 +273,7 @@ AND pa.name = 'health.threshold.availableBandwidthInKbps'
// CacheStats contains the Monitor CacheStats needed by Cachedata. It is NOT
the full object served by the Monitor, but only the data required by the caches
stats endpoint.
type CacheStats struct {
- Caches map[tc.CacheName]CacheStat `json:"caches"`
+ Caches map[tc.CacheName]map[tc.InterfaceName]CacheStat `json:"caches"`
}
type CacheStat struct {
diff --git a/traffic_ops/traffic_ops_golang/cdn/capacity_test.go
b/traffic_ops/traffic_ops_golang/cdn/capacity_test.go
new file mode 100644
index 0000000..c1e7b32
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/cdn/capacity_test.go
@@ -0,0 +1,85 @@
+package cdn
+
+import (
+ "github.com/jmoiron/sqlx"
+ "gopkg.in/DATA-DOG/go-sqlmock.v1"
+ "testing"
+)
+
+/*
+ * 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.
+ */
+
+func TestGetServiceInterfaces(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()
+
+ db := sqlx.NewDb(mockDB, "sqlmock")
+ defer db.Close()
+ cols := []string{"host_name", "interface"}
+ rows := sqlmock.NewRows(cols)
+ rows = rows.AddRow(
+ "host1",
+ "eth1",
+ )
+ rows = rows.AddRow(
+ "host2",
+ "eth2",
+ )
+ mock.ExpectBegin()
+ mock.ExpectQuery("SELECT").WillReturnRows(rows)
+ mock.ExpectCommit()
+
+ m, err := getServiceInterfaces(db.MustBegin().Tx)
+ if err != nil {
+ t.Errorf("Expected no error, but got %v", err.Error())
+ }
+ if len(m) != 2 {
+ t.Errorf("Expected a result of length %v, got %v instead", 2,
len(m))
+ }
+ if m["host1"] != "eth1" {
+ t.Errorf("Expected host1 to have service interface eth1, got %v
instead", m["host1"])
+ }
+ if m["host2"] != "eth2" {
+ t.Errorf("Expected host2 to have service interface eth2, got %v
instead", m["host2"])
+ }
+}
+
+func TestGetStatsFromServiceInterface(t *testing.T) {
+ var data1 []CacheStatData
+ var data2 []CacheStatData
+ kbpsData := CacheStatData{Value: 24.5}
+ maxKbpsData := CacheStatData{Value: 66.8}
+ data1 = append(data1, kbpsData)
+ data2 = append(data2, maxKbpsData)
+
+ c := CacheStat{
+ KBPS: data1,
+ MaxKBPS: data2,
+ }
+ kbps, maxKbps, err := getStatsFromServiceInterface(c)
+ if err != nil {
+ t.Errorf("Expected no error, but got %v", err.Error())
+ }
+ if kbps != 24.5 || maxKbps != 66.8 {
+ t.Errorf("Expected kbps to be 24.5, got %v; Expected maxKbps to
be 66.8, got %v", kbps, maxKbps)
+ }
+}
diff --git a/traffic_ops/traffic_ops_golang/trafficstats/deliveryservice.go
b/traffic_ops/traffic_ops_golang/trafficstats/deliveryservice.go
index bae4875..dece1a3 100644
--- a/traffic_ops/traffic_ops_golang/trafficstats/deliveryservice.go
+++ b/traffic_ops/traffic_ops_golang/trafficstats/deliveryservice.go
@@ -390,4 +390,3 @@ func findMetric(slice []string, val string) (int, bool) {
}
return -1, false
}
-