blob: 57f1de81b8d3f64f5ddca21fdb28b98858445056 [file] [log] [blame]
package main
/*
* 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"
"fmt"
"net/http"
"strconv"
"strings"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
"github.com/apache/incubator-trafficcontrol/lib/go-log"
"github.com/apache/incubator-trafficcontrol/lib/go-tc"
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
)
// CacheMonitorConfigFile ...
const CacheMonitorConfigFile = "rascal.properties"
// MonitorType ...
const MonitorType = "RASCAL"
// RouterType ...
const RouterType = "CCR"
// MonitorProfilePrefix ...
const MonitorProfilePrefix = "RASCAL"
// MonitorConfigFile ...
const MonitorConfigFile = "rascal-config.txt"
// KilobitsPerMegabit ...
const KilobitsPerMegabit = 1000
// DeliveryServiceStatus ...
const DeliveryServiceStatus = "REPORTED"
// BasicServer ...
type BasicServer struct {
Profile string `json:"profile"`
Status string `json:"status"`
IP string `json:"ip"`
IP6 string `json:"ip6"`
Port int `json:"port"`
Cachegroup string `json:"cachegroup"`
HostName string `json:"hostname"`
FQDN string `json:"fqdn"`
}
// Monitor ...
type Monitor struct {
BasicServer
}
// Cache ...
type Cache struct {
BasicServer
InterfaceName string `json:"interfacename"`
Type string `json:"type"`
HashID string `json:"hashid"`
}
// Cachegroup ...
type Cachegroup struct {
Name string `json:"name"`
Coordinates Coordinates `json:"coordinates"`
}
// Coordinates ...
type Coordinates struct {
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
}
// Profile ...
type Profile struct {
Name string `json:"name"`
Type string `json:"type"`
Parameters map[string]interface{} `json:"parameters"`
}
// Monitoring ...
type Monitoring struct {
TrafficServers []Cache `json:"trafficServers"`
TrafficMonitors []Monitor `json:"trafficMonitors"`
Cachegroups []Cachegroup `json:"cacheGroups"`
Profiles []Profile `json:"profiles"`
DeliveryServices []DeliveryService `json:"deliveryServices"`
Config map[string]interface{} `json:"config"`
}
// MonitoringResponse ...
type MonitoringResponse struct {
Response Monitoring `json:"response"`
}
// Router ...
type Router struct {
Type string
Profile string
}
// DeliveryService ...
type DeliveryService struct {
XMLID string `json:"xmlId"`
TotalTPSThreshold float64 `json:"totalTpsThreshold"`
Status string `json:"status"`
TotalKBPSThreshold float64 `json:"totalKbpsThreshold"`
}
// TODO change to use the PathParams, instead of parsing the URL
func monitoringHandler(db *sqlx.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
handleErrs := tc.GetHandleErrorsFunc(w, r)
params, err := api.GetCombinedParams(r)
if err != nil {
log.Errorf("unable to get parameters from request: %s", err)
handleErrs(http.StatusInternalServerError, err)
}
cdnName := params["name"]
resp, err, errType := getMonitoringJSON(cdnName, db)
if err != nil {
tc.HandleErrorsWithType([]error{err}, errType, handleErrs)
return
}
respBts, err := json.Marshal(resp)
if err != nil {
handleErrs(http.StatusInternalServerError, err)
return
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, "%s", respBts)
}
}
func getMonitoringServers(db *sqlx.DB, cdn string) ([]Monitor, []Cache, []Router, error) {
query := `SELECT
me.host_name as hostName,
CONCAT(me.host_name, '.', me.domain_name) as fqdn,
status.name as status,
cachegroup.name as cachegroup,
me.tcp_port as port,
me.ip_address as ip,
me.ip6_address as ip6,
profile.name as profile,
me.interface_name as interfaceName,
type.name as type,
me.xmpp_id as hashID
FROM server me
JOIN type type ON type.id = me.type
JOIN status status ON status.id = me.status
JOIN cachegroup cachegroup ON cachegroup.id = me.cachegroup
JOIN profile profile ON profile.id = me.profile
JOIN cdn cdn ON cdn.id = me.cdn_id
WHERE cdn.name = $1`
rows, err := db.Query(query, cdn)
if err != nil {
return nil, nil, nil, err
}
defer rows.Close()
monitors := []Monitor{}
caches := []Cache{}
routers := []Router{}
for rows.Next() {
var hostName sql.NullString
var fqdn sql.NullString
var status sql.NullString
var cachegroup sql.NullString
var port sql.NullInt64
var ip sql.NullString
var ip6 sql.NullString
var profile sql.NullString
var interfaceName sql.NullString
var ttype sql.NullString
var hashID sql.NullString
if err := rows.Scan(&hostName, &fqdn, &status, &cachegroup, &port, &ip, &ip6, &profile, &interfaceName, &ttype, &hashID); err != nil {
return nil, nil, nil, err
}
if ttype.String == MonitorType {
monitors = append(monitors, Monitor{
BasicServer: BasicServer{
Profile: profile.String,
Status: status.String,
IP: ip.String,
IP6: ip6.String,
Port: int(port.Int64),
Cachegroup: cachegroup.String,
HostName: hostName.String,
FQDN: fqdn.String,
},
})
} else if strings.HasPrefix(ttype.String, "EDGE") || strings.HasPrefix(ttype.String, "MID") {
caches = append(caches, Cache{
BasicServer: BasicServer{
Profile: profile.String,
Status: status.String,
IP: ip.String,
IP6: ip6.String,
Port: int(port.Int64),
Cachegroup: cachegroup.String,
HostName: hostName.String,
FQDN: fqdn.String,
},
InterfaceName: interfaceName.String,
Type: ttype.String,
HashID: hashID.String,
})
} else if ttype.String == RouterType {
routers = append(routers, Router{
Type: ttype.String,
Profile: profile.String,
})
}
}
return monitors, caches, routers, nil
}
func getCachegroups(db *sqlx.DB, cdn string) ([]Cachegroup, error) {
query := `
SELECT name, latitude, longitude
FROM cachegroup
WHERE id IN
(SELECT cachegroup FROM server WHERE server.cdn_id =
(SELECT id FROM cdn WHERE name = $1));`
rows, err := db.Query(query, cdn)
if err != nil {
return nil, err
}
defer rows.Close()
cachegroups := []Cachegroup{}
for rows.Next() {
var name sql.NullString
var lat sql.NullFloat64
var lon sql.NullFloat64
if err := rows.Scan(&name, &lat, &lon); err != nil {
return nil, err
}
cachegroups = append(cachegroups, Cachegroup{
Name: name.String,
Coordinates: Coordinates{
Latitude: lat.Float64,
Longitude: lon.Float64,
},
})
}
return cachegroups, nil
}
func getProfiles(db *sqlx.DB, caches []Cache, routers []Router) ([]Profile, error) {
cacheProfileTypes := map[string]string{}
profiles := map[string]Profile{}
profileNames := []string{}
for _, router := range routers {
profiles[router.Profile] = Profile{
Name: router.Profile,
Type: router.Type,
}
}
for _, cache := range caches {
if _, ok := cacheProfileTypes[cache.Profile]; !ok {
cacheProfileTypes[cache.Profile] = cache.Type
profiles[cache.Profile] = Profile{
Name: cache.Profile,
Type: cache.Type,
}
profileNames = append(profileNames, cache.Profile)
}
}
query := `
SELECT p.name as profile, pr.name, pr.value
FROM parameter pr
JOIN profile p ON p.name = ANY($1)
JOIN profile_parameter pp ON pp.profile = p.id and pp.parameter = pr.id
WHERE pr.config_file = $2;
`
rows, err := db.Query(query, pq.Array(profileNames), CacheMonitorConfigFile)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var profileName sql.NullString
var name sql.NullString
var value sql.NullString
if err := rows.Scan(&profileName, &name, &value); err != nil {
return nil, err
}
if name.String == "" {
return nil, fmt.Errorf("null name") // TODO continue and warn?
}
profile := profiles[profileName.String]
if profile.Parameters == nil {
profile.Parameters = map[string]interface{}{}
}
if valNum, err := strconv.Atoi(value.String); err == nil {
profile.Parameters[name.String] = valNum
} else {
profile.Parameters[name.String] = value.String
}
profiles[profileName.String] = profile
}
profilesArr := []Profile{} // TODO make for efficiency?
for _, profile := range profiles {
profilesArr = append(profilesArr, profile)
}
return profilesArr, nil
}
func getDeliveryServices(db *sqlx.DB, routers []Router) ([]DeliveryService, error) {
profileNames := []string{}
for _, router := range routers {
profileNames = append(profileNames, router.Profile)
}
query := `
SELECT ds.xml_id, ds.global_max_tps, ds.global_max_mbps
FROM deliveryservice ds
JOIN profile profile ON profile.id = ds.profile
WHERE profile.name = ANY($1)
AND ds.active = true
`
rows, err := db.Query(query, pq.Array(profileNames))
if err != nil {
return nil, err
}
defer rows.Close()
dses := []DeliveryService{}
for rows.Next() {
var xmlid sql.NullString
var tps sql.NullFloat64
var mbps sql.NullFloat64
if err := rows.Scan(&xmlid, &tps, &mbps); err != nil {
return nil, err
}
dses = append(dses, DeliveryService{
XMLID: xmlid.String,
TotalTPSThreshold: tps.Float64,
Status: DeliveryServiceStatus,
TotalKBPSThreshold: mbps.Float64 * KilobitsPerMegabit,
})
}
return dses, nil
}
func getConfig(db *sqlx.DB) (map[string]interface{}, error) {
// TODO remove 'like' in query? Slow?
query := fmt.Sprintf(`
SELECT pr.name, pr.value
FROM parameter pr
JOIN profile p ON p.name LIKE '%s%%'
JOIN profile_parameter pp ON pp.profile = p.id and pp.parameter = pr.id
WHERE pr.config_file = '%s'
`, MonitorProfilePrefix, MonitorConfigFile)
rows, err := db.Query(query)
if err != nil {
return nil, err
}
defer rows.Close()
cfg := map[string]interface{}{}
for rows.Next() {
var name sql.NullString
var val sql.NullString
if err := rows.Scan(&name, &val); err != nil {
return nil, err
}
if valNum, err := strconv.Atoi(val.String); err == nil {
cfg[name.String] = valNum
} else {
cfg[name.String] = val.String
}
}
return cfg, nil
}
func getMonitoringJSON(cdnName string, db *sqlx.DB) (*MonitoringResponse, error, tc.ApiErrorType) {
monitors, caches, routers, err := getMonitoringServers(db, cdnName)
if err != nil {
return nil, fmt.Errorf("error getting servers: %v", err), tc.SystemError
}
cachegroups, err := getCachegroups(db, cdnName)
if err != nil {
return nil, fmt.Errorf("error getting cachegroups: %v", err), tc.SystemError
}
profiles, err := getProfiles(db, caches, routers)
if err != nil {
return nil, fmt.Errorf("error getting profiles: %v", err), tc.SystemError
}
deliveryServices, err := getDeliveryServices(db, routers)
if err != nil {
return nil, fmt.Errorf("error getting deliveryservices: %v", err), tc.SystemError
}
config, err := getConfig(db)
if err != nil {
return nil, fmt.Errorf("error getting config: %v", err), tc.SystemError
}
resp := MonitoringResponse{
Response: Monitoring{
TrafficServers: caches,
TrafficMonitors: monitors,
Cachegroups: cachegroups,
Profiles: profiles,
DeliveryServices: deliveryServices,
Config: config,
},
}
return &resp, nil, tc.NoError
}