-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #524 from nkryuchkov/feature/uptime-tracker-milest…
…one1 Uptime tracking for Milestone 1
- Loading branch information
Showing
5 changed files
with
200 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters