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

[WIP] Issue36 - Eureka integration #46

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
68 changes: 68 additions & 0 deletions discovery/eureka.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package discovery

import (
"errors"
"net/http"
"io/ioutil"
"fmt"
"time"
"net"
)

//TODO: This should become a discovery interface. And Eureka just the first implementation
type EurekaClient struct {
eurekaURL string
}

//TODO: Creating our own error type and wrapping standard net/http errors could be useful to prevent
// the original errors from being lost
var errEurekaTimesOut = errors.New("Eureka server timed out")
var errNoEurekaConnection = errors.New("Unable to reach Eureka server")
var errEurekaUnexpectedHTTPResponseCode = errors.New("Eureka returned a non 200 http response code")
const eurekaClientTimeoutInSeconds = 10

func NewEurekaClient(eurekaURL string) (ec EurekaClient, err error) {
ec.eurekaURL = eurekaURL
httpclient := http.Client{Timeout: time.Second * eurekaClientTimeoutInSeconds}
resp, err := httpclient.Get(eurekaURL)
if serr, ok := err.(net.Error); ok && serr.Timeout() {
return ec,errEurekaTimesOut
} else if err != nil {
return ec, errNoEurekaConnection
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return ec, errEurekaUnexpectedHTTPResponseCode
}
return ec, nil
}

var errNoIpsFound = errors.New("No IPs associated to the requested App name")
dcaba marked this conversation as resolved.
Show resolved Hide resolved

// TODO: Probably we should break this function
func (ec EurekaClient) GetIPs(appName string) ([]string, error) {
eurekaAppURL := ec.eurekaURL + "/v2/apps/" + appName
// can we reuse the client but starting from 0 in terms of timeout? to review
httpclient := http.Client{Timeout: time.Second * eurekaClientTimeoutInSeconds}
req, err := http.NewRequest("GET", eurekaAppURL, nil)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
resp, err := httpclient.Do(req)
if serr, ok := err.(net.Error); ok && serr.Timeout() {
return []string{},errEurekaTimesOut
} else if err != nil {
return []string{}, errNoEurekaConnection
}
defer resp.Body.Close()
if resp.StatusCode == 404 {
return []string{},errNoIpsFound
} else if resp.StatusCode != 200 {
return []string{}, errEurekaUnexpectedHTTPResponseCode
}
body, err := ioutil.ReadAll(resp.Body)
fmt.Println("Response from eureka:", string(body))
// parsing to get the right data will be done like this: https://stackoverflow.com/a/35665161
return nil, nil
}
128 changes: 128 additions & 0 deletions discovery/eureka_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package discovery

import (
"gopkg.in/ory-am/dockertest.v3"
dc "github.com/fsouza/go-dockerclient"
dcaba marked this conversation as resolved.
Show resolved Hide resolved
"github.com/jaume-pinyol/fargo"
"log"
"os"
"testing"
"strconv"
"github.com/op/go-logging"
)

var eurekaTestPort = 8080
var eurekaTestURL string = "http://127.0.0.1:" + strconv.Itoa(eurekaTestPort) + "/eureka"

func TestMain(m *testing.M) {
// uses a sensible default on windows (tcp/http) and linux/osx (socket)
pool, err := dockertest.NewPool("")
if err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
// pulls an image, creates a container based on it and runs it
eurekaContainer, err := pool.RunWithOptions(&dockertest.RunOptions{
Repository: "netflixoss/eureka",
Tag: "1.3.1",
//Repository: "containers.schibsted.io/spt-infrastructure/eureka-docker",
//Tag: "latest",
PortBindings: map[dc.Port][]dc.PortBinding{
dc.Port(strconv.Itoa(eurekaTestPort) + "/tcp"): {{HostIP: "", HostPort: strconv.Itoa(eurekaTestPort)}},
},
})
if err != nil {
log.Fatalf("Could not start resource: %s", err)
}

// exponential backoff-retry, because the application in the container might not be ready to accept connections yet
if err := pool.Retry(func() error {
_, err := NewEurekaClient(eurekaTestURL)
return err
}); err != nil {
log.Fatalf("Could not connect to the docker resource: %s", err)
}

code := m.Run()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is Run from Testing doing?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the previous reference about what TestMain actually does


// You can't defer this because os.Exit doesn't care for defer
if err := pool.Purge(eurekaContainer); err != nil {
log.Fatalf("Could not purge resource: %s", err)
}

os.Exit(code)
}

