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)
+}