-
Notifications
You must be signed in to change notification settings - Fork 21
[WIP] Issue36 - Eureka integration #46
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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") | ||
|
|
||
| // 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 | ||
| } | ||
| 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() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is Run from Testing doing?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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."
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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...
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.