trafficcontrol-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dewr...@apache.org
Subject [4/4] incubator-trafficcontrol git commit: Add experimental TO monitoring.json microservice
Date Wed, 05 Jul 2017 18:37:00 GMT
Add experimental TO monitoring.json microservice


Project: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/commit/56eebd2d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/tree/56eebd2d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/diff/56eebd2d

Branch: refs/heads/master
Commit: 56eebd2d1ce275b9debc0d5361e24bb2e95e1d5f
Parents: 9e94ce3
Author: Robert Butts <robert.o.butts@gmail.com>
Authored: Thu Mar 23 10:58:41 2017 -0600
Committer: Dewayne Richardson <dewrich@apache.org>
Committed: Wed Jul 5 12:36:53 2017 -0600

----------------------------------------------------------------------
 .../go-monitoring/to-go-monitoring.go           | 490 +++++++++++++++++++
 1 file changed, 490 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/56eebd2d/traffic_ops/experimental/go-monitoring/to-go-monitoring.go
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/go-monitoring/to-go-monitoring.go b/traffic_ops/experimental/go-monitoring/to-go-monitoring.go
new file mode 100644
index 0000000..9a2ade6
--- /dev/null
+++ b/traffic_ops/experimental/go-monitoring/to-go-monitoring.go
@@ -0,0 +1,490 @@
+// Licensed 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.
+
+package main
+
+import (
+	"database/sql"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"github.com/lib/pq"
+	"net/http"
+	"strings"
+	"time"
+)
+
+const CacheMonitorConfigFile = "rascal.properties"
+const MonitorType = "RASCAL"
+const RouterType = "CCR"
+const MonitorProfilePrefix = "RASCAL"
+const MonitorConfigFile = "rascal-config.txt"
+
+const KilobitsPerMegabit = 1000
+const DeliveryServiceStatus = "REPORTED"
+
+// Args encapsulates the command line arguments
+type Args struct {
+	HTTPPort string
+	DBUser   string
+	DBPass   string
+	DBServer string
+	DBDB     string
+	DBSSL    bool
+}
+
+// getFlags parses and returns the command line arguments. The returned error
+// will be non-nil if any expected arg is missing.
+func getFlags() (Args, error) {
+	var args Args
+	flag.StringVar(&args.HTTPPort, "port", "", "the port to serve on")
+	flag.StringVar(&args.DBUser, "user", "", "the database user")
+	flag.StringVar(&args.DBPass, "pass", "", "the database password")
+	flag.StringVar(&args.DBServer, "server", "", "the database server IP or FQDN, without
scheme")
+	flag.StringVar(&args.DBDB, "db", "", "the database name")
+	flag.BoolVar(&args.DBSSL, "ssl", true, "whether to require or disable SSL connecting
to the database")
+	flag.Parse()
+	if args.HTTPPort == "" {
+		return args, fmt.Errorf("missing port")
+	}
+	if args.DBUser == "" {
+		return args, fmt.Errorf("missing user")
+	}
+	if args.DBPass == "" {
+		return args, fmt.Errorf("missing password")
+	}
+	if args.DBServer == "" {
+		return args, fmt.Errorf("missing server")
+	}
+	if args.DBDB == "" {
+		return args, fmt.Errorf("missing database")
+	}
+	return args, nil
+}
+
+func printUsage() {
+	fmt.Println("Usage:")
+	flag.PrintDefaults()
+	fmt.Println("Example: to-go-monitoring -port 80 -user bill -pass thelizard -server db.to.example.net
-db to")
+}
+
+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"`
+}
+
+type Monitor struct {
+	BasicServer
+}
+
+type Cache struct {
+	BasicServer
+	InterfaceName string `json:"interfaceName"`
+	Type          string `json:"type"`
+	HashID        string `json:"hashId"`
+}
+
+type Cachegroup struct {
+	Name        string      `json:"name"`
+	Coordinates Coordinates `json:"coordinates"`
+}
+
+type Coordinates struct {
+	Latitude  float64 `json:"latitude"`
+	Longitude float64 `json:"longitude"`
+}
+
+type Profile struct {
+	Name       string            `json:"name"`
+	Type       string            `json:"type"`
+	Parameters map[string]string `json:"parameters"`
+}
+
+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]string `json:config`
+}
+
+type MonitoringResponse struct {
+	Response Monitoring `json:"response"`
+}
+
+type Router struct {
+	Type    string
+	Profile string
+}
+
+type DeliveryService struct {
+	XMLID              string  `json:"xmlId"`
+	TotalTPSThreshold  float64 `json:"totalTpsThreshold"`
+	Status             string  `json:"status"`
+	TotalKBPSThreshold float64 `json:"totalKbpsThreshold"`
+}
+
+func getServers(db *sql.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 *sql.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 *sql.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]string{}
+		}
+		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 *sql.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 *sql.DB) (map[string]string, 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]string{}
+
+	for rows.Next() {
+		var name sql.NullString
+		var val sql.NullString
+		if err := rows.Scan(&name, &val); err != nil {
+			return nil, err
+		}
+		cfg[name.String] = val.String
+	}
+	return cfg, nil
+}
+
+func getMonitoringJson(cdnName string, db *sql.DB) (*MonitoringResponse, error) {
+	monitors, caches, routers, err := getServers(db, cdnName)
+	if err != nil {
+		return nil, fmt.Errorf("error getting servers: %v", err)
+	}
+
+	cachegroups, err := getCachegroups(db, cdnName)
+	if err != nil {
+		return nil, fmt.Errorf("error getting cachegroups: %v", err)
+	}
+
+	profiles, err := getProfiles(db, caches, routers)
+	if err != nil {
+		return nil, fmt.Errorf("error getting profiles: %v", err)
+	}
+
+	deliveryServices, err := getDeliveryServices(db, routers)
+	if err != nil {
+		return nil, fmt.Errorf("error getting deliveryservices: %v", err)
+	}
+
+	config, err := getConfig(db)
+	if err != nil {
+		return nil, fmt.Errorf("error getting config: %v", err)
+	}
+
+	resp := MonitoringResponse{
+		Response: Monitoring{
+			TrafficServers:   caches,
+			TrafficMonitors:  monitors,
+			Cachegroups:      cachegroups,
+			Profiles:         profiles,
+			DeliveryServices: deliveryServices,
+			Config:           config,
+		},
+	}
+	return &resp, nil
+}
+
+func rootHandler(w http.ResponseWriter, r *http.Request, db *sql.DB) {
+	handleErr := func(err error) {
+		fmt.Printf("%v %v error %v\n", time.Now(), r.RemoteAddr, err)
+		w.WriteHeader(http.StatusInternalServerError)
+		fmt.Fprintf(w, "Internal Server Error")
+	}
+
+	pathParts := strings.Split(r.URL.Path, "/")
+	if len(pathParts) < 5 {
+		w.WriteHeader(http.StatusNotFound)
+		fmt.Fprintf(w, "404 Not Found")
+		return
+	}
+	cdnName := pathParts[4]
+
+	resp, err := getMonitoringJson(cdnName, db)
+	if err != nil {
+		handleErr(err)
+		return
+	}
+
+	respBts, err := json.Marshal(resp)
+	if err != nil {
+		handleErr(err)
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	fmt.Fprintf(w, "%s", respBts)
+}
+
+func main() {
+	args, err := getFlags()
+	if err != nil {
+		fmt.Println(err)
+		printUsage()
+		return
+	}
+
+	sslStr := "require"
+	if !args.DBSSL {
+		sslStr = "disable"
+	}
+
+	db, err := sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s", args.DBUser,
args.DBPass, args.DBServer, args.DBDB, sslStr))
+	if err != nil {
+		fmt.Printf("Error opening database: %v\n", err)
+		return
+	}
+	defer db.Close()
+
+	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		rootHandler(w, r, db)
+	})
+
+	if err := http.ListenAndServe(":"+args.HTTPPort, nil); err != nil {
+		fmt.Printf("Error stopping server: %v\n", err)
+		return
+	}
+}


Mime
View raw message