github.com/abayer/test-infra@v0.0.5/prow/deck/jobs/jobs.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 jobs implements methods on job information used by Prow component deck 18 package jobs 19 20 import ( 21 "bytes" 22 "fmt" 23 "io/ioutil" 24 "net/http" 25 "regexp" 26 "sort" 27 "sync" 28 "time" 29 30 "github.com/sirupsen/logrus" 31 "k8s.io/apimachinery/pkg/labels" 32 "k8s.io/test-infra/prow/config" 33 "k8s.io/test-infra/prow/kube" 34 ) 35 36 const ( 37 period = 30 * time.Second 38 ) 39 40 // Job holds information about a job prow is running/has run. 41 // TODO(#5216): Remove this, and all associated machinery. 42 type Job struct { 43 Type string `json:"type"` 44 Repo string `json:"repo"` 45 Refs string `json:"refs"` 46 BaseRef string `json:"base_ref"` 47 BaseSHA string `json:"base_sha"` 48 PullSHA string `json:"pull_sha"` 49 Number int `json:"number"` 50 Author string `json:"author"` 51 Job string `json:"job"` 52 BuildID string `json:"build_id"` 53 Context string `json:"context"` 54 Started string `json:"started"` 55 Finished string `json:"finished"` 56 Duration string `json:"duration"` 57 State string `json:"state"` 58 Description string `json:"description"` 59 URL string `json:"url"` 60 PodName string `json:"pod_name"` 61 Agent kube.ProwJobAgent `json:"agent"` 62 ProwJob string `json:"prow_job"` 63 64 st time.Time 65 ft time.Time 66 } 67 68 type serviceClusterClient interface { 69 GetLog(pod string) ([]byte, error) 70 ListPods(selector string) ([]kube.Pod, error) 71 ListProwJobs(selector string) ([]kube.ProwJob, error) 72 } 73 74 type PodLogClient interface { 75 GetContainerLog(pod, container string) ([]byte, error) 76 } 77 78 type ConfigAgent interface { 79 Config() *config.Config 80 } 81 82 // NewJobAgent is a JobAgent constructor 83 func NewJobAgent(kc serviceClusterClient, plClients map[string]PodLogClient, ca ConfigAgent) *JobAgent { 84 return &JobAgent{ 85 kc: kc, 86 pkcs: plClients, 87 c: ca, 88 } 89 } 90 91 // JobAgent creates lists of jobs, updates their status and returns their run logs. 92 type JobAgent struct { 93 kc serviceClusterClient 94 pkcs map[string]PodLogClient 95 c ConfigAgent 96 prowJobs []kube.ProwJob 97 jobs []Job 98 jobsMap map[string]Job // pod name -> Job 99 jobsIDMap map[string]map[string]kube.ProwJob // job name -> id -> ProwJob 100 mut sync.Mutex 101 } 102 103 // Start will start the job and periodically update it. 104 func (ja *JobAgent) Start() { 105 ja.tryUpdate() 106 go func() { 107 t := time.Tick(period) 108 for range t { 109 ja.tryUpdate() 110 } 111 }() 112 } 113 114 // Jobs returns a thread-safe snapshot of the current job state. 115 func (ja *JobAgent) Jobs() []Job { 116 ja.mut.Lock() 117 defer ja.mut.Unlock() 118 res := make([]Job, len(ja.jobs)) 119 copy(res, ja.jobs) 120 return res 121 } 122 123 // ProwJobs returns a thread-safe snapshot of the current prow jobs. 124 func (ja *JobAgent) ProwJobs() []kube.ProwJob { 125 ja.mut.Lock() 126 defer ja.mut.Unlock() 127 res := make([]kube.ProwJob, len(ja.prowJobs)) 128 copy(res, ja.prowJobs) 129 return res 130 } 131 132 var jobNameRE = regexp.MustCompile(`^([\w-]+)-(\d+)$`) 133 134 // GetJobLog returns the job logs, works for both kubernetes and jenkins agent types. 135 func (ja *JobAgent) GetJobLog(job, id string) ([]byte, error) { 136 var j kube.ProwJob 137 ja.mut.Lock() 138 idMap, ok := ja.jobsIDMap[job] 139 if ok { 140 j, ok = idMap[id] 141 } 142 ja.mut.Unlock() 143 if !ok { 144 return nil, fmt.Errorf("no such job found: %s (id: %s)", job, id) 145 } 146 if j.Spec.Agent == kube.KubernetesAgent { 147 client, ok := ja.pkcs[j.ClusterAlias()] 148 if !ok { 149 return nil, fmt.Errorf("cannot get logs for prowjob %q with agent %q: unknown cluster alias %q", j.ObjectMeta.Name, j.Spec.Agent, j.ClusterAlias()) 150 } 151 return client.GetContainerLog(j.Status.PodName, kube.TestContainerName) 152 } 153 for _, agentToTmpl := range ja.c.Config().Deck.ExternalAgentLogs { 154 if agentToTmpl.Agent != string(j.Spec.Agent) { 155 continue 156 } 157 if !agentToTmpl.Selector.Matches(labels.Set(j.ObjectMeta.Labels)) { 158 continue 159 } 160 var b bytes.Buffer 161 if err := agentToTmpl.URLTemplate.Execute(&b, &j); err != nil { 162 return nil, fmt.Errorf("cannot execute URL template for prowjob %q with agent %q: %v", j.ObjectMeta.Name, j.Spec.Agent, err) 163 } 164 resp, err := http.Get(b.String()) 165 if err != nil { 166 return nil, err 167 } 168 defer resp.Body.Close() 169 return ioutil.ReadAll(resp.Body) 170 } 171 return nil, fmt.Errorf("cannot get logs for prowjob %q with agent %q: the agent is missing from the prow config file", j.ObjectMeta.Name, j.Spec.Agent) 172 } 173 174 func (ja *JobAgent) tryUpdate() { 175 if err := ja.update(); err != nil { 176 logrus.WithError(err).Warning("Error updating job list.") 177 } 178 } 179 180 type byStartTime []Job 181 182 func (a byStartTime) Len() int { return len(a) } 183 func (a byStartTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 184 func (a byStartTime) Less(i, j int) bool { return a[i].st.After(a[j].st) } 185 186 func (ja *JobAgent) update() error { 187 pjs, err := ja.kc.ListProwJobs(kube.EmptySelector) 188 if err != nil { 189 return err 190 } 191 var njs []Job 192 njsMap := make(map[string]Job) 193 njsIDMap := make(map[string]map[string]kube.ProwJob) 194 for _, j := range pjs { 195 ft := time.Time{} 196 if j.Status.CompletionTime != nil { 197 ft = j.Status.CompletionTime.Time 198 } 199 buildID := j.Status.BuildID 200 nj := Job{ 201 Type: string(j.Spec.Type), 202 Job: j.Spec.Job, 203 Context: j.Spec.Context, 204 Agent: j.Spec.Agent, 205 ProwJob: j.ObjectMeta.Name, 206 BuildID: buildID, 207 208 Started: fmt.Sprintf("%d", j.Status.StartTime.Time.Unix()), 209 State: string(j.Status.State), 210 Description: j.Status.Description, 211 PodName: j.Status.PodName, 212 URL: j.Status.URL, 213 214 st: j.Status.StartTime.Time, 215 ft: ft, 216 } 217 if !nj.ft.IsZero() { 218 nj.Finished = nj.ft.Format(time.RFC3339Nano) 219 duration := nj.ft.Sub(nj.st) 220 duration -= duration % time.Second // strip fractional seconds 221 nj.Duration = duration.String() 222 } 223 if j.Spec.Refs != nil { 224 nj.Repo = fmt.Sprintf("%s/%s", j.Spec.Refs.Org, j.Spec.Refs.Repo) 225 nj.Refs = j.Spec.Refs.String() 226 nj.BaseRef = j.Spec.Refs.BaseRef 227 nj.BaseSHA = j.Spec.Refs.BaseSHA 228 if len(j.Spec.Refs.Pulls) == 1 { 229 nj.Number = j.Spec.Refs.Pulls[0].Number 230 nj.Author = j.Spec.Refs.Pulls[0].Author 231 nj.PullSHA = j.Spec.Refs.Pulls[0].SHA 232 } 233 } 234 njs = append(njs, nj) 235 if nj.PodName != "" { 236 njsMap[nj.PodName] = nj 237 } 238 if _, ok := njsIDMap[j.Spec.Job]; !ok { 239 njsIDMap[j.Spec.Job] = make(map[string]kube.ProwJob) 240 } 241 njsIDMap[j.Spec.Job][buildID] = j 242 } 243 sort.Sort(byStartTime(njs)) 244 245 ja.mut.Lock() 246 defer ja.mut.Unlock() 247 ja.prowJobs = pjs 248 ja.jobs = njs 249 ja.jobsMap = njsMap 250 ja.jobsIDMap = njsIDMap 251 return nil 252 }