Skip to content
This repository was archived by the owner on Nov 24, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions lib/go-tc/cdnrouters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package tc

type CDNRouting struct {
StaticRoute float64 `json:"staticRoute"`
Geo float64 `json:"geo"`
Err float64 `json:"err"`
Fed float64 `json:"fed"`
CZ float64 `json:"cz"`
DeepCZ float64 `json:"deepCz"`
RegionalAlternate float64 `json:"regionalAlternate"`
DSR float64 `json:"dsr"`
Miss float64 `json:"miss"`
RegionalDenied float64 `json:"regionalDenied"`
}
59 changes: 59 additions & 0 deletions lib/go-tc/crsstats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package tc

type CRSStats struct {
App CRSStatsApp `json:"app"`
Stats CRSStatsStats `json:"stats"`
}

type CRSStatsApp struct {
BuildTimestamp string `json:"buildTimestamp"`
Name string `json:"name"`
DeployDir string `json:"deploy-dir"`
GitRevision string `json:"git-revision"`
Version string `json:"version"`
}

type CRSStatsStats struct {
DNSMap map[string]CRSStatsStat
HTTPMap map[string]CRSStatsStat
TotalDNSCount uint64 `json:"totalDnsCount"`
TotalHTTPCount uint64 `json:"totalHttpCount"`
TotalDSMissCount uint64 `json:"totalDsMissCount"`
AppStartTime uint64 `json:"appStartTime"`
AverageDnsTime uint64 `json:"averageDnsTime"`
AverageHttpTime uint64 `json:"averageHttpTime"`
UpdateTracker CRSStatsUpdateTracker `json:"updateTracker"`
}

type CRSStatsStat struct {
CZCount uint64 `json:"czCount"`
GeoCount uint64 `json:"geoCount"`
DeepCZCount uint64 `json:"deepCzCount"`
MissCount uint64 `json:"missCount"`
DSRCount uint64 `json:"dsrCount"`
ErrCount uint64 `json:"errCount"`
StaticRouteCount uint64 `json:"staticRouteCount"`
FedCount uint64 `json:"fedCount"`
RegionalDeniedCount uint64 `json:"regionalDeniedCount"`
RegionalAlternateCount uint64 `json:"regionalAlternateCount"`
}

type CRSStatsUpdateTracker struct {
LastHttpsCertificatesCheck uint64 `json:"lastHttpsCertificatesCheck"`
LastGeolocationDatabaseUpdaterUpdate uint64 `json:"lastGeolocationDatabaseUpdaterUpdate"`
LastCacheStateCheck uint64 `json:"lastCacheStateCheck"`
LastCacheStateChange uint64 `json:"lastCacheStateChange"`
LastNetworkUpdaterUpdate uint64 `json:"lastNetworkUpdaterUpdate"`
LastHTTPSCertificatesUpdate uint64 `json:"lastHttpsCertificatesUpdate"`
LastConfigCheck uint64 `json:"lastConfigCheck"`
LastConfigChange uint64 `json:"lastConfigChange"`
LastHTTPSCertificatesFetchFail uint64 `json:"lastHttpsCertificatesFetchFail"`
LastNetworkUpdaterCheck uint64 `json:"lastNetworkUpdaterCheck"`
NewDNSSECKeysFound uint64 `json:"newDnsSecKeysFound"`
LastGeolocationDatabaseUpdaterCheck uint64 `json:"lastGeolocationDatabaseUpdaterCheck"`
LastHTTPSCertificatesFetchSuccess uint64 `json:"lastHttpsCertificatesFetchSuccess"`
LastSteeringWatcherCheck uint64 `json:"lastSteeringWatcherCheck"`
LastDNSSECKeysCheck uint64 `json:"lastDnsSecKeysCheck"`
LastFederationsWatcherCheck uint64 `json:"lastFederationsWatcherCheck"`
LastHTTPSCertificatesFetchAttempt uint64 `json:"lastHttpsCertificatesFetchAttempt"`
}
218 changes: 218 additions & 0 deletions traffic_ops/traffic_ops_golang/cdn/routing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
package cdn