func TestEurekaClientNoEureka(t *testing.T) {
_, err := NewEurekaClient("http://localhost:9999/thisshouldntwork")
if err != errNoEurekaConnection {
t.Fatal("We shouldnt reach eureka if Eureka hostname/port is completely wrong. Actual err:", err)
}
}

func TestEurekaClientEurekaDoesNotReply(t *testing.T) {
_, err := NewEurekaClient("http://192.0.2.1:9999/thisshouldtimeout")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Addresses starting with "192.0.2.", "198.51.100.", or "203.0.113." are reserved for use in documentation and sample configurations. They should never be used in a live network configuration. No one has permission to use these addresses on the Internet."
I guess doing testing is no documentation...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you suggest another IP range which traffic will be dropped for sure? In fact, these networks are referenced as "TEST NETWORKs" in the RFC, so that was the best option I found...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you were right, don't change it.

if err != errEurekaTimesOut {
t.Fatal("Pointing to a destination that drops packages should fail because of timeout. Actual err:", err)
}
}

func TestEurekaClientWrongEurekaContext(t *testing.T) {
_, err := NewEurekaClient(eurekaTestURL + "badsuffix")
if err != errEurekaUnexpectedHTTPResponseCode {
t.Fatal("Eureka should be reachable but, when asking a wrong URL, it should return a non 200 response code")
}
}

func TestEurekaClientUnknownApp(t *testing.T) {
appName := "unknown"
eurekaClient, err := NewEurekaClient(eurekaTestURL)
if err != nil {
t.Fatal("We cannot connect to the specified eureka server:", err)
}
t.Log("Connection to Eureka established")
_, err = eurekaClient.GetIPs(appName)
if err != errNoIpsFound {
t.Fatal("Eureka did return something different from an No-IPs-error associated to the unknown App")
}
}

func TestEurekaClientValidApp(t *testing.T) {
appName := "testApp"
ipAddr := "192.0.2.1"
port := 10080
registerDummyAppInTestEureka(appName, ipAddr, port)
eurekaClient, err := NewEurekaClient(eurekaTestURL)
if err != nil {
t.Fatal("We cannot connect to the specified eureka server:", err)
}
t.Log("Connection to Eureka established")
ipsFromEureka, err := eurekaClient.GetIPs(appName)
if err != nil {
t.Fatal("Eureka returned an error when requesting the IPs:", err)
}
if len(ipsFromEureka) != 1 || ipsFromEureka[0] != ipAddr {
t.Fatal("Eureka returned a set of IPs we did not expect for our service:", ipsFromEureka)
}

}
func registerDummyAppInTestEureka(appName string, ipAddr string, port int) {
logging.SetLevel(logging.ERROR, "fargo")
fargoclient := fargo.NewConn(eurekaTestURL + "/v2")
appInstance := &fargo.Instance{
HostName: "dummyhost",
Port: port,
SecurePort: port,
App: appName,
IPAddr: ipAddr,
VipAddress: ipAddr,
SecureVipAddress: ipAddr,
DataCenterInfo: fargo.DataCenterInfo{Name: fargo.MyOwn},
Status: fargo.UP,
Overriddenstatus: fargo.UNKNOWN,
HealthCheckUrl: "http://" + ipAddr + ":" + "8080" + "/healthcheck",
StatusPageUrl: "http://" + ipAddr + ":" + "8080" + "/healthcheck",
HomePageUrl: "http://" + ipAddr + ":" + "8080" + "/",
AsgName: "dummyAsg",
}
fargoclient.RegisterInstance(appInstance)
}