1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-10-24 07:03:53 +08:00
Files
Jeromy 4ace45d930 vendor in new iptb code (and as a result, go-multiaddr-net and friends
License: MIT
Signed-off-by: Jeromy <jeromyj@gmail.com>
2015-10-11 22:10:16 -07:00

693 lines
14 KiB
Go

package main
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"path"
"strconv"
"strings"
"sync"
"syscall"
"time"
kingpin "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/alecthomas/kingpin"
serial "github.com/ipfs/go-ipfs/repo/fsrepo/serialize"
ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
manet "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"
)
// GetNumNodes returns the number of testbed nodes configured in the testbed directory
func GetNumNodes() int {
for i := 0; i < 2000; i++ {
_, err := os.Stat(IpfsDirN(i))
if os.IsNotExist(err) {
return i
}
}
panic("i dont know whats going on")
}
func TestBedDir() string {
tbd := os.Getenv("IPTB_ROOT")
if len(tbd) != 0 {
return tbd
}
home := os.Getenv("HOME")
if len(home) == 0 {
panic("could not find home")
}
return path.Join(home, "testbed")
}
func IpfsDirN(n int) string {
return path.Join(TestBedDir(), fmt.Sprint(n))
}
func YesNoPrompt(prompt string) bool {
var s string
for {
fmt.Println(prompt)
fmt.Scanf("%s", &s)
switch s {
case "y", "Y":
return true
case "n", "N":
return false
}
fmt.Println("Please press either 'y' or 'n'")
}
}
type initCfg struct {
Count int
Force bool
Bootstrap string
PortStart int
Mdns bool
Utp bool
}
func (c *initCfg) swarmAddrForPeer(i int) string {
str := "/ip4/0.0.0.0/tcp/%d"
if c.Utp {
str = "/ip4/0.0.0.0/udp/%d/utp"
}
if c.PortStart == 0 {
return fmt.Sprintf(str, 0)
}
return fmt.Sprintf(str, c.PortStart+i)
}
func (c *initCfg) apiAddrForPeer(i int) string {
if c.PortStart == 0 {
return "/ip4/127.0.0.1/tcp/0"
}
return fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", c.PortStart+1000+i)
}
func IpfsInit(cfg *initCfg) error {
p := IpfsDirN(0)
if _, err := os.Stat(p); !os.IsNotExist(err) {
if !cfg.Force && !YesNoPrompt("testbed nodes already exist, overwrite? [y/n]") {
return nil
}
err := os.RemoveAll(TestBedDir())
if err != nil {
return err
}
}
wait := sync.WaitGroup{}
for i := 0; i < cfg.Count; i++ {
wait.Add(1)
go func(v int) {
defer wait.Done()
dir := IpfsDirN(v)
err := os.MkdirAll(dir, 0777)
if err != nil {
log.Println("ERROR: ", err)
return
}
cmd := exec.Command("ipfs", "init", "-b=1024")
cmd.Env = append(cmd.Env, "IPFS_PATH="+dir)
out, err := cmd.CombinedOutput()
if err != nil {
log.Println("ERROR: ", err)
log.Println(string(out))
}
}(i)
}
wait.Wait()
// Now setup bootstrapping
switch cfg.Bootstrap {
case "star":
err := starBootstrap(cfg)
if err != nil {
return err
}
case "none":
err := clearBootstrapping(cfg)
if err != nil {
return err
}
default:
return fmt.Errorf("unrecognized bootstrapping option: %s", cfg.Bootstrap)
}
return nil
}
func starBootstrap(icfg *initCfg) error {
// '0' node is the bootstrap node
cfgpath := path.Join(IpfsDirN(0), "config")
bcfg, err := serial.Load(cfgpath)
if err != nil {
return err
}
bcfg.Bootstrap = nil
bcfg.Addresses.Swarm = []string{icfg.swarmAddrForPeer(0)}
bcfg.Addresses.API = icfg.apiAddrForPeer(0)
bcfg.Addresses.Gateway = ""
bcfg.Discovery.MDNS.Enabled = icfg.Mdns
err = serial.WriteConfigFile(cfgpath, bcfg)
if err != nil {
return err
}
for i := 1; i < icfg.Count; i++ {
cfgpath := path.Join(IpfsDirN(i), "config")
cfg, err := serial.Load(cfgpath)
if err != nil {
return err
}
ba := fmt.Sprintf("%s/ipfs/%s", bcfg.Addresses.Swarm[0], bcfg.Identity.PeerID)
ba = strings.Replace(ba, "0.0.0.0", "127.0.0.1", -1)
cfg.Bootstrap = []string{ba}
cfg.Addresses.Gateway = ""
cfg.Discovery.MDNS.Enabled = icfg.Mdns
cfg.Addresses.Swarm = []string{
icfg.swarmAddrForPeer(i),
}
cfg.Addresses.API = icfg.apiAddrForPeer(i)
err = serial.WriteConfigFile(cfgpath, cfg)
if err != nil {
return err
}
}
return nil
}
func clearBootstrapping(icfg *initCfg) error {
for i := 0; i < icfg.Count; i++ {
cfgpath := path.Join(IpfsDirN(i), "config")
cfg, err := serial.Load(cfgpath)
if err != nil {
return err
}
cfg.Bootstrap = nil
cfg.Addresses.Gateway = ""
cfg.Addresses.Swarm = []string{icfg.swarmAddrForPeer(i)}
cfg.Addresses.API = icfg.apiAddrForPeer(i)
cfg.Discovery.MDNS.Enabled = icfg.Mdns
err = serial.WriteConfigFile(cfgpath, cfg)
if err != nil {
return err
}
}
return nil
}
func IpfsPidOf(n int) (int, error) {
dir := IpfsDirN(n)
b, err := ioutil.ReadFile(path.Join(dir, "daemon.pid"))
if err != nil {
return -1, err
}
return strconv.Atoi(string(b))
}
func KillNode(i int) error {
pid, err := IpfsPidOf(i)
if err != nil {
return fmt.Errorf("error killing daemon %d: %s", i, err)
}
p, err := os.FindProcess(pid)
if err != nil {
return fmt.Errorf("error killing daemon %d: %s", i, err)
}
err = p.Kill()
if err != nil {
return fmt.Errorf("error killing daemon %d: %s\n", i, err)
}
p.Wait()
err = os.Remove(path.Join(IpfsDirN(i), "daemon.pid"))
if err != nil {
return fmt.Errorf("error removing pid file for daemon %d: %s\n", i, err)
}
return nil
}
func IpfsKillAll() error {
n := GetNumNodes()
for i := 0; i < n; i++ {
err := KillNode(i)
if err != nil {
return err
}
}
return nil
}
func envForDaemon(n int) []string {
envs := os.Environ()
npath := "IPFS_PATH=" + IpfsDirN(n)
for i, e := range envs {
p := strings.Split(e, "=")
if p[0] == "IPFS_PATH" {
envs[i] = npath
return envs
}
}
return append(envs, npath)
}
func IpfsStart(waitall bool) error {
var addrs []string
n := GetNumNodes()
for i := 0; i < n; i++ {
dir := IpfsDirN(i)
cmd := exec.Command("ipfs", "daemon")
cmd.Dir = dir
cmd.Env = envForDaemon(i)
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
stdout, err := os.Create(path.Join(dir, "daemon.stdout"))
if err != nil {
return err
}
stderr, err := os.Create(path.Join(dir, "daemon.stderr"))
if err != nil {
return err
}
cmd.Stdout = stdout
cmd.Stderr = stderr
err = cmd.Start()
if err != nil {
return err
}
pid := cmd.Process.Pid
fmt.Printf("Started daemon %d, pid = %d\n", i, pid)
err = ioutil.WriteFile(path.Join(dir, "daemon.pid"), []byte(fmt.Sprint(pid)), 0666)
if err != nil {
return err
}
// Make sure node 0 is up before starting the rest so
// bootstrapping works properly
cfg, err := serial.Load(path.Join(IpfsDirN(i), "config"))
if err != nil {
return err
}
maddr := ma.StringCast(cfg.Addresses.API)
_, addr, err := manet.DialArgs(maddr)
if err != nil {
return err
}
addrs = append(addrs, addr)
err = waitOnAPI(cfg.Identity.PeerID, i)
if err != nil {
return err
}
}
if waitall {
for i := 0; i < n; i++ {
err := waitOnSwarmPeers(i)
if err != nil {
return err
}
}
}
return nil
}
func waitOnAPI(peerid string, nnum int) error {
for i := 0; i < 50; i++ {
err := tryAPICheck(peerid, nnum)
if err == nil {
return nil
}
time.Sleep(time.Millisecond * 200)
}
return fmt.Errorf("node %d failed to come online in given time period", nnum)
}
func getNodesAPIAddr(nnum int) (string, error) {
addrb, err := ioutil.ReadFile(path.Join(IpfsDirN(nnum), "api"))
if err != nil {
return "", err
}
maddr, err := ma.NewMultiaddr(string(addrb))
if err != nil {
fmt.Println("error parsing multiaddr: ", err)
return "", err
}
_, addr, err := manet.DialArgs(maddr)
if err != nil {
fmt.Println("error on multiaddr dialargs: ", err)
return "", err
}
return addr, nil
}
func tryAPICheck(peerid string, nnum int) error {
addr, err := getNodesAPIAddr(nnum)
if err != nil {
return err
}
resp, err := http.Get("http://" + addr + "/api/v0/id")
if err != nil {
return err
}
out := make(map[string]interface{})
err = json.NewDecoder(resp.Body).Decode(&out)
if err != nil {
return fmt.Errorf("liveness check failed: %s", err)
}
id, ok := out["ID"]
if !ok {
return fmt.Errorf("liveness check failed: ID field not present in output")
}
idstr := id.(string)
if idstr != peerid {
return fmt.Errorf("liveness check failed: unexpected peer at endpoint")
}
return nil
}
func waitOnSwarmPeers(nnum int) error {
addr, err := getNodesAPIAddr(nnum)
if err != nil {
return err
}
for i := 0; i < 50; i++ {
resp, err := http.Get("http://" + addr + "/api/v0/swarm/peers")
if err == nil {
out := make(map[string]interface{})
err := json.NewDecoder(resp.Body).Decode(&out)
if err != nil {
return fmt.Errorf("liveness check failed: %s", err)
}
peers := out["Strings"].([]interface{})
if len(peers) == 0 {
time.Sleep(time.Millisecond * 200)
continue
}
return nil
}
time.Sleep(time.Millisecond * 200)
}
return fmt.Errorf("node at %s failed to bootstrap in given time period", addr)
}
// GetPeerID reads the config of node 'n' and returns its peer ID
func GetPeerID(n int) (string, error) {
cfg, err := serial.Load(path.Join(IpfsDirN(n), "config"))
if err != nil {
return "", err
}
return cfg.Identity.PeerID, nil
}
// IpfsShell sets up environment variables for a new shell to more easily
// control the given daemon
func IpfsShell(n int) error {
shell := os.Getenv("SHELL")
if shell == "" {
return fmt.Errorf("couldnt find shell!")
}
dir := IpfsDirN(n)
nenvs := []string{"IPFS_PATH=" + dir}
nnodes := GetNumNodes()
for i := 0; i < nnodes; i++ {
peerid, err := GetPeerID(i)
if err != nil {
return err
}
nenvs = append(nenvs, fmt.Sprintf("NODE%d=%s", i, peerid))
}
nenvs = append(os.Environ(), nenvs...)
return syscall.Exec(shell, []string{shell}, nenvs)
}
func ConnectNodes(from, to int) error {
if from == to {
// skip connecting to self..
return nil
}
fmt.Printf("connecting %d -> %d\n", from, to)
cmd := exec.Command("ipfs", "id", "-f", "<addrs>")
cmd.Env = []string{"IPFS_PATH=" + IpfsDirN(to)}
out, err := cmd.Output()
if err != nil {
fmt.Println("ERR: ", string(out))
return err
}
addr := strings.Split(string(out), "\n")[0]
connectcmd := exec.Command("ipfs", "swarm", "connect", addr)
connectcmd.Env = []string{"IPFS_PATH=" + IpfsDirN(from)}
out, err = connectcmd.CombinedOutput()
if err != nil {
fmt.Println(string(out))
return err
}
return nil
}
func parseRange(s string) ([]int, error) {
if strings.HasPrefix(s, "[") && strings.HasSuffix(s, "]") {
ranges := strings.Split(s[1:len(s)-1], ",")
var out []int
for _, r := range ranges {
rng, err := expandDashRange(r)
if err != nil {
return nil, err
}
out = append(out, rng...)
}
return out, nil
} else {
i, err := strconv.Atoi(s)
if err != nil {
return nil, err
}
return []int{i}, nil
}
}
func expandDashRange(s string) ([]int, error) {
parts := strings.Split(s, "-")
if len(parts) == 0 {
i, err := strconv.Atoi(s)
if err != nil {
return nil, err
}
return []int{i}, nil
}
low, err := strconv.Atoi(parts[0])
if err != nil {
return nil, err
}
hi, err := strconv.Atoi(parts[1])
if err != nil {
return nil, err
}
var out []int
for i := low; i <= hi; i++ {
out = append(out, i)
}
return out, nil
}
func GetAttr(attr string, node int) (string, error) {
switch attr {
case "id":
return GetPeerID(node)
default:
return "", errors.New("unrecognized attribute")
}
}
var helptext = `Ipfs Testbed
Commands:
init
creates and initializes 'n' repos
Options:
-n=[number of nodes]
-f - force overwriting of existing nodes
-bootstrap - select bootstrapping style for cluster
choices: star, none
start
starts up all testbed nodes
Options:
-wait - wait until daemons are fully initialized
stop
kills all testbed nodes
restart
kills, then restarts all testbed nodes
shell [n]
execs your shell with environment variables set as follows:
IPFS_PATH - set to testbed node n's IPFS_PATH
NODE[x] - set to the peer ID of node x
get [attribute] [node]
get an attribute of the given node
currently supports: "id"
Env Vars:
IPTB_ROOT:
Used to specify the directory that nodes will be created in.
`
func handleErr(s string, err error) {
if err != nil {
fmt.Fprintln(os.Stderr, s, err)
os.Exit(1)
}
}
func main() {
cfg := new(initCfg)
kingpin.Flag("n", "number of ipfs nodes to initialize").Short('n').IntVar(&cfg.Count)
kingpin.Flag("port", "port to start allocations from").Default("4002").Short('p').IntVar(&cfg.PortStart)
kingpin.Flag("force", "force initialization (overwrite existing configs)").Short('f').BoolVar(&cfg.Force)
kingpin.Flag("mdns", "turn on mdns for nodes").BoolVar(&cfg.Mdns)
kingpin.Flag("bootstrap", "select bootstrapping style for cluster").Default("star").StringVar(&cfg.Bootstrap)
kingpin.Flag("utp", "use utp for addresses").BoolVar(&cfg.Utp)
wait := kingpin.Flag("wait", "wait for nodes to come fully online before exiting").Bool()
var args []string
kingpin.Arg("args", "arguments").StringsVar(&args)
kingpin.Parse()
switch args[0] {
case "init":
if cfg.Count == 0 {
fmt.Printf("please specify number of nodes: '%s init -n 10'\n", os.Args[0])
os.Exit(1)
}
err := IpfsInit(cfg)
handleErr("ipfs init err: ", err)
case "start":
err := IpfsStart(*wait)
handleErr("ipfs start err: ", err)
case "stop", "kill":
if len(args) > 1 {
i, err := strconv.Atoi(args[1])
if err != nil {
fmt.Println("failed to parse node number: ", err)
os.Exit(1)
}
err = KillNode(i)
if err != nil {
fmt.Println("failed to kill node: ", err)
}
return
}
err := IpfsKillAll()
handleErr("ipfs kill err: ", err)
case "restart":
err := IpfsKillAll()
handleErr("ipfs kill err: ", err)
err = IpfsStart(*wait)
handleErr("ipfs start err: ", err)
case "shell":
if len(args) < 2 {
fmt.Println("please specify which node you want a shell for")
os.Exit(1)
}
n, err := strconv.Atoi(args[1])
handleErr("parse err: ", err)
err = IpfsShell(n)
handleErr("ipfs shell err: ", err)
case "connect":
if len(args) < 3 {
fmt.Println("iptb connect [node] [node]")
os.Exit(1)
}
from, err := parseRange(args[1])
if err != nil {
fmt.Printf("failed to parse: %s\n", err)
return
}
to, err := parseRange(args[2])
if err != nil {
fmt.Printf("failed to parse: %s\n", err)
return
}
for _, f := range from {
for _, t := range to {
err = ConnectNodes(f, t)
if err != nil {
fmt.Printf("failed to connect: %s\n", err)
return
}
}
}
case "get":
if len(args) < 3 {
fmt.Println("iptb get [attr] [node]")
os.Exit(1)
}
attr := args[1]
num, err := strconv.Atoi(args[2])
handleErr("error parsing node number: ", err)
val, err := GetAttr(attr, num)
handleErr("error getting attribute: ", err)
fmt.Println(val)
default:
kingpin.Usage()
fmt.Println(helptext)
os.Exit(1)
}
}