Skip to content

Commit

Permalink
Add an utility to check the network visibility from other nodes (#853)
Browse files Browse the repository at this point in the history
* add network check util

* building pass

* avoid data race and first addr start by 1

* linting passing

* increasing http server

* increase again http timeout

* Put the latest versions of protobuf tooling

- https://pkg.go.dev/google.golang.org/grpc/cmd/protoc-gen-go-grpc latest 1.1.0
- protoc https://github.com/protocolbuffers/protobuf/releases/tag/v3.17.1
- https://pkg.go.dev/google.golang.org/protobuf/cmd/protoc-gen-go 1.27

* changing get path

* handle tls and non tls on CLI

* protobuf reverted to previous version

* tried again to be compatible with github action generate

* using new path for protoc-gen-go

* removed dep on gen-go-grpc

* lint action simplified

* linting pass

* try to remove the gocritic complaints...
  • Loading branch information
nikkolasg committed Nov 20, 2021
1 parent b30b074 commit 1dd837f
Show file tree
Hide file tree
Showing 32 changed files with 1,544 additions and 841 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/generate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ jobs:
- name: Install Protoc
uses: arduino/setup-protoc@v1.1.2
with:
version: '3.14.0'
version: '3.17.x'
- name: Install Protoc-gen-go
run: |
go get github.com/golang/protobuf/protoc-gen-go@v1.5.2
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.27.1
go get google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0
- name: Generate
run: go generate ./...&& go mod tidy
Expand Down
20 changes: 5 additions & 15 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,14 @@ on:
branches: [ master ]

jobs:
lint:
golangci:
name: lint
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: '^1.17'
- uses: actions/cache@v2
id: lint-dep-cache
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
- name: Fetch Dependencies
run: go get ./...
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v2.3.0
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
version: v1.41
version: v1.43
args: --timeout 5m
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ issues:
- path: protobuf/gen.go
linters:
- lll
source: "^//go:generate "
# https://github.com/go-critic/go-critic/issues/926
- linters:
- gocritic
Expand Down
1 change: 1 addition & 0 deletions chain/beacon/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type Config struct {
Clock clock.Clock
}

//nolint:gocritic
// Handler holds the logic to initiate, and react to the TBLS protocol. Each time
// a full signature can be recosntructed, it saves it to the given Store.
type Handler struct {
Expand Down
11 changes: 6 additions & 5 deletions client/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ var errClientClosed = fmt.Errorf("client closed")
const defaultClientExec = "unknown"
const defaultHTTTPTimeout = 60 * time.Second

const httpWaitMaxCounter = 10
const httpWaitMaxCounter = 20
const httpWaitInterval = 2 * time.Second
const maxTimeoutHTTPRequest = 5 * time.Second

// New creates a new client pointing to an HTTP endpoint
func New(url string, chainHash []byte, transport nhttp.RoundTripper) (client.Client, error) {
Expand Down Expand Up @@ -124,7 +125,7 @@ func Ping(ctx context.Context, root string) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()

req, err := nhttp.NewRequestWithContext(ctx, "GET", url, nil)
req, err := nhttp.NewRequestWithContext(ctx, "GET", url, nhttp.NoBody)
if err != nil {
return fmt.Errorf("creating request: %w", err)
}
Expand Down Expand Up @@ -177,7 +178,7 @@ func IsServerReady(addr string) error {
counter := 0

for {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
ctx, cancel := context.WithTimeout(context.Background(), maxTimeoutHTTPRequest)
defer cancel()

err := Ping(ctx, "http://"+addr)
Expand Down Expand Up @@ -242,7 +243,7 @@ func (h *httpClient) FetchChainInfo(ctx context.Context, chainHash []byte) (*cha
defer cancel()

go func() {
req, err := nhttp.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%sinfo", h.root), nil)
req, err := nhttp.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%sinfo", h.root), nhttp.NoBody)
if err != nil {
resC <- httpInfoResponse{nil, fmt.Errorf("creating request: %w", err)}
return
Expand Down Expand Up @@ -308,7 +309,7 @@ func (h *httpClient) Get(ctx context.Context, round uint64) (client.Result, erro
defer cancel()

go func() {
req, err := nhttp.NewRequestWithContext(ctx, "GET", url, nil)
req, err := nhttp.NewRequestWithContext(ctx, "GET", url, nhttp.NoBody)
if err != nil {
resC <- httpGetResponse{nil, fmt.Errorf("creating request: %w", err)}
return
Expand Down
1 change: 1 addition & 0 deletions client/mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
)

// MockClient provide a mocked client interface
//nolint:gocritic
type MockClient struct {
sync.Mutex
OptionalInfo *chain.Info
Expand Down
8 changes: 8 additions & 0 deletions cmd/drand-cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,14 @@ var appCommands = []*cli.Command{
Flags: toArray(groupFlag, certsDirFlag, insecureFlag, verboseFlag),
Action: checkConnection,
},
{
Name: "remote-status",
Usage: "Ask for the statuses of remote nodes indicated by " +
"`ADDRESS1 ADDRESS2 ADDRESS3...`, including the network " +
"visibility over the rest of the addresses given.",
Flags: toArray(controlFlag, jsonFlag),
Action: remoteStatusCmd,
},
{
Name: "ping",
Usage: "Pings the daemon checking its state\n",
Expand Down
148 changes: 146 additions & 2 deletions cmd/drand-cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (
"context"
"encoding/hex"
"fmt"
"io/ioutil"
gnet "net"
"os"
"os/exec"
"path"
"strconv"
"strings"
Expand Down Expand Up @@ -430,7 +432,7 @@ func testStartedDrandFunctional(t *testing.T, ctrlPort, rootPath, address string
resetCmd := []string{"drand", "util", "reset", "--folder", rootPath, "--id", beaconID}
r, w, err := os.Pipe()
require.NoError(t, err)
_, err = w.Write([]byte("y\n"))
_, err = w.WriteString("y\n")
require.NoError(t, err)
os.Stdin = r
require.NoError(t, CLI().Run(resetCmd))
Expand Down Expand Up @@ -494,9 +496,9 @@ func TestClientTLS(t *testing.T) {
defer os.RemoveAll(tmpPath)

groupPath := path.Join(tmpPath, "group.toml")
pubPath := path.Join(tmpPath, "pub.key")
certPath := path.Join(tmpPath, "server.pem")
keyPath := path.Join(tmpPath, "key.pem")
pubPath := path.Join(tmpPath, "pub.key")

freePort := test.FreePort()
addr := "127.0.0.1:" + freePort
Expand Down Expand Up @@ -628,3 +630,145 @@ func getSBFolderStructure() string {

return tmp
}

func TestDrandStatus(t *testing.T) {
n := 4
instances, tempPath := launchDrandInstances(t, n)
defer os.RemoveAll(tempPath)
allAddresses := make([]string, 0, n)
for _, instance := range instances {
allAddresses = append(allAddresses, instance.addr)
}

defer func() { output = os.Stdout }()

// check that each node can reach each other
for i, instance := range instances {
remote := []string{"drand", "util", "remote-status", "--control", instance.ctrlPort}
remote = append(remote, allAddresses...)
var buff bytes.Buffer
output = &buff

err := CLI().Run(remote)
require.NoError(t, err)
for j, instance := range instances {
if i == j {
continue
}
require.True(t, strings.Contains(buff.String(), instance.addr+" -> OK"))
}
}
// stop one and check that all nodes report this node down
toStop := 2
insToStop := instances[toStop]
insToStop.stop()

for i, instance := range instances {
if i == toStop {
continue
}
remote := []string{"drand", "util", "remote-status", "--control", instance.ctrlPort}
remote = append(remote, allAddresses...)
var buff bytes.Buffer
output = &buff

err := CLI().Run(remote)
require.NoError(t, err)
for j, instance := range instances {
if i == j {
continue
}
if j != toStop {
require.True(t, strings.Contains(buff.String(), instance.addr+" -> OK"))
} else {
require.True(t, strings.Contains(buff.String(), instance.addr+" -> X"))
}
}
}
}

type drandInstance struct {
path string
ctrlPort string
addr string
metrics string
certPath string
keyPath string
certsDir string
}

func (d *drandInstance) stop() error {
return CLI().Run([]string{"drand", "stop", "--control", d.ctrlPort})
}

func (d *drandInstance) run(t *testing.T) {
startArgs := []string{
"drand",
"start",
"--tls-cert", d.certPath,
"--tls-key", d.keyPath,
"--certs-dir", d.certsDir,
"--control", d.ctrlPort,
"--folder", d.path,
"--metrics", d.metrics,
}
go CLI().Run(startArgs)
// make sure we run each one sequentially
testStatus(t, d.ctrlPort)
}

//nolint: gocritic
func launchDrandInstances(t *testing.T, n int) ([]*drandInstance, string) {
beaconID := common.GetBeaconIDFromEnv()

tmpPath := path.Join(os.TempDir(), "drand")
os.Mkdir(tmpPath, 0740)
certsDir, err := ioutil.TempDir(tmpPath, "certs")
require.NoError(t, err)

var ins = make([]*drandInstance, 0, n)
for i := 1; i <= n; i++ {
nodePath, err := ioutil.TempDir(tmpPath, "node")
require.NoError(t, err)

certPath := path.Join(nodePath, "cert")
keyPath := path.Join(nodePath, "tlskey")
pubPath := path.Join(tmpPath, "pub.key")

freePort := test.FreePort()
addr := "127.0.0." + strconv.Itoa(i) + ":" + freePort
ctrlPort := test.FreePort()
metricsPort := test.FreePort()

// generate key so it loads
// XXX let's remove this requirement - no need for longterm keys
priv := key.NewTLSKeyPair(addr)
require.NoError(t, key.Save(pubPath, priv.Public, false))
config := core.NewConfig(core.WithConfigFolder(nodePath))
fileStore := key.NewFileStore(config.ConfigFolderMB(), beaconID)
fileStore.SaveKeyPair(priv)

h, _, _ := gnet.SplitHostPort(addr)
if err := httpscerts.Generate(certPath, keyPath, h); err != nil {
panic(err)
}
// copy into one folder for giving a common CERT folder
_, err = exec.Command("cp", certPath, path.Join(certsDir, fmt.Sprintf("cert-%d", i))).Output()
require.NoError(t, err)

ins = append(ins, &drandInstance{
addr: addr,
ctrlPort: ctrlPort,
path: nodePath,
keyPath: keyPath,
metrics: metricsPort,
certPath: certPath,
certsDir: certsDir,
})
}

for _, instance := range ins {
instance.run(t)
}
return ins, tmpPath
}
52 changes: 51 additions & 1 deletion cmd/drand-cli/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import (
"github.com/drand/drand/core"
"github.com/drand/drand/key"
"github.com/drand/drand/net"
control "github.com/drand/drand/protobuf/drand"
"github.com/drand/drand/protobuf/drand" //nolint:stylecheck

control "github.com/drand/drand/protobuf/drand" //nolint:stylecheck

json "github.com/nikkolasg/hexjson"
"github.com/urfave/cli/v2"
Expand Down Expand Up @@ -310,6 +312,54 @@ func getTimeout(c *cli.Context) (timeout time.Duration, err error) {
return core.DefaultDKGTimeout, nil
}

func remoteStatusCmd(c *cli.Context) error {
client, err := controlClient(c)
if err != nil {
return err
}
ips := c.Args().Slice()
isTLS := !c.IsSet(insecureFlag.Name)
addresses := make([]*drand.Address, len(ips))
for i := 0; i < len(ips); i++ {
addresses[i] = &drand.Address{
Address: ips[i],
Tls: isTLS,
}
}
resp, err := client.RemoteStatus(c.Context, addresses)
if err != nil {
return err
}
// set default value for all keys so json output outputs something for all
// keys
defaultMap := make(map[string]*control.StatusResponse)
for _, addr := range addresses {
if resp, ok := resp[addr.GetAddress()]; !ok {
defaultMap[addr.GetAddress()] = nil
} else {
defaultMap[addr.GetAddress()] = resp
}
}

if c.IsSet(jsonFlag.Name) {
str, err := json.Marshal(defaultMap)
if err != nil {
return fmt.Errorf("cannot marshal the response ... %s", err)
}
fmt.Fprintf(output, "%s \n", string(str))
} else {
for addr, resp := range defaultMap {
fmt.Fprintf(output, "Status of node %s\n", addr)
if resp == nil {
fmt.Fprintf(output, "\t- NO STATUS; can't connect\n")
} else {
fmt.Fprintf(output, "%s\n", core.StatusResponseToString(resp))
}
}
}
return nil
}

func pingpongCmd(c *cli.Context) error {
client, err := controlClient(c)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/relay/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func Relay(c *cli.Context) error {
}

// jumpstart bootup
req, _ := http.NewRequest("GET", "/public/0", nil)
req, _ := http.NewRequest("GET", "/public/0", http.NoBody)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
Expand Down
2 changes: 2 additions & 0 deletions core/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,5 @@ var DefaultResharingOffset = 30 * time.Second

// PrivateRandLength is the length of expected private randomness buffers
const PrivateRandLength = 32

const callMaxTimeout = 10 * time.Second
Loading

0 comments on commit 1dd837f

Please sign in to comment.