You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ra...@apache.org on 2022/05/10 14:57:52 UTC

[trafficcontrol] branch master updated: Convert Traffic Ops into a Service Oriented architecture(SOA) product (#6754)

This is an automated email from the ASF dual-hosted git repository.

rawlin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new 000c1b9133 Convert Traffic Ops into a Service Oriented architecture(SOA) product (#6754)
000c1b9133 is described below

commit 000c1b913317f3936f8ed3491030ed17052fcbba
Author: Srijeet Chatterjee <30...@users.noreply.github.com>
AuthorDate: Tue May 10 08:57:46 2022 -0600

    Convert Traffic Ops into a Service Oriented architecture(SOA) product (#6754)
    
    * initial changes
    
    * working changes
    
    * formatting and cleanup
    
    * cleanup
    
    * adding log handlers
    
    * add dcos, cleanup
    
    * add todos
    
    * add todos
    
    * cleanup config
    
    * add insecure option to backend config
    
    * Adding proper log handlers
    
    * remove priv level from backend config
    
    * code review first pass
    
    * code review fixes final
    
    * change log call
    
    * change mutex type
    
    * addressing code review comments
    
    * change regex handling
---
 CHANGELOG.md                                       |   1 +
 docs/source/admin/traffic_ops.rst                  |  34 +++++-
 traffic_ops/app/conf/backends.conf                 |  52 +++++++++
 traffic_ops/app/conf/production/backends.conf      |   3 +
 traffic_ops/etc/init.d/traffic_ops                 |   2 +-
 traffic_ops/traffic_ops_golang/config/config.go    |  54 +++++++++
 .../routing/middleware/wrappers.go                 |  10 +-
 traffic_ops/traffic_ops_golang/routing/routing.go  | 123 ++++++++++++++++++++-
 .../traffic_ops_golang/traffic_ops_golang.go       |  40 ++++++-
 9 files changed, 308 insertions(+), 11 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a6c302f728..7c6565a933 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 - Replaces all Traffic Portal Tenant select boxes with a novel tree select box [#6427](https://github.com/apache/trafficcontrol/issues/6427).
 - Traffic Monitor: Add support for `access.log` to TM.
 - Added functionality for login to provide a Bearer token and for that token to be later used for authorization.
+- [Traffic Ops] Added support for backend configurations so that Traffic Ops can act as a reverse proxy for these services [#6754](https://github.com/apache/trafficcontrol/pull/6754).
 - Added functionality for CDN locks, so that they can be shared amongst a list of specified usernames. 
 - [Traffic Ops | Traffic Go Clients | T3C] Add additional timestamp fields to server for queuing and dequeueing config and revalidate updates.
 - Added layered profile feature to 4.0 for `GET` /servers/, `POST` /servers/, `PUT` /servers/{id} and `DELETE` /servers/{id}.
diff --git a/docs/source/admin/traffic_ops.rst b/docs/source/admin/traffic_ops.rst
index d0b1d84120..6ea3cbf479 100644
--- a/docs/source/admin/traffic_ops.rst
+++ b/docs/source/admin/traffic_ops.rst
@@ -19,7 +19,7 @@
 ***********
 Traffic Ops
 ***********
-Traffic Ops is quite possible the single most complex and most important Traffic Control component. It has many different configuration options that affect a wide range of other components and their interactions.
+Traffic Ops is quite possibly the single most complex and most important Traffic Control component. It has many different configuration options that affect a wide range of other components and their interactions.
 
 .. _to-install:
 
@@ -257,7 +257,7 @@ While this section contains instructions for running Traffic Ops manually, the o
 
 traffic_ops_golang
 ------------------
-``traffic_ops_golang [--version] [--plugins] [--api-routes] --cfg CONFIG_PATH --dbcfg DB_CONFIG_PATH [--riakcfg RIAK_CONFIG_PATH]``
+``traffic_ops_golang [--version] [--plugins] [--api-routes] --cfg CONFIG_PATH --dbcfg DB_CONFIG_PATH [--riakcfg RIAK_CONFIG_PATH] [--backendcfg BACKEND_CONFIG_PATH]``
 
 .. option:: --cfg CONFIG_PATH
 
@@ -282,6 +282,10 @@ traffic_ops_golang
 
 	.. impl-detail:: The name of this flag is derived from the current database used in the implementation of Traffic Vault - `Riak KV <https://riak.com/products/riak-kv/index.html>`_.
 
+.. option:: --backendcfg BACKEND_CONFIG_PATH
+
+	This optional command line flag specifies the absolute or relative path to a configuration file used by Traffic Ops to act as a reverse proxy and forward requests on the specified paths to the corresponding hosts - `backends.conf`_
+
 .. option:: --version
 
 	Print version information and exit.
@@ -586,6 +590,32 @@ This file sets authentication options for connections to Riak when used as the T
 
 .. impl-detail:: The name of this file is derived from the current database used in the implementation of Traffic Vault - `Riak KV <https://riak.com/products/riak-kv/index.html>`_.
 
+backends.conf
+"""""""""""""
+This file deals with the configuration parameters of running Traffic Ops as a reverse proxy for certain endpoints that need to be served externally by other backend services. It is a JSON-format set of options and their respective values. `traffic_ops_golang`_ will use whatever file is specified (if any) by its :option:`--backendcfg` option. The keys of the file are described below.
+
+:routes: This is an array of options to configure Traffic Ops to forward requests of specified types to the appropriate backends.
+
+	:path:              The regex matching the endpoint that will be served by the backend, for example, :regexp:`^/api/4.0/foo?$`.
+	:method:            The HTTP method for the above mentioned path, for example, ``GET`` or ``PUT``.
+	:routeId:           The integral identifier for the new route being added.
+	:hosts:             An array of the host object, which specifies the protocol, hostname and port where the request (if matched) needs to be forwarded to.
+
+		:protocol:     The protocol/scheme to be followed while forwarding the requests to the backend service.
+		:hostname:     The hostname of the server where the backend service is running.
+		:port:         The port (integer) on the backend server where the service is running.
+
+	:insecure:          A boolean specifying whether or not TO should verify the backend server's certificate chain and host name. This is not recommended for production use. This is an optional parameter, defaulting to ``false`` when not present.
+	:permissions:       An array of permissions (strings) specifying the permissions required by the user to use this API route.
+	:opts:              A collection of key value pairs to control how the requests should be forwarded/ handled, for example, ``"alg": "roundrobin"``. Currently, only ``roundrobin`` is supported (which is also the default if nothing is specified) by Traffic Ops.
+
+Example backends.conf
+'''''''''''''''''''''
+.. include:: ../../../traffic_ops/app/conf/backends.conf
+	:code: json
+	:tab-width: 4
+
+
 Installing the SSL Certificate
 ------------------------------
 By default, Traffic Ops runs as an SSL web server (that is, over HTTPS), and a certificate needs to be installed.
diff --git a/traffic_ops/app/conf/backends.conf b/traffic_ops/app/conf/backends.conf
new file mode 100644
index 0000000000..2401a44996
--- /dev/null
+++ b/traffic_ops/app/conf/backends.conf
@@ -0,0 +1,52 @@
+{
+  "routes": [
+    {
+      "path": "^/api/4.0/foo?$",
+      "method": "GET",
+      "hosts": [
+        {
+          "protocol": "https",
+          "hostname": "localhost",
+          "port": 8444
+        },
+        {
+          "protocol": "https",
+          "hostname": "localhost",
+          "port": 8445
+        }
+      ],
+      "insecure": true,
+      "permissions": [
+        "CDN:READ"
+      ],
+      "routeId": 123456,
+      "opts": {
+        "alg": "roundrobin"
+      }
+    },
+    {
+      "path": "^/api/4.0/foos?$",
+      "method": "GET",
+      "hosts": [
+        {
+          "protocol": "https",
+          "hostname": "localhost",
+          "port": 8444
+        },
+        {
+          "protocol": "https",
+          "hostname": "localhost",
+          "port": 8445
+        }
+      ],
+      "insecure": true,
+      "permissions": [
+        "CDN:READ"
+      ],
+      "routeId": 123457,
+      "opts": {
+        "alg": "roundrobin"
+      }
+    }
+  ]
+}
diff --git a/traffic_ops/app/conf/production/backends.conf b/traffic_ops/app/conf/production/backends.conf
new file mode 100644
index 0000000000..4feaf1db51
--- /dev/null
+++ b/traffic_ops/app/conf/production/backends.conf
@@ -0,0 +1,3 @@
+{
+	"routes": [],
+}
diff --git a/traffic_ops/etc/init.d/traffic_ops b/traffic_ops/etc/init.d/traffic_ops
index d2fad881b1..12674c981b 100755
--- a/traffic_ops/etc/init.d/traffic_ops
+++ b/traffic_ops/etc/init.d/traffic_ops
@@ -50,7 +50,7 @@ start ()
 	stop
 	echo -e "Starting Traffic Ops\n"
 	ulimit -n 200000 || echo "Setting ulimit max files failed for traffic_ops_golang"
-	cd $TO_DIR && $TO_DIR/bin/traffic_ops_golang -cfg $TO_DIR/conf/cdn.conf -dbcfg $TO_DIR/conf/production/database.conf -riakcfg $TO_DIR/conf/production/riak.conf &
+	cd $TO_DIR && $TO_DIR/bin/traffic_ops_golang -cfg $TO_DIR/conf/cdn.conf -dbcfg $TO_DIR/conf/production/database.conf -riakcfg $TO_DIR/conf/production/riak.conf -backendcfg $TO_DIR/conf/production/backends.conf &
 }
 
 stop ()
diff --git a/traffic_ops/traffic_ops_golang/config/config.go b/traffic_ops/traffic_ops_golang/config/config.go
index f9680c10a3..521da1d9bd 100644
--- a/traffic_ops/traffic_ops_golang/config/config.go
+++ b/traffic_ops/traffic_ops_golang/config/config.go
@@ -36,6 +36,35 @@ import (
 	"github.com/apache/trafficcontrol/lib/go-util"
 )
 
+// Options is a structure used to hold the route configuration options that can be supplied for the backend routes.
+type Options struct {
+	Algorithm string `json:"alg"`
+}
+
+// Host is a structure that holds the host info for the backend route.
+type Host struct {
+	Protocol string `json:"protocol"`
+	Hostname string `json:"hostname"`
+	Port     int    `json:"port"`
+}
+
+// BackendRoute holds all the information about a configured route, for which Traffic Ops serves as a reverse proxy.
+type BackendRoute struct {
+	Path        string   `json:"path"`
+	Method      string   `json:"method"`
+	Hosts       []Host   `json:"hosts"`
+	Opts        Options  `json:"opts"`
+	ID          int      `json:"routeId"`
+	Insecure    bool     `json:"insecure"`
+	Permissions []string `json:"permissions"`
+	Index       int
+}
+
+// BackendConfig is a structure that holds the configuration supplied to Traffic Ops, which makes it act as a reverse proxy to the specified routes.
+type BackendConfig struct {
+	Routes []BackendRoute `json:"routes"`
+}
+
 // Config reflects the structure of the cdn.conf file
 type Config struct {
 	URL                         *url.URL `json:"-"`
@@ -286,6 +315,31 @@ func (c Config) EventLog() log.LogLocation {
 const BlockStartup = true
 const AllowStartup = false
 
+func LoadBackendConfig(backendConfigPath string) (BackendConfig, error) {
+	confBytes, err := ioutil.ReadFile(backendConfigPath)
+	if err != nil {
+		return BackendConfig{}, fmt.Errorf("reading backend conf '%s': %v", backendConfigPath, err)
+	}
+
+	cfg := BackendConfig{}
+	err = json.Unmarshal(confBytes, &cfg)
+	if err != nil {
+		return BackendConfig{}, fmt.Errorf("unmarshalling '%s': %v", backendConfigPath, err)
+	}
+	for _, r := range cfg.Routes {
+		if r.Opts.Algorithm != "" && r.Opts.Algorithm != "roundrobin" {
+			return cfg, errors.New("algorithm can only be roundrobin or blank")
+		}
+		for _, h := range r.Hosts {
+			rawURL := h.Protocol + "://" + h.Hostname + ":" + strconv.Itoa(h.Port)
+			if _, err = url.ParseRequestURI(rawURL); err != nil {
+				return cfg, fmt.Errorf("couldn't convert host info into a valid URI: %v", err)
+			}
+		}
+	}
+	return cfg, nil
+}
+
 func LoadCdnConfig(cdnConfPath string) (Config, error) {
 	// load json from cdn.conf
 	confBytes, err := ioutil.ReadFile(cdnConfPath)
diff --git a/traffic_ops/traffic_ops_golang/routing/middleware/wrappers.go b/traffic_ops/traffic_ops_golang/routing/middleware/wrappers.go
index e307faadb9..57ed0bb520 100644
--- a/traffic_ops/traffic_ops_golang/routing/middleware/wrappers.go
+++ b/traffic_ops/traffic_ops_golang/routing/middleware/wrappers.go
@@ -212,7 +212,7 @@ func WrapAccessLog(secret string, h http.Handler) http.HandlerFunc {
 					imsType = IMSMISS
 				}
 			}
-			log.EventfRaw(`%s - %s [%s] "%v %v?%v %s" %v %v %v "%v" %v %s`, r.RemoteAddr, user, time.Now().Format(AccessLogTimeFormat), r.Method, r.URL.Path, r.URL.RawQuery, r.Proto, iw.Code, iw.ByteCount, int(time.Now().Sub(start)/time.Millisecond), r.UserAgent(), r.Header.Get(RouteID), imsType)
+			log.EventfRaw(`%s - %s [%s] "%v %v?%v %s" %v %v %v "%v" %v %s`, r.RemoteAddr, user, time.Now().Format(AccessLogTimeFormat), r.Method, r.URL.Path, r.URL.RawQuery, r.Proto, iw.Code, iw.ByteCount, int(time.Now().Sub(start)/time.Millisecond), r.UserAgent(), r.Context().Value(RouteID), imsType)
 		}()
 		h.ServeHTTP(iw, r)
 	}
@@ -276,6 +276,14 @@ func NotImplementedHandler() http.Handler {
 	})
 }
 
+func BackendErrorHandler(code int, userErr error, sysErr error) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set(rfc.ContentType, rfc.ApplicationJSON)
+		w.WriteHeader(code)
+		api.HandleErr(w, r, nil, code, userErr, sysErr)
+	})
+}
+
 // DisabledRouteHandler returns a http.Handler which returns a HTTP 5xx code to the client, and an error message indicating the route is currently disabled.
 // This is used for routes which have been disabled via configuration. See config.ConfigTrafficOpsGolang.RoutingBlacklist.DisabledRoutes.
 func DisabledRouteHandler() http.Handler {
diff --git a/traffic_ops/traffic_ops_golang/routing/routing.go b/traffic_ops/traffic_ops_golang/routing/routing.go
index 3031e1c209..c3ca1adcb4 100644
--- a/traffic_ops/traffic_ops_golang/routing/routing.go
+++ b/traffic_ops/traffic_ops_golang/routing/routing.go
@@ -23,18 +23,23 @@ package routing
 
 import (
 	"context"
+	"crypto/tls"
 	"errors"
 	"fmt"
 	"net/http"
+	"net/http/httputil"
+	"net/url"
 	"regexp"
 	"sort"
 	"strconv"
 	"strings"
+	"sync"
 	"sync/atomic"
 	"time"
 
 	"github.com/apache/trafficcontrol/lib/go-log"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/config"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/plugin"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/routing/middleware"
@@ -46,6 +51,28 @@ import (
 // RoutePrefix is a prefix that all API routes must match.
 const RoutePrefix = "^api" // TODO config?
 
+type backendConfigSynced struct {
+	cfg config.BackendConfig
+	*sync.RWMutex
+}
+
+// backendCfg stores the current backend config supplied to traffic ops.
+var backendCfg = backendConfigSynced{RWMutex: &sync.RWMutex{}}
+
+// GetBackendConfig returns the current BackendConfig.
+func GetBackendConfig() config.BackendConfig {
+	backendCfg.RLock()
+	defer backendCfg.RUnlock()
+	return backendCfg.cfg
+}
+
+// SetBackendConfig sets the BackendConfig to the value supplied.
+func SetBackendConfig(backendConfig config.BackendConfig) {
+	backendCfg.Lock()
+	defer backendCfg.Unlock()
+	backendCfg.cfg = backendConfig
+}
+
 // A Route defines an association with a client request and a handler for that
 // request.
 type Route struct {
@@ -85,6 +112,7 @@ type ServerData struct {
 	Profiling    *bool // Yes this is a field in the config but we want to live reload this value and NOT the entire config
 	Plugins      plugin.Plugins
 	TrafficVault trafficvault.TrafficVault
+	Mux          *http.ServeMux
 }
 
 // CompiledRoute ...
@@ -270,8 +298,8 @@ func Handler(
 		}
 
 		routeCtx := context.WithValue(ctx, api.PathParamsKey, params)
+		routeCtx = context.WithValue(routeCtx, middleware.RouteID, strconv.Itoa(compiledRoute.ID))
 		r = r.WithContext(routeCtx)
-		r.Header.Add(middleware.RouteID, strconv.Itoa(compiledRoute.ID))
 		compiledRoute.Handler(w, r)
 		return
 	}
@@ -280,8 +308,97 @@ func Handler(
 		h.ServeHTTP(w, r)
 		return
 	}
+	var backendRouteHandled bool
+	backendConfig := GetBackendConfig()
+	for i, backendRoute := range backendConfig.Routes {
+		var params []string
+		routeParams := map[string]string{}
+		if backendRoute.Method == r.Method {
+			for open := strings.Index(backendRoute.Path, "{"); open > 0; open = strings.Index(backendRoute.Path, "{") {
+				close := strings.Index(backendRoute.Path, "}")
+				if close < 0 {
+					panic("malformed route")
+				}
+				param := backendRoute.Path[open+1 : close]
+				params = append(params, param)
+				backendRoute.Path = backendRoute.Path[:open] + `([^/]+)` + backendRoute.Path[close+1:]
+			}
+			regex := regexp.MustCompile(backendRoute.Path)
+			match := regex.FindStringSubmatch(r.URL.Path)
+			if len(match) == 0 {
+				continue
+			}
+			for i, v := range params {
+				routeParams[v] = match[i+1]
+			}
+			if backendRoute.Opts.Algorithm == "" || backendRoute.Opts.Algorithm == "roundrobin" {
+				index := backendRoute.Index % len(backendRoute.Hosts)
+				host := backendRoute.Hosts[index]
+				backendRoute.Index++
+				backendConfig.Routes[i] = backendRoute
+				backendRouteHandled = true
+				rp := httputil.NewSingleHostReverseProxy(&url.URL{
+					Host:   host.Hostname + ":" + strconv.Itoa(host.Port),
+					Scheme: host.Protocol,
+				})
+				rp.Transport = &http.Transport{
+					TLSClientConfig: &tls.Config{InsecureSkipVerify: backendRoute.Insecure},
+				}
+				rp.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
+					api.HandleErr(w, r, nil, http.StatusInternalServerError, nil, err)
+					return
+				}
+				routeCtx := context.WithValue(ctx, api.DBContextKey, db)
+				routeCtx = context.WithValue(routeCtx, api.PathParamsKey, routeParams)
+				routeCtx = context.WithValue(routeCtx, middleware.RouteID, strconv.Itoa(backendRoute.ID))
+				r = r.WithContext(routeCtx)
+				userErr, sysErr, code := HandleBackendRoute(cfg, backendRoute, w, r)
+				if userErr != nil || sysErr != nil {
+					h2 := middleware.WrapAccessLog(cfg.Secrets[0], middleware.BackendErrorHandler(code, userErr, sysErr))
+					h2.ServeHTTP(w, r)
+					return
+				}
+				backendHandler := middleware.WrapAccessLog(cfg.Secrets[0], rp)
+				backendHandler.ServeHTTP(w, r)
+				return
+			} else {
+				h2 := middleware.WrapAccessLog(cfg.Secrets[0], middleware.BackendErrorHandler(http.StatusBadRequest, errors.New("only an algorithm of roundrobin is supported by the backend options currently"), nil))
+				h2.ServeHTTP(w, r)
+				return
+			}
+		}
+	}
+	if !backendRouteHandled {
+		catchall.ServeHTTP(w, r)
+	}
+}
 
-	catchall.ServeHTTP(w, r)
+// HandleBackendRoute does all the pre processing for the backend routes.
+func HandleBackendRoute(cfg *config.Config, route config.BackendRoute, w http.ResponseWriter, r *http.Request) (error, error, int) {
+	var userErr, sysErr error
+	var errCode int
+	var user auth.CurrentUser
+	var inf *api.APIInfo
+
+	user, userErr, sysErr, errCode = api.GetUserFromReq(w, r, cfg.Secrets[0])
+	if userErr != nil || sysErr != nil {
+		return userErr, sysErr, errCode
+	}
+	if cfg.RoleBasedPermissions {
+		missingPerms := user.MissingPermissions(route.Permissions...)
+		if len(missingPerms) != 0 {
+			msg := strings.Join(missingPerms, ", ")
+			return fmt.Errorf("missing required Permissions: %s", msg), nil, http.StatusForbidden
+		}
+	}
+	api.AddUserToReq(r, user)
+	var params []string
+	inf, userErr, sysErr, errCode = api.NewInfo(r, params, nil)
+	if userErr != nil || sysErr != nil {
+		return userErr, sysErr, errCode
+	}
+	defer inf.Close()
+	return nil, nil, http.StatusOK
 }
 
 // IsRequestAPIAndUnknownVersion returns true if the request starts with `/api` and is a version not in the list of versions.
@@ -335,7 +452,7 @@ func RegisterRoutes(d ServerData) error {
 
 	compiledRoutes := CompileRoutes(routes)
 	getReqID := nextReqIDGetter()
-	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+	d.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 		Handler(compiledRoutes, versions, catchall, d.DB, &d.Config, getReqID, d.Plugins, d.TrafficVault, w, r)
 	})
 	return nil
diff --git a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
index a04c486d36..42a3acf45e 100644
--- a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
+++ b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
@@ -22,6 +22,7 @@ package main
 import (
 	"crypto/tls"
 	"encoding/json"
+	"errors"
 	"flag"
 	"fmt"
 	"io/ioutil"
@@ -64,6 +65,7 @@ func main() {
 	configFileName := flag.String("cfg", "", "The config file path")
 	dbConfigFileName := flag.String("dbcfg", "", "The db config file path")
 	riakConfigFileName := flag.String("riakcfg", "", "The riak config file path (DEPRECATED: use traffic_vault_backend = riak and traffic_vault_config in cdn.conf instead)")
+	backendConfigFileName := flag.String("backendcfg", "", "The backend config file path")
 	flag.Parse()
 
 	if *showVersion {
@@ -164,7 +166,18 @@ func main() {
 		log.Errorln(debugServer.ListenAndServe())
 	}()
 
-	if err := routing.RegisterRoutes(routing.ServerData{DB: db, Config: cfg, Profiling: &profiling, Plugins: plugins, TrafficVault: trafficVault}); err != nil {
+	var backendConfig config.BackendConfig
+	if *backendConfigFileName != "" {
+		backendConfig, err = config.LoadBackendConfig(*backendConfigFileName)
+		routing.SetBackendConfig(backendConfig)
+		if err != nil {
+			log.Errorf("error loading backend config: %v", err)
+		}
+	}
+
+	mux := http.NewServeMux()
+	d := routing.ServerData{DB: db, Config: cfg, Profiling: &profiling, Plugins: plugins, TrafficVault: trafficVault, Mux: mux}
+	if err := routing.RegisterRoutes(d); err != nil {
 		log.Errorf("registering routes: %v\n", err)
 		os.Exit(1)
 	}
@@ -213,7 +226,7 @@ func main() {
 		} else {
 			file.Close()
 		}
-
+		server.Handler = mux
 		if err := server.ListenAndServeTLS(cfg.CertPath, cfg.KeyPath); err != nil {
 			log.Errorf("stopping server: %v\n", err)
 			os.Exit(1)
@@ -232,10 +245,16 @@ func main() {
 		continuousProfile(&profiling, &profilingLocation, cfg.Version)
 	}
 
-	reloadProfilingConfig := func() {
+	reloadProfilingAndBackendConfig := func() {
 		setNewProfilingInfo(*configFileName, &profiling, &profilingLocation, cfg.Version)
+		backendConfig, err = getNewBackendConfig(backendConfigFileName)
+		if err != nil {
+			log.Errorf("could not reload backend config: %v", err)
+		} else {
+			routing.SetBackendConfig(backendConfig)
+		}
 	}
-	signalReloader(unix.SIGHUP, reloadProfilingConfig)
+	signalReloader(unix.SIGHUP, reloadProfilingAndBackendConfig)
 }
 
 func setupTrafficVault(riakConfigFileName string, cfg *config.Config) trafficvault.TrafficVault {
@@ -293,6 +312,19 @@ func setupTrafficVault(riakConfigFileName string, cfg *config.Config) trafficvau
 	return &disabled.Disabled{}
 }
 
+func getNewBackendConfig(backendConfigFileName *string) (config.BackendConfig, error) {
+	if backendConfigFileName == nil {
+		return config.BackendConfig{}, errors.New("no backend config filename")
+	}
+	log.Infof("setting new backend config to %s", *backendConfigFileName)
+	backendConfig, err := config.LoadBackendConfig(*backendConfigFileName)
+	if err != nil {
+		log.Errorf("error reloading config: %v", err)
+		return backendConfig, err
+	}
+	return backendConfig, nil
+}
+
 func setNewProfilingInfo(configFileName string, currentProfilingEnabled *bool, currentProfilingLocation *string, version string) {
 	newProfilingEnabled, newProfilingLocation, err := reloadProfilingInfo(configFileName)
 	if err != nil {