Przeglądaj źródła

:construction: Add golang client API with test suite

Ettore Di Giacinto 3 lat temu
rodzic
commit
1826d5aa17

+ 1 - 1
.github/servicestest.sh

@@ -4,7 +4,7 @@
 if [ $1 == "expose" ]; then
     ./edgevpn service-add testservice 127.0.0.1:8080 &
 
-    ((count = 100))                        
+    ((count = 240))                        
     while [[ $count -ne 0 ]] ; do
         sleep 2
         curl http://localhost:8080/api/ledger/tests/services | grep "doneservice"

+ 10 - 0
.github/tests.sh

@@ -0,0 +1,10 @@
+#!/bin/bash
+
+set -ex
+
+./edgevpn api &
+
+GO111MODULE=off go get github.com/onsi/ginkgo/ginkgo
+GO111MODULE=off go get github.com/onsi/gomega/...
+
+TEST_INSTANCE="http://localhost:8080" ginkgo -r api/client

+ 28 - 1
.github/workflows/test.yml

@@ -38,6 +38,33 @@ jobs:
           path: edgevpn
           if-no-files-found: error
   
+  test-suite:
+    runs-on: ubuntu-latest
+    needs: build
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+      - name: Set up Go
+        uses: actions/setup-go@v2
+        with:
+          go-version: 1.16
+      - name: Download result for build
+        uses: actions/download-artifact@v2
+        with:
+          name: connection
+          path: ./
+      - name: Download result for build
+        uses: actions/download-artifact@v2
+        with:
+          name: edgevpn
+          path: ./
+      - name: Test suite
+        run: |
+              chmod +x edgevpn
+              EDGEVPNCONFIG=config.yaml ./.github/tests.sh
+
   vpntest:
     runs-on: ubuntu-latest
     needs: build
@@ -66,7 +93,7 @@ jobs:
       - name: Ping test
         run: |
               chmod +x edgevpn
-              sudo EDGEVPNCONFIG=config.yaml IFACE=edgevpn0 ADDRESS=${{ matrix.ip }} ./edgevpn &
+              sudo EDGEVPNCONFIG=config.yaml IFACE=edgevpn0 ADDRESS=${{ matrix.ip }} ./edgevpn --api &
               bash ./.github/vpntest.sh ${{ matrix.target_ip }}
 
   servicestest:

+ 145 - 3
api/client/client.go

@@ -1,12 +1,14 @@
 package client
 
 import (
+	"encoding/base64"
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"net/http"
 	"time"
 
+	"github.com/mudler/edgevpn/pkg/blockchain"
 	"github.com/mudler/edgevpn/pkg/edgevpn/types"
 )
 
