Skip to content
This repository was archived by the owner on Nov 24, 2025. It is now read-only.
Merged
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
45 changes: 28 additions & 17 deletions traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,16 +306,22 @@ func GetDSTenantIDFromXMLID(tx *sql.Tx, xmlid string) (int, bool, error) {
return id, true, nil
}

// returns returns the delivery service name and cdn, whether it existed, and any error.
func GetDSNameAndCDNFromID(tx *sql.Tx, id int) (tc.DeliveryServiceName, tc.CDNName, bool, error) {
dsName := tc.DeliveryServiceName("")
cdnName := tc.CDNName("")
if err := tx.QueryRow(`SELECT ds.xml_id, cdn.name FROM deliveryservice ds JOIN cdn ON cdn.id = ds.cdn_id WHERE ds.id = $1`, id).Scan(&dsName, &cdnName); err != nil {
name := tc.DeliveryServiceName("")
cdn := tc.CDNName("")
if err := tx.QueryRow(`
SELECT ds.xml_id, cdn.name
FROM deliveryservice as ds
JOIN cdn on cdn.id = ds.cdn_id
WHERE ds.id = $1
`, id).Scan(&name, &cdn); err != nil {
if err == sql.ErrNoRows {
return tc.DeliveryServiceName(""), tc.CDNName(""), false, nil
}
return tc.DeliveryServiceName(""), tc.CDNName(""), false, errors.New("querying: " + err.Error())
return tc.DeliveryServiceName(""), tc.CDNName(""), false, errors.New("querying delivery service name: " + err.Error())
}
return dsName, cdnName, true, nil
return name, cdn, true, nil
}

// GetProfileNameFromID returns the profile's name, whether a profile with ID exists, or any error.
Expand Down Expand Up @@ -560,6 +566,23 @@ func GetCDNs(tx *sql.Tx) (map[tc.CDNName]struct{}, error) {
return cdns, 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")
}

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

// GetCacheGroupNameFromID Get Cache Group name from a given ID
func GetCacheGroupNameFromID(tx *sql.Tx, id int64) (tc.CacheGroupName, bool, error) {
name := ""
Expand Down Expand Up @@ -599,18 +622,6 @@ func GetUserByEmail(tx *sqlx.Tx, email string) (tc.User, bool, error) {
return u, true, nil
}

// GetGlobalParam returns the global parameter with the requested name, whether it existed, and any error
func GetGlobalParam(tx *sql.Tx, name string) (string, bool, error) {
val := ""
if err := tx.QueryRow(`SELECT value FROM parameter WHERE config_file = 'global' and name = $1`, name).Scan(&val); err != nil {
if err == sql.ErrNoRows {
return "", false, nil
}
return "", false, errors.New("querying global parameter '" + name + "': " + err.Error())
}
return val, true, nil
}

// UsernameExists reports whether or not the the given username exists as a user in the database to
// which the passed transaction refers. If anything goes wrong when checking the existence of said
// user, the error is directly returned to the caller. Note that in that case, no real meaning
Expand Down
142 changes: 142 additions & 0 deletions traffic_ops/traffic_ops_golang/deliveryservice/health.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package deliveryservice

/*
* 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"
"errors"
"net/http"
"strings"

"github.com/apache/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/util/monitorhlp"
)

func GetHealth(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()

dsID := inf.IntParams["id"]

userErr, sysErr, errCode = tenant.CheckID(inf.Tx.Tx, inf.User, dsID)
if userErr != nil || sysErr != nil {
api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
return
}

ds, cdn, ok, err := dbhelpers.GetDSNameAndCDNFromID(inf.Tx.Tx, dsID)
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting delivery service name from ID: "+err.Error()))
return
}
if !ok {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, nil, nil)
}

health, err := getHealth(inf.Tx.Tx, ds, cdn)
if err != nil {
api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting delivery service health: "+err.Error()))
return
}

api.WriteResp(w, r, health)
}

func getHealth(tx *sql.Tx, ds tc.DeliveryServiceName, cdn tc.CDNName) (tc.HealthData, error) {
monitors, err := monitorhlp.GetURLs(tx)
if err != nil {
return tc.HealthData{}, errors.New("getting monitors: " + err.Error())
}
monitor, ok := monitors[cdn]
if !ok {
return tc.HealthData{}, nil // TODO emulates old Perl behavior; change to return error?
}
return getMonitorHealth(tx, ds, monitor)
}

func getMonitorHealth(tx *sql.Tx, ds tc.DeliveryServiceName, monitorFQDN string) (tc.HealthData, error) {
client, err := monitorhlp.GetClient(tx)
if err != nil {
return tc.HealthData{}, errors.New("getting monitor client: " + err.Error())
}

totalOnline := uint64(0)
totalOffline := uint64(0)
cgData := map[tc.CacheGroupName]tc.HealthDataCacheGroup{}

crStates, err := monitorhlp.GetCRStates(monitorFQDN, client)
// TODO on err, try another online monitor
if err != nil {
return tc.HealthData{}, errors.New("getting CRStates for delivery service '" + string(ds) + "' monitor '" + monitorFQDN + "': " + err.Error())
Comment thread
ocket8888 marked this conversation as resolved.
}
crConfig, err := monitorhlp.GetCRConfig(monitorFQDN, client)
// TODO on err, try another online monitor
if err != nil {
return tc.HealthData{}, errors.New("getting CRConfig for delivery service '" + string(ds) + "' monitor '" + monitorFQDN + "': " + err.Error())
}
cgData, totalOnline, totalOffline = addHealth(ds, cgData, totalOnline, totalOffline, crStates, crConfig)

healthData := tc.HealthData{TotalOffline: totalOffline, TotalOnline: totalOnline, CacheGroups: []tc.HealthDataCacheGroup{}}
for _, health := range cgData {
healthData.CacheGroups = append(healthData.CacheGroups, health)
}
return healthData, nil
}

// addHealth adds the given cache states to the given data and totals, and returns the new data and totals
func addHealth(ds tc.DeliveryServiceName, data map[tc.CacheGroupName]tc.HealthDataCacheGroup, totalOnline uint64, totalOffline uint64, crStates tc.CRStates, crConfig tc.CRConfig) (map[tc.CacheGroupName]tc.HealthDataCacheGroup, uint64, uint64) {
for cacheName, avail := range crStates.Caches {
cache, ok := crConfig.ContentServers[string(cacheName)]
if !ok {
continue // TODO warn?
}
if _, ok := cache.DeliveryServices[string(ds)]; !ok {
continue
}
if cache.ServerStatus == nil || *cache.ServerStatus != tc.CRConfigServerStatus(tc.CacheStatusReported) {
continue
}
if cache.ServerType == nil || !strings.HasPrefix(string(*cache.ServerType), string(tc.CacheTypeEdge)) {
continue
}
if cache.CacheGroup == nil {
continue // TODO warn?
}

cgHealth := data[tc.CacheGroupName(*cache.CacheGroup)]
cgHealth.Name = tc.CacheGroupName(*cache.CacheGroup)
if avail.IsAvailable {
cgHealth.Online++
totalOnline++
} else {
cgHealth.Offline++
totalOffline++
}
data[tc.CacheGroupName(*cache.CacheGroup)] = cgHealth
}
return data, totalOnline, totalOffline
}
2 changes: 2 additions & 0 deletions traffic_ops/traffic_ops_golang/routing/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,8 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) {
{1.1, http.MethodGet, `deliveryservices/{id}/urlkeys/?(\.json)?$`, deliveryservice.GetURLKeysByID, auth.PrivLevelReadOnly, Authenticated, nil, 393197114, noPerlBypass},
{1.1, http.MethodGet, `riak/bucket/{bucket}/key/{key}/values/?(\.json)?$`, apiriak.GetBucketKey, auth.PrivLevelAdmin, Authenticated, nil, 2020510801, noPerlBypass},

{1.1, http.MethodGet, `deliveryservices/{id}/health/?(\.json)?$`, deliveryservice.GetHealth, auth.PrivLevelReadOnly, Authenticated, nil, 2034590101, perlBypass},

{1.1, http.MethodGet, `steering/{deliveryservice}/targets/?(\.json)?$`, api.ReadHandler(&steeringtargets.TOSteeringTargetV11{}), auth.PrivLevelReadOnly, Authenticated, nil, 1569607824, noPerlBypass},
{1.1, http.MethodGet, `steering/{deliveryservice}/targets/{target}$`, api.ReadHandler(&steeringtargets.TOSteeringTargetV11{}), auth.PrivLevelReadOnly, Authenticated, nil, 105995848, noPerlBypass},
{1.1, http.MethodPost, `steering/{deliveryservice}/targets/?(\.json)?$`, api.CreateHandler(&steeringtargets.TOSteeringTargetV11{}), auth.PrivLevelSteering, Authenticated, nil, 1338216397, noPerlBypass},
Expand Down