blob: 60b9b4c2add2b038defb51c229f8b3e9caa206ff [file] [log] [blame]
package main
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import (
"crypto/tls"
"encoding/json"
"errors"
"flag"
"fmt"
"io/ioutil"
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
"path/filepath"
"runtime/pprof"
"strings"
"time"
"github.com/apache/trafficcontrol/lib/go-log"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/about"
"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"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/server"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/trafficvault"
_ "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/trafficvault/backends" // init traffic vault backends
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/trafficvault/backends/disabled"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/trafficvault/backends/riaksvc"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
"golang.org/x/sys/unix"
)
// set the version at build time: `go build -X "main.version=..."`
var version = "development"
func init() {
about.SetAbout(version)
}
func main() {
showVersion := flag.Bool("version", false, "Show version and exit")
showPlugins := flag.Bool("plugins", false, "Show the list of plugins and exit")
showRoutes := flag.Bool("api-routes", false, "Show the list of API routes and exit")
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 {
fmt.Println(about.About.RPMVersion)
os.Exit(0)
}
if *showPlugins {
fmt.Println(strings.Join(plugin.List(), "\n"))
os.Exit(0)
}
if *showRoutes {
fake := routing.ServerData{Config: config.NewFakeConfig()}
routes, _, _ := routing.Routes(fake)
if len(*configFileName) != 0 {
cfg, err := config.LoadCdnConfig(*configFileName)
if err != nil {
fmt.Printf("Loading cdn config from '%s': %v", *configFileName, err)
os.Exit(1)
}
disabledRoutes := routing.GetRouteIDMap(cfg.DisabledRoutes)
for _, r := range routes {
_, isDisabled := disabledRoutes[r.ID]
fmt.Printf("%s\tis_disabled=%t\n", r, isDisabled)
}
} else {
for _, r := range routes {
fmt.Printf("%s\n", r)
}
}
os.Exit(0)
}
if len(os.Args) < 2 {
flag.Usage()
os.Exit(1)
}
cfg, errsToLog, blockStart := config.LoadConfig(*configFileName, *dbConfigFileName, version)
for _, err := range errsToLog {
fmt.Fprintf(os.Stderr, "Loading Config: %v\n", err)
}
if blockStart {
os.Exit(1)
}
if err := log.InitCfg(cfg); err != nil {
fmt.Printf("Error initializing loggers: %v\n", err)
for _, err := range errsToLog {
fmt.Println(err)
}
os.Exit(1)
}
for _, err := range errsToLog {
log.Warnln(err)
}
logConfig(cfg)
err := auth.LoadPasswordBlacklist("app/conf/invalid_passwords.txt")
if err != nil {
log.Errorf("loading password blacklist: %v\n", err)
os.Exit(1)
}
sslStr := "require"
if !cfg.DB.SSL {
sslStr = "disable"
}
db, err := sqlx.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=%s&fallback_application_name=trafficops", cfg.DB.User, cfg.DB.Password, cfg.DB.Hostname, cfg.DB.Port, cfg.DB.DBName, sslStr))
if err != nil {
log.Errorf("opening database: %v\n", err)
os.Exit(1)
}
defer db.Close()
db.SetMaxOpenConns(cfg.MaxDBConnections)
db.SetMaxIdleConns(cfg.DBMaxIdleConnections)
db.SetConnMaxLifetime(time.Duration(cfg.DBConnMaxLifetimeSeconds) * time.Second)
auth.InitUsersCache(time.Duration(cfg.UserCacheRefreshIntervalSec)*time.Second, db.DB, time.Duration(cfg.DBQueryTimeoutSeconds)*time.Second)
server.InitServerUpdateStatusCache(time.Duration(cfg.ServerUpdateStatusCacheRefreshIntervalSec)*time.Second, db.DB, time.Duration(cfg.DBQueryTimeoutSeconds)*time.Second)
trafficVault := setupTrafficVault(*riakConfigFileName, &cfg)
// TODO combine
plugins := plugin.Get(cfg)
profiling := cfg.ProfilingEnabled
pprofMux := http.DefaultServeMux
http.DefaultServeMux = http.NewServeMux() // this is so we don't serve pprof over 443.
pprofMux.Handle("/db-stats", routing.DBStatsHandler(db))
pprofMux.Handle("/memory-stats", routing.MemoryStatsHandler())
go func() {
debugServer := http.Server{
Addr: "localhost:6060",
Handler: pprofMux,
}
log.Errorln(debugServer.ListenAndServe())
}()
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)
}
plugins.OnStartup(plugin.StartupData{Data: plugin.Data{SharedCfg: cfg.PluginSharedConfig, AppCfg: cfg}})
log.Infof("Listening on " + cfg.Port)
httpServer := &http.Server{
Addr: ":" + cfg.Port,
TLSConfig: cfg.TLSConfig,
ReadTimeout: time.Duration(cfg.ReadTimeout) * time.Second,
ReadHeaderTimeout: time.Duration(cfg.ReadHeaderTimeout) * time.Second,
WriteTimeout: time.Duration(cfg.WriteTimeout) * time.Second,
IdleTimeout: time.Duration(cfg.IdleTimeout) * time.Second,
ErrorLog: log.Error,
}
if httpServer.TLSConfig == nil {
httpServer.TLSConfig = &tls.Config{}
}
// Deprecated in 5.0
httpServer.TLSConfig.InsecureSkipVerify = cfg.Insecure
// end deprecated block
go func() {
if cfg.KeyPath == "" {
log.Errorf("key cannot be blank in %s", cfg.ConfigHypnotoad.Listen)
os.Exit(1)
}
if cfg.CertPath == "" {
log.Errorf("cert cannot be blank in %s", cfg.ConfigHypnotoad.Listen)
os.Exit(1)
}
if file, err := os.Open(cfg.CertPath); err != nil {
log.Errorf("cannot open %s for read: %s", cfg.CertPath, err.Error())
os.Exit(1)
} else {
file.Close()
}
if file, err := os.Open(cfg.KeyPath); err != nil {
log.Errorf("cannot open %s for read: %s", cfg.KeyPath, err.Error())
os.Exit(1)
} else {
file.Close()
}
httpServer.Handler = mux
if err := httpServer.ListenAndServeTLS(cfg.CertPath, cfg.KeyPath); err != nil {
log.Errorf("stopping server: %v\n", err)
os.Exit(1)
}
}()
profilingLocation, err := getProcessedProfilingLocation(cfg.ProfilingLocation, cfg.LogLocationError)
if err != nil {
log.Errorln("unable to determine profiling location: " + err.Error())
}
log.Infof("profiling location: %s\n", profilingLocation)
log.Infof("profiling enabled set to %t\n", profiling)
if profiling {
continuousProfile(&profiling, &profilingLocation, cfg.Version)
}
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, reloadProfilingAndBackendConfig)
}
func setupTrafficVault(riakConfigFileName string, cfg *config.Config) trafficvault.TrafficVault {
var err error
trafficVaultConfigBytes := []byte{}
trafficVaultBackend := ""
if len(riakConfigFileName) > 0 {
// use legacy riak config if given
log.Warnln("using deprecated --riakcfg flag, use traffic_vault_backend = riak and traffic_vault_config in cdn.conf instead")
trafficVaultConfigBytes, err = ioutil.ReadFile(riakConfigFileName)
if err != nil {
log.Errorf("reading riak conf '%s': %s", riakConfigFileName, err.Error())
os.Exit(1)
}
cfg.TrafficVaultEnabled = true
trafficVaultBackend = riaksvc.RiakBackendName
}
if len(cfg.TrafficVaultBackend) > 0 {
if len(cfg.TrafficVaultConfig) == 0 {
log.Errorln("traffic_vault_backend is non-empty but traffic_vault_config is empty")
os.Exit(1)
}
cfg.TrafficVaultEnabled = true
// traffic_vault_config should override legacy riak config if both are used
trafficVaultConfigBytes = cfg.TrafficVaultConfig
trafficVaultBackend = cfg.TrafficVaultBackend
}
if trafficVaultBackend == riaksvc.RiakBackendName && cfg.RiakPort != nil {
// inject riak_port into traffic_vault_config.port if unset there
log.Warnln("using deprecated field 'riak_port', use 'port' field in traffic_vault_config instead")
tmp := make(map[string]interface{})
err := json.Unmarshal(trafficVaultConfigBytes, &tmp)
if err != nil {
log.Errorf("failed to unmarshal riak config: %s", err.Error())
os.Exit(1)
}
if _, ok := tmp["port"]; !ok {
tmp["port"] = *cfg.RiakPort
}
trafficVaultConfigBytes, err = json.Marshal(tmp)
if err != nil {
log.Errorf("failed to marshal riak config: %s", err.Error())
os.Exit(1)
}
}
if cfg.TrafficVaultEnabled {
trafficVault, err := trafficvault.GetBackend(trafficVaultBackend, trafficVaultConfigBytes)
if err != nil {
log.Errorf("failed to get Traffic Vault backend '%s': %s", cfg.TrafficVaultBackend, err.Error())
os.Exit(1)
}
return trafficVault
}
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 {
log.Errorln("reloading config: ", err.Error())
return
}
if newProfilingLocation != "" && *currentProfilingLocation != newProfilingLocation {
*currentProfilingLocation = newProfilingLocation
log.Infof("profiling location set to: %s\n", *currentProfilingLocation)
}
if *currentProfilingEnabled != newProfilingEnabled {
log.Infof("profiling enabled set to %t\n", newProfilingEnabled)
log.Infof("profiling location set to: %s\n", *currentProfilingLocation)
*currentProfilingEnabled = newProfilingEnabled
if *currentProfilingEnabled {
continuousProfile(currentProfilingEnabled, currentProfilingLocation, version)
}
}
}
func getProcessedProfilingLocation(rawProfilingLocation string, errorLogLocation string) (string, error) {
profilingLocation := os.TempDir()
if errorLogLocation != "" && errorLogLocation != log.LogLocationNull && errorLogLocation != log.LogLocationStderr && errorLogLocation != log.LogLocationStdout {
errorDir := filepath.Dir(errorLogLocation)
if _, err := os.Stat(errorDir); err == nil {
profilingLocation = errorDir
}
}
profilingLocation = filepath.Join(profilingLocation, "profiling")
if rawProfilingLocation != "" {
profilingLocation = rawProfilingLocation
} else {
//if it isn't a provided location create the profiling directory under the default temp location if it doesn't exist
if _, err := os.Stat(profilingLocation); err != nil {
err = os.Mkdir(profilingLocation, 0755)
if err != nil {
return "", fmt.Errorf("unable to create profiling location: %s", err.Error())
}
}
}
return profilingLocation, nil
}
func reloadProfilingInfo(configFileName string) (bool, string, error) {
cfg, err := config.LoadCdnConfig(configFileName)
if err != nil {
return false, "", err
}
profilingLocation, err := getProcessedProfilingLocation(cfg.ProfilingLocation, cfg.LogLocationError)
if err != nil {
return false, "", err
}
return cfg.ProfilingEnabled, profilingLocation, nil
}
func continuousProfile(profiling *bool, profilingDir *string, version string) {
if *profiling && *profilingDir != "" {
go func() {
for {
now := time.Now().UTC()
filename := filepath.Join(*profilingDir, fmt.Sprintf("tocpu-%s-%s.pprof", version, now.Format(time.RFC3339)))
f, err := os.Create(filename)
if err != nil {
log.Errorf("creating profile: %v\n", err)
log.Infof("Exiting profiling")
break
}
pprof.StartCPUProfile(f)
time.Sleep(time.Minute)
pprof.StopCPUProfile()
f.Close()
if !*profiling {
break
}
}
}()
}
}
func signalReloader(sig os.Signal, f func()) {
c := make(chan os.Signal, 1)
signal.Notify(c, sig)
for range c {
log.Debugln("received SIGHUP")
f()
}
}
func logConfig(cfg config.Config) {
log.Infof(`Using Config values:
Port: %s
Db Server: %s
Db User: %s
Db Name: %s
Db Ssl: %t
Max Db Connections: %d
TO URL: %s
Insecure: %t
Cert Path: %s
Key Path: %s
Proxy Timeout: %v
Proxy KeepAlive: %v
Proxy tls handshake: %v
Proxy header timeout: %v
Read Timeout: %v
Read Header Timeout: %v
Write Timeout: %v
Idle Timeout: %v
Error Log: %s
Warn Log: %s
Info Log: %s
Debug Log: %s
Event Log: %s
LDAP Enabled: %v
InfluxDB Enabled: %v`, cfg.Port, cfg.DB.Hostname, cfg.DB.User, cfg.DB.DBName, cfg.DB.SSL, cfg.MaxDBConnections, cfg.Listen[0], cfg.Insecure, cfg.CertPath, cfg.KeyPath, time.Duration(cfg.ProxyTimeout)*time.Second, time.Duration(cfg.ProxyKeepAlive)*time.Second, time.Duration(cfg.ProxyTLSTimeout)*time.Second, time.Duration(cfg.ProxyReadHeaderTimeout)*time.Second, time.Duration(cfg.ReadTimeout)*time.Second, time.Duration(cfg.ReadHeaderTimeout)*time.Second, time.Duration(cfg.WriteTimeout)*time.Second, time.Duration(cfg.IdleTimeout)*time.Second, cfg.LogLocationError, cfg.LogLocationWarning, cfg.LogLocationInfo, cfg.LogLocationDebug, cfg.LogLocationEvent, cfg.LDAPEnabled, cfg.InfluxEnabled)
}