github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/pubsub/subscriber/subscriber.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  	"encoding/json"
    21  	"fmt"
    22  
    23  	"cloud.google.com/go/pubsub"
    24  
    25  	"github.com/prometheus/client_golang/prometheus"
    26  	"github.com/sirupsen/logrus"
    27  
    28  	"k8s.io/test-infra/prow/config"
    29  	"k8s.io/test-infra/prow/kube"
    30  	"k8s.io/test-infra/prow/pjutil"
    31  )
    32  
    33  const (
    34  	prowEventType        = "prow.k8s.io/pubsub.EventType"
    35  	periodicProwJobEvent = "prow.k8s.io/pubsub.PeriodicProwJobEvent"
    36  )
    37  
    38  // PeriodicProwJobEvent contains the minimum information required to start a ProwJob.
    39  type PeriodicProwJobEvent struct {
    40  	Name        string            `json:"name"`
    41  	Type        string            `json:"type"`
    42  	Envs        map[string]string `json:"envs,omitempty"`
    43  	Annotations map[string]string `json:"annotations,omitempty"`
    44  }
    45  
    46  // FromPayload set the PeriodicProwJobEvent from the PubSub message payload.
    47  func (pe *PeriodicProwJobEvent) FromPayload(data []byte) error {
    48  	if err := json.Unmarshal(data, pe); err != nil {
    49  		return err
    50  	}
    51  	return nil
    52  }
    53  
    54  // ToMessage generates a PubSub Message from a PeriodicProwJobEvent.
    55  func (pe *PeriodicProwJobEvent) ToMessage() (*pubsub.Message, error) {
    56  	data, err := json.Marshal(pe)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  	message := pubsub.Message{
    61  		Data: data,
    62  		Attributes: map[string]string{
    63  			prowEventType: periodicProwJobEvent,
    64  		},
    65  	}
    66  	return &message, nil
    67  }
    68  
    69  // KubeClientInterface mostly for testing.
    70  type KubeClientInterface interface {
    71  	CreateProwJob(job *kube.ProwJob) (*kube.ProwJob, error)
    72  }
    73  
    74  // Subscriber handles Pub/Sub subscriptions, update metrics,
    75  // validates them using Prow Configuration and
    76  // use a KubeClientInterface to create Prow Jobs.
    77  type Subscriber struct {
    78  	ConfigAgent *config.Agent
    79  	Metrics     *Metrics
    80  	KubeClient  KubeClientInterface
    81  }
    82  
    83  type messageInterface interface {
    84  	getAttributes() map[string]string
    85  	getPayload() []byte
    86  	getID() string
    87  	ack()
    88  	nack()
    89  }
    90  
    91  type pubSubMessage struct {
    92  	pubsub.Message
    93  }
    94  
    95  func (m *pubSubMessage) getAttributes() map[string]string {
    96  	return m.Attributes
    97  }
    98  
    99  func (m *pubSubMessage) getPayload() []byte {
   100  	return m.Data
   101  }
   102  
   103  func (m *pubSubMessage) getID() string {
   104  	return m.ID
   105  }
   106  
   107  func (m *pubSubMessage) ack() {
   108  	m.Message.Ack()
   109  }
   110  func (m *pubSubMessage) nack() {
   111  	m.Message.Nack()
   112  }
   113  
   114  func extractFromAttribute(attrs map[string]string, key string) (string, error) {
   115  	value, ok := attrs[key]
   116  	if !ok {
   117  		return "", fmt.Errorf("unable to find %s from the attributes", key)
   118  	}
   119  	return value, nil
   120  }
   121  
   122  func (s *Subscriber) handleMessage(msg messageInterface, subscription string) error {
   123  	l := logrus.WithFields(logrus.Fields{
   124  		"pubsub-subscription": subscription,
   125  		"pubsub-id":           msg.getID()})
   126  	s.Metrics.MessageCounter.With(prometheus.Labels{subscriptionLabel: subscription}).Inc()
   127  	l.Info("Received message")
   128  	eType, err := extractFromAttribute(msg.getAttributes(), prowEventType)
   129  	if err != nil {
   130  		l.WithError(err).Error("failed to read message")
   131  		s.Metrics.ErrorCounter.With(prometheus.Labels{subscriptionLabel: subscription})
   132  		return err
   133  	}
   134  	switch eType {
   135  	case periodicProwJobEvent:
   136  		err := s.handlePeriodicJob(l, msg, subscription)
   137  		if err != nil {
   138  			l.WithError(err).Error("failed to create Prow Periodic Job")
   139  			s.Metrics.ErrorCounter.With(prometheus.Labels{subscriptionLabel: subscription})
   140  		}
   141  		return err
   142  	}
   143  	err = fmt.Errorf("unsupported event type")
   144  	l.WithError(err).Error("failed to read message")
   145  	s.Metrics.ErrorCounter.With(prometheus.Labels{subscriptionLabel: subscription})
   146  	return err
   147  }
   148  
   149  func (s *Subscriber) handlePeriodicJob(l *logrus.Entry, msg messageInterface, subscription string) error {
   150  	l.Info("looking for periodic job")
   151  	var pe PeriodicProwJobEvent
   152  	if err := pe.FromPayload(msg.getPayload()); err != nil {
   153  		return err
   154  	}
   155  	var periodicJob *config.Periodic
   156  	for _, job := range s.ConfigAgent.Config().AllPeriodics() {
   157  		if job.Name == pe.Name {
   158  			periodicJob = &job
   159  			break
   160  		}
   161  	}
   162  	if periodicJob == nil {
   163  		err := fmt.Errorf("failed to find associated periodic job %s", pe.Name)
   164  		l.WithError(err).Errorf("failed to create job %s", pe.Name)
   165  		return err
   166  	}
   167  	prowJobSpec := pjutil.PeriodicSpec(*periodicJob)
   168  	var prowJob kube.ProwJob
   169  	// Add annotations
   170  	prowJob = pjutil.NewProwJobWithAnnotation(prowJobSpec, periodicJob.Labels, pe.Annotations)
   171  	// Add Environments to containers
   172  	if prowJob.Spec.PodSpec != nil {
   173  		for _, c := range prowJob.Spec.PodSpec.Containers {
   174  			for k, v := range pe.Envs {
   175  				c.Env = append(c.Env, kube.EnvVar{Name: k, Value: v})
   176  			}
   177  		}
   178  	}
   179  	_, err := s.KubeClient.CreateProwJob(&prowJob)
   180  	if err != nil {
   181  		l.WithError(err).Errorf("failed to create job %s", prowJob.Name)
   182  	} else {
   183  		l.Infof("periodic job %s created", prowJob.Name)
   184  	}
   185  	return err
   186  }