cmd/hc, cmd/gw, cmd/captplanet: simplify setup/run commands (#178)

also allows much more customization of services within captain planet,
such as reconfiguring the overlay service to use redis
This commit is contained in:
JT Olio 2018-07-30 01:38:31 -06:00 committed by GitHub
parent 3fa7a30b31
commit 79d2639ba1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 220 additions and 244 deletions

View File

@ -4,11 +4,9 @@
package main
import (
"flag"
"path/filepath"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
monkit "gopkg.in/spacemonkeygo/monkit.v2"
"storj.io/storj/pkg/process"
)
@ -25,7 +23,10 @@ var (
)
func main() {
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
process.ExecuteWithConfig(rootCmd,
filepath.Join(defaultConfDir, "config.yaml"))
// process.Exec will load this for this command.
runCmd.Flags().String("config",
filepath.Join(defaultConfDir, "config.yaml"), "path to configuration")
setupCmd.Flags().String("config",
filepath.Join(defaultConfDir, "setup.yaml"), "path to configuration")
process.Exec(rootCmd)
}

View File

@ -5,8 +5,6 @@ package main
import (
"fmt"
"net"
"path/filepath"
"github.com/spf13/cobra"
@ -20,122 +18,76 @@ import (
"storj.io/storj/pkg/provider"
)
const (
farmerCount = 40
)
type HeavyClient struct {
Identity provider.IdentityConfig
Kademlia kademlia.Config
PointerDB pointerdb.Config
Overlay overlay.Config
}
type Farmer struct {
Identity provider.IdentityConfig
Kademlia kademlia.Config
Storage psservice.Config
}
var (
runCmd = &cobra.Command{
Use: "run",
Short: "Run all providers",
RunE: cmdRun,
}
runCfg Config
runCfg struct {
HeavyClient HeavyClient
Farmers [farmerCount]Farmer
Gateway miniogw.Config
}
)
func init() {
rootCmd.AddCommand(runCmd)
cfgstruct.Bind(runCmd.Flags(), &runCfg,
cfgstruct.ConfDir(defaultConfDir),
)
cfgstruct.Bind(runCmd.Flags(), &runCfg, cfgstruct.ConfDir(defaultConfDir))
}
func cmdRun(cmd *cobra.Command, args []string) (err error) {
ctx := process.Ctx(cmd)
defer mon.Task()(&ctx)(&err)
startingPort := runCfg.StartingPort
errch := make(chan error, runCfg.FarmerCount+2)
// define heavy client config programmatically
type HeavyClient struct {
Identity provider.IdentityConfig
Kademlia kademlia.Config
PointerDB pointerdb.Config
Overlay overlay.Config
}
hc := HeavyClient{
Identity: provider.IdentityConfig{
CertPath: filepath.Join(runCfg.BasePath, "hc", "ident.leaf.cert"),
KeyPath: filepath.Join(runCfg.BasePath, "hc", "ident.leaf.key"),
Address: joinHostPort(runCfg.ListenHost, startingPort+1),
},
Kademlia: kademlia.Config{
TODOListenAddr: joinHostPort(runCfg.ListenHost, startingPort+2),
BootstrapAddr: joinHostPort(runCfg.ListenHost, startingPort+4),
},
PointerDB: pointerdb.Config{
DatabaseURL: "bolt://" + filepath.Join(
runCfg.BasePath, "hc", "pointerdb.db"),
},
Overlay: overlay.Config{
DatabaseURL: "bolt://" + filepath.Join(
runCfg.BasePath, "hc", "overlay.db"),
},
}
errch := make(chan error, len(runCfg.Farmers)+2)
// start heavy client
go func() {
_, _ = fmt.Printf("starting heavy client on %s\n", hc.Identity.Address)
errch <- hc.Identity.Run(ctx, hc.Kademlia, hc.PointerDB, hc.Overlay)
_, _ = fmt.Printf("starting heavy client on %s\n",
runCfg.HeavyClient.Identity.Address)
errch <- runCfg.HeavyClient.Identity.Run(ctx,
runCfg.HeavyClient.Kademlia,
runCfg.HeavyClient.PointerDB,
runCfg.HeavyClient.Overlay)
}()
// define and start a bunch of farmers programmatically
type Farmer struct {
Identity provider.IdentityConfig
Kademlia kademlia.Config
Storage psservice.Config
}
for i := 0; i < runCfg.FarmerCount; i++ {
basepath := filepath.Join(runCfg.BasePath, fmt.Sprintf("f%d", i))
farmer := Farmer{
Identity: provider.IdentityConfig{
CertPath: filepath.Join(basepath, "ident.leaf.cert"),
KeyPath: filepath.Join(basepath, "ident.leaf.key"),
Address: joinHostPort(runCfg.ListenHost, startingPort+i*2+3),
},
Kademlia: kademlia.Config{
TODOListenAddr: joinHostPort(runCfg.ListenHost, startingPort+i*2+4),
BootstrapAddr: joinHostPort(runCfg.ListenHost, startingPort+1),
},
Storage: psservice.Config{
Path: filepath.Join(basepath, "data"),
},
}
// start the farmers
for i := 0; i < len(runCfg.Farmers); i++ {
go func(i int) {
_, _ = fmt.Printf("starting farmer %d grpc on %s, kad on %s\n",
i, farmer.Identity.Address, farmer.Kademlia.TODOListenAddr)
errch <- farmer.Identity.Run(ctx, farmer.Kademlia, farmer.Storage)
_, _ = fmt.Printf("starting farmer %d grpc on %s, kad on %s\n", i,
runCfg.Farmers[i].Identity.Address,
runCfg.Farmers[i].Kademlia.TODOListenAddr)
errch <- runCfg.Farmers[i].Identity.Run(ctx,
runCfg.Farmers[i].Kademlia,
runCfg.Farmers[i].Storage)
}(i)
}
// start s3 gateway
gw := miniogw.Config{
IdentityConfig: provider.IdentityConfig{
CertPath: filepath.Join(runCfg.BasePath, "gw", "ident.leaf.cert"),
KeyPath: filepath.Join(runCfg.BasePath, "gw", "ident.leaf.key"),
Address: joinHostPort(runCfg.ListenHost, startingPort),
},
MinioConfig: runCfg.MinioConfig,
ClientConfig: miniogw.ClientConfig{
OverlayAddr: joinHostPort(
runCfg.ListenHost, startingPort+1),
PointerDBAddr: joinHostPort(
runCfg.ListenHost, startingPort+1),
},
RSConfig: runCfg.RSConfig,
}
gw.MinioConfig.MinioDir = filepath.Join(runCfg.BasePath, "gw", "minio")
// start s3 gateway
go func() {
_, _ = fmt.Printf("starting minio gateway on %s\n",
gw.IdentityConfig.Address)
errch <- gw.Run(ctx)
runCfg.Gateway.IdentityConfig.Address)
errch <- runCfg.Gateway.Run(ctx)
}()
return <-errch
}
func joinHostPort(host string, port int) string {
return net.JoinHostPort(host, fmt.Sprint(port))
}

View File

@ -5,25 +5,22 @@ package main
import (
"fmt"
"net"
"os"
"path/filepath"
"github.com/spf13/cobra"
"storj.io/storj/pkg/cfgstruct"
"storj.io/storj/pkg/miniogw"
"storj.io/storj/pkg/peertls"
"storj.io/storj/pkg/process"
)
// Config defines broad Captain Planet configuration
type Config struct {
FarmerCount int `help:"number of farmers to run" default:"20"`
BasePath string `help:"base path for captain planet storage" default:"$CONFDIR"`
ListenHost string `help:"the host for providers to listen on" default:"127.0.0.1"`
StartingPort int `help:"all providers will listen on ports consecutively starting with this one" default:"7777"`
miniogw.RSConfig
miniogw.MinioConfig
}
var (
@ -54,7 +51,7 @@ func cmdSetup(cmd *cobra.Command, args []string) (err error) {
return err
}
for i := 0; i < setupCfg.FarmerCount; i++ {
for i := 0; i < len(runCfg.Farmers); i++ {
farmerPath := filepath.Join(setupCfg.BasePath, fmt.Sprintf("f%d", i))
err = os.MkdirAll(farmerPath, 0700)
if err != nil {
@ -78,5 +75,58 @@ func cmdSetup(cmd *cobra.Command, args []string) (err error) {
return err
}
return process.SaveConfig(cmd)
startingPort := setupCfg.StartingPort
overrides := map[string]interface{}{
"heavy-client.identity.cert-path": filepath.Join(
setupCfg.BasePath, "hc", "ident.leaf.cert"),
"heavy-client.identity.key-path": filepath.Join(
setupCfg.BasePath, "hc", "ident.leaf.key"),
"heavy-client.identity.address": joinHostPort(
setupCfg.ListenHost, startingPort+1),
"heavy-client.kademlia.todo-listen-addr": joinHostPort(
setupCfg.ListenHost, startingPort+2),
"heavy-client.kademlia.bootstrap-addr": joinHostPort(
setupCfg.ListenHost, startingPort+4),
"heavy-client.pointer-db.database-url": "bolt://" + filepath.Join(
setupCfg.BasePath, "hc", "pointerdb.db"),
"heavy-client.overlay.database-url": "bolt://" + filepath.Join(
setupCfg.BasePath, "hc", "overlay.db"),
"gateway.cert-path": filepath.Join(
setupCfg.BasePath, "gw", "ident.leaf.cert"),
"gateway.key-path": filepath.Join(
setupCfg.BasePath, "gw", "ident.leaf.key"),
"gateway.address": joinHostPort(
setupCfg.ListenHost, startingPort),
"gateway.overlay-addr": joinHostPort(
setupCfg.ListenHost, startingPort+1),
"gateway.pointer-db-addr": joinHostPort(
setupCfg.ListenHost, startingPort+1),
"gateway.minio-dir": filepath.Join(
setupCfg.BasePath, "gw", "minio"),
"pointer-db.auth.api-key": "abc123",
}
for i := 0; i < len(runCfg.Farmers); i++ {
basepath := filepath.Join(setupCfg.BasePath, fmt.Sprintf("f%d", i))
farmer := fmt.Sprintf("farmers.%02d.", i)
overrides[farmer+"identity.cert-path"] = filepath.Join(
basepath, "ident.leaf.cert")
overrides[farmer+"identity.key-path"] = filepath.Join(
basepath, "ident.leaf.key")
overrides[farmer+"identity.address"] = joinHostPort(
setupCfg.ListenHost, startingPort+i*2+3)
overrides[farmer+"kademlia.todo-listen-addr"] = joinHostPort(
setupCfg.ListenHost, startingPort+i*2+4)
overrides[farmer+"kademlia.bootstrap-addr"] = joinHostPort(
setupCfg.ListenHost, startingPort+1)
overrides[farmer+"storage.path"] = filepath.Join(basepath, "data")
}
return process.SaveConfig(runCmd.Flags(),
filepath.Join(setupCfg.BasePath, "config.yaml"), overrides)
}
func joinHostPort(host string, port int) string {
return net.JoinHostPort(host, fmt.Sprint(port))
}

View File

@ -4,10 +4,8 @@
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
@ -22,67 +20,54 @@ var (
Use: "gw",
Short: "Gateway",
}
runCmd = &cobra.Command{
Use: "run",
Short: "Run the gateway",
RunE: cmdRun,
}
setupCmd = &cobra.Command{
Use: "setup",
Short: "Create config files",
RunE: cmdSetup,
}
cfg miniogw.Config
runCfg miniogw.Config
setupCfg struct {
BasePath string `default:"$CONFDIR" help:"base path for setup"`
}
defaultConfDir = "$HOME/.storj/gw"
)
func init() {
rootCmd.AddCommand(&cobra.Command{
Use: "run",
Short: "Run the gateway",
RunE: cmdRun,
})
rootCmd.AddCommand(&cobra.Command{
Use: "setup",
Short: "Create config files",
RunE: cmdSetup,
})
cfgstruct.Bind(rootCmd.PersistentFlags(), &cfg,
cfgstruct.ConfDir(defaultConfDir))
rootCmd.AddCommand(runCmd)
rootCmd.AddCommand(setupCmd)
cfgstruct.Bind(runCmd.Flags(), &runCfg, cfgstruct.ConfDir(defaultConfDir))
cfgstruct.Bind(setupCmd.Flags(), &setupCfg, cfgstruct.ConfDir(defaultConfDir))
}
func cmdRun(cmd *cobra.Command, args []string) (err error) {
return cfg.Run(process.Ctx(cmd))
return runCfg.Run(process.Ctx(cmd))
}
func cmdSetup(cmd *cobra.Command, args []string) (err error) {
ctx := process.Ctx(cmd)
// TODO: clean this up somehow?
if !strings.HasSuffix(cfg.IdentityConfig.CertPath, ".leaf.cert") {
return fmt.Errorf("certificate path should end with .leaf.cert")
}
certpath := strings.TrimSuffix(cfg.IdentityConfig.CertPath, ".leaf.cert")
if !strings.HasSuffix(cfg.IdentityConfig.KeyPath, ".leaf.key") {
return fmt.Errorf("key path should end with .leaf.key")
}
keypath := strings.TrimSuffix(cfg.IdentityConfig.KeyPath, ".leaf.key")
err = os.MkdirAll(filepath.Dir(certpath), 0700)
if err != nil {
return err
}
err = os.MkdirAll(filepath.Dir(keypath), 0700)
err = os.MkdirAll(setupCfg.BasePath, 0700)
if err != nil {
return err
}
_, err = peertls.NewTLSFileOptions(certpath, keypath, true, false)
identityPath := filepath.Join(setupCfg.BasePath, "identity")
_, err = peertls.NewTLSFileOptions(identityPath, identityPath, true, false)
if err != nil {
return err
}
err = os.MkdirAll(filepath.Dir(process.CfgPath(ctx)), 0700)
if err != nil {
return err
}
return process.SaveConfigAs(cmd, process.CfgPath(ctx))
return process.SaveConfig(runCmd.Flags(),
filepath.Join(setupCfg.BasePath, "config.yaml"), nil)
}
func main() {
process.ExecuteWithConfig(rootCmd,
filepath.Join(defaultConfDir, "config.yaml"))
runCmd.Flags().String("config",
filepath.Join(defaultConfDir, "config.yaml"), "path to configuration")
process.Exec(rootCmd)
}

View File

@ -4,10 +4,8 @@
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
@ -20,80 +18,65 @@ import (
"storj.io/storj/pkg/provider"
)
const (
defaultConfFolder = "$HOME/.storj/hc"
)
var (
rootCmd = &cobra.Command{
Use: "hc",
Short: "Heavy client",
}
runCmd = &cobra.Command{
Use: "run",
Short: "Run the heavy client",
RunE: cmdRun,
}
setupCmd = &cobra.Command{
Use: "setup",
Short: "Create config files",
RunE: cmdSetup,
}
cfg struct {
runCfg struct {
Identity provider.IdentityConfig
Kademlia kademlia.Config
PointerDB pointerdb.Config
Overlay overlay.Config
}
setupCfg struct {
BasePath string `default:"$CONFDIR" help:"base path for setup"`
}
defaultConfDir = "$HOME/.storj/hc"
)
func init() {
rootCmd.AddCommand(&cobra.Command{
Use: "run",
Short: "Run the heavy client",
RunE: cmdRun,
})
rootCmd.AddCommand(&cobra.Command{
Use: "setup",
Short: "Create config files",
RunE: cmdSetup,
})
cfgstruct.Bind(rootCmd.PersistentFlags(), &cfg,
cfgstruct.ConfDir(defaultConfFolder))
rootCmd.AddCommand(runCmd)
rootCmd.AddCommand(setupCmd)
cfgstruct.Bind(runCmd.Flags(), &runCfg, cfgstruct.ConfDir(defaultConfDir))
cfgstruct.Bind(setupCmd.Flags(), &setupCfg, cfgstruct.ConfDir(defaultConfDir))
}
func cmdRun(cmd *cobra.Command, args []string) (err error) {
return cfg.Identity.Run(process.Ctx(cmd),
cfg.Kademlia, cfg.PointerDB, cfg.Overlay)
return runCfg.Identity.Run(process.Ctx(cmd),
runCfg.Kademlia, runCfg.PointerDB, runCfg.Overlay)
}
func cmdSetup(cmd *cobra.Command, args []string) (err error) {
ctx := process.Ctx(cmd)
// TODO: clean this up somehow?
if !strings.HasSuffix(cfg.Identity.CertPath, ".leaf.cert") {
return fmt.Errorf("certificate path should end with .leaf.cert")
}
certpath := strings.TrimSuffix(cfg.Identity.CertPath, ".leaf.cert")
if !strings.HasSuffix(cfg.Identity.KeyPath, ".leaf.key") {
return fmt.Errorf("key path should end with .leaf.key")
}
keypath := strings.TrimSuffix(cfg.Identity.KeyPath, ".leaf.key")
err = os.MkdirAll(filepath.Dir(certpath), 0700)
if err != nil {
return err
}
err = os.MkdirAll(filepath.Dir(keypath), 0700)
err = os.MkdirAll(setupCfg.BasePath, 0700)
if err != nil {
return err
}
_, err = peertls.NewTLSFileOptions(certpath, keypath, true, false)
identityPath := filepath.Join(setupCfg.BasePath, "identity")
_, err = peertls.NewTLSFileOptions(identityPath, identityPath, true, false)
if err != nil {
return err
}
err = os.MkdirAll(filepath.Dir(process.CfgPath(ctx)), 0700)
if err != nil {
return err
}
return process.SaveConfigAs(cmd, process.CfgPath(ctx))
return process.SaveConfig(runCmd.Flags(),
filepath.Join(setupCfg.BasePath, "config.yaml"), nil)
}
func main() {
process.ExecuteWithConfig(rootCmd,
filepath.Join(defaultConfFolder, "config.yaml"))
runCmd.Flags().String("config",
filepath.Join(defaultConfDir, "config.yaml"), "path to configuration")
process.Exec(rootCmd)
}

View File

@ -8,7 +8,6 @@ import (
"flag"
"log"
"os"
"strings"
"sync"
"github.com/spf13/cobra"
@ -23,9 +22,15 @@ import (
// ExecuteWithConfig runs a Cobra command with the provided default config
func ExecuteWithConfig(cmd *cobra.Command, defaultConfig string) {
cfgFile := flag.String("config", os.ExpandEnv(defaultConfig), "config file")
flag.String("config", os.ExpandEnv(defaultConfig), "config file")
Exec(cmd)
}
// Exec runs a Cobra command. If a "config" flag is defined it will be parsed
// and loaded using viper.
func Exec(cmd *cobra.Command) {
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
cleanup(cmd, cfgFile)
cleanup(cmd)
_ = cmd.Execute()
}
@ -36,6 +41,39 @@ var (
contexts = map[*cobra.Command]context.Context{}
)
// SaveConfig will save all flags with default values to outfilewith specific
// values specified in 'overrides' overridden.
func SaveConfig(flagset *pflag.FlagSet, outfile string,
overrides map[string]interface{}) error {
vip := viper.New()
err := vip.BindPFlags(pflag.CommandLine)
if err != nil {
return err
}
flagset.VisitAll(func(f *pflag.Flag) {
// stop processing if we hit an error on a BindPFlag call
if err != nil {
return
}
if f.Name == "config" {
return
}
err = vip.BindPFlag(f.Name, f)
})
if err != nil {
return err
}
if overrides != nil {
for key, val := range overrides {
vip.Set(key, val)
}
}
return vip.WriteConfigAs(os.ExpandEnv(outfile))
}
// Ctx returns the appropriate context.Context for ExecuteWithConfig commands
func Ctx(cmd *cobra.Command) context.Context {
contextMtx.Lock()
@ -47,44 +85,9 @@ func Ctx(cmd *cobra.Command) context.Context {
return ctx
}
type ctxKey int
const (
ctxKeyVip ctxKey = iota
ctxKeyCfg
)
// SaveConfig outputs the configuration to the configured (or default) config
// file given to ExecuteWithConfig
func SaveConfig(cmd *cobra.Command) error {
ctx := Ctx(cmd)
return getViper(ctx).WriteConfigAs(CfgPath(ctx))
}
// SaveConfigAs outputs the configuration to the provided path assuming the
// command was executed with ExecuteWithConfig
func SaveConfigAs(cmd *cobra.Command, path string) error {
return getViper(Ctx(cmd)).WriteConfigAs(path)
}
func getViper(ctx context.Context) *viper.Viper {
if v, ok := ctx.Value(ctxKeyVip).(*viper.Viper); ok {
return v
}
return nil
}
// CfgPath returns the configuration path used with ExecuteWithConfig
func CfgPath(ctx context.Context) string {
if v, ok := ctx.Value(ctxKeyCfg).(string); ok {
return v
}
return ""
}
func cleanup(cmd *cobra.Command, cfgFile *string) {
func cleanup(cmd *cobra.Command) {
for _, ccmd := range cmd.Commands() {
cleanup(ccmd, cfgFile)
cleanup(ccmd)
}
if cmd.Run != nil {
panic("Please use cobra's RunE instead of Run")
@ -104,16 +107,22 @@ func cleanup(cmd *cobra.Command, cfgFile *string) {
}
vip.SetEnvPrefix("storj")
vip.AutomaticEnv()
if *cfgFile != "" && fileExists(*cfgFile) {
vip.SetConfigFile(*cfgFile)
err = vip.ReadInConfig()
if err != nil {
return err
cfgFlag := cmd.Flags().Lookup("config")
if cfgFlag != nil && cfgFlag.Value.String() != "" {
path := os.ExpandEnv(cfgFlag.Value.String())
if cfgFlag.Changed || fileExists(path) {
vip.SetConfigFile(path)
err = vip.ReadInConfig()
if err != nil {
return err
}
}
}
// go back and propagate changed config values to appropriate flags
var brokenKeys []string
var brokenVals []string
for _, key := range vip.AllKeys() {
if cmd.Flags().Lookup(key) == nil {
// flag couldn't be found
@ -122,14 +131,11 @@ func cleanup(cmd *cobra.Command, cfgFile *string) {
err := cmd.Flags().Set(key, vip.GetString(key))
if err != nil {
// flag couldn't be set
brokenKeys = append(brokenKeys, key)
brokenVals = append(brokenVals, key)
}
}
}
ctx = context.WithValue(ctx, ctxKeyVip, vip)
ctx = context.WithValue(ctx, ctxKeyCfg, *cfgFile)
logger, err := utils.NewLogger(*logDisposition)
if err != nil {
return err
@ -139,12 +145,11 @@ func cleanup(cmd *cobra.Command, cfgFile *string) {
defer zap.RedirectStdLog(logger)()
// okay now that logging is working, inform about the broken keys
// these keys are almost certainly broken because they have capital
// letters
if len(brokenKeys) > 0 {
logger.Sugar().Infof("TODO: these flags are not configurable via "+
"config file, probably due to having uppercase letters: %s",
strings.Join(brokenKeys, ", "))
for _, key := range brokenKeys {
logger.Sugar().Infof("Invalid configuration file key: %s", key)
}
for _, key := range brokenVals {
logger.Sugar().Infof("Invalid configuration file value for key: %s", key)
}
err = initMetrics(ctx, monkit.Default,