Skip to content

Commit

Permalink
probeservices, oonimkall: implement checkin api (#1158)
Browse files Browse the repository at this point in the history
* First draft of checkin API

* Correcting names

* Tests

* Update probeservices/checkin.go

Co-authored-by: Simone Basso <[email protected]>

* Update model/checkininfo.go

Co-authored-by: Simone Basso <[email protected]>

* Update probeservices/checkin_test.go

Co-authored-by: Simone Basso <[email protected]>

* Update probeservices/checkin.go

Co-authored-by: Simone Basso <[email protected]>

* check for result.WebConnectivity == nil

* adding ReportID

* Adding parameters

* Fix parameters

* comment

* Start implementing the wrapper

* Update session.go

* Adding integration tests

* fix: enforce GC and sleep for more time

* format

* fix(session.go): use the correct probe ASN

* fix: message format and testing

* fix(oonimkall): add one more test case

Co-authored-by: Simone Basso <[email protected]>
  • Loading branch information
lorenzoPrimi and bassosimone authored Jan 29, 2021
1 parent 87c73c8 commit 36624ab
Show file tree
Hide file tree
Showing 8 changed files with 421 additions and 1 deletion.
5 changes: 5 additions & 0 deletions geolocate/geolocate.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ type Results struct {
ResolverNetworkName string
}

// ASNString returns the ASN as a string
func (r *Results) ASNString() string {
return fmt.Sprintf("AS%d", r.ASN)
}

type probeIPLookupper interface {
LookupProbeIP(ctx context.Context) (addr string, err error)
}
Expand Down
19 changes: 19 additions & 0 deletions model/checkinconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package model

// CheckInConfigWebConnectivity is the configuration for the WebConnectivity test
type CheckInConfigWebConnectivity struct {
CategoryCodes []string `json:"category_codes"` // CategoryCodes is an array of category codes
}

// CheckInConfig contains configuration for calling the checkin API.
type CheckInConfig struct {
Charging bool `json:"charging"` // Charging indicate if the phone is actually charging
OnWiFi bool `json:"on_wifi"` // OnWiFi indicate if the phone is actually connected to a WiFi network
Platform string `json:"platform"` // Platform of the probe
ProbeASN string `json:"probe_asn"` // ProbeASN is the probe country code
ProbeCC string `json:"probe_cc"` // ProbeCC is the probe country code
RunType string `json:"run_type"` // RunType
SoftwareName string `json:"software_name"` // SoftwareName of the probe
SoftwareVersion string `json:"software_version"` // SoftwareVersion of the probe
WebConnectivity CheckInConfigWebConnectivity `json:"web_connectivity"` // WebConnectivity class contain an array of categories
}
12 changes: 12 additions & 0 deletions model/checkininfo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package model

// CheckInInfoWebConnectivity contains the array of URLs returned by the checkin API
type CheckInInfoWebConnectivity struct {
ReportID string `json:"report_id"`
URLs []URLInfo `json:"urls"`
}

// CheckInInfo contains the return test objects from the checkin API
type CheckInInfo struct {
WebConnectivity *CheckInInfoWebConnectivity `json:"web_connectivity"`
}
120 changes: 120 additions & 0 deletions oonimkall/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package oonimkall
import (
"context"
"encoding/json"
"errors"
"fmt"
"runtime"
"sync"
Expand Down Expand Up @@ -96,6 +97,10 @@ type Session struct {
mtx sync.Mutex
submitter *probeservices.Submitter
sessp *engine.Session

// Hooks for testing (should not appear in Java/ObjC)
TestingCheckInBeforeNewProbeServicesClient func(ctx *Context)
TestingCheckInBeforeCheckIn func(ctx *Context)
}

// NewSession creates a new session. You should use a session for running
Expand Down Expand Up @@ -255,3 +260,118 @@ func (sess *Session) Submit(ctx *Context, measurement string) (*SubmitMeasuremen
UpdatedReportID: mm.ReportID,
}, nil
}

// CheckInConfigWebConnectivity is the configuration for the WebConnectivity test
type CheckInConfigWebConnectivity struct {
CategoryCodes []string // CategoryCodes is an array of category codes
}

// Add a category code to the array in CheckInConfigWebConnectivity
func (ckw *CheckInConfigWebConnectivity) Add(cat string) {
ckw.CategoryCodes = append(ckw.CategoryCodes, cat)
}

func (ckw *CheckInConfigWebConnectivity) toModel() model.CheckInConfigWebConnectivity {
return model.CheckInConfigWebConnectivity{
CategoryCodes: ckw.CategoryCodes,
}
}

// CheckInConfig contains configuration for calling the checkin API.
type CheckInConfig struct {
Charging bool // Charging indicate if the phone is actually charging
OnWiFi bool // OnWiFi indicate if the phone is actually connected to a WiFi network
Platform string // Platform of the probe
RunType string // RunType
SoftwareName string // SoftwareName of the probe
SoftwareVersion string // SoftwareVersion of the probe
WebConnectivity *CheckInConfigWebConnectivity // WebConnectivity class contain an array of categories
}

// CheckInInfoWebConnectivity contains the array of URLs returned by the checkin API
type CheckInInfoWebConnectivity struct {
ReportID string
URLs []model.URLInfo
}

// URLInfo contains info on a test lists URL
type URLInfo struct {
CategoryCode string
CountryCode string
URL string
}

// Size returns the number of URLs.
func (ckw *CheckInInfoWebConnectivity) Size() int64 {
return int64(len(ckw.URLs))
}

// At gets the URLInfo at position idx from CheckInInfoWebConnectivity.URLs
func (ckw *CheckInInfoWebConnectivity) At(idx int64) *URLInfo {
if idx < 0 || int(idx) >= len(ckw.URLs) {
return nil
}
w := ckw.URLs[idx]
return &URLInfo{
CategoryCode: w.CategoryCode,
CountryCode: w.CountryCode,
URL: w.URL,
}
}

func newCheckInInfoWebConnectivity(ckw *model.CheckInInfoWebConnectivity) *CheckInInfoWebConnectivity {
if ckw == nil {
return nil
}
out := new(CheckInInfoWebConnectivity)
out.ReportID = ckw.ReportID
out.URLs = ckw.URLs
return out
}

// CheckInInfo contains the return test objects from the checkin API
type CheckInInfo struct {
WebConnectivity *CheckInInfoWebConnectivity
}

// CheckIn function is called by probes asking if there are tests to be run
// The config argument contains the mandatory settings.
// Returns the list of tests to run and the URLs, on success, or an explanatory error, in case of failure.
func (sess *Session) CheckIn(ctx *Context, config *CheckInConfig) (*CheckInInfo, error) {
sess.mtx.Lock()
defer sess.mtx.Unlock()
if config.WebConnectivity == nil {
return nil, errors.New("oonimkall: missing webconnectivity config")
}
info, err := sess.sessp.LookupLocationContext(ctx.ctx)
if err != nil {
return nil, err
}
if sess.TestingCheckInBeforeNewProbeServicesClient != nil {
sess.TestingCheckInBeforeNewProbeServicesClient(ctx)
}
psc, err := sess.sessp.NewProbeServicesClient(ctx.ctx)
if err != nil {
return nil, err
}
if sess.TestingCheckInBeforeCheckIn != nil {
sess.TestingCheckInBeforeCheckIn(ctx)
}
cfg := model.CheckInConfig{
Charging: config.Charging,
OnWiFi: config.OnWiFi,
Platform: config.Platform,
ProbeASN: info.ASNString(),
ProbeCC: info.CountryCode,
RunType: config.RunType,
SoftwareVersion: config.SoftwareVersion,
WebConnectivity: config.WebConnectivity.toModel(),
}
result, err := psc.CheckIn(ctx.ctx, cfg)
if err != nil {
return nil, err
}
return &CheckInInfo{
WebConnectivity: newCheckInInfoWebConnectivity(result.WebConnectivity),
}, nil
}
161 changes: 160 additions & 1 deletion oonimkall/session_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import (
"fmt"
"io/ioutil"
"os"
"runtime"
"strings"
"testing"
"time"

engine "github.com/ooni/probe-engine"
"github.com/ooni/probe-engine/geolocate"
"github.com/ooni/probe-engine/model"
"github.com/ooni/probe-engine/oonimkall"
Expand Down Expand Up @@ -261,6 +263,162 @@ func TestSubmitCancelContextAfterFirstSubmission(t *testing.T) {
}
}

func TestCheckInSuccess(t *testing.T) {
sess, err := NewSession()
if err != nil {
t.Fatal(err)
}
ctx := sess.NewContext()
config := oonimkall.CheckInConfig{
Charging: true,
OnWiFi: true,
Platform: "android",
RunType: "timed",
SoftwareName: "ooniprobe-android",
SoftwareVersion: "2.7.1",
WebConnectivity: &oonimkall.CheckInConfigWebConnectivity{},
}
config.WebConnectivity.Add("NEWS")
config.WebConnectivity.Add("CULTR")
result, err := sess.CheckIn(ctx, &config)
if err != nil {
t.Fatalf("unexpected error: %+v", err)
}
if result == nil || result.WebConnectivity == nil {
t.Fatal("got nil result or WebConnectivity")
}
if len(result.WebConnectivity.URLs) < 1 {
t.Fatal("unexpected number of URLs")
}
if result.WebConnectivity.ReportID == "" {
t.Fatal("got empty report ID")
}
siz := result.WebConnectivity.Size()
if siz <= 0 {
t.Fatal("unexpected number of URLs")
}
for idx := int64(0); idx < siz; idx++ {
entry := result.WebConnectivity.At(idx)
if entry.CategoryCode != "NEWS" && entry.CategoryCode != "CULTR" {
t.Fatalf("unexpected category code: %+v", entry)
}
}
if result.WebConnectivity.At(-1) != nil {
t.Fatal("expected nil here")
}
if result.WebConnectivity.At(siz) != nil {
t.Fatal("expected nil here")
}
}

func TestCheckInLookupLocationFailure(t *testing.T) {
sess, err := NewSession()
if err != nil {
t.Fatal(err)
}
ctx := sess.NewContext()
config := oonimkall.CheckInConfig{
Charging: true,
OnWiFi: true,
Platform: "android",
RunType: "timed",
SoftwareName: "ooniprobe-android",
SoftwareVersion: "2.7.1",
WebConnectivity: &oonimkall.CheckInConfigWebConnectivity{},
}
config.WebConnectivity.Add("NEWS")
config.WebConnectivity.Add("CULTR")
ctx.Cancel() // immediate failure
result, err := sess.CheckIn(ctx, &config)
if !errors.Is(err, geolocate.ErrAllIPLookuppersFailed) {
t.Fatalf("not the error we expected: %+v", err)
}
if result != nil {
t.Fatal("expected nil result here")
}
}

func TestCheckInNewProbeServicesFailure(t *testing.T) {
sess, err := NewSession()
if err != nil {
t.Fatal(err)
}
sess.TestingCheckInBeforeNewProbeServicesClient = func(ctx *oonimkall.Context) {
ctx.Cancel() // cancel execution
}
ctx := sess.NewContext()
config := oonimkall.CheckInConfig{
Charging: true,
OnWiFi: true,
Platform: "android",
RunType: "timed",
SoftwareName: "ooniprobe-android",
SoftwareVersion: "2.7.1",
WebConnectivity: &oonimkall.CheckInConfigWebConnectivity{},
}
config.WebConnectivity.Add("NEWS")
config.WebConnectivity.Add("CULTR")
result, err := sess.CheckIn(ctx, &config)
if !errors.Is(err, engine.ErrAllProbeServicesFailed) {
t.Fatalf("not the error we expected: %+v", err)
}
if result != nil {
t.Fatal("expected nil result here")
}
}

func TestCheckInCheckInFailure(t *testing.T) {
sess, err := NewSession()
if err != nil {
t.Fatal(err)
}
sess.TestingCheckInBeforeCheckIn = func(ctx *oonimkall.Context) {
ctx.Cancel() // cancel execution
}
ctx := sess.NewContext()
config := oonimkall.CheckInConfig{
Charging: true,
OnWiFi: true,
Platform: "android",
RunType: "timed",
SoftwareName: "ooniprobe-android",
SoftwareVersion: "2.7.1",
WebConnectivity: &oonimkall.CheckInConfigWebConnectivity{},
}
config.WebConnectivity.Add("NEWS")
config.WebConnectivity.Add("CULTR")
result, err := sess.CheckIn(ctx, &config)
if !errors.Is(err, context.Canceled) {
t.Fatalf("not the error we expected: %+v", err)
}
if result != nil {
t.Fatal("expected nil result here")
}
}

func TestCheckInNoParams(t *testing.T) {
sess, err := NewSession()
if err != nil {
t.Fatal(err)
}
ctx := sess.NewContext()
config := oonimkall.CheckInConfig{
Charging: true,
OnWiFi: true,
Platform: "android",
RunType: "timed",
SoftwareName: "ooniprobe-android",
SoftwareVersion: "2.7.1",
}
result, err := sess.CheckIn(ctx, &config)
if err == nil || err.Error() != "oonimkall: missing webconnectivity config" {
t.Fatalf("not the error we expected: %+v", err)
}
if result != nil {
t.Fatal("unexpected not nil result here")
}
}

func TestMain(m *testing.M) {
// Here we're basically testing whether eventually the finalizers
// will run and the number of active sessions and cancels will become
Expand All @@ -270,12 +428,13 @@ func TestMain(m *testing.M) {
os.Exit(exitcode)
}
for {
runtime.GC()
m, n := oonimkall.ActiveContexts.Load(), oonimkall.ActiveSessions.Load()
fmt.Printf("./oonimkall: ActiveContexts: %d; ActiveSessions: %d\n", m, n)
if m == 0 && n == 0 {
break
}
time.Sleep(1)
time.Sleep(1 * time.Second)
}
os.Exit(0)
}
10 changes: 10 additions & 0 deletions oonimkall/session_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package oonimkall

import "testing"

func TestNewCheckInInfoWebConnectivityNilPointer(t *testing.T) {
out := newCheckInInfoWebConnectivity(nil)
if out != nil {
t.Fatal("expected nil pointer")
}
}
Loading

0 comments on commit 36624ab

Please sign in to comment.