Example #1
0
func (s *endpointServiceImpl) send(ctx context.Context, data []byte) error {
	ctx = log.SetField(ctx, "endpointURL", s.url)
	return retryCall(ctx, "endpoint.send", func() error {
		startTime := clock.Now(ctx)

		log.Debugf(ctx, "Pushing message to endpoint.")
		req, err := http.NewRequest("POST", s.url, bytes.NewReader(data))
		if err != nil {
			log.Errorf(log.SetError(ctx, err), "Failed to create HTTP request.")
			return err
		}
		req.Header.Add("content-type", protobufContentType)
		req.Header.Add("user-agent", monitoringEndpointUserAgent)

		resp, err := s.client.Do(req)
		if err != nil {
			// Treat a client error as transient.
			log.Warningf(log.SetError(ctx, err), "Failed proxy client request.")
			return errors.WrapTransient(err)
		}
		defer resp.Body.Close()

		// Read the full response body. This will enable us to re-use the
		// connection.
		bodyData, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			log.Errorf(log.SetError(ctx, err), "Error during endpoint connection.")
			return errors.WrapTransient(err)
		}

		log.Fields{
			"status":        resp.Status,
			"statusCode":    resp.StatusCode,
			"headers":       resp.Header,
			"contentLength": resp.ContentLength,
			"body":          string(bodyData),
			"duration":      clock.Now(ctx).Sub(startTime),
		}.Debugf(ctx, "Received HTTP response from endpoint.")

		if http.StatusOK <= resp.StatusCode && resp.StatusCode < http.StatusMultipleChoices {
			log.Debugf(ctx, "Message pushed successfully.")
			return nil
		}

		err = fmt.Errorf("http: server error (%d)", resp.StatusCode)
		if resp.StatusCode >= http.StatusInternalServerError {
			err = errors.WrapTransient(err)
		}

		log.Fields{
			log.ErrorKey: err,
			"status":     resp.Status,
			"statusCode": resp.StatusCode,
		}.Warningf(ctx, "Proxy error.")
		return err
	})
}
Example #2
0
// wrapTransient examines the supplied error. If it's not a recognized error
// value, it is treated as transient.
//
// This is because, at the moment, the transiant nature of the pubsub return
// codes is not discernable, so we will error on the side of caution (retry).
func (*pubsubClient) wrapTransient(err error) error {
	switch err {
	case nil:
		return nil

	case context.Canceled:
		return err

	default:
		return errors.WrapTransient(err)
	}
}
Example #3
0
func TestPubSub(t *testing.T) {
	t.Parallel()

	Convey(`Using a testing Pub/Sub config`, t, func() {
		// Do not retry.
		ctx := context.WithValue(context.Background(), backoffPolicyKey, func() retry.Iterator {
			return &retry.Limited{}
		})

		config := pubsubConfig{
			project:      "test-project",
			topic:        "test-topic",
			subscription: "test-subscription",
			create:       true,
			batchSize:    64,
		}

		svc := &testPubSubService{}
		defer So(svc, mock.ShouldHaveNoErrors)

		Convey(`When the subscription does not exist`, func() {
			svc.MockCall("SubExists", "test-subscription").WithResult(false, nil)

			Convey(`And the topic does not exist, will create a new topic and subscription.`, func() {
				svc.MockCall("TopicExists", "test-topic").WithResult(false, nil)
				svc.MockCall("CreateTopic", "test-topic").WithResult(nil)
				svc.MockCall("CreatePullSub", "test-subscription", "test-topic").WithResult(nil)

				_, err := newPubSubClient(ctx, config, svc)
				So(err, ShouldBeNil)
			})

			Convey(`And the topic exists, will create a new subscription.`, func() {
				svc.MockCall("TopicExists", "test-topic").WithResult(true, nil)
				svc.MockCall("CreatePullSub", "test-subscription", "test-topic").WithResult(nil)

				_, err := newPubSubClient(ctx, config, svc)
				So(err, ShouldBeNil)
			})

			Convey(`Will fail to create a new client when "create" is false.`, func() {
				config.create = false

				_, err := newPubSubClient(ctx, config, svc)
				So(err, ShouldNotBeNil)
			})
		})

		Convey(`Will create a new client.`, func() {
			svc.MockCall("SubExists", "test-subscription").WithResult(true, nil)

			client, err := newPubSubClient(ctx, config, svc)
			So(err, ShouldBeNil)

			Convey(`When executing pull/ack with no messages`, func() {
				svc.MockCall("Pull", "test-subscription", 64).WithResult(nil, nil)

				Convey(`Returns errNoMessages.`, func() {
					err := client.pullAckMessages(ctx, func([]*pubsub.Message) {})
					So(err, ShouldEqual, errNoMessages)
				})
			})

			Convey(`When executing pull/ack with one message`, func() {
				msgs := []*pubsub.Message{
					{
						ID:    "id0",
						AckID: "ack0",
						Data:  []byte{0xd0, 0x65},
					},
				}
				svc.MockCall("Pull", "test-subscription", 64).WithResult(msgs, nil)

				Convey(`Returns and ACKs that message.`, func() {
					svc.MockCall("Ack", "test-subscription", []string{"ack0"}).WithResult(nil)

					var pullMsg []*pubsub.Message
					err := client.pullAckMessages(ctx, func(msg []*pubsub.Message) {
						pullMsg = msg
					})
					So(err, ShouldBeNil)

					So(pullMsg, ShouldResemble, msgs)
				})

				Convey(`ACKs the message even if the handler panics.`, func() {
					svc.MockCall("Ack", "test-subscription", []string{"ack0"}).WithResult(nil)

					So(func() {
						client.pullAckMessages(ctx, func(msg []*pubsub.Message) {
							panic("Handler failure!")
						})
					}, ShouldPanic)
				})

				Convey(`Does not ACK the message if the handler clears it.`, func() {
					err := client.pullAckMessages(ctx, func(msg []*pubsub.Message) {
						for i := range msg {
							msg[i] = nil
						}
					})
					So(err, ShouldBeNil)
				})
			})

			Convey(`When executing pull/ack with an error`, func() {
				e := errors.New("TEST ERROR")
				svc.MockCall("Pull", "test-subscription", 64).WithResult(nil, e)

				Convey(`Returns the error as transient.`, func() {
					So(client.pullAckMessages(ctx, func([]*pubsub.Message) {}), ShouldResemble, errors.WrapTransient(e))
				})
			})
		})
	})
}