ooni-probe-cli/pkg/oonimkall/taskmodel.go

375 lines
12 KiB
Go

package oonimkall
import (
"context"
"io"
"github.com/ooni/probe-cli/v3/internal/engine"
"github.com/ooni/probe-cli/v3/internal/model"
)
//
// Task Model
//
// The oonimkall package allows you to run OONI network
// experiments as "tasks". This file defines all the
// underlying model entailed by running such tasks.
//
// Logging
//
// This section of the file defines the types and the
// interfaces required to implement logging.
//
// The rest of the codebase will use a generic model.Logger
// as a logger. This is a pretty fundamental interface in
// ooni/probe-cli and so it's not defined in this file.
//
const taskABIVersion = 1
// Running tasks emit logs using different log levels. We
// define log levels with the usual semantics.
//
// The logger used by a task _may_ be configured to not
// emit log events that are less severe than a given
// severity.
//
// We use the following definitions both for defining the
// log level of a log and for configuring the maximum
// acceptable log level emitted by a logger.
const (
logLevelDebug2 = "DEBUG2"
logLevelDebug = "DEBUG"
logLevelInfo = "INFO"
logLevelErr = "ERR"
logLevelWarning = "WARNING"
)
//
// Emitting Events
//
// While it is running, a task emits events. This section
// of the file defines the types needed to emit events.
//
// type of emitted events.
const (
eventTypeFailureIPLookup = "failure.ip_lookup"
eventTypeFailureASNLookup = "failure.asn_lookup"
eventTypeFailureCCLookup = "failure.cc_lookup"
eventTypeFailureMeasurement = "failure.measurement"
eventTypeFailureMeasurementSubmission = "failure.measurement_submission"
eventTypeFailureReportCreate = "failure.report_create"
eventTypeFailureResolverLookup = "failure.resolver_lookup"
eventTypeFailureStartup = "failure.startup"
eventTypeLog = "log"
eventTypeMeasurement = "measurement"
eventTypeStatusEnd = "status.end"
eventTypeStatusGeoIPLookup = "status.geoip_lookup"
eventTypeStatusMeasurementDone = "status.measurement_done"
eventTypeStatusMeasurementStart = "status.measurement_start"
eventTypeStatusMeasurementSubmission = "status.measurement_submission"
eventTypeStatusProgress = "status.progress"
eventTypeStatusQueued = "status.queued"
eventTypeStatusReportCreate = "status.report_create"
eventTypeStatusResolverLookup = "status.resolver_lookup"
eventTypeStatusStarted = "status.started"
)
// taskEmitter is anything that allows us to
// emit events while running a task.
//
// Note that a task emitter _may_ be configured
// to ignore _some_ events though.
type taskEmitter interface {
// Emit emits the event (unless the emitter is
// configured to ignore this event key).
Emit(key string, value interface{})
}
// taskEmitterCloser is a closeable taskEmitter.
type taskEmitterCloser interface {
taskEmitter
io.Closer
}
type eventEmpty struct{}
// eventFailure contains information on a failure.
type eventFailure struct {
Failure string `json:"failure"`
}
// eventLog is an event containing a log message.
type eventLog struct {
LogLevel string `json:"log_level"`
Message string `json:"message"`
}
type eventMeasurementGeneric struct {
Failure string `json:"failure,omitempty"`
Idx int64 `json:"idx"`
Input string `json:"input"`
JSONStr string `json:"json_str,omitempty"`
}
type eventStatusEnd struct {
DownloadedKB float64 `json:"downloaded_kb"`
Failure string `json:"failure"`
UploadedKB float64 `json:"uploaded_kb"`
}
type eventStatusGeoIPLookup struct {
ProbeASN string `json:"probe_asn"`
ProbeCC string `json:"probe_cc"`
ProbeIP string `json:"probe_ip"`
ProbeNetworkName string `json:"probe_network_name"`
}
// eventStatusProgress reports progress information.
type eventStatusProgress struct {
Message string `json:"message"`
Percentage float64 `json:"percentage"`
}
type eventStatusReportGeneric struct {
ReportID string `json:"report_id"`
}
type eventStatusResolverLookup struct {
ResolverASN string `json:"resolver_asn"`
ResolverIP string `json:"resolver_ip"`
ResolverNetworkName string `json:"resolver_network_name"`
}
// event is an event emitted by a task. This structure extends the event
// described by MK v0.10.9 FFI API (https://git.io/Jv4Rv).
type event struct {
Key string `json:"key"`
Value interface{} `json:"value"`
}
//
// OONI Session
//
// For performing several operations, including running
// experiments, we need to create an OONI session.
//
// This section of the file defines the interface between
// our oonimkall API and the real session.
//
// The abstraction representing a OONI session is taskSession.
//
// taskKVStoreFSBuilder constructs a KVStore with
// filesystem backing for running tests.
type taskKVStoreFSBuilder interface {
// NewFS creates a new KVStore using the filesystem.
NewFS(path string) (model.KeyValueStore, error)
}
// taskSessionBuilder constructs a new Session.
type taskSessionBuilder interface {
// NewSession creates a new taskSession.
NewSession(ctx context.Context,
config engine.SessionConfig) (taskSession, error)
}
// taskSession abstracts a OONI session.
type taskSession interface {
// A session can be closed.
io.Closer
// NewExperimentBuilderByName creates the builder for constructing
// a new experiment given the experiment's name.
NewExperimentBuilderByName(name string) (taskExperimentBuilder, error)
// MaybeLookupBackendsContext lookups the OONI backend unless
// this operation has already been performed.
MaybeLookupBackendsContext(ctx context.Context) error
// MaybeLookupLocationContext lookups the probe location unless
// this operation has already been performed.
MaybeLookupLocationContext(ctx context.Context) error
// ProbeIP must be called after MaybeLookupLocationContext
// and returns the resolved probe IP.
ProbeIP() string
// ProbeASNString must be called after MaybeLookupLocationContext
// and returns the resolved probe ASN as a string.
ProbeASNString() string
// ProbeCC must be called after MaybeLookupLocationContext
// and returns the resolved probe country code.
ProbeCC() string
// ProbeNetworkName must be called after MaybeLookupLocationContext
// and returns the resolved probe country code.
ProbeNetworkName() string
// ResolverANSString must be called after MaybeLookupLocationContext
// and returns the resolved resolver's ASN as a string.
ResolverASNString() string
// ResolverIP must be called after MaybeLookupLocationContext
// and returns the resolved resolver's IP.
ResolverIP() string
// ResolverNetworkName must be called after MaybeLookupLocationContext
// and returns the resolved resolver's network name.
ResolverNetworkName() string
}
// taskExperimentBuilder builds a taskExperiment.
type taskExperimentBuilder interface {
// SetCallbacks sets the experiment callbacks.
SetCallbacks(callbacks model.ExperimentCallbacks)
// InputPolicy returns the experiment's input policy.
InputPolicy() model.InputPolicy
// NewExperiment creates the new experiment.
NewExperimentInstance() taskExperiment
// Interruptible returns whether this experiment is interruptible.
Interruptible() bool
}
// taskExperiment is a runnable experiment.
type taskExperiment interface {
// KibiBytesReceived returns the KiB received by the experiment.
KibiBytesReceived() float64
// KibiBytesSent returns the KiB sent by the experiment.
KibiBytesSent() float64
// OpenReportContext opens a new report.
OpenReportContext(ctx context.Context) error
// ReportID must be called after a successful OpenReportContext
// and returns the report ID for this measurement.
ReportID() string
// MeasureWithContext runs the measurement.
MeasureWithContext(ctx context.Context, input string) (
measurement *model.Measurement, err error)
// SubmitAndUpdateMeasurementContext submits the measurement
// and updates its report ID on success.
SubmitAndUpdateMeasurementContext(
ctx context.Context, measurement *model.Measurement) error
}
//
// Task Running
//
// This section contains the interfaces allowing us
// to run a task until completion.
//
// taskRunner runs a task until completion.
type taskRunner interface {
// Run runs until completion.
Run(ctx context.Context)
}
//
// Task Settings
//
// This section defines the settings used by a task.
//
// Settings contains settings for a task. This structure derives from
// the one described by MK v0.10.9 FFI API (https://git.io/Jv4Rv), yet
// since 2020-12-03 we're not backwards compatible anymore.
type settings struct {
// Annotations contains the annotations to be added
// to every measurements performed by the task.
Annotations map[string]string `json:"annotations,omitempty"`
// AssetsDir is the directory where to store assets. This
// field is an extension of MK's specification. If
// this field is empty, the task won't start.
AssetsDir string `json:"assets_dir"`
// DisabledEvents contains disabled events. See
// https://git.io/Jv4Rv for the events names.
//
// This setting is currently ignored. We noticed the
// code was ignoring it on 2021-12-01.
DisabledEvents []string `json:"disabled_events,omitempty"`
// Inputs contains the inputs. The task will fail if it
// requires input and you provide no input.
Inputs []string `json:"inputs,omitempty"`
// LogLevel contains the logs level. See https://git.io/Jv4Rv
// for the names of the available log levels.
LogLevel string `json:"log_level,omitempty"`
// Name contains the task name. By https://git.io/Jv4Rv the
// names are in camel case, e.g. `Ndt`.
Name string `json:"name"`
// Options contains the task options.
Options settingsOptions `json:"options"`
// Proxy allows you to optionally force a specific proxy
// rather than using no proxy (the default).
//
// Use `psiphon:///` to force using Psiphon with the
// embedded configuration file. Not all builds have
// an embedded configuration file, but OONI builds have
// such a file, so they can use this functionality.
//
// Use `socks5://10.0.0.1:9050/` to connect to a SOCKS5
// proxy running on 10.0.0.1:9050. This could be, for
// example, a suitably configured `tor` instance.
Proxy string
// StateDir is the directory where to store persistent data. This
// field is an extension of MK's specification. If
// this field is empty, the task won't start.
StateDir string `json:"state_dir"`
// TempDir is the temporary directory. This field is an extension of MK's
// specification. If this field is empty, we will pick the tempdir that
// ioutil.TempDir uses by default, which may not work on mobile. According
// to our experiments as of 2020-06-10, leaving the TempDir empty works
// for iOS and does not work for Android.
TempDir string `json:"temp_dir"`
// TunnelDir is the directory where to store persistent state
// related to circumvention tunnels. This directory is required
// only if you want to use the tunnels. Added since 3.10.0.
TunnelDir string `json:"tunnel_dir"`
// Version indicates the version of this structure.
Version int64 `json:"version"`
}
// settingsOptions contains the settings options
type settingsOptions struct {
// MaxRuntime is the maximum runtime expressed in seconds. A negative
// value for this field disables the maximum runtime. Using
// a zero value will also mean disabled. This is not the
// original behaviour of Measurement Kit, which used to run
// for zero time in such case.
MaxRuntime float64 `json:"max_runtime,omitempty"`
// NoCollector indicates whether to use a collector
NoCollector bool `json:"no_collector,omitempty"`
// ProbeServicesBaseURL contains the probe services base URL.
ProbeServicesBaseURL string `json:"probe_services_base_url,omitempty"`
// SoftwareName is the software name. If this option is not
// present, then the library startup will fail.
SoftwareName string `json:"software_name,omitempty"`
// SoftwareVersion is the software version. If this option is not
// present, then the library startup will fail.
SoftwareVersion string `json:"software_version,omitempty"`
}