Piecestore Farmer CLI (#92)

* separated commands and actions from main.go, created delete command

* refactored piecestore-farmer to use cobra CLI

* added piecestore-farmer main.go and long command descriptions

* pkg/process: start replacing pkg/process with cobra helpers

* address review comments

* addressed github comments, rename pstore.DetermineID to GenerateID, remove utils.NewID and use pstore.GenerateID instead

* addressed comments

* pulled proc and got CLI working with process package

* goimports

* stdout formatting

* WIP adding tests for pstoreFarmer CLI, fixed bug in check for preexisting config

* copyright header, change exported variable

* changed error formatting to be consistent

* more tests

* stdout, add (\n)s in zap logs, set home inside SetConfigPath() instead of passing in

* resolved merge conflicts

* goimports and fixed merge error

* removed generateID func to use kademlia.NewID instead, refactored to match kademlia.NewKademlia()
This commit is contained in:
Cameron 2018-07-02 15:02:29 -04:00 committed by Dennis Coyle
parent fb251f58e2
commit 3bb5f44da7
7 changed files with 741 additions and 278 deletions

View File

@ -0,0 +1,82 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package cmd
import (
"path/filepath"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/viper"
"github.com/zeebo/errs"
"golang.org/x/net/context"
"storj.io/storj/pkg/kademlia"
proto "storj.io/storj/protos/overlay"
)
// Config stores values from a farmer node config file
type Config struct {
NodeID string
PsHost string
PsPort string
KadListenPort string
KadPort string
KadHost string
PieceStoreDir string
}
// SetConfigPath sets and returns viper config directory and filepath
func SetConfigPath(fileName string) (configDir, configFile string, err error) {
home, err := homedir.Dir()
if err != nil {
return "", "", err
}
configDir = filepath.Join(home, ".storj")
configFile = filepath.Join(configDir, fileName+".yaml")
viper.SetConfigFile(configFile)
return configDir, configFile, nil
}
// GetConfigValues returns a struct with config file values
func GetConfigValues() Config {
config := Config{
NodeID: viper.GetString("piecestore.id"),
PsHost: viper.GetString("piecestore.host"),
PsPort: viper.GetString("piecestore.port"),
KadListenPort: viper.GetString("kademlia.listen.port"),
KadPort: viper.GetString("kademlia.port"),
KadHost: viper.GetString("kademlia.host"),
PieceStoreDir: viper.GetString("piecestore.dir"),
}
return config
}
// ConnectToKad joins the Kademlia network
func ConnectToKad(ctx context.Context, id, ip, kadListenPort, kadAddress string) (*kademlia.Kademlia, error) {
node := proto.Node{
Id: id,
Address: &proto.NodeAddress{
Transport: proto.NodeTransport_TCP,
Address: kadAddress,
},
}
kad, err := kademlia.NewKademlia(kademlia.StringToNodeID(id), []proto.Node{node}, ip, kadListenPort)
if err != nil {
return nil, errs.New("Failed to instantiate new Kademlia: %s", err.Error())
}
if err := kad.ListenAndServe(); err != nil {
return nil, errs.New("Failed to ListenAndServe on new Kademlia: %s", err.Error())
}
if err := kad.Bootstrap(ctx); err != nil {
return nil, errs.New("Failed to Bootstrap on new Kademlia: %s", err.Error())
}
return kad, nil
}

View File

@ -0,0 +1,90 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package cmd
import (
"fmt"
"os"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/zeebo/errs"
"go.uber.org/zap"
"storj.io/storj/pkg/kademlia"
)
// createCmd represents the create command
var createCmd = &cobra.Command{
Use: "create",
Short: "Create a new farmer node",
Long: "Create a config file and set values for a new farmer node",
RunE: createNode,
}
func init() {
RootCmd.AddCommand(createCmd)
nodeID, err := kademlia.NewID()
if err != nil {
zap.S().Fatal(err)
}
home, err := homedir.Dir()
if err != nil {
zap.S().Fatal(err)
}
createCmd.Flags().String("kademliaHost", "bootstrap.storj.io", "Kademlia server `host`")
createCmd.Flags().String("kademliaPort", "8080", "Kademlia server `port`")
createCmd.Flags().String("kademliaListenPort", "7776", "Kademlia server `listen port`")
createCmd.Flags().String("pieceStoreHost", "127.0.0.1", "Farmer's public ip/host")
createCmd.Flags().String("pieceStorePort", "7777", "`port` where piece store data is accessed")
createCmd.Flags().String("dir", home, "`dir` of drive being shared")
viper.BindPFlag("kademlia.host", createCmd.Flags().Lookup("kademliaHost"))
viper.BindPFlag("kademlia.port", createCmd.Flags().Lookup("kademliaPort"))
viper.BindPFlag("kademlia.listen.port", createCmd.Flags().Lookup("kademliaListenPort"))
viper.BindPFlag("piecestore.host", createCmd.Flags().Lookup("pieceStoreHost"))
viper.BindPFlag("piecestore.port", createCmd.Flags().Lookup("pieceStorePort"))
viper.BindPFlag("piecestore.dir", createCmd.Flags().Lookup("dir"))
viper.SetDefault("piecestore.id", nodeID.String())
}
// createNode creates a config file for a new farmer node
func createNode(cmd *cobra.Command, args []string) error {
configDir, configFile, err := SetConfigPath(viper.GetString("piecestore.id"))
if err != nil {
return err
}
pieceStoreDir := viper.GetString("piecestore.dir")
err = os.MkdirAll(pieceStoreDir, 0700)
if err != nil {
return err
}
err = os.MkdirAll(configDir, 0700)
if err != nil {
return err
}
if _, err := os.Stat(configFile); err == nil {
return errs.New("Config already exists")
}
err = viper.WriteConfigAs(configFile)
if err != nil {
return err
}
path := viper.ConfigFileUsed()
fmt.Printf("Node %s created\n", viper.GetString("piecestore.id"))
fmt.Println("Config: ", path)
return nil
}

View File

@ -0,0 +1,68 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package cmd
import (
"fmt"
"os"
"path/filepath"
_ "github.com/mattn/go-sqlite3" // sqlite driver
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/zeebo/errs"
)
// deleteCmd represents the delete command
var deleteCmd = &cobra.Command{
Use: "delete",
Short: "Delete a farmer node by ID",
Long: "Delete config and all data stored on node by node ID",
RunE: deleteNode,
}
func init() {
RootCmd.AddCommand(deleteCmd)
}
// deleteNode deletes a farmer node by ID
func deleteNode(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errs.New("No ID specified")
}
nodeID := args[0]
_, configFile, err := SetConfigPath(nodeID)
if err != nil {
return err
}
if _, err := os.Stat(configFile); os.IsNotExist(err) {
return errs.New("Invalid node ID. Config file does not exist")
}
if err := viper.ReadInConfig(); err != nil {
return err
}
// get folder for stored data
piecestoreDir := viper.GetString("piecestore.dir")
piecestoreDir = filepath.Join(piecestoreDir, fmt.Sprintf("store-%s", nodeID))
// remove all folders and files stored on node
if err := os.RemoveAll(piecestoreDir); err != nil {
return err
}
// delete node config
err = os.Remove(configFile)
if err != nil {
return err
}
fmt.Printf("Node %s deleted\n", nodeID)
return nil
}

