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  }