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
 }
-

Reply via email to