@@ -60,7 +62,8 @@ func NewClient(o ...Option) *Client {
 }
 
 func (c *Client) do(method, endpoint string, params map[string]string) (*http.Response, error) {
-	baseURL := fmt.Sprintf("%s/%s", c.host, endpoint)
+	baseURL := fmt.Sprintf("%s%s", c.host, endpoint)
+
 	req, err := http.NewRequest(method, baseURL, nil)
 	if err != nil {
 		return nil, err
@@ -123,7 +126,7 @@ func (c *Client) Users() (data []types.User, err error) {
 	return
 }
 
-func (c *Client) Ledger() (data map[string]string, err error) {
+func (c *Client) Ledger() (data map[string]map[string]blockchain.Data, err error) {
 	res, err := c.do(http.MethodGet, ledgerURL, nil)
 	if err != nil {
 		return
@@ -139,7 +142,7 @@ func (c *Client) Ledger() (data map[string]string, err error) {
 	return
 }
 
-func (c *Client) Blockchain() (data []map[string]string, err error) {
+func (c *Client) Blockchain() (data blockchain.Block, err error) {
 	res, err := c.do(http.MethodGet, blockchainURL, nil)
 	if err != nil {
 		return
@@ -170,3 +173,142 @@ func (c *Client) Machines() (resp []types.Machine, err error) {
 	}
 	return
 }
+
+func (c *Client) GetBucket(b string) (resp map[string]blockchain.Data, err error) {
+	res, err := c.do(http.MethodGet, fmt.Sprintf("%s/%s", ledgerURL, b), nil)
+	if err != nil {
+		return
+	}
+	defer res.Body.Close()
+	body, err := ioutil.ReadAll(res.Body)
+	if err != nil {
+		return resp, err
+	}
+	if err = json.Unmarshal(body, &resp); err != nil {
+		return resp, err
+	}
+	return
+}
+
+func (c *Client) GetBucketKeys(b string) (resp []string, err error) {
+	d, err := c.GetBucket(b)
+	if err != nil {
+		return resp, err
+	}
+	for k := range d {
+		resp = append(resp, k)
+	}
+	return
+}
+
+func (c *Client) GetBuckets() (resp []string, err error) {
+	d, err := c.Ledger()
+	if err != nil {
+		return resp, err
+	}
+	for k := range d {
+		resp = append(resp, k)
+	}
+	return
+}
+
+func (c *Client) GetBucketKey(b, k string) (resp blockchain.Data, err error) {
+	res, err := c.do(http.MethodGet, fmt.Sprintf("%s/%s/%s", ledgerURL, b, k), nil)
+	if err != nil {
+		return
+	}
+	defer res.Body.Close()
+	body, err := ioutil.ReadAll(res.Body)
+	if err != nil {
+		return resp, err
+	}
+
+	var r string
+	if err = json.Unmarshal(body, &r); err != nil {
+		return resp, err
+	}
+
+	if err = json.Unmarshal([]byte(r), &r); err != nil {
+		return resp, err
+	}
+
+	d, err := base64.URLEncoding.DecodeString(r)
+	if err != nil {
+		return resp, err
+	}
+	resp = blockchain.Data(string(d))
+	return
+}
+
+func (c *Client) Put(b, k string, v interface{}) (err error) {
+	s := struct{ State string }{}
+
+	dat, err := json.Marshal(v)
+	if err != nil {
+		return
+	}
+
+	d := base64.URLEncoding.EncodeToString(dat)
+
+	res, err := c.do(http.MethodPut, fmt.Sprintf("%s/%s/%s/%s", ledgerURL, b, k, d), nil)
+	if err != nil {
+		return
+	}
+	defer res.Body.Close()
+	body, err := ioutil.ReadAll(res.Body)
+	if err != nil {
+		return err
+	}
+
+	if err = json.Unmarshal(body, &s); err != nil {
+		return err
+	}
+
+	if s.State != "Announcing" {
+		return fmt.Errorf("unexpected state '%s'", s.State)
+	}
+
+	return
+}
+
+func (c *Client) Delete(b, k string) (err error) {
+	s := struct{ State string }{}
+	res, err := c.do(http.MethodDelete, fmt.Sprintf("%s/%s/%s", ledgerURL, b, k), nil)
+	if err != nil {
+		return
+	}
+	defer res.Body.Close()
+	body, err := ioutil.ReadAll(res.Body)
+	if err != nil {
+		return err
+	}
+	if err = json.Unmarshal(body, &s); err != nil {
+		return err
+	}
+	if s.State != "Announcing" {
+		return fmt.Errorf("unexpected state '%s'", s.State)
+	}
+
+	return
+}
+
+func (c *Client) DeleteBucket(b string) (err error) {
+	s := struct{ State string }{}
+	res, err := c.do(http.MethodDelete, fmt.Sprintf("%s/%s", ledgerURL, b), nil)
+	if err != nil {
+		return
+	}
+	defer res.Body.Close()
+	body, err := ioutil.ReadAll(res.Body)
+	if err != nil {
+		return err
+	}
+	if err = json.Unmarshal(body, &s); err != nil {
+		return err
+	}
+	if s.State != "Announcing" {
+		return fmt.Errorf("unexpected state '%s'", s.State)
+	}
+
+	return
+}

+ 19 - 0
api/client/client_suite_test.go

@@ -0,0 +1,19 @@
+package client_test
+
+import (
+	"fmt"
+	"os"
+	"testing"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+)
+
+func TestClient(t *testing.T) {
+	if testInstance == "" {
+		fmt.Println("a testing instance has to be defined with TEST_INSTANCE")
+		os.Exit(1)
+	}
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "Client Suite")
+}

+ 88 - 0
api/client/client_test.go

@@ -0,0 +1,88 @@
+package client_test
+
+import (
+	"math/rand"
+	"os"
+	"time"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+
+	. "github.com/mudler/edgevpn/api/client"
+)
+
+var testInstance = os.Getenv("TEST_INSTANCE")
+
+const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+func randStringBytes(n int) string {
+	b := make([]byte, n)
+	for i := range b {
+		b[i] = letterBytes[rand.Intn(len(letterBytes))]
+	}
+	return string(b)
+}
+
+var _ = Describe("Client", func() {
+	c := NewClient(WithHost(testInstance))
+
+	// Start the test suite only if we have some machines connected
+	BeforeSuite(func() {
+		Eventually(func() (int, error) {
+			m, err := c.Machines()
+			return len(m), err
+		}, 100*time.Second, 1*time.Second).Should(BeNumerically(">=", 0))
+	})
+
+	Context("Operates blockchain", func() {
+		var testBucket string
+
+		AfterEach(func() {
+			Eventually(c.GetBuckets, 100*time.Second, 1*time.Second).Should(ContainElement(testBucket))
+			err := c.DeleteBucket(testBucket)
+			Expect(err).ToNot(HaveOccurred())
+			Eventually(c.GetBuckets, 100*time.Second, 1*time.Second).ShouldNot(ContainElement(testBucket))
+		})
+
+		BeforeEach(func() {
+			testBucket = randStringBytes(10)
+		})
+
+		It("Puts string data", func() {
+			err := c.Put(testBucket, "foo", "bar")
+			Expect(err).ToNot(HaveOccurred())
+
+			Eventually(c.GetBuckets, 100*time.Second, 1*time.Second).Should(ContainElement(testBucket))
+			Eventually(func() ([]string, error) { return c.GetBucketKeys(testBucket) }, 100*time.Second, 1*time.Second).Should(ContainElement("foo"))
+
+			Eventually(func() (string, error) {
+				resp, err := c.GetBucketKey(testBucket, "foo")
+				if err == nil {
+					var r string
+					resp.Unmarshal(&r)
+					return r, nil
+				}
+				return "", err
+			}, 100*time.Second, 1*time.Second).Should(Equal("bar"))
+
+			m, err := c.Ledger()
+			Expect(err).ToNot(HaveOccurred())
+			Expect(len(m) > 0).To(BeTrue())
+		})
+
+		It("Puts random data", func() {
+			err := c.Put(testBucket, "foo2", struct{ Foo string }{Foo: "bar"})
+			Expect(err).ToNot(HaveOccurred())
+			Eventually(func() (string, error) {
+				resp, err := c.GetBucketKey(testBucket, "foo2")
+				if err == nil {
+					var r struct{ Foo string }
+					resp.Unmarshal(&r)
+					return r.Foo, nil
+				}
+
+				return "", err
+			}, 100*time.Second, 1*time.Second).Should(Equal("bar"))
+		})
+	})
+})

+ 8 - 9
go.mod

@@ -5,10 +5,9 @@ go 1.16
 require (
 	github.com/gookit/color v1.5.0 // indirect
 	github.com/ipfs/go-ipns v0.1.2 // indirect
-	github.com/ipfs/go-log v1.0.5 // indirect
-	github.com/ipfs/go-log/v2 v2.3.0
+	github.com/ipfs/go-log v1.0.5
 	github.com/kr/text v0.2.0 // indirect
-	github.com/labstack/echo/v4 v4.6.1 // indirect
+	github.com/labstack/echo/v4 v4.6.1
 	github.com/libp2p/go-libp2p v0.15.1
 	github.com/libp2p/go-libp2p-core v0.9.0
 	github.com/libp2p/go-libp2p-discovery v0.5.1
@@ -16,20 +15,20 @@ require (
 	github.com/libp2p/go-libp2p-pubsub v0.5.4
 	github.com/libp2p/go-libp2p-quic-transport v0.12.0 // indirect
 	github.com/lthibault/jitterbug v2.0.0+incompatible
-	github.com/mudler/go-isterminal v0.0.0-20211031135732-5e4e06fc5a58 // indirect
+	github.com/mudler/go-isterminal v0.0.0-20211031135732-5e4e06fc5a58
 	github.com/multiformats/go-multiaddr v0.4.0
-	github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
+	github.com/onsi/ginkgo v1.16.4
+	github.com/onsi/gomega v1.13.0
+	github.com/peterbourgon/diskv v2.0.1+incompatible
 	github.com/pkg/errors v0.9.1
-	github.com/pterm/pterm v0.12.33 // indirect
+	github.com/pterm/pterm v0.12.33
 	github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091
 	github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
-	github.com/urfave/cli v1.22.5 // indirect
+	github.com/urfave/cli v1.22.5
 	github.com/vishvananda/netlink v1.1.0
 	github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
 	go.opencensus.io v0.23.0 // indirect
-	go.uber.org/zap v1.19.0
 	golang.org/x/net v0.0.0-20210913180222-943fd674d43e
-	golang.org/x/sys v0.0.0-20211031064116-611d5d643895 // indirect
 	gopkg.in/yaml.v2 v2.4.0
 )
 

+ 1 - 6
go.sum

@@ -48,6 +48,7 @@ github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETF
 github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
 github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
 github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
+github.com/MarvinJWendt/testza v0.2.10 h1:cX4zE9TofXxe72a6EPIYAxC+8cVWTsmmgsXTZIT+5bQ=
 github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
@@ -219,7 +220,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
 github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
-github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
 github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
@@ -716,7 +716,6 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
-github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
 github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
@@ -1125,7 +1124,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
-golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e h1:VvfwVmMH40bpMeizC9/K7ipM5Qjucuu16RWfneFPyhQ=
 golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
@@ -1215,7 +1213,6 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
 golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c=
 golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210913180222-943fd674d43e h1:+b/22bPvDYt4NPDcy4xAGCmON713ONAWFeY3Z7I3tR8=
 golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -1315,9 +1312,7 @@ golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 h1:uCLL3g5wH2xjxVREVuAbP9JM5PPKjRbXKRa6IBjkzmU=
 golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 h1:xrCZDmdtoloIiooiA9q0OQb9r8HejIHYoHGhGCe1pGg=
 golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211031064116-611d5d643895 h1:iaNpwpnrgL5jzWS0vCNnfa8HqzxveCFpFx3uC/X4Tps=