Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service Discovery V2 #16

Merged
merged 2 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/servicediscovery/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ Service discovery defines the client & server behavior for discovery of a Kubesc
In order to test your service discovery backend endpoint run:

```bash
go test ./... -url domain.example
go test ./... -url domain.example -version v1
```
2 changes: 2 additions & 0 deletions pkg/servicediscovery/schema/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ type IBackendServices interface {
SetGatewayUrl(string)
SetApiServerUrl(string)
SetMetricsUrl(string)
SetSynchronizerUrl(string)
GetReportReceiverHttpUrl() string
GetReportReceiverWebsocketUrl() string
GetGatewayUrl() string
GetApiServerUrl() string
GetMetricsUrl() string
GetSynchronizerUrl() string
}

type IServiceDiscoveryClient interface {
Expand Down
5 changes: 4 additions & 1 deletion pkg/servicediscovery/servicediscovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import (
"github.com/kubescape/backend/pkg/servicediscovery/schema"
)

var supporterVersions = []string{"v1"}
var supporterVersions = []string{
"v1",
"v2",
}

// WriteServiceDiscoveryResponse writes the service discovery response to the HTTP response writer
// This is used by the service discovery server to respond to HTTP GET requests
Expand Down
75 changes: 75 additions & 0 deletions pkg/servicediscovery/servicediscovery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,24 @@ import (

"github.com/kubescape/backend/pkg/servicediscovery/schema"
v1 "github.com/kubescape/backend/pkg/servicediscovery/v1"
v2 "github.com/kubescape/backend/pkg/servicediscovery/v2"
"github.com/stretchr/testify/assert"
)

// v1
var _ schema.IServiceDiscoveryServer = &v1.ServiceDiscoveryServerV1{}
var _ schema.IServiceDiscoveryClient = &v1.ServiceDiscoveryClientV1{}

// v2
var _ schema.IServiceDiscoveryServer = &v2.ServiceDiscoveryServerV2{}
var _ schema.IServiceDiscoveryClient = &v2.ServiceDiscoveryClientV2{}

var testUrl string
var testVersion string

func init() {
flag.StringVar(&testUrl, "url", "", "Service Discovery Server To Test Against")
flag.StringVar(&testVersion, "version", "", "Service Discovery Version To Test Against")
}

func TestServiceDiscoveryClientV1(t *testing.T) {
Expand All @@ -25,6 +33,10 @@ func TestServiceDiscoveryClientV1(t *testing.T) {
t.Skip("skipping test because no URL was provided")
}

if testVersion != "v1" {
t.Skip()
}

client, err := v1.NewServiceDiscoveryClientV1(testUrl)
if err != nil {
t.Fatalf("failed to create client: %s", err.Error())
Expand Down Expand Up @@ -75,3 +87,66 @@ func TestServiceDiscoveryStreamV1(t *testing.T) {
assert.NotEmpty(t, services.GetReportReceiverHttpUrl())
assert.NotEmpty(t, services.GetReportReceiverWebsocketUrl())
}

func TestServiceDiscoveryClientV2(t *testing.T) {
flag.Parse()
if testUrl == "" {
t.Skip("skipping test because no URL was provided")
}
if testVersion != "v2" {
t.Skip()
}

client, err := v2.NewServiceDiscoveryClientV2(testUrl)
if err != nil {
t.Fatalf("failed to create client: %s", err.Error())
}
sdUrl := client.GetServiceDiscoveryUrl()
t.Logf("testing URL: %s", sdUrl)
services, err := GetServices(client)
if err != nil {
assert.FailNowf(t, fmt.Sprintf("failed to get services from url: %s (HTTP GET)", sdUrl), err.Error())
}

assert.NotNil(t, services)
assert.NotEmpty(t, services.GetApiServerUrl())
assert.NotEmpty(t, services.GetMetricsUrl())
assert.NotEmpty(t, services.GetGatewayUrl())
assert.NotEmpty(t, services.GetReportReceiverHttpUrl())
assert.NotEmpty(t, services.GetReportReceiverWebsocketUrl())
assert.NotEmpty(t, services.GetSynchronizerUrl())
}

func TestServiceDiscoveryFileV2(t *testing.T) {
file := v2.NewServiceDiscoveryFileV2("testdata/v2.json")
services, err := GetServices(file)
if err != nil {
assert.FailNowf(t, "failed to get services from file: %s", err.Error())
}

assert.NotNil(t, services)
assert.NotEmpty(t, services.GetApiServerUrl())
assert.NotEmpty(t, services.GetGatewayUrl())
assert.NotEmpty(t, services.GetMetricsUrl())
assert.NotEmpty(t, services.GetReportReceiverHttpUrl())
assert.NotEmpty(t, services.GetReportReceiverWebsocketUrl())
assert.NotEmpty(t, services.GetSynchronizerUrl())
}

func TestServiceDiscoveryStreamV2(t *testing.T) {
stream := []byte("{\"version\": \"v2\",\"response\": {\"event-receiver-http\": \"https://er-test.com\",\"event-receiver-ws\": \"wss://er-test.com\",\"gateway\": \"https://gw.test.com\",\"api-server\": \"https://api.test.com\",\"metrics\": \"https://metrics.test.com\", \"synchronizer\": \"wss://synchronizer.test.com\"}}")
services, err := GetServices(
v2.NewServiceDiscoveryStreamV2(stream),
)
if err != nil {
assert.FailNowf(t, "failed to get services from stream: %s", err.Error())
}

assert.NotNil(t, services)
assert.NotEmpty(t, services.GetApiServerUrl())
assert.NotEmpty(t, services.GetGatewayUrl())
assert.NotEmpty(t, services.GetMetricsUrl())
assert.NotEmpty(t, services.GetReportReceiverHttpUrl())
assert.NotEmpty(t, services.GetReportReceiverWebsocketUrl())
assert.NotEmpty(t, services.GetSynchronizerUrl())
}
11 changes: 11 additions & 0 deletions pkg/servicediscovery/testdata/v2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": "v1",
"response": {
"event-receiver-http": "https://er-test.com",
"event-receiver-ws": "wss://er-test.com",
"gateway": "https://gw.test.com",
"api-server": "https://api.test.com",
"metrics": "https://metrics.test.com",
"synchronizer": "wss://synchronizer.test.com"
}
}
4 changes: 4 additions & 0 deletions pkg/servicediscovery/v1/datastructures.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package v1

import "github.com/kubescape/backend/pkg/servicediscovery/schema"

type ServiceDiscoveryClientV1 struct {
host string
scheme string
Expand All @@ -13,6 +15,8 @@ type ServiceDiscoveryServerV1 struct {
}

type ServicesV1 struct {
schema.IBackendServices `json:"-"`

EventReceiverHttpUrl string `json:"event-receiver-http"`
EventReceiverWebsocketUrl string `json:"event-receiver-ws"`
GatewayUrl string `json:"gateway"`
Expand Down
6 changes: 6 additions & 0 deletions pkg/servicediscovery/v2/consts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package v2

const (
ServiceDiscoveryPathV2 = "/api/v2/servicediscovery"
ApiVersion = "v2"
)
178 changes: 178 additions & 0 deletions pkg/servicediscovery/v2/datastructuremethods.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package v2

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"

"github.com/kubescape/backend/pkg/servicediscovery/schema"
"github.com/kubescape/backend/pkg/utils"
)

func NewServiceDiscoveryClientV2(url string) (*ServiceDiscoveryClientV2, error) {
scheme, host, err := utils.ParseHost(url)
if err != nil {
return nil, err
}
return &ServiceDiscoveryClientV2{scheme: scheme, host: host, path: ServiceDiscoveryPathV2}, nil
}

func (sds *ServiceDiscoveryClientV2) GetServiceDiscoveryUrl() string {
u := url.URL{
Host: sds.host,
Scheme: sds.scheme,
Path: sds.path,
}
return u.String()
}

func (sds *ServiceDiscoveryClientV2) GetHost() string {
return sds.host
}
func (sds *ServiceDiscoveryClientV2) GetPath() string {
return sds.path
}

func (sds *ServiceDiscoveryClientV2) GetScheme() string {
return sds.scheme
}

func (sds *ServiceDiscoveryClientV2) ParseResponse(response json.RawMessage) (schema.IBackendServices, error) {
var services ServicesV2
if err := json.Unmarshal(response, &services); err == nil {
return &services, nil
}

return nil, fmt.Errorf("invalid response")
}

func (sds *ServiceDiscoveryClientV2) Get() (io.Reader, error) {
response, err := http.Get(sds.GetServiceDiscoveryUrl())
if err != nil {
return nil, err
}

if response.StatusCode < 200 || response.StatusCode >= 300 {
return nil, fmt.Errorf("server (%s) responded: %v", sds.GetHost(), response.StatusCode)
}
return response.Body, nil
}

func NewServiceDiscoveryServerV2(services ServicesV2) *ServiceDiscoveryServerV2 {
return &ServiceDiscoveryServerV2{version: ApiVersion, services: services}
}

func (sds *ServiceDiscoveryServerV2) GetResponse() json.RawMessage {
resp, _ := json.Marshal(sds.services)
return resp
}

func (sds *ServiceDiscoveryServerV2) GetVersion() string {
return sds.version
}

func (sds *ServiceDiscoveryServerV2) GetCachedResponse() ([]byte, bool) {
return sds.cachedResponse, sds.cachedResponse != nil
}

func (sds *ServiceDiscoveryServerV2) CacheResponse(response []byte) {
if sds.cachedResponse == nil {
sds.cachedResponse = response
}
}

func (s *ServicesV2) SetReportReceiverHttpUrl(val string) {
s.EventReceiverHttpUrl = val
}

func (s *ServicesV2) SetReportReceiverWebsocketUrl(val string) {
s.EventReceiverWebsocketUrl = val
}

func (s *ServicesV2) SetApiServerUrl(val string) {
s.ApiServerUrl = val
}

func (s *ServicesV2) SetMetricsUrl(val string) {
s.MetricsUrl = val
}

func (s *ServicesV2) GetReportReceiverHttpUrl() string {
return s.EventReceiverHttpUrl
}

func (s *ServicesV2) GetReportReceiverWebsocketUrl() string {
return s.EventReceiverWebsocketUrl
}

func (s *ServicesV2) GetApiServerUrl() string {
return s.ApiServerUrl
}

func (s *ServicesV2) GetMetricsUrl() string {
return s.MetricsUrl
}

func (s *ServicesV2) SetSynchronizerUrl(val string) {
s.SynchronizerUrl = val
}

func (s *ServicesV2) GetSynchronizerUrl() string {
return s.SynchronizerUrl
}

func (s *ServicesV2) SetGatewayUrl(val string) {
s.GatewayUrl = val
}

func (s *ServicesV2) GetGatewayUrl() string {
return s.GatewayUrl
}

func NewServiceDiscoveryFileV2(path string) *ServiceDiscoveryFileV2 {
return &ServiceDiscoveryFileV2{path: path}
}

func (s *ServiceDiscoveryFileV2) Get() (io.Reader, error) {
jsonFile, err := os.Open(s.path)
if err != nil {
return nil, fmt.Errorf("failed to open file (%s): %v", s.path, err)
}
data, err := io.ReadAll(jsonFile)
if err != nil {
return nil, fmt.Errorf("failed to read file (%s): %v", s.path, err)
}
jsonFile.Close()

return bytes.NewReader(data), nil
}

func (s *ServiceDiscoveryFileV2) ParseResponse(response json.RawMessage) (schema.IBackendServices, error) {
var services ServicesV2
if err := json.Unmarshal(response, &services); err == nil {
return &services, nil
}

return nil, fmt.Errorf("invalid response")
}

func NewServiceDiscoveryStreamV2(data []byte) *ServiceDiscoveryStreamV2 {
return &ServiceDiscoveryStreamV2{data: data}
}

func (s *ServiceDiscoveryStreamV2) Get() (io.Reader, error) {
return bytes.NewReader(s.data), nil
}

func (s *ServiceDiscoveryStreamV2) ParseResponse(response json.RawMessage) (schema.IBackendServices, error) {
var services ServicesV2
if err := json.Unmarshal(response, &services); err == nil {
return &services, nil
}

return nil, fmt.Errorf("invalid response")
}
34 changes: 34 additions & 0 deletions pkg/servicediscovery/v2/datastructures.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package v2

import "github.com/kubescape/backend/pkg/servicediscovery/schema"

type ServiceDiscoveryClientV2 struct {
host string
scheme string
path string
}

type ServiceDiscoveryServerV2 struct {
version string
services ServicesV2
cachedResponse []byte
}

type ServicesV2 struct {
schema.IBackendServices `json:"-"`

EventReceiverHttpUrl string `json:"event-receiver-http"`
EventReceiverWebsocketUrl string `json:"event-receiver-ws"`
GatewayUrl string `json:"gateway"`
ApiServerUrl string `json:"api-server"`
MetricsUrl string `json:"metrics"`
SynchronizerUrl string `json:"synchronizer"`
}

type ServiceDiscoveryFileV2 struct {
path string
}

type ServiceDiscoveryStreamV2 struct {
data []byte
}