Skip to content

Commit

Permalink
Merge pull request #524 from nkryuchkov/feature/uptime-tracker-milest…
Browse files Browse the repository at this point in the history
…one1

Uptime tracking for Milestone 1
  • Loading branch information
志宇 authored Aug 21, 2019
2 parents a0a985b + 6e250c3 commit d4cd530
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 0 deletions.
2 changes: 2 additions & 0 deletions cmd/skywire-cli/commands/node/gen-config.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ func defaultConfig() *visor.Config {

conf.Hypervisors = []visor.HypervisorConfig{}

conf.Uptime.Tracker = ""

conf.AppsPath = "./apps"
conf.LocalPath = "./local"

Expand Down
21 changes: 21 additions & 0 deletions cmd/skywire-visor/commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package commands

import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
Expand All @@ -22,6 +23,7 @@ import (
"github.com/skycoin/skycoin/src/util/logging"
"github.com/spf13/cobra"

"github.com/skycoin/skywire/internal/utclient"
"github.com/skycoin/skywire/pkg/util/pathutil"
"github.com/skycoin/skywire/pkg/visor"
)
Expand Down Expand Up @@ -148,6 +150,25 @@ func (cfg *runCfg) runNode() *runCfg {
cfg.logger.Fatal("Failed to initialize node: ", err)
}

if cfg.conf.Uptime.Tracker != "" {
uptimeTracker, err := utclient.NewHTTP(cfg.conf.Uptime.Tracker, cfg.conf.Node.StaticPubKey, cfg.conf.Node.StaticSecKey)
if err != nil {
cfg.logger.Error("Failed to connect to uptime tracker: ", err)
} else {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()

go func() {
for range ticker.C {
ctx := context.Background()
if err := uptimeTracker.UpdateNodeUptime(ctx); err != nil {
cfg.logger.Error("Failed to update node uptime: ", err)
}
}
}()
}
}

go func() {
if err := node.Start(); err != nil {
cfg.logger.Fatal("Failed to start node: ", err)
Expand Down
90 changes: 90 additions & 0 deletions internal/utclient/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Package utclient implements uptime tracker client
package utclient

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"

"github.com/skycoin/dmsg/cipher"

"github.com/skycoin/skywire/internal/httpauth"
)

// Error is the object returned to the client when there's an error.
type Error struct {
Error string `json:"error"`
}

// APIClient implements messaging discovery API client.
type APIClient interface {
UpdateNodeUptime(context.Context) error
}

// httpClient implements Client for uptime tracker API.
type httpClient struct {
client *httpauth.Client
pk cipher.PubKey
sk cipher.SecKey
}

// NewHTTP creates a new client setting a public key to the client to be used for auth.
// When keys are set, the client will sign request before submitting.
// The signature information is transmitted in the header using:
// * SW-Public: The specified public key
// * SW-Nonce: The nonce for that public key
// * SW-Sig: The signature of the payload + the nonce
func NewHTTP(addr string, pk cipher.PubKey, sk cipher.SecKey) (APIClient, error) {
client, err := httpauth.NewClient(context.Background(), addr, pk, sk)
if err != nil {
return nil, fmt.Errorf("httpauth: %s", err)
}

return &httpClient{client: client, pk: pk, sk: sk}, nil
}

// Get performs a new GET request.
func (c *httpClient) Get(ctx context.Context, path string) (*http.Response, error) {
req, err := http.NewRequest("GET", c.client.Addr()+path, new(bytes.Buffer))
if err != nil {
return nil, err
}

return c.client.Do(req.WithContext(ctx))
}

// UpdateNodeUptime updates node uptime.
func (c *httpClient) UpdateNodeUptime(ctx context.Context) error {
resp, err := c.Get(ctx, "/update")
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("status: %d, error: %v", resp.StatusCode, extractError(resp.Body))
}

return nil
}

// extractError returns the decoded error message from Body.
func extractError(r io.Reader) error {
var apiError Error

body, err := ioutil.ReadAll(r)
if err != nil {
return err
}

if err := json.Unmarshal(body, &apiError); err != nil {
return errors.New(string(body))
}

return errors.New(apiError.Error)
}
83 changes: 83 additions & 0 deletions internal/utclient/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package utclient

import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"sync"
"testing"

"github.com/skycoin/dmsg/cipher"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/skycoin/skywire/internal/httpauth"
)

var testPubKey, testSecKey = cipher.GenerateKeyPair()

func TestClientAuth(t *testing.T) {
wg := sync.WaitGroup{}

headerCh := make(chan http.Header, 1)
srv := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
switch url := r.URL.String(); url {
case "/":
defer wg.Done()
headerCh <- r.Header

case fmt.Sprintf("/security/nonces/%s", testPubKey):
fmt.Fprintf(w, `{"edge": "%s", "next_nonce": 1}`, testPubKey)

default:
t.Errorf("Don't know how to handle URL = '%s'", url)
}
},
))
defer srv.Close()

client, err := NewHTTP(srv.URL, testPubKey, testSecKey)
require.NoError(t, err)
c := client.(*httpClient)

wg.Add(1)
_, err = c.Get(context.TODO(), "/")
require.NoError(t, err)

header := <-headerCh
assert.Equal(t, testPubKey.Hex(), header.Get("SW-Public"))
assert.Equal(t, "1", header.Get("SW-Nonce"))
assert.NotEmpty(t, header.Get("SW-Sig")) // TODO: check for the right key

wg.Wait()
}

func TestUpdateNodeUptime(t *testing.T) {
urlCh := make(chan string, 1)
srv := httptest.NewServer(authHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
urlCh <- r.URL.String()
})))
defer srv.Close()

c, err := NewHTTP(srv.URL, testPubKey, testSecKey)
require.NoError(t, err)

err = c.UpdateNodeUptime(context.TODO())
require.NoError(t, err)

assert.Equal(t, "/update", <-urlCh)
}

func authHandler(next http.Handler) http.Handler {
m := http.NewServeMux()
m.Handle("/security/nonces/", http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(&httpauth.NextNonceResponse{Edge: testPubKey, NextNonce: 1}) // nolint: errcheck
},
))
m.Handle("/", next)
return m
}
4 changes: 4 additions & 0 deletions pkg/visor/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ type Config struct {
} `json:"table"`
} `json:"routing"`

Uptime struct {
Tracker string `json:"tracker"`
} `json:"uptime"`

Apps []AppConfig `json:"apps"`

TrustedNodes []cipher.PubKey `json:"trusted_nodes"`
Expand Down

0 comments on commit d4cd530

Please sign in to comment.