feat(miniooni): run local oonirun v2 descriptor (#966)
We introduce the -f, --input-file FILE option with which we are able to run an OONI Run v2 descriptor stored locally. In this running mode, there are no checks related to whether the descriptor has changed, since we're dealing with a local file. Closes https://github.com/ooni/probe/issues/2328
This commit is contained in:
parent
ad01856beb
commit
c420c8bb29
|
@ -196,6 +196,13 @@ func registerOONIRun(rootCmd *cobra.Command, globalOptions *Options) {
|
|||
[]string{},
|
||||
"URL of the OONI Run v2 descriptor to run (may be specified multiple times)",
|
||||
)
|
||||
flags.StringSliceVarP(
|
||||
&globalOptions.InputFilePaths,
|
||||
"input-file",
|
||||
"f",
|
||||
[]string{},
|
||||
"Path to the OONI Run v2 descriptor to run (may be specified multiple times)",
|
||||
)
|
||||
}
|
||||
|
||||
// registerAllExperiments registers a subcommand for each experiment
|
||||
|
|
|
@ -6,25 +6,18 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"github.com/ooni/probe-cli/v3/internal/engine"
|
||||
"github.com/ooni/probe-cli/v3/internal/oonirun"
|
||||
"github.com/ooni/probe-cli/v3/internal/runtimex"
|
||||
)
|
||||
|
||||
// ooniRunMain runs the experiments described by the given OONI Run URLs. This
|
||||
// function works with both v1 and v2 OONI Run URLs.
|
||||
func ooniRunMain(ctx context.Context,
|
||||
sess *engine.Session, currentOptions *Options, annotations map[string]string) {
|
||||
runtimex.PanicIfTrue(
|
||||
len(currentOptions.Inputs) <= 0,
|
||||
"in oonirun mode you need to specify at least one URL using `-i URL`",
|
||||
)
|
||||
runtimex.PanicIfTrue(
|
||||
len(currentOptions.InputFilePaths) > 0,
|
||||
"in oonirun mode you cannot specify any `-f FILE` file",
|
||||
)
|
||||
logger := sess.Logger()
|
||||
cfg := &oonirun.LinkConfig{
|
||||
AcceptChanges: currentOptions.Yes,
|
||||
|
@ -49,4 +42,20 @@ func ooniRunMain(ctx context.Context,
|
|||
continue
|
||||
}
|
||||
}
|
||||
for _, filename := range currentOptions.InputFilePaths {
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
logger.Warnf("oonirun: reading OONI Run v2 descriptor failed: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
var descr oonirun.V2Descriptor
|
||||
if err := json.Unmarshal(data, &descr); err != nil {
|
||||
logger.Warnf("oonirun: parsing OONI Run v2 descriptor failed: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
if err := oonirun.V2MeasureDescriptor(ctx, cfg, &descr); err != nil {
|
||||
logger.Warnf("oonirun: running link failed: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,8 +30,8 @@ var (
|
|||
v2CountFailedExperiments = &atomicx.Int64{}
|
||||
)
|
||||
|
||||
// v2Descriptor describes a single nettest to run.
|
||||
type v2Descriptor struct {
|
||||
// V2Descriptor describes a list of nettests to run together.
|
||||
type V2Descriptor struct {
|
||||
// Name is the name of this descriptor.
|
||||
Name string `json:"name"`
|
||||
|
||||
|
@ -42,11 +42,11 @@ type v2Descriptor struct {
|
|||
Author string `json:"author"`
|
||||
|
||||
// Nettests contains the list of nettests to run.
|
||||
Nettests []v2Nettest `json:"nettests"`
|
||||
Nettests []V2Nettest `json:"nettests"`
|
||||
}
|
||||
|
||||
// v2Nettest specifies how a nettest should run.
|
||||
type v2Nettest struct {
|
||||
// V2Nettest specifies how a nettest should run.
|
||||
type V2Nettest struct {
|
||||
// Inputs contains inputs for the experiment.
|
||||
Inputs []string `json:"inputs"`
|
||||
|
||||
|
@ -66,7 +66,7 @@ var ErrHTTPRequestFailed = errors.New("oonirun: HTTP request failed")
|
|||
// getV2DescriptorFromHTTPSURL GETs a v2Descriptor instance from
|
||||
// a static URL (e.g., from a GitHub repo or from a Gist).
|
||||
func getV2DescriptorFromHTTPSURL(ctx context.Context, client model.HTTPClient,
|
||||
logger model.Logger, URL string) (*v2Descriptor, error) {
|
||||
logger model.Logger, URL string) (*V2Descriptor, error) {
|
||||
template := httpx.APIClientTemplate{
|
||||
Accept: "",
|
||||
Authorization: "",
|
||||
|
@ -77,7 +77,7 @@ func getV2DescriptorFromHTTPSURL(ctx context.Context, client model.HTTPClient,
|
|||
Logger: logger,
|
||||
UserAgent: model.HTTPHeaderUserAgent,
|
||||
}
|
||||
var desc v2Descriptor
|
||||
var desc V2Descriptor
|
||||
if err := template.Build().GetJSON(ctx, "", &desc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ func getV2DescriptorFromHTTPSURL(ctx context.Context, client model.HTTPClient,
|
|||
// v2DescriptorCache contains all the known v2Descriptor entries.
|
||||
type v2DescriptorCache struct {
|
||||
// Entries contains all the cached descriptors.
|
||||
Entries map[string]*v2Descriptor
|
||||
Entries map[string]*V2Descriptor
|
||||
}
|
||||
|
||||
// v2DescriptorCacheKey is the name of the kvstore2 entry keeping
|
||||
|
@ -100,7 +100,7 @@ func v2DescriptorCacheLoad(fsstore model.KeyValueStore) (*v2DescriptorCache, err
|
|||
if err != nil {
|
||||
if errors.Is(err, kvstore.ErrNoSuchKey) {
|
||||
cache := &v2DescriptorCache{
|
||||
Entries: make(map[string]*v2Descriptor),
|
||||
Entries: make(map[string]*V2Descriptor),
|
||||
}
|
||||
return cache, nil
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ func v2DescriptorCacheLoad(fsstore model.KeyValueStore) (*v2DescriptorCache, err
|
|||
return nil, err
|
||||
}
|
||||
if cache.Entries == nil {
|
||||
cache.Entries = make(map[string]*v2Descriptor)
|
||||
cache.Entries = make(map[string]*V2Descriptor)
|
||||
}
|
||||
return &cache, nil
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ func v2DescriptorCacheLoad(fsstore model.KeyValueStore) (*v2DescriptorCache, err
|
|||
// - err is the error that occurred, or nil in case of success.
|
||||
func (cache *v2DescriptorCache) PullChangesWithoutSideEffects(
|
||||
ctx context.Context, client model.HTTPClient, logger model.Logger,
|
||||
URL string) (oldValue, newValue *v2Descriptor, err error) {
|
||||
URL string) (oldValue, newValue *V2Descriptor, err error) {
|
||||
oldValue = cache.Entries[URL]
|
||||
newValue, err = getV2DescriptorFromHTTPSURL(ctx, client, logger, URL)
|
||||
return
|
||||
|
@ -149,7 +149,7 @@ func (cache *v2DescriptorCache) PullChangesWithoutSideEffects(
|
|||
//
|
||||
// Note: this method modifies cache and is not safe for concurrent usage.
|
||||
func (cache *v2DescriptorCache) Update(
|
||||
fsstore model.KeyValueStore, URL string, entry *v2Descriptor) error {
|
||||
fsstore model.KeyValueStore, URL string, entry *V2Descriptor) error {
|
||||
cache.Entries[URL] = entry
|
||||
data, err := json.Marshal(cache)
|
||||
runtimex.PanicOnError(err, "json.Marshal failed")
|
||||
|
@ -159,9 +159,9 @@ func (cache *v2DescriptorCache) Update(
|
|||
// ErrNilDescriptor indicates that we have been passed a descriptor that is nil.
|
||||
var ErrNilDescriptor = errors.New("oonirun: descriptor is nil")
|
||||
|
||||
// v2MeasureDescriptor performs the measurement or measurements
|
||||
// V2MeasureDescriptor performs the measurement or measurements
|
||||
// described by the given list of v2Descriptor.
|
||||
func v2MeasureDescriptor(ctx context.Context, config *LinkConfig, desc *v2Descriptor) error {
|
||||
func V2MeasureDescriptor(ctx context.Context, config *LinkConfig, desc *V2Descriptor) error {
|
||||
if desc == nil {
|
||||
// Note: we have a test checking that we can handle a nil
|
||||
// descriptor, yet adding also this extra safety net feels
|
||||
|
@ -208,7 +208,7 @@ func v2MeasureDescriptor(ctx context.Context, config *LinkConfig, desc *v2Descri
|
|||
var ErrNeedToAcceptChanges = errors.New("oonirun: need to accept changes")
|
||||
|
||||
// v2DescriptorDiff shows what changed between the old and the new descriptors.
|
||||
func v2DescriptorDiff(oldValue, newValue *v2Descriptor, URL string) string {
|
||||
func v2DescriptorDiff(oldValue, newValue *V2Descriptor, URL string) string {
|
||||
oldData, err := json.MarshalIndent(oldValue, "", " ")
|
||||
runtimex.PanicOnError(err, "json.MarshalIndent failed unexpectedly")
|
||||
newData, err := json.MarshalIndent(newValue, "", " ")
|
||||
|
@ -253,5 +253,5 @@ func v2MeasureHTTPS(ctx context.Context, config *LinkConfig, URL string) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
return v2MeasureDescriptor(ctx, config, newValue) // handles nil newValue gracefully
|
||||
return V2MeasureDescriptor(ctx, config, newValue) // handles nil newValue gracefully
|
||||
}
|
||||
|
|
|
@ -17,11 +17,11 @@ import (
|
|||
|
||||
func TestOONIRunV2LinkCommonCase(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
descriptor := &v2Descriptor{
|
||||
descriptor := &V2Descriptor{
|
||||
Name: "",
|
||||
Description: "",
|
||||
Author: "",
|
||||
Nettests: []v2Nettest{{
|
||||
Nettests: []V2Nettest{{
|
||||
Inputs: []string{},
|
||||
Options: map[string]any{
|
||||
"SleepTime": int64(10 * time.Millisecond),
|
||||
|
@ -56,11 +56,11 @@ func TestOONIRunV2LinkCommonCase(t *testing.T) {
|
|||
|
||||
func TestOONIRunV2LinkCannotUpdateCache(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
descriptor := &v2Descriptor{
|
||||
descriptor := &V2Descriptor{
|
||||
Name: "",
|
||||
Description: "",
|
||||
Author: "",
|
||||
Nettests: []v2Nettest{{
|
||||
Nettests: []V2Nettest{{
|
||||
Inputs: []string{},
|
||||
Options: map[string]any{
|
||||
"SleepTime": int64(10 * time.Millisecond),
|
||||
|
@ -104,11 +104,11 @@ func TestOONIRunV2LinkCannotUpdateCache(t *testing.T) {
|
|||
|
||||
func TestOONIRunV2LinkWithoutAcceptChanges(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
descriptor := &v2Descriptor{
|
||||
descriptor := &V2Descriptor{
|
||||
Name: "",
|
||||
Description: "",
|
||||
Author: "",
|
||||
Nettests: []v2Nettest{{
|
||||
Nettests: []V2Nettest{{
|
||||
Inputs: []string{},
|
||||
Options: map[string]any{
|
||||
"SleepTime": int64(10 * time.Millisecond),
|
||||
|
@ -170,11 +170,11 @@ func TestOONIRunV2LinkNilDescriptor(t *testing.T) {
|
|||
func TestOONIRunV2LinkEmptyTestName(t *testing.T) {
|
||||
emptyTestNamesPrev := v2CountEmptyNettestNames.Load()
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
descriptor := &v2Descriptor{
|
||||
descriptor := &V2Descriptor{
|
||||
Name: "",
|
||||
Description: "",
|
||||
Author: "",
|
||||
Nettests: []v2Nettest{{
|
||||
Nettests: []V2Nettest{{
|
||||
Inputs: []string{},
|
||||
Options: map[string]any{
|
||||
"SleepTime": int64(10 * time.Millisecond),
|
||||
|
@ -214,7 +214,7 @@ func TestV2MeasureDescriptor(t *testing.T) {
|
|||
t.Run("with nil descriptor", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
config := &LinkConfig{}
|
||||
err := v2MeasureDescriptor(ctx, config, nil)
|
||||
err := V2MeasureDescriptor(ctx, config, nil)
|
||||
if !errors.Is(err, ErrNilDescriptor) {
|
||||
t.Fatal("unexpected err", err)
|
||||
}
|
||||
|
@ -269,17 +269,17 @@ func TestV2MeasureDescriptor(t *testing.T) {
|
|||
ReportFile: "",
|
||||
Session: sess,
|
||||
}
|
||||
descr := &v2Descriptor{
|
||||
descr := &V2Descriptor{
|
||||
Name: "",
|
||||
Description: "",
|
||||
Author: "",
|
||||
Nettests: []v2Nettest{{
|
||||
Nettests: []V2Nettest{{
|
||||
Inputs: []string{},
|
||||
Options: map[string]any{},
|
||||
TestName: "example",
|
||||
}},
|
||||
}
|
||||
err := v2MeasureDescriptor(ctx, config, descr)
|
||||
err := V2MeasureDescriptor(ctx, config, descr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user