@cryptotaxi247 / kubo / commits / e5512b411

make publish more configurable and add test for repub

License: MIT Signed-off-by: Jeromy <jeromyj@gmail.com>

Jeromy committed Sep 30, 2015 at 11:03 UTC e5512b411532b93696ced59dff88b1f0d87ea331
9 files changed +200 -31
cmd/ipfs/main.go
+6 -3
@@ -173,10 +173,13 @@ func (i *cmdInvocation) Run(ctx context.Context) (output io.Reader, err error) {
173 if err != nil {
174 return nil, err
175 }
176 - if debug || u.GetenvBool("DEBUG") || os.Getenv("IPFS_LOGGING") == "debug" {
176 + if debug || os.Getenv("IPFS_LOGGING") == "debug" {
177 u.Debug = true
178 logging.SetDebugLogging()
179 }
180 + if u.GetenvBool("DEBUG") {
181 + u.Debug = true
182 + }
183
184 res, err := callCommand(ctx, i.req, Root, i.cmd)
185 if err != nil {
@@ -668,6 +671,6 @@ func apiClientForAddr(addr ma.Multiaddr) (cmdsHttp.Client, error) {
671 }
672
673 func isConnRefused(err error) bool {
671 - return strings.Contains(err.Error(), "connection refused") ||
672 - strings.Contains(err.Error(), "target machine actively refused it")
674 + return strings.Contains(err.Error(), "connection refused") ||
675 + strings.Contains(err.Error(), "target machine actively refused it")
676 }
core/commands/publish.go
+38 -23
@@ -5,6 +5,7 @@ import (
5 "fmt"
6 "io"
7 "strings"
8 + "time"
9
10 context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
11
@@ -46,8 +47,11 @@ Publish an <ipfs-path> to another public key (not implemented):
47 },
48
49 Arguments: []cmds.Argument{
49 - cmds.StringArg("name", false, false, "The IPNS name to publish to. Defaults to your node's peerID"),
50 - cmds.StringArg("ipfs-path", true, false, "IPFS path of the obejct to be published at <name>").EnableStdin(),
50 + cmds.StringArg("ipfs-path", true, false, "IPFS path of the obejct to be published").EnableStdin(),
51 + },
52 + Options: []cmds.Option{
53 + cmds.BoolOption("resolve", "resolve given path before publishing (default=true)"),
54 + cmds.StringOption("lifetime", "t", "time duration that the record will be valid for (default: 24hrs)"),
55 },
56 Run: func(req cmds.Request, res cmds.Response) {
57 log.Debug("Begin Publish")
@@ -65,32 +69,34 @@ Publish an <ipfs-path> to another public key (not implemented):
69 }
70 }
71
68 - args := req.Arguments()
72 + pstr := req.Arguments()[0]
73
74 if n.Identity == "" {
75 res.SetError(errors.New("Identity not loaded!"), cmds.ErrNormal)
76 return
77 }
78
75 - var name string
76 - var pstr string
79 + popts := &publishOpts{
80 + verifyExists: true,
81 + pubValidTime: time.Hour * 24,
82 + }
83
78 - switch len(args) {
79 - case 2:
80 - name = args[0]
81 - pstr = args[1]
82 - if name != n.Identity.Pretty() {
83 - res.SetError(errors.New("keychains not yet implemented"), cmds.ErrNormal)
84 + verif, found, _ := req.Option("resolve").Bool()
85 + if found {
86 + popts.verifyExists = verif
87 + }
88 + validtime, found, _ := req.Option("lifetime").String()
89 + if found {
90 + d, err := time.ParseDuration(validtime)
91 + if err != nil {
92 + res.SetError(fmt.Errorf("error parsing lifetime option: %s", err), cmds.ErrNormal)
93 return
94 }
86 - case 1:
87 - // name = n.Identity.Pretty()
88 - pstr = args[0]
95 +
96 + popts.pubValidTime = d
97 }
98
91 - // TODO n.Keychain.Get(name).PrivKey
92 - // TODO(cryptix): is req.Context().Context a child of n.Context()?
93 - output, err := publish(req.Context(), n, n.PrivateKey, path.Path(pstr))
99 + output, err := publish(req.Context(), n, n.PrivateKey, path.Path(pstr), popts)
100 if err != nil {
101 res.SetError(err, cmds.ErrNormal)
102 return
@@ -107,14 +113,23 @@ Publish an <ipfs-path> to another public key (not implemented):
113 Type: IpnsEntry{},
114 }
115
110 -func publish(ctx context.Context, n *core.IpfsNode, k crypto.PrivKey, ref path.Path) (*IpnsEntry, error) {
111 - // First, verify the path exists
112 - _, err := core.Resolve(ctx, n, ref)
113 - if err != nil {
114 - return nil, err
116 +type publishOpts struct {
117 + verifyExists bool
118 + pubValidTime time.Duration
119 +}
120 +
121 +func publish(ctx context.Context, n *core.IpfsNode, k crypto.PrivKey, ref path.Path, opts *publishOpts) (*IpnsEntry, error) {
122 +
123 + if opts.verifyExists {
124 + // verify the path exists
125 + _, err := core.Resolve(ctx, n, ref)
126 + if err != nil {
127 + return nil, err
128 + }
129 }
130
117 - err = n.Namesys.Publish(ctx, k, ref)
131 + eol := time.Now().Add(opts.pubValidTime)
132 + err := n.Namesys.PublishWithEOL(ctx, k, ref, eol)
133 if err != nil {
134 return nil, err
135 }
core/core.go
+23 -3
@@ -56,6 +56,7 @@ import (
56 pin "github.com/ipfs/go-ipfs/pin"
57 repo "github.com/ipfs/go-ipfs/repo"
58 config "github.com/ipfs/go-ipfs/repo/config"
59 + u "github.com/ipfs/go-ipfs/util"
60 )
61
62 const IpnsValidatorTag = "ipns"
@@ -229,26 +230,45 @@ func (n *IpfsNode) startOnlineServicesWithHost(ctx context.Context, host p2phost
230 n.Namesys = namesys.NewNameSystem(n.Routing)
231
232 // setup ipns republishing
232 - n.IpnsRepub = ipnsrp.NewRepublisher(n.Routing, n.Repo.Datastore(), n.Peerstore)
233 - n.IpnsRepub.AddName(n.Identity)
233 + err = n.setupIpnsRepublisher()
234 + if err != nil {
235 + return err
236 + }
237
238 + return nil
239 +}
240 +
241 +func (n *IpfsNode) setupIpnsRepublisher() error {
242 cfg, err := n.Repo.Config()
243 if err != nil {
244 return err
245 }
246 +
247 + n.IpnsRepub = ipnsrp.NewRepublisher(n.Routing, n.Repo.Datastore(), n.Peerstore)
248 + n.IpnsRepub.AddName(n.Identity)
249 +
250 if cfg.Ipns.RepublishPeriod != "" {
251 d, err := time.ParseDuration(cfg.Ipns.RepublishPeriod)
252 if err != nil {
253 return fmt.Errorf("failure to parse config setting IPNS.RepublishPeriod: %s", err)
254 }
255
245 - if d < time.Minute || d > (time.Hour*24) {
256 + if !u.Debug && (d < time.Minute || d > (time.Hour*24)) {
257 return fmt.Errorf("config setting IPNS.RepublishPeriod is not between 1min and 1day: %s", d)
258 }
259
260 n.IpnsRepub.Interval = d
261 }
262
263 + if cfg.Ipns.RecordLifetime != "" {
264 + d, err := time.ParseDuration(cfg.Ipns.RepublishPeriod)
265 + if err != nil {
266 + return fmt.Errorf("failure to parse config setting IPNS.RecordLifetime: %s", err)
267 + }
268 +
269 + n.IpnsRepub.RecordLifetime = d
270 + }
271 +
272 n.Process().Go(n.IpnsRepub.Run)
273
274 return nil
core/corehttp/gateway_test.go
+5
@@ -7,6 +7,7 @@ import (
7 "net/http/httptest"
8 "strings"
9 "testing"
10 + "time"
11
12 context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
13 core "github.com/ipfs/go-ipfs/core"
@@ -37,6 +38,10 @@ func (m mockNamesys) Publish(ctx context.Context, name ci.PrivKey, value path.Pa
38 return errors.New("not implemented for mockNamesys")
39 }
40
41 +func (m mockNamesys) PublishWithEOL(ctx context.Context, name ci.PrivKey, value path.Path, _ time.Time) error {
42 + return errors.New("not implemented for mockNamesys")
43 +}
44 +
45 func newNodeWithMockNamesys(ns mockNamesys) (*core.IpfsNode, error) {
46 c := config.Config{
47 Identity: config.Identity{
namesys/interface.go
+5
@@ -31,6 +31,7 @@ package namesys
31
32 import (
33 "errors"
34 + "time"
35
36 context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
37 ci "github.com/ipfs/go-ipfs/p2p/crypto"
@@ -105,4 +106,8 @@ type Publisher interface {
106 // Publish establishes a name-value mapping.
107 // TODO make this not PrivKey specific.
108 Publish(ctx context.Context, name ci.PrivKey, value path.Path) error
109 +
110 + // TODO: to be replaced by a more generic 'PublishWithValidity' type
111 + // call once the records spec is implemented
112 + PublishWithEOL(ctx context.Context, name ci.PrivKey, value path.Path, eol time.Time) error
113 }
namesys/namesys.go
+5
@@ -2,6 +2,7 @@ package namesys
2
3 import (
4 "strings"
5 + "time"
6
7 context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
8 ci "github.com/ipfs/go-ipfs/p2p/crypto"
@@ -81,3 +82,7 @@ func (ns *mpns) resolveOnce(ctx context.Context, name string) (path.Path, error)
82 func (ns *mpns) Publish(ctx context.Context, name ci.PrivKey, value path.Path) error {
83 return ns.publishers["/ipns/"].Publish(ctx, name, value)
84 }
85 +
86 +func (ns *mpns) PublishWithEOL(ctx context.Context, name ci.PrivKey, val path.Path, eol time.Time) error {
87 + return ns.publishers["/ipns/"].PublishWithEOL(ctx, name, val, eol)
88 +}
repo/config/ipns.go
+1
@@ -2,4 +2,5 @@ package config
2
3 type Ipns struct {
4 RepublishPeriod string
5 + RecordLifetime string
6 }
test/sharness/t0100-name.sh
+3 -2
@@ -56,13 +56,14 @@ test_expect_success "resolve output looks good" '
56
57 # publish with an explicit node ID
58
59 -test_expect_success "'ipfs name publish <local-id> <hash>' succeeds" '
59 +test_expect_failure "'ipfs name publish <local-id> <hash>' succeeds" '
60 PEERID=`ipfs id --format="<id>"` &&
61 test_check_peerid "${PEERID}" &&
62 + echo ipfs name publish "${PEERID}" "/ipfs/$HASH_WELCOME_DOCS" &&
63 ipfs name publish "${PEERID}" "/ipfs/$HASH_WELCOME_DOCS" >actual_node_id_publish
64 '
65
65 -test_expect_success "publish with our explicit node ID looks good" '
66 +test_expect_failure "publish with our explicit node ID looks good" '
67 echo "Published to ${PEERID}: /ipfs/$HASH_WELCOME_DOCS" >expected_node_id_publish &&
68 test_cmp expected_node_id_publish actual_node_id_publish
69 '
test/sharness/t0240-republisher.sh new
+114
@@ -0,0 +1,114 @@
1 +#!/bin/sh
2 +#
3 +# Copyright (c) 2014 Jeromy Johnson
4 +# MIT Licensed; see the LICENSE file in this repository.
5 +#
6 +
7 +test_description="Test ipfs repo operations"
8 +
9 +. lib/test-lib.sh
10 +
11 +export IPTB_ROOT="`pwd`/.iptb"
12 +export DEBUG=true
13 +
14 +ipfsi() {
15 + local dir=$1; shift; IPFS_PATH="$IPTB_ROOT/$dir" ipfs $@
16 +}
17 +
18 +setup_iptb() {
19 + test_expect_success "iptb init" '
20 + iptb init -n4 --bootstrap none --port 0
21 + '
22 +
23 + test_expect_success "set configs up" '
24 + for i in `seq 0 3`
25 + do
26 + ipfsi $i config Ipns.RepublishPeriod 20s
27 + done
28 + '
29 +
30 + test_expect_success "start up nodes" '
31 + iptb start
32 + '
33 +
34 + test_expect_success "connect nodes" '
35 + iptb connect 0 1 &&
36 + iptb connect 0 2 &&
37 + iptb connect 0 3
38 + '
39 +
40 + test_expect_success "nodes have connections" '
41 + ipfsi 0 swarm peers | grep ipfs &&
42 + ipfsi 1 swarm peers | grep ipfs &&
43 + ipfsi 2 swarm peers | grep ipfs &&
44 + ipfsi 3 swarm peers | grep ipfs
45 + '
46 +}
47 +
48 +teardown_iptb() {
49 + test_expect_success "shut down nodes" '
50 + iptb kill
51 + '
52 +}
53 +
54 +verify_can_resolve() {
55 + node=$1
56 + name=$2
57 + expected=$3
58 +
59 + test_expect_success "node can resolve entry" '
60 + ipfsi $node name resolve $name > resolve
61 + '
62 +
63 + test_expect_success "output looks right" '
64 + printf /ipfs/$expected > expected &&
65 + test_cmp resolve expected
66 + '
67 +}
68 +
69 +verify_cannot_resolve() {
70 + node=$1
71 + name=$2
72 +
73 + echo "verifying resolution fails on node $node"
74 + test_expect_success "node cannot resolve entry" '
75 + # TODO: this should work without the timeout option
76 + # but it currently hangs for some reason every so often
77 + test_expect_code 1 ipfsi $node name resolve --timeout=300ms $name
78 + '
79 +}
80 +
81 +setup_iptb
82 +
83 +test_expect_success "publish succeeds" '
84 + HASH=$(echo "foobar" | ipfsi 1 add -q) &&
85 + ipfsi 1 name publish -t 5s $HASH
86 +'
87 +
88 +test_expect_success "other nodes can resolve" '
89 + id=$(ipfsi 1 id -f "<id>") &&
90 + verify_can_resolve 0 $id $HASH &&
91 + verify_can_resolve 1 $id $HASH &&
92 + verify_can_resolve 2 $id $HASH &&
93 + verify_can_resolve 3 $id $HASH
94 +'
95 +
96 +test_expect_success "after five seconds, records are invalid" '
97 + go-sleep 5s &&
98 + verify_cannot_resolve 0 $id &&
99 + verify_cannot_resolve 1 $id &&
100 + verify_cannot_resolve 2 $id &&
101 + verify_cannot_resolve 3 $id
102 +'
103 +
104 +test_expect_success "republisher fires after twenty seconds" '
105 + go-sleep 15s &&
106 + verify_can_resolve 0 $id $HASH &&
107 + verify_can_resolve 1 $id $HASH &&
108 + verify_can_resolve 2 $id $HASH &&
109 + verify_can_resolve 3 $id $HASH
110 +'
111 +
112 +teardown_iptb
113 +
114 +test_done