github.com/yrj2011/jx-test-infra@v0.0.0-20190529031832-7a2065ee98eb/prow/artifact-uploader/controller.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 artifact_uploader 18 19 import ( 20 "bytes" 21 "fmt" 22 "path" 23 "time" 24 25 "github.com/golang/glog" 26 api "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/labels" 29 "k8s.io/apimachinery/pkg/selection" 30 "k8s.io/apimachinery/pkg/util/runtime" 31 "k8s.io/apimachinery/pkg/util/wait" 32 core "k8s.io/client-go/kubernetes/typed/core/v1" 33 "k8s.io/client-go/tools/cache" 34 "k8s.io/client-go/util/workqueue" 35 36 "k8s.io/test-infra/prow/gcsupload" 37 "k8s.io/test-infra/prow/kube" 38 "k8s.io/test-infra/prow/pod-utils/downwardapi" 39 "k8s.io/test-infra/prow/pod-utils/gcs" 40 ) 41 42 const ( 43 // ContainerLogDir is the prefix under which we place 44 // container logs in cloud storage 45 ContainerLogDir = "logs" 46 ) 47 48 // item describes a container that we saw finish execution 49 type item struct { 50 namespace string 51 podName string 52 containerName string 53 prowJobId string 54 } 55 56 func NewController(client core.CoreV1Interface, prowJobClient *kube.Client, gcsConfig *gcsupload.Options) Controller { 57 queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) 58 optionsModifier := func(options *metav1.ListOptions) { 59 req, _ := labels.NewRequirement(kube.ProwJobIDLabel, selection.Exists, []string{}) 60 options.LabelSelector = req.String() 61 } 62 podListWatcher := cache.NewFilteredListWatchFromClient(client.RESTClient(), "pods", api.NamespaceAll, optionsModifier) 63 indexer, informer := cache.NewIndexerInformer(podListWatcher, &api.Pod{}, 0, cache.ResourceEventHandlerFuncs{ 64 UpdateFunc: func(old interface{}, new interface{}) { 65 oldPod := old.(*api.Pod) 66 newPod := new.(*api.Pod) 67 68 containers := findFinishedContainers(oldPod.Status.InitContainerStatuses, newPod.Status.InitContainerStatuses) 69 containers = append(containers, findFinishedContainers(oldPod.Status.ContainerStatuses, newPod.Status.ContainerStatuses)...) 70 71 for _, container := range containers { 72 queue.Add(item{ 73 namespace: newPod.Namespace, 74 podName: newPod.Name, 75 containerName: container, 76 prowJobId: newPod.Labels[kube.ProwJobIDLabel], 77 }) 78 } 79 80 }, 81 }, cache.Indexers{}) 82 83 return Controller{ 84 queue: queue, 85 indexer: indexer, 86 informer: informer, 87 client: client, 88 prowJobClient: prowJobClient, 89 gcsConfig: gcsConfig, 90 } 91 } 92 93 func findFinishedContainers(old, new []api.ContainerStatus) []string { 94 var containerNames []string 95 for _, oldInitContainer := range old { 96 if oldInitContainer.Name == kube.TestContainerName { 97 // logs from the test container will be uploaded by the 98 // sidecar, so we do not need to worry about them here 99 continue 100 } 101 for _, newInitContainer := range new { 102 // we need to take action if we see a container that is 103 // terminated that was not terminated last time we saw it 104 if oldInitContainer.Name == newInitContainer.Name && 105 oldInitContainer.State.Terminated == nil && newInitContainer.State.Terminated != nil { 106 containerNames = append(containerNames, newInitContainer.Name) 107 } 108 } 109 } 110 return containerNames 111 } 112 113 type Controller struct { 114 queue workqueue.RateLimitingInterface 115 indexer cache.Indexer 116 informer cache.Controller 117 118 client core.CoreV1Interface 119 prowJobClient *kube.Client 120 121 gcsConfig *gcsupload.Options 122 } 123 124 func (c *Controller) Run(numWorkers int, stopCh chan struct{}) { 125 defer runtime.HandleCrash() 126 defer c.queue.ShutDown() 127 go c.informer.Run(stopCh) 128 if !cache.WaitForCacheSync(stopCh, c.informer.HasSynced) { 129 runtime.HandleError(fmt.Errorf("timed out waiting for caches to sync")) 130 return 131 } 132 133 for i := 0; i < numWorkers; i++ { 134 go wait.Until(c.runWorker, time.Second, stopCh) 135 } 136 137 <-stopCh 138 } 139 140 // runWorker runs the worker until the queue signals to quit 141 func (c *Controller) runWorker() { 142 for c.processNextItem() { 143 } 144 } 145 146 // processNextItem attempts to upload container logs to GCS 147 func (c *Controller) processNextItem() bool { 148 key, quit := c.queue.Get() 149 if quit { 150 return false 151 } 152 defer c.queue.Done(key) 153 154 workItem := key.(item) 155 156 prowJob, err := c.prowJobClient.GetProwJob(workItem.prowJobId) 157 if err != nil { 158 c.handleErr(err, workItem) 159 return true 160 } 161 spec := downwardapi.NewJobSpec(prowJob.Spec, prowJob.Status.BuildID, prowJob.Name) 162 163 result := c.client.Pods(workItem.namespace).GetLogs(workItem.podName, &api.PodLogOptions{Container: workItem.containerName}).Do() 164 if err := result.Error(); err != nil { 165 c.handleErr(err, workItem) 166 return true 167 } 168 169 // error is checked above 170 log, _ := result.Raw() 171 var target string 172 if workItem.podName == workItem.prowJobId { 173 target = path.Join(ContainerLogDir, fmt.Sprintf("%s.txt", workItem.containerName)) 174 } else { 175 target = path.Join(ContainerLogDir, workItem.podName, fmt.Sprintf("%s.txt", workItem.containerName)) 176 } 177 data := gcs.DataUpload(bytes.NewReader(log)) 178 if err := c.gcsConfig.Run(&spec, map[string]gcs.UploadFunc{target: data}); err != nil { 179 c.handleErr(err, workItem) 180 return true 181 } 182 c.queue.Forget(key) 183 return true 184 } 185 186 // handleErr checks if an error happened and makes sure we will retry later. 187 func (c *Controller) handleErr(err error, key item) { 188 if c.queue.NumRequeues(key) < 5 { 189 glog.Infof("Error uploading logs for container %v in pod %v: %v", key.containerName, key.podName, err) 190 c.queue.AddRateLimited(key) 191 return 192 } 193 194 c.queue.Forget(key) 195 glog.Infof("Giving up on upload of logs for container %v in pod %v: %v", key.containerName, key.podName, err) 196 }