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