Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BasicHost changes for introspection #774

Merged

Conversation

aarshkshah1992
Copy link
Contributor

@aarshkshah1992 aarshkshah1992 commented Jan 22, 2020

For #775

Allows the BasicHost to be "introspectable".

@aarshkshah1992 aarshkshah1992 force-pushed the feat/introspectable-host branch from 004f0e7 to 694477a Compare January 22, 2020 16:18
@aarshkshah1992 aarshkshah1992 force-pushed the feat/introspectable-host branch from 1c61da3 to b5a2ce9 Compare January 23, 2020 10:11
@@ -191,12 +194,54 @@ func NewHost(ctx context.Context, net network.Network, opts *HostOpts) (*BasicHo
h.pings = ping.NewPingService(h)
}

if h.introspector == nil {
h.introspector = introspection.NewDefaultIntrospector()
}
Copy link
Contributor Author

@aarshkshah1992 aarshkshah1992 Jan 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This duplicates the code in config.NewNode() here.

We can safely assume here that an introspector exists.

However, do we want to allow users to be able to disable introspection ? That decision will affect how we've currently configured introspection on the host.

return h, nil
}

func (h *BasicHost) runtimeDataProvider() *coreit.RuntimeProviders {
// TODO What is the version here ?
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does version mean ?

@@ -783,6 +828,13 @@ func (h *BasicHost) AllAddrs() []ma.Multiaddr {
return dedupAddrs(finalAddrs)
}

// TODO Do this for routed & relay hosts as well
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to give this a thought.

@aarshkshah1992
Copy link
Contributor Author

TODO

  • Release go-libp2p-core & configure the version here
  • Address the TODOs in code

@aarshkshah1992 aarshkshah1992 changed the title [WIP] BasicHost changes for introspection BasicHost changes for introspection Jan 23, 2020
@aarshkshah1992 aarshkshah1992 changed the base branch from master to feat/introspection January 23, 2020 11:55
Copy link
Member

@raulk raulk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some initial feedback. This is shaping up good!

config/config.go Outdated
@@ -77,6 +79,9 @@ type Config struct {

EnableAutoRelay bool
StaticRelays []peer.AddrInfo

Introspector coreit.Introspector
IntrospectionServerAddr string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should allow the user to pass in an address. For UX, I would recommend the following:

  • We add an ListenAddress() string method to the Introspector interface, so we can query it later.
  • The Introspector implementation has a DefaultAddress, and may use fallback logic if the default listen address is taken. For example, the default address could be: ws://localhost:8096, and if the port is occupied, we can fall back to trying 8096 + [1,100], trying one by one until we find an available port, or fail entirely. See https://github.com/libp2p/go-libp2p-daemon/blob/b95e77dbfcd186ccf817f51e95f73f9fd5982600/p2pd/main.go#L30-L60 for an example of this logic.
  • We expose the final listen address via the ListenAddress() string method.
  • Pushing this concern to the implementation is better, because alternate implementations may use different protocols (e.g. Unix sockets) and heuristics.

For a user that wants to set a concrete address, they would construct the concrete implementation via its constructor (which would take a configuration object, that includes the listen addr), and inject that via the Introspector option.

Copy link
Contributor Author

@aarshkshah1992 aarshkshah1992 Jan 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@raulk

I am not sure I agree. Why shouldn't we allow to user to pass in a address while creating a libp2p node ?

The address is a required by the introspection WS server which is owned by the Host.

The Introspector is only responsible for proving a registry for sub-systems & resolving the state when it is asked to. It is not/shouldn't be responsible for serving that state over the wire & hence dosen't need to be bothered with finding a free port which is anyways not a trivial operation & muddles the construction logic of the Introspector.

I'd like to make ListenAddress() string a part of the IntrospectableHost interface that you've described here. The go-libp2p-introspection.StartWsServer method returns the address it was able to bind to(using the same logic as the code that you've linked) which the host then captures & returns via ListenAddress() string.

Wdyt ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I was unclear. We should totally allow passing in a listen address, but not as a dedicated host option. For two reasons:

  1. Creating such granular field-level options muddies the namespace.
  2. Listen addresses, along with all potential customisations of the introspection endpoint, are implementation-dependent.

Users wanting to customise the listen addr of an introspection server, should be able to do that through the endpoint's constructor:

ep, err := introspection.Endpoint(&introspection.EndpointOpts{
    ListenAddress: "...",
}
h, err := host.New(host.Introspector(ep))

config/config.go Outdated Show resolved Hide resolved
options.go Outdated Show resolved Hide resolved
p2p/host/basic/basic_host.go Outdated Show resolved Hide resolved
Copy link
Member

@raulk raulk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm gonna be refactoring things a little bit myself; no need for you to do anything here, @aarshkshah1992. Just wanting to provide feedback for future PRs.

package libp2p

import (
"context"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please group imports in groups, in this order:

  1. one group for stdlib
  2. one group for go-libp2p-core
  3. other go-libp2p imports
  4. 3rd party libs

})
s1, err := h1.NewStream(ctx, h3.ID(), p1)
require.NoError(t, err)
wg.Add(1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is racy. It's possible for wg.Done() to get called before the the wg.Add(1). Move the Add to before opening the stream to remove the race.

_, err = s1.Write(msg1)
require.NoError(t, err)
bz1 := make([]byte, len(msg2))
wg.Wait()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need this wait at all. You can use io.ReadFull instead.

msg3 := []byte("111")
msg4 := []byte("0000")

iaddr := "0.0.0.0:9999"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests should not listen on 0.0.0.0 (that's insecure) nor on a fixed port (it might be occupied). Instead, we should listen on 127.0.0.1:0, and allow the listener to allocate a port. ListenAddrs() should then return the actual addresses, not the user-supplied ones.

bz1 := make([]byte, len(msg2))
wg.Wait()
_, err = s1.Read(bz1)
require.NoError(t, err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can do require := require.New(t) at the top, and omit the first t parameter from all assertions.

// host2 -> OPENS Stream 2 -> host1 , writes a message & reads the response
p2 := protocol.ID("h2h1")
h1.SetStreamHandler(p2, func(s network.Stream) {
bz := make([]byte, len(msg3))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use buf. It shadows previous usages, and it's idiomatic.

_, err = s2.Write(msg3)
require.NoError(t, err)
bz2 := make([]byte, len(msg4))
wg.Wait()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for waitgroups. This code is racy, and we can use io.ReadFull instead.

// wait till connection is established
i := 0
var connection *websocket.Conn
for {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this loop declaration can be made more elegant, by moving the init, test, increment up here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aarshkshah1992
Copy link
Contributor Author

aarshkshah1992 commented Feb 7, 2020

@raulk

Turns out I didn't push the changes after basing them on the new core changes. :/
Thanks for the review. I can make the changes myself if you like. Let me know & I'll fix the PR.

@raulk raulk merged commit 570621a into libp2p:feat/introspection Feb 10, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants