From e5512b411532b93696ced59dff88b1f0d87ea331 Mon Sep 17 00:00:00 2001 From: Jeromy Date: Wed, 30 Sep 2015 11:03:44 -0700 Subject: [PATCH] make publish more configurable and add test for repub License: MIT Signed-off-by: Jeromy --- cmd/ipfs/main.go | 9 ++- core/commands/publish.go | 67 ++++++++++------- core/core.go | 26 ++++++- core/corehttp/gateway_test.go | 5 ++ namesys/interface.go | 5 ++ namesys/namesys.go | 5 ++ repo/config/ipns.go | 1 + test/sharness/t0100-name.sh | 5 +- test/sharness/t0240-republisher.sh | 114 +++++++++++++++++++++++++++++ 9 files changed, 203 insertions(+), 34 deletions(-) create mode 100755 test/sharness/t0240-republisher.sh diff --git a/cmd/ipfs/main.go b/cmd/ipfs/main.go index eec4ae840..a16e68d7d 100644 --- a/cmd/ipfs/main.go +++ b/cmd/ipfs/main.go @@ -173,10 +173,13 @@ func (i *cmdInvocation) Run(ctx context.Context) (output io.Reader, err error) { if err != nil { return nil, err } - if debug || u.GetenvBool("DEBUG") || os.Getenv("IPFS_LOGGING") == "debug" { + if debug || os.Getenv("IPFS_LOGGING") == "debug" { u.Debug = true logging.SetDebugLogging() } + if u.GetenvBool("DEBUG") { + u.Debug = true + } res, err := callCommand(ctx, i.req, Root, i.cmd) if err != nil { @@ -668,6 +671,6 @@ func apiClientForAddr(addr ma.Multiaddr) (cmdsHttp.Client, error) { } func isConnRefused(err error) bool { - return strings.Contains(err.Error(), "connection refused") || - strings.Contains(err.Error(), "target machine actively refused it") + return strings.Contains(err.Error(), "connection refused") || + strings.Contains(err.Error(), "target machine actively refused it") } diff --git a/core/commands/publish.go b/core/commands/publish.go index c1798e15d..0737a0117 100644 --- a/core/commands/publish.go +++ b/core/commands/publish.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "strings" + "time" context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" @@ -46,8 +47,11 @@ Publish an to another public key (not implemented): }, Arguments: []cmds.Argument{ - cmds.StringArg("name", false, false, "The IPNS name to publish to. Defaults to your node's peerID"), - cmds.StringArg("ipfs-path", true, false, "IPFS path of the obejct to be published at ").EnableStdin(), + cmds.StringArg("ipfs-path", true, false, "IPFS path of the obejct to be published").EnableStdin(), + }, + Options: []cmds.Option{ + cmds.BoolOption("resolve", "resolve given path before publishing (default=true)"), + cmds.StringOption("lifetime", "t", "time duration that the record will be valid for (default: 24hrs)"), }, Run: func(req cmds.Request, res cmds.Response) { log.Debug("Begin Publish") @@ -65,32 +69,34 @@ Publish an to another public key (not implemented): } } - args := req.Arguments() + pstr := req.Arguments()[0] if n.Identity == "" { res.SetError(errors.New("Identity not loaded!"), cmds.ErrNormal) return } - var name string - var pstr string - - switch len(args) { - case 2: - name = args[0] - pstr = args[1] - if name != n.Identity.Pretty() { - res.SetError(errors.New("keychains not yet implemented"), cmds.ErrNormal) - return - } - case 1: - // name = n.Identity.Pretty() - pstr = args[0] + popts := &publishOpts{ + verifyExists: true, + pubValidTime: time.Hour * 24, } - // TODO n.Keychain.Get(name).PrivKey - // TODO(cryptix): is req.Context().Context a child of n.Context()? - output, err := publish(req.Context(), n, n.PrivateKey, path.Path(pstr)) + verif, found, _ := req.Option("resolve").Bool() + if found { + popts.verifyExists = verif + } + validtime, found, _ := req.Option("lifetime").String() + if found { + d, err := time.ParseDuration(validtime) + if err != nil { + res.SetError(fmt.Errorf("error parsing lifetime option: %s", err), cmds.ErrNormal) + return + } + + popts.pubValidTime = d + } + + output, err := publish(req.Context(), n, n.PrivateKey, path.Path(pstr), popts) if err != nil { res.SetError(err, cmds.ErrNormal) return @@ -107,14 +113,23 @@ Publish an to another public key (not implemented): Type: IpnsEntry{}, } -func publish(ctx context.Context, n *core.IpfsNode, k crypto.PrivKey, ref path.Path) (*IpnsEntry, error) { - // First, verify the path exists - _, err := core.Resolve(ctx, n, ref) - if err != nil { - return nil, err +type publishOpts struct { + verifyExists bool + pubValidTime time.Duration +} + +func publish(ctx context.Context, n *core.IpfsNode, k crypto.PrivKey, ref path.Path, opts *publishOpts) (*IpnsEntry, error) { + + if opts.verifyExists { + // verify the path exists + _, err := core.Resolve(ctx, n, ref) + if err != nil { + return nil, err + } } - err = n.Namesys.Publish(ctx, k, ref) + eol := time.Now().Add(opts.pubValidTime) + err := n.Namesys.PublishWithEOL(ctx, k, ref, eol) if err != nil { return nil, err } diff --git a/core/core.go b/core/core.go index 8b61a42b4..fee58a928 100644 --- a/core/core.go +++ b/core/core.go @@ -56,6 +56,7 @@ import ( pin "github.com/ipfs/go-ipfs/pin" repo "github.com/ipfs/go-ipfs/repo" config "github.com/ipfs/go-ipfs/repo/config" + u "github.com/ipfs/go-ipfs/util" ) const IpnsValidatorTag = "ipns" @@ -229,26 +230,45 @@ func (n *IpfsNode) startOnlineServicesWithHost(ctx context.Context, host p2phost n.Namesys = namesys.NewNameSystem(n.Routing) // setup ipns republishing - n.IpnsRepub = ipnsrp.NewRepublisher(n.Routing, n.Repo.Datastore(), n.Peerstore) - n.IpnsRepub.AddName(n.Identity) + err = n.setupIpnsRepublisher() + if err != nil { + return err + } + return nil +} + +func (n *IpfsNode) setupIpnsRepublisher() error { cfg, err := n.Repo.Config() if err != nil { return err } + + n.IpnsRepub = ipnsrp.NewRepublisher(n.Routing, n.Repo.Datastore(), n.Peerstore) + n.IpnsRepub.AddName(n.Identity) + if cfg.Ipns.RepublishPeriod != "" { d, err := time.ParseDuration(cfg.Ipns.RepublishPeriod) if err != nil { return fmt.Errorf("failure to parse config setting IPNS.RepublishPeriod: %s", err) } - if d < time.Minute || d > (time.Hour*24) { + if !u.Debug && (d < time.Minute || d > (time.Hour*24)) { return fmt.Errorf("config setting IPNS.RepublishPeriod is not between 1min and 1day: %s", d) } n.IpnsRepub.Interval = d } + if cfg.Ipns.RecordLifetime != "" { + d, err := time.ParseDuration(cfg.Ipns.RepublishPeriod) + if err != nil { + return fmt.Errorf("failure to parse config setting IPNS.RecordLifetime: %s", err) + } + + n.IpnsRepub.RecordLifetime = d + } + n.Process().Go(n.IpnsRepub.Run) return nil diff --git a/core/corehttp/gateway_test.go b/core/corehttp/gateway_test.go index e84c6f51c..9c258cbae 100644 --- a/core/corehttp/gateway_test.go +++ b/core/corehttp/gateway_test.go @@ -7,6 +7,7 @@ import ( "net/http/httptest" "strings" "testing" + "time" context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" core "github.com/ipfs/go-ipfs/core" @@ -37,6 +38,10 @@ func (m mockNamesys) Publish(ctx context.Context, name ci.PrivKey, value path.Pa return errors.New("not implemented for mockNamesys") } +func (m mockNamesys) PublishWithEOL(ctx context.Context, name ci.PrivKey, value path.Path, _ time.Time) error { + return errors.New("not implemented for mockNamesys") +} + func newNodeWithMockNamesys(ns mockNamesys) (*core.IpfsNode, error) { c := config.Config{ Identity: config.Identity{ diff --git a/namesys/interface.go b/namesys/interface.go index 5903c78a3..09c296c23 100644 --- a/namesys/interface.go +++ b/namesys/interface.go @@ -31,6 +31,7 @@ package namesys import ( "errors" + "time" context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" ci "github.com/ipfs/go-ipfs/p2p/crypto" @@ -105,4 +106,8 @@ type Publisher interface { // Publish establishes a name-value mapping. // TODO make this not PrivKey specific. Publish(ctx context.Context, name ci.PrivKey, value path.Path) error + + // TODO: to be replaced by a more generic 'PublishWithValidity' type + // call once the records spec is implemented + PublishWithEOL(ctx context.Context, name ci.PrivKey, value path.Path, eol time.Time) error } diff --git a/namesys/namesys.go b/namesys/namesys.go index 7fe317b66..ed03b4cc2 100644 --- a/namesys/namesys.go +++ b/namesys/namesys.go @@ -2,6 +2,7 @@ package namesys import ( "strings" + "time" context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" ci "github.com/ipfs/go-ipfs/p2p/crypto" @@ -81,3 +82,7 @@ func (ns *mpns) resolveOnce(ctx context.Context, name string) (path.Path, error) func (ns *mpns) Publish(ctx context.Context, name ci.PrivKey, value path.Path) error { return ns.publishers["/ipns/"].Publish(ctx, name, value) } + +func (ns *mpns) PublishWithEOL(ctx context.Context, name ci.PrivKey, val path.Path, eol time.Time) error { + return ns.publishers["/ipns/"].PublishWithEOL(ctx, name, val, eol) +} diff --git a/repo/config/ipns.go b/repo/config/ipns.go index feb77be10..feab6f044 100644 --- a/repo/config/ipns.go +++ b/repo/config/ipns.go @@ -2,4 +2,5 @@ package config type Ipns struct { RepublishPeriod string + RecordLifetime string } diff --git a/test/sharness/t0100-name.sh b/test/sharness/t0100-name.sh index d782729ca..aa8924bc0 100755 --- a/test/sharness/t0100-name.sh +++ b/test/sharness/t0100-name.sh @@ -56,13 +56,14 @@ test_expect_success "resolve output looks good" ' # publish with an explicit node ID -test_expect_success "'ipfs name publish ' succeeds" ' +test_expect_failure "'ipfs name publish ' succeeds" ' PEERID=`ipfs id --format=""` && test_check_peerid "${PEERID}" && + echo ipfs name publish "${PEERID}" "/ipfs/$HASH_WELCOME_DOCS" && ipfs name publish "${PEERID}" "/ipfs/$HASH_WELCOME_DOCS" >actual_node_id_publish ' -test_expect_success "publish with our explicit node ID looks good" ' +test_expect_failure "publish with our explicit node ID looks good" ' echo "Published to ${PEERID}: /ipfs/$HASH_WELCOME_DOCS" >expected_node_id_publish && test_cmp expected_node_id_publish actual_node_id_publish ' diff --git a/test/sharness/t0240-republisher.sh b/test/sharness/t0240-republisher.sh new file mode 100755 index 000000000..0168a664f --- /dev/null +++ b/test/sharness/t0240-republisher.sh @@ -0,0 +1,114 @@ +#!/bin/sh +# +# Copyright (c) 2014 Jeromy Johnson +# MIT Licensed; see the LICENSE file in this repository. +# + +test_description="Test ipfs repo operations" + +. lib/test-lib.sh + +export IPTB_ROOT="`pwd`/.iptb" +export DEBUG=true + +ipfsi() { + local dir=$1; shift; IPFS_PATH="$IPTB_ROOT/$dir" ipfs $@ +} + +setup_iptb() { + test_expect_success "iptb init" ' + iptb init -n4 --bootstrap none --port 0 + ' + + test_expect_success "set configs up" ' + for i in `seq 0 3` + do + ipfsi $i config Ipns.RepublishPeriod 20s + done + ' + + test_expect_success "start up nodes" ' + iptb start + ' + + test_expect_success "connect nodes" ' + iptb connect 0 1 && + iptb connect 0 2 && + iptb connect 0 3 + ' + + test_expect_success "nodes have connections" ' + ipfsi 0 swarm peers | grep ipfs && + ipfsi 1 swarm peers | grep ipfs && + ipfsi 2 swarm peers | grep ipfs && + ipfsi 3 swarm peers | grep ipfs + ' +} + +teardown_iptb() { + test_expect_success "shut down nodes" ' + iptb kill + ' +} + +verify_can_resolve() { + node=$1 + name=$2 + expected=$3 + + test_expect_success "node can resolve entry" ' + ipfsi $node name resolve $name > resolve + ' + + test_expect_success "output looks right" ' + printf /ipfs/$expected > expected && + test_cmp resolve expected + ' +} + +verify_cannot_resolve() { + node=$1 + name=$2 + + echo "verifying resolution fails on node $node" + test_expect_success "node cannot resolve entry" ' + # TODO: this should work without the timeout option + # but it currently hangs for some reason every so often + test_expect_code 1 ipfsi $node name resolve --timeout=300ms $name + ' +} + +setup_iptb + +test_expect_success "publish succeeds" ' + HASH=$(echo "foobar" | ipfsi 1 add -q) && + ipfsi 1 name publish -t 5s $HASH +' + +test_expect_success "other nodes can resolve" ' + id=$(ipfsi 1 id -f "") && + verify_can_resolve 0 $id $HASH && + verify_can_resolve 1 $id $HASH && + verify_can_resolve 2 $id $HASH && + verify_can_resolve 3 $id $HASH +' + +test_expect_success "after five seconds, records are invalid" ' + go-sleep 5s && + verify_cannot_resolve 0 $id && + verify_cannot_resolve 1 $id && + verify_cannot_resolve 2 $id && + verify_cannot_resolve 3 $id +' + +test_expect_success "republisher fires after twenty seconds" ' + go-sleep 15s && + verify_can_resolve 0 $id $HASH && + verify_can_resolve 1 $id $HASH && + verify_can_resolve 2 $id $HASH && + verify_can_resolve 3 $id $HASH +' + +teardown_iptb + +test_done