You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by de...@apache.org on 2017/08/10 16:04:48 UTC
[07/43] incubator-trafficcontrol git commit: Add experimental Go TO
proxying old Perl app
Add experimental Go TO proxying old Perl app
Project: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/commit/aa901953
Tree: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/tree/aa901953
Diff: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/diff/aa901953
Branch: refs/heads/master
Commit: aa901953ae5ba309ae97a793cf90818b0096ea08
Parents: 9c4eccc
Author: Robert Butts <ro...@gmail.com>
Authored: Sat Jul 8 21:02:56 2017 -0600
Committer: Dewayne Richardson <de...@apache.org>
Committed: Thu Aug 10 09:46:02 2017 -0600
----------------------------------------------------------------------
.../go-monitoring/to-go-monitoring.go | 528 -------------------
traffic_ops/experimental/goto/config.go | 80 +++
traffic_ops/experimental/goto/config.json | 13 +
traffic_ops/experimental/goto/goto.go | 56 ++
traffic_ops/experimental/goto/monitoring.go | 398 ++++++++++++++
traffic_ops/experimental/goto/routes.go | 95 ++++
traffic_ops/experimental/goto/wrappers.go | 55 ++
traffic_ops/experimental/tocookie/tocookie.go | 6 +
8 files changed, 703 insertions(+), 528 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aa901953/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
deleted file mode 100644
index a508212..0000000
--- a/traffic_ops/experimental/go-monitoring/to-go-monitoring.go
+++ /dev/null
@@ -1,528 +0,0 @@
-// 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/apache/incubator-trafficcontrol/traffic_ops/experimental/tocookie"
- "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
- Auth bool
- TOSecret string
-}
-
-// 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.StringVar(&args.TOSecret, "secret", "", "the Traffic Ops secret, used to authenticate mojolicious cookies")
- flag.BoolVar(&args.Auth, "authenticate", true, "whether to authenticate requests, requiring valid Traffic Ops cookies")
- 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")
- }
- if args.Auth && args.TOSecret == "" {
- return args, fmt.Errorf("missing secret")
- }
- 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
-}
-
-// authenticate attempts to authenticate the given request, looking for an auth cookie, using the auth settings in args. Returns nil if authentication succeeds, or a descriptive error if authentication fails (which should NOT be returned to clients, for security reasons).
-func authenticate(r *http.Request, args Args) error {
- if !args.Auth {
- return nil
- }
-
- cookie, err := r.Cookie(tocookie.Name)
- if err != nil {
- return fmt.Errorf("getting cookie: %v", err)
- }
- if cookie == nil {
- return fmt.Errorf("no auth cookie")
- }
-
- if _, err := tocookie.Parse(args.TOSecret, cookie.Value); err != nil {
- return fmt.Errorf("parsing cookie: %v", err)
- }
- return nil
-}
-
-func rootHandler(w http.ResponseWriter, r *http.Request, db *sql.DB, args Args) {
- start := time.Now()
- defer func() {
- now := time.Now()
- fmt.Printf("%v %v served %v in %v\n", now, r.RemoteAddr, r.URL.Path, now.Sub(start))
- }()
-
- handleErr := func(err error, status int) {
- fmt.Printf("%v %v error %v\n", time.Now(), r.RemoteAddr, err)
- w.WriteHeader(status)
- fmt.Fprintf(w, http.StatusText(status))
- }
-
- if err := authenticate(r, args); err != nil {
- handleErr(err, http.StatusUnauthorized)
- return
- }
-
- pathParts := strings.Split(r.URL.Path, "/")
- if len(pathParts) < 5 {
- handleErr(fmt.Errorf("nonexistent path requested: '%v'", r.URL.Path), http.StatusNotFound)
- return
- }
- cdnName := pathParts[4]
-
- resp, err := getMonitoringJson(cdnName, db)
- if err != nil {
- handleErr(err, http.StatusInternalServerError)
- return
- }
-
- respBts, err := json.Marshal(resp)
- if err != nil {
- handleErr(err, http.StatusInternalServerError)
- 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, args)
- })
-
- if err := http.ListenAndServe(":"+args.HTTPPort, nil); err != nil {
- fmt.Printf("Error stopping server: %v\n", err)
- return
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aa901953/traffic_ops/experimental/goto/config.go
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/goto/config.go b/traffic_ops/experimental/goto/config.go
new file mode 100644
index 0000000..a0f3401
--- /dev/null
+++ b/traffic_ops/experimental/goto/config.go
@@ -0,0 +1,80 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/url"
+)
+
+type Config struct {
+ HTTPPort string `json:"port"`
+ DBUser string `json:"db_user"`
+ DBPass string `json:"db_pass"`
+ DBServer string `json:"db_server"`
+ DBDB string `json:"db_name"`
+ DBSSL bool `json:"db_ssl"`
+ TOSecret string `json:"to_secret"`
+ TOURLStr string `json:"to_url"`
+ TOURL *url.URL `json:"-"`
+ NoAuth bool `json:"no_auth"`
+ CertPath string `json:"cert_path"`
+ KeyPath string `json:"key_path"`
+}
+
+func LoadConfig(fileName string) (Config, error) {
+ if fileName == "" {
+ return Config{}, fmt.Errorf("no filename")
+ }
+
+ configBytes, err := ioutil.ReadFile(fileName)
+ if err != nil {
+ return Config{}, err
+ }
+
+ cfg := Config{}
+ if err := json.Unmarshal(configBytes, &cfg); err != nil {
+ return Config{}, err
+ }
+
+ if cfg, err = ParseConfig(cfg); err != nil {
+ return Config{}, err
+ }
+
+ return cfg, nil
+}
+
+// ParseConfig validates required fields, and parses non-JSON types
+func ParseConfig(cfg Config) (Config, error) {
+ if cfg.HTTPPort == "" {
+ return Config{}, fmt.Errorf("missing port")
+ }
+ if cfg.DBUser == "" {
+ return Config{}, fmt.Errorf("missing database user")
+ }
+ if cfg.DBPass == "" {
+ return Config{}, fmt.Errorf("missing database password")
+ }
+ if cfg.DBServer == "" {
+ return Config{}, fmt.Errorf("missing database server")
+ }
+ if cfg.DBDB == "" {
+ return Config{}, fmt.Errorf("missing database name")
+ }
+ if cfg.TOSecret == "" {
+ return Config{}, fmt.Errorf("missing secret")
+ }
+ if cfg.CertPath == "" {
+ return Config{}, fmt.Errorf("missing certificate path")
+ }
+ if cfg.KeyPath == "" {
+ return Config{}, fmt.Errorf("missing certificate key path")
+ }
+
+ var err error
+ if cfg.TOURL, err = url.Parse(cfg.TOURLStr); err != nil {
+ return Config{}, fmt.Errorf("Invalid Traffic Ops URL '%v': err", cfg.TOURL, err)
+ }
+
+ return cfg, nil
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aa901953/traffic_ops/experimental/goto/config.json
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/goto/config.json b/traffic_ops/experimental/goto/config.json
new file mode 100644
index 0000000..19d2599
--- /dev/null
+++ b/traffic_ops/experimental/goto/config.json
@@ -0,0 +1,13 @@
+{
+ "port": "443",
+ "db_user": "bill",
+ "db_pass": "thelizard",
+ "db_server": "db.trafficops.example.net",
+ "db_name": "trafficops",
+ "db_ssl": true,
+ "to_secret": "walrus",
+ "to_url": "https://trafficops.example.net:60443",
+ "no_auth": false,
+ "cert_path": "/opt/traffic_ops/cert",
+ "key_path": "/opt/traffic_ops/key"
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aa901953/traffic_ops/experimental/goto/goto.go
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/goto/goto.go b/traffic_ops/experimental/goto/goto.go
new file mode 100644
index 0000000..48d3c4e
--- /dev/null
+++ b/traffic_ops/experimental/goto/goto.go
@@ -0,0 +1,56 @@
+// 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"
+ "flag"
+ "fmt"
+ _ "github.com/lib/pq"
+ "net/http"
+)
+
+const DefaultConfigPath = "/etc/goto/config.json"
+
+func main() {
+ configFileName := flag.String("cfg", "", "The config file path")
+ flag.Parse()
+ if *configFileName == "" {
+ *configFileName = DefaultConfigPath
+ }
+
+ cfg, err := LoadConfig(*configFileName)
+ if err != nil {
+ fmt.Println("Error loading config '" + *configFileName + "': " + err.Error())
+ return
+ }
+
+ sslStr := "require"
+ if !cfg.DBSSL {
+ sslStr = "disable"
+ }
+
+ db, err := sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s", cfg.DBUser, cfg.DBPass, cfg.DBServer, cfg.DBDB, sslStr))
+ if err != nil {
+ fmt.Printf("Error opening database: %v\n", err)
+ return
+ }
+ defer db.Close()
+
+ RegisterRoutes(ServerData{DB: db, Config: cfg})
+ fmt.Println("Listening on " + cfg.HTTPPort)
+ if err := http.ListenAndServeTLS(":"+cfg.HTTPPort, cfg.CertPath, cfg.KeyPath, nil); err != nil {
+ fmt.Printf("Error stopping server: %v\n", err)
+ return
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aa901953/traffic_ops/experimental/goto/monitoring.go
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/goto/monitoring.go b/traffic_ops/experimental/goto/monitoring.go
new file mode 100644
index 0000000..b353fb5
--- /dev/null
+++ b/traffic_ops/experimental/goto/monitoring.go
@@ -0,0 +1,398 @@
+package main
+
+import (
+ "database/sql"
+ "encoding/json"
+ "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"
+
+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"`
+}
+
+// TODO change to use the ParamMap, instead of parsing the URL
+func monitoringHandler(db *sql.DB) RegexHandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request, p ParamMap) {
+ handleErr := func(err error, status int) {
+ fmt.Printf("%v %v error %v\n", time.Now(), r.RemoteAddr, err)
+ w.WriteHeader(status)
+ fmt.Fprintf(w, http.StatusText(status))
+ }
+
+ cdnName := p["cdn"]
+
+ resp, err := getMonitoringJson(cdnName, db)
+ if err != nil {
+ handleErr(err, http.StatusInternalServerError)
+ return
+ }
+
+ respBts, err := json.Marshal(resp)
+ if err != nil {
+ handleErr(err, http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ fmt.Fprintf(w, "%s", respBts)
+ }
+}
+
+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
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aa901953/traffic_ops/experimental/goto/routes.go
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/goto/routes.go b/traffic_ops/experimental/goto/routes.go
new file mode 100644
index 0000000..cc14450
--- /dev/null
+++ b/traffic_ops/experimental/goto/routes.go
@@ -0,0 +1,95 @@
+package main
+
+import (
+ "crypto/tls"
+ "database/sql"
+ "net/http"
+ "net/http/httputil"
+ "regexp"
+ "strings"
+)
+
+type ServerData struct {
+ Config
+ DB *sql.DB
+}
+
+type ParamMap map[string]string
+
+type RegexHandlerFunc func(w http.ResponseWriter, r *http.Request, params ParamMap)
+
+func getMonitoringRoute(d ServerData) RegexHandlerFunc {
+ return wrapLogTime(wrapAuth(monitoringHandler(d.DB), d.NoAuth, d.TOSecret))
+}
+
+// getRootHandler returns the / handler for the service, which reverse-proxies the old Perl Traffic Ops
+func getRootHandler(d ServerData) http.Handler {
+ // debug
+ tr := &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+ }
+ rp := httputil.NewSingleHostReverseProxy(d.TOURL)
+ rp.Transport = tr
+ return rp
+}
+
+// GetRoutes returns the map of regex routes, and a catchall route if no regex matches.
+func GetRoutes(d ServerData) (map[string]RegexHandlerFunc, http.Handler) {
+ return map[string]RegexHandlerFunc{
+ "api/1.2/cdns/{cdn}/configs/monitoring.json": getMonitoringRoute(d),
+ }, getRootHandler(d)
+}
+
+type CompiledRoute struct {
+ Handler RegexHandlerFunc
+ Regex *regexp.Regexp
+ Params []string
+}
+
+func CompileRoutes(routes *map[string]RegexHandlerFunc) map[string]CompiledRoute {
+ compiledRoutes := map[string]CompiledRoute{}
+ for route, handler := range *routes {
+ originalRoute := route
+ var params []string
+ for open := strings.Index(route, "{"); open > 0; open = strings.Index(route, "{") {
+ close := strings.Index(route, "}")
+ if close < 0 {
+ panic("malformed route")
+ }
+ param := route[open+1 : close]
+
+ params = append(params, param)
+ route = route[:open] + `(.+)` + route[close+1:]
+ }
+ regex := regexp.MustCompile(route)
+ compiledRoutes[originalRoute] = CompiledRoute{Handler: handler, Regex: regex, Params: params}
+ }
+ return compiledRoutes
+}
+
+func Handler(routes map[string]CompiledRoute, catchall http.Handler, w http.ResponseWriter, r *http.Request) {
+ requested := r.URL.Path[1:]
+
+ for _, compiledRoute := range routes {
+ match := compiledRoute.Regex.FindStringSubmatch(requested)
+ if len(match) == 0 {
+ continue
+ }
+
+ params := map[string]string{}
+ for i, v := range compiledRoute.Params {
+ params[v] = match[i+1]
+ }
+ compiledRoute.Handler(w, r, params)
+ return
+ }
+ catchall.ServeHTTP(w, r)
+}
+
+func RegisterRoutes(d ServerData) {
+ routes, catchall := GetRoutes(d)
+ compiledRoutes := CompileRoutes(&routes)
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ Handler(compiledRoutes, catchall, w, r)
+ })
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aa901953/traffic_ops/experimental/goto/wrappers.go
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/goto/wrappers.go b/traffic_ops/experimental/goto/wrappers.go
new file mode 100644
index 0000000..daf5e34
--- /dev/null
+++ b/traffic_ops/experimental/goto/wrappers.go
@@ -0,0 +1,55 @@
+package main
+
+import (
+ "fmt"
+ "github.com/apache/incubator-trafficcontrol/traffic_ops/experimental/tocookie"
+ "log" // TODO change to traffic_monitor_golang/common/log
+ "net/http"
+ "time"
+)
+
+func wrapAuth(h RegexHandlerFunc, noAuth bool, secret string) RegexHandlerFunc {
+ if noAuth {
+ return h
+ }
+ return func(w http.ResponseWriter, r *http.Request, p ParamMap) {
+ handleUnauthorized := func(reason string) {
+ log.Printf("%v %v %v sent 401 - %v\n", time.Now(), r.RemoteAddr, r.URL.Path, reason)
+ status := http.StatusUnauthorized
+ w.WriteHeader(status)
+ fmt.Fprintf(w, http.StatusText(status))
+ }
+
+ cookie, err := r.Cookie(tocookie.Name)
+ if err != nil {
+ handleUnauthorized("error getting cookie: " + err.Error())
+ return
+ }
+
+ if cookie == nil {
+ handleUnauthorized("no auth cookie")
+ return
+ }
+
+ oldCookie, err := tocookie.Parse(secret, cookie.Value)
+ if err != nil {
+ handleUnauthorized("cookie error: " + err.Error())
+ return
+ }
+
+ newCookieVal := tocookie.Refresh(oldCookie, secret)
+ http.SetCookie(w, &http.Cookie{Name: tocookie.Name, Value: newCookieVal})
+ h(w, r, p)
+ }
+}
+
+func wrapLogTime(h RegexHandlerFunc) RegexHandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request, p ParamMap) {
+ start := time.Now()
+ defer func() {
+ now := time.Now()
+ log.Printf("%v %v served %v in %v\n", now, r.RemoteAddr, r.URL.Path, now.Sub(start))
+ }()
+ h(w, r, p)
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/aa901953/traffic_ops/experimental/tocookie/tocookie.go
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/tocookie/tocookie.go b/traffic_ops/experimental/tocookie/tocookie.go
index 4dd319b..658224d 100644
--- a/traffic_ops/experimental/tocookie/tocookie.go
+++ b/traffic_ops/experimental/tocookie/tocookie.go
@@ -24,6 +24,7 @@ import (
)
const Name = "mojolicious"
+const DefaultDuration = time.Hour
type Cookie struct {
AuthData string `json:"auth_data"`
@@ -95,3 +96,8 @@ func New(user string, expiration time.Time, key string) string {
msg, _ := json.Marshal(cookieMsg)
return NewRawMsg(msg, []byte(key))
}
+
+// Update takes an existing cookie and returns a new serialized cookie with an updated expiration
+func Refresh(c *Cookie, key string) string {
+ return New(c.AuthData, time.Now().Add(DefaultDuration), key)
+}