mirror of
https://github.com/restic/restic.git
synced 2026-09-20 02:58:30 +00:00
Update dependencies
Among others, this updates minio-go, so that the new "eu-west-3" zone for AWS is supported.
This commit is contained in:
+5
-3
@@ -1,10 +1,10 @@
|
||||
// Copyright 2017, Google Inc. All rights reserved.
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
)
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -31,6 +32,7 @@ import (
|
||||
"google.golang.org/api/option"
|
||||
)
|
||||
|
||||
var _ = fmt.Sprintf
|
||||
var _ = iterator.Done
|
||||
var _ = strconv.FormatUint
|
||||
var _ = time.Now
|
||||
@@ -53,7 +55,7 @@ func TestPublisherSmoke(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var formattedProject string = PublisherProjectPath(projectId)
|
||||
var formattedProject string = fmt.Sprintf("projects/%s", projectId)
|
||||
var request = &pubsubpb.ListTopicsRequest{
|
||||
Project: formattedProject,
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
// Copyright 2017, Google Inc. All rights reserved.
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@@ -17,7 +17,7 @@
|
||||
// Package pubsub is an auto-generated package for the
|
||||
// Google Cloud Pub/Sub API.
|
||||
//
|
||||
// NOTE: This package is in alpha. It is not stable, and is likely to be subject to changes.
|
||||
// NOTE: This package is in alpha. It is not stable, and is likely to change.
|
||||
//
|
||||
// Provides reliable, many-to-many, asynchronous messaging between
|
||||
// applications.
|
||||
|
||||
+89
-54
@@ -1,10 +1,10 @@
|
||||
// Copyright 2017, Google Inc. All rights reserved.
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@@ -18,6 +18,7 @@ package pubsub
|
||||
|
||||
import (
|
||||
emptypb "github.com/golang/protobuf/ptypes/empty"
|
||||
timestamppb "github.com/golang/protobuf/ptypes/timestamp"
|
||||
iampb "google.golang.org/genproto/googleapis/iam/v1"
|
||||
pubsubpb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
field_maskpb "google.golang.org/genproto/protobuf/field_mask"
|
||||
@@ -450,7 +451,7 @@ func TestPublisherCreateTopic(t *testing.T) {
|
||||
|
||||
mockPublisher.resps = append(mockPublisher.resps[:0], expectedResponse)
|
||||
|
||||
var formattedName string = PublisherTopicPath("[PROJECT]", "[TOPIC]")
|
||||
var formattedName string = fmt.Sprintf("projects/%s/topics/%s", "[PROJECT]", "[TOPIC]")
|
||||
var request = &pubsubpb.Topic{
|
||||
Name: formattedName,
|
||||
}
|
||||
@@ -479,7 +480,7 @@ func TestPublisherCreateTopicError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockPublisher.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var formattedName string = PublisherTopicPath("[PROJECT]", "[TOPIC]")
|
||||
var formattedName string = fmt.Sprintf("projects/%s/topics/%s", "[PROJECT]", "[TOPIC]")
|
||||
var request = &pubsubpb.Topic{
|
||||
Name: formattedName,
|
||||
}
|
||||
@@ -573,7 +574,7 @@ func TestPublisherPublish(t *testing.T) {
|
||||
|
||||
mockPublisher.resps = append(mockPublisher.resps[:0], expectedResponse)
|
||||
|
||||
var formattedTopic string = PublisherTopicPath("[PROJECT]", "[TOPIC]")
|
||||
var formattedTopic string = fmt.Sprintf("projects/%s/topics/%s", "[PROJECT]", "[TOPIC]")
|
||||
var data []byte = []byte("-86")
|
||||
var messagesElement = &pubsubpb.PubsubMessage{
|
||||
Data: data,
|
||||
@@ -608,7 +609,7 @@ func TestPublisherPublishError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockPublisher.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var formattedTopic string = PublisherTopicPath("[PROJECT]", "[TOPIC]")
|
||||
var formattedTopic string = fmt.Sprintf("projects/%s/topics/%s", "[PROJECT]", "[TOPIC]")
|
||||
var data []byte = []byte("-86")
|
||||
var messagesElement = &pubsubpb.PubsubMessage{
|
||||
Data: data,
|
||||
@@ -644,7 +645,7 @@ func TestPublisherGetTopic(t *testing.T) {
|
||||
|
||||
mockPublisher.resps = append(mockPublisher.resps[:0], expectedResponse)
|
||||
|
||||
var formattedTopic string = PublisherTopicPath("[PROJECT]", "[TOPIC]")
|
||||
var formattedTopic string = fmt.Sprintf("projects/%s/topics/%s", "[PROJECT]", "[TOPIC]")
|
||||
var request = &pubsubpb.GetTopicRequest{
|
||||
Topic: formattedTopic,
|
||||
}
|
||||
@@ -673,7 +674,7 @@ func TestPublisherGetTopicError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockPublisher.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var formattedTopic string = PublisherTopicPath("[PROJECT]", "[TOPIC]")
|
||||
var formattedTopic string = fmt.Sprintf("projects/%s/topics/%s", "[PROJECT]", "[TOPIC]")
|
||||
var request = &pubsubpb.GetTopicRequest{
|
||||
Topic: formattedTopic,
|
||||
}
|
||||
@@ -706,7 +707,7 @@ func TestPublisherListTopics(t *testing.T) {
|
||||
|
||||
mockPublisher.resps = append(mockPublisher.resps[:0], expectedResponse)
|
||||
|
||||
var formattedProject string = PublisherProjectPath("[PROJECT]")
|
||||
var formattedProject string = fmt.Sprintf("projects/%s", "[PROJECT]")
|
||||
var request = &pubsubpb.ListTopicsRequest{
|
||||
Project: formattedProject,
|
||||
}
|
||||
@@ -745,7 +746,7 @@ func TestPublisherListTopicsError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockPublisher.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var formattedProject string = PublisherProjectPath("[PROJECT]")
|
||||
var formattedProject string = fmt.Sprintf("projects/%s", "[PROJECT]")
|
||||
var request = &pubsubpb.ListTopicsRequest{
|
||||
Project: formattedProject,
|
||||
}
|
||||
@@ -778,7 +779,7 @@ func TestPublisherListTopicSubscriptions(t *testing.T) {
|
||||
|
||||
mockPublisher.resps = append(mockPublisher.resps[:0], expectedResponse)
|
||||
|
||||
var formattedTopic string = PublisherTopicPath("[PROJECT]", "[TOPIC]")
|
||||
var formattedTopic string = fmt.Sprintf("projects/%s/topics/%s", "[PROJECT]", "[TOPIC]")
|
||||
var request = &pubsubpb.ListTopicSubscriptionsRequest{
|
||||
Topic: formattedTopic,
|
||||
}
|
||||
@@ -817,7 +818,7 @@ func TestPublisherListTopicSubscriptionsError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockPublisher.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var formattedTopic string = PublisherTopicPath("[PROJECT]", "[TOPIC]")
|
||||
var formattedTopic string = fmt.Sprintf("projects/%s/topics/%s", "[PROJECT]", "[TOPIC]")
|
||||
var request = &pubsubpb.ListTopicSubscriptionsRequest{
|
||||
Topic: formattedTopic,
|
||||
}
|
||||
@@ -844,7 +845,7 @@ func TestPublisherDeleteTopic(t *testing.T) {
|
||||
|
||||
mockPublisher.resps = append(mockPublisher.resps[:0], expectedResponse)
|
||||
|
||||
var formattedTopic string = PublisherTopicPath("[PROJECT]", "[TOPIC]")
|
||||
var formattedTopic string = fmt.Sprintf("projects/%s/topics/%s", "[PROJECT]", "[TOPIC]")
|
||||
var request = &pubsubpb.DeleteTopicRequest{
|
||||
Topic: formattedTopic,
|
||||
}
|
||||
@@ -870,7 +871,7 @@ func TestPublisherDeleteTopicError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockPublisher.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var formattedTopic string = PublisherTopicPath("[PROJECT]", "[TOPIC]")
|
||||
var formattedTopic string = fmt.Sprintf("projects/%s/topics/%s", "[PROJECT]", "[TOPIC]")
|
||||
var request = &pubsubpb.DeleteTopicRequest{
|
||||
Topic: formattedTopic,
|
||||
}
|
||||
@@ -905,8 +906,8 @@ func TestSubscriberCreateSubscription(t *testing.T) {
|
||||
|
||||
mockSubscriber.resps = append(mockSubscriber.resps[:0], expectedResponse)
|
||||
|
||||
var formattedName string = SubscriberSubscriptionPath("[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedTopic string = SubscriberTopicPath("[PROJECT]", "[TOPIC]")
|
||||
var formattedName string = fmt.Sprintf("projects/%s/subscriptions/%s", "[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedTopic string = fmt.Sprintf("projects/%s/topics/%s", "[PROJECT]", "[TOPIC]")
|
||||
var request = &pubsubpb.Subscription{
|
||||
Name: formattedName,
|
||||
Topic: formattedTopic,
|
||||
@@ -936,8 +937,8 @@ func TestSubscriberCreateSubscriptionError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockSubscriber.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var formattedName string = SubscriberSubscriptionPath("[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedTopic string = SubscriberTopicPath("[PROJECT]", "[TOPIC]")
|
||||
var formattedName string = fmt.Sprintf("projects/%s/subscriptions/%s", "[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedTopic string = fmt.Sprintf("projects/%s/topics/%s", "[PROJECT]", "[TOPIC]")
|
||||
var request = &pubsubpb.Subscription{
|
||||
Name: formattedName,
|
||||
Topic: formattedTopic,
|
||||
@@ -974,7 +975,7 @@ func TestSubscriberGetSubscription(t *testing.T) {
|
||||
|
||||
mockSubscriber.resps = append(mockSubscriber.resps[:0], expectedResponse)
|
||||
|
||||
var formattedSubscription string = SubscriberSubscriptionPath("[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedSubscription string = fmt.Sprintf("projects/%s/subscriptions/%s", "[PROJECT]", "[SUBSCRIPTION]")
|
||||
var request = &pubsubpb.GetSubscriptionRequest{
|
||||
Subscription: formattedSubscription,
|
||||
}
|
||||
@@ -1003,7 +1004,7 @@ func TestSubscriberGetSubscriptionError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockSubscriber.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var formattedSubscription string = SubscriberSubscriptionPath("[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedSubscription string = fmt.Sprintf("projects/%s/subscriptions/%s", "[PROJECT]", "[SUBSCRIPTION]")
|
||||
var request = &pubsubpb.GetSubscriptionRequest{
|
||||
Subscription: formattedSubscription,
|
||||
}
|
||||
@@ -1025,12 +1026,12 @@ func TestSubscriberGetSubscriptionError(t *testing.T) {
|
||||
func TestSubscriberUpdateSubscription(t *testing.T) {
|
||||
var name string = "name3373707"
|
||||
var topic string = "topic110546223"
|
||||
var ackDeadlineSeconds int32 = 2135351438
|
||||
var ackDeadlineSeconds2 int32 = 921632575
|
||||
var retainAckedMessages bool = false
|
||||
var expectedResponse = &pubsubpb.Subscription{
|
||||
Name: name,
|
||||
Topic: topic,
|
||||
AckDeadlineSeconds: ackDeadlineSeconds,
|
||||
AckDeadlineSeconds: ackDeadlineSeconds2,
|
||||
RetainAckedMessages: retainAckedMessages,
|
||||
}
|
||||
|
||||
@@ -1039,8 +1040,15 @@ func TestSubscriberUpdateSubscription(t *testing.T) {
|
||||
|
||||
mockSubscriber.resps = append(mockSubscriber.resps[:0], expectedResponse)
|
||||
|
||||
var subscription *pubsubpb.Subscription = &pubsubpb.Subscription{}
|
||||
var updateMask *field_maskpb.FieldMask = &field_maskpb.FieldMask{}
|
||||
var ackDeadlineSeconds int32 = 42
|
||||
var subscription = &pubsubpb.Subscription{
|
||||
AckDeadlineSeconds: ackDeadlineSeconds,
|
||||
}
|
||||
var pathsElement string = "ack_deadline_seconds"
|
||||
var paths = []string{pathsElement}
|
||||
var updateMask = &field_maskpb.FieldMask{
|
||||
Paths: paths,
|
||||
}
|
||||
var request = &pubsubpb.UpdateSubscriptionRequest{
|
||||
Subscription: subscription,
|
||||
UpdateMask: updateMask,
|
||||
@@ -1070,8 +1078,15 @@ func TestSubscriberUpdateSubscriptionError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockSubscriber.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var subscription *pubsubpb.Subscription = &pubsubpb.Subscription{}
|
||||
var updateMask *field_maskpb.FieldMask = &field_maskpb.FieldMask{}
|
||||
var ackDeadlineSeconds int32 = 42
|
||||
var subscription = &pubsubpb.Subscription{
|
||||
AckDeadlineSeconds: ackDeadlineSeconds,
|
||||
}
|
||||
var pathsElement string = "ack_deadline_seconds"
|
||||
var paths = []string{pathsElement}
|
||||
var updateMask = &field_maskpb.FieldMask{
|
||||
Paths: paths,
|
||||
}
|
||||
var request = &pubsubpb.UpdateSubscriptionRequest{
|
||||
Subscription: subscription,
|
||||
UpdateMask: updateMask,
|
||||
@@ -1105,7 +1120,7 @@ func TestSubscriberListSubscriptions(t *testing.T) {
|
||||
|
||||
mockSubscriber.resps = append(mockSubscriber.resps[:0], expectedResponse)
|
||||
|
||||
var formattedProject string = SubscriberProjectPath("[PROJECT]")
|
||||
var formattedProject string = fmt.Sprintf("projects/%s", "[PROJECT]")
|
||||
var request = &pubsubpb.ListSubscriptionsRequest{
|
||||
Project: formattedProject,
|
||||
}
|
||||
@@ -1144,7 +1159,7 @@ func TestSubscriberListSubscriptionsError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockSubscriber.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var formattedProject string = SubscriberProjectPath("[PROJECT]")
|
||||
var formattedProject string = fmt.Sprintf("projects/%s", "[PROJECT]")
|
||||
var request = &pubsubpb.ListSubscriptionsRequest{
|
||||
Project: formattedProject,
|
||||
}
|
||||
@@ -1171,7 +1186,7 @@ func TestSubscriberDeleteSubscription(t *testing.T) {
|
||||
|
||||
mockSubscriber.resps = append(mockSubscriber.resps[:0], expectedResponse)
|
||||
|
||||
var formattedSubscription string = SubscriberSubscriptionPath("[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedSubscription string = fmt.Sprintf("projects/%s/subscriptions/%s", "[PROJECT]", "[SUBSCRIPTION]")
|
||||
var request = &pubsubpb.DeleteSubscriptionRequest{
|
||||
Subscription: formattedSubscription,
|
||||
}
|
||||
@@ -1197,7 +1212,7 @@ func TestSubscriberDeleteSubscriptionError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockSubscriber.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var formattedSubscription string = SubscriberSubscriptionPath("[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedSubscription string = fmt.Sprintf("projects/%s/subscriptions/%s", "[PROJECT]", "[SUBSCRIPTION]")
|
||||
var request = &pubsubpb.DeleteSubscriptionRequest{
|
||||
Subscription: formattedSubscription,
|
||||
}
|
||||
@@ -1223,7 +1238,7 @@ func TestSubscriberModifyAckDeadline(t *testing.T) {
|
||||
|
||||
mockSubscriber.resps = append(mockSubscriber.resps[:0], expectedResponse)
|
||||
|
||||
var formattedSubscription string = SubscriberSubscriptionPath("[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedSubscription string = fmt.Sprintf("projects/%s/subscriptions/%s", "[PROJECT]", "[SUBSCRIPTION]")
|
||||
var ackIds []string = nil
|
||||
var ackDeadlineSeconds int32 = 2135351438
|
||||
var request = &pubsubpb.ModifyAckDeadlineRequest{
|
||||
@@ -1253,7 +1268,7 @@ func TestSubscriberModifyAckDeadlineError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockSubscriber.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var formattedSubscription string = SubscriberSubscriptionPath("[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedSubscription string = fmt.Sprintf("projects/%s/subscriptions/%s", "[PROJECT]", "[SUBSCRIPTION]")
|
||||
var ackIds []string = nil
|
||||
var ackDeadlineSeconds int32 = 2135351438
|
||||
var request = &pubsubpb.ModifyAckDeadlineRequest{
|
||||
@@ -1283,7 +1298,7 @@ func TestSubscriberAcknowledge(t *testing.T) {
|
||||
|
||||
mockSubscriber.resps = append(mockSubscriber.resps[:0], expectedResponse)
|
||||
|
||||
var formattedSubscription string = SubscriberSubscriptionPath("[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedSubscription string = fmt.Sprintf("projects/%s/subscriptions/%s", "[PROJECT]", "[SUBSCRIPTION]")
|
||||
var ackIds []string = nil
|
||||
var request = &pubsubpb.AcknowledgeRequest{
|
||||
Subscription: formattedSubscription,
|
||||
@@ -1311,7 +1326,7 @@ func TestSubscriberAcknowledgeError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockSubscriber.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var formattedSubscription string = SubscriberSubscriptionPath("[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedSubscription string = fmt.Sprintf("projects/%s/subscriptions/%s", "[PROJECT]", "[SUBSCRIPTION]")
|
||||
var ackIds []string = nil
|
||||
var request = &pubsubpb.AcknowledgeRequest{
|
||||
Subscription: formattedSubscription,
|
||||
@@ -1339,7 +1354,7 @@ func TestSubscriberPull(t *testing.T) {
|
||||
|
||||
mockSubscriber.resps = append(mockSubscriber.resps[:0], expectedResponse)
|
||||
|
||||
var formattedSubscription string = SubscriberSubscriptionPath("[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedSubscription string = fmt.Sprintf("projects/%s/subscriptions/%s", "[PROJECT]", "[SUBSCRIPTION]")
|
||||
var maxMessages int32 = 496131527
|
||||
var request = &pubsubpb.PullRequest{
|
||||
Subscription: formattedSubscription,
|
||||
@@ -1370,7 +1385,7 @@ func TestSubscriberPullError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockSubscriber.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var formattedSubscription string = SubscriberSubscriptionPath("[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedSubscription string = fmt.Sprintf("projects/%s/subscriptions/%s", "[PROJECT]", "[SUBSCRIPTION]")
|
||||
var maxMessages int32 = 496131527
|
||||
var request = &pubsubpb.PullRequest{
|
||||
Subscription: formattedSubscription,
|
||||
@@ -1403,7 +1418,7 @@ func TestSubscriberStreamingPull(t *testing.T) {
|
||||
|
||||
mockSubscriber.resps = append(mockSubscriber.resps[:0], expectedResponse)
|
||||
|
||||
var formattedSubscription string = SubscriberSubscriptionPath("[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedSubscription string = fmt.Sprintf("projects/%s/subscriptions/%s", "[PROJECT]", "[SUBSCRIPTION]")
|
||||
var streamAckDeadlineSeconds int32 = 1875467245
|
||||
var request = &pubsubpb.StreamingPullRequest{
|
||||
Subscription: formattedSubscription,
|
||||
@@ -1444,7 +1459,7 @@ func TestSubscriberStreamingPullError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockSubscriber.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var formattedSubscription string = SubscriberSubscriptionPath("[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedSubscription string = fmt.Sprintf("projects/%s/subscriptions/%s", "[PROJECT]", "[SUBSCRIPTION]")
|
||||
var streamAckDeadlineSeconds int32 = 1875467245
|
||||
var request = &pubsubpb.StreamingPullRequest{
|
||||
Subscription: formattedSubscription,
|
||||
@@ -1483,7 +1498,7 @@ func TestSubscriberModifyPushConfig(t *testing.T) {
|
||||
|
||||
mockSubscriber.resps = append(mockSubscriber.resps[:0], expectedResponse)
|
||||
|
||||
var formattedSubscription string = SubscriberSubscriptionPath("[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedSubscription string = fmt.Sprintf("projects/%s/subscriptions/%s", "[PROJECT]", "[SUBSCRIPTION]")
|
||||
var pushConfig *pubsubpb.PushConfig = &pubsubpb.PushConfig{}
|
||||
var request = &pubsubpb.ModifyPushConfigRequest{
|
||||
Subscription: formattedSubscription,
|
||||
@@ -1511,7 +1526,7 @@ func TestSubscriberModifyPushConfigError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockSubscriber.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var formattedSubscription string = SubscriberSubscriptionPath("[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedSubscription string = fmt.Sprintf("projects/%s/subscriptions/%s", "[PROJECT]", "[SUBSCRIPTION]")
|
||||
var pushConfig *pubsubpb.PushConfig = &pubsubpb.PushConfig{}
|
||||
var request = &pubsubpb.ModifyPushConfigRequest{
|
||||
Subscription: formattedSubscription,
|
||||
@@ -1545,7 +1560,7 @@ func TestSubscriberListSnapshots(t *testing.T) {
|
||||
|
||||
mockSubscriber.resps = append(mockSubscriber.resps[:0], expectedResponse)
|
||||
|
||||
var formattedProject string = SubscriberProjectPath("[PROJECT]")
|
||||
var formattedProject string = fmt.Sprintf("projects/%s", "[PROJECT]")
|
||||
var request = &pubsubpb.ListSnapshotsRequest{
|
||||
Project: formattedProject,
|
||||
}
|
||||
@@ -1584,7 +1599,7 @@ func TestSubscriberListSnapshotsError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockSubscriber.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var formattedProject string = SubscriberProjectPath("[PROJECT]")
|
||||
var formattedProject string = fmt.Sprintf("projects/%s", "[PROJECT]")
|
||||
var request = &pubsubpb.ListSnapshotsRequest{
|
||||
Project: formattedProject,
|
||||
}
|
||||
@@ -1616,8 +1631,8 @@ func TestSubscriberCreateSnapshot(t *testing.T) {
|
||||
|
||||
mockSubscriber.resps = append(mockSubscriber.resps[:0], expectedResponse)
|
||||
|
||||
var formattedName string = SubscriberSnapshotPath("[PROJECT]", "[SNAPSHOT]")
|
||||
var formattedSubscription string = SubscriberSubscriptionPath("[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedName string = fmt.Sprintf("projects/%s/snapshots/%s", "[PROJECT]", "[SNAPSHOT]")
|
||||
var formattedSubscription string = fmt.Sprintf("projects/%s/subscriptions/%s", "[PROJECT]", "[SUBSCRIPTION]")
|
||||
var request = &pubsubpb.CreateSnapshotRequest{
|
||||
Name: formattedName,
|
||||
Subscription: formattedSubscription,
|
||||
@@ -1647,8 +1662,8 @@ func TestSubscriberCreateSnapshotError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockSubscriber.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var formattedName string = SubscriberSnapshotPath("[PROJECT]", "[SNAPSHOT]")
|
||||
var formattedSubscription string = SubscriberSubscriptionPath("[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedName string = fmt.Sprintf("projects/%s/snapshots/%s", "[PROJECT]", "[SNAPSHOT]")
|
||||
var formattedSubscription string = fmt.Sprintf("projects/%s/subscriptions/%s", "[PROJECT]", "[SUBSCRIPTION]")
|
||||
var request = &pubsubpb.CreateSnapshotRequest{
|
||||
Name: formattedName,
|
||||
Subscription: formattedSubscription,
|
||||
@@ -1681,8 +1696,18 @@ func TestSubscriberUpdateSnapshot(t *testing.T) {
|
||||
|
||||
mockSubscriber.resps = append(mockSubscriber.resps[:0], expectedResponse)
|
||||
|
||||
var snapshot *pubsubpb.Snapshot = &pubsubpb.Snapshot{}
|
||||
var updateMask *field_maskpb.FieldMask = &field_maskpb.FieldMask{}
|
||||
var seconds int64 = 123456
|
||||
var expireTime = ×tamppb.Timestamp{
|
||||
Seconds: seconds,
|
||||
}
|
||||
var snapshot = &pubsubpb.Snapshot{
|
||||
ExpireTime: expireTime,
|
||||
}
|
||||
var pathsElement string = "expire_time"
|
||||
var paths = []string{pathsElement}
|
||||
var updateMask = &field_maskpb.FieldMask{
|
||||
Paths: paths,
|
||||
}
|
||||
var request = &pubsubpb.UpdateSnapshotRequest{
|
||||
Snapshot: snapshot,
|
||||
UpdateMask: updateMask,
|
||||
@@ -1712,8 +1737,18 @@ func TestSubscriberUpdateSnapshotError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockSubscriber.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var snapshot *pubsubpb.Snapshot = &pubsubpb.Snapshot{}
|
||||
var updateMask *field_maskpb.FieldMask = &field_maskpb.FieldMask{}
|
||||
var seconds int64 = 123456
|
||||
var expireTime = ×tamppb.Timestamp{
|
||||
Seconds: seconds,
|
||||
}
|
||||
var snapshot = &pubsubpb.Snapshot{
|
||||
ExpireTime: expireTime,
|
||||
}
|
||||
var pathsElement string = "expire_time"
|
||||
var paths = []string{pathsElement}
|
||||
var updateMask = &field_maskpb.FieldMask{
|
||||
Paths: paths,
|
||||
}
|
||||
var request = &pubsubpb.UpdateSnapshotRequest{
|
||||
Snapshot: snapshot,
|
||||
UpdateMask: updateMask,
|
||||
@@ -1741,7 +1776,7 @@ func TestSubscriberDeleteSnapshot(t *testing.T) {
|
||||
|
||||
mockSubscriber.resps = append(mockSubscriber.resps[:0], expectedResponse)
|
||||
|
||||
var formattedSnapshot string = SubscriberSnapshotPath("[PROJECT]", "[SNAPSHOT]")
|
||||
var formattedSnapshot string = fmt.Sprintf("projects/%s/snapshots/%s", "[PROJECT]", "[SNAPSHOT]")
|
||||
var request = &pubsubpb.DeleteSnapshotRequest{
|
||||
Snapshot: formattedSnapshot,
|
||||
}
|
||||
@@ -1767,7 +1802,7 @@ func TestSubscriberDeleteSnapshotError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockSubscriber.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var formattedSnapshot string = SubscriberSnapshotPath("[PROJECT]", "[SNAPSHOT]")
|
||||
var formattedSnapshot string = fmt.Sprintf("projects/%s/snapshots/%s", "[PROJECT]", "[SNAPSHOT]")
|
||||
var request = &pubsubpb.DeleteSnapshotRequest{
|
||||
Snapshot: formattedSnapshot,
|
||||
}
|
||||
@@ -1793,7 +1828,7 @@ func TestSubscriberSeek(t *testing.T) {
|
||||
|
||||
mockSubscriber.resps = append(mockSubscriber.resps[:0], expectedResponse)
|
||||
|
||||
var formattedSubscription string = SubscriberSubscriptionPath("[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedSubscription string = fmt.Sprintf("projects/%s/subscriptions/%s", "[PROJECT]", "[SUBSCRIPTION]")
|
||||
var request = &pubsubpb.SeekRequest{
|
||||
Subscription: formattedSubscription,
|
||||
}
|
||||
@@ -1822,7 +1857,7 @@ func TestSubscriberSeekError(t *testing.T) {
|
||||
errCode := codes.PermissionDenied
|
||||
mockSubscriber.err = gstatus.Error(errCode, "test error")
|
||||
|
||||
var formattedSubscription string = SubscriberSubscriptionPath("[PROJECT]", "[SUBSCRIPTION]")
|
||||
var formattedSubscription string = fmt.Sprintf("projects/%s/subscriptions/%s", "[PROJECT]", "[SUBSCRIPTION]")
|
||||
var request = &pubsubpb.SeekRequest{
|
||||
Subscription: formattedSubscription,
|
||||
}
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pubsub
|
||||
|
||||
// PublisherProjectPath returns the path for the project resource.
|
||||
//
|
||||
// Deprecated: Use
|
||||
// fmt.Sprintf("projects/%s", project)
|
||||
// instead.
|
||||
func PublisherProjectPath(project string) string {
|
||||
return "" +
|
||||
"projects/" +
|
||||
project +
|
||||
""
|
||||
}
|
||||
|
||||
// PublisherTopicPath returns the path for the topic resource.
|
||||
//
|
||||
// Deprecated: Use
|
||||
// fmt.Sprintf("projects/%s/topics/%s", project, topic)
|
||||
// instead.
|
||||
func PublisherTopicPath(project, topic string) string {
|
||||
return "" +
|
||||
"projects/" +
|
||||
project +
|
||||
"/topics/" +
|
||||
topic +
|
||||
""
|
||||
}
|
||||
|
||||
// SubscriberProjectPath returns the path for the project resource.
|
||||
//
|
||||
// Deprecated: Use
|
||||
// fmt.Sprintf("projects/%s", project)
|
||||
// instead.
|
||||
func SubscriberProjectPath(project string) string {
|
||||
return "" +
|
||||
"projects/" +
|
||||
project +
|
||||
""
|
||||
}
|
||||
|
||||
// SubscriberSnapshotPath returns the path for the snapshot resource.
|
||||
//
|
||||
// Deprecated: Use
|
||||
// fmt.Sprintf("projects/%s/snapshots/%s", project, snapshot)
|
||||
// instead.
|
||||
func SubscriberSnapshotPath(project, snapshot string) string {
|
||||
return "" +
|
||||
"projects/" +
|
||||
project +
|
||||
"/snapshots/" +
|
||||
snapshot +
|
||||
""
|
||||
}
|
||||
|
||||
// SubscriberSubscriptionPath returns the path for the subscription resource.
|
||||
//
|
||||
// Deprecated: Use
|
||||
// fmt.Sprintf("projects/%s/subscriptions/%s", project, subscription)
|
||||
// instead.
|
||||
func SubscriberSubscriptionPath(project, subscription string) string {
|
||||
return "" +
|
||||
"projects/" +
|
||||
project +
|
||||
"/subscriptions/" +
|
||||
subscription +
|
||||
""
|
||||
}
|
||||
|
||||
// SubscriberTopicPath returns the path for the topic resource.
|
||||
//
|
||||
// Deprecated: Use
|
||||
// fmt.Sprintf("projects/%s/topics/%s", project, topic)
|
||||
// instead.
|
||||
func SubscriberTopicPath(project, topic string) string {
|
||||
return "" +
|
||||
"projects/" +
|
||||
project +
|
||||
"/topics/" +
|
||||
topic +
|
||||
""
|
||||
}
|
||||
+12
-30
@@ -1,10 +1,10 @@
|
||||
// Copyright 2017, Google Inc. All rights reserved.
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@@ -105,8 +105,8 @@ type PublisherClient struct {
|
||||
// The call options for this service.
|
||||
CallOptions *PublisherCallOptions
|
||||
|
||||
// The metadata to be sent with each request.
|
||||
Metadata metadata.MD
|
||||
// The x-goog-* metadata to be sent with each request.
|
||||
xGoogMetadata metadata.MD
|
||||
}
|
||||
|
||||
// NewPublisherClient creates a new publisher client.
|
||||
@@ -145,25 +145,7 @@ func (c *PublisherClient) Close() error {
|
||||
func (c *PublisherClient) SetGoogleClientInfo(keyval ...string) {
|
||||
kv := append([]string{"gl-go", version.Go()}, keyval...)
|
||||
kv = append(kv, "gapic", version.Repo, "gax", gax.Version, "grpc", grpc.Version)
|
||||
c.Metadata = metadata.Pairs("x-goog-api-client", gax.XGoogHeader(kv...))
|
||||
}
|
||||
|
||||
// PublisherProjectPath returns the path for the project resource.
|
||||
func PublisherProjectPath(project string) string {
|
||||
return "" +
|
||||
"projects/" +
|
||||
project +
|
||||
""
|
||||
}
|
||||
|
||||
// PublisherTopicPath returns the path for the topic resource.
|
||||
func PublisherTopicPath(project, topic string) string {
|
||||
return "" +
|
||||
"projects/" +
|
||||
project +
|
||||
"/topics/" +
|
||||
topic +
|
||||
""
|
||||
c.xGoogMetadata = metadata.Pairs("x-goog-api-client", gax.XGoogHeader(kv...))
|
||||
}
|
||||
|
||||
func (c *PublisherClient) SubscriptionIAM(subscription *pubsubpb.Subscription) *iam.Handle {
|
||||
@@ -176,7 +158,7 @@ func (c *PublisherClient) TopicIAM(topic *pubsubpb.Topic) *iam.Handle {
|
||||
|
||||
// CreateTopic creates the given topic with the given name.
|
||||
func (c *PublisherClient) CreateTopic(ctx context.Context, req *pubsubpb.Topic, opts ...gax.CallOption) (*pubsubpb.Topic, error) {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.CreateTopic[0:len(c.CallOptions.CreateTopic):len(c.CallOptions.CreateTopic)], opts...)
|
||||
var resp *pubsubpb.Topic
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
@@ -197,7 +179,7 @@ func (c *PublisherClient) CreateTopic(ctx context.Context, req *pubsubpb.Topic,
|
||||
// corrected in V2. See
|
||||
// https://cloud.google.com/apis/design/standard_methods#update for details.
|
||||
func (c *PublisherClient) UpdateTopic(ctx context.Context, req *pubsubpb.UpdateTopicRequest, opts ...gax.CallOption) (*pubsubpb.Topic, error) {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.UpdateTopic[0:len(c.CallOptions.UpdateTopic):len(c.CallOptions.UpdateTopic)], opts...)
|
||||
var resp *pubsubpb.Topic
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
@@ -215,7 +197,7 @@ func (c *PublisherClient) UpdateTopic(ctx context.Context, req *pubsubpb.UpdateT
|
||||
// does not exist. The message payload must not be empty; it must contain
|
||||
// either a non-empty data field, or at least one attribute.
|
||||
func (c *PublisherClient) Publish(ctx context.Context, req *pubsubpb.PublishRequest, opts ...gax.CallOption) (*pubsubpb.PublishResponse, error) {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.Publish[0:len(c.CallOptions.Publish):len(c.CallOptions.Publish)], opts...)
|
||||
var resp *pubsubpb.PublishResponse
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
@@ -231,7 +213,7 @@ func (c *PublisherClient) Publish(ctx context.Context, req *pubsubpb.PublishRequ
|
||||
|
||||
// GetTopic gets the configuration of a topic.
|
||||
func (c *PublisherClient) GetTopic(ctx context.Context, req *pubsubpb.GetTopicRequest, opts ...gax.CallOption) (*pubsubpb.Topic, error) {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.GetTopic[0:len(c.CallOptions.GetTopic):len(c.CallOptions.GetTopic)], opts...)
|
||||
var resp *pubsubpb.Topic
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
@@ -247,7 +229,7 @@ func (c *PublisherClient) GetTopic(ctx context.Context, req *pubsubpb.GetTopicRe
|
||||
|
||||
// ListTopics lists matching topics.
|
||||
func (c *PublisherClient) ListTopics(ctx context.Context, req *pubsubpb.ListTopicsRequest, opts ...gax.CallOption) *TopicIterator {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.ListTopics[0:len(c.CallOptions.ListTopics):len(c.CallOptions.ListTopics)], opts...)
|
||||
it := &TopicIterator{}
|
||||
it.InternalFetch = func(pageSize int, pageToken string) ([]*pubsubpb.Topic, string, error) {
|
||||
@@ -282,7 +264,7 @@ func (c *PublisherClient) ListTopics(ctx context.Context, req *pubsubpb.ListTopi
|
||||
|
||||
// ListTopicSubscriptions lists the name of the subscriptions for this topic.
|
||||
func (c *PublisherClient) ListTopicSubscriptions(ctx context.Context, req *pubsubpb.ListTopicSubscriptionsRequest, opts ...gax.CallOption) *StringIterator {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.ListTopicSubscriptions[0:len(c.CallOptions.ListTopicSubscriptions):len(c.CallOptions.ListTopicSubscriptions)], opts...)
|
||||
it := &StringIterator{}
|
||||
it.InternalFetch = func(pageSize int, pageToken string) ([]string, string, error) {
|
||||
@@ -321,7 +303,7 @@ func (c *PublisherClient) ListTopicSubscriptions(ctx context.Context, req *pubsu
|
||||
// configuration or subscriptions. Existing subscriptions to this topic are
|
||||
// not deleted, but their topic field is set to _deleted-topic_.
|
||||
func (c *PublisherClient) DeleteTopic(ctx context.Context, req *pubsubpb.DeleteTopicRequest, opts ...gax.CallOption) error {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.DeleteTopic[0:len(c.CallOptions.DeleteTopic):len(c.CallOptions.DeleteTopic)], opts...)
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
|
||||
+9
-9
@@ -1,10 +1,10 @@
|
||||
// Copyright 2017, Google Inc. All rights reserved.
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@@ -75,7 +75,7 @@ func ExamplePublisherClient_CreateTopic() {
|
||||
}
|
||||
|
||||
req := &pubsubpb.Topic{
|
||||
// TODO: Fill request struct fields.
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.CreateTopic(ctx, req)
|
||||
if err != nil {
|
||||
@@ -93,7 +93,7 @@ func ExamplePublisherClient_UpdateTopic() {
|
||||
}
|
||||
|
||||
req := &pubsubpb.UpdateTopicRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.UpdateTopic(ctx, req)
|
||||
if err != nil {
|
||||
@@ -111,7 +111,7 @@ func ExamplePublisherClient_Publish() {
|
||||
}
|
||||
|
||||
req := &pubsubpb.PublishRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.Publish(ctx, req)
|
||||
if err != nil {
|
||||
@@ -129,7 +129,7 @@ func ExamplePublisherClient_GetTopic() {
|
||||
}
|
||||
|
||||
req := &pubsubpb.GetTopicRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.GetTopic(ctx, req)
|
||||
if err != nil {
|
||||
@@ -147,7 +147,7 @@ func ExamplePublisherClient_ListTopics() {
|
||||
}
|
||||
|
||||
req := &pubsubpb.ListTopicsRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
it := c.ListTopics(ctx, req)
|
||||
for {
|
||||
@@ -171,7 +171,7 @@ func ExamplePublisherClient_ListTopicSubscriptions() {
|
||||
}
|
||||
|
||||
req := &pubsubpb.ListTopicSubscriptionsRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
it := c.ListTopicSubscriptions(ctx, req)
|
||||
for {
|
||||
@@ -195,7 +195,7 @@ func ExamplePublisherClient_DeleteTopic() {
|
||||
}
|
||||
|
||||
req := &pubsubpb.DeleteTopicRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
err = c.DeleteTopic(ctx, req)
|
||||
if err != nil {
|
||||
|
||||
+20
-58
@@ -1,10 +1,10 @@
|
||||
// Copyright 2017, Google Inc. All rights reserved.
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@@ -134,8 +134,8 @@ type SubscriberClient struct {
|
||||
// The call options for this service.
|
||||
CallOptions *SubscriberCallOptions
|
||||
|
||||
// The metadata to be sent with each request.
|
||||
Metadata metadata.MD
|
||||
// The x-goog-* metadata to be sent with each request.
|
||||
xGoogMetadata metadata.MD
|
||||
}
|
||||
|
||||
// NewSubscriberClient creates a new subscriber client.
|
||||
@@ -174,45 +174,7 @@ func (c *SubscriberClient) Close() error {
|
||||
func (c *SubscriberClient) SetGoogleClientInfo(keyval ...string) {
|
||||
kv := append([]string{"gl-go", version.Go()}, keyval...)
|
||||
kv = append(kv, "gapic", version.Repo, "gax", gax.Version, "grpc", grpc.Version)
|
||||
c.Metadata = metadata.Pairs("x-goog-api-client", gax.XGoogHeader(kv...))
|
||||
}
|
||||
|
||||
// SubscriberProjectPath returns the path for the project resource.
|
||||
func SubscriberProjectPath(project string) string {
|
||||
return "" +
|
||||
"projects/" +
|
||||
project +
|
||||
""
|
||||
}
|
||||
|
||||
// SubscriberSnapshotPath returns the path for the snapshot resource.
|
||||
func SubscriberSnapshotPath(project, snapshot string) string {
|
||||
return "" +
|
||||
"projects/" +
|
||||
project +
|
||||
"/snapshots/" +
|
||||
snapshot +
|
||||
""
|
||||
}
|
||||
|
||||
// SubscriberSubscriptionPath returns the path for the subscription resource.
|
||||
func SubscriberSubscriptionPath(project, subscription string) string {
|
||||
return "" +
|
||||
"projects/" +
|
||||
project +
|
||||
"/subscriptions/" +
|
||||
subscription +
|
||||
""
|
||||
}
|
||||
|
||||
// SubscriberTopicPath returns the path for the topic resource.
|
||||
func SubscriberTopicPath(project, topic string) string {
|
||||
return "" +
|
||||
"projects/" +
|
||||
project +
|
||||
"/topics/" +
|
||||
topic +
|
||||
""
|
||||
c.xGoogMetadata = metadata.Pairs("x-goog-api-client", gax.XGoogHeader(kv...))
|
||||
}
|
||||
|
||||
func (c *SubscriberClient) SubscriptionIAM(subscription *pubsubpb.Subscription) *iam.Handle {
|
||||
@@ -234,7 +196,7 @@ func (c *SubscriberClient) TopicIAM(topic *pubsubpb.Topic) *iam.Handle {
|
||||
// The generated name is populated in the returned Subscription object.
|
||||
// Note that for REST API requests, you must specify a name in the request.
|
||||
func (c *SubscriberClient) CreateSubscription(ctx context.Context, req *pubsubpb.Subscription, opts ...gax.CallOption) (*pubsubpb.Subscription, error) {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.CreateSubscription[0:len(c.CallOptions.CreateSubscription):len(c.CallOptions.CreateSubscription)], opts...)
|
||||
var resp *pubsubpb.Subscription
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
@@ -250,7 +212,7 @@ func (c *SubscriberClient) CreateSubscription(ctx context.Context, req *pubsubpb
|
||||
|
||||
// GetSubscription gets the configuration details of a subscription.
|
||||
func (c *SubscriberClient) GetSubscription(ctx context.Context, req *pubsubpb.GetSubscriptionRequest, opts ...gax.CallOption) (*pubsubpb.Subscription, error) {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.GetSubscription[0:len(c.CallOptions.GetSubscription):len(c.CallOptions.GetSubscription)], opts...)
|
||||
var resp *pubsubpb.Subscription
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
@@ -271,7 +233,7 @@ func (c *SubscriberClient) GetSubscription(ctx context.Context, req *pubsubpb.Ge
|
||||
// corrected in V2. See
|
||||
// https://cloud.google.com/apis/design/standard_methods#update for details.
|
||||
func (c *SubscriberClient) UpdateSubscription(ctx context.Context, req *pubsubpb.UpdateSubscriptionRequest, opts ...gax.CallOption) (*pubsubpb.Subscription, error) {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.UpdateSubscription[0:len(c.CallOptions.UpdateSubscription):len(c.CallOptions.UpdateSubscription)], opts...)
|
||||
var resp *pubsubpb.Subscription
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
@@ -287,7 +249,7 @@ func (c *SubscriberClient) UpdateSubscription(ctx context.Context, req *pubsubpb
|
||||
|
||||
// ListSubscriptions lists matching subscriptions.
|
||||
func (c *SubscriberClient) ListSubscriptions(ctx context.Context, req *pubsubpb.ListSubscriptionsRequest, opts ...gax.CallOption) *SubscriptionIterator {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.ListSubscriptions[0:len(c.CallOptions.ListSubscriptions):len(c.CallOptions.ListSubscriptions)], opts...)
|
||||
it := &SubscriptionIterator{}
|
||||
it.InternalFetch = func(pageSize int, pageToken string) ([]*pubsubpb.Subscription, string, error) {
|
||||
@@ -326,7 +288,7 @@ func (c *SubscriberClient) ListSubscriptions(ctx context.Context, req *pubsubpb.
|
||||
// the same name, but the new one has no association with the old
|
||||
// subscription or its topic unless the same topic is specified.
|
||||
func (c *SubscriberClient) DeleteSubscription(ctx context.Context, req *pubsubpb.DeleteSubscriptionRequest, opts ...gax.CallOption) error {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.DeleteSubscription[0:len(c.CallOptions.DeleteSubscription):len(c.CallOptions.DeleteSubscription)], opts...)
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
@@ -342,7 +304,7 @@ func (c *SubscriberClient) DeleteSubscription(ctx context.Context, req *pubsubpb
|
||||
// processing was interrupted. Note that this does not modify the
|
||||
// subscription-level ackDeadlineSeconds used for subsequent messages.
|
||||
func (c *SubscriberClient) ModifyAckDeadline(ctx context.Context, req *pubsubpb.ModifyAckDeadlineRequest, opts ...gax.CallOption) error {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.ModifyAckDeadline[0:len(c.CallOptions.ModifyAckDeadline):len(c.CallOptions.ModifyAckDeadline)], opts...)
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
@@ -360,7 +322,7 @@ func (c *SubscriberClient) ModifyAckDeadline(ctx context.Context, req *pubsubpb.
|
||||
// but such a message may be redelivered later. Acknowledging a message more
|
||||
// than once will not result in an error.
|
||||
func (c *SubscriberClient) Acknowledge(ctx context.Context, req *pubsubpb.AcknowledgeRequest, opts ...gax.CallOption) error {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.Acknowledge[0:len(c.CallOptions.Acknowledge):len(c.CallOptions.Acknowledge)], opts...)
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
@@ -375,7 +337,7 @@ func (c *SubscriberClient) Acknowledge(ctx context.Context, req *pubsubpb.Acknow
|
||||
// there are too many concurrent pull requests pending for the given
|
||||
// subscription.
|
||||
func (c *SubscriberClient) Pull(ctx context.Context, req *pubsubpb.PullRequest, opts ...gax.CallOption) (*pubsubpb.PullResponse, error) {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.Pull[0:len(c.CallOptions.Pull):len(c.CallOptions.Pull)], opts...)
|
||||
var resp *pubsubpb.PullResponse
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
@@ -402,7 +364,7 @@ func (c *SubscriberClient) Pull(ctx context.Context, req *pubsubpb.PullRequest,
|
||||
// (e.g., a server restart). These should also be retried by the client. Flow
|
||||
// control can be achieved by configuring the underlying RPC channel.
|
||||
func (c *SubscriberClient) StreamingPull(ctx context.Context, opts ...gax.CallOption) (pubsubpb.Subscriber_StreamingPullClient, error) {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.StreamingPull[0:len(c.CallOptions.StreamingPull):len(c.CallOptions.StreamingPull)], opts...)
|
||||
var resp pubsubpb.Subscriber_StreamingPullClient
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
@@ -423,7 +385,7 @@ func (c *SubscriberClient) StreamingPull(ctx context.Context, opts ...gax.CallOp
|
||||
// attributes of a push subscription. Messages will accumulate for delivery
|
||||
// continuously through the call regardless of changes to the PushConfig.
|
||||
func (c *SubscriberClient) ModifyPushConfig(ctx context.Context, req *pubsubpb.ModifyPushConfigRequest, opts ...gax.CallOption) error {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.ModifyPushConfig[0:len(c.CallOptions.ModifyPushConfig):len(c.CallOptions.ModifyPushConfig)], opts...)
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
@@ -435,7 +397,7 @@ func (c *SubscriberClient) ModifyPushConfig(ctx context.Context, req *pubsubpb.M
|
||||
|
||||
// ListSnapshots lists the existing snapshots.
|
||||
func (c *SubscriberClient) ListSnapshots(ctx context.Context, req *pubsubpb.ListSnapshotsRequest, opts ...gax.CallOption) *SnapshotIterator {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.ListSnapshots[0:len(c.CallOptions.ListSnapshots):len(c.CallOptions.ListSnapshots)], opts...)
|
||||
it := &SnapshotIterator{}
|
||||
it.InternalFetch = func(pageSize int, pageToken string) ([]*pubsubpb.Snapshot, string, error) {
|
||||
@@ -479,7 +441,7 @@ func (c *SubscriberClient) ListSnapshots(ctx context.Context, req *pubsubpb.List
|
||||
// The generated name is populated in the returned Snapshot object.
|
||||
// Note that for REST API requests, you must specify a name in the request.
|
||||
func (c *SubscriberClient) CreateSnapshot(ctx context.Context, req *pubsubpb.CreateSnapshotRequest, opts ...gax.CallOption) (*pubsubpb.Snapshot, error) {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.CreateSnapshot[0:len(c.CallOptions.CreateSnapshot):len(c.CallOptions.CreateSnapshot)], opts...)
|
||||
var resp *pubsubpb.Snapshot
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
@@ -500,7 +462,7 @@ func (c *SubscriberClient) CreateSnapshot(ctx context.Context, req *pubsubpb.Cre
|
||||
// corrected in V2. See
|
||||
// https://cloud.google.com/apis/design/standard_methods#update for details.
|
||||
func (c *SubscriberClient) UpdateSnapshot(ctx context.Context, req *pubsubpb.UpdateSnapshotRequest, opts ...gax.CallOption) (*pubsubpb.Snapshot, error) {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.UpdateSnapshot[0:len(c.CallOptions.UpdateSnapshot):len(c.CallOptions.UpdateSnapshot)], opts...)
|
||||
var resp *pubsubpb.Snapshot
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
@@ -519,7 +481,7 @@ func (c *SubscriberClient) UpdateSnapshot(ctx context.Context, req *pubsubpb.Upd
|
||||
// created with the same name, but the new one has no association with the old
|
||||
// snapshot or its subscription, unless the same subscription is specified.
|
||||
func (c *SubscriberClient) DeleteSnapshot(ctx context.Context, req *pubsubpb.DeleteSnapshotRequest, opts ...gax.CallOption) error {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.DeleteSnapshot[0:len(c.CallOptions.DeleteSnapshot):len(c.CallOptions.DeleteSnapshot)], opts...)
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
var err error
|
||||
@@ -532,7 +494,7 @@ func (c *SubscriberClient) DeleteSnapshot(ctx context.Context, req *pubsubpb.Del
|
||||
// Seek seeks an existing subscription to a point in time or to a given snapshot,
|
||||
// whichever is provided in the request.
|
||||
func (c *SubscriberClient) Seek(ctx context.Context, req *pubsubpb.SeekRequest, opts ...gax.CallOption) (*pubsubpb.SeekResponse, error) {
|
||||
ctx = insertMetadata(ctx, c.Metadata)
|
||||
ctx = insertMetadata(ctx, c.xGoogMetadata)
|
||||
opts = append(c.CallOptions.Seek[0:len(c.CallOptions.Seek):len(c.CallOptions.Seek)], opts...)
|
||||
var resp *pubsubpb.SeekResponse
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, settings gax.CallSettings) error {
|
||||
|
||||
+17
-17
@@ -1,10 +1,10 @@
|
||||
// Copyright 2017, Google Inc. All rights reserved.
|
||||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@@ -77,7 +77,7 @@ func ExampleSubscriberClient_CreateSubscription() {
|
||||
}
|
||||
|
||||
req := &pubsubpb.Subscription{
|
||||
// TODO: Fill request struct fields.
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.CreateSubscription(ctx, req)
|
||||
if err != nil {
|
||||
@@ -95,7 +95,7 @@ func ExampleSubscriberClient_GetSubscription() {
|
||||
}
|
||||
|
||||
req := &pubsubpb.GetSubscriptionRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.GetSubscription(ctx, req)
|
||||
if err != nil {
|
||||
@@ -113,7 +113,7 @@ func ExampleSubscriberClient_UpdateSubscription() {
|
||||
}
|
||||
|
||||
req := &pubsubpb.UpdateSubscriptionRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.UpdateSubscription(ctx, req)
|
||||
if err != nil {
|
||||
@@ -131,7 +131,7 @@ func ExampleSubscriberClient_ListSubscriptions() {
|
||||
}
|
||||
|
||||
req := &pubsubpb.ListSubscriptionsRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
it := c.ListSubscriptions(ctx, req)
|
||||
for {
|
||||
@@ -155,7 +155,7 @@ func ExampleSubscriberClient_DeleteSubscription() {
|
||||
}
|
||||
|
||||
req := &pubsubpb.DeleteSubscriptionRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
err = c.DeleteSubscription(ctx, req)
|
||||
if err != nil {
|
||||
@@ -171,7 +171,7 @@ func ExampleSubscriberClient_ModifyAckDeadline() {
|
||||
}
|
||||
|
||||
req := &pubsubpb.ModifyAckDeadlineRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
err = c.ModifyAckDeadline(ctx, req)
|
||||
if err != nil {
|
||||
@@ -187,7 +187,7 @@ func ExampleSubscriberClient_Acknowledge() {
|
||||
}
|
||||
|
||||
req := &pubsubpb.AcknowledgeRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
err = c.Acknowledge(ctx, req)
|
||||
if err != nil {
|
||||
@@ -203,7 +203,7 @@ func ExampleSubscriberClient_Pull() {
|
||||
}
|
||||
|
||||
req := &pubsubpb.PullRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.Pull(ctx, req)
|
||||
if err != nil {
|
||||
@@ -225,7 +225,7 @@ func ExampleSubscriberClient_StreamingPull() {
|
||||
}
|
||||
go func() {
|
||||
reqs := []*pubsubpb.StreamingPullRequest{
|
||||
// TODO: Create requests.
|
||||
// TODO: Create requests.
|
||||
}
|
||||
for _, req := range reqs {
|
||||
if err := stream.Send(req); err != nil {
|
||||
@@ -255,7 +255,7 @@ func ExampleSubscriberClient_ModifyPushConfig() {
|
||||
}
|
||||
|
||||
req := &pubsubpb.ModifyPushConfigRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
err = c.ModifyPushConfig(ctx, req)
|
||||
if err != nil {
|
||||
@@ -271,7 +271,7 @@ func ExampleSubscriberClient_ListSnapshots() {
|
||||
}
|
||||
|
||||
req := &pubsubpb.ListSnapshotsRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
it := c.ListSnapshots(ctx, req)
|
||||
for {
|
||||
@@ -295,7 +295,7 @@ func ExampleSubscriberClient_CreateSnapshot() {
|
||||
}
|
||||
|
||||
req := &pubsubpb.CreateSnapshotRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.CreateSnapshot(ctx, req)
|
||||
if err != nil {
|
||||
@@ -313,7 +313,7 @@ func ExampleSubscriberClient_UpdateSnapshot() {
|
||||
}
|
||||
|
||||
req := &pubsubpb.UpdateSnapshotRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.UpdateSnapshot(ctx, req)
|
||||
if err != nil {
|
||||
@@ -331,7 +331,7 @@ func ExampleSubscriberClient_DeleteSnapshot() {
|
||||
}
|
||||
|
||||
req := &pubsubpb.DeleteSnapshotRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
err = c.DeleteSnapshot(ctx, req)
|
||||
if err != nil {
|
||||
@@ -347,7 +347,7 @@ func ExampleSubscriberClient_Seek() {
|
||||
}
|
||||
|
||||
req := &pubsubpb.SeekRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.Seek(ctx, req)
|
||||
if err != nil {
|
||||
|
||||
+1
-2
@@ -20,7 +20,6 @@ import (
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -147,7 +146,7 @@ loop:
|
||||
wg.Wait()
|
||||
ok := true
|
||||
for i, con := range consumers {
|
||||
if got, want := con.counts, wantCounts; !reflect.DeepEqual(got, want) {
|
||||
if got, want := con.counts, wantCounts; !testutil.Equal(got, want) {
|
||||
t.Errorf("%d: message counts: %v\n", i, diff(got, want))
|
||||
ok = false
|
||||
}
|
||||
|
||||
+88
@@ -279,3 +279,91 @@ func ExampleSubscription_Update() {
|
||||
}
|
||||
_ = subConfig // TODO: Use SubscriptionConfig.
|
||||
}
|
||||
|
||||
func ExampleSubscription_CreateSnapshot() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
sub := client.Subscription("subName")
|
||||
snapConfig, err := sub.CreateSnapshot(ctx, "snapshotName")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
_ = snapConfig // TODO: Use SnapshotConfig.
|
||||
}
|
||||
|
||||
func ExampleSubscription_SeekToSnapshot() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
sub := client.Subscription("subName")
|
||||
snap := client.Snapshot("snapshotName")
|
||||
if err := sub.SeekToSnapshot(ctx, snap); err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleSubscription_SeekToTime() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
sub := client.Subscription("subName")
|
||||
if err := sub.SeekToTime(ctx, time.Now().Add(-time.Hour)); err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleSnapshot_Delete() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
snap := client.Snapshot("snapshotName")
|
||||
if err := snap.Delete(ctx); err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleClient_Snapshots() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// List all snapshots for the project.
|
||||
iter := client.Snapshots(ctx)
|
||||
_ = iter // TODO: iterate using Next.
|
||||
}
|
||||
|
||||
func ExampleSnapshotConfigIterator_Next() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// List all snapshots for the project.
|
||||
iter := client.Snapshots(ctx)
|
||||
for {
|
||||
snapConfig, err := iter.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
_ = snapConfig // TODO: use the SnapshotConfig.
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(jba): write an example for PublishResult.Ready
|
||||
// TODO(jba): write an example for Subscription.IAM
|
||||
// TODO(jba): write an example for Topic.IAM
|
||||
// TODO(jba): write an example for Topic.Stop
|
||||
|
||||
+181
-6
@@ -15,17 +15,23 @@
|
||||
package pubsub
|
||||
|
||||
// This file provides a fake/mock in-memory pubsub server.
|
||||
// (Really just a mock at the moment, but we hope to turn it into
|
||||
// more of a fake.)
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/internal/testutil"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
durpb "github.com/golang/protobuf/ptypes/duration"
|
||||
emptypb "github.com/golang/protobuf/ptypes/empty"
|
||||
"golang.org/x/net/context"
|
||||
pb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
type fakeServer struct {
|
||||
@@ -39,6 +45,8 @@ type fakeServer struct {
|
||||
Deadlines map[string]int32 // deadlines by message ID
|
||||
pullResponses []*pullResponse
|
||||
wg sync.WaitGroup
|
||||
subs map[string]*pb.Subscription
|
||||
topics map[string]*pb.Topic
|
||||
}
|
||||
|
||||
type pullResponse struct {
|
||||
@@ -55,6 +63,8 @@ func newFakeServer() (*fakeServer, error) {
|
||||
Addr: srv.Addr,
|
||||
Acked: map[string]bool{},
|
||||
Deadlines: map[string]int32{},
|
||||
subs: map[string]*pb.Subscription{},
|
||||
topics: map[string]*pb.Topic{},
|
||||
}
|
||||
pb.RegisterPublisherServer(srv.Gsrv, fake)
|
||||
pb.RegisterSubscriberServer(srv.Gsrv, fake)
|
||||
@@ -138,10 +148,175 @@ func (s *fakeServer) StreamingPull(stream pb.Subscriber_StreamingPullServer) err
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
minMessageRetentionDuration = 10 * time.Minute
|
||||
maxMessageRetentionDuration = 168 * time.Hour
|
||||
)
|
||||
|
||||
var defaultMessageRetentionDuration = ptypes.DurationProto(maxMessageRetentionDuration)
|
||||
|
||||
func checkMRD(pmrd *durpb.Duration) error {
|
||||
mrd, err := ptypes.Duration(pmrd)
|
||||
if err != nil || mrd < minMessageRetentionDuration || mrd > maxMessageRetentionDuration {
|
||||
return grpc.Errorf(codes.InvalidArgument, "bad message_retention_duration %+v", pmrd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkAckDeadline(ads int32) error {
|
||||
if ads < 10 || ads > 600 {
|
||||
// PubSub service returns Unknown.
|
||||
return grpc.Errorf(codes.Unknown, "bad ack_deadline_seconds: %d", ads)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *fakeServer) CreateSubscription(ctx context.Context, sub *pb.Subscription) (*pb.Subscription, error) {
|
||||
if s.subs[sub.Name] != nil {
|
||||
return nil, grpc.Errorf(codes.AlreadyExists, "subscription %q", sub.Name)
|
||||
}
|
||||
sub2 := proto.Clone(sub).(*pb.Subscription)
|
||||
if err := checkAckDeadline(sub.AckDeadlineSeconds); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sub.MessageRetentionDuration == nil {
|
||||
sub2.MessageRetentionDuration = defaultMessageRetentionDuration
|
||||
}
|
||||
if err := checkMRD(sub2.MessageRetentionDuration); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sub.PushConfig == nil {
|
||||
sub2.PushConfig = &pb.PushConfig{}
|
||||
}
|
||||
s.subs[sub.Name] = sub2
|
||||
return sub2, nil
|
||||
}
|
||||
|
||||
func (s *fakeServer) GetSubscription(ctx context.Context, req *pb.GetSubscriptionRequest) (*pb.Subscription, error) {
|
||||
return &pb.Subscription{
|
||||
Name: req.Subscription,
|
||||
AckDeadlineSeconds: 10,
|
||||
PushConfig: &pb.PushConfig{},
|
||||
if sub := s.subs[req.Subscription]; sub != nil {
|
||||
return sub, nil
|
||||
}
|
||||
return nil, grpc.Errorf(codes.NotFound, "subscription %q", req.Subscription)
|
||||
}
|
||||
|
||||
func (s *fakeServer) UpdateSubscription(ctx context.Context, req *pb.UpdateSubscriptionRequest) (*pb.Subscription, error) {
|
||||
sub := s.subs[req.Subscription.Name]
|
||||
if sub == nil {
|
||||
return nil, grpc.Errorf(codes.NotFound, "subscription %q", req.Subscription.Name)
|
||||
}
|
||||
for _, path := range req.UpdateMask.Paths {
|
||||
switch path {
|
||||
case "push_config":
|
||||
sub.PushConfig = req.Subscription.PushConfig
|
||||
|
||||
case "ack_deadline_seconds":
|
||||
a := req.Subscription.AckDeadlineSeconds
|
||||
if err := checkAckDeadline(a); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sub.AckDeadlineSeconds = a
|
||||
|
||||
case "retain_acked_messages":
|
||||
sub.RetainAckedMessages = req.Subscription.RetainAckedMessages
|
||||
|
||||
case "message_retention_duration":
|
||||
if err := checkMRD(req.Subscription.MessageRetentionDuration); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sub.MessageRetentionDuration = req.Subscription.MessageRetentionDuration
|
||||
|
||||
// TODO(jba): labels
|
||||
default:
|
||||
return nil, grpc.Errorf(codes.InvalidArgument, "unknown field name %q", path)
|
||||
}
|
||||
}
|
||||
return sub, nil
|
||||
}
|
||||
|
||||
func (s *fakeServer) DeleteSubscription(_ context.Context, req *pb.DeleteSubscriptionRequest) (*emptypb.Empty, error) {
|
||||
if s.subs[req.Subscription] == nil {
|
||||
return nil, grpc.Errorf(codes.NotFound, "subscription %q", req.Subscription)
|
||||
}
|
||||
delete(s.subs, req.Subscription)
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *fakeServer) CreateTopic(_ context.Context, t *pb.Topic) (*pb.Topic, error) {
|
||||
if s.topics[t.Name] != nil {
|
||||
return nil, grpc.Errorf(codes.AlreadyExists, "topic %q", t.Name)
|
||||
}
|
||||
t2 := proto.Clone(t).(*pb.Topic)
|
||||
s.topics[t.Name] = t2
|
||||
return t2, nil
|
||||
}
|
||||
|
||||
func (s *fakeServer) GetTopic(_ context.Context, req *pb.GetTopicRequest) (*pb.Topic, error) {
|
||||
if t := s.topics[req.Topic]; t != nil {
|
||||
return t, nil
|
||||
}
|
||||
return nil, grpc.Errorf(codes.NotFound, "topic %q", req.Topic)
|
||||
}
|
||||
|
||||
func (s *fakeServer) DeleteTopic(_ context.Context, req *pb.DeleteTopicRequest) (*emptypb.Empty, error) {
|
||||
if s.topics[req.Topic] == nil {
|
||||
return nil, grpc.Errorf(codes.NotFound, "topic %q", req.Topic)
|
||||
}
|
||||
delete(s.topics, req.Topic)
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *fakeServer) ListTopics(_ context.Context, req *pb.ListTopicsRequest) (*pb.ListTopicsResponse, error) {
|
||||
var names []string
|
||||
for n := range s.topics {
|
||||
if strings.HasPrefix(n, req.Project) {
|
||||
names = append(names, n)
|
||||
}
|
||||
}
|
||||
sort.Strings(names)
|
||||
from, to, nextToken, err := testutil.PageBounds(int(req.PageSize), req.PageToken, len(names))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := &pb.ListTopicsResponse{NextPageToken: nextToken}
|
||||
for i := from; i < to; i++ {
|
||||
res.Topics = append(res.Topics, s.topics[names[i]])
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *fakeServer) ListSubscriptions(_ context.Context, req *pb.ListSubscriptionsRequest) (*pb.ListSubscriptionsResponse, error) {
|
||||
var names []string
|
||||
for _, sub := range s.subs {
|
||||
if strings.HasPrefix(sub.Name, req.Project) {
|
||||
names = append(names, sub.Name)
|
||||
}
|
||||
}
|
||||
sort.Strings(names)
|
||||
from, to, nextToken, err := testutil.PageBounds(int(req.PageSize), req.PageToken, len(names))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := &pb.ListSubscriptionsResponse{NextPageToken: nextToken}
|
||||
for i := from; i < to; i++ {
|
||||
res.Subscriptions = append(res.Subscriptions, s.subs[names[i]])
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *fakeServer) ListTopicSubscriptions(_ context.Context, req *pb.ListTopicSubscriptionsRequest) (*pb.ListTopicSubscriptionsResponse, error) {
|
||||
var names []string
|
||||
for _, sub := range s.subs {
|
||||
if sub.Topic == req.Topic {
|
||||
names = append(names, sub.Name)
|
||||
}
|
||||
}
|
||||
sort.Strings(names)
|
||||
from, to, nextToken, err := testutil.PageBounds(int(req.PageSize), req.PageToken, len(names))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pb.ListTopicSubscriptionsResponse{
|
||||
Subscriptions: names[from:to],
|
||||
NextPageToken: nextToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
+110
-25
@@ -16,7 +16,6 @@ package pubsub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -29,6 +28,8 @@ import (
|
||||
"cloud.google.com/go/internal/testutil"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -149,7 +150,7 @@ func TestAll(t *testing.T) {
|
||||
md := extractMessageData(m)
|
||||
got[md.ID] = md
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
if !testutil.Equal(got, want) {
|
||||
t.Errorf("messages: got: %v ; want: %v", got, want)
|
||||
}
|
||||
|
||||
@@ -160,14 +161,14 @@ func TestAll(t *testing.T) {
|
||||
t.Errorf("sub IAM: %s", msg)
|
||||
}
|
||||
|
||||
snap, err := sub.createSnapshot(ctx, "")
|
||||
snap, err := sub.CreateSnapshot(ctx, "")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateSnapshot error: %v", err)
|
||||
}
|
||||
|
||||
timeoutCtx, _ = context.WithTimeout(ctx, time.Minute)
|
||||
err = internal.Retry(timeoutCtx, gax.Backoff{}, func() (bool, error) {
|
||||
snapIt := client.snapshots(timeoutCtx)
|
||||
snapIt := client.Snapshots(timeoutCtx)
|
||||
for {
|
||||
s, err := snapIt.Next()
|
||||
if err == nil && s.name == snap.name {
|
||||
@@ -186,7 +187,7 @@ func TestAll(t *testing.T) {
|
||||
}
|
||||
|
||||
err = internal.Retry(timeoutCtx, gax.Backoff{}, func() (bool, error) {
|
||||
err := sub.seekToSnapshot(timeoutCtx, snap.snapshot)
|
||||
err := sub.SeekToSnapshot(timeoutCtx, snap.Snapshot)
|
||||
return err == nil, err
|
||||
})
|
||||
if err != nil {
|
||||
@@ -194,7 +195,7 @@ func TestAll(t *testing.T) {
|
||||
}
|
||||
|
||||
err = internal.Retry(timeoutCtx, gax.Backoff{}, func() (bool, error) {
|
||||
err := sub.seekToTime(timeoutCtx, time.Now())
|
||||
err := sub.SeekToTime(timeoutCtx, time.Now())
|
||||
return err == nil, err
|
||||
})
|
||||
if err != nil {
|
||||
@@ -202,8 +203,8 @@ func TestAll(t *testing.T) {
|
||||
}
|
||||
|
||||
err = internal.Retry(timeoutCtx, gax.Backoff{}, func() (bool, error) {
|
||||
snapHandle := client.snapshot(snap.ID())
|
||||
err := snapHandle.delete(timeoutCtx)
|
||||
snapHandle := client.Snapshot(snap.ID())
|
||||
err := snapHandle.Delete(timeoutCtx)
|
||||
return err == nil, err
|
||||
})
|
||||
if err != nil {
|
||||
@@ -247,7 +248,7 @@ func testIAM(ctx context.Context, h *iam.Handle, permission string) (msg string,
|
||||
if policy, err = h.Policy(ctx); err != nil {
|
||||
return fmt.Sprintf("Policy: %v", err), false
|
||||
}
|
||||
if got, want := policy.Members(iam.Viewer), []string{member}; !reflect.DeepEqual(got, want) {
|
||||
if got, want := policy.Members(iam.Viewer), []string{member}; !testutil.Equal(got, want) {
|
||||
return fmt.Sprintf("after Add: got %v, want %v", got, want), false
|
||||
}
|
||||
// Now remove that member, set the policy, and check that it's empty again.
|
||||
@@ -271,7 +272,7 @@ func testIAM(ctx context.Context, h *iam.Handle, permission string) (msg string,
|
||||
if err != nil {
|
||||
return fmt.Sprintf("TestPermissions: %v", err), false
|
||||
}
|
||||
if !reflect.DeepEqual(gotPerms, wantPerms) {
|
||||
if !testutil.Equal(gotPerms, wantPerms) {
|
||||
return fmt.Sprintf("TestPermissions: got %v, want %v", gotPerms, wantPerms), false
|
||||
}
|
||||
return "", true
|
||||
@@ -296,40 +297,62 @@ func TestSubscriptionUpdate(t *testing.T) {
|
||||
}
|
||||
defer sub.Delete(ctx)
|
||||
|
||||
sc, err := sub.Config(ctx)
|
||||
got, err := sub.Config(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(sc.PushConfig, PushConfig{}) {
|
||||
t.Fatalf("got %+v, want empty PushConfig")
|
||||
want := SubscriptionConfig{
|
||||
Topic: topic,
|
||||
AckDeadline: 10 * time.Second,
|
||||
RetainAckedMessages: false,
|
||||
RetentionDuration: defaultRetentionDuration,
|
||||
}
|
||||
// Add a PushConfig.
|
||||
if !testutil.Equal(got, want) {
|
||||
t.Fatalf("\ngot %+v\nwant %+v", got, want)
|
||||
}
|
||||
// Add a PushConfig and change other fields.
|
||||
projID := testutil.ProjID()
|
||||
pc := PushConfig{
|
||||
Endpoint: "https://" + projID + ".appspot.com/_ah/push-handlers/push",
|
||||
Attributes: map[string]string{"x-goog-version": "v1"},
|
||||
}
|
||||
sc, err = sub.Update(ctx, SubscriptionConfigToUpdate{PushConfig: &pc})
|
||||
got, err = sub.Update(ctx, SubscriptionConfigToUpdate{
|
||||
PushConfig: &pc,
|
||||
AckDeadline: 2 * time.Minute,
|
||||
RetainAckedMessages: true,
|
||||
RetentionDuration: 2 * time.Hour,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Despite the docs which say that Get always returns a valid "x-goog-version"
|
||||
// attribute, none is returned. See
|
||||
// https://cloud.google.com/pubsub/docs/reference/rpc/google.pubsub.v1#google.pubsub.v1.PushConfig
|
||||
pc.Attributes = nil
|
||||
if got, want := sc.PushConfig, pc; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("setting push config: got\n%+v\nwant\n%+v", got, want)
|
||||
want = SubscriptionConfig{
|
||||
Topic: topic,
|
||||
PushConfig: pc,
|
||||
AckDeadline: 2 * time.Minute,
|
||||
RetainAckedMessages: true,
|
||||
RetentionDuration: 2 * time.Hour,
|
||||
}
|
||||
if !testutil.Equal(got, want) {
|
||||
t.Fatalf("\ngot %+v\nwant %+v", got, want)
|
||||
}
|
||||
// Remove the PushConfig, turning the subscription back into pull mode.
|
||||
// Change AckDeadline, but nothing else.
|
||||
pc = PushConfig{}
|
||||
sc, err = sub.Update(ctx, SubscriptionConfigToUpdate{PushConfig: &pc})
|
||||
got, err = sub.Update(ctx, SubscriptionConfigToUpdate{
|
||||
PushConfig: &pc,
|
||||
AckDeadline: 30 * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := sc.PushConfig, pc; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("removing push config: got\n%+v\nwant %+v", got, want)
|
||||
want.PushConfig = pc
|
||||
want.AckDeadline = 30 * time.Second
|
||||
// service issue: PushConfig attributes are not removed.
|
||||
// TODO(jba): remove when issue resolved.
|
||||
want.PushConfig.Attributes = map[string]string{"x-goog-version": "v1"}
|
||||
if !testutil.Equal(got, want) {
|
||||
t.Fatalf("\ngot %+v\nwant %+v", got, want)
|
||||
}
|
||||
|
||||
// If nothing changes, our client returns an error.
|
||||
_, err = sub.Update(ctx, SubscriptionConfigToUpdate{})
|
||||
if err == nil {
|
||||
@@ -361,3 +384,65 @@ func TestPublicTopic(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegration_Errors(t *testing.T) {
|
||||
// Test various edge conditions.
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
client := integrationTestClient(t, ctx)
|
||||
defer client.Close()
|
||||
|
||||
topic, err := client.CreateTopic(ctx, topicIDs.New())
|
||||
if err != nil {
|
||||
t.Fatalf("CreateTopic error: %v", err)
|
||||
}
|
||||
defer topic.Stop()
|
||||
defer topic.Delete(ctx)
|
||||
|
||||
// Out-of-range retention duration.
|
||||
sub, err := client.CreateSubscription(ctx, subIDs.New(), SubscriptionConfig{
|
||||
Topic: topic,
|
||||
RetentionDuration: 1 * time.Second,
|
||||
})
|
||||
if want := codes.InvalidArgument; grpc.Code(err) != want {
|
||||
t.Errorf("got <%v>, want %s", err, want)
|
||||
}
|
||||
if err == nil {
|
||||
sub.Delete(ctx)
|
||||
}
|
||||
|
||||
// Ack deadline less than minimum.
|
||||
sub, err = client.CreateSubscription(ctx, subIDs.New(), SubscriptionConfig{
|
||||
Topic: topic,
|
||||
AckDeadline: 5 * time.Second,
|
||||
})
|
||||
if want := codes.Unknown; grpc.Code(err) != want {
|
||||
t.Errorf("got <%v>, want %s", err, want)
|
||||
}
|
||||
if err == nil {
|
||||
sub.Delete(ctx)
|
||||
}
|
||||
|
||||
// Updating a non-existent subscription.
|
||||
sub = client.Subscription(subIDs.New())
|
||||
_, err = sub.Update(ctx, SubscriptionConfigToUpdate{AckDeadline: 20 * time.Second})
|
||||
if want := codes.NotFound; grpc.Code(err) != want {
|
||||
t.Errorf("got <%v>, want %s", err, want)
|
||||
}
|
||||
// Deleting a non-existent subscription.
|
||||
err = sub.Delete(ctx)
|
||||
if want := codes.NotFound; grpc.Code(err) != want {
|
||||
t.Errorf("got <%v>, want %s", err, want)
|
||||
}
|
||||
|
||||
// Updating out-of-range retention duration.
|
||||
sub, err = client.CreateSubscription(ctx, subIDs.New(), SubscriptionConfig{Topic: topic})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer sub.Delete(ctx)
|
||||
_, err = sub.Update(ctx, SubscriptionConfigToUpdate{RetentionDuration: 1000 * time.Hour})
|
||||
if want := codes.InvalidArgument; grpc.Code(err) != want {
|
||||
t.Errorf("got <%v>, want %s", err, want)
|
||||
}
|
||||
}
|
||||
|
||||
+68
-51
@@ -18,6 +18,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
vkit "cloud.google.com/go/pubsub/apiv1"
|
||||
"golang.org/x/net/context"
|
||||
pb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
)
|
||||
@@ -26,16 +27,15 @@ import (
|
||||
// when it is no longer needed.
|
||||
// subName is the full name of the subscription to pull messages from.
|
||||
// ctx is the context to use for acking messages and extending message deadlines.
|
||||
func newMessageIterator(ctx context.Context, s service, subName string, po *pullOptions) *streamingMessageIterator {
|
||||
sp := s.newStreamingPuller(ctx, subName, int32(po.ackDeadline.Seconds()))
|
||||
_ = sp.open() // error stored in sp
|
||||
return newStreamingMessageIterator(ctx, sp, po)
|
||||
func newMessageIterator(ctx context.Context, subc *vkit.SubscriberClient, subName string, po *pullOptions) *streamingMessageIterator {
|
||||
ps := newPullStream(ctx, subc, subName, int32(po.ackDeadline.Seconds()))
|
||||
return newStreamingMessageIterator(ctx, ps, po)
|
||||
}
|
||||
|
||||
type streamingMessageIterator struct {
|
||||
ctx context.Context
|
||||
po *pullOptions
|
||||
sp *streamingPuller
|
||||
ps *pullStream
|
||||
kaTicker *time.Ticker // keep-alive (deadline extensions)
|
||||
ackTicker *time.Ticker // message acks
|
||||
nackTicker *time.Ticker // message nacks (more frequent than acks)
|
||||
@@ -47,10 +47,11 @@ type streamingMessageIterator struct {
|
||||
mu sync.Mutex
|
||||
keepAliveDeadlines map[string]time.Time
|
||||
pendingReq *pb.StreamingPullRequest
|
||||
err error // error from stream failure
|
||||
pendingModAcks map[string]int32 // ack IDs whose ack deadline is to be modified
|
||||
err error // error from stream failure
|
||||
}
|
||||
|
||||
func newStreamingMessageIterator(ctx context.Context, sp *streamingPuller, po *pullOptions) *streamingMessageIterator {
|
||||
func newStreamingMessageIterator(ctx context.Context, ps *pullStream, po *pullOptions) *streamingMessageIterator {
|
||||
// TODO: make kaTicker frequency more configurable. (ackDeadline - 5s) is a
|
||||
// reasonable default for now, because the minimum ack period is 10s. This
|
||||
// gives us 5s grace.
|
||||
@@ -62,7 +63,7 @@ func newStreamingMessageIterator(ctx context.Context, sp *streamingPuller, po *p
|
||||
nackTicker := time.NewTicker(100 * time.Millisecond)
|
||||
it := &streamingMessageIterator{
|
||||
ctx: ctx,
|
||||
sp: sp,
|
||||
ps: ps,
|
||||
po: po,
|
||||
kaTicker: kaTicker,
|
||||
ackTicker: ackTicker,
|
||||
@@ -72,6 +73,7 @@ func newStreamingMessageIterator(ctx context.Context, sp *streamingPuller, po *p
|
||||
drained: make(chan struct{}),
|
||||
keepAliveDeadlines: map[string]time.Time{},
|
||||
pendingReq: &pb.StreamingPullRequest{},
|
||||
pendingModAcks: map[string]int32{},
|
||||
}
|
||||
it.wg.Add(1)
|
||||
go it.sender()
|
||||
@@ -121,20 +123,11 @@ func (it *streamingMessageIterator) done(ackID string, ack bool) {
|
||||
if ack {
|
||||
it.pendingReq.AckIds = append(it.pendingReq.AckIds, ackID)
|
||||
} else {
|
||||
it.addDeadlineMod(ackID, 0) // Nack indicated by modifying the deadline to zero.
|
||||
it.pendingModAcks[ackID] = 0 // Nack indicated by modifying the deadline to zero.
|
||||
}
|
||||
it.checkDrained()
|
||||
}
|
||||
|
||||
// addDeadlineMod adds the ack ID to the pending request with the given deadline.
|
||||
//
|
||||
// Called with the lock held.
|
||||
func (it *streamingMessageIterator) addDeadlineMod(ackID string, deadlineSecs int32) {
|
||||
pr := it.pendingReq
|
||||
pr.ModifyDeadlineAckIds = append(pr.ModifyDeadlineAckIds, ackID)
|
||||
pr.ModifyDeadlineSeconds = append(pr.ModifyDeadlineSeconds, deadlineSecs)
|
||||
}
|
||||
|
||||
// fail is called when a stream method returns a permanent error.
|
||||
func (it *streamingMessageIterator) fail(err error) {
|
||||
it.mu.Lock()
|
||||
@@ -162,20 +155,33 @@ func (it *streamingMessageIterator) receive() ([]*Message, error) {
|
||||
return nil, err
|
||||
}
|
||||
// Receive messages from stream. This may block indefinitely.
|
||||
msgs, err := it.sp.fetchMessages()
|
||||
// The streamingPuller handles retries, so any error here
|
||||
// is fatal.
|
||||
res, err := it.ps.Recv()
|
||||
// The pullStream handles retries, so any error here is fatal.
|
||||
if err != nil {
|
||||
it.fail(err)
|
||||
return nil, err
|
||||
}
|
||||
// We received some messages. Remember them so we can
|
||||
// keep them alive.
|
||||
deadline := time.Now().Add(it.po.maxExtension)
|
||||
msgs, err := convertMessages(res.ReceivedMessages)
|
||||
if err != nil {
|
||||
it.fail(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We received some messages. Remember them so we can keep them alive. Also,
|
||||
// arrange for a receipt mod-ack (which will occur at the next firing of
|
||||
// nackTicker).
|
||||
maxExt := time.Now().Add(it.po.maxExtension)
|
||||
deadline := trunc32(int64(it.po.ackDeadline.Seconds()))
|
||||
it.mu.Lock()
|
||||
for _, m := range msgs {
|
||||
m.doneFunc = it.done
|
||||
it.keepAliveDeadlines[m.ackID] = deadline
|
||||
it.keepAliveDeadlines[m.ackID] = maxExt
|
||||
// The receipt mod-ack uses the subscription's configured ack deadline. Don't
|
||||
// change the mod-ack if one is already pending. This is possible if there
|
||||
// are retries.
|
||||
if _, ok := it.pendingModAcks[m.ackID]; !ok {
|
||||
it.pendingModAcks[m.ackID] = deadline
|
||||
}
|
||||
}
|
||||
it.mu.Unlock()
|
||||
return msgs, nil
|
||||
@@ -187,7 +193,7 @@ func (it *streamingMessageIterator) sender() {
|
||||
defer it.kaTicker.Stop()
|
||||
defer it.ackTicker.Stop()
|
||||
defer it.nackTicker.Stop()
|
||||
defer it.sp.closeSend()
|
||||
defer it.ps.CloseSend()
|
||||
|
||||
done := false
|
||||
for !done {
|
||||
@@ -206,28 +212,34 @@ func (it *streamingMessageIterator) sender() {
|
||||
// All outstanding messages have been marked done:
|
||||
// nothing left to do except send the final request.
|
||||
it.mu.Lock()
|
||||
send = (len(it.pendingReq.AckIds) > 0 || len(it.pendingReq.ModifyDeadlineAckIds) > 0)
|
||||
send = (len(it.pendingReq.AckIds) > 0 || len(it.pendingModAcks) > 0)
|
||||
done = true
|
||||
|
||||
case <-it.kaTicker.C:
|
||||
it.mu.Lock()
|
||||
send = it.handleKeepAlives()
|
||||
it.handleKeepAlives()
|
||||
send = (len(it.pendingModAcks) > 0)
|
||||
|
||||
case <-it.nackTicker.C:
|
||||
it.mu.Lock()
|
||||
send = (len(it.pendingReq.ModifyDeadlineAckIds) > 0)
|
||||
send = (len(it.pendingModAcks) > 0)
|
||||
|
||||
case <-it.ackTicker.C:
|
||||
it.mu.Lock()
|
||||
send = (len(it.pendingReq.AckIds) > 0)
|
||||
|
||||
}
|
||||
// Lock is held here.
|
||||
if send {
|
||||
req := it.pendingReq
|
||||
it.pendingReq = &pb.StreamingPullRequest{}
|
||||
modAcks := it.pendingModAcks
|
||||
it.pendingModAcks = map[string]int32{}
|
||||
it.mu.Unlock()
|
||||
err := it.sp.send(req)
|
||||
for id, s := range modAcks {
|
||||
req.ModifyDeadlineAckIds = append(req.ModifyDeadlineAckIds, id)
|
||||
req.ModifyDeadlineSeconds = append(req.ModifyDeadlineSeconds, s)
|
||||
}
|
||||
err := it.send(req)
|
||||
if err != nil {
|
||||
// The streamingPuller handles retries, so any error here
|
||||
// is fatal to the iterator.
|
||||
@@ -240,32 +252,37 @@ func (it *streamingMessageIterator) sender() {
|
||||
}
|
||||
}
|
||||
|
||||
// handleKeepAlives modifies the pending request to include deadline extensions
|
||||
// for live messages. It also purges expired messages. It reports whether
|
||||
// there were any live messages.
|
||||
//
|
||||
// Called with the lock held.
|
||||
func (it *streamingMessageIterator) handleKeepAlives() bool {
|
||||
live, expired := getKeepAliveAckIDs(it.keepAliveDeadlines)
|
||||
for _, e := range expired {
|
||||
delete(it.keepAliveDeadlines, e)
|
||||
func (it *streamingMessageIterator) send(req *pb.StreamingPullRequest) error {
|
||||
// Note: len(modAckIDs) == len(modSecs)
|
||||
var rest *pb.StreamingPullRequest
|
||||
for len(req.AckIds) > 0 || len(req.ModifyDeadlineAckIds) > 0 {
|
||||
req, rest = splitRequest(req, maxPayload)
|
||||
if err := it.ps.Send(req); err != nil {
|
||||
return err
|
||||
}
|
||||
req = rest
|
||||
}
|
||||
dl := trunc32(int64(it.po.ackDeadline.Seconds()))
|
||||
for _, m := range live {
|
||||
it.addDeadlineMod(m, dl)
|
||||
}
|
||||
it.checkDrained()
|
||||
return len(live) > 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func getKeepAliveAckIDs(items map[string]time.Time) (live, expired []string) {
|
||||
// handleKeepAlives modifies the pending request to include deadline extensions
|
||||
// for live messages. It also purges expired messages.
|
||||
//
|
||||
// Called with the lock held.
|
||||
func (it *streamingMessageIterator) handleKeepAlives() {
|
||||
now := time.Now()
|
||||
for id, expiry := range items {
|
||||
dl := trunc32(int64(it.po.ackDeadline.Seconds()))
|
||||
for id, expiry := range it.keepAliveDeadlines {
|
||||
if expiry.Before(now) {
|
||||
expired = append(expired, id)
|
||||
// This delete will not result in skipping any map items, as implied by
|
||||
// the spec at https://golang.org/ref/spec#For_statements, "For
|
||||
// statements with range clause", note 3, and stated explicitly at
|
||||
// https://groups.google.com/forum/#!msg/golang-nuts/UciASUb03Js/pzSq5iVFAQAJ.
|
||||
delete(it.keepAliveDeadlines, id)
|
||||
} else {
|
||||
live = append(live, id)
|
||||
// This will not overwrite a nack, because nacking removes the ID from keepAliveDeadlines.
|
||||
it.pendingModAcks[id] = dl
|
||||
}
|
||||
}
|
||||
return live, expired
|
||||
it.checkDrained()
|
||||
}
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
// Copyright 2018 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pstest_test
|
||||
|
||||
import (
|
||||
"cloud.google.com/go/pubsub"
|
||||
"cloud.google.com/go/pubsub/pstest"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func ExampleNewServer() {
|
||||
ctx := context.Background()
|
||||
// Start a fake server running locally.
|
||||
srv := pstest.NewServer()
|
||||
// Connect to the server without using TLS.
|
||||
conn, err := grpc.Dial(srv.Addr, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// Use the connection when creating a pubsub client.
|
||||
client, err := pubsub.NewClient(ctx, "project", option.WithGRPCConn(conn))
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
defer client.Close()
|
||||
_ = client // TODO: Use the client.
|
||||
}
|
||||
+751
@@ -0,0 +1,751 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package pstest provides a fake Cloud PubSub service for testing. It implements a
|
||||
// simplified form of the service, suitable for unit tests. It may behave
|
||||
// differently from the actual service in ways in which the service is
|
||||
// non-deterministic or unspecified: timing, delivery order, etc.
|
||||
//
|
||||
// This package is EXPERIMENTAL and is subject to change without notice.
|
||||
//
|
||||
// See the example for usage.
|
||||
package pstest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/internal/testutil"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
durpb "github.com/golang/protobuf/ptypes/duration"
|
||||
emptypb "github.com/golang/protobuf/ptypes/empty"
|
||||
"golang.org/x/net/context"
|
||||
pb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
// For testing. Note that even though changes to the now variable are atomic, a call
|
||||
// to the stored function can race with a change to that function. This could be a
|
||||
// problem if tests are run in parallel, or even if concurrent parts of the same test
|
||||
// change the value of the variable.
|
||||
var now atomic.Value
|
||||
|
||||
func init() {
|
||||
now.Store(time.Now)
|
||||
}
|
||||
|
||||
func timeNow() time.Time {
|
||||
return now.Load().(func() time.Time)()
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
Addr string // The address that the server is listening on.
|
||||
gServer gServer
|
||||
}
|
||||
|
||||
type gServer struct {
|
||||
pb.PublisherServer
|
||||
pb.SubscriberServer
|
||||
|
||||
mu sync.Mutex
|
||||
topics map[string]*topic
|
||||
subs map[string]*subscription
|
||||
msgs []*Message // all messages ever published
|
||||
msgsByID map[string]*Message
|
||||
wg sync.WaitGroup
|
||||
nextID int
|
||||
streamTimeout time.Duration
|
||||
}
|
||||
|
||||
// NewServer creates a new fake server running in the current process.
|
||||
func NewServer() *Server {
|
||||
srv, err := testutil.NewServer()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("pstest.NewServer: %v", err))
|
||||
}
|
||||
s := &Server{
|
||||
Addr: srv.Addr,
|
||||
gServer: gServer{
|
||||
topics: map[string]*topic{},
|
||||
subs: map[string]*subscription{},
|
||||
msgsByID: map[string]*Message{},
|
||||
},
|
||||
}
|
||||
pb.RegisterPublisherServer(srv.Gsrv, &s.gServer)
|
||||
pb.RegisterSubscriberServer(srv.Gsrv, &s.gServer)
|
||||
srv.Start()
|
||||
return s
|
||||
}
|
||||
|
||||
// Publish behaves as if the Publish RPC was called with a message with the given
|
||||
// data and attrs. It returns the ID of the message.
|
||||
// The topic will be created if it doesn't exist.
|
||||
//
|
||||
// Publish panics if there is an error, which is appropriate for testing.
|
||||
func (s *Server) Publish(topic string, data []byte, attrs map[string]string) string {
|
||||
_, _ = s.gServer.CreateTopic(nil, &pb.Topic{Name: topic})
|
||||
req := &pb.PublishRequest{
|
||||
Topic: topic,
|
||||
Messages: []*pb.PubsubMessage{{Data: data, Attributes: attrs}},
|
||||
}
|
||||
res, err := s.gServer.Publish(nil, req)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("pstest.Server.Publish: %v", err))
|
||||
}
|
||||
return res.MessageIds[0]
|
||||
}
|
||||
|
||||
// SetStreamTimeout sets the amount of time a stream will be active before it shuts
|
||||
// itself down. This mimics the real service's behavior of closing streams after 30
|
||||
// minutes. If SetStreamTimeout is never called or is passed zero, streams never shut
|
||||
// down.
|
||||
func (s *Server) SetStreamTimeout(d time.Duration) {
|
||||
s.gServer.mu.Lock()
|
||||
defer s.gServer.mu.Unlock()
|
||||
s.gServer.streamTimeout = d
|
||||
}
|
||||
|
||||
// A Message is a message that was published to the server.
|
||||
type Message struct {
|
||||
ID string
|
||||
Data []byte
|
||||
Attributes map[string]string
|
||||
PublishTime time.Time
|
||||
Deliveries int // number of times delivery of the message was attempted
|
||||
Acks int // number of acks received from clients
|
||||
|
||||
// protected by server mutex
|
||||
deliveries int
|
||||
acks int
|
||||
}
|
||||
|
||||
// Messages returns information about all messages ever published.
|
||||
func (s *Server) Messages() []*Message {
|
||||
s.gServer.mu.Lock()
|
||||
defer s.gServer.mu.Unlock()
|
||||
|
||||
var msgs []*Message
|
||||
for _, m := range s.gServer.msgs {
|
||||
m.Deliveries = m.deliveries
|
||||
m.Acks = m.acks
|
||||
msgs = append(msgs, m)
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
||||
// Message returns the message with the given ID, or nil if no message
|
||||
// with that ID was published.
|
||||
func (s *Server) Message(id string) *Message {
|
||||
s.gServer.mu.Lock()
|
||||
defer s.gServer.mu.Unlock()
|
||||
|
||||
m := s.gServer.msgsByID[id]
|
||||
if m != nil {
|
||||
m.Deliveries = m.deliveries
|
||||
m.Acks = m.acks
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Wait blocks until all server activity has completed.
|
||||
func (s *Server) Wait() {
|
||||
s.gServer.wg.Wait()
|
||||
}
|
||||
|
||||
func (s *gServer) CreateTopic(_ context.Context, t *pb.Topic) (*pb.Topic, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.topics[t.Name] != nil {
|
||||
return nil, grpc.Errorf(codes.AlreadyExists, "topic %q", t.Name)
|
||||
}
|
||||
top := newTopic(t)
|
||||
s.topics[t.Name] = top
|
||||
return top.proto, nil
|
||||
}
|
||||
|
||||
func (s *gServer) GetTopic(_ context.Context, req *pb.GetTopicRequest) (*pb.Topic, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if t := s.topics[req.Topic]; t != nil {
|
||||
return t.proto, nil
|
||||
}
|
||||
return nil, grpc.Errorf(codes.NotFound, "topic %q", req.Topic)
|
||||
}
|
||||
|
||||
func (s *gServer) UpdateTopic(_ context.Context, req *pb.UpdateTopicRequest) (*pb.Topic, error) {
|
||||
return nil, grpc.Errorf(codes.Unimplemented, "unimplemented")
|
||||
}
|
||||
|
||||
func (s *gServer) ListTopics(_ context.Context, req *pb.ListTopicsRequest) (*pb.ListTopicsResponse, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
var names []string
|
||||
for n := range s.topics {
|
||||
if strings.HasPrefix(n, req.Project) {
|
||||
names = append(names, n)
|
||||
}
|
||||
}
|
||||
sort.Strings(names)
|
||||
from, to, nextToken, err := testutil.PageBounds(int(req.PageSize), req.PageToken, len(names))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := &pb.ListTopicsResponse{NextPageToken: nextToken}
|
||||
for i := from; i < to; i++ {
|
||||
res.Topics = append(res.Topics, s.topics[names[i]].proto)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *gServer) ListTopicSubscriptions(_ context.Context, req *pb.ListTopicSubscriptionsRequest) (*pb.ListTopicSubscriptionsResponse, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
var names []string
|
||||
for name, sub := range s.subs {
|
||||
if sub.topic.proto.Name == req.Topic {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
sort.Strings(names)
|
||||
from, to, nextToken, err := testutil.PageBounds(int(req.PageSize), req.PageToken, len(names))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pb.ListTopicSubscriptionsResponse{
|
||||
Subscriptions: names[from:to],
|
||||
NextPageToken: nextToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *gServer) DeleteTopic(_ context.Context, req *pb.DeleteTopicRequest) (*emptypb.Empty, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
t := s.topics[req.Topic]
|
||||
if t == nil {
|
||||
return nil, grpc.Errorf(codes.NotFound, "topic %q", req.Topic)
|
||||
}
|
||||
t.stop()
|
||||
delete(s.topics, req.Topic)
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *gServer) CreateSubscription(_ context.Context, ps *pb.Subscription) (*pb.Subscription, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if ps.Name == "" {
|
||||
return nil, grpc.Errorf(codes.InvalidArgument, "missing name")
|
||||
}
|
||||
if s.subs[ps.Name] != nil {
|
||||
return nil, grpc.Errorf(codes.AlreadyExists, "subscription %q", ps.Name)
|
||||
}
|
||||
if ps.Topic == "" {
|
||||
return nil, grpc.Errorf(codes.InvalidArgument, "missing topic")
|
||||
}
|
||||
top := s.topics[ps.Topic]
|
||||
if top == nil {
|
||||
return nil, grpc.Errorf(codes.NotFound, "topic %q", ps.Topic)
|
||||
}
|
||||
if err := checkAckDeadline(ps.AckDeadlineSeconds); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ps.MessageRetentionDuration == nil {
|
||||
ps.MessageRetentionDuration = defaultMessageRetentionDuration
|
||||
}
|
||||
if err := checkMRD(ps.MessageRetentionDuration); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ps.PushConfig == nil {
|
||||
ps.PushConfig = &pb.PushConfig{}
|
||||
}
|
||||
|
||||
sub := newSubscription(top, &s.mu, ps)
|
||||
top.subs[ps.Name] = sub
|
||||
s.subs[ps.Name] = sub
|
||||
sub.start(&s.wg)
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
func checkAckDeadline(ads int32) error {
|
||||
if ads < 10 || ads > 600 {
|
||||
// PubSub service returns Unknown.
|
||||
return grpc.Errorf(codes.Unknown, "bad ack_deadline_seconds: %d", ads)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
minMessageRetentionDuration = 10 * time.Minute
|
||||
maxMessageRetentionDuration = 168 * time.Hour
|
||||
)
|
||||
|
||||
var defaultMessageRetentionDuration = ptypes.DurationProto(maxMessageRetentionDuration)
|
||||
|
||||
func checkMRD(pmrd *durpb.Duration) error {
|
||||
mrd, err := ptypes.Duration(pmrd)
|
||||
if err != nil || mrd < minMessageRetentionDuration || mrd > maxMessageRetentionDuration {
|
||||
return grpc.Errorf(codes.InvalidArgument, "bad message_retention_duration %+v", pmrd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *gServer) GetSubscription(_ context.Context, req *pb.GetSubscriptionRequest) (*pb.Subscription, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if sub := s.subs[req.Subscription]; sub != nil {
|
||||
return sub.proto, nil
|
||||
}
|
||||
return nil, grpc.Errorf(codes.NotFound, "subscription %q", req.Subscription)
|
||||
}
|
||||
|
||||
func (s *gServer) UpdateSubscription(_ context.Context, req *pb.UpdateSubscriptionRequest) (*pb.Subscription, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
sub := s.subs[req.Subscription.Name]
|
||||
if sub == nil {
|
||||
return nil, grpc.Errorf(codes.NotFound, "subscription %q", req.Subscription.Name)
|
||||
}
|
||||
|
||||
for _, path := range req.UpdateMask.Paths {
|
||||
switch path {
|
||||
case "push_config":
|
||||
sub.proto.PushConfig = req.Subscription.PushConfig
|
||||
|
||||
case "ack_deadline_seconds":
|
||||
a := req.Subscription.AckDeadlineSeconds
|
||||
if err := checkAckDeadline(a); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sub.proto.AckDeadlineSeconds = a
|
||||
|
||||
case "retain_acked_messages":
|
||||
sub.proto.RetainAckedMessages = req.Subscription.RetainAckedMessages
|
||||
|
||||
case "message_retention_duration":
|
||||
if err := checkMRD(req.Subscription.MessageRetentionDuration); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sub.proto.MessageRetentionDuration = req.Subscription.MessageRetentionDuration
|
||||
|
||||
// TODO(jba): labels
|
||||
default:
|
||||
return nil, grpc.Errorf(codes.InvalidArgument, "unknown field name %q", path)
|
||||
}
|
||||
}
|
||||
return sub.proto, nil
|
||||
}
|
||||
|
||||
func (s *gServer) ListSubscriptions(_ context.Context, req *pb.ListSubscriptionsRequest) (*pb.ListSubscriptionsResponse, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
var names []string
|
||||
for name := range s.subs {
|
||||
if strings.HasPrefix(name, req.Project) {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
sort.Strings(names)
|
||||
from, to, nextToken, err := testutil.PageBounds(int(req.PageSize), req.PageToken, len(names))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := &pb.ListSubscriptionsResponse{NextPageToken: nextToken}
|
||||
for i := from; i < to; i++ {
|
||||
res.Subscriptions = append(res.Subscriptions, s.subs[names[i]].proto)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *gServer) DeleteSubscription(_ context.Context, req *pb.DeleteSubscriptionRequest) (*emptypb.Empty, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
sub := s.subs[req.Subscription]
|
||||
if sub == nil {
|
||||
return nil, grpc.Errorf(codes.NotFound, "subscription %q", req.Subscription)
|
||||
}
|
||||
sub.stop()
|
||||
delete(s.subs, req.Subscription)
|
||||
sub.topic.deleteSub(sub)
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *gServer) Publish(_ context.Context, req *pb.PublishRequest) (*pb.PublishResponse, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if req.Topic == "" {
|
||||
return nil, grpc.Errorf(codes.InvalidArgument, "missing topic")
|
||||
}
|
||||
top := s.topics[req.Topic]
|
||||
if top == nil {
|
||||
return nil, grpc.Errorf(codes.NotFound, "topic %q", req.Topic)
|
||||
}
|
||||
var ids []string
|
||||
for _, pm := range req.Messages {
|
||||
id := fmt.Sprintf("m%d", s.nextID)
|
||||
s.nextID++
|
||||
pm.MessageId = id
|
||||
pubTime := timeNow()
|
||||
tsPubTime, err := ptypes.TimestampProto(pubTime)
|
||||
if err != nil {
|
||||
return nil, grpc.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
pm.PublishTime = tsPubTime
|
||||
m := &Message{
|
||||
ID: id,
|
||||
Data: pm.Data,
|
||||
Attributes: pm.Attributes,
|
||||
PublishTime: pubTime,
|
||||
}
|
||||
top.publish(pm, m)
|
||||
ids = append(ids, id)
|
||||
s.msgs = append(s.msgs, m)
|
||||
s.msgsByID[id] = m
|
||||
}
|
||||
return &pb.PublishResponse{MessageIds: ids}, nil
|
||||
}
|
||||
|
||||
type topic struct {
|
||||
proto *pb.Topic
|
||||
subs map[string]*subscription
|
||||
}
|
||||
|
||||
func newTopic(pt *pb.Topic) *topic {
|
||||
return &topic{
|
||||
proto: pt,
|
||||
subs: map[string]*subscription{},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *topic) stop() {
|
||||
for _, sub := range t.subs {
|
||||
sub.proto.Topic = "_deleted-topic_"
|
||||
sub.stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *topic) deleteSub(sub *subscription) {
|
||||
delete(t.subs, sub.proto.Name)
|
||||
}
|
||||
|
||||
func (t *topic) publish(pm *pb.PubsubMessage, m *Message) {
|
||||
for _, s := range t.subs {
|
||||
s.msgs[pm.MessageId] = &message{
|
||||
publishTime: m.PublishTime,
|
||||
proto: &pb.ReceivedMessage{
|
||||
AckId: pm.MessageId,
|
||||
Message: pm,
|
||||
},
|
||||
deliveries: &m.deliveries,
|
||||
acks: &m.acks,
|
||||
streamIndex: -1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type subscription struct {
|
||||
topic *topic
|
||||
mu *sync.Mutex
|
||||
proto *pb.Subscription
|
||||
ackTimeout time.Duration
|
||||
msgs map[string]*message // unacked messages by message ID
|
||||
streams []*stream
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func newSubscription(t *topic, mu *sync.Mutex, ps *pb.Subscription) *subscription {
|
||||
return &subscription{
|
||||
topic: t,
|
||||
mu: mu,
|
||||
proto: ps,
|
||||
ackTimeout: 10 * time.Second,
|
||||
msgs: map[string]*message{},
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *subscription) start(wg *sync.WaitGroup) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-s.done:
|
||||
return
|
||||
case <-time.After(1 * time.Second):
|
||||
s.deliver()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *subscription) stop() {
|
||||
close(s.done)
|
||||
}
|
||||
|
||||
func (s *gServer) StreamingPull(sps pb.Subscriber_StreamingPullServer) error {
|
||||
// Receive initial message configuring the pull.
|
||||
req, err := sps.Recv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Subscription == "" {
|
||||
return grpc.Errorf(codes.InvalidArgument, "missing subscription")
|
||||
}
|
||||
s.mu.Lock()
|
||||
sub := s.subs[req.Subscription]
|
||||
s.mu.Unlock()
|
||||
if sub == nil {
|
||||
return grpc.Errorf(codes.NotFound, "subscription %s", req.Subscription)
|
||||
}
|
||||
// Create a new stream to handle the pull.
|
||||
st := sub.newStream(sps, s.streamTimeout)
|
||||
err = st.pull(&s.wg)
|
||||
sub.deleteStream(st)
|
||||
return err
|
||||
}
|
||||
|
||||
var retentionDuration = 10 * time.Minute
|
||||
|
||||
func (s *subscription) deliver() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
tNow := timeNow()
|
||||
for id, m := range s.msgs {
|
||||
// Mark a message as re-deliverable if its ack deadline has expired.
|
||||
if m.outstanding() && tNow.After(m.ackDeadline) {
|
||||
m.makeAvailable()
|
||||
}
|
||||
pubTime, err := ptypes.Timestamp(m.proto.Message.PublishTime)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Remove messages that have been undelivered for a long time.
|
||||
if !m.outstanding() && tNow.Sub(pubTime) > retentionDuration {
|
||||
delete(s.msgs, id)
|
||||
}
|
||||
}
|
||||
// Try to deliver each remaining message.
|
||||
curIndex := 0
|
||||
for _, m := range s.msgs {
|
||||
// If the message was never delivered before, start with the stream at
|
||||
// curIndex. If it was delivered before, start with the stream after the one
|
||||
// that owned it.
|
||||
if m.streamIndex < 0 {
|
||||
delIndex, ok := s.deliverMessage(m, curIndex, tNow)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
curIndex = delIndex + 1
|
||||
m.streamIndex = curIndex
|
||||
} else {
|
||||
delIndex, ok := s.deliverMessage(m, m.streamIndex, tNow)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
m.streamIndex = delIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deliverMessage attempts to deliver m to the stream at index i. If it can't, it
|
||||
// tries streams i+1, i+2, ..., wrapping around. It returns the index of the stream
|
||||
// it delivered the message to, or 0, false if it didn't deliver the message because
|
||||
// there are no active streams.
|
||||
func (s *subscription) deliverMessage(m *message, i int, tNow time.Time) (int, bool) {
|
||||
for len(s.streams) > 0 {
|
||||
if i >= len(s.streams) {
|
||||
i = 0
|
||||
}
|
||||
st := s.streams[i]
|
||||
select {
|
||||
case <-st.done:
|
||||
s.streams = deleteStreamAt(s.streams, i)
|
||||
|
||||
case st.msgc <- m.proto:
|
||||
(*m.deliveries)++
|
||||
m.ackDeadline = tNow.Add(st.ackTimeout)
|
||||
return i, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (s *subscription) newStream(gs pb.Subscriber_StreamingPullServer, timeout time.Duration) *stream {
|
||||
st := &stream{
|
||||
sub: s,
|
||||
done: make(chan struct{}),
|
||||
msgc: make(chan *pb.ReceivedMessage),
|
||||
gstream: gs,
|
||||
ackTimeout: s.ackTimeout,
|
||||
timeout: timeout,
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.streams = append(s.streams, st)
|
||||
s.mu.Unlock()
|
||||
return st
|
||||
}
|
||||
|
||||
func (s *subscription) deleteStream(st *stream) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
var i int
|
||||
for i = 0; i < len(s.streams); i++ {
|
||||
if s.streams[i] == st {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i < len(s.streams) {
|
||||
s.streams = deleteStreamAt(s.streams, i)
|
||||
}
|
||||
}
|
||||
func deleteStreamAt(s []*stream, i int) []*stream {
|
||||
// Preserve order for round-robin delivery.
|
||||
return append(s[:i], s[i+1:]...)
|
||||
}
|
||||
|
||||
type message struct {
|
||||
proto *pb.ReceivedMessage
|
||||
publishTime time.Time
|
||||
ackDeadline time.Time
|
||||
deliveries *int
|
||||
acks *int
|
||||
streamIndex int // index of stream that currently owns msg, for round-robin delivery
|
||||
}
|
||||
|
||||
func (m *message) outstanding() bool {
|
||||
return !m.ackDeadline.IsZero()
|
||||
}
|
||||
|
||||
func (m *message) makeAvailable() {
|
||||
m.ackDeadline = time.Time{}
|
||||
}
|
||||
|
||||
type stream struct {
|
||||
sub *subscription
|
||||
done chan struct{} // closed when the stream is finished
|
||||
msgc chan *pb.ReceivedMessage
|
||||
gstream pb.Subscriber_StreamingPullServer
|
||||
ackTimeout time.Duration
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// pull manages the StreamingPull interaction for the life of the stream.
|
||||
func (st *stream) pull(wg *sync.WaitGroup) error {
|
||||
errc := make(chan error, 2)
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
errc <- st.sendLoop()
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
errc <- st.recvLoop()
|
||||
}()
|
||||
var tchan <-chan time.Time
|
||||
if st.timeout > 0 {
|
||||
tchan = time.After(st.timeout)
|
||||
}
|
||||
// Wait until one of the goroutines returns an error, or we time out.
|
||||
var err error
|
||||
select {
|
||||
case err = <-errc:
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
case <-tchan:
|
||||
}
|
||||
close(st.done) // stop the other goroutine
|
||||
return err
|
||||
}
|
||||
|
||||
func (st *stream) sendLoop() error {
|
||||
for {
|
||||
select {
|
||||
case <-st.done:
|
||||
return nil
|
||||
case rm := <-st.msgc:
|
||||
res := &pb.StreamingPullResponse{ReceivedMessages: []*pb.ReceivedMessage{rm}}
|
||||
if err := st.gstream.Send(res); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (st *stream) recvLoop() error {
|
||||
for {
|
||||
req, err := st.gstream.Recv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st.sub.handleStreamingPullRequest(st, req)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *subscription) handleStreamingPullRequest(st *stream, req *pb.StreamingPullRequest) {
|
||||
// Lock the entire server.
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
for _, ackID := range req.AckIds {
|
||||
s.ack(ackID)
|
||||
}
|
||||
for i, id := range req.ModifyDeadlineAckIds {
|
||||
s.modifyAckDeadline(id, secsToDur(req.ModifyDeadlineSeconds[i]))
|
||||
}
|
||||
if req.StreamAckDeadlineSeconds > 0 {
|
||||
st.ackTimeout = secsToDur(req.StreamAckDeadlineSeconds)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *subscription) ack(id string) {
|
||||
m := s.msgs[id]
|
||||
if m != nil {
|
||||
(*m.acks)++
|
||||
delete(s.msgs, id)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *subscription) modifyAckDeadline(id string, d time.Duration) {
|
||||
m := s.msgs[id]
|
||||
if m == nil { // already acked: ignore.
|
||||
return
|
||||
}
|
||||
if d == 0 { // nack
|
||||
m.makeAvailable()
|
||||
} else { // extend the deadline by d
|
||||
m.ackDeadline = timeNow().Add(d)
|
||||
}
|
||||
}
|
||||
|
||||
func secsToDur(secs int32) time.Duration {
|
||||
return time.Duration(secs) * time.Second
|
||||
}
|
||||
+342
@@ -0,0 +1,342 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pstest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
|
||||
"cloud.google.com/go/internal/testutil"
|
||||
"golang.org/x/net/context"
|
||||
pb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func TestTopics(t *testing.T) {
|
||||
pclient, _, server := newFake(t)
|
||||
ctx := context.Background()
|
||||
var topics []*pb.Topic
|
||||
for i := 1; i < 3; i++ {
|
||||
topics = append(topics, mustCreateTopic(t, pclient, &pb.Topic{
|
||||
Name: fmt.Sprintf("projects/P/topics/T%d", i),
|
||||
Labels: map[string]string{"num": fmt.Sprintf("%d", i)},
|
||||
}))
|
||||
}
|
||||
if got, want := len(server.gServer.topics), len(topics); got != want {
|
||||
t.Fatalf("got %d topics, want %d", got, want)
|
||||
}
|
||||
for _, top := range topics {
|
||||
got, err := pclient.GetTopic(ctx, &pb.GetTopicRequest{Topic: top.Name})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !testutil.Equal(got, top) {
|
||||
t.Errorf("\ngot %+v\nwant %+v", got, top)
|
||||
}
|
||||
}
|
||||
|
||||
res, err := pclient.ListTopics(ctx, &pb.ListTopicsRequest{Project: "projects/P"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := res.Topics, topics; !testutil.Equal(got, want) {
|
||||
t.Errorf("\ngot %+v\nwant %+v", got, want)
|
||||
}
|
||||
|
||||
for _, top := range topics {
|
||||
if _, err := pclient.DeleteTopic(ctx, &pb.DeleteTopicRequest{Topic: top.Name}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if got, want := len(server.gServer.topics), 0; got != want {
|
||||
t.Fatalf("got %d topics, want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubscriptions(t *testing.T) {
|
||||
pclient, sclient, server := newFake(t)
|
||||
ctx := context.Background()
|
||||
topic := mustCreateTopic(t, pclient, &pb.Topic{Name: "projects/P/topics/T"})
|
||||
var subs []*pb.Subscription
|
||||
for i := 0; i < 3; i++ {
|
||||
subs = append(subs, mustCreateSubscription(t, sclient, &pb.Subscription{
|
||||
Name: fmt.Sprintf("projects/P/subscriptions/S%d", i),
|
||||
Topic: topic.Name,
|
||||
AckDeadlineSeconds: int32(10 * (i + 1)),
|
||||
}))
|
||||
}
|
||||
|
||||
if got, want := len(server.gServer.subs), len(subs); got != want {
|
||||
t.Fatalf("got %d subscriptions, want %d", got, want)
|
||||
}
|
||||
for _, s := range subs {
|
||||
got, err := sclient.GetSubscription(ctx, &pb.GetSubscriptionRequest{Subscription: s.Name})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !testutil.Equal(got, s) {
|
||||
t.Errorf("\ngot %+v\nwant %+v", got, s)
|
||||
}
|
||||
}
|
||||
|
||||
res, err := sclient.ListSubscriptions(ctx, &pb.ListSubscriptionsRequest{Project: "projects/P"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := res.Subscriptions, subs; !testutil.Equal(got, want) {
|
||||
t.Errorf("\ngot %+v\nwant %+v", got, want)
|
||||
}
|
||||
|
||||
res2, err := pclient.ListTopicSubscriptions(ctx, &pb.ListTopicSubscriptionsRequest{Topic: topic.Name})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := len(res2.Subscriptions), len(subs); got != want {
|
||||
t.Fatalf("got %d subs, want %d", got, want)
|
||||
}
|
||||
for i, got := range res2.Subscriptions {
|
||||
want := subs[i].Name
|
||||
if !testutil.Equal(got, want) {
|
||||
t.Errorf("\ngot %+v\nwant %+v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range subs {
|
||||
if _, err := sclient.DeleteSubscription(ctx, &pb.DeleteSubscriptionRequest{Subscription: s.Name}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if got, want := len(server.gServer.subs), 0; got != want {
|
||||
t.Fatalf("got %d subscriptions, want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublish(t *testing.T) {
|
||||
s := NewServer()
|
||||
var ids []string
|
||||
for i := 0; i < 3; i++ {
|
||||
ids = append(ids, s.Publish("t", []byte("hello"), nil))
|
||||
}
|
||||
s.Wait()
|
||||
ms := s.Messages()
|
||||
if got, want := len(ms), len(ids); got != want {
|
||||
t.Errorf("got %d messages, want %d", got, want)
|
||||
}
|
||||
for i, id := range ids {
|
||||
if got, want := ms[i].ID, id; got != want {
|
||||
t.Errorf("got %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
m := s.Message(ids[1])
|
||||
if m == nil {
|
||||
t.Error("got nil, want a message")
|
||||
}
|
||||
}
|
||||
|
||||
// Note: this sets the fake's "now" time, so it is senstive to concurrent changes to "now".
|
||||
func publish(t *testing.T, pclient pb.PublisherClient, topic *pb.Topic, messages []*pb.PubsubMessage) map[string]*pb.PubsubMessage {
|
||||
pubTime := time.Now()
|
||||
now.Store(func() time.Time { return pubTime })
|
||||
defer func() { now.Store(time.Now) }()
|
||||
|
||||
res, err := pclient.Publish(context.Background(), &pb.PublishRequest{
|
||||
Topic: topic.Name,
|
||||
Messages: messages,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tsPubTime, err := ptypes.TimestampProto(pubTime)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := map[string]*pb.PubsubMessage{}
|
||||
for i, id := range res.MessageIds {
|
||||
want[id] = &pb.PubsubMessage{
|
||||
Data: messages[i].Data,
|
||||
Attributes: messages[i].Attributes,
|
||||
MessageId: id,
|
||||
PublishTime: tsPubTime,
|
||||
}
|
||||
}
|
||||
return want
|
||||
}
|
||||
|
||||
func TestStreamingPull(t *testing.T) {
|
||||
// A simple test of streaming pull.
|
||||
pclient, sclient, _ := newFake(t)
|
||||
top := mustCreateTopic(t, pclient, &pb.Topic{Name: "projects/P/topics/T"})
|
||||
sub := mustCreateSubscription(t, sclient, &pb.Subscription{
|
||||
Name: "projects/P/subscriptions/S",
|
||||
Topic: top.Name,
|
||||
AckDeadlineSeconds: 10,
|
||||
})
|
||||
|
||||
want := publish(t, pclient, top, []*pb.PubsubMessage{
|
||||
{Data: []byte("d1")},
|
||||
{Data: []byte("d2")},
|
||||
{Data: []byte("d3")},
|
||||
})
|
||||
got := pullN(t, len(want), sclient, sub)
|
||||
if diff := testutil.Diff(got, want); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiSubs(t *testing.T) {
|
||||
// Each subscription gets every message.
|
||||
pclient, sclient, _ := newFake(t)
|
||||
top := mustCreateTopic(t, pclient, &pb.Topic{Name: "projects/P/topics/T"})
|
||||
sub1 := mustCreateSubscription(t, sclient, &pb.Subscription{
|
||||
Name: "projects/P/subscriptions/S1",
|
||||
Topic: top.Name,
|
||||
AckDeadlineSeconds: 10,
|
||||
})
|
||||
sub2 := mustCreateSubscription(t, sclient, &pb.Subscription{
|
||||
Name: "projects/P/subscriptions/S2",
|
||||
Topic: top.Name,
|
||||
AckDeadlineSeconds: 10,
|
||||
})
|
||||
|
||||
want := publish(t, pclient, top, []*pb.PubsubMessage{
|
||||
{Data: []byte("d1")},
|
||||
{Data: []byte("d2")},
|
||||
{Data: []byte("d3")},
|
||||
})
|
||||
got1 := pullN(t, len(want), sclient, sub1)
|
||||
got2 := pullN(t, len(want), sclient, sub2)
|
||||
if diff := testutil.Diff(got1, want); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
if diff := testutil.Diff(got2, want); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiStreams(t *testing.T) {
|
||||
// Messages are handed out to the streams of a subscription in round-robin order.
|
||||
pclient, sclient, _ := newFake(t)
|
||||
top := mustCreateTopic(t, pclient, &pb.Topic{Name: "projects/P/topics/T"})
|
||||
sub := mustCreateSubscription(t, sclient, &pb.Subscription{
|
||||
Name: "projects/P/subscriptions/S",
|
||||
Topic: top.Name,
|
||||
AckDeadlineSeconds: 10,
|
||||
})
|
||||
want := publish(t, pclient, top, []*pb.PubsubMessage{
|
||||
{Data: []byte("d1")},
|
||||
{Data: []byte("d2")},
|
||||
{Data: []byte("d3")},
|
||||
{Data: []byte("d4")},
|
||||
})
|
||||
streams := []pb.Subscriber_StreamingPullClient{
|
||||
mustStartPull(t, sclient, sub),
|
||||
mustStartPull(t, sclient, sub),
|
||||
}
|
||||
got := map[string]*pb.PubsubMessage{}
|
||||
for i := 0; i < 2; i++ {
|
||||
for _, st := range streams {
|
||||
res, err := st.Recv()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m := res.ReceivedMessages[0]
|
||||
got[m.Message.MessageId] = m.Message
|
||||
}
|
||||
}
|
||||
if diff := testutil.Diff(got, want); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamingPullTimeout(t *testing.T) {
|
||||
pclient, sclient, srv := newFake(t)
|
||||
timeout := 200 * time.Millisecond
|
||||
srv.SetStreamTimeout(timeout)
|
||||
top := mustCreateTopic(t, pclient, &pb.Topic{Name: "projects/P/topics/T"})
|
||||
sub := mustCreateSubscription(t, sclient, &pb.Subscription{
|
||||
Name: "projects/P/subscriptions/S",
|
||||
Topic: top.Name,
|
||||
AckDeadlineSeconds: 10,
|
||||
})
|
||||
stream := mustStartPull(t, sclient, sub)
|
||||
time.Sleep(2 * timeout)
|
||||
_, err := stream.Recv()
|
||||
if err != io.EOF {
|
||||
t.Errorf("got %v, want io.EOF", err)
|
||||
}
|
||||
}
|
||||
|
||||
func mustStartPull(t *testing.T, sc pb.SubscriberClient, sub *pb.Subscription) pb.Subscriber_StreamingPullClient {
|
||||
spc, err := sc.StreamingPull(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := spc.Send(&pb.StreamingPullRequest{Subscription: sub.Name}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return spc
|
||||
}
|
||||
|
||||
func pullN(t *testing.T, n int, sc pb.SubscriberClient, sub *pb.Subscription) map[string]*pb.PubsubMessage {
|
||||
spc := mustStartPull(t, sc, sub)
|
||||
got := map[string]*pb.PubsubMessage{}
|
||||
for i := 0; i < n; i++ {
|
||||
res, err := spc.Recv()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, m := range res.ReceivedMessages {
|
||||
got[m.Message.MessageId] = m.Message
|
||||
}
|
||||
}
|
||||
if err := spc.CloseSend(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err := spc.Recv()
|
||||
if err != io.EOF {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return got
|
||||
}
|
||||
|
||||
func mustCreateTopic(t *testing.T, pc pb.PublisherClient, topic *pb.Topic) *pb.Topic {
|
||||
top, err := pc.CreateTopic(context.Background(), topic)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return top
|
||||
}
|
||||
|
||||
func mustCreateSubscription(t *testing.T, sc pb.SubscriberClient, sub *pb.Subscription) *pb.Subscription {
|
||||
sub, err := sc.CreateSubscription(context.Background(), sub)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return sub
|
||||
}
|
||||
|
||||
func newFake(t *testing.T) (pb.PublisherClient, pb.SubscriberClient, *Server) {
|
||||
srv := NewServer()
|
||||
conn, err := grpc.Dial(srv.Addr, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return pb.NewPublisherClient(conn), pb.NewSubscriberClient(conn), srv
|
||||
}
|
||||
+28
-69
@@ -20,12 +20,12 @@ import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"google.golang.org/api/iterator"
|
||||
"cloud.google.com/go/internal/version"
|
||||
vkit "cloud.google.com/go/pubsub/apiv1"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -46,7 +46,8 @@ const prodAddr = "https://pubsub.googleapis.com/"
|
||||
// A Client may be shared by multiple goroutines.
|
||||
type Client struct {
|
||||
projectID string
|
||||
s service
|
||||
pubc *vkit.PublisherClient
|
||||
subc *vkit.SubscriberClient
|
||||
}
|
||||
|
||||
// NewClient creates a new PubSub client.
|
||||
@@ -64,86 +65,44 @@ func NewClient(ctx context.Context, projectID string, opts ...option.ClientOptio
|
||||
o = []option.ClientOption{
|
||||
// Create multiple connections to increase throughput.
|
||||
option.WithGRPCConnectionPool(runtime.GOMAXPROCS(0)),
|
||||
|
||||
// TODO(grpc/grpc-go#1388) using connection pool without WithBlock
|
||||
// can cause RPCs to fail randomly. We can delete this after the issue is fixed.
|
||||
option.WithGRPCDialOption(grpc.WithBlock()),
|
||||
|
||||
option.WithGRPCDialOption(grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||
Time: 5 * time.Minute,
|
||||
})),
|
||||
}
|
||||
}
|
||||
o = append(o, opts...)
|
||||
s, err := newPubSubService(ctx, o)
|
||||
pubc, err := vkit.NewPublisherClient(ctx, o...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("constructing pubsub client: %v", err)
|
||||
return nil, fmt.Errorf("pubsub: %v", err)
|
||||
}
|
||||
|
||||
c := &Client{
|
||||
subc, err := vkit.NewSubscriberClient(ctx, option.WithGRPCConn(pubc.Connection()))
|
||||
if err != nil {
|
||||
// Should never happen, since we are passing in the connection.
|
||||
// If it does, we cannot close, because the user may have passed in their
|
||||
// own connection originally.
|
||||
return nil, fmt.Errorf("pubsub: %v", err)
|
||||
}
|
||||
pubc.SetGoogleClientInfo("gccl", version.Repo)
|
||||
subc.SetGoogleClientInfo("gccl", version.Repo)
|
||||
return &Client{
|
||||
projectID: projectID,
|
||||
s: s,
|
||||
}
|
||||
|
||||
return c, nil
|
||||
pubc: pubc,
|
||||
subc: subc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close closes any resources held by the client.
|
||||
// Close releases any resources held by the client,
|
||||
// such as memory and goroutines.
|
||||
//
|
||||
// Close need not be called at program exit.
|
||||
// If the client is available for the lifetime of the program, then Close need not be
|
||||
// called at exit.
|
||||
func (c *Client) Close() error {
|
||||
return c.s.close()
|
||||
// Return the first error, because the first call closes the connection.
|
||||
err := c.pubc.Close()
|
||||
_ = c.subc.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) fullyQualifiedProjectName() string {
|
||||
return fmt.Sprintf("projects/%s", c.projectID)
|
||||
}
|
||||
|
||||
// pageToken stores the next page token for a server response which is split over multiple pages.
|
||||
type pageToken struct {
|
||||
tok string
|
||||
explicit bool
|
||||
}
|
||||
|
||||
func (pt *pageToken) set(tok string) {
|
||||
pt.tok = tok
|
||||
pt.explicit = true
|
||||
}
|
||||
|
||||
func (pt *pageToken) get() string {
|
||||
return pt.tok
|
||||
}
|
||||
|
||||
// more returns whether further pages should be fetched from the server.
|
||||
func (pt *pageToken) more() bool {
|
||||
return pt.tok != "" || !pt.explicit
|
||||
}
|
||||
|
||||
// stringsIterator provides an iterator API for a sequence of API page fetches that return lists of strings.
|
||||
type stringsIterator struct {
|
||||
ctx context.Context
|
||||
strings []string
|
||||
token pageToken
|
||||
fetch func(ctx context.Context, tok string) (*stringsPage, error)
|
||||
}
|
||||
|
||||
// Next returns the next string. If there are no more strings, iterator.Done will be returned.
|
||||
func (si *stringsIterator) Next() (string, error) {
|
||||
for len(si.strings) == 0 && si.token.more() {
|
||||
page, err := si.fetch(si.ctx, si.token.get())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
si.token.set(page.tok)
|
||||
si.strings = page.strings
|
||||
}
|
||||
|
||||
if len(si.strings) == 0 {
|
||||
return "", iterator.Done
|
||||
}
|
||||
|
||||
s := si.strings[0]
|
||||
si.strings = si.strings[1:]
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
// Copyright 2018 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pubsub
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
vkit "cloud.google.com/go/pubsub/apiv1"
|
||||
gax "github.com/googleapis/gax-go"
|
||||
"golang.org/x/net/context"
|
||||
pb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// A pullStream supports the methods of a StreamingPullClient, but re-opens
|
||||
// the stream on a retryable error.
|
||||
type pullStream struct {
|
||||
ctx context.Context
|
||||
open func() (pb.Subscriber_StreamingPullClient, error)
|
||||
|
||||
mu sync.Mutex
|
||||
spc *pb.Subscriber_StreamingPullClient
|
||||
err error // permanent error
|
||||
}
|
||||
|
||||
func newPullStream(ctx context.Context, subc *vkit.SubscriberClient, subName string, ackDeadlineSecs int32) *pullStream {
|
||||
return &pullStream{
|
||||
ctx: ctx,
|
||||
open: func() (pb.Subscriber_StreamingPullClient, error) {
|
||||
spc, err := subc.StreamingPull(ctx, gax.WithGRPCOptions(grpc.MaxCallRecvMsgSize(maxSendRecvBytes)))
|
||||
if err == nil {
|
||||
err = spc.Send(&pb.StreamingPullRequest{
|
||||
Subscription: subName,
|
||||
StreamAckDeadlineSeconds: ackDeadlineSecs,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return spc, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// get returns either a valid *StreamingPullClient (SPC), or a permanent error.
|
||||
// If the argument is nil, this is the first call for an RPC, and the current
|
||||
// SPC will be returned (or a new one will be opened). Otherwise, this call is a
|
||||
// request to re-open the stream because of a retryable error, and the argument
|
||||
// is a pointer to the SPC that returned the error.
|
||||
func (s *pullStream) get(spc *pb.Subscriber_StreamingPullClient) (*pb.Subscriber_StreamingPullClient, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
// A stored error is permanent.
|
||||
if s.err != nil {
|
||||
return nil, s.err
|
||||
}
|
||||
// If the context is done, so are we.
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
s.err = s.ctx.Err()
|
||||
return nil, s.err
|
||||
default:
|
||||
}
|
||||
// TODO(jba): We can use the following instead of the above after we drop support for 1.8:
|
||||
// s.err = s.ctx.Err()
|
||||
// if s.err != nil {
|
||||
// return nil, s.err
|
||||
// }
|
||||
|
||||
// If the current and argument SPCs differ, return the current one. This subsumes two cases:
|
||||
// 1. We have an SPC and the caller is getting the stream for the first time.
|
||||
// 2. The caller wants to retry, but they have an older SPC; we've already retried.
|
||||
if spc != s.spc {
|
||||
return s.spc, nil
|
||||
}
|
||||
// Either this is the very first call on this stream (s.spc == nil), or we have a valid
|
||||
// retry request. Either way, open a new stream.
|
||||
// The lock is held here for a long time, but it doesn't matter because no callers could get
|
||||
// anything done anyway.
|
||||
s.spc = new(pb.Subscriber_StreamingPullClient)
|
||||
*s.spc, s.err = s.open() // Setting s.err means any error from open is permanent. Reconsider.
|
||||
return s.spc, s.err
|
||||
}
|
||||
|
||||
func (s *pullStream) call(f func(pb.Subscriber_StreamingPullClient) error) error {
|
||||
var (
|
||||
spc *pb.Subscriber_StreamingPullClient
|
||||
err error
|
||||
bo gax.Backoff
|
||||
)
|
||||
for {
|
||||
spc, err = s.get(spc)
|
||||
if err != nil {
|
||||
// Preserve the existing behavior of not retrying on open. Is that a bug?
|
||||
// (If we do decide to retry, don't retry after we're closed.)
|
||||
return err
|
||||
}
|
||||
err = f(*spc)
|
||||
if err != nil {
|
||||
if isRetryable(err) {
|
||||
gax.Sleep(s.ctx, bo.Pause())
|
||||
continue
|
||||
}
|
||||
s.err = err
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (s *pullStream) Send(req *pb.StreamingPullRequest) error {
|
||||
return s.call(func(spc pb.Subscriber_StreamingPullClient) error {
|
||||
return spc.Send(req)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *pullStream) Recv() (*pb.StreamingPullResponse, error) {
|
||||
var res *pb.StreamingPullResponse
|
||||
err := s.call(func(spc pb.Subscriber_StreamingPullClient) error {
|
||||
var err error
|
||||
res, err = spc.Recv()
|
||||
return err
|
||||
})
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (s *pullStream) CloseSend() error {
|
||||
err := s.call(func(spc pb.Subscriber_StreamingPullClient) error {
|
||||
return spc.CloseSend()
|
||||
})
|
||||
s.mu.Lock()
|
||||
s.err = io.EOF // should not be retried
|
||||
s.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
+126
-469
@@ -18,230 +18,12 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
|
||||
"cloud.google.com/go/iam"
|
||||
"cloud.google.com/go/internal/version"
|
||||
vkit "cloud.google.com/go/pubsub/apiv1"
|
||||
durpb "github.com/golang/protobuf/ptypes/duration"
|
||||
gax "github.com/googleapis/gax-go"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/option"
|
||||
pb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type nextStringFunc func() (string, error)
|
||||
type nextSnapshotFunc func() (*snapshotConfig, error)
|
||||
|
||||
// service provides an internal abstraction to isolate the generated
|
||||
// PubSub API; most of this package uses this interface instead.
|
||||
// The single implementation, *apiService, contains all the knowledge
|
||||
// of the generated PubSub API (except for that present in legacy code).
|
||||
type service interface {
|
||||
createSubscription(ctx context.Context, subName string, cfg SubscriptionConfig) error
|
||||
getSubscriptionConfig(ctx context.Context, subName string) (SubscriptionConfig, string, error)
|
||||
listProjectSubscriptions(ctx context.Context, projName string) nextStringFunc
|
||||
deleteSubscription(ctx context.Context, name string) error
|
||||
subscriptionExists(ctx context.Context, name string) (bool, error)
|
||||
modifyPushConfig(ctx context.Context, subName string, conf PushConfig) error
|
||||
|
||||
createTopic(ctx context.Context, name string) error
|
||||
deleteTopic(ctx context.Context, name string) error
|
||||
topicExists(ctx context.Context, name string) (bool, error)
|
||||
listProjectTopics(ctx context.Context, projName string) nextStringFunc
|
||||
listTopicSubscriptions(ctx context.Context, topicName string) nextStringFunc
|
||||
|
||||
modifyAckDeadline(ctx context.Context, subName string, deadline time.Duration, ackIDs []string) error
|
||||
fetchMessages(ctx context.Context, subName string, maxMessages int32) ([]*Message, error)
|
||||
publishMessages(ctx context.Context, topicName string, msgs []*Message) ([]string, error)
|
||||
|
||||
// splitAckIDs divides ackIDs into
|
||||
// * a batch of a size which is suitable for passing to acknowledge or
|
||||
// modifyAckDeadline, and
|
||||
// * the rest.
|
||||
splitAckIDs(ackIDs []string) ([]string, []string)
|
||||
|
||||
// acknowledge ACKs the IDs in ackIDs.
|
||||
acknowledge(ctx context.Context, subName string, ackIDs []string) error
|
||||
|
||||
iamHandle(resourceName string) *iam.Handle
|
||||
|
||||
newStreamingPuller(ctx context.Context, subName string, ackDeadline int32) *streamingPuller
|
||||
|
||||
createSnapshot(ctx context.Context, snapName, subName string) (*snapshotConfig, error)
|
||||
deleteSnapshot(ctx context.Context, snapName string) error
|
||||
listProjectSnapshots(ctx context.Context, projName string) nextSnapshotFunc
|
||||
|
||||
// TODO(pongad): Raw proto returns an empty SeekResponse; figure out if we want to return it before GA.
|
||||
seekToTime(ctx context.Context, subName string, t time.Time) error
|
||||
seekToSnapshot(ctx context.Context, subName, snapName string) error
|
||||
|
||||
close() error
|
||||
}
|
||||
|
||||
type apiService struct {
|
||||
pubc *vkit.PublisherClient
|
||||
subc *vkit.SubscriberClient
|
||||
}
|
||||
|
||||
func newPubSubService(ctx context.Context, opts []option.ClientOption) (*apiService, error) {
|
||||
pubc, err := vkit.NewPublisherClient(ctx, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subc, err := vkit.NewSubscriberClient(ctx, option.WithGRPCConn(pubc.Connection()))
|
||||
if err != nil {
|
||||
_ = pubc.Close() // ignore error
|
||||
return nil, err
|
||||
}
|
||||
pubc.SetGoogleClientInfo("gccl", version.Repo)
|
||||
subc.SetGoogleClientInfo("gccl", version.Repo)
|
||||
return &apiService{pubc: pubc, subc: subc}, nil
|
||||
}
|
||||
|
||||
func (s *apiService) close() error {
|
||||
// Return the first error, because the first call closes the connection.
|
||||
err := s.pubc.Close()
|
||||
_ = s.subc.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *apiService) createSubscription(ctx context.Context, subName string, cfg SubscriptionConfig) error {
|
||||
var rawPushConfig *pb.PushConfig
|
||||
if cfg.PushConfig.Endpoint != "" || len(cfg.PushConfig.Attributes) != 0 {
|
||||
rawPushConfig = &pb.PushConfig{
|
||||
Attributes: cfg.PushConfig.Attributes,
|
||||
PushEndpoint: cfg.PushConfig.Endpoint,
|
||||
}
|
||||
}
|
||||
var retentionDuration *durpb.Duration
|
||||
if cfg.retentionDuration != 0 {
|
||||
retentionDuration = ptypes.DurationProto(cfg.retentionDuration)
|
||||
}
|
||||
|
||||
_, err := s.subc.CreateSubscription(ctx, &pb.Subscription{
|
||||
Name: subName,
|
||||
Topic: cfg.Topic.name,
|
||||
PushConfig: rawPushConfig,
|
||||
AckDeadlineSeconds: trunc32(int64(cfg.AckDeadline.Seconds())),
|
||||
RetainAckedMessages: cfg.retainAckedMessages,
|
||||
MessageRetentionDuration: retentionDuration,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *apiService) getSubscriptionConfig(ctx context.Context, subName string) (SubscriptionConfig, string, error) {
|
||||
rawSub, err := s.subc.GetSubscription(ctx, &pb.GetSubscriptionRequest{Subscription: subName})
|
||||
if err != nil {
|
||||
return SubscriptionConfig{}, "", err
|
||||
}
|
||||
var rd time.Duration
|
||||
// TODO(pongad): Remove nil-check after white list is removed.
|
||||
if rawSub.MessageRetentionDuration != nil {
|
||||
if rd, err = ptypes.Duration(rawSub.MessageRetentionDuration); err != nil {
|
||||
return SubscriptionConfig{}, "", err
|
||||
}
|
||||
}
|
||||
sub := SubscriptionConfig{
|
||||
AckDeadline: time.Second * time.Duration(rawSub.AckDeadlineSeconds),
|
||||
PushConfig: PushConfig{
|
||||
Endpoint: rawSub.PushConfig.PushEndpoint,
|
||||
Attributes: rawSub.PushConfig.Attributes,
|
||||
},
|
||||
retainAckedMessages: rawSub.RetainAckedMessages,
|
||||
retentionDuration: rd,
|
||||
}
|
||||
return sub, rawSub.Topic, nil
|
||||
}
|
||||
|
||||
// stringsPage contains a list of strings and a token for fetching the next page.
|
||||
type stringsPage struct {
|
||||
strings []string
|
||||
tok string
|
||||
}
|
||||
|
||||
func (s *apiService) listProjectSubscriptions(ctx context.Context, projName string) nextStringFunc {
|
||||
it := s.subc.ListSubscriptions(ctx, &pb.ListSubscriptionsRequest{
|
||||
Project: projName,
|
||||
})
|
||||
return func() (string, error) {
|
||||
sub, err := it.Next()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sub.Name, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *apiService) deleteSubscription(ctx context.Context, name string) error {
|
||||
return s.subc.DeleteSubscription(ctx, &pb.DeleteSubscriptionRequest{Subscription: name})
|
||||
}
|
||||
|
||||
func (s *apiService) subscriptionExists(ctx context.Context, name string) (bool, error) {
|
||||
_, err := s.subc.GetSubscription(ctx, &pb.GetSubscriptionRequest{Subscription: name})
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if grpc.Code(err) == codes.NotFound {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func (s *apiService) createTopic(ctx context.Context, name string) error {
|
||||
_, err := s.pubc.CreateTopic(ctx, &pb.Topic{Name: name})
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *apiService) listProjectTopics(ctx context.Context, projName string) nextStringFunc {
|
||||
it := s.pubc.ListTopics(ctx, &pb.ListTopicsRequest{
|
||||
Project: projName,
|
||||
})
|
||||
return func() (string, error) {
|
||||
topic, err := it.Next()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return topic.Name, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *apiService) deleteTopic(ctx context.Context, name string) error {
|
||||
return s.pubc.DeleteTopic(ctx, &pb.DeleteTopicRequest{Topic: name})
|
||||
}
|
||||
|
||||
func (s *apiService) topicExists(ctx context.Context, name string) (bool, error) {
|
||||
_, err := s.pubc.GetTopic(ctx, &pb.GetTopicRequest{Topic: name})
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if grpc.Code(err) == codes.NotFound {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func (s *apiService) listTopicSubscriptions(ctx context.Context, topicName string) nextStringFunc {
|
||||
it := s.pubc.ListTopicSubscriptions(ctx, &pb.ListTopicSubscriptionsRequest{
|
||||
Topic: topicName,
|
||||
})
|
||||
return it.Next
|
||||
}
|
||||
|
||||
func (s *apiService) modifyAckDeadline(ctx context.Context, subName string, deadline time.Duration, ackIDs []string) error {
|
||||
return s.subc.ModifyAckDeadline(ctx, &pb.ModifyAckDeadlineRequest{
|
||||
Subscription: subName,
|
||||
AckIds: ackIDs,
|
||||
AckDeadlineSeconds: trunc32(int64(deadline.Seconds())),
|
||||
})
|
||||
}
|
||||
|
||||
// maxPayload is the maximum number of bytes to devote to actual ids in
|
||||
// acknowledgement or modifyAckDeadline requests. A serialized
|
||||
// AcknowledgeRequest proto has a small constant overhead, plus the size of the
|
||||
@@ -260,36 +42,6 @@ const (
|
||||
maxSendRecvBytes = 20 * 1024 * 1024 // 20M
|
||||
)
|
||||
|
||||
// splitAckIDs splits ids into two slices, the first of which contains at most maxPayload bytes of ackID data.
|
||||
func (s *apiService) splitAckIDs(ids []string) ([]string, []string) {
|
||||
total := reqFixedOverhead
|
||||
for i, id := range ids {
|
||||
total += len(id) + overheadPerID
|
||||
if total > maxPayload {
|
||||
return ids[:i], ids[i:]
|
||||
}
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (s *apiService) acknowledge(ctx context.Context, subName string, ackIDs []string) error {
|
||||
return s.subc.Acknowledge(ctx, &pb.AcknowledgeRequest{
|
||||
Subscription: subName,
|
||||
AckIds: ackIDs,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *apiService) fetchMessages(ctx context.Context, subName string, maxMessages int32) ([]*Message, error) {
|
||||
resp, err := s.subc.Pull(ctx, &pb.PullRequest{
|
||||
Subscription: subName,
|
||||
MaxMessages: maxMessages,
|
||||
}, gax.WithGRPCOptions(grpc.MaxCallRecvMsgSize(maxSendRecvBytes)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return convertMessages(resp.ReceivedMessages)
|
||||
}
|
||||
|
||||
func convertMessages(rms []*pb.ReceivedMessage) ([]*Message, error) {
|
||||
msgs := make([]*Message, 0, len(rms))
|
||||
for i, m := range rms {
|
||||
@@ -302,38 +54,6 @@ func convertMessages(rms []*pb.ReceivedMessage) ([]*Message, error) {
|
||||
return msgs, nil
|
||||
}
|
||||
|
||||
func (s *apiService) publishMessages(ctx context.Context, topicName string, msgs []*Message) ([]string, error) {
|
||||
rawMsgs := make([]*pb.PubsubMessage, len(msgs))
|
||||
for i, msg := range msgs {
|
||||
rawMsgs[i] = &pb.PubsubMessage{
|
||||
Data: msg.Data,
|
||||
Attributes: msg.Attributes,
|
||||
}
|
||||
}
|
||||
resp, err := s.pubc.Publish(ctx, &pb.PublishRequest{
|
||||
Topic: topicName,
|
||||
Messages: rawMsgs,
|
||||
}, gax.WithGRPCOptions(grpc.MaxCallSendMsgSize(maxSendRecvBytes)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.MessageIds, nil
|
||||
}
|
||||
|
||||
func (s *apiService) modifyPushConfig(ctx context.Context, subName string, conf PushConfig) error {
|
||||
return s.subc.ModifyPushConfig(ctx, &pb.ModifyPushConfigRequest{
|
||||
Subscription: subName,
|
||||
PushConfig: &pb.PushConfig{
|
||||
Attributes: conf.Attributes,
|
||||
PushEndpoint: conf.Endpoint,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *apiService) iamHandle(resourceName string) *iam.Handle {
|
||||
return iam.InternalNewHandle(s.pubc.Connection(), resourceName)
|
||||
}
|
||||
|
||||
func trunc32(i int64) int32 {
|
||||
if i > math.MaxInt32 {
|
||||
i = math.MaxInt32
|
||||
@@ -341,103 +61,103 @@ func trunc32(i int64) int32 {
|
||||
return int32(i)
|
||||
}
|
||||
|
||||
func (s *apiService) newStreamingPuller(ctx context.Context, subName string, ackDeadlineSecs int32) *streamingPuller {
|
||||
p := &streamingPuller{
|
||||
ctx: ctx,
|
||||
subName: subName,
|
||||
ackDeadlineSecs: ackDeadlineSecs,
|
||||
subc: s.subc,
|
||||
}
|
||||
p.c = sync.NewCond(&p.mu)
|
||||
return p
|
||||
}
|
||||
// func newStreamingPuller(ctx context.Context, subc *vkit.SubscriberClient, subName string, ackDeadlineSecs int32) *streamingPuller {
|
||||
// p := &streamingPuller{
|
||||
// ctx: ctx,
|
||||
// subName: subName,
|
||||
// ackDeadlineSecs: ackDeadlineSecs,
|
||||
// subc: subc,
|
||||
// }
|
||||
// p.c = sync.NewCond(&p.mu)
|
||||
// return p
|
||||
// }
|
||||
|
||||
type streamingPuller struct {
|
||||
ctx context.Context
|
||||
subName string
|
||||
ackDeadlineSecs int32
|
||||
subc *vkit.SubscriberClient
|
||||
// type streamingPuller struct {
|
||||
// ctx context.Context
|
||||
// subName string
|
||||
// ackDeadlineSecs int32
|
||||
// subc *vkit.SubscriberClient
|
||||
|
||||
mu sync.Mutex
|
||||
c *sync.Cond
|
||||
inFlight bool
|
||||
closed bool // set after CloseSend called
|
||||
spc pb.Subscriber_StreamingPullClient
|
||||
err error
|
||||
}
|
||||
// mu sync.Mutex
|
||||
// c *sync.Cond
|
||||
// inFlight bool
|
||||
// closed bool // set after CloseSend called
|
||||
// spc pb.Subscriber_StreamingPullClient
|
||||
// err error
|
||||
// }
|
||||
|
||||
// open establishes (or re-establishes) a stream for pulling messages.
|
||||
// It takes care that only one RPC is in flight at a time.
|
||||
func (p *streamingPuller) open() error {
|
||||
p.c.L.Lock()
|
||||
defer p.c.L.Unlock()
|
||||
p.openLocked()
|
||||
return p.err
|
||||
}
|
||||
// // open establishes (or re-establishes) a stream for pulling messages.
|
||||
// // It takes care that only one RPC is in flight at a time.
|
||||
// func (p *streamingPuller) open() error {
|
||||
// p.c.L.Lock()
|
||||
// defer p.c.L.Unlock()
|
||||
// p.openLocked()
|
||||
// return p.err
|
||||
// }
|
||||
|
||||
func (p *streamingPuller) openLocked() {
|
||||
if p.inFlight {
|
||||
// Another goroutine is opening; wait for it.
|
||||
for p.inFlight {
|
||||
p.c.Wait()
|
||||
}
|
||||
return
|
||||
}
|
||||
// No opens in flight; start one.
|
||||
// Keep the lock held, to avoid a race where we
|
||||
// close the old stream while opening a new one.
|
||||
p.inFlight = true
|
||||
spc, err := p.subc.StreamingPull(p.ctx, gax.WithGRPCOptions(grpc.MaxCallRecvMsgSize(maxSendRecvBytes)))
|
||||
if err == nil {
|
||||
err = spc.Send(&pb.StreamingPullRequest{
|
||||
Subscription: p.subName,
|
||||
StreamAckDeadlineSeconds: p.ackDeadlineSecs,
|
||||
})
|
||||
}
|
||||
p.spc = spc
|
||||
p.err = err
|
||||
p.inFlight = false
|
||||
p.c.Broadcast()
|
||||
}
|
||||
// func (p *streamingPuller) openLocked() {
|
||||
// if p.inFlight {
|
||||
// // Another goroutine is opening; wait for it.
|
||||
// for p.inFlight {
|
||||
// p.c.Wait()
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
// // No opens in flight; start one.
|
||||
// // Keep the lock held, to avoid a race where we
|
||||
// // close the old stream while opening a new one.
|
||||
// p.inFlight = true
|
||||
// spc, err := p.subc.StreamingPull(p.ctx, gax.WithGRPCOptions(grpc.MaxCallRecvMsgSize(maxSendRecvBytes)))
|
||||
// if err == nil {
|
||||
// err = spc.Send(&pb.StreamingPullRequest{
|
||||
// Subscription: p.subName,
|
||||
// StreamAckDeadlineSeconds: p.ackDeadlineSecs,
|
||||
// })
|
||||
// }
|
||||
// p.spc = spc
|
||||
// p.err = err
|
||||
// p.inFlight = false
|
||||
// p.c.Broadcast()
|
||||
// }
|
||||
|
||||
func (p *streamingPuller) call(f func(pb.Subscriber_StreamingPullClient) error) error {
|
||||
p.c.L.Lock()
|
||||
defer p.c.L.Unlock()
|
||||
// Wait for an open in flight.
|
||||
for p.inFlight {
|
||||
p.c.Wait()
|
||||
}
|
||||
var err error
|
||||
var bo gax.Backoff
|
||||
for {
|
||||
select {
|
||||
case <-p.ctx.Done():
|
||||
p.err = p.ctx.Err()
|
||||
default:
|
||||
}
|
||||
if p.err != nil {
|
||||
return p.err
|
||||
}
|
||||
spc := p.spc
|
||||
// Do not call f with the lock held. Only one goroutine calls Send
|
||||
// (streamingMessageIterator.sender) and only one calls Recv
|
||||
// (streamingMessageIterator.receiver). If we locked, then a
|
||||
// blocked Recv would prevent a Send from happening.
|
||||
p.c.L.Unlock()
|
||||
err = f(spc)
|
||||
p.c.L.Lock()
|
||||
if !p.closed && err != nil && isRetryable(err) {
|
||||
// Sleep with exponential backoff. Normally we wouldn't hold the lock while sleeping,
|
||||
// but here it can't do any harm, since the stream is broken anyway.
|
||||
gax.Sleep(p.ctx, bo.Pause())
|
||||
p.openLocked()
|
||||
continue
|
||||
}
|
||||
// Not an error, or not a retryable error; stop retrying.
|
||||
p.err = err
|
||||
return err
|
||||
}
|
||||
}
|
||||
// func (p *streamingPuller) call(f func(pb.Subscriber_StreamingPullClient) error) error {
|
||||
// p.c.L.Lock()
|
||||
// defer p.c.L.Unlock()
|
||||
// // Wait for an open in flight.
|
||||
// for p.inFlight {
|
||||
// p.c.Wait()
|
||||
// }
|
||||
// var err error
|
||||
// var bo gax.Backoff
|
||||
// for {
|
||||
// select {
|
||||
// case <-p.ctx.Done():
|
||||
// p.err = p.ctx.Err()
|
||||
// default:
|
||||
// }
|
||||
// if p.err != nil {
|
||||
// return p.err
|
||||
// }
|
||||
// spc := p.spc
|
||||
// // Do not call f with the lock held. Only one goroutine calls Send
|
||||
// // (streamingMessageIterator.sender) and only one calls Recv
|
||||
// // (streamingMessageIterator.receiver). If we locked, then a
|
||||
// // blocked Recv would prevent a Send from happening.
|
||||
// p.c.L.Unlock()
|
||||
// err = f(spc)
|
||||
// p.c.L.Lock()
|
||||
// if !p.closed && err != nil && isRetryable(err) {
|
||||
// // Sleep with exponential backoff. Normally we wouldn't hold the lock while sleeping,
|
||||
// // but here it can't do any harm, since the stream is broken anyway.
|
||||
// gax.Sleep(p.ctx, bo.Pause())
|
||||
// p.openLocked()
|
||||
// continue
|
||||
// }
|
||||
// // Not an error, or not a retryable error; stop retrying.
|
||||
// p.err = err
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
|
||||
// Logic from https://github.com/GoogleCloudPlatform/google-cloud-java/blob/master/google-cloud-pubsub/src/main/java/com/google/cloud/pubsub/v1/StatusUtil.java.
|
||||
func isRetryable(err error) bool {
|
||||
@@ -455,42 +175,42 @@ func isRetryable(err error) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *streamingPuller) fetchMessages() ([]*Message, error) {
|
||||
var res *pb.StreamingPullResponse
|
||||
err := p.call(func(spc pb.Subscriber_StreamingPullClient) error {
|
||||
var err error
|
||||
res, err = spc.Recv()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return convertMessages(res.ReceivedMessages)
|
||||
}
|
||||
// func (p *streamingPuller) fetchMessages() ([]*Message, error) {
|
||||
// var res *pb.StreamingPullResponse
|
||||
// err := p.call(func(spc pb.Subscriber_StreamingPullClient) error {
|
||||
// var err error
|
||||
// res, err = spc.Recv()
|
||||
// return err
|
||||
// })
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// return convertMessages(res.ReceivedMessages)
|
||||
// }
|
||||
|
||||
func (p *streamingPuller) send(req *pb.StreamingPullRequest) error {
|
||||
// Note: len(modAckIDs) == len(modSecs)
|
||||
var rest *pb.StreamingPullRequest
|
||||
for len(req.AckIds) > 0 || len(req.ModifyDeadlineAckIds) > 0 {
|
||||
req, rest = splitRequest(req, maxPayload)
|
||||
err := p.call(func(spc pb.Subscriber_StreamingPullClient) error {
|
||||
x := spc.Send(req)
|
||||
return x
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req = rest
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// func (p *streamingPuller) send(req *pb.StreamingPullRequest) error {
|
||||
// // Note: len(modAckIDs) == len(modSecs)
|
||||
// var rest *pb.StreamingPullRequest
|
||||
// for len(req.AckIds) > 0 || len(req.ModifyDeadlineAckIds) > 0 {
|
||||
// req, rest = splitRequest(req, maxPayload)
|
||||
// err := p.call(func(spc pb.Subscriber_StreamingPullClient) error {
|
||||
// x := spc.Send(req)
|
||||
// return x
|
||||
// })
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// req = rest
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func (p *streamingPuller) closeSend() {
|
||||
p.mu.Lock()
|
||||
p.closed = true
|
||||
p.spc.CloseSend()
|
||||
p.mu.Unlock()
|
||||
}
|
||||
// func (p *streamingPuller) closeSend() {
|
||||
// p.mu.Lock()
|
||||
// p.closed = true
|
||||
// p.spc.CloseSend()
|
||||
// p.mu.Unlock()
|
||||
// }
|
||||
|
||||
// Split req into a prefix that is smaller than maxSize, and a remainder.
|
||||
func splitRequest(req *pb.StreamingPullRequest, maxSize int) (prefix, remainder *pb.StreamingPullRequest) {
|
||||
@@ -533,66 +253,3 @@ func splitRequest(req *pb.StreamingPullRequest, maxSize int) (prefix, remainder
|
||||
req.ModifyDeadlineSeconds = req.ModifyDeadlineSeconds[:k]
|
||||
return req, remainder
|
||||
}
|
||||
|
||||
func (s *apiService) createSnapshot(ctx context.Context, snapName, subName string) (*snapshotConfig, error) {
|
||||
snap, err := s.subc.CreateSnapshot(ctx, &pb.CreateSnapshotRequest{
|
||||
Name: snapName,
|
||||
Subscription: subName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.toSnapshotConfig(snap)
|
||||
}
|
||||
|
||||
func (s *apiService) deleteSnapshot(ctx context.Context, snapName string) error {
|
||||
return s.subc.DeleteSnapshot(ctx, &pb.DeleteSnapshotRequest{Snapshot: snapName})
|
||||
}
|
||||
|
||||
func (s *apiService) listProjectSnapshots(ctx context.Context, projName string) nextSnapshotFunc {
|
||||
it := s.subc.ListSnapshots(ctx, &pb.ListSnapshotsRequest{
|
||||
Project: projName,
|
||||
})
|
||||
return func() (*snapshotConfig, error) {
|
||||
snap, err := it.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.toSnapshotConfig(snap)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *apiService) toSnapshotConfig(snap *pb.Snapshot) (*snapshotConfig, error) {
|
||||
exp, err := ptypes.Timestamp(snap.ExpireTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &snapshotConfig{
|
||||
snapshot: &snapshot{
|
||||
s: s,
|
||||
name: snap.Name,
|
||||
},
|
||||
Topic: newTopic(s, snap.Topic),
|
||||
Expiration: exp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *apiService) seekToTime(ctx context.Context, subName string, t time.Time) error {
|
||||
ts, err := ptypes.TimestampProto(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.subc.Seek(ctx, &pb.SeekRequest{
|
||||
Subscription: subName,
|
||||
Target: &pb.SeekRequest_Time{ts},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *apiService) seekToSnapshot(ctx context.Context, subName, snapName string) error {
|
||||
_, err := s.subc.Seek(ctx, &pb.SeekRequest{
|
||||
Subscription: subName,
|
||||
Target: &pb.SeekRequest_Snapshot{snapName},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
+4
-3
@@ -15,9 +15,10 @@
|
||||
package pubsub
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"cloud.google.com/go/internal/testutil"
|
||||
|
||||
pb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
)
|
||||
|
||||
@@ -58,10 +59,10 @@ func TestSplitRequest(t *testing.T) {
|
||||
ModifyDeadlineSeconds: modDeadlines[len(m1) : len(m1)+len(m2)],
|
||||
}
|
||||
got1, got2 := splitRequest(req, reqFixedOverhead+40)
|
||||
if !reflect.DeepEqual(got1, want1) {
|
||||
if !testutil.Equal(got1, want1) {
|
||||
t.Errorf("#%d: first:\ngot %+v\nwant %+v", i, got1, want1)
|
||||
}
|
||||
if !reflect.DeepEqual(got2, want2) {
|
||||
if !testutil.Equal(got2, want2) {
|
||||
t.Errorf("#%d: second:\ngot %+v\nwant %+v", i, got2, want2)
|
||||
}
|
||||
}
|
||||
|
||||
+71
-30
@@ -15,23 +15,25 @@
|
||||
package pubsub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
vkit "cloud.google.com/go/pubsub/apiv1"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"golang.org/x/net/context"
|
||||
pb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
)
|
||||
|
||||
// Snapshot is a reference to a PubSub snapshot.
|
||||
type snapshot struct {
|
||||
s service
|
||||
type Snapshot struct {
|
||||
c *Client
|
||||
|
||||
// The fully qualified identifier for the snapshot, in the format "projects/<projid>/snapshots/<snap>"
|
||||
name string
|
||||
}
|
||||
|
||||
// ID returns the unique identifier of the snapshot within its project.
|
||||
func (s *snapshot) ID() string {
|
||||
func (s *Snapshot) ID() string {
|
||||
slash := strings.LastIndex(s.name, "/")
|
||||
if slash == -1 {
|
||||
// name is not a fully-qualified name.
|
||||
@@ -41,44 +43,52 @@ func (s *snapshot) ID() string {
|
||||
}
|
||||
|
||||
// SnapshotConfig contains the details of a Snapshot.
|
||||
type snapshotConfig struct {
|
||||
*snapshot
|
||||
type SnapshotConfig struct {
|
||||
*Snapshot
|
||||
Topic *Topic
|
||||
Expiration time.Time
|
||||
}
|
||||
|
||||
// Snapshot creates a reference to a snapshot.
|
||||
func (c *Client) snapshot(id string) *snapshot {
|
||||
return &snapshot{
|
||||
s: c.s,
|
||||
name: vkit.SubscriberSnapshotPath(c.projectID, id),
|
||||
func (c *Client) Snapshot(id string) *Snapshot {
|
||||
return &Snapshot{
|
||||
c: c,
|
||||
name: fmt.Sprintf("projects/%s/snapshots/%s", c.projectID, id),
|
||||
}
|
||||
}
|
||||
|
||||
// Snapshots returns an iterator which returns snapshots for this project.
|
||||
func (c *Client) snapshots(ctx context.Context) *snapshotConfigIterator {
|
||||
return &snapshotConfigIterator{
|
||||
next: c.s.listProjectSnapshots(ctx, c.fullyQualifiedProjectName()),
|
||||
func (c *Client) Snapshots(ctx context.Context) *SnapshotConfigIterator {
|
||||
it := c.subc.ListSnapshots(ctx, &pb.ListSnapshotsRequest{
|
||||
Project: c.fullyQualifiedProjectName(),
|
||||
})
|
||||
next := func() (*SnapshotConfig, error) {
|
||||
snap, err := it.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toSnapshotConfig(snap, c)
|
||||
}
|
||||
return &SnapshotConfigIterator{next: next}
|
||||
}
|
||||
|
||||
// SnapshotConfigIterator is an iterator that returns a series of snapshots.
|
||||
type snapshotConfigIterator struct {
|
||||
next nextSnapshotFunc
|
||||
type SnapshotConfigIterator struct {
|
||||
next func() (*SnapshotConfig, error)
|
||||
}
|
||||
|
||||
// Next returns the next SnapshotConfig. Its second return value is iterator.Done if there are no more results.
|
||||
// Once Next returns iterator.Done, all subsequent calls will return iterator.Done.
|
||||
func (snaps *snapshotConfigIterator) Next() (*snapshotConfig, error) {
|
||||
func (snaps *SnapshotConfigIterator) Next() (*SnapshotConfig, error) {
|
||||
return snaps.next()
|
||||
}
|
||||
|
||||
// Delete deletes a snapshot.
|
||||
func (snap *snapshot) delete(ctx context.Context) error {
|
||||
return snap.s.deleteSnapshot(ctx, snap.name)
|
||||
func (snap *Snapshot) Delete(ctx context.Context) error {
|
||||
return snap.c.subc.DeleteSnapshot(ctx, &pb.DeleteSnapshotRequest{Snapshot: snap.name})
|
||||
}
|
||||
|
||||
// SeekTime seeks the subscription to a point in time.
|
||||
// SeekToTime seeks the subscription to a point in time.
|
||||
//
|
||||
// Messages retained in the subscription that were published before this
|
||||
// time are marked as acknowledged, and messages retained in the
|
||||
@@ -89,11 +99,19 @@ func (snap *snapshot) delete(ctx context.Context) error {
|
||||
// window (or to a point before the system's notion of the subscription
|
||||
// creation time), only retained messages will be marked as unacknowledged,
|
||||
// and already-expunged messages will not be restored.
|
||||
func (s *Subscription) seekToTime(ctx context.Context, t time.Time) error {
|
||||
return s.s.seekToTime(ctx, s.name, t)
|
||||
func (s *Subscription) SeekToTime(ctx context.Context, t time.Time) error {
|
||||
ts, err := ptypes.TimestampProto(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.c.subc.Seek(ctx, &pb.SeekRequest{
|
||||
Subscription: s.name,
|
||||
Target: &pb.SeekRequest_Time{ts},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Snapshot creates a new snapshot from this subscription.
|
||||
// CreateSnapshot creates a new snapshot from this subscription.
|
||||
// The snapshot will be for the topic this subscription is subscribed to.
|
||||
// If the name is empty string, a unique name is assigned.
|
||||
//
|
||||
@@ -103,17 +121,40 @@ func (s *Subscription) seekToTime(ctx context.Context, t time.Time) error {
|
||||
// unacknowledged when Snapshot returns without error.
|
||||
// (b) Any messages published to the subscription's topic following
|
||||
// Snapshot returning without error.
|
||||
func (s *Subscription) createSnapshot(ctx context.Context, name string) (*snapshotConfig, error) {
|
||||
func (s *Subscription) CreateSnapshot(ctx context.Context, name string) (*SnapshotConfig, error) {
|
||||
if name != "" {
|
||||
name = vkit.SubscriberSnapshotPath(strings.Split(s.name, "/")[1], name)
|
||||
name = fmt.Sprintf("projects/%s/snapshots/%s", strings.Split(s.name, "/")[1], name)
|
||||
}
|
||||
return s.s.createSnapshot(ctx, name, s.name)
|
||||
snap, err := s.c.subc.CreateSnapshot(ctx, &pb.CreateSnapshotRequest{
|
||||
Name: name,
|
||||
Subscription: s.name,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toSnapshotConfig(snap, s.c)
|
||||
}
|
||||
|
||||
// SeekSnapshot seeks the subscription to a snapshot.
|
||||
// SeekToSnapshot seeks the subscription to a snapshot.
|
||||
//
|
||||
// The snapshot needs not be created from this subscription,
|
||||
// but the snapshot must be for the topic this subscription is subscribed to.
|
||||
func (s *Subscription) seekToSnapshot(ctx context.Context, snap *snapshot) error {
|
||||
return s.s.seekToSnapshot(ctx, s.name, snap.name)
|
||||
// The snapshot need not be created from this subscription,
|
||||
// but it must be for the topic this subscription is subscribed to.
|
||||
func (s *Subscription) SeekToSnapshot(ctx context.Context, snap *Snapshot) error {
|
||||
_, err := s.c.subc.Seek(ctx, &pb.SeekRequest{
|
||||
Subscription: s.name,
|
||||
Target: &pb.SeekRequest_Snapshot{snap.name},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func toSnapshotConfig(snap *pb.Snapshot, c *Client) (*SnapshotConfig, error) {
|
||||
exp, err := ptypes.Timestamp(snap.ExpireTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SnapshotConfig{
|
||||
Snapshot: &Snapshot{c: c, name: snap.Name},
|
||||
Topic: newTopic(c, snap.Topic),
|
||||
Expiration: exp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
+21
-9
@@ -20,14 +20,17 @@ package pubsub
|
||||
|
||||
import (
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/internal/testutil"
|
||||
|
||||
tspb "github.com/golang/protobuf/ptypes/timestamp"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/option"
|
||||
pb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
@@ -57,8 +60,18 @@ func TestStreamingPullMultipleFetches(t *testing.T) {
|
||||
testStreamingPullIteration(t, client, server, testMessages)
|
||||
}
|
||||
|
||||
func newTestSubscription(t *testing.T, client *Client, name string) *Subscription {
|
||||
topic := client.Topic("t")
|
||||
sub, err := client.CreateSubscription(context.Background(), name,
|
||||
SubscriptionConfig{Topic: topic})
|
||||
if err != nil {
|
||||
t.Fatalf("CreateSubscription: %v", err)
|
||||
}
|
||||
return sub
|
||||
}
|
||||
|
||||
func testStreamingPullIteration(t *testing.T, client *Client, server *fakeServer, msgs []*pb.ReceivedMessage) {
|
||||
sub := client.Subscription("s")
|
||||
sub := newTestSubscription(t, client, "s")
|
||||
gotMsgs, err := pullN(context.Background(), sub, len(msgs), func(_ context.Context, m *Message) {
|
||||
id, err := strconv.Atoi(m.ackID)
|
||||
if err != nil {
|
||||
@@ -89,8 +102,7 @@ func testStreamingPullIteration(t *testing.T, client *Client, server *fakeServer
|
||||
t.Errorf("%d: no message for ackID %q", i, want.ackID)
|
||||
continue
|
||||
}
|
||||
got.doneFunc = nil // Don't compare done; it's a function.
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
if !testutil.Equal(got, want, cmp.AllowUnexported(Message{}), cmpopts.IgnoreTypes(func(string, bool) {})) {
|
||||
t.Errorf("%d: got\n%#v\nwant\n%#v", i, got, want)
|
||||
}
|
||||
}
|
||||
@@ -116,7 +128,7 @@ func TestStreamingPullError(t *testing.T) {
|
||||
client, server := newFake(t)
|
||||
server.addStreamingPullMessages(testMessages[:1])
|
||||
server.addStreamingPullError(grpc.Errorf(codes.Unknown, ""))
|
||||
sub := client.Subscription("s")
|
||||
sub := newTestSubscription(t, client, "s")
|
||||
// Use only one goroutine, since the fake server is configured to
|
||||
// return only one error.
|
||||
sub.ReceiveSettings.NumGoroutines = 1
|
||||
@@ -144,7 +156,7 @@ func TestStreamingPullCancel(t *testing.T) {
|
||||
// return and all messages have been acked.
|
||||
client, server := newFake(t)
|
||||
server.addStreamingPullMessages(testMessages)
|
||||
sub := client.Subscription("s")
|
||||
sub := newTestSubscription(t, client, "s")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
var n int32
|
||||
err := sub.Receive(ctx, func(ctx2 context.Context, m *Message) {
|
||||
@@ -179,7 +191,7 @@ func TestStreamingPullOneActive(t *testing.T) {
|
||||
// Only one call to Pull can be active at a time.
|
||||
client, srv := newFake(t)
|
||||
srv.addStreamingPullMessages(testMessages[:1])
|
||||
sub := client.Subscription("s")
|
||||
sub := newTestSubscription(t, client, "s")
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
err := sub.Receive(ctx, func(ctx context.Context, m *Message) {
|
||||
m.Ack()
|
||||
@@ -209,7 +221,7 @@ func TestStreamingPullConcurrent(t *testing.T) {
|
||||
for i := 0; i < nMessages; i += 2 {
|
||||
server.addStreamingPullMessages([]*pb.ReceivedMessage{newMsg(i), newMsg(i + 1)})
|
||||
}
|
||||
sub := client.Subscription("s")
|
||||
sub := newTestSubscription(t, client, "s")
|
||||
ctx, _ := context.WithTimeout(context.Background(), time.Second)
|
||||
gotMsgs, err := pullN(ctx, sub, nMessages, func(ctx context.Context, m *Message) {
|
||||
m.Ack()
|
||||
@@ -233,7 +245,7 @@ func TestStreamingPullFlowControl(t *testing.T) {
|
||||
// Callback invocations should not occur if flow control limits are exceeded.
|
||||
client, server := newFake(t)
|
||||
server.addStreamingPullMessages(testMessages)
|
||||
sub := client.Subscription("s")
|
||||
sub := newTestSubscription(t, client, "s")
|
||||
sub.ReceiveSettings.MaxOutstandingMessages = 2
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
activec := make(chan int)
|
||||
|
||||
+133
-31
@@ -23,15 +23,20 @@ import (
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/iam"
|
||||
"cloud.google.com/go/internal/optional"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
durpb "github.com/golang/protobuf/ptypes/duration"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/sync/errgroup"
|
||||
pb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
fmpb "google.golang.org/genproto/protobuf/field_mask"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
// Subscription is a reference to a PubSub subscription.
|
||||
type Subscription struct {
|
||||
s service
|
||||
c *Client
|
||||
|
||||
// The fully qualified identifier for the subscription, in the format "projects/<projid>/subscriptions/<name>"
|
||||
name string
|
||||
@@ -45,13 +50,9 @@ type Subscription struct {
|
||||
|
||||
// Subscription creates a reference to a subscription.
|
||||
func (c *Client) Subscription(id string) *Subscription {
|
||||
return newSubscription(c.s, fmt.Sprintf("projects/%s/subscriptions/%s", c.projectID, id))
|
||||
}
|
||||
|
||||
func newSubscription(s service, name string) *Subscription {
|
||||
return &Subscription{
|
||||
s: s,
|
||||
name: name,
|
||||
c: c,
|
||||
name: fmt.Sprintf("projects/%s/subscriptions/%s", c.projectID, id),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,16 +73,25 @@ func (s *Subscription) ID() string {
|
||||
|
||||
// Subscriptions returns an iterator which returns all of the subscriptions for the client's project.
|
||||
func (c *Client) Subscriptions(ctx context.Context) *SubscriptionIterator {
|
||||
it := c.subc.ListSubscriptions(ctx, &pb.ListSubscriptionsRequest{
|
||||
Project: c.fullyQualifiedProjectName(),
|
||||
})
|
||||
return &SubscriptionIterator{
|
||||
s: c.s,
|
||||
next: c.s.listProjectSubscriptions(ctx, c.fullyQualifiedProjectName()),
|
||||
c: c,
|
||||
next: func() (string, error) {
|
||||
sub, err := it.Next()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sub.Name, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SubscriptionIterator is an iterator that returns a series of subscriptions.
|
||||
type SubscriptionIterator struct {
|
||||
s service
|
||||
next nextStringFunc
|
||||
c *Client
|
||||
next func() (string, error)
|
||||
}
|
||||
|
||||
// Next returns the next subscription. If there are no more subscriptions, iterator.Done will be returned.
|
||||
@@ -90,7 +100,7 @@ func (subs *SubscriptionIterator) Next() (*Subscription, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newSubscription(subs.s, subName), nil
|
||||
return &Subscription{c: subs.c, name: subName}, nil
|
||||
}
|
||||
|
||||
// PushConfig contains configuration for subscriptions that operate in push mode.
|
||||
@@ -102,6 +112,13 @@ type PushConfig struct {
|
||||
Attributes map[string]string
|
||||
}
|
||||
|
||||
func (pc *PushConfig) toProto() *pb.PushConfig {
|
||||
return &pb.PushConfig{
|
||||
Attributes: pc.Attributes,
|
||||
PushEndpoint: pc.Endpoint,
|
||||
}
|
||||
}
|
||||
|
||||
// Subscription config contains the configuration of a subscription.
|
||||
type SubscriptionConfig struct {
|
||||
Topic *Topic
|
||||
@@ -115,13 +132,52 @@ type SubscriptionConfig struct {
|
||||
|
||||
// Whether to retain acknowledged messages. If true, acknowledged messages
|
||||
// will not be expunged until they fall out of the RetentionDuration window.
|
||||
retainAckedMessages bool
|
||||
RetainAckedMessages bool
|
||||
|
||||
// How long to retain messages in backlog, from the time of publish. If RetainAckedMessages is true,
|
||||
// this duration affects the retention of acknowledged messages,
|
||||
// otherwise only unacknowledged messages are retained.
|
||||
// How long to retain messages in backlog, from the time of publish. If
|
||||
// RetainAckedMessages is true, this duration affects the retention of
|
||||
// acknowledged messages, otherwise only unacknowledged messages are retained.
|
||||
// Defaults to 7 days. Cannot be longer than 7 days or shorter than 10 minutes.
|
||||
retentionDuration time.Duration
|
||||
RetentionDuration time.Duration
|
||||
}
|
||||
|
||||
func (cfg *SubscriptionConfig) toProto(name string) *pb.Subscription {
|
||||
var pbPushConfig *pb.PushConfig
|
||||
if cfg.PushConfig.Endpoint != "" || len(cfg.PushConfig.Attributes) != 0 {
|
||||
pbPushConfig = &pb.PushConfig{
|
||||
Attributes: cfg.PushConfig.Attributes,
|
||||
PushEndpoint: cfg.PushConfig.Endpoint,
|
||||
}
|
||||
}
|
||||
var retentionDuration *durpb.Duration
|
||||
if cfg.RetentionDuration != 0 {
|
||||
retentionDuration = ptypes.DurationProto(cfg.RetentionDuration)
|
||||
}
|
||||
return &pb.Subscription{
|
||||
Name: name,
|
||||
Topic: cfg.Topic.name,
|
||||
PushConfig: pbPushConfig,
|
||||
AckDeadlineSeconds: trunc32(int64(cfg.AckDeadline.Seconds())),
|
||||
RetainAckedMessages: cfg.RetainAckedMessages,
|
||||
MessageRetentionDuration: retentionDuration,
|
||||
}
|
||||
}
|
||||
|
||||
func protoToSubscriptionConfig(pbSub *pb.Subscription, c *Client) (SubscriptionConfig, error) {
|
||||
rd, err := ptypes.Duration(pbSub.MessageRetentionDuration)
|
||||
if err != nil {
|
||||
return SubscriptionConfig{}, err
|
||||
}
|
||||
return SubscriptionConfig{
|
||||
Topic: newTopic(c, pbSub.Topic),
|
||||
AckDeadline: time.Second * time.Duration(pbSub.AckDeadlineSeconds),
|
||||
PushConfig: PushConfig{
|
||||
Endpoint: pbSub.PushConfig.PushEndpoint,
|
||||
Attributes: pbSub.PushConfig.Attributes,
|
||||
},
|
||||
RetainAckedMessages: pbSub.RetainAckedMessages,
|
||||
RetentionDuration: rd,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ReceiveSettings configure the Receive method.
|
||||
@@ -171,31 +227,47 @@ var DefaultReceiveSettings = ReceiveSettings{
|
||||
|
||||
// Delete deletes the subscription.
|
||||
func (s *Subscription) Delete(ctx context.Context) error {
|
||||
return s.s.deleteSubscription(ctx, s.name)
|
||||
return s.c.subc.DeleteSubscription(ctx, &pb.DeleteSubscriptionRequest{Subscription: s.name})
|
||||
}
|
||||
|
||||
// Exists reports whether the subscription exists on the server.
|
||||
func (s *Subscription) Exists(ctx context.Context) (bool, error) {
|
||||
return s.s.subscriptionExists(ctx, s.name)
|
||||
_, err := s.c.subc.GetSubscription(ctx, &pb.GetSubscriptionRequest{Subscription: s.name})
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if grpc.Code(err) == codes.NotFound {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Config fetches the current configuration for the subscription.
|
||||
func (s *Subscription) Config(ctx context.Context) (SubscriptionConfig, error) {
|
||||
conf, topicName, err := s.s.getSubscriptionConfig(ctx, s.name)
|
||||
pbSub, err := s.c.subc.GetSubscription(ctx, &pb.GetSubscriptionRequest{Subscription: s.name})
|
||||
if err != nil {
|
||||
return SubscriptionConfig{}, err
|
||||
}
|
||||
conf.Topic = &Topic{
|
||||
s: s.s,
|
||||
name: topicName,
|
||||
cfg, err := protoToSubscriptionConfig(pbSub, s.c)
|
||||
if err != nil {
|
||||
return SubscriptionConfig{}, err
|
||||
}
|
||||
return conf, nil
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// SubscriptionConfigToUpdate describes how to update a subscription.
|
||||
type SubscriptionConfigToUpdate struct {
|
||||
// If non-nil, the push config is changed.
|
||||
PushConfig *PushConfig
|
||||
|
||||
// If non-zero, the ack deadline is changed.
|
||||
AckDeadline time.Duration
|
||||
|
||||
// If set, RetainAckedMessages is changed.
|
||||
RetainAckedMessages optional.Bool
|
||||
|
||||
// If non-zero, RetentionDuration is changed.
|
||||
RetentionDuration time.Duration
|
||||
}
|
||||
|
||||
// Update changes an existing subscription according to the fields set in cfg.
|
||||
@@ -203,17 +275,44 @@ type SubscriptionConfigToUpdate struct {
|
||||
//
|
||||
// Update returns an error if no fields were modified.
|
||||
func (s *Subscription) Update(ctx context.Context, cfg SubscriptionConfigToUpdate) (SubscriptionConfig, error) {
|
||||
if cfg.PushConfig == nil {
|
||||
req := s.updateRequest(&cfg)
|
||||
if len(req.UpdateMask.Paths) == 0 {
|
||||
return SubscriptionConfig{}, errors.New("pubsub: UpdateSubscription call with nothing to update")
|
||||
}
|
||||
if err := s.s.modifyPushConfig(ctx, s.name, *cfg.PushConfig); err != nil {
|
||||
rpsub, err := s.c.subc.UpdateSubscription(ctx, req)
|
||||
if err != nil {
|
||||
return SubscriptionConfig{}, err
|
||||
}
|
||||
return s.Config(ctx)
|
||||
return protoToSubscriptionConfig(rpsub, s.c)
|
||||
}
|
||||
|
||||
func (s *Subscription) updateRequest(cfg *SubscriptionConfigToUpdate) *pb.UpdateSubscriptionRequest {
|
||||
psub := &pb.Subscription{Name: s.name}
|
||||
var paths []string
|
||||
if cfg.PushConfig != nil {
|
||||
psub.PushConfig = cfg.PushConfig.toProto()
|
||||
paths = append(paths, "push_config")
|
||||
}
|
||||
if cfg.AckDeadline != 0 {
|
||||
psub.AckDeadlineSeconds = trunc32(int64(cfg.AckDeadline.Seconds()))
|
||||
paths = append(paths, "ack_deadline_seconds")
|
||||
}
|
||||
if cfg.RetainAckedMessages != nil {
|
||||
psub.RetainAckedMessages = optional.ToBool(cfg.RetainAckedMessages)
|
||||
paths = append(paths, "retain_acked_messages")
|
||||
}
|
||||
if cfg.RetentionDuration != 0 {
|
||||
psub.MessageRetentionDuration = ptypes.DurationProto(cfg.RetentionDuration)
|
||||
paths = append(paths, "message_retention_duration")
|
||||
}
|
||||
return &pb.UpdateSubscriptionRequest{
|
||||
Subscription: psub,
|
||||
UpdateMask: &fmpb.FieldMask{Paths: paths},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Subscription) IAM() *iam.Handle {
|
||||
return s.s.iamHandle(s.name)
|
||||
return iam.InternalNewHandle(s.c.subc.Connection(), s.name)
|
||||
}
|
||||
|
||||
// CreateSubscription creates a new subscription on a topic.
|
||||
@@ -250,8 +349,11 @@ func (c *Client) CreateSubscription(ctx context.Context, id string, cfg Subscrip
|
||||
}
|
||||
|
||||
sub := c.Subscription(id)
|
||||
err := c.s.createSubscription(ctx, sub.name, cfg)
|
||||
return sub, err
|
||||
_, err := c.subc.CreateSubscription(ctx, cfg.toProto(sub.name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sub, nil
|
||||
}
|
||||
|
||||
var errReceiveInProgress = errors.New("pubsub: Receive already in progress for this subscription")
|
||||
@@ -347,7 +449,7 @@ func (s *Subscription) receive(ctx context.Context, po *pullOptions, fc *flowCon
|
||||
// The iterator does not use the context passed to Receive. If it did, canceling
|
||||
// that context would immediately stop the iterator without waiting for unacked
|
||||
// messages.
|
||||
iter := newMessageIterator(context.Background(), s.s, s.name, po)
|
||||
iter := newMessageIterator(context.Background(), s.c.subc, s.name, po)
|
||||
|
||||
// We cannot use errgroup from Receive here. Receive might already be calling group.Wait,
|
||||
// and group.Wait cannot be called concurrently with group.Go. We give each receive() its
|
||||
|
||||
+121
-90
@@ -15,49 +15,17 @@
|
||||
package pubsub
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/internal/testutil"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"google.golang.org/api/iterator"
|
||||
)
|
||||
|
||||
type subListService struct {
|
||||
service
|
||||
subs []string
|
||||
err error
|
||||
|
||||
t *testing.T // for error logging.
|
||||
}
|
||||
|
||||
func (s *subListService) newNextStringFunc() nextStringFunc {
|
||||
return func() (string, error) {
|
||||
if len(s.subs) == 0 {
|
||||
return "", iterator.Done
|
||||
}
|
||||
sn := s.subs[0]
|
||||
s.subs = s.subs[1:]
|
||||
return sn, s.err
|
||||
}
|
||||
}
|
||||
|
||||
func (s *subListService) listProjectSubscriptions(ctx context.Context, projName string) nextStringFunc {
|
||||
if projName != "projects/projid" {
|
||||
s.t.Fatalf("unexpected call: projName: %q", projName)
|
||||
return nil
|
||||
}
|
||||
return s.newNextStringFunc()
|
||||
}
|
||||
|
||||
func (s *subListService) listTopicSubscriptions(ctx context.Context, topicName string) nextStringFunc {
|
||||
if topicName != "projects/projid/topics/topic" {
|
||||
s.t.Fatalf("unexpected call: topicName: %q", topicName)
|
||||
return nil
|
||||
}
|
||||
return s.newNextStringFunc()
|
||||
}
|
||||
|
||||
// All returns the remaining subscriptions from this iterator.
|
||||
func slurpSubs(it *SubscriptionIterator) ([]*Subscription, error) {
|
||||
var subs []*Subscription
|
||||
@@ -75,75 +43,138 @@ func slurpSubs(it *SubscriptionIterator) ([]*Subscription, error) {
|
||||
|
||||
func TestSubscriptionID(t *testing.T) {
|
||||
const id = "id"
|
||||
serv := &subListService{
|
||||
subs: []string{"projects/projid/subscriptions/s1", "projects/projid/subscriptions/s2"},
|
||||
t: t,
|
||||
}
|
||||
c := &Client{projectID: "projid", s: serv}
|
||||
c := &Client{projectID: "projid"}
|
||||
s := c.Subscription(id)
|
||||
if got, want := s.ID(), id; got != want {
|
||||
t.Errorf("Subscription.ID() = %q; want %q", got, want)
|
||||
}
|
||||
want := []string{"s1", "s2"}
|
||||
subs, err := slurpSubs(c.Subscriptions(context.Background()))
|
||||
if err != nil {
|
||||
t.Errorf("error listing subscriptions: %v", err)
|
||||
}
|
||||
|
||||
func TestListProjectSubscriptions(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
c, _ := newFake(t)
|
||||
topic := mustCreateTopic(t, c, "t")
|
||||
var want []string
|
||||
for i := 1; i <= 2; i++ {
|
||||
id := fmt.Sprintf("s%d", i)
|
||||
want = append(want, id)
|
||||
_, err := c.CreateSubscription(ctx, id, SubscriptionConfig{Topic: topic})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
for i, s := range subs {
|
||||
if got, want := s.ID(), want[i]; got != want {
|
||||
t.Errorf("Subscription.ID() = %q; want %q", got, want)
|
||||
subs, err := slurpSubs(c.Subscriptions(ctx))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got := getSubIDs(subs)
|
||||
if !testutil.Equal(got, want) {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func getSubIDs(subs []*Subscription) []string {
|
||||
var names []string
|
||||
for _, sub := range subs {
|
||||
names = append(names, sub.ID())
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func TestListTopicSubscriptions(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
c, _ := newFake(t)
|
||||
topics := []*Topic{
|
||||
mustCreateTopic(t, c, "t0"),
|
||||
mustCreateTopic(t, c, "t1"),
|
||||
}
|
||||
wants := make([][]string, 2)
|
||||
for i := 0; i < 5; i++ {
|
||||
id := fmt.Sprintf("s%d", i)
|
||||
sub, err := c.CreateSubscription(ctx, id, SubscriptionConfig{Topic: topics[i%2]})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wants[i%2] = append(wants[i%2], sub.ID())
|
||||
}
|
||||
|
||||
for i, topic := range topics {
|
||||
subs, err := slurpSubs(topic.Subscriptions(ctx))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := getSubIDs(subs)
|
||||
if !testutil.Equal(got, wants[i]) {
|
||||
t.Errorf("#%d: got %v, want %v", i, got, wants[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListProjectSubscriptions(t *testing.T) {
|
||||
snames := []string{"projects/projid/subscriptions/s1", "projects/projid/subscriptions/s2",
|
||||
"projects/projid/subscriptions/s3"}
|
||||
s := &subListService{subs: snames, t: t}
|
||||
c := &Client{projectID: "projid", s: s}
|
||||
subs, err := slurpSubs(c.Subscriptions(context.Background()))
|
||||
const defaultRetentionDuration = 168 * time.Hour
|
||||
|
||||
func TestUpdateSubscription(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client, _ := newFake(t)
|
||||
defer client.Close()
|
||||
|
||||
topic := client.Topic("t")
|
||||
sub, err := client.CreateSubscription(ctx, "s", SubscriptionConfig{Topic: topic})
|
||||
if err != nil {
|
||||
t.Errorf("error listing subscriptions: %v", err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := subNames(subs)
|
||||
want := []string{
|
||||
"projects/projid/subscriptions/s1",
|
||||
"projects/projid/subscriptions/s2",
|
||||
"projects/projid/subscriptions/s3"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("sub list: got: %v, want: %v", got, want)
|
||||
cfg, err := sub.Config(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(s.subs) != 0 {
|
||||
t.Errorf("outstanding subs: %v", s.subs)
|
||||
want := SubscriptionConfig{
|
||||
Topic: topic,
|
||||
AckDeadline: 10 * time.Second,
|
||||
RetainAckedMessages: false,
|
||||
RetentionDuration: defaultRetentionDuration,
|
||||
}
|
||||
if !testutil.Equal(cfg, want) {
|
||||
t.Fatalf("\ngot %+v\nwant %+v", cfg, want)
|
||||
}
|
||||
|
||||
got, err := sub.Update(ctx, SubscriptionConfigToUpdate{
|
||||
AckDeadline: 20 * time.Second,
|
||||
RetainAckedMessages: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want = SubscriptionConfig{
|
||||
Topic: topic,
|
||||
AckDeadline: 20 * time.Second,
|
||||
RetainAckedMessages: true,
|
||||
RetentionDuration: defaultRetentionDuration,
|
||||
}
|
||||
if !testutil.Equal(got, want) {
|
||||
t.Fatalf("\ngot %+v\nwant %+v", got, want)
|
||||
}
|
||||
|
||||
got, err = sub.Update(ctx, SubscriptionConfigToUpdate{RetentionDuration: 2 * time.Hour})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want.RetentionDuration = 2 * time.Hour
|
||||
if !testutil.Equal(got, want) {
|
||||
t.Fatalf("\ngot %+v\nwant %+v", got, want)
|
||||
}
|
||||
|
||||
_, err = sub.Update(ctx, SubscriptionConfigToUpdate{})
|
||||
if err == nil {
|
||||
t.Fatal("got nil, want error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListTopicSubscriptions(t *testing.T) {
|
||||
snames := []string{"projects/projid/subscriptions/s1", "projects/projid/subscriptions/s2",
|
||||
"projects/projid/subscriptions/s3"}
|
||||
s := &subListService{subs: snames, t: t}
|
||||
c := &Client{projectID: "projid", s: s}
|
||||
subs, err := slurpSubs(c.Topic("topic").Subscriptions(context.Background()))
|
||||
if err != nil {
|
||||
t.Errorf("error listing subscriptions: %v", err)
|
||||
func (t1 *Topic) Equal(t2 *Topic) bool {
|
||||
if t1 == nil && t2 == nil {
|
||||
return true
|
||||
}
|
||||
got := subNames(subs)
|
||||
want := []string{
|
||||
"projects/projid/subscriptions/s1",
|
||||
"projects/projid/subscriptions/s2",
|
||||
"projects/projid/subscriptions/s3"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("sub list: got: %v, want: %v", got, want)
|
||||
}
|
||||
if len(s.subs) != 0 {
|
||||
t.Errorf("outstanding subs: %v", s.subs)
|
||||
if t1 == nil || t2 == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func subNames(subs []*Subscription) []string {
|
||||
var names []string
|
||||
for _, sub := range subs {
|
||||
names = append(names, sub.name)
|
||||
}
|
||||
return names
|
||||
return t1.c == t2.c && t1.name == t2.name
|
||||
}
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
// Copyright 2018 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pubsub
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"cloud.google.com/go/pubsub/pstest"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Using the fake PubSub server in the pstest package, verify that streaming
|
||||
// pull resumes if the server stream times out.
|
||||
func TestStreamTimeout(t *testing.T) {
|
||||
log.SetFlags(log.Lmicroseconds)
|
||||
ctx := context.Background()
|
||||
srv := pstest.NewServer()
|
||||
srv.SetStreamTimeout(2 * time.Second)
|
||||
conn, err := grpc.Dial(srv.Addr, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
client, err := NewClient(ctx, "P", option.WithGRPCConn(conn))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
topic, err := client.CreateTopic(ctx, "T")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sub, err := client.CreateSubscription(ctx, "sub", SubscriptionConfig{Topic: topic, AckDeadline: 10 * time.Second})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
const nPublish = 8
|
||||
rctx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
||||
defer cancel()
|
||||
errc := make(chan error)
|
||||
var nSeen int64
|
||||
go func() {
|
||||
errc <- sub.Receive(rctx, func(ctx context.Context, m *Message) {
|
||||
m.Ack()
|
||||
n := atomic.AddInt64(&nSeen, 1)
|
||||
if n >= nPublish {
|
||||
cancel()
|
||||
}
|
||||
})
|
||||
}()
|
||||
|
||||
for i := 0; i < nPublish; i++ {
|
||||
pr := topic.Publish(ctx, &Message{Data: []byte("msg")})
|
||||
_, err := pr.Get(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
}
|
||||
|
||||
err = <-errc
|
||||
if err := sub.Delete(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n := atomic.LoadInt64(&nSeen)
|
||||
t.Logf("Receive returned %v after seeing %d messages\n", err, n)
|
||||
if n < nPublish {
|
||||
t.Errorf("got %d messages, want %d", n, nPublish)
|
||||
}
|
||||
}
|
||||
+52
-23
@@ -24,9 +24,12 @@ import (
|
||||
|
||||
"cloud.google.com/go/iam"
|
||||
"github.com/golang/protobuf/proto"
|
||||
gax "github.com/googleapis/gax-go"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/support/bundler"
|
||||
pb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -47,7 +50,7 @@ var ErrOversizedMessage = bundler.ErrOversizedItem
|
||||
//
|
||||
// The methods of Topic are safe for use by multiple goroutines.
|
||||
type Topic struct {
|
||||
s service
|
||||
c *Client
|
||||
// The fully qualified identifier for the topic, in the format "projects/<projid>/topics/<name>"
|
||||
name string
|
||||
|
||||
@@ -102,8 +105,11 @@ var DefaultPublishSettings = PublishSettings{
|
||||
// If the topic already exists an error will be returned.
|
||||
func (c *Client) CreateTopic(ctx context.Context, id string) (*Topic, error) {
|
||||
t := c.Topic(id)
|
||||
err := c.s.createTopic(ctx, t.name)
|
||||
return t, err
|
||||
_, err := c.pubc.CreateTopic(ctx, &pb.Topic{Name: t.name})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// Topic creates a reference to a topic in the client's project.
|
||||
@@ -123,15 +129,15 @@ func (c *Client) Topic(id string) *Topic {
|
||||
//
|
||||
// Avoid creating many Topic instances if you use them to publish.
|
||||
func (c *Client) TopicInProject(id, projectID string) *Topic {
|
||||
return newTopic(c.s, fmt.Sprintf("projects/%s/topics/%s", projectID, id))
|
||||
return newTopic(c, fmt.Sprintf("projects/%s/topics/%s", projectID, id))
|
||||
}
|
||||
|
||||
func newTopic(s service, name string) *Topic {
|
||||
func newTopic(c *Client, name string) *Topic {
|
||||
// bundlec is unbuffered. A buffer would occupy memory not
|
||||
// accounted for by the bundler, so BufferedByteLimit would be a lie:
|
||||
// the actual memory consumed would be higher.
|
||||
return &Topic{
|
||||
s: s,
|
||||
c: c,
|
||||
name: name,
|
||||
PublishSettings: DefaultPublishSettings,
|
||||
bundlec: make(chan []*bundledMessage),
|
||||
@@ -140,16 +146,23 @@ func newTopic(s service, name string) *Topic {
|
||||
|
||||
// Topics returns an iterator which returns all of the topics for the client's project.
|
||||
func (c *Client) Topics(ctx context.Context) *TopicIterator {
|
||||
it := c.pubc.ListTopics(ctx, &pb.ListTopicsRequest{Project: c.fullyQualifiedProjectName()})
|
||||
return &TopicIterator{
|
||||
s: c.s,
|
||||
next: c.s.listProjectTopics(ctx, c.fullyQualifiedProjectName()),
|
||||
c: c,
|
||||
next: func() (string, error) {
|
||||
topic, err := it.Next()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return topic.Name, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TopicIterator is an iterator that returns a series of topics.
|
||||
type TopicIterator struct {
|
||||
s service
|
||||
next nextStringFunc
|
||||
c *Client
|
||||
next func() (string, error)
|
||||
}
|
||||
|
||||
// Next returns the next topic. If there are no more topics, iterator.Done will be returned.
|
||||
@@ -158,7 +171,7 @@ func (tps *TopicIterator) Next() (*Topic, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newTopic(tps.s, topicName), nil
|
||||
return newTopic(tps.c, topicName), nil
|
||||
}
|
||||
|
||||
// ID returns the unique idenfier of the topic within its project.
|
||||
@@ -178,7 +191,7 @@ func (t *Topic) String() string {
|
||||
|
||||
// Delete deletes the topic.
|
||||
func (t *Topic) Delete(ctx context.Context) error {
|
||||
return t.s.deleteTopic(ctx, t.name)
|
||||
return t.c.pubc.DeleteTopic(ctx, &pb.DeleteTopicRequest{Topic: t.name})
|
||||
}
|
||||
|
||||
// Exists reports whether the topic exists on the server.
|
||||
@@ -186,21 +199,30 @@ func (t *Topic) Exists(ctx context.Context) (bool, error) {
|
||||
if t.name == "_deleted-topic_" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return t.s.topicExists(ctx, t.name)
|
||||
_, err := t.c.pubc.GetTopic(ctx, &pb.GetTopicRequest{Topic: t.name})
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if grpc.Code(err) == codes.NotFound {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
func (t *Topic) IAM() *iam.Handle {
|
||||
return t.s.iamHandle(t.name)
|
||||
return iam.InternalNewHandle(t.c.pubc.Connection(), t.name)
|
||||
}
|
||||
|
||||
// Subscriptions returns an iterator which returns the subscriptions for this topic.
|
||||
//
|
||||
// Some of the returned subscriptions may belong to a project other than t.
|
||||
func (t *Topic) Subscriptions(ctx context.Context) *SubscriptionIterator {
|
||||
// NOTE: zero or more Subscriptions that are ultimately returned by this
|
||||
// Subscriptions iterator may belong to a different project to t.
|
||||
it := t.c.pubc.ListTopicSubscriptions(ctx, &pb.ListTopicSubscriptionsRequest{
|
||||
Topic: t.name,
|
||||
})
|
||||
return &SubscriptionIterator{
|
||||
s: t.s,
|
||||
next: t.s.listTopicSubscriptions(ctx, t.name),
|
||||
c: t.c,
|
||||
next: it.Next,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,16 +375,23 @@ func (t *Topic) initBundler() {
|
||||
}
|
||||
|
||||
func (t *Topic) publishMessageBundle(ctx context.Context, bms []*bundledMessage) {
|
||||
msgs := make([]*Message, len(bms))
|
||||
pbMsgs := make([]*pb.PubsubMessage, len(bms))
|
||||
for i, bm := range bms {
|
||||
msgs[i], bm.msg = bm.msg, nil // release bm.msg for GC
|
||||
pbMsgs[i] = &pb.PubsubMessage{
|
||||
Data: bm.msg.Data,
|
||||
Attributes: bm.msg.Attributes,
|
||||
}
|
||||
bm.msg = nil // release bm.msg for GC
|
||||
}
|
||||
ids, err := t.s.publishMessages(ctx, t.name, msgs)
|
||||
res, err := t.c.pubc.Publish(ctx, &pb.PublishRequest{
|
||||
Topic: t.name,
|
||||
Messages: pbMsgs,
|
||||
}, gax.WithGRPCOptions(grpc.MaxCallSendMsgSize(maxSendRecvBytes)))
|
||||
for i, bm := range bms {
|
||||
if err != nil {
|
||||
bm.res.set("", err)
|
||||
} else {
|
||||
bm.res.set(ids[i], nil)
|
||||
bm.res.set(res.MessageIds[i], nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+27
-67
@@ -15,11 +15,13 @@
|
||||
package pubsub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/internal/testutil"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
@@ -28,46 +30,18 @@ import (
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
type topicListService struct {
|
||||
service
|
||||
topics []string
|
||||
err error
|
||||
t *testing.T // for error logging.
|
||||
}
|
||||
|
||||
func (s *topicListService) newNextStringFunc() nextStringFunc {
|
||||
return func() (string, error) {
|
||||
if len(s.topics) == 0 {
|
||||
return "", iterator.Done
|
||||
}
|
||||
tn := s.topics[0]
|
||||
s.topics = s.topics[1:]
|
||||
return tn, s.err
|
||||
}
|
||||
}
|
||||
|
||||
func (s *topicListService) listProjectTopics(ctx context.Context, projName string) nextStringFunc {
|
||||
if projName != "projects/projid" {
|
||||
s.t.Fatalf("unexpected call: projName: %q", projName)
|
||||
return nil
|
||||
}
|
||||
return s.newNextStringFunc()
|
||||
}
|
||||
|
||||
func checkTopicListing(t *testing.T, want []string) {
|
||||
s := &topicListService{topics: want, t: t}
|
||||
c := &Client{projectID: "projid", s: s}
|
||||
func checkTopicListing(t *testing.T, c *Client, want []string) {
|
||||
topics, err := slurpTopics(c.Topics(context.Background()))
|
||||
if err != nil {
|
||||
t.Errorf("error listing topics: %v", err)
|
||||
t.Fatalf("error listing topics: %v", err)
|
||||
}
|
||||
got := topicNames(topics)
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
var got []string
|
||||
for _, topic := range topics {
|
||||
got = append(got, topic.ID())
|
||||
}
|
||||
if !testutil.Equal(got, want) {
|
||||
t.Errorf("topic list: got: %v, want: %v", got, want)
|
||||
}
|
||||
if len(s.topics) != 0 {
|
||||
t.Errorf("outstanding topics: %v", s.topics)
|
||||
}
|
||||
}
|
||||
|
||||
// All returns the remaining topics from this iterator.
|
||||
@@ -87,38 +61,27 @@ func slurpTopics(it *TopicIterator) ([]*Topic, error) {
|
||||
|
||||
func TestTopicID(t *testing.T) {
|
||||
const id = "id"
|
||||
serv := &topicListService{
|
||||
topics: []string{"projects/projid/topics/t1", "projects/projid/topics/t2"},
|
||||
t: t,
|
||||
}
|
||||
c := &Client{projectID: "projid", s: serv}
|
||||
c, _ := newFake(t)
|
||||
s := c.Topic(id)
|
||||
if got, want := s.ID(), id; got != want {
|
||||
t.Errorf("Token.ID() = %q; want %q", got, want)
|
||||
}
|
||||
want := []string{"t1", "t2"}
|
||||
topics, err := slurpTopics(c.Topics(context.Background()))
|
||||
if err != nil {
|
||||
t.Errorf("error listing topics: %v", err)
|
||||
}
|
||||
for i, topic := range topics {
|
||||
if got, want := topic.ID(), want[i]; got != want {
|
||||
t.Errorf("Token.ID() = %q; want %q", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListTopics(t *testing.T) {
|
||||
checkTopicListing(t, []string{
|
||||
"projects/projid/topics/t1",
|
||||
"projects/projid/topics/t2",
|
||||
"projects/projid/topics/t3",
|
||||
"projects/projid/topics/t4"})
|
||||
c, _ := newFake(t)
|
||||
var ids []string
|
||||
for i := 1; i <= 4; i++ {
|
||||
id := fmt.Sprintf("t%d", i)
|
||||
ids = append(ids, id)
|
||||
mustCreateTopic(t, c, id)
|
||||
}
|
||||
checkTopicListing(t, c, ids)
|
||||
}
|
||||
|
||||
func TestListCompletelyEmptyTopics(t *testing.T) {
|
||||
var want []string
|
||||
checkTopicListing(t, want)
|
||||
c, _ := newFake(t)
|
||||
checkTopicListing(t, c, nil)
|
||||
}
|
||||
|
||||
func TestStopPublishOrder(t *testing.T) {
|
||||
@@ -148,11 +111,10 @@ func TestPublishTimeout(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s, err := newPubSubService(context.Background(), []option.ClientOption{option.WithGRPCConn(conn)})
|
||||
c, err := NewClient(ctx, "projectID", option.WithGRPCConn(conn))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c := &Client{s: s}
|
||||
topic := c.Topic("t")
|
||||
topic.PublishSettings.Timeout = 3 * time.Second
|
||||
r := topic.Publish(ctx, &Message{})
|
||||
@@ -176,12 +138,10 @@ func (s *alwaysFailPublish) Publish(ctx context.Context, req *pubsubpb.PublishRe
|
||||
return nil, grpc.Errorf(codes.Unavailable, "try again")
|
||||
}
|
||||
|
||||
func topicNames(topics []*Topic) []string {
|
||||
var names []string
|
||||
|
||||
for _, topic := range topics {
|
||||
names = append(names, topic.name)
|
||||
|
||||
func mustCreateTopic(t *testing.T, c *Client, id string) *Topic {
|
||||
topic, err := c.CreateTopic(context.Background(), id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return names
|
||||
return topic
|
||||
}
|
||||
|
||||
-63
@@ -1,63 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pubsub
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type modDeadlineCall struct {
|
||||
subName string
|
||||
deadline time.Duration
|
||||
ackIDs []string
|
||||
}
|
||||
|
||||
type acknowledgeCall struct {
|
||||
subName string
|
||||
ackIDs []string
|
||||
}
|
||||
|
||||
type testService struct {
|
||||
service
|
||||
|
||||
// The arguments of each call to modifyAckDealine are written to this channel.
|
||||
modDeadlineCalled chan modDeadlineCall
|
||||
|
||||
// The arguments of each call to acknowledge are written to this channel.
|
||||
acknowledgeCalled chan acknowledgeCall
|
||||
}
|
||||
|
||||
func (s *testService) modifyAckDeadline(ctx context.Context, subName string, deadline time.Duration, ackIDs []string) error {
|
||||
s.modDeadlineCalled <- modDeadlineCall{
|
||||
subName: subName,
|
||||
deadline: deadline,
|
||||
ackIDs: ackIDs,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *testService) acknowledge(ctx context.Context, subName string, ackIDs []string) error {
|
||||
s.acknowledgeCalled <- acknowledgeCall{
|
||||
subName: subName,
|
||||
ackIDs: ackIDs,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *testService) splitAckIDs(ids []string) ([]string, []string) {
|
||||
return ids, nil
|
||||
}
|
||||
Reference in New Issue
Block a user