View File

@ -0,0 +1,383 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package cmd
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
)
// TestCreate tests the farmer CLI create command
func TestCreate(t *testing.T) {
home, err := homedir.Dir()
if err != nil {
t.Error(err.Error())
}
tempFile, err := ioutil.TempFile(os.TempDir(), "")
if err != nil {
t.Error(err.Error())
}
defer os.Remove(tempFile.Name())
tests := []struct {
it string
expectedConfig Config
args []string
err string
}{
{
it: "should successfully write config with default values",
expectedConfig: Config{
NodeID: "",
PsHost: "127.0.0.1",
PsPort: "7777",
KadListenPort: "7776",
KadPort: "8080",
KadHost: "bootstrap.storj.io",
PieceStoreDir: home,
},
args: []string{
"create",
},
err: "",
},
{
it: "should successfully write config with flag values",
expectedConfig: Config{
NodeID: "",
PsHost: "123.4.5.6",
PsPort: "1234",
KadListenPort: "4321",
KadPort: "4444",
KadHost: "hack@theplanet.com",
PieceStoreDir: os.TempDir(),
},
args: []string{
"create",
fmt.Sprintf("--pieceStoreHost=%s", "123.4.5.6"),
fmt.Sprintf("--pieceStorePort=%s", "1234"),
fmt.Sprintf("--kademliaListenPort=%s", "4321"),
fmt.Sprintf("--kademliaPort=%s", "4444"),
fmt.Sprintf("--kademliaHost=%s", "hack@theplanet.com"),
fmt.Sprintf("--dir=%s", os.TempDir()),
},
err: "",
},
{
it: "should err when a config with identical ID already exists",
expectedConfig: Config{
NodeID: "",
PsHost: "123.4.5.6",
PsPort: "1234",
KadListenPort: "4321",
KadPort: "4444",
KadHost: "hack@theplanet.com",
PieceStoreDir: home,
},
args: []string{
"create",
fmt.Sprintf("--dir=%s", home),
},
err: "Config already exists",
},
{
it: "should err when pieceStoreDir is not a directory",
expectedConfig: Config{
NodeID: "",
PsHost: "123.4.5.6",
PsPort: "1234",
KadListenPort: "4321",
KadPort: "4444",
KadHost: "hack@theplanet.com",
PieceStoreDir: tempFile.Name(),
},
args: []string{
"create",
fmt.Sprintf("--dir=%s", tempFile.Name()),
},
err: fmt.Sprintf("mkdir %s: not a directory", tempFile.Name()),
},
}
for _, tt := range tests {
t.Run(tt.it, func(t *testing.T) {
assert := assert.New(t)
RootCmd.SetArgs(tt.args)
path := filepath.Join(home, ".storj", viper.GetString("piecestore.id")+".yaml")
if tt.err == "Config already exists" {
_, err := os.Create(path)
if err != nil {
t.Error(err.Error())
return
}
}
defer os.Remove(path)
err := RootCmd.Execute()
if tt.err != "" {
if err != nil {
assert.Equal(tt.err, err.Error())
return
}
} else if err != nil {
t.Error(err.Error())
return
}
viper.SetConfigFile(path)
err = viper.ReadInConfig()
if err != nil {
t.Error(err.Error())
return
}
tt.expectedConfig.NodeID = viper.GetString("piecestore.id")
assert.Equal(tt.expectedConfig, GetConfigValues())
return
})
}
}
// TestStart tests the farmer CLI start command
func TestStart(t *testing.T) {
home, err := homedir.Dir()
if err != nil {
t.Error(err.Error())
}
tests := []struct {
it string
createArgs []string
startArgs []string
err string
}{
{
it: "should err with no ID specified",
createArgs: []string{
"create",
fmt.Sprintf("--kademliaHost=%s", "bootstrap.storj.io"),
fmt.Sprintf("--kademliaPort=%s", "8080"),
fmt.Sprintf("--kademliaListenPort=%s", "7776"),
fmt.Sprintf("--pieceStoreHost=%s", "127.0.0.1"),
fmt.Sprintf("--pieceStorePort=%s", "7777"),
fmt.Sprintf("--dir=%s", os.TempDir()),
},
startArgs: []string{
"start",
},
err: "No ID specified",
},
{
it: "should err with invalid Farmer IP",
createArgs: []string{
"create",
fmt.Sprintf("--kademliaHost=%s", "bootstrap.storj.io"),
fmt.Sprintf("--kademliaPort=%s", "8080"),
fmt.Sprintf("--kademliaListenPort=%s", "7776"),
fmt.Sprintf("--pieceStoreHost=%s", "123"),
fmt.Sprintf("--pieceStorePort=%s", "7777"),
fmt.Sprintf("--dir=%s", os.TempDir()),
},
startArgs: []string{
"start",
viper.GetString("piecestore.id"),
},
err: "Failed to instantiate new Kademlia: lookup 123: no such host",
},
{
it: "should err with missing Kademlia Listen Port",
createArgs: []string{
"create",
fmt.Sprintf("--kademliaHost=%s", "bootstrap.storj.io"),
fmt.Sprintf("--kademliaPort=%s", "8080"),
fmt.Sprintf("--kademliaListenPort=%s", ""),
fmt.Sprintf("--pieceStoreHost=%s", "127.0.0.1"),
fmt.Sprintf("--pieceStorePort=%s", "7777"),
fmt.Sprintf("--dir=%s", os.TempDir()),
},
startArgs: []string{
"start",
viper.GetString("piecestore.id"),
},
err: "Failed to instantiate new Kademlia: node error: must specify port in request to NewKademlia",
},
{
it: "should err with missing IP",
createArgs: []string{
"create",
fmt.Sprintf("--kademliaHost=%s", "bootstrap.storj.io"),
fmt.Sprintf("--kademliaPort=%s", "8080"),
fmt.Sprintf("--kademliaListenPort=%s", "7776"),
fmt.Sprintf("--pieceStoreHost=%s", ""),
fmt.Sprintf("--pieceStorePort=%s", "7777"),
fmt.Sprintf("--dir=%s", os.TempDir()),
},
startArgs: []string{
"start",
viper.GetString("piecestore.id"),
},
err: "Failed to instantiate new Kademlia: lookup : no such host",
},
}
for _, tt := range tests {
t.Run(tt.it, func(t *testing.T) {
assert := assert.New(t)
RootCmd.SetArgs(tt.createArgs)
err = RootCmd.Execute()
if err != nil {
t.Error(err.Error())
return
}
path := filepath.Join(home, ".storj", viper.GetString("piecestore.id")+".yaml")
defer os.Remove(path)
RootCmd.SetArgs(tt.startArgs)
err := RootCmd.Execute()
if tt.err != "" {
if err != nil {
assert.Equal(tt.err, err.Error())
}
} else if err != nil {
t.Error(err.Error())
return
}
})
}
}
// TestDelete tests the farmer CLI delete command
func TestDelete(t *testing.T) {
home, err := homedir.Dir()
if err != nil {
t.Error(err.Error())
}
tests := []struct {
it string
createArgs []string
deleteArgs []string
err string
}{
{
it: "should successfully delete node config",
createArgs: []string{
"create",
fmt.Sprintf("--kademliaHost=%s", "bootstrap.storj.io"),
fmt.Sprintf("--kademliaPort=%s", "8080"),
fmt.Sprintf("--kademliaListenPort=%s", "7776"),
fmt.Sprintf("--pieceStoreHost=%s", "127.0.0.1"),
fmt.Sprintf("--pieceStorePort=%s", "7777"),
fmt.Sprintf("--dir=%s", os.TempDir()),
},
deleteArgs: []string{
"delete",
viper.GetString("piecestore.id"),
},
err: "",
},
{
it: "should err with no ID specified",
createArgs: []string{
"create",
fmt.Sprintf("--kademliaHost=%s", "bootstrap.storj.io"),
fmt.Sprintf("--kademliaPort=%s", "8080"),
fmt.Sprintf("--kademliaListenPort=%s", "7776"),
fmt.Sprintf("--pieceStoreHost=%s", "127.0.0.1"),
fmt.Sprintf("--pieceStorePort=%s", "7777"),
fmt.Sprintf("--dir=%s", os.TempDir()),
},
deleteArgs: []string{
"delete",
},
err: "No ID specified",
},
{
it: "should err with no ID specified",
createArgs: []string{
"create",
fmt.Sprintf("--kademliaHost=%s", "bootstrap.storj.io"),
fmt.Sprintf("--kademliaPort=%s", "8080"),
fmt.Sprintf("--kademliaListenPort=%s", "7776"),
fmt.Sprintf("--pieceStoreHost=%s", "127.0.0.1"),
fmt.Sprintf("--pieceStorePort=%s", "7777"),
fmt.Sprintf("--dir=%s", os.TempDir()),
},
deleteArgs: []string{
"delete",
"123",
},
err: "Invalid node ID. Config file does not exist",
},
}
for _, tt := range tests {
t.Run(tt.it, func(t *testing.T) {
assert := assert.New(t)
RootCmd.SetArgs(tt.createArgs)
err := RootCmd.Execute()
if err != nil {
t.Error(err.Error())
return
}
configPath := filepath.Join(home, ".storj", viper.GetString("piecestore.id")+".yaml")
if _, err := os.Stat(configPath); err != nil {
t.Error(err.Error())
return
}
if tt.err != "" {
defer os.Remove(configPath)
}
RootCmd.SetArgs(tt.deleteArgs)
err = RootCmd.Execute()
if tt.err != "" {
if err != nil {
assert.Equal(tt.err, err.Error())
return
}
} else if err != nil {
t.Error(err.Error())
return
}
// if err is nil, file was not deleted
if _, err := os.Stat(configPath); err == nil {
t.Error(err.Error())
return
}
return
})
}
}
func TestMain(m *testing.M) {
m.Run()
}

