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  }