From b11a7b8a8747fada1f2e76867207cc594136792b Mon Sep 17 00:00:00 2001 From: Atul Kshirsagar Date: Tue, 27 Jan 2015 17:48:40 -0800 Subject: [PATCH] Allow flexible routing - Provide an easy way to build routing for clients [#86337946] Signed-off-by: Matthew Sykes --- cmd/receptor/actual_lrp_test.go | 2 +- cmd/receptor/desired_lrp_test.go | 13 +++-- cmd/receptor/event_test.go | 32 ++++++++++-- handlers/desired_lrp_handlers_test.go | 16 +++++- resources.go | 72 +++++++++++++++++++++++---- resources_test.go | 42 ++++++++++++++++ serialization/desired_lrps.go | 55 ++++++++++++++++++-- serialization/desired_lrps_test.go | 46 +++++++++++++---- 8 files changed, 243 insertions(+), 35 deletions(-) diff --git a/cmd/receptor/actual_lrp_test.go b/cmd/receptor/actual_lrp_test.go index c8dc5c9..ebc5a3a 100644 --- a/cmd/receptor/actual_lrp_test.go +++ b/cmd/receptor/actual_lrp_test.go @@ -30,7 +30,7 @@ var _ = Describe("Actual LRP API", func() { "instance-guid-"+index, "cell-id", ) - netInfo := models.NewActualLRPNetInfo("the-host", []models.PortMapping{{ContainerPort: 80, HostPort: uint32(1000 + i)}}) + netInfo := models.NewActualLRPNetInfo("the-host", []models.PortMapping{{ContainerPort: 80, HostPort: uint16(1000 + i)}}) err := bbs.StartActualLRP(lrpKey, containerKey, netInfo, logger) Ω(err).ShouldNot(HaveOccurred()) } diff --git a/cmd/receptor/desired_lrp_test.go b/cmd/receptor/desired_lrp_test.go index 50d35b4..187bb98 100644 --- a/cmd/receptor/desired_lrp_test.go +++ b/cmd/receptor/desired_lrp_test.go @@ -1,6 +1,7 @@ package main_test import ( + "encoding/json" "fmt" "sync/atomic" @@ -82,7 +83,13 @@ var _ = Describe("Desired LRP API", func() { instances := 6 annotation := "update-annotation" - routes := []string{"updated-route"} + rawMessage := json.RawMessage([]byte(`[{"port":8080,"hostnames":["updated-route"]}]`)) + routes := map[string]*json.RawMessage{ + "cf-router": &rawMessage, + } + routingInfo := receptor.RoutingInfo{ + CFRoutes: []receptor.CFRoute{{Port: 8080, Hostnames: []string{"updated-route"}}}, + } BeforeEach(func() { createLRPReq := newValidDesiredLRPCreateRequest() @@ -92,7 +99,7 @@ var _ = Describe("Desired LRP API", func() { update := receptor.DesiredLRPUpdateRequest{ Instances: &instances, Annotation: &annotation, - Routes: routes, + Routes: &routingInfo, } updateErr = client.UpdateDesiredLRP(createLRPReq.ProcessGuid, update) @@ -199,7 +206,7 @@ func newValidDesiredLRPCreateRequest() receptor.DesiredLRPCreateRequest { Domain: "test-domain", Stack: "some-stack", Instances: 1, - Ports: []uint32{1234, 5678}, + Ports: []uint16{1234, 5678}, Action: &models.RunAction{ Path: "/bin/bash", }, diff --git a/cmd/receptor/event_test.go b/cmd/receptor/event_test.go index e9695c9..df18b95 100644 --- a/cmd/receptor/event_test.go +++ b/cmd/receptor/event_test.go @@ -1,6 +1,7 @@ package main_test import ( + "encoding/json" "time" "github.com/cloudfoundry-incubator/receptor" @@ -40,11 +41,14 @@ var _ = Describe("Event", func() { } }() + rawMessage := json.RawMessage([]byte(`{"port":8080,"hosts":["primer-route"]}`)) primerLRP := models.DesiredLRP{ ProcessGuid: "primer-guid", Domain: "primer-domain", Stack: "primer-stack", - Routes: []string{"primer-route"}, + Routes: map[string]*json.RawMessage{ + "router": &rawMessage, + }, Action: &models.RunAction{ Path: "true", }, @@ -59,7 +63,12 @@ var _ = Describe("Event", func() { case <-events: break PRIMING case <-time.After(50 * time.Millisecond): - err = bbs.UpdateDesiredLRP(logger, primerLRP.ProcessGuid, models.DesiredLRPUpdate{Routes: []string{"garbage-route"}}) + routeMsg := json.RawMessage([]byte(`{"port":8080,"hosts":["garbage-route"]}`)) + err = bbs.UpdateDesiredLRP(logger, primerLRP.ProcessGuid, models.DesiredLRPUpdate{ + Routes: map[string]*json.RawMessage{ + "router": &routeMsg, + }, + }) Ω(err).ShouldNot(HaveOccurred()) } } @@ -85,11 +94,15 @@ var _ = Describe("Event", func() { Describe("Desired LRPs", func() { BeforeEach(func() { + routeMessage := json.RawMessage([]byte(`[{"port":8080,"hostnames":["original-route"]}]`)) + routes := map[string]*json.RawMessage{ + receptor.CFRouter: &routeMessage, + } desiredLRP = models.DesiredLRP{ ProcessGuid: "some-guid", Domain: "some-domain", Stack: "some-stack", - Routes: []string{"original-route"}, + Routes: routes, Action: &models.RunAction{ Path: "true", }, @@ -109,7 +122,16 @@ var _ = Describe("Event", func() { Ω(desiredLRPCreatedEvent.DesiredLRPResponse).Should(Equal(serialization.DesiredLRPToResponse(desiredLRP))) By("updating an existing DesiredLRP") - newRoutes := []string{"new-route"} + routeMessage := json.RawMessage([]byte(`[{"port":8080,"hostnames":["new-route"]}]`)) + newRoutes := map[string]*json.RawMessage{ + receptor.CFRouter: &routeMessage, + } + expectedRoutingInfo := receptor.RoutingInfo{ + CFRoutes: []receptor.CFRoute{{ + Port: 8080, + Hostnames: []string{"new-route"}, + }}, + } err = bbs.UpdateDesiredLRP(logger, desiredLRP.ProcessGuid, models.DesiredLRPUpdate{Routes: newRoutes}) Ω(err).ShouldNot(HaveOccurred()) @@ -117,7 +139,7 @@ var _ = Describe("Event", func() { desiredLRPChangedEvent, ok := event.(receptor.DesiredLRPChangedEvent) Ω(ok).Should(BeTrue()) - Ω(desiredLRPChangedEvent.After.Routes).Should(Equal(newRoutes)) + Ω(desiredLRPChangedEvent.After.Routes).Should(Equal(&expectedRoutingInfo)) By("removing the DesiredLRP") err = bbs.RemoveDesiredLRPByProcessGuid(logger, desiredLRP.ProcessGuid) diff --git a/handlers/desired_lrp_handlers_test.go b/handlers/desired_lrp_handlers_test.go index 7090bb1..bfaa395 100644 --- a/handlers/desired_lrp_handlers_test.go +++ b/handlers/desired_lrp_handlers_test.go @@ -278,12 +278,24 @@ var _ = Describe("Desired LRP Handlers", func() { expectedProcessGuid := "some-guid" instances := 15 annotation := "new-annotation" - routes := []string{"new-route-1", "new-route-2"} + routingInfo := receptor.RoutingInfo{ + CFRoutes: []receptor.CFRoute{ + { + Port: 8080, + Hostnames: []string{"new-route-1", "new-route-2"}, + }, + }, + } + + routeMessage := json.RawMessage(`[{"port":8080,"hostnames":["new-route-1","new-route-2"]}]`) + routes := map[string]*json.RawMessage{ + receptor.CFRouter: &routeMessage, + } validUpdateRequest := receptor.DesiredLRPUpdateRequest{ Instances: &instances, Annotation: &annotation, - Routes: routes, + Routes: &routingInfo, } expectedUpdate := models.DesiredLRPUpdate{ diff --git a/resources.go b/resources.go index 662fc32..b13577e 100644 --- a/resources.go +++ b/resources.go @@ -12,8 +12,8 @@ type EnvironmentVariable struct { } type PortMapping struct { - ContainerPort uint32 `json:"container_port"` - HostPort uint32 `json:"host_port,omitempty"` + ContainerPort uint16 `json:"container_port"` + HostPort uint16 `json:"host_port,omitempty"` } const ( @@ -154,6 +154,60 @@ func (response *TaskResponse) UnmarshalJSON(payload []byte) error { return nil } +const CFRouter = "cf-router" + +type CFRoute struct { + Port uint16 `json:"port"` + Hostnames []string `json:"hostnames"` +} + +type CFRoutes []CFRoute + +type RoutingInfo struct { + CFRoutes + Other map[string]*json.RawMessage +} + +func (r RoutingInfo) MarshalJSON() ([]byte, error) { + out := make(map[string]*json.RawMessage) + for k, v := range r.Other { + out[k] = v + } + + if len(r.CFRoutes) > 0 { + bytes, err := json.Marshal(r.CFRoutes) + if err != nil { + return nil, err + } + raw := json.RawMessage(bytes) + out[CFRouter] = &raw + } + + return json.Marshal(out) +} + +func (r *RoutingInfo) UnmarshalJSON(data []byte) error { + var out map[string]*json.RawMessage + err := json.Unmarshal(data, &out) + if err != nil { + return err + } + + if cfroutes, ok := out[CFRouter]; ok { + delete(out, CFRouter) + err := json.Unmarshal(*cfroutes, &r.CFRoutes) + if err != nil { + return err + } + } + + if len(out) > 0 { + r.Other = out + } + + return nil +} + type DesiredLRPCreateRequest struct { ProcessGuid string `json:"process_guid"` Domain string `json:"domain"` @@ -169,8 +223,8 @@ type DesiredLRPCreateRequest struct { MemoryMB int `json:"memory_mb"` CPUWeight uint `json:"cpu_weight"` Privileged bool `json:"privileged"` - Ports []uint32 `json:"ports"` - Routes []string `json:"routes"` + Ports []uint16 `json:"ports"` + Routes *RoutingInfo `json:"routes,omitempty"` LogGuid string `json:"log_guid"` LogSource string `json:"log_source"` Annotation string `json:"annotation,omitempty"` @@ -270,9 +324,9 @@ func (request *DesiredLRPCreateRequest) UnmarshalJSON(payload []byte) error { } type DesiredLRPUpdateRequest struct { - Instances *int `json:"instances,omitempty"` - Routes []string `json:"routes,omitempty"` - Annotation *string `json:"annotation,omitempty"` + Instances *int `json:"instances,omitempty"` + Routes *RoutingInfo `json:"routes,omitempty"` + Annotation *string `json:"annotation,omitempty"` } type DesiredLRPResponse struct { @@ -290,8 +344,8 @@ type DesiredLRPResponse struct { MemoryMB int `json:"memory_mb"` CPUWeight uint `json:"cpu_weight"` Privileged bool `json:"privileged"` - Ports []uint32 `json:"ports"` - Routes []string `json:"routes"` + Ports []uint16 `json:"ports"` + Routes *RoutingInfo `json:"routes,omitempty"` LogGuid string `json:"log_guid"` LogSource string `json:"log_source"` Annotation string `json:"annotation,omitempty"` diff --git a/resources_test.go b/resources_test.go index f4e04a6..0296fc5 100644 --- a/resources_test.go +++ b/resources_test.go @@ -324,4 +324,46 @@ var _ = Describe("Resources", func() { }) }) }) + + Describe("RoutingInfo", func() { + var r receptor.RoutingInfo + + BeforeEach(func() { + r.Other = make(map[string]*json.RawMessage) + }) + + Context("Serialization", func() { + jsonRoutes := `{ + "cf-router": [{ "port": 1, "hostnames": ["a", "b"]}], + "foo" : "bar" + }` + + Context("MarshalJson", func() { + It("marshals routes when present", func() { + r.CFRoutes = append(r.CFRoutes, receptor.CFRoute{Port: 1, Hostnames: []string{"a", "b"}}) + msg := json.RawMessage([]byte(`"bar"`)) + r.Other["foo"] = &msg + bytes, err := json.Marshal(r) + Ω(err).ShouldNot(HaveOccurred()) + Ω(bytes).Should(MatchJSON(jsonRoutes)) + }) + }) + + Context("Unmarshal", func() { + It("returns both cf-routes and other", func() { + err := json.Unmarshal([]byte(jsonRoutes), &r) + Ω(err).ShouldNot(HaveOccurred()) + + Ω(r.CFRoutes).Should(HaveLen(1)) + route := r.CFRoutes[0] + Ω(route.Port).Should(Equal(uint16(1))) + Ω(route.Hostnames).Should(ConsistOf("a", "b")) + + Ω(r.Other).Should(HaveLen(1)) + raw := r.Other["foo"] + Ω([]byte(*raw)).Should(Equal([]byte(`"bar"`))) + }) + }) + }) + }) }) diff --git a/serialization/desired_lrps.go b/serialization/desired_lrps.go index c7efd72..b5559a5 100644 --- a/serialization/desired_lrps.go +++ b/serialization/desired_lrps.go @@ -1,11 +1,14 @@ package serialization import ( + "encoding/json" + "github.com/cloudfoundry-incubator/receptor" "github.com/cloudfoundry-incubator/runtime-schema/models" ) func DesiredLRPFromRequest(req receptor.DesiredLRPCreateRequest) models.DesiredLRP { + return models.DesiredLRP{ ProcessGuid: req.ProcessGuid, Domain: req.Domain, @@ -22,7 +25,7 @@ func DesiredLRPFromRequest(req receptor.DesiredLRPCreateRequest) models.DesiredL CPUWeight: req.CPUWeight, Privileged: req.Privileged, Ports: req.Ports, - Routes: req.Routes, + Routes: RoutingInfoToRawMessages(req.Routes), LogGuid: req.LogGuid, LogSource: req.LogSource, Annotation: req.Annotation, @@ -47,7 +50,7 @@ func DesiredLRPToResponse(lrp models.DesiredLRP) receptor.DesiredLRPResponse { CPUWeight: lrp.CPUWeight, Privileged: lrp.Privileged, Ports: lrp.Ports, - Routes: lrp.Routes, + Routes: RoutingInfoFromRawMessages(lrp.Routes), LogGuid: lrp.LogGuid, LogSource: lrp.LogSource, Annotation: lrp.Annotation, @@ -72,7 +75,7 @@ func DesiredLRPFromResponse(resp receptor.DesiredLRPResponse) models.DesiredLRP CPUWeight: resp.CPUWeight, Privileged: resp.Privileged, Ports: resp.Ports, - Routes: resp.Routes, + Routes: RoutingInfoToRawMessages(resp.Routes), LogGuid: resp.LogGuid, LogSource: resp.LogSource, Annotation: resp.Annotation, @@ -82,7 +85,51 @@ func DesiredLRPFromResponse(resp receptor.DesiredLRPResponse) models.DesiredLRP func DesiredLRPUpdateFromRequest(req receptor.DesiredLRPUpdateRequest) models.DesiredLRPUpdate { return models.DesiredLRPUpdate{ Instances: req.Instances, - Routes: req.Routes, + Routes: RoutingInfoToRawMessages(req.Routes), Annotation: req.Annotation, } } + +func RoutingInfoToRawMessages(r *receptor.RoutingInfo) map[string]*json.RawMessage { + if r == nil { + return nil + } + + out := make(map[string]*json.RawMessage) + for k, v := range r.Other { + out[k] = v + } + + if len(r.CFRoutes) > 0 { + bytes, err := json.Marshal(r.CFRoutes) + if err != nil { + panic(err) + } + raw := json.RawMessage(bytes) + out[receptor.CFRouter] = &raw + } + + return out +} + +func RoutingInfoFromRawMessages(raw map[string]*json.RawMessage) *receptor.RoutingInfo { + if len(raw) == 0 { + return nil + } + + var r receptor.RoutingInfo + + if cfroutes, ok := raw[receptor.CFRouter]; ok { + delete(raw, receptor.CFRouter) + err := json.Unmarshal(*cfroutes, &r.CFRoutes) + if err != nil { + panic(err) + } + } + + if len(raw) > 0 { + r.Other = raw + } + + return &r +} diff --git a/serialization/desired_lrps_test.go b/serialization/desired_lrps_test.go index 8e57b57..9df70aa 100644 --- a/serialization/desired_lrps_test.go +++ b/serialization/desired_lrps_test.go @@ -1,6 +1,8 @@ package serialization_test import ( + "encoding/json" + "github.com/cloudfoundry-incubator/receptor" "github.com/cloudfoundry-incubator/receptor/serialization" "github.com/cloudfoundry-incubator/runtime-schema/models" @@ -10,6 +12,23 @@ import ( ) var _ = Describe("DesiredLRP Serialization", func() { + var routes map[string]*json.RawMessage + var routingInfo receptor.RoutingInfo + + BeforeEach(func() { + routingInfo.CFRoutes = []receptor.CFRoute{ + { + Port: 1, + Hostnames: []string{"route-1", "route-2"}, + }, + } + + raw := json.RawMessage([]byte(`[{"port":1,"hostnames":["route-1","route-2"]}]`)) + routes = map[string]*json.RawMessage{ + receptor.CFRouter: &raw, + } + }) + Describe("DesiredLRPFromRequest", func() { var request receptor.DesiredLRPCreateRequest var desiredLRP models.DesiredLRP @@ -31,7 +50,7 @@ var _ = Describe("DesiredLRP Serialization", func() { RootFSPath: "the-rootfs-path", Annotation: "foo", Instances: 1, - Ports: []uint32{2345, 6789}, + Ports: []uint16{2345, 6789}, Action: &models.RunAction{ Path: "the-path", }, @@ -40,6 +59,7 @@ var _ = Describe("DesiredLRP Serialization", func() { EgressRules: []models.SecurityGroupRule{ securityRule, }, + Routes: &routingInfo, } }) JustBeforeEach(func() { @@ -54,13 +74,17 @@ var _ = Describe("DesiredLRP Serialization", func() { Ω(desiredLRP.Annotation).Should(Equal("foo")) Ω(desiredLRP.StartTimeout).Should(Equal(uint(4))) Ω(desiredLRP.Ports).Should(HaveLen(2)) - Ω(desiredLRP.Ports[0]).Should(Equal(uint32(2345))) - Ω(desiredLRP.Ports[1]).Should(Equal(uint32(6789))) + Ω(desiredLRP.Ports[0]).Should(Equal(uint16(2345))) + Ω(desiredLRP.Ports[1]).Should(Equal(uint16(6789))) Ω(desiredLRP.Privileged).Should(BeTrue()) Ω(desiredLRP.EgressRules).Should(HaveLen(1)) Ω(desiredLRP.EgressRules[0].Protocol).Should(Equal(securityRule.Protocol)) Ω(desiredLRP.EgressRules[0].PortRange).Should(Equal(securityRule.PortRange)) Ω(desiredLRP.EgressRules[0].Destinations).Should(Equal(securityRule.Destinations)) + Ω(desiredLRP.Routes).Should(HaveLen(1)) + cfroute, ok := desiredLRP.Routes[receptor.CFRouter] + Ω(ok).Should(BeTrue()) + Ω([]byte(*cfroute)).Should(MatchJSON(`[{"port": 1,"hostnames": ["route-1", "route-2"]}]`)) }) }) @@ -91,10 +115,10 @@ var _ = Describe("DesiredLRP Serialization", func() { MemoryMB: 1234, CPUWeight: 192, Privileged: true, - Ports: []uint32{ + Ports: []uint16{ 456, }, - Routes: []string{"route-0", "route-1"}, + Routes: routes, LogGuid: "log-guid-0", LogSource: "log-source-name-0", Annotation: "annotation-0", @@ -120,10 +144,10 @@ var _ = Describe("DesiredLRP Serialization", func() { MemoryMB: 1234, CPUWeight: 192, Privileged: true, - Ports: []uint32{ + Ports: []uint16{ 456, }, - Routes: []string{"route-0", "route-1"}, + Routes: &routingInfo, LogGuid: "log-guid-0", LogSource: "log-source-name-0", Annotation: "annotation-0", @@ -156,10 +180,10 @@ var _ = Describe("DesiredLRP Serialization", func() { MemoryMB: 1234, CPUWeight: 192, Privileged: true, - Ports: []uint32{ + Ports: []uint16{ 456, }, - Routes: []string{"route-0", "route-1"}, + Routes: &routingInfo, LogGuid: "log-guid-0", LogSource: "log-source-name-0", Annotation: "annotation-0", @@ -182,10 +206,10 @@ var _ = Describe("DesiredLRP Serialization", func() { MemoryMB: 1234, CPUWeight: 192, Privileged: true, - Ports: []uint32{ + Ports: []uint16{ 456, }, - Routes: []string{"route-0", "route-1"}, + Routes: routes, LogGuid: "log-guid-0", LogSource: "log-source-name-0", Annotation: "annotation-0",