2021-02-02 12:05:47 +01:00
|
|
|
package engine
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2021-03-29 19:37:32 +02:00
|
|
|
"io/fs"
|
2021-12-03 15:30:56 +01:00
|
|
|
"net/url"
|
2021-02-02 12:05:47 +01:00
|
|
|
|
2021-04-07 14:14:25 +02:00
|
|
|
"github.com/apex/log"
|
refactor: flatten and separate (#353)
* refactor(atomicx): move outside the engine package
After merging probe-engine into probe-cli, my impression is that we have
too much unnecessary nesting of packages in this repository.
The idea of this commit and of a bunch of following commits will instead
be to reduce the nesting and simplify the structure.
While there, improve the documentation.
* fix: always use the atomicx package
For consistency, never use sync/atomic and always use ./internal/atomicx
so we can just grep and make sure we're not risking to crash if we make
a subtle mistake on a 32 bit platform.
While there, mention in the contributing guidelines that we want to
always prefer the ./internal/atomicx package over sync/atomic.
* fix(atomicx): remove unnecessary constructor
We don't need a constructor here. The default constructed `&Int64{}`
instance is already usable and the constructor does not add anything to
what we are doing, rather it just creates extra confusion.
* cleanup(atomicx): we are not using Float64
Because atomicx.Float64 is unused, we can safely zap it.
* cleanup(atomicx): simplify impl and improve tests
We can simplify the implementation by using defer and by letting
the Load() method call Add(0).
We can improve tests by making many goroutines updated the
atomic int64 value concurrently.
* refactor(fsx): can live in the ./internal pkg
Let us reduce the amount of nesting. While there, ensure that the
package only exports the bare minimum, and improve the documentation
of the tests, to ease reading the code.
* refactor: move runtimex to ./internal
* refactor: move shellx into the ./internal package
While there, remove unnecessary dependency between packages.
While there, specify in the contributing guidelines that
one should use x/sys/execabs instead of os/exec.
* refactor: move ooapi into the ./internal pkg
* refactor(humanize): move to ./internal and better docs
* refactor: move platform to ./internal
* refactor(randx): move to ./internal
* refactor(multierror): move into the ./internal pkg
* refactor(kvstore): all kvstores in ./internal
Rather than having part of the kvstore inside ./internal/engine/kvstore
and part in ./internal/engine/kvstore.go, let us put every piece of code
that is kvstore related into the ./internal/kvstore package.
* fix(kvstore): always return ErrNoSuchKey on Get() error
It should help to use the kvstore everywhere removing all the
copies that are lingering around the tree.
* sessionresolver: make KVStore mandatory
Simplifies implementation. While there, use the ./internal/kvstore
package rather than having our private implementation.
* fix(ooapi): use the ./internal/kvstore package
* fix(platform): better documentation
2021-06-04 10:34:18 +02:00
|
|
|
"github.com/ooni/probe-cli/v3/internal/fsx"
|
2022-01-03 13:53:23 +01:00
|
|
|
"github.com/ooni/probe-cli/v3/internal/model"
|
2022-08-17 10:57:03 +02:00
|
|
|
"github.com/ooni/probe-cli/v3/internal/registry"
|
2021-12-03 15:30:56 +01:00
|
|
|
"github.com/ooni/probe-cli/v3/internal/stuninput"
|
2021-02-02 12:05:47 +01:00
|
|
|
)
|
|
|
|
|
2021-03-26 09:34:27 +01:00
|
|
|
// These errors are returned by the InputLoader.
|
2021-02-02 12:05:47 +01:00
|
|
|
var (
|
2021-03-29 18:46:26 +02:00
|
|
|
ErrNoURLsReturned = errors.New("no URLs returned")
|
2021-02-02 12:05:47 +01:00
|
|
|
ErrDetectedEmptyFile = errors.New("file did not contain any input")
|
2021-03-26 09:34:27 +01:00
|
|
|
ErrInputRequired = errors.New("no input provided")
|
|
|
|
ErrNoInputExpected = errors.New("we did not expect any input")
|
2021-12-03 15:30:56 +01:00
|
|
|
ErrNoStaticInput = errors.New("no static input for this experiment")
|
2021-02-02 12:05:47 +01:00
|
|
|
)
|
|
|
|
|
2021-03-26 09:34:27 +01:00
|
|
|
// InputLoaderSession is the session according to an InputLoader. We
|
|
|
|
// introduce this abstraction because it helps us with testing.
|
2021-02-02 12:05:47 +01:00
|
|
|
type InputLoaderSession interface {
|
2021-03-29 18:46:26 +02:00
|
|
|
CheckIn(ctx context.Context,
|
2022-01-03 13:53:23 +01:00
|
|
|
config *model.OOAPICheckInConfig) (*model.OOAPICheckInInfo, error)
|
2021-02-02 12:05:47 +01:00
|
|
|
}
|
|
|
|
|
2021-04-07 14:14:25 +02:00
|
|
|
// InputLoaderLogger is the logger according to an InputLoader.
|
|
|
|
type InputLoaderLogger interface {
|
|
|
|
// Warnf formats and emits a warning message.
|
|
|
|
Warnf(format string, v ...interface{})
|
|
|
|
}
|
|
|
|
|
2021-02-02 12:05:47 +01:00
|
|
|
// InputLoader loads input according to the specified policy
|
2021-03-26 09:34:27 +01:00
|
|
|
// either from command line and input files or from OONI services. The
|
|
|
|
// behaviour depends on the input policy as described below.
|
2021-02-02 12:05:47 +01:00
|
|
|
//
|
2021-03-29 20:00:50 +02:00
|
|
|
// You MUST NOT change any public field of this structure when
|
|
|
|
// in use, because that MAY lead to data races.
|
|
|
|
//
|
2021-02-02 12:05:47 +01:00
|
|
|
// InputNone
|
|
|
|
//
|
|
|
|
// We fail if there is any StaticInput or any SourceFiles. If
|
|
|
|
// there's no input, we return a single, empty entry that causes
|
|
|
|
// experiments that don't require input to run once.
|
|
|
|
//
|
|
|
|
// InputOptional
|
|
|
|
//
|
|
|
|
// We gather input from StaticInput and SourceFiles. If there is
|
|
|
|
// input, we return it. Otherwise we return a single, empty entry
|
|
|
|
// that causes experiments that don't require input to run once.
|
|
|
|
//
|
2021-03-26 09:34:27 +01:00
|
|
|
// InputOrQueryBackend
|
2021-02-02 12:05:47 +01:00
|
|
|
//
|
|
|
|
// We gather input from StaticInput and SourceFiles. If there is
|
|
|
|
// input, we return it. Otherwise, we use OONI's probe services
|
2021-03-26 09:34:27 +01:00
|
|
|
// to gather input using the best API for the task.
|
2021-02-02 12:05:47 +01:00
|
|
|
//
|
2021-12-03 15:30:56 +01:00
|
|
|
// InputOrStaticDefault
|
|
|
|
//
|
|
|
|
// We gather input from StaticInput and SourceFiles. If there is
|
|
|
|
// input, we return it. Otherwise, we return an internal static
|
|
|
|
// list of inputs to be used with this experiment.
|
|
|
|
//
|
2021-02-02 12:05:47 +01:00
|
|
|
// InputStrictlyRequired
|
|
|
|
//
|
2021-03-26 09:34:27 +01:00
|
|
|
// We gather input from StaticInput and SourceFiles. If there is
|
|
|
|
// input, we return it. Otherwise, we return an error.
|
2021-03-29 20:00:50 +02:00
|
|
|
type InputLoader struct {
|
2021-03-29 18:46:26 +02:00
|
|
|
// CheckInConfig contains options for the CheckIn API. If
|
|
|
|
// not set, then we'll create a default config. If set but
|
|
|
|
// there are fields inside it that are not set, then we
|
|
|
|
// will set them to a default value.
|
2022-01-03 13:53:23 +01:00
|
|
|
CheckInConfig *model.OOAPICheckInConfig
|
2021-03-29 18:46:26 +02:00
|
|
|
|
2021-12-03 15:30:56 +01:00
|
|
|
// ExperimentName is the name of the experiment. This field
|
|
|
|
// is only used together with the InputOrStaticDefault policy.
|
|
|
|
ExperimentName string
|
|
|
|
|
2021-03-29 18:46:26 +02:00
|
|
|
// InputPolicy specifies the input policy for the
|
|
|
|
// current experiment. We will not load any input if
|
|
|
|
// the policy says we should not. You MUST fill in
|
|
|
|
// this field.
|
2022-08-17 10:57:03 +02:00
|
|
|
InputPolicy model.InputPolicy
|
2021-03-29 18:46:26 +02:00
|
|
|
|
2021-04-07 14:14:25 +02:00
|
|
|
// Logger is the optional logger that the InputLoader
|
|
|
|
// should be using. If not set, we will use the default
|
|
|
|
// logger of github.com/apex/log.
|
|
|
|
Logger InputLoaderLogger
|
|
|
|
|
2021-03-29 18:46:26 +02:00
|
|
|
// Session is the current measurement session. You
|
|
|
|
// MUST fill in this field.
|
|
|
|
Session InputLoaderSession
|
|
|
|
|
2021-02-02 12:05:47 +01:00
|
|
|
// StaticInputs contains optional input to be added
|
|
|
|
// to the resulting input list if possible.
|
|
|
|
StaticInputs []string
|
|
|
|
|
|
|
|
// SourceFiles contains optional files to read input
|
|
|
|
// from. Each file should contain a single input string
|
2021-03-26 09:34:27 +01:00
|
|
|
// per line. We will fail if any file is unreadable
|
|
|
|
// as well as if any file is empty.
|
2021-02-02 12:05:47 +01:00
|
|
|
SourceFiles []string
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load attempts to load input using the specified input loader. We will
|
|
|
|
// return a list of URLs because this is the only input we support.
|
2022-01-03 13:53:23 +01:00
|
|
|
func (il *InputLoader) Load(ctx context.Context) ([]model.OOAPIURLInfo, error) {
|
2021-02-02 12:05:47 +01:00
|
|
|
switch il.InputPolicy {
|
2022-08-17 10:57:03 +02:00
|
|
|
case model.InputOptional:
|
2021-02-02 12:05:47 +01:00
|
|
|
return il.loadOptional()
|
2022-08-17 10:57:03 +02:00
|
|
|
case model.InputOrQueryBackend:
|
2021-03-26 09:34:27 +01:00
|
|
|
return il.loadOrQueryBackend(ctx)
|
2022-08-17 10:57:03 +02:00
|
|
|
case model.InputStrictlyRequired:
|
2021-02-02 12:05:47 +01:00
|
|
|
return il.loadStrictlyRequired(ctx)
|
2022-08-17 10:57:03 +02:00
|
|
|
case model.InputOrStaticDefault:
|
2021-12-03 15:30:56 +01:00
|
|
|
return il.loadOrStaticDefault(ctx)
|
2021-02-02 12:05:47 +01:00
|
|
|
default:
|
|
|
|
return il.loadNone()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-26 09:34:27 +01:00
|
|
|
// loadNone implements the InputNone policy.
|
2022-01-03 13:53:23 +01:00
|
|
|
func (il *InputLoader) loadNone() ([]model.OOAPIURLInfo, error) {
|
2021-02-02 12:05:47 +01:00
|
|
|
if len(il.StaticInputs) > 0 || len(il.SourceFiles) > 0 {
|
|
|
|
return nil, ErrNoInputExpected
|
|
|
|
}
|
2021-03-26 09:34:27 +01:00
|
|
|
// Note that we need to return a single empty entry.
|
2022-01-03 13:53:23 +01:00
|
|
|
return []model.OOAPIURLInfo{{}}, nil
|
2021-02-02 12:05:47 +01:00
|
|
|
}
|
|
|
|
|
2021-03-26 09:34:27 +01:00
|
|
|
// loadOptional implements the InputOptional policy.
|
2022-01-03 13:53:23 +01:00
|
|
|
func (il *InputLoader) loadOptional() ([]model.OOAPIURLInfo, error) {
|
2021-02-02 12:05:47 +01:00
|
|
|
inputs, err := il.loadLocal()
|
|
|
|
if err == nil && len(inputs) <= 0 {
|
2021-03-26 09:34:27 +01:00
|
|
|
// Note that we need to return a single empty entry.
|
2022-01-03 13:53:23 +01:00
|
|
|
inputs = []model.OOAPIURLInfo{{}}
|
2021-02-02 12:05:47 +01:00
|
|
|
}
|
|
|
|
return inputs, err
|
|
|
|
}
|
|
|
|
|
2021-03-26 09:34:27 +01:00
|
|
|
// loadStrictlyRequired implements the InputStrictlyRequired policy.
|
2022-01-03 13:53:23 +01:00
|
|
|
func (il *InputLoader) loadStrictlyRequired(ctx context.Context) ([]model.OOAPIURLInfo, error) {
|
2021-02-02 12:05:47 +01:00
|
|
|
inputs, err := il.loadLocal()
|
|
|
|
if err != nil || len(inputs) > 0 {
|
|
|
|
return inputs, err
|
|
|
|
}
|
|
|
|
return nil, ErrInputRequired
|
|
|
|
}
|
|
|
|
|
2021-03-26 09:34:27 +01:00
|
|
|
// loadOrQueryBackend implements the InputOrQueryBackend policy.
|
2022-01-03 13:53:23 +01:00
|
|
|
func (il *InputLoader) loadOrQueryBackend(ctx context.Context) ([]model.OOAPIURLInfo, error) {
|
2021-02-02 12:05:47 +01:00
|
|
|
inputs, err := il.loadLocal()
|
|
|
|
if err != nil || len(inputs) > 0 {
|
|
|
|
return inputs, err
|
|
|
|
}
|
2021-03-29 20:00:50 +02:00
|
|
|
return il.loadRemote(ctx)
|
2021-02-02 12:05:47 +01:00
|
|
|
}
|
|
|
|
|
2021-12-03 15:30:56 +01:00
|
|
|
// TODO(https://github.com/ooni/probe/issues/1390): we need to
|
|
|
|
// implement serving DNSCheck targets from the API
|
|
|
|
var dnsCheckDefaultInput = []string{
|
|
|
|
"https://dns.google/dns-query",
|
|
|
|
"https://8.8.8.8/dns-query",
|
|
|
|
"dot://8.8.8.8:853/",
|
|
|
|
"dot://8.8.4.4:853/",
|
|
|
|
"https://8.8.4.4/dns-query",
|
|
|
|
"https://cloudflare-dns.com/dns-query",
|
|
|
|
"https://1.1.1.1/dns-query",
|
|
|
|
"https://1.0.0.1/dns-query",
|
|
|
|
"dot://1.1.1.1:853/",
|
|
|
|
"dot://1.0.0.1:853/",
|
|
|
|
"https://dns.quad9.net/dns-query",
|
|
|
|
"https://9.9.9.9/dns-query",
|
|
|
|
"dot://9.9.9.9:853/",
|
|
|
|
"dot://dns.quad9.net/",
|
2022-02-17 17:52:16 +01:00
|
|
|
"https://family.cloudflare-dns.com/dns-query",
|
|
|
|
"dot://family.cloudflare-dns.com/dns-query",
|
|
|
|
"https://dns11.quad9.net/dns-query",
|
|
|
|
"dot://dns11.quad9.net/dns-query",
|
|
|
|
"https://dns9.quad9.net/dns-query",
|
|
|
|
"dot://dns9.quad9.net/dns-query",
|
|
|
|
"https://dns12.quad9.net/dns-query",
|
|
|
|
"dot://dns12.quad9.net/dns-query",
|
|
|
|
"https://1dot1dot1dot1.cloudflare-dns.com/dns-query",
|
|
|
|
"dot://1dot1dot1dot1.cloudflare-dns.com/dns-query",
|
|
|
|
"https://dns.adguard.com/dns-query",
|
|
|
|
"dot://dns.adguard.com/dns-query",
|
|
|
|
"https://dns-family.adguard.com/dns-query",
|
|
|
|
"dot://dns-family.adguard.com/dns-query",
|
|
|
|
"https://dns.cloudflare.com/dns-query",
|
|
|
|
"https://adblock.doh.mullvad.net/dns-query",
|
|
|
|
"dot://adblock.doh.mullvad.net/dns-query",
|
|
|
|
"https://dns.alidns.com/dns-query",
|
|
|
|
"dot://dns.alidns.com/dns-query",
|
|
|
|
"https://doh.opendns.com/dns-query",
|
|
|
|
"https://dns.nextdns.io/dns-query",
|
|
|
|
"dot://dns.nextdns.io/dns-query",
|
|
|
|
"https://dns10.quad9.net/dns-query",
|
|
|
|
"dot://dns10.quad9.net/dns-query",
|
|
|
|
"https://security.cloudflare-dns.com/dns-query",
|
|
|
|
"dot://security.cloudflare-dns.com/dns-query",
|
|
|
|
"https://dns.switch.ch/dns-query",
|
|
|
|
"dot://dns.switch.ch/dns-query",
|
2021-12-03 15:30:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
var stunReachabilityDefaultInput = stuninput.AsnStunReachabilityInput()
|
|
|
|
|
2021-12-03 17:43:09 +01:00
|
|
|
// StaticBareInputForExperiment returns the list of strings an
|
2021-12-03 15:30:56 +01:00
|
|
|
// experiment should use as static input. In case there is no
|
|
|
|
// static input for this experiment, we return an error.
|
2021-12-03 17:43:09 +01:00
|
|
|
func StaticBareInputForExperiment(name string) ([]string, error) {
|
2021-12-03 15:30:56 +01:00
|
|
|
// Implementation note: we may be called from pkg/oonimkall
|
|
|
|
// with a non-canonical experiment name, so we need to convert
|
|
|
|
// the experiment name to be canonical before proceeding.
|
2022-08-17 10:57:03 +02:00
|
|
|
switch registry.CanonicalizeExperimentName(name) {
|
2021-12-03 15:30:56 +01:00
|
|
|
case "dnscheck":
|
|
|
|
return dnsCheckDefaultInput, nil
|
|
|
|
case "stunreachability":
|
|
|
|
return stunReachabilityDefaultInput, nil
|
|
|
|
default:
|
|
|
|
return nil, ErrNoStaticInput
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// staticInputForExperiment returns the static input for the given experiment
|
|
|
|
// or an error if there's no static input for the experiment.
|
2022-01-03 13:53:23 +01:00
|
|
|
func staticInputForExperiment(name string) ([]model.OOAPIURLInfo, error) {
|
2021-12-03 17:43:09 +01:00
|
|
|
return stringListToModelURLInfo(StaticBareInputForExperiment(name))
|
2021-12-03 15:30:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// loadOrStaticDefault implements the InputOrStaticDefault policy.
|
2022-01-03 13:53:23 +01:00
|
|
|
func (il *InputLoader) loadOrStaticDefault(ctx context.Context) ([]model.OOAPIURLInfo, error) {
|
2021-12-03 15:30:56 +01:00
|
|
|
inputs, err := il.loadLocal()
|
|
|
|
if err != nil || len(inputs) > 0 {
|
|
|
|
return inputs, err
|
|
|
|
}
|
|
|
|
return staticInputForExperiment(il.ExperimentName)
|
|
|
|
}
|
|
|
|
|
2021-03-26 09:34:27 +01:00
|
|
|
// loadLocal loads inputs from StaticInputs and SourceFiles.
|
2022-01-03 13:53:23 +01:00
|
|
|
func (il *InputLoader) loadLocal() ([]model.OOAPIURLInfo, error) {
|
|
|
|
inputs := []model.OOAPIURLInfo{}
|
2021-02-02 12:05:47 +01:00
|
|
|
for _, input := range il.StaticInputs {
|
2022-01-03 13:53:23 +01:00
|
|
|
inputs = append(inputs, model.OOAPIURLInfo{URL: input})
|
2021-02-02 12:05:47 +01:00
|
|
|
}
|
|
|
|
for _, filepath := range il.SourceFiles {
|
refactor: flatten and separate (#353)
* refactor(atomicx): move outside the engine package
After merging probe-engine into probe-cli, my impression is that we have
too much unnecessary nesting of packages in this repository.
The idea of this commit and of a bunch of following commits will instead
be to reduce the nesting and simplify the structure.
While there, improve the documentation.
* fix: always use the atomicx package
For consistency, never use sync/atomic and always use ./internal/atomicx
so we can just grep and make sure we're not risking to crash if we make
a subtle mistake on a 32 bit platform.
While there, mention in the contributing guidelines that we want to
always prefer the ./internal/atomicx package over sync/atomic.
* fix(atomicx): remove unnecessary constructor
We don't need a constructor here. The default constructed `&Int64{}`
instance is already usable and the constructor does not add anything to
what we are doing, rather it just creates extra confusion.
* cleanup(atomicx): we are not using Float64
Because atomicx.Float64 is unused, we can safely zap it.
* cleanup(atomicx): simplify impl and improve tests
We can simplify the implementation by using defer and by letting
the Load() method call Add(0).
We can improve tests by making many goroutines updated the
atomic int64 value concurrently.
* refactor(fsx): can live in the ./internal pkg
Let us reduce the amount of nesting. While there, ensure that the
package only exports the bare minimum, and improve the documentation
of the tests, to ease reading the code.
* refactor: move runtimex to ./internal
* refactor: move shellx into the ./internal package
While there, remove unnecessary dependency between packages.
While there, specify in the contributing guidelines that
one should use x/sys/execabs instead of os/exec.
* refactor: move ooapi into the ./internal pkg
* refactor(humanize): move to ./internal and better docs
* refactor: move platform to ./internal
* refactor(randx): move to ./internal
* refactor(multierror): move into the ./internal pkg
* refactor(kvstore): all kvstores in ./internal
Rather than having part of the kvstore inside ./internal/engine/kvstore
and part in ./internal/engine/kvstore.go, let us put every piece of code
that is kvstore related into the ./internal/kvstore package.
* fix(kvstore): always return ErrNoSuchKey on Get() error
It should help to use the kvstore everywhere removing all the
copies that are lingering around the tree.
* sessionresolver: make KVStore mandatory
Simplifies implementation. While there, use the ./internal/kvstore
package rather than having our private implementation.
* fix(ooapi): use the ./internal/kvstore package
* fix(platform): better documentation
2021-06-04 10:34:18 +02:00
|
|
|
extra, err := il.readfile(filepath, fsx.OpenFile)
|
2021-02-02 12:05:47 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-02-26 10:16:34 +01:00
|
|
|
// See https://github.com/ooni/probe-engine/issues/1123.
|
2021-02-02 12:05:47 +01:00
|
|
|
if len(extra) <= 0 {
|
|
|
|
return nil, fmt.Errorf("%w: %s", ErrDetectedEmptyFile, filepath)
|
|
|
|
}
|
|
|
|
inputs = append(inputs, extra...)
|
|
|
|
}
|
|
|
|
return inputs, nil
|
|
|
|
}
|
|
|
|
|
2021-03-26 09:34:27 +01:00
|
|
|
// inputLoaderOpenFn is the type of the function to open a file.
|
2021-03-29 19:37:32 +02:00
|
|
|
type inputLoaderOpenFn func(filepath string) (fs.File, error)
|
2021-03-26 09:34:27 +01:00
|
|
|
|
|
|
|
// readfile reads inputs from the specified file. The open argument should be
|
2021-03-31 15:59:19 +02:00
|
|
|
// compatible with stdlib's fs.Open and helps us with unit testing.
|
2022-01-03 13:53:23 +01:00
|
|
|
func (il *InputLoader) readfile(filepath string, open inputLoaderOpenFn) ([]model.OOAPIURLInfo, error) {
|
|
|
|
inputs := []model.OOAPIURLInfo{}
|
2021-02-02 12:05:47 +01:00
|
|
|
filep, err := open(filepath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer filep.Close()
|
|
|
|
// Implementation note: when you save file with vim, you have newline at
|
|
|
|
// end of file and you don't want to consider that an input line. While there
|
|
|
|
// ignore any other empty line that may occur inside the file.
|
|
|
|
scanner := bufio.NewScanner(filep)
|
|
|
|
for scanner.Scan() {
|
|
|
|
line := scanner.Text()
|
|
|
|
if line != "" {
|
2022-01-03 13:53:23 +01:00
|
|
|
inputs = append(inputs, model.OOAPIURLInfo{URL: line})
|
2021-02-02 12:05:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if scanner.Err() != nil {
|
|
|
|
return nil, scanner.Err()
|
|
|
|
}
|
|
|
|
return inputs, nil
|
|
|
|
}
|
|
|
|
|
2021-03-26 09:34:27 +01:00
|
|
|
// loadRemote loads inputs from a remote source.
|
2022-01-03 13:53:23 +01:00
|
|
|
func (il *InputLoader) loadRemote(ctx context.Context) ([]model.OOAPIURLInfo, error) {
|
2021-03-29 18:46:26 +02:00
|
|
|
config := il.CheckInConfig
|
|
|
|
if config == nil {
|
|
|
|
// Note: Session.CheckIn documentation says it will fill in
|
|
|
|
// any field with a required value with a reasonable default
|
|
|
|
// if such value is missing. So, here we just need to be
|
|
|
|
// concerned about NOT passing it a NULL pointer.
|
2022-01-03 13:53:23 +01:00
|
|
|
config = &model.OOAPICheckInConfig{}
|
2021-02-02 12:05:47 +01:00
|
|
|
}
|
2021-04-07 14:14:25 +02:00
|
|
|
reply, err := il.checkIn(ctx, config)
|
2021-02-02 12:05:47 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-03-29 18:46:26 +02:00
|
|
|
if reply.WebConnectivity == nil || len(reply.WebConnectivity.URLs) <= 0 {
|
|
|
|
return nil, ErrNoURLsReturned
|
|
|
|
}
|
|
|
|
return reply.WebConnectivity.URLs, nil
|
2021-02-02 12:05:47 +01:00
|
|
|
}
|
2021-04-07 14:14:25 +02:00
|
|
|
|
|
|
|
// checkIn executes the check-in and filters the returned URLs to exclude
|
|
|
|
// the URLs that are not part of the requested categories. This is done for
|
|
|
|
// robustness, just in case we or the API do something wrong.
|
|
|
|
func (il *InputLoader) checkIn(
|
2022-01-03 13:53:23 +01:00
|
|
|
ctx context.Context, config *model.OOAPICheckInConfig) (*model.OOAPICheckInInfo, error) {
|
2021-04-07 14:14:25 +02:00
|
|
|
reply, err := il.Session.CheckIn(ctx, config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// Note: safe to assume that reply is not nil if err is nil
|
|
|
|
if reply.WebConnectivity != nil && len(reply.WebConnectivity.URLs) > 0 {
|
|
|
|
reply.WebConnectivity.URLs = il.preventMistakes(
|
|
|
|
reply.WebConnectivity.URLs, config.WebConnectivity.CategoryCodes,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return reply, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// preventMistakes makes the code more robust with respect to any possible
|
|
|
|
// integration issue where the backend returns to us URLs that don't
|
|
|
|
// belong to the category codes we requested.
|
2022-01-03 13:53:23 +01:00
|
|
|
func (il *InputLoader) preventMistakes(input []model.OOAPIURLInfo, categories []string) (output []model.OOAPIURLInfo) {
|
2021-04-07 14:14:25 +02:00
|
|
|
if len(categories) <= 0 {
|
|
|
|
return input
|
|
|
|
}
|
|
|
|
for _, entry := range input {
|
|
|
|
var found bool
|
|
|
|
for _, cat := range categories {
|
|
|
|
if entry.CategoryCode == cat {
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
|
|
|
il.logger().Warnf("URL %+v not in %+v; skipping", entry, categories)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
output = append(output, entry)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// logger returns the configured logger or apex/log's default.
|
|
|
|
func (il *InputLoader) logger() InputLoaderLogger {
|
|
|
|
if il.Logger != nil {
|
|
|
|
return il.Logger
|
|
|
|
}
|
|
|
|
return log.Log
|
|
|
|
}
|
2021-12-03 15:30:56 +01:00
|
|
|
|
|
|
|
// stringListToModelURLInfo is an utility function to convert
|
|
|
|
// a list of strings containing URLs into a list of model.URLInfo
|
|
|
|
// which would have been returned by an hypothetical backend
|
|
|
|
// API serving input for a test for which we don't have an API
|
|
|
|
// yet (e.g., stunreachability and dnscheck).
|
2022-01-03 13:53:23 +01:00
|
|
|
func stringListToModelURLInfo(input []string, err error) ([]model.OOAPIURLInfo, error) {
|
2021-12-03 15:30:56 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-01-03 13:53:23 +01:00
|
|
|
var output []model.OOAPIURLInfo
|
2021-12-03 15:30:56 +01:00
|
|
|
for _, URL := range input {
|
|
|
|
if _, err := url.Parse(URL); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-01-03 13:53:23 +01:00
|
|
|
output = append(output, model.OOAPIURLInfo{
|
2021-12-03 15:30:56 +01:00
|
|
|
CategoryCode: "MISC", // hard to find a category
|
|
|
|
CountryCode: "XX", // representing no country
|
|
|
|
URL: URL,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return output, nil
|
|
|
|
}
|