View File

@ -0,0 +1,15 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package cmd
import (
"github.com/spf13/cobra"
)
// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
Use: "piecestore-farmer",
Short: "Piecestore-Farmer CLI",
Long: "Piecestore-Farmer command line utility for creating, starting, and deleting farmer nodes",
}

View File

@ -0,0 +1,100 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package cmd
import (
"fmt"
"net"
"path/filepath"
_ "github.com/mattn/go-sqlite3" // sqlite driver
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/zeebo/errs"
"go.uber.org/zap"
"golang.org/x/net/context"
"google.golang.org/grpc"
"storj.io/storj/pkg/piecestore/rpc/server"
"storj.io/storj/pkg/piecestore/rpc/server/ttl"
pb "storj.io/storj/protos/piecestore"
)
// startCmd represents the start command
var startCmd = &cobra.Command{
Use: "start",
Short: "Start a farmer node by ID",
Long: "Start farmer node by ID using farmer node config values",
RunE: startNode,
}
func init() {
RootCmd.AddCommand(startCmd)
}
// startNode starts a farmer node by ID
func startNode(cmd *cobra.Command, args []string) error {
ctx := context.Background()
if len(args) == 0 {
return errs.New("No ID specified")
}
_, _, err := SetConfigPath(args[0])
if err != nil {
return err
}
err = viper.ReadInConfig()
if err != nil {
return err
}
config := GetConfigValues()
dbPath := filepath.Join(config.PieceStoreDir, fmt.Sprintf("store-%s", config.NodeID), "ttl-data.db")
dataDir := filepath.Join(config.PieceStoreDir, fmt.Sprintf("store-%s", config.NodeID), "piece-store-data")
_, err = ConnectToKad(ctx, config.NodeID, config.PsHost, config.KadListenPort, fmt.Sprintf("%s:%s", config.KadHost, config.KadPort))
if err != nil {
return err
}
ttlDB, err := ttl.NewTTL(dbPath)
if err != nil {
return err
}
// create a listener on TCP port
lis, err := net.Listen("tcp", fmt.Sprintf(":%s", config.PsPort))
if err != nil {
return err
}
defer lis.Close()
// create a server instance
s := server.Server{PieceStoreDir: dataDir, DB: ttlDB}
// create a gRPC server object
grpcServer := grpc.NewServer()
// attach the api service to the server
pb.RegisterPieceStoreRoutesServer(grpcServer, &s)
// routinely check DB and delete expired entries
go func() {
err := s.DB.DBCleanup(dataDir)
zap.S().Fatalf("Error in DBCleanup: %v\n", err)
}()
fmt.Printf("Node %s started\n", config.NodeID)
// start the server
if err := grpcServer.Serve(lis); err != nil {
zap.S().Fatalf("failed to serve: %s\n", err)
}
return nil
}

