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 }