Skip to content

Commit

Permalink
feat: add ECS service connect support (#4226)
Browse files Browse the repository at this point in the history
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the Apache 2.0 License.
  • Loading branch information
iamhopaul123 authored Nov 28, 2022
1 parent 30e418a commit 3313b1d
Show file tree
Hide file tree
Showing 85 changed files with 1,442 additions and 839 deletions.
2 changes: 1 addition & 1 deletion e2e/apprunner/back-end/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func HealthCheck(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK)
}

// ServiceDiscoveryGet just returns true no matter what
// ServiceDiscoveryGet just returns true no matter what.
func ServiceDiscoveryGet(w http.ResponseWriter, req *http.Request) {
log.Printf("Get on ServiceDiscovery endpoint Succeeded with message %s\n", message)
w.WriteHeader(http.StatusOK)
Expand Down
2 changes: 1 addition & 1 deletion e2e/apprunner/front-end/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const (
postgresDriver = "postgres"
)

// SimpleGet just returns true no matter what
// SimpleGet just returns true no matter what.
func SimpleGet(w http.ResponseWriter, req *http.Request) {
log.Println("Get Succeeded")
w.WriteHeader(http.StatusOK)
Expand Down
8 changes: 4 additions & 4 deletions e2e/customized-env/customized_env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,14 +325,14 @@ environments:
}

Expect(len(svc.ServiceDiscoveries)).To(Equal(3))
var envs, namespaces, wantedNamespaces []string
var envs, endpoints, wantedEndpoints []string
for _, sd := range svc.ServiceDiscoveries {
envs = append(envs, sd.Environment[0])
namespaces = append(namespaces, sd.Namespace)
wantedNamespaces = append(wantedNamespaces, fmt.Sprintf("%s.%s.%s.local:80", svc.SvcName, sd.Environment[0], appName))
endpoints = append(endpoints, sd.Endpoint)
wantedEndpoints = append(wantedEndpoints, fmt.Sprintf("%s.%s.%s.local:80", svc.SvcName, sd.Environment[0], appName))
}
Expect(envs).To(ConsistOf("test", "prod", "shared"))
Expect(namespaces).To(ConsistOf(wantedNamespaces))
Expect(endpoints).To(ConsistOf(wantedEndpoints))

// Call each environment's endpoint and ensure it returns a 200
for _, env := range []string{"test", "prod", "shared"} {
Expand Down
1 change: 1 addition & 0 deletions e2e/exec/exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ var _ = Describe("exec flow", func() {
})
Expect(err).NotTo(HaveOccurred())
Expect(len(svc.Routes)).To(Equal(1))
Expect(len(svc.ServiceConnects)).To(Equal(0))

route := svc.Routes[0]
Expect(route.Environment).To(Equal(envName))
Expand Down
2 changes: 1 addition & 1 deletion e2e/init/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ var _ = Describe("init flow", func() {
It("should return a valid service discovery namespace", func() {
Expect(len(svc.ServiceDiscoveries)).To(Equal(1))
Expect(svc.ServiceDiscoveries[0].Environment).To(Equal([]string{"test"}))
Expect(svc.ServiceDiscoveries[0].Namespace).To(Equal(fmt.Sprintf("%s.%s.%s.local:80", svcName, envName, appName)))
Expect(svc.ServiceDiscoveries[0].Endpoint).To(Equal(fmt.Sprintf("%s.%s.%s.local:80", svcName, envName, appName)))
})

It("should return the correct environment variables", func() {
Expand Down
11 changes: 6 additions & 5 deletions e2e/internal/client/outputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ type SvcShowOutput struct {
Type string `json:"type"`
AppName string `json:"application"`
Configs []SvcShowConfigurations `json:"configurations"`
ServiceDiscoveries []SvcShowServiceDiscoveries `json:"serviceDiscovery"`
ServiceDiscoveries []SvcShowServiceEndpoints `json:"serviceDiscovery"`
ServiceConnects []SvcShowServiceEndpoints `json:"serviceConnect"`
Routes []SvcShowRoutes `json:"routes"`
Variables []SvcShowVariables `json:"variables"`
Resources map[string][]*SvcShowResourceInfo `json:"resources"`
Expand All @@ -86,10 +87,10 @@ type SvcShowRoutes struct {
URL string `json:"url"`
}

// SvcShowServiceDiscoveries contains serialized service discovery info for an service.
type SvcShowServiceDiscoveries struct {
// SvcShowServiceEndpoints contains serialized endpoint info for a service.
type SvcShowServiceEndpoints struct {
Environment []string `json:"environment"`
Namespace string `json:"namespace"`
Endpoint string `json:"endpoint"`
}

// SvcShowVariables contains serialized environment variables for a service.
Expand Down Expand Up @@ -127,7 +128,7 @@ func toSvcListOutput(jsonInput string) (*SvcListOutput, error) {
return &output, json.Unmarshal([]byte(jsonInput), &output)
}

//JobListOutput is the JSON output for job list.
// JobListOutput is the JSON output for job list.
type JobListOutput struct {
Jobs []WkldDescription `json:"jobs"`
}
Expand Down
8 changes: 4 additions & 4 deletions e2e/multi-env-app/multi_env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,14 +218,14 @@ var _ = Describe("Multiple Env App", func() {
}

Expect(len(svc.ServiceDiscoveries)).To(Equal(2))
var envs, namespaces, wantedNamespaces []string
var envs, endpoints, wantedEndpoints []string
for _, sd := range svc.ServiceDiscoveries {
envs = append(envs, sd.Environment[0])
namespaces = append(namespaces, sd.Namespace)
wantedNamespaces = append(wantedNamespaces, fmt.Sprintf("%s.%s.%s.local:80", svc.SvcName, sd.Environment[0], appName))
endpoints = append(endpoints, sd.Endpoint)
wantedEndpoints = append(wantedEndpoints, fmt.Sprintf("%s.%s.%s.local:80", svc.SvcName, sd.Environment[0], appName))
}
Expect(envs).To(ConsistOf("test", "prod"))
Expect(namespaces).To(ConsistOf(wantedNamespaces))
Expect(endpoints).To(ConsistOf(wantedEndpoints))

// Call each environment's endpoint and ensure it returns a 200
for _, env := range []string{"test", "prod"} {
Expand Down
12 changes: 6 additions & 6 deletions e2e/multi-svc-app/back-end/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,24 @@ func HealthCheck(w http.ResponseWriter, req *http.Request, ps httprouter.Params)
w.WriteHeader(http.StatusOK)
}

// SimpleGet just returns true no matter what
// SimpleGet just returns true no matter what.
func SimpleGet(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
log.Println("Get Succeeded")
w.WriteHeader(http.StatusOK)
w.Write([]byte("back-end"))
}

// ServiceDiscoveryGet just returns true no matter what
func ServiceDiscoveryGet(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
log.Println("Get on ServiceDiscovery endpoint Succeeded")
// Get just returns true no matter what.
func Get(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
log.Println("Get on service endpoint Succeeded")
w.WriteHeader(http.StatusOK)
w.Write([]byte("back-end-service-discovery"))
w.Write([]byte("back-end-service"))
}

func main() {
router := httprouter.New()
router.GET("/back-end/", SimpleGet)
router.GET("/service-discovery/", ServiceDiscoveryGet)
router.GET("/service-endpoint/", Get)

// Health Check
router.GET("/", HealthCheck)
Expand Down
3 changes: 3 additions & 0 deletions e2e/multi-svc-app/copilot/front-end/manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ http:
# To match all requests you can use the "/" path.
path: '/'

network:
connect: true

# Number of CPU units for the task.
cpu: 256
# Amount of memory in MiB used by the task.
Expand Down
28 changes: 18 additions & 10 deletions e2e/multi-svc-app/front-end/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,35 @@ var (
volumeName = "efsTestVolume"
)

// SimpleGet just returns true no matter what
// SimpleGet just returns true no matter what.
func SimpleGet(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
log.Println("Get Succeeded")
w.WriteHeader(http.StatusOK)
w.Write([]byte("front-end"))
}

// ServiceDiscoveryGet calls the back-end service, via service-discovery.
// ServiceGet calls the back-end service, via service-connect and service-discovery.
// This call should succeed and return the value from the backend service.
// This test assumes the backend app is called "back-end". The 'service-discovery' endpoint
// of the back-end service is unreachable from the LB, so the only way to get it is
// through service discovery. The response should be `back-end-service-discovery`
func ServiceDiscoveryGet(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
endpoint := fmt.Sprintf("http://back-end.%s/service-discovery/", os.Getenv("COPILOT_SERVICE_DISCOVERY_ENDPOINT"))
resp, err := http.Get(endpoint)
// This test assumes the backend app is called "back-end". The 'service-connect' and
// 'service-discovery' endpoint of the back-end service is unreachable from the LB,
// so the only way to get it is through service connect and service discovery.
// The response should be `back-end-service`
func ServiceGet(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
resp, err := http.Get("http://back-end/service-endpoint/")
if err != nil {
log.Printf("🚨 could call service connect endpoint: err=%s\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Println("Get on service connect endpoint Succeeded")
sdEndpoint := fmt.Sprintf("http://back-end.%s/service-endpoint/", os.Getenv("COPILOT_SERVICE_DISCOVERY_ENDPOINT"))
resp, err = http.Get(sdEndpoint)
if err != nil {
log.Printf("🚨 could call service discovery endpoint: err=%s\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Println("Get on ServiceDiscovery endpoint Succeeded")
log.Println("Get on service discovery endpoint Succeeded")
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
w.WriteHeader(http.StatusOK)
Expand Down Expand Up @@ -123,7 +131,7 @@ func PutEFSCheck(w http.ResponseWriter, req *http.Request, ps httprouter.Params)
func main() {
router := httprouter.New()
router.GET("/", SimpleGet)
router.GET("/service-discovery-test", ServiceDiscoveryGet)
router.GET("/service-endpoint-test", ServiceGet)
router.GET("/magicwords/", GetMagicWords)
router.GET("/job-checker/", GetJobCheck)
router.GET("/job-setter/", SetJobCheck)
Expand Down
2 changes: 1 addition & 1 deletion e2e/multi-svc-app/multi_svc_app_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ var cli *client.CLI
var aws *client.AWS
var appName string

/**
/*
The multi svc suite runs through several tests focusing on creating multiple
services in one app.
*/
Expand Down
29 changes: 15 additions & 14 deletions e2e/multi-svc-app/multi_svc_app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ var _ = Describe("Multiple Service App", func() {
routeURL string
)
BeforeAll(func() {
_, backEndDeployErr = cli.SvcDeploy(&client.SvcDeployInput{
Name: "back-end",
EnvName: "test",
ImageTag: "gallopinggurdey",
})
_, frontEndDeployErr = cli.SvcDeploy(&client.SvcDeployInput{
Name: "front-end",
EnvName: "test",
Expand All @@ -214,11 +219,6 @@ var _ = Describe("Multiple Service App", func() {
EnvName: "test",
ImageTag: "gallopinggurdey",
})
_, backEndDeployErr = cli.SvcDeploy(&client.SvcDeployInput{
Name: "back-end",
EnvName: "test",
ImageTag: "gallopinggurdey",
})
})

It("svc deploy should succeed", func() {
Expand Down Expand Up @@ -301,40 +301,41 @@ var _ = Describe("Multiple Service App", func() {
Expect(svcs["back-end"].Type).To(Equal("Backend Service"))
})

It("service discovery should be enabled and working", func() {
It("service internal endpoint should be enabled and working", func() {
// The front-end service is set up to have a path called
// "/front-end/service-discovery-test" - this route
// "/front-end/service-endpoint-test" - this route
// calls a function which makes a call via the service
// discovery endpoint, "back-end.local". If that back-end
// connect/discovery endpoint, "back-end.local". If that back-end
// call succeeds, the back-end returns a response
// "back-end-service-discovery". This should be forwarded
// "back-end-service". This should be forwarded
// back to us via the front-end api.
// [test] -- http req -> [front-end] -- service-discovery -> [back-end]
// [test] -- http req -> [front-end] -- service-connect -> [back-end]
svcName := "front-end"
svc, svcShowErr := cli.SvcShow(&client.SvcShowRequest{
AppName: appName,
Name: svcName,
})
Expect(svcShowErr).NotTo(HaveOccurred())
Expect(len(svc.Routes)).To(Equal(1))
Expect(len(svc.ServiceConnects)).To(Equal(1))
Expect(svc.ServiceConnects[0].Endpoint).To(Equal(fmt.Sprintf("%s:80", svcName)))

// Calls the front end's service discovery endpoint - which should connect
// Calls the front end's service connect/discovery endpoint - which should connect
// to the backend, and pipe the backend response to us.
route := svc.Routes[0]

Expect(route.Environment).To(Equal("test"))
routeURL = route.URL

resp, fetchErr := http.Get(fmt.Sprintf("%s/service-discovery-test/", route.URL))
resp, fetchErr := http.Get(fmt.Sprintf("%s/service-endpoint-test/", route.URL))
Expect(fetchErr).NotTo(HaveOccurred())
Expect(resp.StatusCode).To(Equal(200))

// Read the response - our deployed apps should return a body with their
// name as the value.
bodyBytes, err := ioutil.ReadAll(resp.Body)
Expect(err).NotTo(HaveOccurred())
Expect(string(bodyBytes)).To(Equal("back-end-service-discovery"))

Expect(string(bodyBytes)).To(Equal("back-end-service"))
})

It("should be able to write to EFS volume", func() {
Expand Down
2 changes: 1 addition & 1 deletion e2e/multi-svc-app/www/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/julienschmidt/httprouter"
)

// SimpleGet just returns true no matter what
// SimpleGet just returns true no matter what.
func SimpleGet(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
log.Println("Get Succeeded")
w.WriteHeader(http.StatusOK)
Expand Down
37 changes: 37 additions & 0 deletions internal/pkg/aws/ecs/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,43 @@ func (s *Service) ServiceStatus() ServiceStatus {
}
}

// ServiceConnectAliases returns the ECS Service Connect client aliases for a service.
func (s *Service) ServiceConnectAliases() []string {
if len(s.Deployments) == 0 {
return nil
}
lastDeployment := s.Deployments[0]
scConfig := lastDeployment.ServiceConnectConfiguration
if scConfig == nil || !aws.BoolValue(scConfig.Enabled) {
return nil
}
var aliases []string
for _, service := range scConfig.Services {
defaultName := aws.StringValue(service.PortName)
if aws.StringValue(service.DiscoveryName) != "" {
defaultName = aws.StringValue(service.DiscoveryName)
}
defaultAlias := fmt.Sprintf("%s.%s", defaultName, aws.StringValue(scConfig.Namespace))
if len(service.ClientAliases) == 0 {
aliases = append(aliases, defaultAlias)
continue
}
for _, clientAlias := range service.ClientAliases {
alias := defaultAlias
if aws.StringValue(clientAlias.DnsName) != "" {
alias = aws.StringValue(clientAlias.DnsName)
}
aliases = append(aliases, fmt.Sprintf("%s:%v", alias, aws.Int64Value(clientAlias.Port)))
}
}
return aliases
}

// LastUpdatedAt returns the last updated time of the ECS service.
func (s *Service) LastUpdatedAt() time.Time {
return aws.TimeValue(s.Deployments[0].UpdatedAt)
}

// TargetGroups returns the ARNs of target groups attached to the service.
func (s *Service) TargetGroups() []string {
var targetGroupARNs []string
Expand Down
Loading

0 comments on commit 3313b1d

Please sign in to comment.