TUN-3410: Request the v1 Tunnelstore API
This commit is contained in:
parent
e2ff7f65fc
commit
61f3fab757
|
@ -27,6 +27,7 @@ var (
|
||||||
ErrUnauthorized = errors.New("unauthorized")
|
ErrUnauthorized = errors.New("unauthorized")
|
||||||
ErrBadRequest = errors.New("incorrect request parameters")
|
ErrBadRequest = errors.New("incorrect request parameters")
|
||||||
ErrNotFound = errors.New("not found")
|
ErrNotFound = errors.New("not found")
|
||||||
|
ErrAPINoSuccess = errors.New("API call failed")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Tunnel struct {
|
type Tunnel struct {
|
||||||
|
@ -39,7 +40,7 @@ type Tunnel struct {
|
||||||
|
|
||||||
type Connection struct {
|
type Connection struct {
|
||||||
ColoName string `json:"colo_name"`
|
ColoName string `json:"colo_name"`
|
||||||
ID uuid.UUID `json:"uuid"`
|
ID uuid.UUID `json:"id"`
|
||||||
IsPendingReconnect bool `json:"is_pending_reconnect"`
|
IsPendingReconnect bool `json:"is_pending_reconnect"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,11 +92,9 @@ func (dr *DNSRoute) MarshalJSON() ([]byte, error) {
|
||||||
|
|
||||||
func (dr *DNSRoute) UnmarshalResult(body io.Reader) (RouteResult, error) {
|
func (dr *DNSRoute) UnmarshalResult(body io.Reader) (RouteResult, error) {
|
||||||
var result DNSRouteResult
|
var result DNSRouteResult
|
||||||
if err := json.NewDecoder(body).Decode(&result); err != nil {
|
err := parseResponse(body, &result)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result.route = dr
|
result.route = dr
|
||||||
return &result, nil
|
return &result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dr *DNSRoute) RecordType() string {
|
func (dr *DNSRoute) RecordType() string {
|
||||||
|
@ -152,11 +151,9 @@ func (lr *LBRoute) RecordType() string {
|
||||||
|
|
||||||
func (lr *LBRoute) UnmarshalResult(body io.Reader) (RouteResult, error) {
|
func (lr *LBRoute) UnmarshalResult(body io.Reader) (RouteResult, error) {
|
||||||
var result LBRouteResult
|
var result LBRouteResult
|
||||||
if err := json.NewDecoder(body).Decode(&result); err != nil {
|
err := parseResponse(body, &result)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result.route = lr
|
result.route = lr
|
||||||
return &result, nil
|
return &result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (res *LBRouteResult) SuccessSummary() string {
|
func (res *LBRouteResult) SuccessSummary() string {
|
||||||
|
@ -313,16 +310,18 @@ func (r *RESTClient) ListTunnels(filter *Filter) ([]*Tunnel, error) {
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusOK {
|
if resp.StatusCode == http.StatusOK {
|
||||||
var tunnels []*Tunnel
|
return parseListTunnels(resp.Body)
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&tunnels); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to decode response")
|
|
||||||
}
|
|
||||||
return tunnels, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, r.statusCodeToError("list tunnels", resp)
|
return nil, r.statusCodeToError("list tunnels", resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseListTunnels(body io.ReadCloser) ([]*Tunnel, error) {
|
||||||
|
var tunnels []*Tunnel
|
||||||
|
err := parseResponse(body, &tunnels)
|
||||||
|
return tunnels, err
|
||||||
|
}
|
||||||
|
|
||||||
func (r *RESTClient) CleanupConnections(tunnelID uuid.UUID) error {
|
func (r *RESTClient) CleanupConnections(tunnelID uuid.UUID) error {
|
||||||
endpoint := r.baseEndpoints.accountLevel
|
endpoint := r.baseEndpoints.accountLevel
|
||||||
endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/connections", tunnelID))
|
endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/connections", tunnelID))
|
||||||
|
@ -370,15 +369,48 @@ func (r *RESTClient) sendRequest(method string, url url.URL, body interface{}) (
|
||||||
req.Header.Set("Content-Type", jsonContentType)
|
req.Header.Set("Content-Type", jsonContentType)
|
||||||
}
|
}
|
||||||
req.Header.Add("X-Auth-User-Service-Key", r.authToken)
|
req.Header.Add("X-Auth-User-Service-Key", r.authToken)
|
||||||
|
req.Header.Add("Accept", "application/json;version=1")
|
||||||
return r.client.Do(req)
|
return r.client.Do(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseResponse(reader io.Reader, data interface{}) error {
|
||||||
|
// Schema for Tunnelstore responses in the v1 API.
|
||||||
|
// Roughly, it's a wrapper around a particular result that adds failures/errors/etc
|
||||||
|
var result struct {
|
||||||
|
Result json.RawMessage `json:"result"`
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Errors []string `json:"errors"`
|
||||||
|
}
|
||||||
|
// First, parse the wrapper and check the API call succeeded
|
||||||
|
if err := json.NewDecoder(reader).Decode(&result); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to decode response")
|
||||||
|
}
|
||||||
|
if err := checkErrors(result.Errors); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !result.Success {
|
||||||
|
return ErrAPINoSuccess
|
||||||
|
}
|
||||||
|
// At this point we know the API call succeeded, so, parse out the inner
|
||||||
|
// result into the datatype provided as a parameter.
|
||||||
|
return json.Unmarshal(result.Result, &data)
|
||||||
|
}
|
||||||
|
|
||||||
func unmarshalTunnel(reader io.Reader) (*Tunnel, error) {
|
func unmarshalTunnel(reader io.Reader) (*Tunnel, error) {
|
||||||
var tunnel Tunnel
|
var tunnel Tunnel
|
||||||
if err := json.NewDecoder(reader).Decode(&tunnel); err != nil {
|
err := parseResponse(reader, &tunnel)
|
||||||
return nil, errors.Wrap(err, "failed to decode response")
|
return &tunnel, err
|
||||||
}
|
}
|
||||||
return &tunnel, nil
|
|
||||||
|
func checkErrors(errs []string) error {
|
||||||
|
if len(errs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(errs) == 1 {
|
||||||
|
return fmt.Errorf("API error: %s", errs[0])
|
||||||
|
}
|
||||||
|
allErrs := strings.Join(errs, "; ")
|
||||||
|
return fmt.Errorf("API errors: %s", allErrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RESTClient) statusCodeToError(op string, resp *http.Response) error {
|
func (r *RESTClient) statusCodeToError(op string, resp *http.Response) error {
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
package tunnelstore
|
package tunnelstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -12,7 +19,7 @@ func TestDNSRouteUnmarshalResult(t *testing.T) {
|
||||||
userHostname: "example.com",
|
userHostname: "example.com",
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := route.UnmarshalResult(strings.NewReader(`{"cname": "new"}`))
|
result, err := route.UnmarshalResult(strings.NewReader(`{"success": true, "result": {"cname": "new"}}`))
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, &DNSRouteResult{
|
assert.Equal(t, &DNSRouteResult{
|
||||||
|
@ -20,9 +27,20 @@ func TestDNSRouteUnmarshalResult(t *testing.T) {
|
||||||
CName: ChangeNew,
|
CName: ChangeNew,
|
||||||
}, result)
|
}, result)
|
||||||
|
|
||||||
_, err = route.UnmarshalResult(strings.NewReader(`abc`))
|
badJSON := []string{
|
||||||
|
`abc`,
|
||||||
|
`{"success": false, "result": {"cname": "new"}}`,
|
||||||
|
`{"errors": ["foo"], "result": {"cname": "new"}}`,
|
||||||
|
`{"errors": ["foo", "bar"], "result": {"cname": "new"}}`,
|
||||||
|
`{"result": {"cname": "new"}}`,
|
||||||
|
`{"result": {"cname": "new"}}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, j := range badJSON {
|
||||||
|
_, err = route.UnmarshalResult(strings.NewReader(j))
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLBRouteUnmarshalResult(t *testing.T) {
|
func TestLBRouteUnmarshalResult(t *testing.T) {
|
||||||
route := &LBRoute{
|
route := &LBRoute{
|
||||||
|
@ -30,7 +48,7 @@ func TestLBRouteUnmarshalResult(t *testing.T) {
|
||||||
lbPool: "pool",
|
lbPool: "pool",
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := route.UnmarshalResult(strings.NewReader(`{"pool": "unchanged", "load_balancer": "updated"}`))
|
result, err := route.UnmarshalResult(strings.NewReader(`{"success": true, "result": {"pool": "unchanged", "load_balancer": "updated"}}`))
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, &LBRouteResult{
|
assert.Equal(t, &LBRouteResult{
|
||||||
|
@ -39,9 +57,19 @@ func TestLBRouteUnmarshalResult(t *testing.T) {
|
||||||
Pool: ChangeUnchanged,
|
Pool: ChangeUnchanged,
|
||||||
}, result)
|
}, result)
|
||||||
|
|
||||||
_, err = route.UnmarshalResult(strings.NewReader(`abc`))
|
badJSON := []string{
|
||||||
|
`abc`,
|
||||||
|
`{"success": false, "result": {"pool": "unchanged", "load_balancer": "updated"}}`,
|
||||||
|
`{"errors": ["foo"], "result": {"pool": "unchanged", "load_balancer": "updated"}}`,
|
||||||
|
`{"errors": ["foo", "bar"], "result": {"pool": "unchanged", "load_balancer": "updated"}}`,
|
||||||
|
`{"result": {"pool": "unchanged", "load_balancer": "updated"}}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, j := range badJSON {
|
||||||
|
_, err = route.UnmarshalResult(strings.NewReader(j))
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLBRouteResultSuccessSummary(t *testing.T) {
|
func TestLBRouteResultSuccessSummary(t *testing.T) {
|
||||||
route := &LBRoute{
|
route := &LBRoute{
|
||||||
|
@ -76,3 +104,104 @@ func TestLBRouteResultSuccessSummary(t *testing.T) {
|
||||||
assert.Equal(t, tt.expected, actual, "case %d", i+1)
|
assert.Equal(t, tt.expected, actual, "case %d", i+1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_parseListTunnels(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
body string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want []*Tunnel
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty list",
|
||||||
|
args: args{body: `{"success": true, "result": []}`},
|
||||||
|
want: []*Tunnel{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "success is false",
|
||||||
|
args: args{body: `{"success": false, "result": []}`},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "errors are present",
|
||||||
|
args: args{body: `{"errors": ["foo"], "result": []}`},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid response",
|
||||||
|
args: args{body: `abc`},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
body := ioutil.NopCloser(bytes.NewReader([]byte(tt.args.body)))
|
||||||
|
got, err := parseListTunnels(body)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("parseListTunnels() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("parseListTunnels() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_unmarshalTunnel(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
reader io.Reader
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want *Tunnel
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
// TODO: Add test cases.
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := unmarshalTunnel(tt.args.reader)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("unmarshalTunnel() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("unmarshalTunnel() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalTunnelOk(t *testing.T) {
|
||||||
|
|
||||||
|
jsonBody := `{"success": true, "result": {"id": "00000000-0000-0000-0000-000000000000","name":"test","created_at":"0001-01-01T00:00:00Z","connections":[]}}`
|
||||||
|
expected := Tunnel{
|
||||||
|
ID: uuid.Nil,
|
||||||
|
Name: "test",
|
||||||
|
CreatedAt: time.Time{},
|
||||||
|
Connections: []Connection{},
|
||||||
|
}
|
||||||
|
actual, err := unmarshalTunnel(bytes.NewReader([]byte(jsonBody)))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, &expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalTunnelErr(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []string{
|
||||||
|
`abc`,
|
||||||
|
`{"success": true, "result": abc}`,
|
||||||
|
`{"success": false, "result": {"id": "00000000-0000-0000-0000-000000000000","name":"test","created_at":"0001-01-01T00:00:00Z","connections":[]}}}`,
|
||||||
|
`{"errors": ["foo"], "result": {"id": "00000000-0000-0000-0000-000000000000","name":"test","created_at":"0001-01-01T00:00:00Z","connections":[]}}}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
_, err := unmarshalTunnel(bytes.NewReader([]byte(test)))
|
||||||
|
assert.Error(t, err, fmt.Sprintf("Test #%v failed", i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue