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:
parent
fb251f58e2
commit
3bb5f44da7
82
cmd/piecestore-farmer/cmd/common.go
Normal file
82
cmd/piecestore-farmer/cmd/common.go
Normal 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
|
||||
}
|
90
cmd/piecestore-farmer/cmd/create.go
Normal file
90
cmd/piecestore-farmer/cmd/create.go
Normal 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
|
||||
}
|
68
cmd/piecestore-farmer/cmd/delete.go
Normal file
68
cmd/piecestore-farmer/cmd/delete.go
Normal 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
|
||||
}
|
383
cmd/piecestore-farmer/cmd/main_test.go
Normal file
383
cmd/piecestore-farmer/cmd/main_test.go
Normal 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()
|
||||
}
|
15
cmd/piecestore-farmer/cmd/root.go
Normal file
15
cmd/piecestore-farmer/cmd/root.go
Normal 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",
|
||||
}
|
100
cmd/piecestore-farmer/cmd/start.go
Normal file
100
cmd/piecestore-farmer/cmd/start.go
Normal 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
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user