github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/pubsub/subscriber/subscriber_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package subscriber 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/json" 23 "fmt" 24 "net/http" 25 "net/http/httptest" 26 "reflect" 27 "strings" 28 "testing" 29 "time" 30 31 "cloud.google.com/go/pubsub" 32 "github.com/sirupsen/logrus" 33 "k8s.io/test-infra/prow/config" 34 "k8s.io/test-infra/prow/kube" 35 "k8s.io/test-infra/prow/pubsub/reporter" 36 ) 37 38 type kubeTestClient struct { 39 pj *kube.ProwJob 40 } 41 42 type pubSubTestClient struct { 43 messageChan chan fakeMessage 44 } 45 46 type fakeSubscription struct { 47 name string 48 messageChan chan fakeMessage 49 } 50 51 type fakeMessage pubsub.Message 52 53 func (m *fakeMessage) getAttributes() map[string]string { 54 return m.Attributes 55 } 56 57 func (m *fakeMessage) getPayload() []byte { 58 return m.Data 59 } 60 61 func (m *fakeMessage) getID() string { 62 return m.ID 63 } 64 65 func (m *fakeMessage) ack() {} 66 func (m *fakeMessage) nack() {} 67 68 func (s *fakeSubscription) string() string { 69 return s.name 70 } 71 72 func (s *fakeSubscription) receive(ctx context.Context, f func(context.Context, messageInterface)) error { 73 derivedCtx, cancel := context.WithCancel(ctx) 74 msg := <-s.messageChan 75 go func() { 76 f(derivedCtx, &msg) 77 cancel() 78 }() 79 for { 80 select { 81 case <-ctx.Done(): 82 return ctx.Err() 83 case <-derivedCtx.Done(): 84 return fmt.Errorf("message processed") 85 } 86 } 87 } 88 89 func (c *pubSubTestClient) new(ctx context.Context, project string) (pubsubClientInterface, error) { 90 return c, nil 91 } 92 93 func (c *pubSubTestClient) subscription(id string) subscriptionInterface { 94 return &fakeSubscription{name: id, messageChan: c.messageChan} 95 } 96 97 func (c *kubeTestClient) CreateProwJob(job *kube.ProwJob) (*kube.ProwJob, error) { 98 c.pj = job 99 return job, nil 100 } 101 102 func TestPeriodicProwJobEvent_ToFromMessage(t *testing.T) { 103 pe := PeriodicProwJobEvent{ 104 Annotations: map[string]string{ 105 reporter.PubSubProjectLabel: "project", 106 reporter.PubSubTopicLabel: "topic", 107 reporter.PubSubRunIDLabel: "asdfasdfn", 108 }, 109 Envs: map[string]string{ 110 "ENV1": "test", 111 "ENV2": "test2", 112 }, 113 Name: "ProwJobName", 114 Type: periodicProwJobEvent, 115 } 116 m, err := pe.ToMessage() 117 if err != nil { 118 t.Error(err) 119 } 120 if m.Attributes[prowEventType] != periodicProwJobEvent { 121 t.Errorf("%s should be %s found %s instead", prowEventType, periodicProwJobEvent, m.Attributes[prowEventType]) 122 } 123 var newPe PeriodicProwJobEvent 124 if err = newPe.FromPayload(m.Data); err != nil { 125 t.Error(err) 126 } 127 if !reflect.DeepEqual(pe, newPe) { 128 t.Error("JSON encoding failed. ") 129 } 130 } 131 132 func TestHandleMessage(t *testing.T) { 133 for _, tc := range []struct { 134 name string 135 msg *pubSubMessage 136 pe *PeriodicProwJobEvent 137 s string 138 config *config.Config 139 err string 140 labels []string 141 }{ 142 { 143 name: "PeriodicJobNoPubsub", 144 pe: &PeriodicProwJobEvent{ 145 Name: "test", 146 }, 147 config: &config.Config{ 148 JobConfig: config.JobConfig{ 149 Periodics: []config.Periodic{ 150 { 151 JobBase: config.JobBase{ 152 Name: "test", 153 }, 154 }, 155 }, 156 }, 157 }, 158 }, 159 { 160 name: "UnknownEventType", 161 msg: &pubSubMessage{ 162 Message: pubsub.Message{ 163 Attributes: map[string]string{ 164 prowEventType: "unsupported", 165 }, 166 }, 167 }, 168 config: &config.Config{}, 169 err: "unsupported event type", 170 labels: []string{reporter.PubSubTopicLabel, reporter.PubSubRunIDLabel, reporter.PubSubProjectLabel}, 171 }, 172 { 173 name: "NoEventType", 174 msg: &pubSubMessage{ 175 Message: pubsub.Message{}, 176 }, 177 config: &config.Config{}, 178 err: "unable to find prow.k8s.io/pubsub.EventType from the attributes", 179 labels: []string{reporter.PubSubTopicLabel, reporter.PubSubRunIDLabel, reporter.PubSubProjectLabel}, 180 }, 181 } { 182 t.Run(tc.name, func(t1 *testing.T) { 183 kc := &kubeTestClient{} 184 ca := &config.Agent{} 185 ca.Set(tc.config) 186 s := Subscriber{ 187 Metrics: NewMetrics(), 188 KubeClient: kc, 189 ConfigAgent: ca, 190 } 191 if tc.pe != nil { 192 m, err := tc.pe.ToMessage() 193 if err != nil { 194 t.Error(err) 195 } 196 m.ID = "id" 197 tc.msg = &pubSubMessage{*m} 198 } 199 if err := s.handleMessage(tc.msg, tc.s); err != nil { 200 if err.Error() != tc.err { 201 t1.Errorf("Expected error %v got %v", tc.err, err.Error()) 202 } else if tc.err == "" { 203 if kc.pj == nil { 204 t.Errorf("Prow job not created") 205 } 206 for _, k := range tc.labels { 207 if _, ok := kc.pj.Labels[k]; !ok { 208 t.Errorf("label %s is missing", k) 209 } 210 } 211 } 212 } 213 }) 214 } 215 } 216 217 func TestHandlePeriodicJob(t *testing.T) { 218 for _, tc := range []struct { 219 name string 220 pe *PeriodicProwJobEvent 221 s string 222 config *config.Config 223 err string 224 }{ 225 { 226 name: "PeriodicJobNoPubsub", 227 pe: &PeriodicProwJobEvent{ 228 Name: "test", 229 }, 230 config: &config.Config{ 231 JobConfig: config.JobConfig{ 232 Periodics: []config.Periodic{ 233 { 234 JobBase: config.JobBase{ 235 Name: "test", 236 }, 237 }, 238 }, 239 }, 240 }, 241 }, 242 { 243 name: "PeriodicJobPubsubSet", 244 pe: &PeriodicProwJobEvent{ 245 Name: "test", 246 Annotations: map[string]string{ 247 reporter.PubSubProjectLabel: "project", 248 reporter.PubSubRunIDLabel: "runid", 249 reporter.PubSubTopicLabel: "topic", 250 }, 251 }, 252 config: &config.Config{ 253 JobConfig: config.JobConfig{ 254 Periodics: []config.Periodic{ 255 { 256 JobBase: config.JobBase{ 257 Name: "test", 258 }, 259 }, 260 }, 261 }, 262 }, 263 }, 264 { 265 name: "JobNotFound", 266 pe: &PeriodicProwJobEvent{ 267 Name: "test", 268 }, 269 config: &config.Config{}, 270 err: "failed to find associated periodic job test", 271 }, 272 } { 273 t.Run(tc.name, func(t1 *testing.T) { 274 kc := &kubeTestClient{} 275 ca := &config.Agent{} 276 ca.Set(tc.config) 277 s := Subscriber{ 278 Metrics: NewMetrics(), 279 KubeClient: kc, 280 ConfigAgent: ca, 281 } 282 m, err := tc.pe.ToMessage() 283 if err != nil { 284 t.Error(err) 285 } 286 m.ID = "id" 287 if err := s.handlePeriodicJob(logrus.NewEntry(logrus.New()), &pubSubMessage{*m}, tc.s); err != nil { 288 if err.Error() != tc.err { 289 t1.Errorf("Expected error %v got %v", tc.err, err.Error()) 290 } else if tc.err == "" { 291 if kc.pj == nil { 292 t.Errorf("Prow job not created") 293 } 294 } 295 } 296 }) 297 } 298 } 299 300 func TestPushServer_ServeHTTP(t *testing.T) { 301 kc := &kubeTestClient{} 302 pushServer := PushServer{ 303 Subscriber: &Subscriber{ 304 ConfigAgent: &config.Agent{}, 305 Metrics: NewMetrics(), 306 KubeClient: kc, 307 }, 308 } 309 for _, tc := range []struct { 310 name string 311 url string 312 secret string 313 pushRequest interface{} 314 pe *PeriodicProwJobEvent 315 expectedCode int 316 }{ 317 { 318 name: "WrongToken", 319 secret: "wrongToken", 320 url: "https://prow.k8s.io/push", 321 pushRequest: pushRequest{ 322 Message: message{ 323 ID: "runid", 324 }, 325 }, 326 expectedCode: http.StatusForbidden, 327 }, 328 { 329 name: "NoToken", 330 url: "https://prow.k8s.io/push", 331 pushRequest: pushRequest{ 332 Message: message{ 333 ID: "runid", 334 }, 335 }, 336 expectedCode: http.StatusNotModified, 337 }, 338 { 339 name: "RightToken", 340 secret: "secret", 341 url: "https://prow.k8s.io/push?token=secret", 342 pushRequest: pushRequest{ 343 Message: message{ 344 ID: "runid", 345 }, 346 }, 347 expectedCode: http.StatusNotModified, 348 }, 349 { 350 name: "InvalidPushRequest", 351 secret: "secret", 352 url: "https://prow.k8s.io/push?token=secret", 353 pushRequest: "invalid", 354 expectedCode: http.StatusBadRequest, 355 }, 356 { 357 name: "SuccessToken", 358 secret: "secret", 359 url: "https://prow.k8s.io/push?token=secret", 360 pushRequest: pushRequest{}, 361 pe: &PeriodicProwJobEvent{ 362 Name: "test", 363 }, 364 expectedCode: http.StatusOK, 365 }, 366 { 367 name: "SuccessNoToken", 368 url: "https://prow.k8s.io/push", 369 pushRequest: pushRequest{}, 370 pe: &PeriodicProwJobEvent{ 371 Name: "test", 372 }, 373 expectedCode: http.StatusOK, 374 }, 375 } { 376 t.Run(tc.name, func(t1 *testing.T) { 377 c := &config.Config{ 378 JobConfig: config.JobConfig{ 379 Periodics: []config.Periodic{ 380 { 381 JobBase: config.JobBase{ 382 Name: "test", 383 }, 384 }, 385 }, 386 }, 387 } 388 pushServer.Subscriber.ConfigAgent.Set(c) 389 pushServer.TokenGenerator = func() []byte { return []byte(tc.secret) } 390 kc.pj = nil 391 392 body := new(bytes.Buffer) 393 394 if tc.pe != nil { 395 msg, err := tc.pe.ToMessage() 396 if err != nil { 397 t.Error(err) 398 } 399 tc.pushRequest = pushRequest{ 400 Message: message{ 401 Attributes: msg.Attributes, 402 ID: "id", 403 Data: msg.Data, 404 }, 405 } 406 } 407 408 if err := json.NewEncoder(body).Encode(tc.pushRequest); err != nil { 409 t1.Errorf(err.Error()) 410 } 411 req := httptest.NewRequest(http.MethodPost, tc.url, body) 412 w := httptest.NewRecorder() 413 pushServer.ServeHTTP(w, req) 414 resp := w.Result() 415 if resp.StatusCode != tc.expectedCode { 416 t1.Errorf("exected code %d got %d", tc.expectedCode, resp.StatusCode) 417 } 418 }) 419 } 420 } 421 422 func TestPullServer_RunShutdown(t *testing.T) { 423 kc := &kubeTestClient{} 424 s := &Subscriber{ 425 ConfigAgent: &config.Agent{}, 426 KubeClient: kc, 427 Metrics: NewMetrics(), 428 } 429 c := &config.Config{} 430 s.ConfigAgent.Set(c) 431 pullServer := PullServer{ 432 Subscriber: s, 433 Client: &pubSubTestClient{}, 434 } 435 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 436 errChan := make(chan error) 437 go func() { 438 errChan <- pullServer.Run(ctx) 439 }() 440 time.Sleep(10 * time.Millisecond) 441 cancel() 442 err := <-errChan 443 if err != nil { 444 if !strings.HasPrefix(err.Error(), "context canceled") { 445 t.Errorf("unexpected error: %v", err) 446 } 447 } 448 } 449 450 func TestPullServer_RunHandlePullFail(t *testing.T) { 451 kc := &kubeTestClient{} 452 s := &Subscriber{ 453 ConfigAgent: &config.Agent{}, 454 KubeClient: kc, 455 Metrics: NewMetrics(), 456 } 457 c := &config.Config{ 458 ProwConfig: config.ProwConfig{ 459 PubSubSubscriptions: map[string][]string{ 460 "project": {"test"}, 461 }, 462 }, 463 } 464 messageChan := make(chan fakeMessage, 1) 465 s.ConfigAgent.Set(c) 466 pullServer := PullServer{ 467 Subscriber: s, 468 Client: &pubSubTestClient{messageChan: messageChan}, 469 } 470 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 471 errChan := make(chan error) 472 messageChan <- fakeMessage{ 473 Attributes: map[string]string{}, 474 ID: "test", 475 } 476 defer cancel() 477 go func() { 478 errChan <- pullServer.Run(ctx) 479 }() 480 err := <-errChan 481 // Should fail since Pub/Sub cred are not set 482 if !strings.HasPrefix(err.Error(), "message processed") { 483 t.Errorf("unexpected error: %v", err) 484 } 485 } 486 487 func TestPullServer_RunConfigChange(t *testing.T) { 488 kc := &kubeTestClient{} 489 s := &Subscriber{ 490 ConfigAgent: &config.Agent{}, 491 KubeClient: kc, 492 Metrics: NewMetrics(), 493 } 494 c := &config.Config{} 495 messageChan := make(chan fakeMessage, 1) 496 s.ConfigAgent.Set(c) 497 pullServer := PullServer{ 498 Subscriber: s, 499 Client: &pubSubTestClient{messageChan: messageChan}, 500 } 501 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 502 defer cancel() 503 errChan := make(chan error) 504 go func() { 505 errChan <- pullServer.Run(ctx) 506 }() 507 select { 508 case <-errChan: 509 t.Error("should not fail") 510 case <-time.After(10 * time.Millisecond): 511 newConfig := &config.Config{ 512 ProwConfig: config.ProwConfig{ 513 PubSubSubscriptions: map[string][]string{ 514 "project": {"test"}, 515 }, 516 }, 517 } 518 s.ConfigAgent.Set(newConfig) 519 messageChan <- fakeMessage{ 520 Attributes: map[string]string{}, 521 ID: "test", 522 } 523 err := <-errChan 524 if !strings.HasPrefix(err.Error(), "message processed") { 525 t.Errorf("unexpected error: %v", err) 526 } 527 } 528 }