View File

@ -4,285 +4,10 @@
package main
import (
"crypto/rand"
"errors"
"flag"
"fmt"
"log"
"net"
"os"
"os/user"
"path"
"sort"
_ "github.com/mattn/go-sqlite3"
"github.com/mr-tron/base58/base58"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/urfave/cli"
"golang.org/x/net/context"
"google.golang.org/grpc"
"storj.io/storj/pkg/kademlia"
"storj.io/storj/pkg/piecestore/rpc/server"
"storj.io/storj/pkg/piecestore/rpc/server/ttl"
"storj.io/storj/cmd/piecestore-farmer/cmd"
"storj.io/storj/pkg/process"
proto "storj.io/storj/protos/overlay"
pb "storj.io/storj/protos/piecestore"
)
func newID() string {
b := make([]byte, 32)
_, err := rand.Read(b)
if err != nil {
panic(err)
}
encoding := base58.Encode(b)
return encoding[:20]
}
func connectToKad(id, ip, kadlistenport, kadaddress string) *kademlia.Kademlia {
node := proto.Node{
Id: string(id),
Address: &proto.NodeAddress{
Transport: proto.NodeTransport_TCP,
Address: kadaddress,
},
}
nodeid, err := kademlia.NewID()
if err != nil {
log.Fatalf("Failed to instantiate new Kademlia ID: %s", err.Error())
}
kad, err := kademlia.NewKademlia(nodeid, []proto.Node{node}, ip, kadlistenport)
if err != nil {
log.Fatalf("Failed to instantiate new Kademlia: %s", err.Error())
}
if err := kad.ListenAndServe(); err != nil {
log.Fatalf("Failed to ListenAndServe on new Kademlia: %s", err.Error())
}
if err := kad.Bootstrap(context.Background()); err != nil {
log.Fatalf("Failed to Bootstrap on new Kademlia: %s", err.Error())
}
return kad
}
func main() { process.Must(process.Main(process.ServiceFunc(run))) }
func run(ctx context.Context, _ *cobra.Command, _ []string) error {
app := cli.NewApp()
app.Name = "Piece Store Farmer CLI"
app.Usage = "Connect your drive to the network"
app.Version = "1.0.0"
// Flags
app.Flags = []cli.Flag{}
var kadhost string
var kadport string
var kadlistenport string
var pshost string
var psport string
var dir string
app.Commands = []cli.Command{
{
Name: "create",
Aliases: []string{"c"},
Usage: "create farmer node",
ArgsUsage: "",
Flags: []cli.Flag{
cli.StringFlag{Name: "pieceStoreHost", Usage: "Farmer's public ip/host", Destination: &pshost},
cli.StringFlag{Name: "pieceStorePort", Usage: "`port` where piece store data is accessed", Destination: &psport},
cli.StringFlag{Name: "kademliaPort", Usage: "Kademlia server `host`", Destination: &kadport},
cli.StringFlag{Name: "kademliaHost", Usage: "Kademlia server `host`", Destination: &kadhost},
cli.StringFlag{Name: "kademliaListenPort", Usage: "Kademlia server `host`", Destination: &kadlistenport},
cli.StringFlag{Name: "dir", Usage: "`dir` of drive being shared", Destination: &dir},
},
Action: func(c *cli.Context) error {
nodeID := newID()
usr, err := user.Current()
if err != nil {
return err
}
viper.SetDefault("piecestore.host", "127.0.0.1")
viper.SetDefault("piecestore.port", "7777")
viper.SetDefault("piecestore.dir", usr.HomeDir)
viper.SetDefault("piecestore.id", nodeID)
viper.SetDefault("kademlia.host", "bootstrap.storj.io")
viper.SetDefault("kademlia.port", "8080")
viper.SetDefault("kademlia.listen.port", "7776")
viper.SetConfigName(nodeID)
viper.SetConfigType("yaml")
configPath := path.Join(usr.HomeDir, ".storj/")
if err = os.MkdirAll(configPath, 0700); err != nil {
return err
}
viper.AddConfigPath(configPath)
fullPath := path.Join(configPath, fmt.Sprintf("%s.yaml", nodeID))
_, err = os.Stat(fullPath)
if os.IsExist(err) {
if err != nil {
return errors.New("config already exists")
}
return err
}
// Create empty file at configPath
_, err = os.Create(fullPath)
if err != nil {
return err
}
if pshost != "" {
viper.Set("piecestore.host", pshost)
}
if psport != "" {
viper.Set("piecestore.port", psport)
}
if dir != "" {
viper.Set("piecestore.dir", dir)
}
if kadhost != "" {
viper.Set("kademlia.host", kadhost)
}
if kadport != "" {
viper.Set("kademlia.port", kadport)
}
if kadlistenport != "" {
viper.Set("kademlia.listen.port", kadlistenport)
}
if err := viper.WriteConfig(); err != nil {
return err
}
path := viper.ConfigFileUsed()
fmt.Printf("Config: %s\n", path)
fmt.Printf("ID: %s\n", nodeID)
return nil
},
},
{
Name: "start",
Aliases: []string{"s"},
Usage: "start farmer node",
ArgsUsage: "[id]",
Action: func(c *cli.Context) error {
if c.Args().Get(0) == "" {
return errors.New("no id specified")
}
usr, err := user.Current()
if err != nil {
log.Fatalf(err.Error())
}
configPath := path.Join(usr.HomeDir, ".storj/")
viper.AddConfigPath(configPath)
viper.SetConfigName(c.Args().Get(0))
viper.SetConfigType("yaml")
if err := viper.ReadInConfig(); err != nil {
log.Fatalf(err.Error())
}
nodeid := viper.GetString("piecestore.id")
pshost = viper.GetString("piecestore.host")
psport = viper.GetString("piecestore.port")
kadlistenport = viper.GetString("kademlia.listen.port")
kadport = viper.GetString("kademlia.port")
kadhost = viper.GetString("kademlia.host")
piecestoreDir := viper.GetString("piecestore.dir")
dbPath := path.Join(piecestoreDir, fmt.Sprintf("store-%s", nodeid), "/ttl-data.db")
dataDir := path.Join(piecestoreDir, fmt.Sprintf("store-%s", nodeid), "/piece-store-data/")
if err = os.MkdirAll(piecestoreDir, 0700); err != nil {
log.Fatalf(err.Error())
}
_ = connectToKad(nodeid, pshost, kadlistenport, fmt.Sprintf("%s:%s", kadhost, kadport))
fileInfo, err := os.Stat(piecestoreDir)
if err != nil {
log.Fatalf(err.Error())
}
if fileInfo.IsDir() != true {
log.Fatalf("Error: %s is not a directory", piecestoreDir)
}
ttlDB, err := ttl.NewTTL(dbPath)
if err != nil {
log.Fatalf("failed to open DB")
}
// create a listener on TCP port
lis, err := net.Listen("tcp", fmt.Sprintf(":%s", psport))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
defer lis.Close()
// create a server instance
s := server.Server{PieceStoreDir: dataDir, DB: ttlDB}
// create a gRPC server object
grpcServer := grpc.NewServer()
// attach the api service to the server
pb.RegisterPieceStoreRoutesServer(grpcServer, &s)
// routinely check DB and delete expired entries
go func() {
err := s.DB.DBCleanup(dataDir)
log.Fatalf("Error in DBCleanup: %v", err)
}()
// start the server
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to serve: %s", err)
}
return nil
},
},
{
Name: "delete",
Aliases: []string{"d"},
Usage: "delete farmer node",
ArgsUsage: "[id]",
Action: func(c *cli.Context) error {
return nil
},
},
{
Name: "list",
Aliases: []string{"l"},
Usage: "list farmer nodes",
ArgsUsage: "",
Action: func(c *cli.Context) error {
return nil
},
},
}
sort.Sort(cli.FlagsByName(app.Flags))
sort.Sort(cli.CommandsByName(app.Commands))
return app.Run(append([]string{os.Args[0]}, flag.Args()...))
func main() {
process.Execute(cmd.RootCmd)
}