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 }