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 2 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
61 changes: 61 additions & 0 deletions discovery/eureka.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
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

func (ec EurekaClient) GetIPs(appName string) ([]string, error) {
eurekaAppUrl := ec.eurekaUrl + "/v2/apps/" + appName
//resp, err := http.Get(eurekaAppUrl, "application/json; charset=utf-8")
httpclient := http.Client{Timeout: time.Second * eurekaClientTimeoutInSeconds}
resp, err := httpclient.Get(eurekaAppUrl)
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(string(body))
return nil, nil
Copy link
Collaborator

Choose a reason for hiding this comment

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

I guessed you will return the string array with the IPs here instead of nil...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

WIP, WIP, WIP... ;)

}
126 changes: 126 additions & 0 deletions discovery/eureka_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
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 int = 8080
var eurekaTestUrl string = "http://127.0.0.1:" + strconv.Itoa(eurekaTestPort) + "/eureka"

func TestMain(m *testing.M) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

what do you mean with TestMain?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

// uses a sensible default on windows (tcp/http) and linux/osx (socket)
pool, err := dockertest.NewPool("")
Copy link
Collaborator

Choose a reason for hiding this comment

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

why are you using "dockertest" instead of "dockertest.v3"? has the dot no sense?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

if err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
// pulls an image, creates a container based on it and runs it
resource, err := pool.RunWithOptions(&dockertest.RunOptions{
Copy link
Collaborator

Choose a reason for hiding this comment

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

this resource is the Eureka container running, isn't it? a more descriptive name could be great 😄

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

👍 . I was following the examples to strictly 😸

Repository: "netflixoss/eureka",
Tag: "1.3.1",
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(resource); 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")
Copy link
Collaborator

Choose a reason for hiding this comment

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

mmmm, reaching this point I suspect that the m.Run() in TestMain does is to start the Eureka server to run the other tests after that. am I right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Nops. TestMain does run Eureka (and kills it when tests finish), and in the middle, with m.Run , actually runs all your tests there. Its a mechanism allowing you to wrap all your tests and do things at the beginning and end (init only covers the first part, so that's why golang released this in... 1.5?)

Copy link
Collaborator

Choose a reason for hiding this comment

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

👍 seen in your comment above

}
}

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)
}