diff --git a/net.go b/net.go index 97ec8e4..1e53848 100644 --- a/net.go +++ b/net.go @@ -94,8 +94,8 @@ func (e EurekaConnection) AddMetadataString(ins *Instance, key, value string) er return nil } -// RegisterInstance will register the relevant Instance with eureka but DOES -// NOT automatically send heartbeats. See HeartBeatInstance for that +// RegisterInstance will register the given Instance with eureka if it is not already registered, +// but DOES NOT automatically send heartbeats. See HeartBeatInstance for that // functionality func (e *EurekaConnection) RegisterInstance(ins *Instance) error { slug := fmt.Sprintf("%s/%s", EurekaURLSlugs["Apps"], ins.App) @@ -112,10 +112,18 @@ func (e *EurekaConnection) RegisterInstance(ins *Instance) error { return nil } log.Notice("Instance=%s not yet registered with App=%s. Registering.", ins.HostName, ins.App) + return e.ReregisterInstance(ins) +} +// ReregisterInstance will register the given Instance with eureka but DOES +// NOT automatically send heartbeats. See HeartBeatInstance for that +// functionality +func (e *EurekaConnection) ReregisterInstance(ins *Instance) error { + slug := fmt.Sprintf("%s/%s", EurekaURLSlugs["Apps"], ins.App) + reqURL := e.generateURL(slug) out, err := xml.Marshal(ins) if err != nil { - // marshal the xml *with* indents so it's readable if there's an error + // marshal the xml *with* indents so it's readable in the error message out, _ := xml.MarshalIndent(ins, "", " ") log.Error("Error marshalling XML Instance=%s App=%s. Error:\"%s\" XML body=\"%s\"", err.Error(), ins.HostName, ins.App, string(out)) return err @@ -127,14 +135,55 @@ func (e *EurekaConnection) RegisterInstance(ins *Instance) error { } if rcode != 204 { log.Warning("HTTP returned %d registering Instance=%s App=%s Body=\"%s\"", rcode, ins.HostName, ins.App, string(body)) - return fmt.Errorf("http returned %d possible failure creating instance\n", rcode) + return fmt.Errorf("http returned %d possible failure registering instance\n", rcode) } + // read back our registration to ensure that it stuck body, rcode, err = getXML(reqURL + "/" + ins.HostName) xml.Unmarshal(body, ins) return nil } +// DeregisterInstance will deregister the given Instance from eureka. This is good practice +// to do before exiting or otherwise going off line. +func (e *EurekaConnection) DeregisterInstance(ins *Instance) error { + slug := fmt.Sprintf("%s/%s/%s", EurekaURLSlugs["Apps"], ins.App, ins.HostName) + reqURL := e.generateURL(slug) + log.Debug("Deregistering instance with url %s", reqURL) + + rcode, err := deleteReq(reqURL) + if err != nil { + log.Error("Could not complete deregistration Error: ", err.Error()) + return err + } + if rcode != 204 { + log.Warning("HTTP returned %d deregistering Instance=%s App=%s", rcode, ins.HostName, ins.App) + return fmt.Errorf("http returned %d possible failure deregistering instance\n", rcode) + } + + return nil +} + +// UpdateInstanceStatus updates the status of a given instance with eureka. +func (e EurekaConnection) UpdateInstanceStatus(ins *Instance, status StatusType) error { + slug := fmt.Sprintf("%s/%s/%s/status", EurekaURLSlugs["Apps"], ins.App, ins.HostName) + reqURL := e.generateURL(slug) + + params := map[string]string{"value": string(status)} + + log.Debug("Updating instance status url=%s value=%s", reqURL, status) + body, rcode, err := putKV(reqURL, params) + if err != nil { + log.Error("Could not complete update with Error: ", err.Error()) + return err + } + if rcode < 200 || rcode >= 300 { + log.Warning("HTTP returned %d updating status Instance=%s App=%s Body=\"%s\"", rcode, ins.HostName, ins.App, string(body)) + return fmt.Errorf("http returned %d possible failure updating instance status ", rcode) + } + return nil +} + // HeartBeatInstance sends a single eureka heartbeat. Does not continue sending // heartbeats. Errors if the response is not 200. func (e *EurekaConnection) HeartBeatInstance(ins *Instance) error { diff --git a/rpc.go b/rpc.go index 57b3560..3e2c2d1 100644 --- a/rpc.go +++ b/rpc.go @@ -33,7 +33,7 @@ func putKV(reqURL string, pairs map[string]string) ([]byte, int, error) { params.Add(k, v) } parameterizedURL := reqURL + "?" + params.Encode() - log.Notice("Sending metadata request with URL %s", parameterizedURL) + log.Notice("Sending KV request with URL %s", parameterizedURL) req, err := http.NewRequest("PUT", parameterizedURL, nil) if err != nil { log.Error("Could not create PUT %s with Error: %s", reqURL, err.Error()) @@ -61,6 +61,20 @@ func getXML(reqURL string) ([]byte, int, error) { return body, rcode, nil } +func deleteReq(reqURL string) (int, error) { + req, err := http.NewRequest("DELETE", reqURL, nil) + if err != nil { + log.Error("Could not create DELETE %s with Error: %s", reqURL, err.Error()) + return -1, err + } + _, rcode, err := netReq(req) + if err != nil { + log.Error("Could not complete DELETE %s with Error: %s", reqURL, err.Error()) + return rcode, err + } + return rcode, nil +} + func reqXML(req *http.Request) ([]byte, int, error) { req.Header.Set("Content-Type", "application/xml") req.Header.Set("Accept", "application/xml") diff --git a/struct.go b/struct.go index 246971c..6cd4747 100644 --- a/struct.go +++ b/struct.go @@ -39,9 +39,11 @@ type StatusType string // Supported statuses const ( - UP StatusType = "UP" - DOWN StatusType = "DOWN" - STARTING StatusType = "STARTING" + UP StatusType = "UP" + DOWN StatusType = "DOWN" + STARTING StatusType = "STARTING" + OUTOFSERVICE StatusType = "OUT_OF_SERVICE" + UNKNOWN StatusType = "UNKNOWN" ) // Datacenter names diff --git a/tests/net_test.go b/tests/net_test.go index 20cbd7f..e6f74c2 100644 --- a/tests/net_test.go +++ b/tests/net_test.go @@ -8,6 +8,17 @@ import ( "testing" ) +func TestConnectionCreation(t *testing.T) { + Convey("Pull applications", t, func() { + cfg, err := fargo.ReadConfig("./config_sample/local.gcfg") + So(err, ShouldBeNil) + e := fargo.NewConnFromConfig(cfg) + apps, err := e.GetApps() + So(err, ShouldBeNil) + So(len(apps["EUREKA"].Instances), ShouldEqual, 2) + }) +} + func TestGetApps(t *testing.T) { e, _ := fargo.NewConnFromConfigFile("./config_sample/local.gcfg") Convey("Pull applications", t, func() { @@ -67,21 +78,98 @@ func TestRegistration(t *testing.T) { }) } -func TestConnectionCreation(t *testing.T) { - Convey("Pull applications", t, func() { - cfg, err := fargo.ReadConfig("./config_sample/local.gcfg") - So(err, ShouldBeNil) - e := fargo.NewConnFromConfig(cfg) - apps, err := e.GetApps() - So(err, ShouldBeNil) - So(len(apps["EUREKA"].Instances), ShouldEqual, 2) +func TestReregistration(t *testing.T) { + e, _ := fargo.NewConnFromConfigFile("./config_sample/local.gcfg") + i := fargo.Instance{ + HostName: "i-123456", + Port: 9090, + App: "TESTAPP", + IPAddr: "127.0.0.10", + VipAddress: "127.0.0.10", + DataCenterInfo: fargo.DataCenterInfo{Name: fargo.MyOwn}, + SecureVipAddress: "127.0.0.10", + Status: fargo.UP, + } + Convey("Register a TESTAPP instance", t, func() { + Convey("Instance registers correctly", func() { + err := e.RegisterInstance(&i) + So(err, ShouldBeNil) + }) + }) + Convey("Reregister the TESTAPP instance", t, func() { + Convey("Instance reregisters correctly", func() { + err := e.ReregisterInstance(&i) + So(err, ShouldBeNil) + }) + Convey("Instance can check in", func() { + err := e.HeartBeatInstance(&i) + So(err, ShouldBeNil) + }) + }) +} + +func DontTestDeregistration(t *testing.T) { + e, _ := fargo.NewConnFromConfigFile("./config_sample/local.gcfg") + i := fargo.Instance{ + HostName: "i-123456", + Port: 9090, + App: "TESTAPP", + IPAddr: "127.0.0.10", + VipAddress: "127.0.0.10", + DataCenterInfo: fargo.DataCenterInfo{Name: fargo.MyOwn}, + SecureVipAddress: "127.0.0.10", + Status: fargo.UP, + } + Convey("Register a TESTAPP instance", t, func() { + Convey("Instance registers correctly", func() { + err := e.RegisterInstance(&i) + So(err, ShouldBeNil) + }) + }) + Convey("Deregister the TESTAPP instance", t, func() { + Convey("Instance deregisters correctly", func() { + err := e.DeregisterInstance(&i) + So(err, ShouldBeNil) + }) + Convey("Instance cannot check in", func() { + err := e.HeartBeatInstance(&i) + So(err, ShouldNotBeNil) + }) + }) +} + +func TestUpdateStatus(t *testing.T) { + e, _ := fargo.NewConnFromConfigFile("./config_sample/local.gcfg") + i := fargo.Instance{ + HostName: "i-123456", + Port: 9090, + App: "TESTAPP", + IPAddr: "127.0.0.10", + VipAddress: "127.0.0.10", + DataCenterInfo: fargo.DataCenterInfo{Name: fargo.MyOwn}, + SecureVipAddress: "127.0.0.10", + Status: fargo.UP, + } + Convey("Register an instance to TESTAPP", t, func() { + Convey("Instance registers correctly", func() { + err := e.RegisterInstance(&i) + So(err, ShouldBeNil) + }) + }) + Convey("Update an instance status", t, func() { + Convey("Instance updates to OUT_OF_SERVICE correctly", func() { + err := e.UpdateInstanceStatus(&i, fargo.OUTOFSERVICE) + So(err, ShouldBeNil) + }) + Convey("Instance updates to UP corectly", func() { + err := e.UpdateInstanceStatus(&i, fargo.UP) + So(err, ShouldBeNil) + }) }) } func TestMetadataReading(t *testing.T) { - cfg, err := fargo.ReadConfig("./config_sample/local.gcfg") - So(err, ShouldBeNil) - e := fargo.NewConnFromConfig(cfg) + e, _ := fargo.NewConnFromConfigFile("./config_sample/local.gcfg") Convey("Read empty instance metadata", t, func() { a, err := e.GetApp("EUREKA") So(err, ShouldBeNil)