/*
* 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 (
"database/sql"
"encoding/json"
"errors"
"net/http"
"net/url"
"strconv"
"time"

"github.com/apache/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
)

func GetRouting(w http.ResponseWriter, r *http.Request) {
inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id"}, []string{"id"})
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
return
}
defer inf.Close()
api.RespWriter(w, r, inf.Tx.Tx)(getRouting(inf.Tx.Tx))
}

const RouterProxyParameter = "tm.traffic_rtr_fwd_proxy"
const RouterRequestTimeout = time.Second * 10
const RouterOnlineStatus = "ONLINE"

func getRouting(tx *sql.Tx) (tc.CDNRouting, error) {
routers, err := getCDNRouterFQDNs(tx)
if err != nil {
return tc.CDNRouting{}, errors.New("getting monitors: " + err.Error())
}
return getRoutersRouting(tx, routers)
}

func getRoutersRouting(tx *sql.Tx, routers map[tc.CDNName][]string) (tc.CDNRouting, error) {
forwardProxy, forwardProxyExists, err := getGlobalParam(tx, RouterProxyParameter)
if err != nil {
return tc.CDNRouting{}, errors.New("getting global router proxy parameter: " + err.Error())
}
client := &http.Client{Timeout: RouterRequestTimeout}
if forwardProxyExists {
proxyURI, err := url.Parse(forwardProxy)
if err != nil {
return tc.CDNRouting{}, errors.New("router forward proxy '" + forwardProxy + "' in parameter '" + RouterProxyParameter + "' not a URI: " + err.Error())
}
client = &http.Client{Timeout: RouterRequestTimeout, Transport: &http.Transport{Proxy: http.ProxyURL(proxyURI)}}
}

dat := RouterData{}
for cdn, routerFQDNs := range routers {
for _, routerFQDN := range routerFQDNs {
crsStats := tc.CRSStats{}
if err := getCRSStats(routerFQDN, client, &crsStats); err != nil {
return tc.CDNRouting{}, errors.New("getting crs stats for CDN '" + string(cdn) + "' router '" + routerFQDN + "': " + err.Error())
}
dat = addCRSStats(dat, crsStats)
}
}
return sumRouterData(dat), nil
}

type RouterData struct {
StatTotal tc.CRSStatsStat
Total uint64
}

func sumRouterData(d RouterData) tc.CDNRouting {
if d.Total == 0 {
return tc.CDNRouting{} // TODO warn?
}
return tc.CDNRouting{
CZ: float64(d.StatTotal.CZCount) / float64(d.Total) * 100.0,
Geo: float64(d.StatTotal.GeoCount) / float64(d.Total) * 100.0,
DeepCZ: float64(d.StatTotal.DeepCZCount) / float64(d.Total) * 100.0,
Miss: float64(d.StatTotal.MissCount) / float64(d.Total) * 100.0,
DSR: float64(d.StatTotal.DSRCount) / float64(d.Total) * 100.0,
Err: float64(d.StatTotal.ErrCount) / float64(d.Total) * 100.0,
StaticRoute: float64(d.StatTotal.StaticRouteCount) / float64(d.Total) * 100.0,
Fed: float64(d.StatTotal.FedCount) / float64(d.Total) * 100.0,
RegionalDenied: float64(d.StatTotal.RegionalDeniedCount) / float64(d.Total) * 100.0,
RegionalAlternate: float64(d.StatTotal.RegionalAlternateCount) / float64(d.Total) * 100.0,
}
}

func addCRSStats(d RouterData, stats tc.CRSStats) RouterData {
for _, stat := range stats.Stats.DNSMap {
d.StatTotal = sumCRSStat(d.StatTotal, stat)
d.Total += totalCRSStat(stat)
}
return d
}

func totalCRSStat(s tc.CRSStatsStat) uint64 {
return s.CZCount +
s.GeoCount +
s.DeepCZCount +
s.MissCount +
s.DSRCount +
s.ErrCount +
s.StaticRouteCount +
s.FedCount +
s.RegionalDeniedCount +
s.RegionalAlternateCount
}

func sumCRSStat(a, b tc.CRSStatsStat) tc.CRSStatsStat {
return tc.CRSStatsStat{
CZCount: a.CZCount + b.CZCount,
GeoCount: a.GeoCount + b.GeoCount,
DeepCZCount: a.DeepCZCount + b.DeepCZCount,
MissCount: a.MissCount + b.MissCount,
DSRCount: a.DSRCount + b.DSRCount,
ErrCount: a.ErrCount + b.ErrCount,
StaticRouteCount: a.StaticRouteCount + b.StaticRouteCount,
FedCount: a.FedCount + b.FedCount,
RegionalDeniedCount: a.RegionalDeniedCount + b.RegionalDeniedCount,
RegionalAlternateCount: a.RegionalAlternateCount + b.RegionalAlternateCount,
}
}

func getCRSStats(routerFQDN string, client *http.Client, stats interface{}) error {
path := `/crs/stats`
resp, err := client.Get("http://" + routerFQDN + path)
if err != nil {
return errors.New("getting crs stats from router '" + routerFQDN + "': " + err.Error())
}
defer resp.Body.Close()
if err := json.NewDecoder(resp.Body).Decode(stats); err != nil {
return errors.New("decoding stats from router '" + routerFQDN + "': " + err.Error())
}
return nil
}

func getRouterForwardProxy(tx *sql.Tx) (string, error) {
forwardProxy, forwardProxyExists, err := getGlobalParam(tx, RouterProxyParameter)
if err != nil {
return "", errors.New("getting global router proxy parameter: " + err.Error())
} else if !forwardProxyExists {
forwardProxy = ""
}
return forwardProxy, nil
}

// getCDNRouterFQDNs returns an FQDN, including port, of an online router for each CDN, for each router. If a CDN has no online routers, that CDN will not have an entry in the map. The port returned is the API port.
func getCDNRouterFQDNs(tx *sql.Tx) (map[tc.CDNName][]string, error) {
rows, err := tx.Query(`
SELECT s.host_name, s.domain_name, max(pa.value) as port, c.name as cdn
FROM server as s
JOIN type as t ON s.type = t.id
JOIN status as st ON st.id = s.status
JOIN cdn as c ON c.id = s.cdn_id
JOIN profile as pr ON s.profile = pr.id
JOIN profile_parameter as pp ON pp.profile = pr.id
LEFT JOIN parameter as pa ON (pp.parameter = pa.id AND pa.name = 'api.port' AND pa.config_file = 'server.xml')
WHERE t.name = '` + tc.RouterTypeName + `'
AND st.name = '` + RouterOnlineStatus + `'
GROUP BY s.host_name, s.domain_name, c.name
`)
if err != nil {
return nil, errors.New("querying routers: " + err.Error())
}
defer rows.Close()
routers := map[tc.CDNName][]string{}
for rows.Next() {
host := ""
domain := ""
port := sql.NullInt64{}
cdn := ""
if err := rows.Scan(&host, &domain, &port, &cdn); err != nil {
return nil, errors.New("scanning routers: " + err.Error())
}
fqdn := host + "." + domain
if port.Valid {
fqdn += ":" + strconv.FormatInt(port.Int64, 10)
}
routers[tc.CDNName(cdn)] = append(routers[tc.CDNName(cdn)], fqdn)
}
return routers, nil
}

// getGlobalParams returns the value of the global param, whether it existed, or any error
func getGlobalParam(tx *sql.Tx, name string) (string, bool, error) {
return getParam(tx, name, "global")
}

// getGlobalParams returns the value of the param, whether it existed, or any error.
func getParam(tx *sql.Tx, name string, configFile string) (string, bool, error) {
val := ""
if err := tx.QueryRow(`select value from parameter where name = $1 and config_file = $2`, name, configFile).Scan(&val); err != nil {
if err == sql.ErrNoRows {
return "", false, nil
}
return "", false, errors.New("Error querying global paramter '" + name + "': " + err.Error())
}
return val, true, nil
}
2 changes: 1 addition & 1 deletion traffic_ops/traffic_ops_golang/routing/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) {
{1.1, http.MethodGet, `cdns/configs/?(\.json)?$`, cdn.GetConfigs, auth.PrivLevelReadOnly, Authenticated, nil},
{1.1, http.MethodGet, `cdns/domains/?(\.json)?$`, cdn.DomainsHandler, auth.PrivLevelReadOnly, Authenticated, nil},
{1.1, http.MethodGet, `cdns/health$`, handlerToFunc(proxyHandler), 0, NoAuth, []Middleware{}},
{1.1, http.MethodGet, `cdns/routing$`, handlerToFunc(proxyHandler), 0, NoAuth, []Middleware{}},
{1.1, http.MethodGet, `cdns/routing$`, cdn.GetRouting, auth.PrivLevelOperations, Authenticated, nil},

//CDN: CRUD
{1.1, http.MethodGet, `cdns/?(\.json)?$`, api.ReadHandler(&cdn.TOCDN{}), auth.PrivLevelReadOnly, Authenticated, nil},
Expand Down