github.com/uber/kraken@v0.1.4/origin/cmd/cmd.go (about) 1 // Copyright (c) 2016-2019 Uber Technologies, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package cmd 15 16 import ( 17 "encoding/json" 18 "flag" 19 "fmt" 20 "net/http" 21 "os" 22 23 "github.com/uber/kraken/core" 24 "github.com/uber/kraken/lib/backend" 25 "github.com/uber/kraken/lib/blobrefresh" 26 "github.com/uber/kraken/lib/hashring" 27 "github.com/uber/kraken/lib/healthcheck" 28 "github.com/uber/kraken/lib/hostlist" 29 "github.com/uber/kraken/lib/metainfogen" 30 "github.com/uber/kraken/lib/persistedretry" 31 "github.com/uber/kraken/lib/persistedretry/writeback" 32 "github.com/uber/kraken/lib/store" 33 "github.com/uber/kraken/lib/torrent/networkevent" 34 "github.com/uber/kraken/lib/torrent/scheduler" 35 "github.com/uber/kraken/localdb" 36 "github.com/uber/kraken/metrics" 37 "github.com/uber/kraken/nginx" 38 "github.com/uber/kraken/origin/blobclient" 39 "github.com/uber/kraken/origin/blobserver" 40 "github.com/uber/kraken/utils/configutil" 41 "github.com/uber/kraken/utils/handler" 42 "github.com/uber/kraken/utils/log" 43 "github.com/uber/kraken/utils/netutil" 44 45 "github.com/andres-erbsen/clock" 46 "github.com/pressly/chi" 47 "github.com/uber-go/tally" 48 "go.uber.org/zap" 49 ) 50 51 // Flags defines origin CLI flags. 52 type Flags struct { 53 PeerIP string 54 PeerPort int 55 BlobServerHostName string 56 BlobServerPort int 57 ConfigFile string 58 Zone string 59 KrakenCluster string 60 SecretsFile string 61 } 62 63 // ParseFlags parses origin CLI flags. 64 func ParseFlags() *Flags { 65 var flags Flags 66 flag.StringVar( 67 &flags.PeerIP, "peer-ip", "", "ip which peer will announce itself as") 68 flag.IntVar( 69 &flags.PeerPort, "peer-port", 0, "port which peer will announce itself as") 70 flag.StringVar( 71 &flags.BlobServerHostName, "blobserver-hostname", "", "optional hostname to identify origin") 72 flag.IntVar( 73 &flags.BlobServerPort, "blobserver-port", 0, "port which blob server listens on") 74 flag.StringVar( 75 &flags.ConfigFile, "config", "", "configuration file path") 76 flag.StringVar( 77 &flags.Zone, "zone", "", "zone/datacenter name") 78 flag.StringVar( 79 &flags.KrakenCluster, "cluster", "", "cluster name (e.g. prod01-zone1)") 80 flag.StringVar( 81 &flags.SecretsFile, "secrets", "", "path to a secrets YAML file to load into configuration") 82 flag.Parse() 83 return &flags 84 } 85 86 type options struct { 87 config *Config 88 metrics tally.Scope 89 logger *zap.Logger 90 } 91 92 // Option defines an optional Run parameter. 93 type Option func(*options) 94 95 // WithConfig ignores config/secrets flags and directly uses the provided config 96 // struct. 97 func WithConfig(c Config) Option { 98 return func(o *options) { o.config = &c } 99 } 100 101 // WithMetrics ignores metrics config and directly uses the provided tally scope. 102 func WithMetrics(s tally.Scope) Option { 103 return func(o *options) { o.metrics = s } 104 } 105 106 // WithLogger ignores logging config and directly uses the provided logger. 107 func WithLogger(l *zap.Logger) Option { 108 return func(o *options) { o.logger = l } 109 } 110 111 // Run runs the origin. 112 func Run(flags *Flags, opts ...Option) { 113 if flags.PeerPort == 0 { 114 panic("must specify non-zero peer port") 115 } 116 if flags.BlobServerPort == 0 { 117 panic("must specify non-zero blob server port") 118 } 119 120 var overrides options 121 for _, o := range opts { 122 o(&overrides) 123 } 124 125 var config Config 126 if overrides.config != nil { 127 config = *overrides.config 128 } else { 129 if err := configutil.Load(flags.ConfigFile, &config); err != nil { 130 panic(err) 131 } 132 if flags.SecretsFile != "" { 133 if err := configutil.Load(flags.SecretsFile, &config); err != nil { 134 panic(err) 135 } 136 } 137 } 138 139 if overrides.logger != nil { 140 log.SetGlobalLogger(overrides.logger.Sugar()) 141 } else { 142 zlog := log.ConfigureLogger(config.ZapLogging) 143 defer zlog.Sync() 144 } 145 146 stats := overrides.metrics 147 if stats == nil { 148 s, closer, err := metrics.New(config.Metrics, flags.KrakenCluster) 149 if err != nil { 150 log.Fatalf("Failed to init metrics: %s", err) 151 } 152 stats = s 153 defer closer.Close() 154 } 155 156 go metrics.EmitVersion(stats) 157 158 var hostname string 159 if flags.BlobServerHostName == "" { 160 var err error 161 hostname, err = os.Hostname() 162 if err != nil { 163 log.Fatalf("Error getting hostname: %s", err) 164 } 165 } else { 166 hostname = flags.BlobServerHostName 167 } 168 log.Infof("Configuring origin with hostname '%s'", hostname) 169 170 if flags.PeerIP == "" { 171 localIP, err := netutil.GetLocalIP() 172 if err != nil { 173 log.Fatalf("Error getting local ip: %s", err) 174 } 175 flags.PeerIP = localIP 176 } 177 178 cas, err := store.NewCAStore(config.CAStore, stats) 179 if err != nil { 180 log.Fatalf("Failed to create castore: %s", err) 181 } 182 183 pctx, err := core.NewPeerContext( 184 config.PeerIDFactory, flags.Zone, flags.KrakenCluster, flags.PeerIP, flags.PeerPort, true) 185 if err != nil { 186 log.Fatalf("Failed to create peer context: %s", err) 187 } 188 189 backendManager, err := backend.NewManager(config.Backends, config.Auth) 190 if err != nil { 191 log.Fatalf("Error creating backend manager: %s", err) 192 } 193 194 localDB, err := localdb.New(config.LocalDB) 195 if err != nil { 196 log.Fatalf("Error creating local db: %s", err) 197 } 198 199 writeBackManager, err := persistedretry.NewManager( 200 config.WriteBack, 201 stats, 202 writeback.NewStore(localDB), 203 writeback.NewExecutor(stats, cas, backendManager)) 204 if err != nil { 205 log.Fatalf("Error creating write-back manager: %s", err) 206 } 207 208 metaInfoGenerator, err := metainfogen.New(config.MetaInfoGen, cas) 209 if err != nil { 210 log.Fatalf("Error creating metainfo generator: %s", err) 211 } 212 213 blobRefresher := blobrefresh.New(config.BlobRefresh, stats, cas, backendManager, metaInfoGenerator) 214 215 netevents, err := networkevent.NewProducer(config.NetworkEvent) 216 if err != nil { 217 log.Fatalf("Error creating network event producer: %s", err) 218 } 219 220 sched, err := scheduler.NewOriginScheduler( 221 config.Scheduler, stats, pctx, cas, netevents, blobRefresher) 222 if err != nil { 223 log.Fatalf("Error creating scheduler: %s", err) 224 } 225 226 cluster, err := hostlist.New(config.Cluster) 227 if err != nil { 228 log.Fatalf("Error creating cluster host list: %s", err) 229 } 230 231 tls, err := config.TLS.BuildClient() 232 if err != nil { 233 log.Fatalf("Error building client tls config: %s", err) 234 } 235 236 healthCheckFilter := healthcheck.NewFilter(config.HealthCheck, healthcheck.Default(tls)) 237 238 hashRing := hashring.New( 239 config.HashRing, 240 cluster, 241 healthCheckFilter, 242 hashring.WithWatcher(backend.NewBandwidthWatcher(backendManager))) 243 go hashRing.Monitor(nil) 244 245 addr := fmt.Sprintf("%s:%d", hostname, flags.BlobServerPort) 246 if !hashRing.Contains(addr) { 247 // When DNS is used for hash ring membership, the members will be IP 248 // addresses instead of hostnames. 249 ip, err := netutil.GetLocalIP() 250 if err != nil { 251 log.Fatalf("Error getting local ip: %s", err) 252 } 253 addr = fmt.Sprintf("%s:%d", ip, flags.BlobServerPort) 254 if !hashRing.Contains(addr) { 255 log.Fatalf( 256 "Neither %s nor %s (port %d) found in hash ring", 257 hostname, ip, flags.BlobServerPort) 258 } 259 } 260 261 server, err := blobserver.New( 262 config.BlobServer, 263 stats, 264 clock.New(), 265 addr, 266 hashRing, 267 cas, 268 blobclient.NewProvider(blobclient.WithTLS(tls)), 269 blobclient.NewClusterProvider(blobclient.WithTLS(tls)), 270 pctx, 271 backendManager, 272 blobRefresher, 273 metaInfoGenerator, 274 writeBackManager) 275 if err != nil { 276 log.Fatalf("Error initializing blob server: %s", err) 277 } 278 279 h := addTorrentDebugEndpoints(server.Handler(), sched) 280 281 go func() { log.Fatal(server.ListenAndServe(h)) }() 282 283 log.Info("Starting nginx...") 284 log.Fatal(nginx.Run( 285 config.Nginx, 286 map[string]interface{}{ 287 "port": flags.BlobServerPort, 288 "server": nginx.GetServer(config.BlobServer.Listener.Net, config.BlobServer.Listener.Addr), 289 }, 290 nginx.WithTLS(config.TLS))) 291 } 292 293 // addTorrentDebugEndpoints mounts experimental debugging endpoints which are 294 // compatible with the agent server. 295 func addTorrentDebugEndpoints(h http.Handler, sched scheduler.ReloadableScheduler) http.Handler { 296 r := chi.NewRouter() 297 298 r.Patch("/x/config/scheduler", handler.Wrap(func(w http.ResponseWriter, r *http.Request) error { 299 var config scheduler.Config 300 if err := json.NewDecoder(r.Body).Decode(&config); err != nil { 301 return handler.Errorf("decode body: %s", err) 302 } 303 sched.Reload(config) 304 return nil 305 })) 306 307 r.Mount("/", h) 308 309 return r 310 }