github.com/weaviate/weaviate@v1.24.6/usecases/telemetry/telemetry_test.go (about)

     1  //                           _       _
     2  // __      _____  __ ___   ___  __ _| |_ ___
     3  // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
     4  //  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
     5  //   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
     6  //
     7  //  Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
     8  //
     9  //  CONTACT: hello@weaviate.io
    10  //
    11  
    12  package telemetry
    13  
    14  import (
    15  	"context"
    16  	"encoding/base64"
    17  	"encoding/json"
    18  	"errors"
    19  	"fmt"
    20  	"io"
    21  	"net/http"
    22  	"net/http/httptest"
    23  	"runtime"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/sirupsen/logrus/hooks/test"
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  	"github.com/weaviate/weaviate/entities/models"
    31  	"github.com/weaviate/weaviate/entities/verbosity"
    32  	"github.com/weaviate/weaviate/usecases/config"
    33  )
    34  
    35  func TestTelemetry_BuildPayload(t *testing.T) {
    36  	t.Run("happy path", func(t *testing.T) {
    37  		t.Run("on init", func(t *testing.T) {
    38  			tel, sg, mp := newTestTelemeter()
    39  			sg.On("LocalNodeStatus", context.Background(), "", verbosity.OutputVerbose).Return(
    40  				&models.NodeStatus{
    41  					Stats: &models.NodeStats{
    42  						ObjectCount: 100,
    43  					},
    44  				})
    45  			mp.On("GetMeta").Return(
    46  				map[string]interface{}{
    47  					"module-1": nil,
    48  					"module-2": nil,
    49  				})
    50  			payload, err := tel.buildPayload(context.Background(), PayloadType.Init)
    51  			assert.Nil(t, err)
    52  			assert.Equal(t, tel.machineID, payload.MachineID)
    53  			assert.Equal(t, PayloadType.Init, payload.Type)
    54  			assert.Equal(t, config.ServerVersion, payload.Version)
    55  			assert.Equal(t, "module-1,module-2", payload.Modules)
    56  			assert.Equal(t, int64(0), payload.NumObjects)
    57  			assert.Equal(t, runtime.GOOS, payload.OS)
    58  			assert.Equal(t, runtime.GOARCH, payload.Arch)
    59  		})
    60  
    61  		t.Run("on update", func(t *testing.T) {
    62  			tel, sg, mp := newTestTelemeter()
    63  			sg.On("LocalNodeStatus", context.Background(), "", verbosity.OutputVerbose).Return(
    64  				&models.NodeStatus{
    65  					Stats: &models.NodeStats{
    66  						ObjectCount: 1000,
    67  					},
    68  				})
    69  			mp.On("GetMeta").Return(map[string]interface{}{}, nil)
    70  			payload, err := tel.buildPayload(context.Background(), PayloadType.Update)
    71  			assert.Nil(t, err)
    72  			assert.Equal(t, tel.machineID, payload.MachineID)
    73  			assert.Equal(t, PayloadType.Update, payload.Type)
    74  			assert.Equal(t, config.ServerVersion, payload.Version)
    75  			assert.Equal(t, "", payload.Modules)
    76  			assert.Equal(t, int64(1000), payload.NumObjects)
    77  			assert.Equal(t, runtime.GOOS, payload.OS)
    78  			assert.Equal(t, runtime.GOARCH, payload.Arch)
    79  		})
    80  
    81  		t.Run("on terminate", func(t *testing.T) {
    82  			tel, sg, mp := newTestTelemeter()
    83  			sg.On("LocalNodeStatus", context.Background(), "", verbosity.OutputVerbose).Return(
    84  				&models.NodeStatus{
    85  					Stats: &models.NodeStats{
    86  						ObjectCount: 300_000_000_000,
    87  					},
    88  				})
    89  			mp.On("GetMeta").Return(nil, nil)
    90  			payload, err := tel.buildPayload(context.Background(), PayloadType.Terminate)
    91  			assert.Nil(t, err)
    92  			assert.Equal(t, tel.machineID, payload.MachineID)
    93  			assert.Equal(t, PayloadType.Terminate, payload.Type)
    94  			assert.Equal(t, config.ServerVersion, payload.Version)
    95  			assert.Equal(t, "", payload.Modules)
    96  			assert.Equal(t, int64(300_000_000_000), payload.NumObjects)
    97  			assert.Equal(t, runtime.GOOS, payload.OS)
    98  			assert.Equal(t, runtime.GOARCH, payload.Arch)
    99  		})
   100  	})
   101  
   102  	t.Run("failure path", func(t *testing.T) {
   103  		t.Run("fail to get enabled modules", func(t *testing.T) {
   104  			tel, sg, mp := newTestTelemeter()
   105  			sg.On("LocalNodeStatus", context.Background(), "", verbosity.OutputVerbose).Return(
   106  				&models.NodeStatus{Stats: &models.NodeStats{ObjectCount: 10}})
   107  			mp.On("GetMeta").Return(nil, errors.New("FAILURE"))
   108  			payload, err := tel.buildPayload(context.Background(), PayloadType.Terminate)
   109  			assert.Nil(t, payload)
   110  			assert.NotNil(t, err)
   111  			assert.Contains(t, err.Error(), "get enabled modules")
   112  		})
   113  
   114  		t.Run("fail to get node status", func(t *testing.T) {
   115  			tel, sg, mp := newTestTelemeter()
   116  			sg.On("LocalNodeStatus", context.Background(), "", verbosity.OutputVerbose).Return(nil)
   117  			mp.On("GetMeta").Return(
   118  				map[string]interface{}{
   119  					"module-1": nil,
   120  					"module-2": nil,
   121  				})
   122  			payload, err := tel.buildPayload(context.Background(), PayloadType.Terminate)
   123  			assert.Nil(t, payload)
   124  			assert.NotNil(t, err)
   125  			assert.Contains(t, err.Error(), "get object count")
   126  		})
   127  
   128  		t.Run("fail to get node status stats", func(t *testing.T) {
   129  			tel, sg, mp := newTestTelemeter()
   130  			sg.On("LocalNodeStatus", context.Background(), "", verbosity.OutputVerbose).Return(&models.NodeStatus{})
   131  			mp.On("GetMeta").Return(
   132  				map[string]interface{}{
   133  					"module-1": nil,
   134  					"module-2": nil,
   135  				})
   136  			payload, err := tel.buildPayload(context.Background(), PayloadType.Terminate)
   137  			assert.Nil(t, payload)
   138  			assert.NotNil(t, err)
   139  			assert.Contains(t, err.Error(), "get object count")
   140  		})
   141  	})
   142  }
   143  
   144  func TestTelemetry_WithConsumer(t *testing.T) {
   145  	config.ServerVersion = "X.X.X"
   146  	server := httptest.NewServer(&testConsumer{t})
   147  	defer server.Close()
   148  
   149  	consumerURL := fmt.Sprintf("%s/weaviate-telemetry", server.URL)
   150  	opts := []telemetryOpt{
   151  		withConsumerURL(consumerURL),
   152  		withPushInterval(100 * time.Millisecond),
   153  	}
   154  	tel, sg, mp := newTestTelemeter(opts...)
   155  
   156  	sg.On("LocalNodeStatus", context.Background(), "", verbosity.OutputVerbose).Return(
   157  		&models.NodeStatus{
   158  			Stats: &models.NodeStats{
   159  				ObjectCount: 100,
   160  			},
   161  		})
   162  	mp.On("GetMeta").Return(
   163  		map[string]interface{}{
   164  			"module-1": nil,
   165  			"module-2": nil,
   166  		})
   167  
   168  	err := tel.Start(context.Background())
   169  	require.Nil(t, err)
   170  
   171  	ticker := time.NewTicker(100 * time.Millisecond)
   172  	start := time.Now()
   173  	wait := make(chan struct{})
   174  	go func() {
   175  		for range ticker.C {
   176  			if time.Since(start) > time.Second {
   177  				err = tel.Stop(context.Background())
   178  				assert.Nil(t, err)
   179  				wait <- struct{}{}
   180  			}
   181  		}
   182  	}()
   183  	<-wait
   184  }
   185  
   186  type telemetryOpt func(*Telemeter)
   187  
   188  func withConsumerURL(url string) telemetryOpt {
   189  	encoded := base64.StdEncoding.EncodeToString([]byte(url))
   190  	return func(tel *Telemeter) {
   191  		tel.consumer = encoded
   192  	}
   193  }
   194  
   195  func withPushInterval(interval time.Duration) telemetryOpt {
   196  	return func(tel *Telemeter) {
   197  		tel.pushInterval = interval
   198  	}
   199  }
   200  
   201  func newTestTelemeter(opts ...telemetryOpt,
   202  ) (*Telemeter, *fakeNodesStatusGetter, *fakeModulesProvider,
   203  ) {
   204  	sg := &fakeNodesStatusGetter{}
   205  	mp := &fakeModulesProvider{}
   206  	logger, _ := test.NewNullLogger()
   207  	tel := New(sg, mp, logger)
   208  	for _, opt := range opts {
   209  		opt(tel)
   210  	}
   211  	return tel, sg, mp
   212  }
   213  
   214  type testConsumer struct {
   215  	t *testing.T
   216  }
   217  
   218  func (h *testConsumer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   219  	assert.Equal(h.t, "/weaviate-telemetry", r.URL.String())
   220  	assert.Equal(h.t, http.MethodPost, r.Method)
   221  	b, err := io.ReadAll(r.Body)
   222  	defer r.Body.Close()
   223  	require.Nil(h.t, err)
   224  
   225  	var payload Payload
   226  	err = json.Unmarshal(b, &payload)
   227  	require.Nil(h.t, err)
   228  
   229  	assert.NotEmpty(h.t, payload.MachineID)
   230  	assert.Contains(h.t, []string{
   231  		PayloadType.Init,
   232  		PayloadType.Update,
   233  		PayloadType.Terminate,
   234  	}, payload.Type)
   235  	assert.Equal(h.t, config.ServerVersion, payload.Version)
   236  	assert.NotEmpty(h.t, payload.Modules)
   237  	if payload.Type == PayloadType.Init {
   238  		assert.Zero(h.t, payload.NumObjects)
   239  	} else {
   240  		assert.NotZero(h.t, payload.NumObjects)
   241  	}
   242  	assert.Equal(h.t, runtime.GOOS, payload.OS)
   243  	assert.Equal(h.t, runtime.GOARCH, payload.Arch)
   244  
   245  	h.t.Logf("request body: %s", string(b))
   246  	w.WriteHeader(http.StatusOK)
   247  }