Skip to content

Commit 531344b

Browse files
Add Country and Region fallback for invalid lat/lon (#29)
* Add updated config for iperf3 test * Add static region and country latlon maps * Add findLocation with unit tests
1 parent 6ac52b0 commit 531344b

File tree

5 files changed

+4820
-7
lines changed

5 files changed

+4820
-7
lines changed

handler/handler.go

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"strings"
1313
"time"
1414

15+
log "github.com/sirupsen/logrus"
1516
"gopkg.in/square/go-jose.v2/jwt"
1617

1718
"github.com/m-lab/go/host"
@@ -38,6 +39,11 @@ type Locator interface {
3839
Nearest(ctx context.Context, service, lat, lon string) ([]v2.Target, error)
3940
}
4041

42+
func init() {
43+
log.SetFormatter(&log.JSONFormatter{})
44+
log.SetLevel(log.InfoLevel)
45+
}
46+
4147
// NewClient creates a new client.
4248
func NewClient(project string, private Signer, locator Locator) *Client {
4349
return &Client{
@@ -56,6 +62,55 @@ func splitLatLon(latlon string) (string, string) {
5662
return "", ""
5763
}
5864

65+
var (
66+
latlonMethod = "appengine-latlong"
67+
regionMethod = "appengine-region"
68+
countryMethod = "appengine-country"
69+
noneMethod = "appengine-none"
70+
nullLatLon = "0.000000,0.000000"
71+
)
72+
73+
func findLocation(rw http.ResponseWriter, headers http.Header) (string, string) {
74+
fields := log.Fields{
75+
"CityLatLong": headers.Get("X-AppEngine-CityLatLong"),
76+
"Country": headers.Get("X-AppEngine-Country"),
77+
"Region": headers.Get("X-AppEngine-Region"),
78+
}
79+
80+
// First, try the given lat/lon. Avoid invalid values like 0,0.
81+
latlon := headers.Get("X-AppEngine-CityLatLong")
82+
if lat, lon := splitLatLon(latlon); latlon != nullLatLon && lat != "" && lon != "" {
83+
log.WithFields(fields).Info(latlonMethod)
84+
rw.Header().Set("X-Locate-ClientLatLon", latlon)
85+
rw.Header().Set("X-Locate-ClientLatLon-Method", latlonMethod)
86+
return lat, lon
87+
}
88+
// The next two fallback methods require the country, so check this next.
89+
country := headers.Get("X-AppEngine-Country")
90+
if country == "" || static.Countries[country] == "" {
91+
// Without a valid country value, we can neither lookup the
92+
// region nor country.
93+
log.WithFields(fields).Info(noneMethod)
94+
rw.Header().Set("X-Locate-ClientLatLon-Method", noneMethod)
95+
return "", ""
96+
}
97+
// Second, country is valid, so try to lookup region.
98+
region := strings.ToUpper(headers.Get("X-AppEngine-Region"))
99+
if region != "" && static.Regions[country+"-"+region] != "" {
100+
latlon = static.Regions[country+"-"+region]
101+
log.WithFields(fields).Info(regionMethod)
102+
rw.Header().Set("X-Locate-ClientLatLon", latlon)
103+
rw.Header().Set("X-Locate-ClientLatLon-Method", regionMethod)
104+
return splitLatLon(latlon)
105+
}
106+
// Third, region was not found, fallback to using the country.
107+
latlon = static.Countries[country]
108+
log.WithFields(fields).Info(countryMethod)
109+
rw.Header().Set("X-Locate-ClientLatLon", latlon)
110+
rw.Header().Set("X-Locate-ClientLatLon-Method", countryMethod)
111+
return splitLatLon(latlon)
112+
}
113+
59114
// TranslatedQuery uses the legacy mlab-ns service for liveness as a
60115
// transitional step in loading state directly.
61116
func (c *Client) TranslatedQuery(rw http.ResponseWriter, req *http.Request) {
@@ -75,7 +130,7 @@ func (c *Client) TranslatedQuery(rw http.ResponseWriter, req *http.Request) {
75130
}
76131

77132
// Make proxy request using AppEngine provided lat,lon.
78-
lat, lon := splitLatLon(req.Header.Get("X-AppEngine-CityLatLong"))
133+
lat, lon := findLocation(rw, req.Header)
79134
targets, err := c.Nearest(req.Context(), service, lat, lon)
80135
if err != nil {
81136
result.Error = v2.NewError("nearest", "Failed to lookup nearest machines", http.StatusInternalServerError)

handler/handler_test.go

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,15 @@ import (
1515
v2 "github.com/m-lab/locate/api/v2"
1616
"github.com/m-lab/locate/proxy"
1717
"github.com/m-lab/locate/static"
18+
log "github.com/sirupsen/logrus"
1819
"gopkg.in/square/go-jose.v2/jwt"
1920
)
2021

22+
func init() {
23+
// Disable most logs for unit tests.
24+
log.SetLevel(log.FatalLevel)
25+
}
26+
2127
type fakeSigner struct {
2228
err error
2329
}
@@ -52,6 +58,8 @@ func TestClient_TranslatedQuery(t *testing.T) {
5258
locator *fakeLocator
5359
project string
5460
latlon string
61+
header http.Header
62+
wantLatLon string
5563
wantKey string
5664
wantStatus int
5765
}{
@@ -68,14 +76,62 @@ func TestClient_TranslatedQuery(t *testing.T) {
6876
},
6977
wantStatus: http.StatusInternalServerError,
7078
},
79+
{
80+
name: "error-corrupt-latlon",
81+
path: "ndt/ndt5",
82+
signer: &fakeSigner{},
83+
locator: &fakeLocator{
84+
targets: []v2.Target{{Machine: "mlab1-lga0t.measurement-lab.org"}},
85+
},
86+
header: http.Header{
87+
"X-AppEngine-CityLatLong": []string{"corrupt-value"},
88+
},
89+
wantLatLon: "",
90+
wantKey: "ws://:3001/ndt_protocol",
91+
wantStatus: http.StatusOK,
92+
},
7193
{
7294
name: "success-nearest-server",
7395
path: "ndt/ndt5",
7496
signer: &fakeSigner{},
7597
locator: &fakeLocator{
7698
targets: []v2.Target{{Machine: "mlab1-lga0t.measurement-lab.org"}},
7799
},
78-
latlon: "40.3,-70.4",
100+
header: http.Header{
101+
"X-AppEngine-CityLatLong": []string{"40.3,-70.4"},
102+
},
103+
wantLatLon: "40.3,-70.4", // Client receives lat/lon provided by AppEngine.
104+
wantKey: "ws://:3001/ndt_protocol",
105+
wantStatus: http.StatusOK,
106+
},
107+
{
108+
name: "success-nearest-server-using-region",
109+
path: "ndt/ndt5",
110+
signer: &fakeSigner{},
111+
locator: &fakeLocator{
112+
targets: []v2.Target{{Machine: "mlab1-lga0t.measurement-lab.org"}},
113+
},
114+
header: http.Header{
115+
"X-AppEngine-Country": []string{"US"},
116+
"X-AppEngine-Region": []string{"ny"},
117+
},
118+
wantLatLon: "43.19880000,-75.3242000", // Region center.
119+
wantKey: "ws://:3001/ndt_protocol",
120+
wantStatus: http.StatusOK,
121+
},
122+
{
123+
name: "success-nearest-server-using-country",
124+
path: "ndt/ndt5",
125+
signer: &fakeSigner{},
126+
locator: &fakeLocator{
127+
targets: []v2.Target{{Machine: "mlab1-lga0t.measurement-lab.org"}},
128+
},
129+
header: http.Header{
130+
"X-AppEngine-Region": []string{"fake-region"},
131+
"X-AppEngine-Country": []string{"US"},
132+
"X-AppEngine-CityLatLong": []string{"0.000000,0.000000"},
133+
},
134+
wantLatLon: "37.09024,-95.712891", // Country center.
79135
wantKey: "ws://:3001/ndt_protocol",
80136
wantStatus: http.StatusOK,
81137
},
@@ -85,13 +141,13 @@ func TestClient_TranslatedQuery(t *testing.T) {
85141
c := NewClient(tt.project, tt.signer, tt.locator)
86142

87143
mux := http.NewServeMux()
88-
mux.HandleFunc("/v2/query/", c.TranslatedQuery)
144+
mux.HandleFunc("/v2/nearest/", c.TranslatedQuery)
89145
srv := httptest.NewServer(mux)
90146
defer srv.Close()
91147

92-
req, err := http.NewRequest(http.MethodGet, srv.URL+"/v2/query/"+tt.path, nil)
148+
req, err := http.NewRequest(http.MethodGet, srv.URL+"/v2/nearest/"+tt.path, nil)
93149
rtx.Must(err, "Failed to create request")
94-
req.Header.Set("X-AppEngine-CityLatLong", tt.latlon)
150+
req.Header = tt.header
95151

96152
result := &v2.QueryResult{}
97153
resp, err := proxy.UnmarshalResponse(req, result)
@@ -106,6 +162,10 @@ func TestClient_TranslatedQuery(t *testing.T) {
106162
t.Errorf("TranslatedQuery() wrong Content-Type header; got %s, want 'application/json'",
107163
resp.Header.Get("Content-Type"))
108164
}
165+
if resp.Header.Get("X-Locate-ClientLatLon") != tt.wantLatLon {
166+
t.Errorf("TranslatedQuery() wrong X-Locate-ClientLatLon header; got %s, want '%s'",
167+
resp.Header.Get("X-Locate-ClientLatLon"), tt.wantLatLon)
168+
}
109169
if result.Error != nil && result.Error.Status != tt.wantStatus {
110170
t.Errorf("TranslatedQuery() wrong status; got %d, want %d", result.Error.Status, tt.wantStatus)
111171
}
@@ -115,7 +175,6 @@ func TestClient_TranslatedQuery(t *testing.T) {
115175
if result.Results == nil && tt.wantStatus == http.StatusOK {
116176
t.Errorf("TranslatedQuery() wrong status; got %d, want %d", result.Error.Status, tt.wantStatus)
117177
}
118-
// pretty.Print(result)
119178
if len(tt.locator.targets) != len(result.Results) {
120179
t.Errorf("TranslateQuery() wrong result count; got %d, want %d",
121180
len(result.Results), len(tt.locator.targets))

static/configs.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ var Configs = map[string]Ports{
4646
"wehe/replay": {
4747
URL("wss", ":4443", "/v0/envelope/access"),
4848
},
49-
"iperf/test": {
49+
"iperf3/test": {
5050
URL("wss", "", "/v0/envelope/access"),
5151
},
5252
}
@@ -59,6 +59,7 @@ type Ports []url.URL
5959
var LegacyServices = map[string]string{
6060
"neubot/dash": "neubot",
6161
"wehe/replay": "ndt7", // TODO: replace with heartbeat health.
62+
"iperf3/test": "ndt7", // TODO: replace with heartbeat health.
6263
"ndt/ndt5": "ndt_ssl",
6364
"ndt/ndt7": "ndt7",
6465
}

0 commit comments

Comments
 (0)