ooni-probe-cli/internal/engine/probeservices/benchselect.go
Simone Basso 7df25795c0
fix(probeservices): use api.ooni.io (#926)
See https://github.com/ooni/probe/issues/2147.

Note that this PR also tries to reduce usage of legacy names inside unit/integration tests.
2022-09-02 16:48:14 +02:00

146 lines
3.8 KiB
Go

package probeservices
import (
"context"
"time"
"github.com/ooni/probe-cli/v3/internal/model"
)
// Default returns the default probe services
func Default() []model.OOAPIService {
return []model.OOAPIService{{
Address: "https://api.ooni.io",
Type: "https",
}, {
Front: "dkyhjv0wpi2dk.cloudfront.net",
Type: "cloudfront",
Address: "https://dkyhjv0wpi2dk.cloudfront.net",
}}
}
// SortEndpoints gives priority to https, then cloudfronted, then onion.
func SortEndpoints(in []model.OOAPIService) (out []model.OOAPIService) {
for _, entry := range in {
if entry.Type == "https" {
out = append(out, entry)
}
}
for _, entry := range in {
if entry.Type == "cloudfront" {
out = append(out, entry)
}
}
for _, entry := range in {
if entry.Type == "onion" {
out = append(out, entry)
}
}
return
}
// OnlyHTTPS returns the HTTPS endpoints only.
func OnlyHTTPS(in []model.OOAPIService) (out []model.OOAPIService) {
for _, entry := range in {
if entry.Type == "https" {
out = append(out, entry)
}
}
return
}
// OnlyFallbacks returns the fallback endpoints only.
func OnlyFallbacks(in []model.OOAPIService) (out []model.OOAPIService) {
for _, entry := range SortEndpoints(in) {
if entry.Type != "https" {
out = append(out, entry)
}
}
return
}
// Candidate is a candidate probe service.
type Candidate struct {
// Duration is the time it took to access the service.
Duration time.Duration
// Err indicates whether the service works.
Err error
// Endpoint is the service endpoint.
Endpoint model.OOAPIService
// TestHelpers contains the data returned by the endpoint.
TestHelpers map[string][]model.OOAPIService
}
func (c *Candidate) try(ctx context.Context, sess Session) {
client, err := NewClient(sess, c.Endpoint)
if err != nil {
c.Err = err
return
}
start := time.Now()
testhelpers, err := client.GetTestHelpers(ctx)
c.Duration = time.Since(start)
c.Err = err
c.TestHelpers = testhelpers
sess.Logger().Debugf("probe services: %+v: %+v %s", c.Endpoint, err, c.Duration)
}
func try(ctx context.Context, sess Session, svc model.OOAPIService) *Candidate {
candidate := &Candidate{Endpoint: svc}
candidate.try(ctx, sess)
return candidate
}
// TryAll tries all the input services using the provided context and session. It
// returns a list containing information on each candidate that was tried. We will
// try all the HTTPS candidates first. So, the beginning of the list will contain
// all of them, and for each of them you will know whether it worked (by checking the
// Err field) and how fast it was (by checking the Duration field). You should pick
// the fastest one that worked. If none of them works, then TryAll will subsequently
// attempt with all the available fallbacks, and return at the first success. In
// such case, you will see a list of N failing HTTPS candidates, followed by a single
// successful fallback candidate (e.g. cloudfronted). If all candidates fail, you
// see in output a list containing all entries where Err is not nil.
func TryAll(ctx context.Context, sess Session, in []model.OOAPIService) (out []*Candidate) {
var found bool
for _, svc := range OnlyHTTPS(in) {
candidate := try(ctx, sess, svc)
out = append(out, candidate)
if candidate.Err == nil {
found = true
}
}
if !found {
for _, svc := range OnlyFallbacks(in) {
candidate := try(ctx, sess, svc)
out = append(out, candidate)
if candidate.Err == nil {
return
}
}
}
return
}
// SelectBest selects the best among the candidates. If there is no
// suitable candidate, then this function returns nil.
func SelectBest(candidates []*Candidate) (selected *Candidate) {
for _, e := range candidates {
if e.Err != nil {
continue
}
if selected == nil {
selected = e
continue
}
if selected.Duration > e.Duration {
selected = e
continue
}
}
return
}