Skip to content

Commit

Permalink
Read endpoints from AWS service models
Browse files Browse the repository at this point in the history
Find the endpoint with the lowest latency
  • Loading branch information
mtojek committed Nov 25, 2018
1 parent d91c99e commit 4fcfc14
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 10 deletions.
46 changes: 44 additions & 2 deletions closest/regions.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,51 @@
package closest

import (
"errors"
"time"

log "github.com/sirupsen/logrus"
)

var errEndpointsUnavailable = errors.New("service endpoints are unavailable")

// Regions type selects available endpoints end finds the closest one.
type Regions struct{}

// Latencies type stores average latencies measured while accessing service endpoints.
type Latencies map[string]time.Duration

// FindClosest method finds the closest AWS region to the caller.
func (r *Regions) FindClosest(endpoints Endpoints, verbose bool) (string, error) {
return "us-west-66", nil
func (r *Regions) FindClosest(endpoints Endpoints) (string, error) {
latencies := Latencies{}
for regionName, endpoint := range endpoints {
latency, err := r.measureLatency(endpoint)
if err == nil {
latencies[regionName] = latency
}
}

if len(latencies) == 0 {
return "", errEndpointsUnavailable
}
return r.regionWithLowestLatency(latencies), nil
}

func (r *Regions) measureLatency(endpoint string) (time.Duration, error) {
return 0, nil
}

func (r *Regions) regionWithLowestLatency(latencies Latencies) string {
var theRegion string
var theLatency = time.Hour

for regionName, latency := range latencies {
if latency < theLatency {
theRegion = regionName
theLatency = latency
}
}

log.Infof(`Lowest latency was measured while accessing endpoint in the region "%s": %v`, theRegion, theLatency)
return theRegion
}
64 changes: 61 additions & 3 deletions closest/services.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,70 @@
package closest

import (
"errors"

"github.com/aws/aws-sdk-go/aws/endpoints"
log "github.com/sirupsen/logrus"
)

const defaultServiceName = "dynamodb"

var errServiceNotAvailableInAnyRegion = errors.New("service is not available in any region")

// Services type provides a list of supported AWS regions.
type Services struct{}

// Endpoints is a map with region name as a key and an endpoint as value.
type Endpoints map[string]string

// ForService method provides a list of endpoints for a given service.
func (e *Endpoints) ForService(serviceName string) (Endpoints, error) {
return nil, nil
// EndpointsForService method provides a list of endpoints for a given service.
// US-Gov partition will be skipped.
func (s *Services) EndpointsForService(serviceName string) (Endpoints, error) {
serviceName = s.serviceNameOrDefault(serviceName)

var anyRegionExists bool
serviceEndpoints := Endpoints{}
for _, partition := range endpoints.DefaultPartitions() {
if partition.ID() == endpoints.AwsUsGovPartition().ID() {
log.Info(`Partition "us-gov" will be skipped.`)
continue
}

serviceRegions, exists := endpoints.RegionsForService(endpoints.DefaultPartitions(),
partition.ID(), serviceName)
log.Infof(`Service "%s" is available in %d regions in "%s" partition.`, serviceName,
len(serviceRegions), partition.ID())
anyRegionExists = anyRegionExists || exists

if exists {
for regionName := range serviceRegions {
serviceEndpoint, err := partition.EndpointFor(serviceName, regionName)
if err != nil {
return nil, err
}
serviceEndpoints[regionName] = serviceEndpoint.URL
}
}
}

if !anyRegionExists {
return nil, errServiceNotAvailableInAnyRegion
}

if log.IsLevelEnabled(log.InfoLevel) {
log.Infoln("Service is accessing via following endpoints:")
for regionName, endpoint := range serviceEndpoints {
log.Infof(" %s: %s\n", regionName, endpoint)
}
}

return serviceEndpoints, nil
}

func (s *Services) serviceNameOrDefault(serviceName string) string {
if serviceName == "" {
log.Infof("Service name hasn't been provided. Use default service name: %s", defaultServiceName)
return defaultServiceName
}
return serviceName
}
67 changes: 67 additions & 0 deletions closest/services_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package closest

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestServices_EndpointsForService_EmptyServiceName(t *testing.T) {
// given
serviceName := ""
services := new(Services)

// when
endpoints, err := services.EndpointsForService(serviceName)

// then
assert.Nil(t, err, "no errors should be returned")
assert.True(t, len(endpoints) > 0, "at least one endpoint should be returned")
assert.Contains(t, endpoints, "us-west-2")
assert.NotContains(t, endpoints, "us-west-999")
}

func TestServices_EndpointsForService_ServiceNamePassed(t *testing.T) {
// given
serviceName := "polly"
services := new(Services)

// when
endpoints, err := services.EndpointsForService(serviceName)

// then
assert.Nil(t, err, "no errors should be returned")
assert.True(t, len(endpoints) > 0, "at least one endpoint should be returned")
assert.Contains(t, endpoints, "us-west-2")
assert.NotContains(t, endpoints, "us-west-999")
}

func TestServices_EndpointsForService_UnknownServiceName(t *testing.T) {
// given
serviceName := "unknown"
services := new(Services)

// when
endpoints, err := services.EndpointsForService(serviceName)

// then
assert.NotNil(t, err, "an error should be returned")
assert.Nil(t, endpoints, "no endpoints should be returned")
}

func TestServices_EndpointsForService_WithChinaPartition(t *testing.T) {
// given
serviceName := "dynamodb"
services := new(Services)

// when
endpoints, err := services.EndpointsForService(serviceName)

// then
assert.Nil(t, err, "no errors should be returned")
assert.True(t, len(endpoints) > 0, "at least one endpoint should be returned")
assert.Contains(t, endpoints, "us-west-2")
assert.Contains(t, endpoints, "cn-north-1")
assert.Contains(t, endpoints, "cn-northwest-1")
assert.NotContains(t, endpoints, "us-west-999")
}
20 changes: 15 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package main
import (
"flag"
"fmt"
"log"

"github.com/mtojek/aws-closest-region/closest"
log "github.com/sirupsen/logrus"
)

func main() {
Expand All @@ -14,17 +14,27 @@ func main() {
flag.Parse()
serviceName := flag.Arg(0)

endpoints := new(closest.Endpoints)
serviceEndpoints, err := endpoints.ForService(serviceName)
log.SetFormatter(&log.TextFormatter{
DisableLevelTruncation: true,
DisableTimestamp: true,
})
if verbose {
log.SetLevel(log.InfoLevel)
} else {
log.SetLevel(log.ErrorLevel)
}

services := new(closest.Services)
serviceEndpoints, err := services.EndpointsForService(serviceName)
if err != nil {
log.Fatal(err)
}

regions := new(closest.Regions)
closest, err := regions.FindClosest(serviceEndpoints, verbose)
closestEndpoint, err := regions.FindClosest(serviceEndpoints)
if err != nil {
log.Fatal(err)
}

fmt.Println(closest)
fmt.Println(closestEndpoint)
}

0 comments on commit 4fcfc14

Please sign in to comment.