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  }