github.com/justinjmoses/evergreen@v0.0.0-20170530173719-1d50e381ff0d/agent/comm/task_json.go (about)

     1  package comm
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"net/http"
     7  	"time"
     8  
     9  	"github.com/evergreen-ci/evergreen/model"
    10  	"github.com/evergreen-ci/evergreen/model/artifact"
    11  	"github.com/evergreen-ci/evergreen/model/task"
    12  	"github.com/evergreen-ci/evergreen/util"
    13  	"github.com/pkg/errors"
    14  )
    15  
    16  // TaskJSONCommunicator handles plugin-specific JSON-encoded
    17  // communication with the API server.
    18  type TaskJSONCommunicator struct {
    19  	PluginName string
    20  	TaskCommunicator
    21  }
    22  
    23  // TaskPostJSON does an HTTP POST for the communicator's plugin + task.
    24  func (t *TaskJSONCommunicator) TaskPostJSON(endpoint string, data interface{}) (*http.Response, error) {
    25  	return t.TryTaskPost(fmt.Sprintf("%s/%s", t.PluginName, endpoint), data)
    26  }
    27  
    28  // TaskGetJSON does an HTTP GET for the communicator's plugin + task.
    29  func (t *TaskJSONCommunicator) TaskGetJSON(endpoint string) (*http.Response, error) {
    30  	return t.TryTaskGet(fmt.Sprintf("%s/%s", t.PluginName, endpoint))
    31  }
    32  
    33  // TaskPostResults posts a set of test results for the communicator's task.
    34  // If results are empty or nil, this operation is a noop.
    35  func (t *TaskJSONCommunicator) TaskPostResults(results *task.TestResults) error {
    36  	if results == nil || len(results.Results) == 0 {
    37  		return nil
    38  	}
    39  
    40  	retriableSendFile := func() error {
    41  		resp, err := t.TryTaskPost("results", results)
    42  		if resp != nil {
    43  			defer resp.Body.Close()
    44  		}
    45  		if err != nil {
    46  			return util.RetriableError{errors.Wrap(err, "error posting results")}
    47  		}
    48  
    49  		body, _ := ioutil.ReadAll(resp.Body)
    50  		bodyErr := errors.Errorf("error posting results (%v): %s",
    51  			resp.StatusCode, string(body))
    52  		switch resp.StatusCode {
    53  		case http.StatusOK:
    54  			return nil
    55  		case http.StatusBadRequest:
    56  			return bodyErr
    57  		default:
    58  			return util.RetriableError{bodyErr}
    59  		}
    60  	}
    61  
    62  	retryFail, err := util.Retry(retriableSendFile, httpMaxAttempts, 1*time.Second)
    63  	if retryFail {
    64  		return errors.Wrapf(err, "attaching test results failed after %d tries", httpMaxAttempts)
    65  	}
    66  	return nil
    67  }
    68  
    69  // PostTaskFiles is used by the PluginCommunicator interface for attaching task files.
    70  func (t *TaskJSONCommunicator) PostTaskFiles(task_files []*artifact.File) error {
    71  	retriableSendFile := func() error {
    72  		resp, err := t.TryTaskPost("files", task_files)
    73  		if resp != nil {
    74  			defer resp.Body.Close()
    75  		}
    76  		if err != nil {
    77  			return util.RetriableError{errors.Wrap(err, "error posting results")}
    78  		}
    79  		body, readAllErr := ioutil.ReadAll(resp.Body)
    80  		bodyErr := errors.Errorf("error posting results (%v): %v",
    81  			resp.StatusCode, string(body))
    82  		if readAllErr != nil {
    83  			return bodyErr
    84  		}
    85  		switch resp.StatusCode {
    86  		case http.StatusOK:
    87  			return nil
    88  		case http.StatusBadRequest:
    89  			return bodyErr
    90  		default:
    91  			return util.RetriableError{bodyErr}
    92  		}
    93  	}
    94  
    95  	retryFail, err := util.Retry(retriableSendFile, httpMaxAttempts, 1*time.Second)
    96  	if retryFail {
    97  		return errors.Wrapf(err, "attaching task files failed after %d tries", httpMaxAttempts)
    98  	}
    99  	return nil
   100  }
   101  
   102  // TaskPostTestLog posts a test log for a communicator's task. Is a
   103  // noop if the test Log is nil.
   104  func (t *TaskJSONCommunicator) TaskPostTestLog(log *model.TestLog) (string, error) {
   105  	if log == nil {
   106  		return "", nil
   107  	}
   108  
   109  	var logId string
   110  	retriableSendFile := func() error {
   111  		resp, err := t.TryTaskPost("test_logs", log)
   112  		if err != nil {
   113  			return util.RetriableError{errors.Wrap(err, "error posting logs")}
   114  		}
   115  		defer resp.Body.Close()
   116  
   117  		if resp.StatusCode == http.StatusOK {
   118  			logReply := struct {
   119  				Id string `json:"_id"`
   120  			}{}
   121  
   122  			err = util.ReadJSONInto(resp.Body, &logReply)
   123  			logId = logReply.Id
   124  			return errors.WithStack(err)
   125  		}
   126  
   127  		bodyErr, err := ioutil.ReadAll(resp.Body)
   128  		if err != nil {
   129  			return util.RetriableError{errors.WithStack(err)}
   130  		}
   131  
   132  		if resp.StatusCode == http.StatusBadRequest {
   133  			return errors.Errorf("bad request posting logs: %s", string(bodyErr))
   134  		}
   135  
   136  		return util.RetriableError{errors.Errorf("failed posting logs: %s", string(bodyErr))}
   137  	}
   138  
   139  	retryFail, err := util.Retry(retriableSendFile, httpMaxAttempts, 1*time.Second)
   140  	if err != nil {
   141  		if retryFail {
   142  			return "", errors.Wrapf(err, "attaching test logs failed after %v tries", httpMaxAttempts)
   143  		}
   144  		return "", errors.Wrap(err, "attaching test logs failed")
   145  	}
   146  	return logId, nil
   147  }