vendor: Update vendoring for the exec client and server implementations
Signed-off-by: Jacek J. Łakis <jacek.lakis@intel.com> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
This commit is contained in:
parent
d25b88583f
commit
bf51655a7b
2124 changed files with 809703 additions and 5 deletions
159
vendor/cloud.google.com/go/pubsub/acker.go
generated
vendored
Normal file
159
vendor/cloud.google.com/go/pubsub/acker.go
generated
vendored
Normal file
|
@ -0,0 +1,159 @@
|
|||
// 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 (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ackBuffer stores the pending ack IDs and notifies the Dirty channel when it becomes non-empty.
|
||||
type ackBuffer struct {
|
||||
Dirty chan struct{}
|
||||
// Close done when ackBuffer is no longer needed.
|
||||
Done chan struct{}
|
||||
|
||||
mu sync.Mutex
|
||||
pending []string
|
||||
send bool
|
||||
}
|
||||
|
||||
// Add adds ackID to the buffer.
|
||||
func (buf *ackBuffer) Add(ackID string) {
|
||||
buf.mu.Lock()
|
||||
defer buf.mu.Unlock()
|
||||
buf.pending = append(buf.pending, ackID)
|
||||
|
||||
// If we are transitioning into a non-empty notification state.
|
||||
if buf.send && len(buf.pending) == 1 {
|
||||
buf.notify()
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveAll removes all ackIDs from the buffer and returns them.
|
||||
func (buf *ackBuffer) RemoveAll() []string {
|
||||
buf.mu.Lock()
|
||||
defer buf.mu.Unlock()
|
||||
|
||||
ret := buf.pending
|
||||
buf.pending = nil
|
||||
return ret
|
||||
}
|
||||
|
||||
// SendNotifications enables sending dirty notification on empty -> non-empty transitions.
|
||||
// If the buffer is already non-empty, a notification will be sent immediately.
|
||||
func (buf *ackBuffer) SendNotifications() {
|
||||
buf.mu.Lock()
|
||||
defer buf.mu.Unlock()
|
||||
|
||||
buf.send = true
|
||||
// If we are transitioning into a non-empty notification state.
|
||||
if len(buf.pending) > 0 {
|
||||
buf.notify()
|
||||
}
|
||||
}
|
||||
|
||||
func (buf *ackBuffer) notify() {
|
||||
go func() {
|
||||
select {
|
||||
case buf.Dirty <- struct{}{}:
|
||||
case <-buf.Done:
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// acker acks messages in batches.
|
||||
type acker struct {
|
||||
s service
|
||||
Ctx context.Context // The context to use when acknowledging messages.
|
||||
Sub string // The full name of the subscription.
|
||||
AckTick <-chan time.Time // AckTick supplies the frequency with which to make ack requests.
|
||||
|
||||
// Notify is called with an ack ID after the message with that ack ID
|
||||
// has been processed. An ackID is considered to have been processed
|
||||
// if at least one attempt has been made to acknowledge it.
|
||||
Notify func(string)
|
||||
|
||||
ackBuffer
|
||||
|
||||
wg sync.WaitGroup
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// Start intiates processing of ackIDs which are added via Add.
|
||||
// Notify is called with each ackID once it has been processed.
|
||||
func (a *acker) Start() {
|
||||
a.done = make(chan struct{})
|
||||
a.ackBuffer.Dirty = make(chan struct{})
|
||||
a.ackBuffer.Done = a.done
|
||||
|
||||
a.wg.Add(1)
|
||||
go func() {
|
||||
defer a.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-a.ackBuffer.Dirty:
|
||||
a.ack(a.ackBuffer.RemoveAll())
|
||||
case <-a.AckTick:
|
||||
a.ack(a.ackBuffer.RemoveAll())
|
||||
case <-a.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
}
|
||||
|
||||
// Ack adds an ack id to be acked in the next batch.
|
||||
func (a *acker) Ack(ackID string) {
|
||||
a.ackBuffer.Add(ackID)
|
||||
}
|
||||
|
||||
// FastMode switches acker into a mode which acks messages as they arrive, rather than waiting
|
||||
// for a.AckTick.
|
||||
func (a *acker) FastMode() {
|
||||
a.ackBuffer.SendNotifications()
|
||||
}
|
||||
|
||||
// Stop drops all pending messages, and releases resources before returning.
|
||||
func (a *acker) Stop() {
|
||||
close(a.done)
|
||||
a.wg.Wait()
|
||||
}
|
||||
|
||||
const maxAckAttempts = 2
|
||||
|
||||
// ack acknowledges the supplied ackIDs.
|
||||
// After the acknowledgement request has completed (regardless of its success
|
||||
// or failure), ids will be passed to a.Notify.
|
||||
func (a *acker) ack(ids []string) {
|
||||
head, tail := a.s.splitAckIDs(ids)
|
||||
for len(head) > 0 {
|
||||
for i := 0; i < maxAckAttempts; i++ {
|
||||
if a.s.acknowledge(a.Ctx, a.Sub, head) == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
// NOTE: if retry gives up and returns an error, we simply drop
|
||||
// those ack IDs. The messages will be redelivered and this is
|
||||
// a documented behaviour of the API.
|
||||
head, tail = a.s.splitAckIDs(tail)
|
||||
}
|
||||
for _, id := range ids {
|
||||
a.Notify(id)
|
||||
}
|
||||
}
|
262
vendor/cloud.google.com/go/pubsub/acker_test.go
generated
vendored
Normal file
262
vendor/cloud.google.com/go/pubsub/acker_test.go
generated
vendored
Normal file
|
@ -0,0 +1,262 @@
|
|||
// 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 (
|
||||
"errors"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestAcker(t *testing.T) {
|
||||
tick := make(chan time.Time)
|
||||
s := &testService{acknowledgeCalled: make(chan acknowledgeCall)}
|
||||
|
||||
processed := make(chan string, 10)
|
||||
acker := &acker{
|
||||
s: s,
|
||||
Ctx: context.Background(),
|
||||
Sub: "subname",
|
||||
AckTick: tick,
|
||||
Notify: func(ackID string) { processed <- ackID },
|
||||
}
|
||||
acker.Start()
|
||||
|
||||
checkAckProcessed := func(ackIDs []string) {
|
||||
got := <-s.acknowledgeCalled
|
||||
sort.Strings(got.ackIDs)
|
||||
|
||||
want := acknowledgeCall{
|
||||
subName: "subname",
|
||||
ackIDs: ackIDs,
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("acknowledge: got:\n%v\nwant:\n%v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
acker.Ack("a")
|
||||
acker.Ack("b")
|
||||
tick <- time.Time{}
|
||||
checkAckProcessed([]string{"a", "b"})
|
||||
acker.Ack("c")
|
||||
tick <- time.Time{}
|
||||
checkAckProcessed([]string{"c"})
|
||||
acker.Stop()
|
||||
|
||||
// all IDS should have been sent to processed.
|
||||
close(processed)
|
||||
processedIDs := []string{}
|
||||
for id := range processed {
|
||||
processedIDs = append(processedIDs, id)
|
||||
}
|
||||
sort.Strings(processedIDs)
|
||||
want := []string{"a", "b", "c"}
|
||||
if !reflect.DeepEqual(processedIDs, want) {
|
||||
t.Errorf("acker processed: got:\n%v\nwant:\n%v", processedIDs, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAckerFastMode(t *testing.T) {
|
||||
tick := make(chan time.Time)
|
||||
s := &testService{acknowledgeCalled: make(chan acknowledgeCall)}
|
||||
|
||||
processed := make(chan string, 10)
|
||||
acker := &acker{
|
||||
s: s,
|
||||
Ctx: context.Background(),
|
||||
Sub: "subname",
|
||||
AckTick: tick,
|
||||
Notify: func(ackID string) { processed <- ackID },
|
||||
}
|
||||
acker.Start()
|
||||
|
||||
checkAckProcessed := func(ackIDs []string) {
|
||||
got := <-s.acknowledgeCalled
|
||||
sort.Strings(got.ackIDs)
|
||||
|
||||
want := acknowledgeCall{
|
||||
subName: "subname",
|
||||
ackIDs: ackIDs,
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("acknowledge: got:\n%v\nwant:\n%v", got, want)
|
||||
}
|
||||
}
|
||||
// No ticks are sent; fast mode doesn't need them.
|
||||
acker.Ack("a")
|
||||
acker.Ack("b")
|
||||
acker.FastMode()
|
||||
checkAckProcessed([]string{"a", "b"})
|
||||
acker.Ack("c")
|
||||
checkAckProcessed([]string{"c"})
|
||||
acker.Stop()
|
||||
|
||||
// all IDS should have been sent to processed.
|
||||
close(processed)
|
||||
processedIDs := []string{}
|
||||
for id := range processed {
|
||||
processedIDs = append(processedIDs, id)
|
||||
}
|
||||
sort.Strings(processedIDs)
|
||||
want := []string{"a", "b", "c"}
|
||||
if !reflect.DeepEqual(processedIDs, want) {
|
||||
t.Errorf("acker processed: got:\n%v\nwant:\n%v", processedIDs, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAckerStop checks that Stop returns immediately.
|
||||
func TestAckerStop(t *testing.T) {
|
||||
tick := make(chan time.Time)
|
||||
s := &testService{acknowledgeCalled: make(chan acknowledgeCall, 10)}
|
||||
|
||||
processed := make(chan string)
|
||||
acker := &acker{
|
||||
s: s,
|
||||
Ctx: context.Background(),
|
||||
Sub: "subname",
|
||||
AckTick: tick,
|
||||
Notify: func(ackID string) { processed <- ackID },
|
||||
}
|
||||
|
||||
acker.Start()
|
||||
|
||||
stopped := make(chan struct{})
|
||||
|
||||
acker.Ack("a")
|
||||
|
||||
go func() {
|
||||
acker.Stop()
|
||||
stopped <- struct{}{}
|
||||
}()
|
||||
|
||||
// Stopped should have been written to by the time this sleep completes.
|
||||
time.Sleep(time.Millisecond)
|
||||
|
||||
// Receiving from processed should cause Stop to subsequently return,
|
||||
// so it should never be possible to read from stopped before
|
||||
// processed.
|
||||
select {
|
||||
case <-stopped:
|
||||
case <-processed:
|
||||
t.Errorf("acker.Stop processed an ack id before returning")
|
||||
case <-time.After(time.Millisecond):
|
||||
t.Errorf("acker.Stop never returned")
|
||||
}
|
||||
}
|
||||
|
||||
type ackCallResult struct {
|
||||
ackIDs []string
|
||||
err error
|
||||
}
|
||||
|
||||
type ackService struct {
|
||||
service
|
||||
|
||||
calls []ackCallResult
|
||||
|
||||
t *testing.T // used for error logging.
|
||||
}
|
||||
|
||||
func (as *ackService) acknowledge(ctx context.Context, subName string, ackIDs []string) error {
|
||||
if len(as.calls) == 0 {
|
||||
as.t.Fatalf("unexpected call to acknowledge: ackIDs: %v", ackIDs)
|
||||
}
|
||||
call := as.calls[0]
|
||||
as.calls = as.calls[1:]
|
||||
|
||||
if got, want := ackIDs, call.ackIDs; !reflect.DeepEqual(got, want) {
|
||||
as.t.Errorf("unexpected arguments to acknowledge: got: %v ; want: %v", got, want)
|
||||
}
|
||||
return call.err
|
||||
}
|
||||
|
||||
// Test implementation returns the first 2 elements as head, and the rest as tail.
|
||||
func (as *ackService) splitAckIDs(ids []string) ([]string, []string) {
|
||||
if len(ids) < 2 {
|
||||
return ids, nil
|
||||
}
|
||||
return ids[:2], ids[2:]
|
||||
}
|
||||
|
||||
func TestAckerSplitsBatches(t *testing.T) {
|
||||
type testCase struct {
|
||||
calls []ackCallResult
|
||||
}
|
||||
for _, tc := range []testCase{
|
||||
{
|
||||
calls: []ackCallResult{
|
||||
{
|
||||
ackIDs: []string{"a", "b"},
|
||||
},
|
||||
{
|
||||
ackIDs: []string{"c", "d"},
|
||||
},
|
||||
{
|
||||
ackIDs: []string{"e", "f"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
calls: []ackCallResult{
|
||||
{
|
||||
ackIDs: []string{"a", "b"},
|
||||
err: errors.New("bang"),
|
||||
},
|
||||
// On error we retry once.
|
||||
{
|
||||
ackIDs: []string{"a", "b"},
|
||||
err: errors.New("bang"),
|
||||
},
|
||||
// We give up after failing twice, so we move on to the next set, "c" and "d"
|
||||
{
|
||||
ackIDs: []string{"c", "d"},
|
||||
err: errors.New("bang"),
|
||||
},
|
||||
// Again, we retry once.
|
||||
{
|
||||
ackIDs: []string{"c", "d"},
|
||||
},
|
||||
{
|
||||
ackIDs: []string{"e", "f"},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
s := &ackService{
|
||||
t: t,
|
||||
calls: tc.calls,
|
||||
}
|
||||
|
||||
acker := &acker{
|
||||
s: s,
|
||||
Ctx: context.Background(),
|
||||
Sub: "subname",
|
||||
Notify: func(string) {},
|
||||
}
|
||||
|
||||
acker.ack([]string{"a", "b", "c", "d", "e", "f"})
|
||||
|
||||
if len(s.calls) != 0 {
|
||||
t.Errorf("expected ack calls did not occur: %v", s.calls)
|
||||
}
|
||||
}
|
||||
}
|
9
vendor/cloud.google.com/go/pubsub/apiv1/README.md
generated
vendored
Normal file
9
vendor/cloud.google.com/go/pubsub/apiv1/README.md
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
Auto-generated pubsub v1 clients
|
||||
=================================
|
||||
|
||||
This package includes auto-generated clients for the pubsub v1 API.
|
||||
|
||||
Use the handwritten client (in the parent directory,
|
||||
cloud.google.com/go/pubsub) in preference to this.
|
||||
|
||||
This code is EXPERIMENTAL and subject to CHANGE AT ANY TIME.
|
36
vendor/cloud.google.com/go/pubsub/apiv1/doc.go
generated
vendored
Normal file
36
vendor/cloud.google.com/go/pubsub/apiv1/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
// 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.
|
||||
|
||||
// AUTO-GENERATED CODE. DO NOT EDIT.
|
||||
|
||||
// Package pubsub is an experimental, auto-generated package for the
|
||||
// pubsub API.
|
||||
//
|
||||
// Provides reliable, many-to-many, asynchronous messaging between
|
||||
// applications.
|
||||
//
|
||||
// Use the client at cloud.google.com/go/pubsub in preference to this.
|
||||
package pubsub // import "cloud.google.com/go/pubsub/apiv1"
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
func insertXGoog(ctx context.Context, val string) context.Context {
|
||||
md, _ := metadata.FromContext(ctx)
|
||||
md = md.Copy()
|
||||
md["x-goog-api-client"] = []string{val}
|
||||
return metadata.NewContext(ctx, md)
|
||||
}
|
1202
vendor/cloud.google.com/go/pubsub/apiv1/mock_test.go
generated
vendored
Normal file
1202
vendor/cloud.google.com/go/pubsub/apiv1/mock_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
394
vendor/cloud.google.com/go/pubsub/apiv1/publisher_client.go
generated
vendored
Normal file
394
vendor/cloud.google.com/go/pubsub/apiv1/publisher_client.go
generated
vendored
Normal file
|
@ -0,0 +1,394 @@
|
|||
// 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.
|
||||
|
||||
// AUTO-GENERATED CODE. DO NOT EDIT.
|
||||
|
||||
package pubsub
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/iam"
|
||||
"cloud.google.com/go/internal/version"
|
||||
gax "github.com/googleapis/gax-go"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/api/transport"
|
||||
pubsubpb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
var (
|
||||
publisherProjectPathTemplate = gax.MustCompilePathTemplate("projects/{project}")
|
||||
publisherTopicPathTemplate = gax.MustCompilePathTemplate("projects/{project}/topics/{topic}")
|
||||
)
|
||||
|
||||
// PublisherCallOptions contains the retry settings for each method of PublisherClient.
|
||||
type PublisherCallOptions struct {
|
||||
CreateTopic []gax.CallOption
|
||||
Publish []gax.CallOption
|
||||
GetTopic []gax.CallOption
|
||||
ListTopics []gax.CallOption
|
||||
ListTopicSubscriptions []gax.CallOption
|
||||
DeleteTopic []gax.CallOption
|
||||
}
|
||||
|
||||
func defaultPublisherClientOptions() []option.ClientOption {
|
||||
return []option.ClientOption{
|
||||
option.WithEndpoint("pubsub.googleapis.com:443"),
|
||||
option.WithScopes(
|
||||
"https://www.googleapis.com/auth/cloud-platform",
|
||||
"https://www.googleapis.com/auth/pubsub",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func defaultPublisherCallOptions() *PublisherCallOptions {
|
||||
retry := map[[2]string][]gax.CallOption{
|
||||
{"default", "idempotent"}: {
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.DeadlineExceeded,
|
||||
codes.Unavailable,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.3,
|
||||
})
|
||||
}),
|
||||
},
|
||||
{"messaging", "one_plus_delivery"}: {
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.DeadlineExceeded,
|
||||
codes.Unavailable,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.3,
|
||||
})
|
||||
}),
|
||||
},
|
||||
}
|
||||
return &PublisherCallOptions{
|
||||
CreateTopic: retry[[2]string{"default", "idempotent"}],
|
||||
Publish: retry[[2]string{"messaging", "one_plus_delivery"}],
|
||||
GetTopic: retry[[2]string{"default", "idempotent"}],
|
||||
ListTopics: retry[[2]string{"default", "idempotent"}],
|
||||
ListTopicSubscriptions: retry[[2]string{"default", "idempotent"}],
|
||||
DeleteTopic: retry[[2]string{"default", "idempotent"}],
|
||||
}
|
||||
}
|
||||
|
||||
// PublisherClient is a client for interacting with Google Cloud Pub/Sub API.
|
||||
type PublisherClient struct {
|
||||
// The connection to the service.
|
||||
conn *grpc.ClientConn
|
||||
|
||||
// The gRPC API client.
|
||||
publisherClient pubsubpb.PublisherClient
|
||||
|
||||
// The call options for this service.
|
||||
CallOptions *PublisherCallOptions
|
||||
|
||||
// The metadata to be sent with each request.
|
||||
xGoogHeader string
|
||||
}
|
||||
|
||||
// NewPublisherClient creates a new publisher client.
|
||||
//
|
||||
// The service that an application uses to manipulate topics, and to send
|
||||
// messages to a topic.
|
||||
func NewPublisherClient(ctx context.Context, opts ...option.ClientOption) (*PublisherClient, error) {
|
||||
conn, err := transport.DialGRPC(ctx, append(defaultPublisherClientOptions(), opts...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &PublisherClient{
|
||||
conn: conn,
|
||||
CallOptions: defaultPublisherCallOptions(),
|
||||
|
||||
publisherClient: pubsubpb.NewPublisherClient(conn),
|
||||
}
|
||||
c.SetGoogleClientInfo()
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Connection returns the client's connection to the API service.
|
||||
func (c *PublisherClient) Connection() *grpc.ClientConn {
|
||||
return c.conn
|
||||
}
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// the client is no longer required.
|
||||
func (c *PublisherClient) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
// SetGoogleClientInfo sets the name and version of the application in
|
||||
// the `x-goog-api-client` header passed on each request. Intended for
|
||||
// use by Google-written clients.
|
||||
func (c *PublisherClient) SetGoogleClientInfo(keyval ...string) {
|
||||
kv := append([]string{"gl-go", version.Go()}, keyval...)
|
||||
kv = append(kv, "gapic", version.Repo, "gax", gax.Version, "grpc", "")
|
||||
c.xGoogHeader = gax.XGoogHeader(kv...)
|
||||
}
|
||||
|
||||
// PublisherProjectPath returns the path for the project resource.
|
||||
func PublisherProjectPath(project string) string {
|
||||
path, err := publisherProjectPathTemplate.Render(map[string]string{
|
||||
"project": project,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// PublisherTopicPath returns the path for the topic resource.
|
||||
func PublisherTopicPath(project, topic string) string {
|
||||
path, err := publisherTopicPathTemplate.Render(map[string]string{
|
||||
"project": project,
|
||||
"topic": topic,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func (c *PublisherClient) SubscriptionIAM(subscription *pubsubpb.Subscription) *iam.Handle {
|
||||
return iam.InternalNewHandle(c.Connection(), subscription.Name)
|
||||
}
|
||||
|
||||
func (c *PublisherClient) TopicIAM(topic *pubsubpb.Topic) *iam.Handle {
|
||||
return iam.InternalNewHandle(c.Connection(), topic.Name)
|
||||
}
|
||||
|
||||
// CreateTopic creates the given topic with the given name.
|
||||
func (c *PublisherClient) CreateTopic(ctx context.Context, req *pubsubpb.Topic) (*pubsubpb.Topic, error) {
|
||||
ctx = insertXGoog(ctx, c.xGoogHeader)
|
||||
var resp *pubsubpb.Topic
|
||||
err := gax.Invoke(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
resp, err = c.publisherClient.CreateTopic(ctx, req)
|
||||
return err
|
||||
}, c.CallOptions.CreateTopic...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Publish adds one or more messages to the topic. Returns `NOT_FOUND` if the topic
|
||||
// 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) (*pubsubpb.PublishResponse, error) {
|
||||
ctx = insertXGoog(ctx, c.xGoogHeader)
|
||||
var resp *pubsubpb.PublishResponse
|
||||
err := gax.Invoke(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
resp, err = c.publisherClient.Publish(ctx, req)
|
||||
return err
|
||||
}, c.CallOptions.Publish...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetTopic gets the configuration of a topic.
|
||||
func (c *PublisherClient) GetTopic(ctx context.Context, req *pubsubpb.GetTopicRequest) (*pubsubpb.Topic, error) {
|
||||
ctx = insertXGoog(ctx, c.xGoogHeader)
|
||||
var resp *pubsubpb.Topic
|
||||
err := gax.Invoke(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
resp, err = c.publisherClient.GetTopic(ctx, req)
|
||||
return err
|
||||
}, c.CallOptions.GetTopic...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// ListTopics lists matching topics.
|
||||
func (c *PublisherClient) ListTopics(ctx context.Context, req *pubsubpb.ListTopicsRequest) *TopicIterator {
|
||||
ctx = insertXGoog(ctx, c.xGoogHeader)
|
||||
it := &TopicIterator{}
|
||||
it.InternalFetch = func(pageSize int, pageToken string) ([]*pubsubpb.Topic, string, error) {
|
||||
var resp *pubsubpb.ListTopicsResponse
|
||||
req.PageToken = pageToken
|
||||
if pageSize > math.MaxInt32 {
|
||||
req.PageSize = math.MaxInt32
|
||||
} else {
|
||||
req.PageSize = int32(pageSize)
|
||||
}
|
||||
err := gax.Invoke(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
resp, err = c.publisherClient.ListTopics(ctx, req)
|
||||
return err
|
||||
}, c.CallOptions.ListTopics...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return resp.Topics, resp.NextPageToken, nil
|
||||
}
|
||||
fetch := func(pageSize int, pageToken string) (string, error) {
|
||||
items, nextPageToken, err := it.InternalFetch(pageSize, pageToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
it.items = append(it.items, items...)
|
||||
return nextPageToken, nil
|
||||
}
|
||||
it.pageInfo, it.nextFunc = iterator.NewPageInfo(fetch, it.bufLen, it.takeBuf)
|
||||
return it
|
||||
}
|
||||
|
||||
// ListTopicSubscriptions lists the name of the subscriptions for this topic.
|
||||
func (c *PublisherClient) ListTopicSubscriptions(ctx context.Context, req *pubsubpb.ListTopicSubscriptionsRequest) *StringIterator {
|
||||
ctx = insertXGoog(ctx, c.xGoogHeader)
|
||||
it := &StringIterator{}
|
||||
it.InternalFetch = func(pageSize int, pageToken string) ([]string, string, error) {
|
||||
var resp *pubsubpb.ListTopicSubscriptionsResponse
|
||||
req.PageToken = pageToken
|
||||
if pageSize > math.MaxInt32 {
|
||||
req.PageSize = math.MaxInt32
|
||||
} else {
|
||||
req.PageSize = int32(pageSize)
|
||||
}
|
||||
err := gax.Invoke(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
resp, err = c.publisherClient.ListTopicSubscriptions(ctx, req)
|
||||
return err
|
||||
}, c.CallOptions.ListTopicSubscriptions...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return resp.Subscriptions, resp.NextPageToken, nil
|
||||
}
|
||||
fetch := func(pageSize int, pageToken string) (string, error) {
|
||||
items, nextPageToken, err := it.InternalFetch(pageSize, pageToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
it.items = append(it.items, items...)
|
||||
return nextPageToken, nil
|
||||
}
|
||||
it.pageInfo, it.nextFunc = iterator.NewPageInfo(fetch, it.bufLen, it.takeBuf)
|
||||
return it
|
||||
}
|
||||
|
||||
// DeleteTopic deletes the topic with the given name. Returns `NOT_FOUND` if the topic
|
||||
// does not exist. After a topic is deleted, a new topic may be created with
|
||||
// the same name; this is an entirely new topic with none of the old
|
||||
// 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) error {
|
||||
ctx = insertXGoog(ctx, c.xGoogHeader)
|
||||
err := gax.Invoke(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
_, err = c.publisherClient.DeleteTopic(ctx, req)
|
||||
return err
|
||||
}, c.CallOptions.DeleteTopic...)
|
||||
return err
|
||||
}
|
||||
|
||||
// StringIterator manages a stream of string.
|
||||
type StringIterator struct {
|
||||
items []string
|
||||
pageInfo *iterator.PageInfo
|
||||
nextFunc func() error
|
||||
|
||||
// InternalFetch is for use by the Google Cloud Libraries only.
|
||||
// It is not part of the stable interface of this package.
|
||||
//
|
||||
// InternalFetch returns results from a single call to the underlying RPC.
|
||||
// The number of results is no greater than pageSize.
|
||||
// If there are no more results, nextPageToken is empty and err is nil.
|
||||
InternalFetch func(pageSize int, pageToken string) (results []string, nextPageToken string, err error)
|
||||
}
|
||||
|
||||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
func (it *StringIterator) PageInfo() *iterator.PageInfo {
|
||||
return it.pageInfo
|
||||
}
|
||||
|
||||
// Next returns the next result. Its second return value is iterator.Done if there are no more
|
||||
// results. Once Next returns Done, all subsequent calls will return Done.
|
||||
func (it *StringIterator) Next() (string, error) {
|
||||
var item string
|
||||
if err := it.nextFunc(); err != nil {
|
||||
return item, err
|
||||
}
|
||||
item = it.items[0]
|
||||
it.items = it.items[1:]
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (it *StringIterator) bufLen() int {
|
||||
return len(it.items)
|
||||
}
|
||||
|
||||
func (it *StringIterator) takeBuf() interface{} {
|
||||
b := it.items
|
||||
it.items = nil
|
||||
return b
|
||||
}
|
||||
|
||||
// TopicIterator manages a stream of *pubsubpb.Topic.
|
||||
type TopicIterator struct {
|
||||
items []*pubsubpb.Topic
|
||||
pageInfo *iterator.PageInfo
|
||||
nextFunc func() error
|
||||
|
||||
// InternalFetch is for use by the Google Cloud Libraries only.
|
||||
// It is not part of the stable interface of this package.
|
||||
//
|
||||
// InternalFetch returns results from a single call to the underlying RPC.
|
||||
// The number of results is no greater than pageSize.
|
||||
// If there are no more results, nextPageToken is empty and err is nil.
|
||||
InternalFetch func(pageSize int, pageToken string) (results []*pubsubpb.Topic, nextPageToken string, err error)
|
||||
}
|
||||
|
||||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
func (it *TopicIterator) PageInfo() *iterator.PageInfo {
|
||||
return it.pageInfo
|
||||
}
|
||||
|
||||
// Next returns the next result. Its second return value is iterator.Done if there are no more
|
||||
// results. Once Next returns Done, all subsequent calls will return Done.
|
||||
func (it *TopicIterator) Next() (*pubsubpb.Topic, error) {
|
||||
var item *pubsubpb.Topic
|
||||
if err := it.nextFunc(); err != nil {
|
||||
return item, err
|
||||
}
|
||||
item = it.items[0]
|
||||
it.items = it.items[1:]
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (it *TopicIterator) bufLen() int {
|
||||
return len(it.items)
|
||||
}
|
||||
|
||||
func (it *TopicIterator) takeBuf() interface{} {
|
||||
b := it.items
|
||||
it.items = nil
|
||||
return b
|
||||
}
|
181
vendor/cloud.google.com/go/pubsub/apiv1/publisher_client_example_test.go
generated
vendored
Normal file
181
vendor/cloud.google.com/go/pubsub/apiv1/publisher_client_example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,181 @@
|
|||
// 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.
|
||||
|
||||
// AUTO-GENERATED CODE. DO NOT EDIT.
|
||||
|
||||
package pubsub_test
|
||||
|
||||
import (
|
||||
"cloud.google.com/go/pubsub/apiv1"
|
||||
"golang.org/x/net/context"
|
||||
pubsubpb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
)
|
||||
|
||||
func ExampleNewPublisherClient() {
|
||||
ctx := context.Background()
|
||||
c, err := pubsub.NewPublisherClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use client.
|
||||
_ = c
|
||||
}
|
||||
|
||||
func ExamplePublisherClient_SubscriptionIAM() {
|
||||
ctx := context.Background()
|
||||
c, err := pubsub.NewPublisherClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
subscription := &pubsubpb.Subscription{}
|
||||
h := c.SubscriptionIAM(subscription)
|
||||
policy, err := h.Policy(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
//TODO: Use the IAM policy
|
||||
_ = policy
|
||||
}
|
||||
|
||||
func ExamplePublisherClient_TopicIAM() {
|
||||
ctx := context.Background()
|
||||
c, err := pubsub.NewPublisherClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
topic := &pubsubpb.Topic{}
|
||||
h := c.TopicIAM(topic)
|
||||
policy, err := h.Policy(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
//TODO: Use the IAM policy
|
||||
_ = policy
|
||||
}
|
||||
|
||||
func ExamplePublisherClient_CreateTopic() {
|
||||
ctx := context.Background()
|
||||
c, err := pubsub.NewPublisherClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &pubsubpb.Topic{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.CreateTopic(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
|
||||
func ExamplePublisherClient_Publish() {
|
||||
ctx := context.Background()
|
||||
c, err := pubsub.NewPublisherClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &pubsubpb.PublishRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.Publish(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
|
||||
func ExamplePublisherClient_GetTopic() {
|
||||
ctx := context.Background()
|
||||
c, err := pubsub.NewPublisherClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &pubsubpb.GetTopicRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.GetTopic(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
|
||||
func ExamplePublisherClient_ListTopics() {
|
||||
ctx := context.Background()
|
||||
c, err := pubsub.NewPublisherClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &pubsubpb.ListTopicsRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
it := c.ListTopics(ctx, req)
|
||||
for {
|
||||
resp, err := it.Next()
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
break
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
}
|
||||
|
||||
func ExamplePublisherClient_ListTopicSubscriptions() {
|
||||
ctx := context.Background()
|
||||
c, err := pubsub.NewPublisherClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &pubsubpb.ListTopicSubscriptionsRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
it := c.ListTopicSubscriptions(ctx, req)
|
||||
for {
|
||||
resp, err := it.Next()
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
break
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
}
|
||||
|
||||
func ExamplePublisherClient_DeleteTopic() {
|
||||
ctx := context.Background()
|
||||
c, err := pubsub.NewPublisherClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &pubsubpb.DeleteTopicRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
err = c.DeleteTopic(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
}
|
409
vendor/cloud.google.com/go/pubsub/apiv1/subscriber_client.go
generated
vendored
Normal file
409
vendor/cloud.google.com/go/pubsub/apiv1/subscriber_client.go
generated
vendored
Normal file
|
@ -0,0 +1,409 @@
|
|||
// 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.
|
||||
|
||||
// AUTO-GENERATED CODE. DO NOT EDIT.
|
||||
|
||||
package pubsub
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/iam"
|
||||
"cloud.google.com/go/internal/version"
|
||||
gax "github.com/googleapis/gax-go"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/api/transport"
|
||||
pubsubpb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
var (
|
||||
subscriberProjectPathTemplate = gax.MustCompilePathTemplate("projects/{project}")
|
||||
subscriberSubscriptionPathTemplate = gax.MustCompilePathTemplate("projects/{project}/subscriptions/{subscription}")
|
||||
subscriberTopicPathTemplate = gax.MustCompilePathTemplate("projects/{project}/topics/{topic}")
|
||||
)
|
||||
|
||||
// SubscriberCallOptions contains the retry settings for each method of SubscriberClient.
|
||||
type SubscriberCallOptions struct {
|
||||
CreateSubscription []gax.CallOption
|
||||
GetSubscription []gax.CallOption
|
||||
ListSubscriptions []gax.CallOption
|
||||
DeleteSubscription []gax.CallOption
|
||||
ModifyAckDeadline []gax.CallOption
|
||||
Acknowledge []gax.CallOption
|
||||
Pull []gax.CallOption
|
||||
StreamingPull []gax.CallOption
|
||||
ModifyPushConfig []gax.CallOption
|
||||
}
|
||||
|
||||
func defaultSubscriberClientOptions() []option.ClientOption {
|
||||
return []option.ClientOption{
|
||||
option.WithEndpoint("pubsub.googleapis.com:443"),
|
||||
option.WithScopes(
|
||||
"https://www.googleapis.com/auth/cloud-platform",
|
||||
"https://www.googleapis.com/auth/pubsub",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func defaultSubscriberCallOptions() *SubscriberCallOptions {
|
||||
retry := map[[2]string][]gax.CallOption{
|
||||
{"default", "idempotent"}: {
|
||||
gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.DeadlineExceeded,
|
||||
codes.Unavailable,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60000 * time.Millisecond,
|
||||
Multiplier: 1.3,
|
||||
})
|
||||
}),
|
||||
},
|
||||
}
|
||||
return &SubscriberCallOptions{
|
||||
CreateSubscription: retry[[2]string{"default", "idempotent"}],
|
||||
GetSubscription: retry[[2]string{"default", "idempotent"}],
|
||||
ListSubscriptions: retry[[2]string{"default", "idempotent"}],
|
||||
DeleteSubscription: retry[[2]string{"default", "idempotent"}],
|
||||
ModifyAckDeadline: retry[[2]string{"default", "non_idempotent"}],
|
||||
Acknowledge: retry[[2]string{"messaging", "non_idempotent"}],
|
||||
Pull: retry[[2]string{"messaging", "non_idempotent"}],
|
||||
StreamingPull: retry[[2]string{"messaging", "non_idempotent"}],
|
||||
ModifyPushConfig: retry[[2]string{"default", "non_idempotent"}],
|
||||
}
|
||||
}
|
||||
|
||||
// SubscriberClient is a client for interacting with Google Cloud Pub/Sub API.
|
||||
type SubscriberClient struct {
|
||||
// The connection to the service.
|
||||
conn *grpc.ClientConn
|
||||
|
||||
// The gRPC API client.
|
||||
subscriberClient pubsubpb.SubscriberClient
|
||||
|
||||
// The call options for this service.
|
||||
CallOptions *SubscriberCallOptions
|
||||
|
||||
// The metadata to be sent with each request.
|
||||
xGoogHeader string
|
||||
}
|
||||
|
||||
// NewSubscriberClient creates a new subscriber client.
|
||||
//
|
||||
// The service that an application uses to manipulate subscriptions and to
|
||||
// consume messages from a subscription via the `Pull` method.
|
||||
func NewSubscriberClient(ctx context.Context, opts ...option.ClientOption) (*SubscriberClient, error) {
|
||||
conn, err := transport.DialGRPC(ctx, append(defaultSubscriberClientOptions(), opts...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &SubscriberClient{
|
||||
conn: conn,
|
||||
CallOptions: defaultSubscriberCallOptions(),
|
||||
|
||||
subscriberClient: pubsubpb.NewSubscriberClient(conn),
|
||||
}
|
||||
c.SetGoogleClientInfo()
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Connection returns the client's connection to the API service.
|
||||
func (c *SubscriberClient) Connection() *grpc.ClientConn {
|
||||
return c.conn
|
||||
}
|
||||
|
||||
// Close closes the connection to the API service. The user should invoke this when
|
||||
// the client is no longer required.
|
||||
func (c *SubscriberClient) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
// SetGoogleClientInfo sets the name and version of the application in
|
||||
// the `x-goog-api-client` header passed on each request. Intended for
|
||||
// use by Google-written clients.
|
||||
func (c *SubscriberClient) SetGoogleClientInfo(keyval ...string) {
|
||||
kv := append([]string{"gl-go", version.Go()}, keyval...)
|
||||
kv = append(kv, "gapic", version.Repo, "gax", gax.Version, "grpc", "")
|
||||
c.xGoogHeader = gax.XGoogHeader(kv...)
|
||||
}
|
||||
|
||||
// SubscriberProjectPath returns the path for the project resource.
|
||||
func SubscriberProjectPath(project string) string {
|
||||
path, err := subscriberProjectPathTemplate.Render(map[string]string{
|
||||
"project": project,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// SubscriberSubscriptionPath returns the path for the subscription resource.
|
||||
func SubscriberSubscriptionPath(project, subscription string) string {
|
||||
path, err := subscriberSubscriptionPathTemplate.Render(map[string]string{
|
||||
"project": project,
|
||||
"subscription": subscription,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// SubscriberTopicPath returns the path for the topic resource.
|
||||
func SubscriberTopicPath(project, topic string) string {
|
||||
path, err := subscriberTopicPathTemplate.Render(map[string]string{
|
||||
"project": project,
|
||||
"topic": topic,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func (c *SubscriberClient) SubscriptionIAM(subscription *pubsubpb.Subscription) *iam.Handle {
|
||||
return iam.InternalNewHandle(c.Connection(), subscription.Name)
|
||||
}
|
||||
|
||||
func (c *SubscriberClient) TopicIAM(topic *pubsubpb.Topic) *iam.Handle {
|
||||
return iam.InternalNewHandle(c.Connection(), topic.Name)
|
||||
}
|
||||
|
||||
// CreateSubscription creates a subscription to a given topic.
|
||||
// If the subscription already exists, returns `ALREADY_EXISTS`.
|
||||
// If the corresponding topic doesn't exist, returns `NOT_FOUND`.
|
||||
//
|
||||
// If the name is not provided in the request, the server will assign a random
|
||||
// name for this subscription on the same project as the topic, conforming
|
||||
// to the
|
||||
// [resource name format](https://cloud.google.com/pubsub/docs/overview#names).
|
||||
// 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) (*pubsubpb.Subscription, error) {
|
||||
ctx = insertXGoog(ctx, c.xGoogHeader)
|
||||
var resp *pubsubpb.Subscription
|
||||
err := gax.Invoke(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
resp, err = c.subscriberClient.CreateSubscription(ctx, req)
|
||||
return err
|
||||
}, c.CallOptions.CreateSubscription...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetSubscription gets the configuration details of a subscription.
|
||||
func (c *SubscriberClient) GetSubscription(ctx context.Context, req *pubsubpb.GetSubscriptionRequest) (*pubsubpb.Subscription, error) {
|
||||
ctx = insertXGoog(ctx, c.xGoogHeader)
|
||||
var resp *pubsubpb.Subscription
|
||||
err := gax.Invoke(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
resp, err = c.subscriberClient.GetSubscription(ctx, req)
|
||||
return err
|
||||
}, c.CallOptions.GetSubscription...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// ListSubscriptions lists matching subscriptions.
|
||||
func (c *SubscriberClient) ListSubscriptions(ctx context.Context, req *pubsubpb.ListSubscriptionsRequest) *SubscriptionIterator {
|
||||
ctx = insertXGoog(ctx, c.xGoogHeader)
|
||||
it := &SubscriptionIterator{}
|
||||
it.InternalFetch = func(pageSize int, pageToken string) ([]*pubsubpb.Subscription, string, error) {
|
||||
var resp *pubsubpb.ListSubscriptionsResponse
|
||||
req.PageToken = pageToken
|
||||
if pageSize > math.MaxInt32 {
|
||||
req.PageSize = math.MaxInt32
|
||||
} else {
|
||||
req.PageSize = int32(pageSize)
|
||||
}
|
||||
err := gax.Invoke(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
resp, err = c.subscriberClient.ListSubscriptions(ctx, req)
|
||||
return err
|
||||
}, c.CallOptions.ListSubscriptions...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return resp.Subscriptions, resp.NextPageToken, nil
|
||||
}
|
||||
fetch := func(pageSize int, pageToken string) (string, error) {
|
||||
items, nextPageToken, err := it.InternalFetch(pageSize, pageToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
it.items = append(it.items, items...)
|
||||
return nextPageToken, nil
|
||||
}
|
||||
it.pageInfo, it.nextFunc = iterator.NewPageInfo(fetch, it.bufLen, it.takeBuf)
|
||||
return it
|
||||
}
|
||||
|
||||
// DeleteSubscription deletes an existing subscription. All messages retained in the subscription
|
||||
// are immediately dropped. Calls to `Pull` after deletion will return
|
||||
// `NOT_FOUND`. After a subscription is deleted, a new one may be created with
|
||||
// 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) error {
|
||||
ctx = insertXGoog(ctx, c.xGoogHeader)
|
||||
err := gax.Invoke(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
_, err = c.subscriberClient.DeleteSubscription(ctx, req)
|
||||
return err
|
||||
}, c.CallOptions.DeleteSubscription...)
|
||||
return err
|
||||
}
|
||||
|
||||
// ModifyAckDeadline modifies the ack deadline for a specific message. This method is useful
|
||||
// to indicate that more time is needed to process a message by the
|
||||
// subscriber, or to make the message available for redelivery if the
|
||||
// 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) error {
|
||||
ctx = insertXGoog(ctx, c.xGoogHeader)
|
||||
err := gax.Invoke(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
_, err = c.subscriberClient.ModifyAckDeadline(ctx, req)
|
||||
return err
|
||||
}, c.CallOptions.ModifyAckDeadline...)
|
||||
return err
|
||||
}
|
||||
|
||||
// Acknowledge acknowledges the messages associated with the `ack_ids` in the
|
||||
// `AcknowledgeRequest`. The Pub/Sub system can remove the relevant messages
|
||||
// from the subscription.
|
||||
//
|
||||
// Acknowledging a message whose ack deadline has expired may succeed,
|
||||
// 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) error {
|
||||
ctx = insertXGoog(ctx, c.xGoogHeader)
|
||||
err := gax.Invoke(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
_, err = c.subscriberClient.Acknowledge(ctx, req)
|
||||
return err
|
||||
}, c.CallOptions.Acknowledge...)
|
||||
return err
|
||||
}
|
||||
|
||||
// Pull pulls messages from the server. Returns an empty list if there are no
|
||||
// messages available in the backlog. The server may return `UNAVAILABLE` if
|
||||
// there are too many concurrent pull requests pending for the given
|
||||
// subscription.
|
||||
func (c *SubscriberClient) Pull(ctx context.Context, req *pubsubpb.PullRequest) (*pubsubpb.PullResponse, error) {
|
||||
ctx = insertXGoog(ctx, c.xGoogHeader)
|
||||
var resp *pubsubpb.PullResponse
|
||||
err := gax.Invoke(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
resp, err = c.subscriberClient.Pull(ctx, req)
|
||||
return err
|
||||
}, c.CallOptions.Pull...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// StreamingPull (EXPERIMENTAL) StreamingPull is an experimental feature. This RPC will
|
||||
// respond with UNIMPLEMENTED errors unless you have been invited to test
|
||||
// this feature. Contact cloud-pubsub@google.com with any questions.
|
||||
//
|
||||
// Establishes a stream with the server, which sends messages down to the
|
||||
// client. The client streams acknowledgements and ack deadline modifications
|
||||
// back to the server. The server will close the stream and return the status
|
||||
// on any error. The server may close the stream with status `OK` to reassign
|
||||
// server-side resources, in which case, the client should re-establish the
|
||||
// stream. `UNAVAILABLE` may also be returned in the case of a transient error
|
||||
// (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) (pubsubpb.Subscriber_StreamingPullClient, error) {
|
||||
ctx = insertXGoog(ctx, c.xGoogHeader)
|
||||
var resp pubsubpb.Subscriber_StreamingPullClient
|
||||
err := gax.Invoke(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
resp, err = c.subscriberClient.StreamingPull(ctx)
|
||||
return err
|
||||
}, c.CallOptions.StreamingPull...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// ModifyPushConfig modifies the `PushConfig` for a specified subscription.
|
||||
//
|
||||
// This may be used to change a push subscription to a pull one (signified by
|
||||
// an empty `PushConfig`) or vice versa, or change the endpoint URL and other
|
||||
// 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) error {
|
||||
ctx = insertXGoog(ctx, c.xGoogHeader)
|
||||
err := gax.Invoke(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
_, err = c.subscriberClient.ModifyPushConfig(ctx, req)
|
||||
return err
|
||||
}, c.CallOptions.ModifyPushConfig...)
|
||||
return err
|
||||
}
|
||||
|
||||
// SubscriptionIterator manages a stream of *pubsubpb.Subscription.
|
||||
type SubscriptionIterator struct {
|
||||
items []*pubsubpb.Subscription
|
||||
pageInfo *iterator.PageInfo
|
||||
nextFunc func() error
|
||||
|
||||
// InternalFetch is for use by the Google Cloud Libraries only.
|
||||
// It is not part of the stable interface of this package.
|
||||
//
|
||||
// InternalFetch returns results from a single call to the underlying RPC.
|
||||
// The number of results is no greater than pageSize.
|
||||
// If there are no more results, nextPageToken is empty and err is nil.
|
||||
InternalFetch func(pageSize int, pageToken string) (results []*pubsubpb.Subscription, nextPageToken string, err error)
|
||||
}
|
||||
|
||||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
func (it *SubscriptionIterator) PageInfo() *iterator.PageInfo {
|
||||
return it.pageInfo
|
||||
}
|
||||
|
||||
// Next returns the next result. Its second return value is iterator.Done if there are no more
|
||||
// results. Once Next returns Done, all subsequent calls will return Done.
|
||||
func (it *SubscriptionIterator) Next() (*pubsubpb.Subscription, error) {
|
||||
var item *pubsubpb.Subscription
|
||||
if err := it.nextFunc(); err != nil {
|
||||
return item, err
|
||||
}
|
||||
item = it.items[0]
|
||||
it.items = it.items[1:]
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (it *SubscriptionIterator) bufLen() int {
|
||||
return len(it.items)
|
||||
}
|
||||
|
||||
func (it *SubscriptionIterator) takeBuf() interface{} {
|
||||
b := it.items
|
||||
it.items = nil
|
||||
return b
|
||||
}
|
243
vendor/cloud.google.com/go/pubsub/apiv1/subscriber_client_example_test.go
generated
vendored
Normal file
243
vendor/cloud.google.com/go/pubsub/apiv1/subscriber_client_example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,243 @@
|
|||
// 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.
|
||||
|
||||
// AUTO-GENERATED CODE. DO NOT EDIT.
|
||||
|
||||
package pubsub_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"cloud.google.com/go/pubsub/apiv1"
|
||||
"golang.org/x/net/context"
|
||||
pubsubpb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
)
|
||||
|
||||
func ExampleNewSubscriberClient() {
|
||||
ctx := context.Background()
|
||||
c, err := pubsub.NewSubscriberClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use client.
|
||||
_ = c
|
||||
}
|
||||
|
||||
func ExampleSubscriberClient_SubscriptionIAM() {
|
||||
ctx := context.Background()
|
||||
c, err := pubsub.NewSubscriberClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
subscription := &pubsubpb.Subscription{}
|
||||
h := c.SubscriptionIAM(subscription)
|
||||
policy, err := h.Policy(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
//TODO: Use the IAM policy
|
||||
_ = policy
|
||||
}
|
||||
|
||||
func ExampleSubscriberClient_TopicIAM() {
|
||||
ctx := context.Background()
|
||||
c, err := pubsub.NewSubscriberClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
topic := &pubsubpb.Topic{}
|
||||
h := c.TopicIAM(topic)
|
||||
policy, err := h.Policy(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
//TODO: Use the IAM policy
|
||||
_ = policy
|
||||
}
|
||||
|
||||
func ExampleSubscriberClient_CreateSubscription() {
|
||||
ctx := context.Background()
|
||||
c, err := pubsub.NewSubscriberClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &pubsubpb.Subscription{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.CreateSubscription(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
|
||||
func ExampleSubscriberClient_GetSubscription() {
|
||||
ctx := context.Background()
|
||||
c, err := pubsub.NewSubscriberClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &pubsubpb.GetSubscriptionRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.GetSubscription(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
|
||||
func ExampleSubscriberClient_ListSubscriptions() {
|
||||
ctx := context.Background()
|
||||
c, err := pubsub.NewSubscriberClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &pubsubpb.ListSubscriptionsRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
it := c.ListSubscriptions(ctx, req)
|
||||
for {
|
||||
resp, err := it.Next()
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
break
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleSubscriberClient_DeleteSubscription() {
|
||||
ctx := context.Background()
|
||||
c, err := pubsub.NewSubscriberClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &pubsubpb.DeleteSubscriptionRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
err = c.DeleteSubscription(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleSubscriberClient_ModifyAckDeadline() {
|
||||
ctx := context.Background()
|
||||
c, err := pubsub.NewSubscriberClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &pubsubpb.ModifyAckDeadlineRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
err = c.ModifyAckDeadline(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleSubscriberClient_Acknowledge() {
|
||||
ctx := context.Background()
|
||||
c, err := pubsub.NewSubscriberClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &pubsubpb.AcknowledgeRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
err = c.Acknowledge(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleSubscriberClient_Pull() {
|
||||
ctx := context.Background()
|
||||
c, err := pubsub.NewSubscriberClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &pubsubpb.PullRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
resp, err := c.Pull(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
|
||||
func ExampleSubscriberClient_StreamingPull() {
|
||||
ctx := context.Background()
|
||||
c, err := pubsub.NewSubscriberClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
stream, err := c.StreamingPull(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
go func() {
|
||||
reqs := []*pubsubpb.StreamingPullRequest{
|
||||
// TODO: Create requests.
|
||||
}
|
||||
for _, req := range reqs {
|
||||
if err := stream.Send(req); err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
}
|
||||
stream.CloseSend()
|
||||
}()
|
||||
for {
|
||||
resp, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// TODO: handle error.
|
||||
}
|
||||
// TODO: Use resp.
|
||||
_ = resp
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleSubscriberClient_ModifyPushConfig() {
|
||||
ctx := context.Background()
|
||||
c, err := pubsub.NewSubscriberClient(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
req := &pubsubpb.ModifyPushConfigRequest{
|
||||
// TODO: Fill request struct fields.
|
||||
}
|
||||
err = c.ModifyPushConfig(ctx, req)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
}
|
120
vendor/cloud.google.com/go/pubsub/doc.go
generated
vendored
Normal file
120
vendor/cloud.google.com/go/pubsub/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,120 @@
|
|||
// 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 provides an easy way to publish and receive Google Cloud Pub/Sub
|
||||
messages, hiding the the details of the underlying server RPCs. Google Cloud
|
||||
Pub/Sub is a many-to-many, asynchronous messaging system that decouples senders
|
||||
and receivers.
|
||||
|
||||
Note: This package is experimental and may make backwards-incompatible changes.
|
||||
|
||||
More information about Google Cloud Pub/Sub is available at
|
||||
https://cloud.google.com/pubsub/docs
|
||||
|
||||
Publishing
|
||||
|
||||
Google Cloud Pub/Sub messages are published to topics. Topics may be created
|
||||
using the pubsub package like so:
|
||||
|
||||
topic, err := pubsubClient.CreateTopic(context.Background(), "topic-name")
|
||||
|
||||
Messages may then be published to a topic:
|
||||
|
||||
msgIDs, err := topic.Publish(ctx, &pubsub.Message{
|
||||
Data: []byte("payload"),
|
||||
})
|
||||
|
||||
Receiving
|
||||
|
||||
To receive messages published to a topic, clients create subscriptions
|
||||
to the topic. There may be more than one subscription per topic; each message
|
||||
that is published to the topic will be delivered to all of its subscriptions.
|
||||
|
||||
Subsciptions may be created like so:
|
||||
|
||||
sub, err := pubsubClient.CreateSubscription(context.Background(), "sub-name", topic, 0, nil)
|
||||
|
||||
Messages are then consumed from a subscription via an iterator:
|
||||
|
||||
// Construct the iterator
|
||||
it, err := sub.Pull(context.Background())
|
||||
if err != nil {
|
||||
// handle err ...
|
||||
}
|
||||
defer it.Stop()
|
||||
|
||||
// Consume N messages
|
||||
for i := 0; i < N; i++ {
|
||||
msg, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// handle err ...
|
||||
break
|
||||
}
|
||||
|
||||
log.Print("got message: ", string(msg.Data))
|
||||
msg.Done(true)
|
||||
}
|
||||
|
||||
The message iterator returns messages one at a time, fetching batches of
|
||||
messages behind the scenes as needed. Once client code has processed the
|
||||
message, it must call Message.Done, otherwise the message will eventually be
|
||||
redelivered. For more information and configuration options, see "Deadlines"
|
||||
below.
|
||||
|
||||
Note: It is possible for Messages to be redelivered, even if Message.Done has
|
||||
been called. Client code must be robust to multiple deliveries of messages.
|
||||
|
||||
Deadlines
|
||||
|
||||
The default pubsub deadlines are suitable for most use cases, but may be
|
||||
overridden. This section describes the tradeoffs that should be considered
|
||||
when overriding the defaults.
|
||||
|
||||
Behind the scenes, each message returned by the Pub/Sub server has an
|
||||
associated lease, known as an "ACK deadline".
|
||||
Unless a message is acknowledged within the ACK deadline, or the client requests that
|
||||
the ACK deadline be extended, the message will become elegible for redelivery.
|
||||
As a convenience, the pubsub package will automatically extend deadlines until
|
||||
either:
|
||||
* Message.Done is called, or
|
||||
* the "MaxExtension" period elapses from the time the message is fetched from the server.
|
||||
|
||||
The initial ACK deadline given to each messages defaults to 10 seconds, but may
|
||||
be overridden during subscription creation. Selecting an ACK deadline is a
|
||||
tradeoff between message redelivery latency and RPC volume. If the pubsub
|
||||
package fails to acknowledge or extend a message (e.g. due to unexpected
|
||||
termination of the process), a shorter ACK deadline will generally result in
|
||||
faster message redelivery by the Pub/Sub system. However, a short ACK deadline
|
||||
may also increase the number of deadline extension RPCs that the pubsub package
|
||||
sends to the server.
|
||||
|
||||
The default max extension period is DefaultMaxExtension, and can be overridden
|
||||
by passing a MaxExtension option to Subscription.Pull. Selecting a max
|
||||
extension period is a tradeoff between the speed at which client code must
|
||||
process messages, and the redelivery delay if messages fail to be acknowledged
|
||||
(e.g. because client code neglects to do so). Using a large MaxExtension
|
||||
increases the available time for client code to process messages. However, if
|
||||
the client code neglects to call Message.Done, a large MaxExtension will
|
||||
increase the delay before the message is redelivered.
|
||||
|
||||
Authentication
|
||||
|
||||
See examples of authorization and authentication at
|
||||
https://godoc.org/cloud.google.com/go#pkg-examples.
|
||||
*/
|
||||
package pubsub // import "cloud.google.com/go/pubsub"
|
324
vendor/cloud.google.com/go/pubsub/endtoend_test.go
generated
vendored
Normal file
324
vendor/cloud.google.com/go/pubsub/endtoend_test.go
generated
vendored
Normal file
|
@ -0,0 +1,324 @@
|
|||
// Copyright 2014 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 (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"cloud.google.com/go/internal/testutil"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
)
|
||||
|
||||
const timeout = time.Minute * 10
|
||||
const ackDeadline = time.Second * 10
|
||||
|
||||
const batchSize = 100
|
||||
const batches = 100
|
||||
|
||||
// messageCounter keeps track of how many times a given message has been received.
|
||||
type messageCounter struct {
|
||||
mu sync.Mutex
|
||||
counts map[string]int
|
||||
// A value is sent to recv each time Inc is called.
|
||||
recv chan struct{}
|
||||
}
|
||||
|
||||
func (mc *messageCounter) Inc(msgID string) {
|
||||
mc.mu.Lock()
|
||||
mc.counts[msgID] += 1
|
||||
mc.mu.Unlock()
|
||||
mc.recv <- struct{}{}
|
||||
}
|
||||
|
||||
// process pulls messages from an iterator and records them in mc.
|
||||
func process(t *testing.T, it *MessageIterator, mc *messageCounter) {
|
||||
for {
|
||||
m, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected err from iterator: %v", err)
|
||||
return
|
||||
}
|
||||
mc.Inc(m.ID)
|
||||
// Simulate time taken to process m, while continuing to process more messages.
|
||||
go func() {
|
||||
// Some messages will need to have their ack deadline extended due to this delay.
|
||||
delay := rand.Intn(int(ackDeadline * 3))
|
||||
time.After(time.Duration(delay))
|
||||
m.Done(true)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// newIter constructs a new MessageIterator.
|
||||
func newIter(t *testing.T, ctx context.Context, sub *Subscription) *MessageIterator {
|
||||
it, err := sub.Pull(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("error constructing iterator: %v", err)
|
||||
}
|
||||
return it
|
||||
}
|
||||
|
||||
// launchIter launches a number of goroutines to pull from the supplied MessageIterator.
|
||||
func launchIter(t *testing.T, ctx context.Context, it *MessageIterator, mc *messageCounter, n int, wg *sync.WaitGroup) {
|
||||
for j := 0; j < n; j++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
process(t, it, mc)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// iteratorLifetime controls how long iterators live for before they are stopped.
|
||||
type iteratorLifetimes interface {
|
||||
// lifetimeChan should be called when an iterator is started. The
|
||||
// returned channel will send when the iterator should be stopped.
|
||||
lifetimeChan() <-chan time.Time
|
||||
}
|
||||
|
||||
var immortal = &explicitLifetimes{}
|
||||
|
||||
// explicitLifetimes implements iteratorLifetime with hard-coded lifetimes, falling back
|
||||
// to indefinite lifetimes when no explicit lifetimes remain.
|
||||
type explicitLifetimes struct {
|
||||
mu sync.Mutex
|
||||
lifetimes []time.Duration
|
||||
}
|
||||
|
||||
func (el *explicitLifetimes) lifetimeChan() <-chan time.Time {
|
||||
el.mu.Lock()
|
||||
defer el.mu.Unlock()
|
||||
if len(el.lifetimes) == 0 {
|
||||
return nil
|
||||
}
|
||||
lifetime := el.lifetimes[0]
|
||||
el.lifetimes = el.lifetimes[1:]
|
||||
return time.After(lifetime)
|
||||
}
|
||||
|
||||
// consumer consumes messages according to its configuration.
|
||||
type consumer struct {
|
||||
// How many goroutines should pull from the subscription.
|
||||
iteratorsInFlight int
|
||||
// How many goroutines should pull from each iterator.
|
||||
concurrencyPerIterator int
|
||||
|
||||
lifetimes iteratorLifetimes
|
||||
}
|
||||
|
||||
// consume reads messages from a subscription, and keeps track of what it receives in mc.
|
||||
// After consume returns, the caller should wait on wg to ensure that no more updates to mc will be made.
|
||||
func (c *consumer) consume(t *testing.T, ctx context.Context, sub *Subscription, mc *messageCounter, wg *sync.WaitGroup, stop <-chan struct{}) {
|
||||
for i := 0; i < c.iteratorsInFlight; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
it := newIter(t, ctx, sub)
|
||||
launchIter(t, ctx, it, mc, c.concurrencyPerIterator, wg)
|
||||
|
||||
select {
|
||||
case <-c.lifetimes.lifetimeChan():
|
||||
it.Stop()
|
||||
case <-stop:
|
||||
it.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// publish publishes many messages to topic, and returns the published message ids.
|
||||
func publish(t *testing.T, ctx context.Context, topic *Topic) []string {
|
||||
var published []string
|
||||
msgs := make([]*Message, batchSize)
|
||||
for i := 0; i < batches; i++ {
|
||||
for j := 0; j < batchSize; j++ {
|
||||
text := fmt.Sprintf("msg %02d-%02d", i, j)
|
||||
msgs[j] = &Message{Data: []byte(text)}
|
||||
}
|
||||
ids, err := topic.Publish(ctx, msgs...)
|
||||
if err != nil {
|
||||
t.Errorf("Publish error: %v", err)
|
||||
}
|
||||
published = append(published, ids...)
|
||||
}
|
||||
return published
|
||||
}
|
||||
|
||||
// diff returns counts of the differences between got and want.
|
||||
func diff(got, want map[string]int) map[string]int {
|
||||
ids := make(map[string]struct{})
|
||||
for k := range got {
|
||||
ids[k] = struct{}{}
|
||||
}
|
||||
for k := range want {
|
||||
ids[k] = struct{}{}
|
||||
}
|
||||
|
||||
gotWantCount := make(map[string]int)
|
||||
for k := range ids {
|
||||
if got[k] == want[k] {
|
||||
continue
|
||||
}
|
||||
desc := fmt.Sprintf("<got: %v ; want: %v>", got[k], want[k])
|
||||
gotWantCount[desc] += 1
|
||||
}
|
||||
return gotWantCount
|
||||
}
|
||||
|
||||
// TestEndToEnd pumps many messages into a topic and tests that they are all delivered to each subscription for the topic.
|
||||
// It also tests that messages are not unexpectedly redelivered.
|
||||
func TestEndToEnd(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Integration tests skipped in short mode")
|
||||
}
|
||||
ctx := context.Background()
|
||||
ts := testutil.TokenSource(ctx, ScopePubSub, ScopeCloudPlatform)
|
||||
if ts == nil {
|
||||
t.Skip("Integration tests skipped. See CONTRIBUTING.md for details")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
topicName := fmt.Sprintf("endtoend-%d", now.Unix())
|
||||
subPrefix := fmt.Sprintf("endtoend-%d", now.Unix())
|
||||
|
||||
client, err := NewClient(ctx, testutil.ProjID(), option.WithTokenSource(ts))
|
||||
if err != nil {
|
||||
t.Fatalf("Creating client error: %v", err)
|
||||
}
|
||||
|
||||
var topic *Topic
|
||||
if topic, err = client.CreateTopic(ctx, topicName); err != nil {
|
||||
t.Fatalf("CreateTopic error: %v", err)
|
||||
}
|
||||
defer topic.Delete(ctx)
|
||||
|
||||
// Three subscriptions to the same topic.
|
||||
var subA, subB, subC *Subscription
|
||||
if subA, err = client.CreateSubscription(ctx, subPrefix+"-a", topic, ackDeadline, nil); err != nil {
|
||||
t.Fatalf("CreateSub error: %v", err)
|
||||
}
|
||||
defer subA.Delete(ctx)
|
||||
|
||||
if subB, err = client.CreateSubscription(ctx, subPrefix+"-b", topic, ackDeadline, nil); err != nil {
|
||||
t.Fatalf("CreateSub error: %v", err)
|
||||
}
|
||||
defer subB.Delete(ctx)
|
||||
|
||||
if subC, err = client.CreateSubscription(ctx, subPrefix+"-c", topic, ackDeadline, nil); err != nil {
|
||||
t.Fatalf("CreateSub error: %v", err)
|
||||
}
|
||||
defer subC.Delete(ctx)
|
||||
|
||||
expectedCounts := make(map[string]int)
|
||||
for _, id := range publish(t, ctx, topic) {
|
||||
expectedCounts[id] = 1
|
||||
}
|
||||
|
||||
// recv provides an indication that messages are still arriving.
|
||||
recv := make(chan struct{})
|
||||
|
||||
// Keep track of the number of times each message (by message id) was
|
||||
// seen from each subscription.
|
||||
mcA := &messageCounter{counts: make(map[string]int), recv: recv}
|
||||
mcB := &messageCounter{counts: make(map[string]int), recv: recv}
|
||||
mcC := &messageCounter{counts: make(map[string]int), recv: recv}
|
||||
|
||||
stopC := make(chan struct{})
|
||||
|
||||
// We have three subscriptions to our topic.
|
||||
// Each subscription will get a copy of each pulished message.
|
||||
//
|
||||
// subA has just one iterator, while subB has two. The subB iterators
|
||||
// will each process roughly half of the messages for subB. All of
|
||||
// these iterators live until all messages have been consumed. subC is
|
||||
// processed by a series of short-lived iterators.
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
con := &consumer{
|
||||
concurrencyPerIterator: 1,
|
||||
iteratorsInFlight: 2,
|
||||
lifetimes: immortal,
|
||||
}
|
||||
con.consume(t, ctx, subA, mcA, &wg, stopC)
|
||||
|
||||
con = &consumer{
|
||||
concurrencyPerIterator: 1,
|
||||
iteratorsInFlight: 2,
|
||||
lifetimes: immortal,
|
||||
}
|
||||
con.consume(t, ctx, subB, mcB, &wg, stopC)
|
||||
|
||||
con = &consumer{
|
||||
concurrencyPerIterator: 1,
|
||||
iteratorsInFlight: 2,
|
||||
lifetimes: &explicitLifetimes{
|
||||
lifetimes: []time.Duration{ackDeadline, ackDeadline, ackDeadline / 2, ackDeadline / 2},
|
||||
},
|
||||
}
|
||||
con.consume(t, ctx, subC, mcC, &wg, stopC)
|
||||
|
||||
go func() {
|
||||
timeoutC := time.After(timeout)
|
||||
// Every time this ticker ticks, we will check if we have received any
|
||||
// messages since the last time it ticked. We check less frequently
|
||||
// than the ack deadline, so that we can detect if messages are
|
||||
// redelivered after having their ack deadline extended.
|
||||
checkQuiescence := time.NewTicker(ackDeadline * 3)
|
||||
defer checkQuiescence.Stop()
|
||||
|
||||
var received bool
|
||||
for {
|
||||
select {
|
||||
case <-recv:
|
||||
received = true
|
||||
case <-checkQuiescence.C:
|
||||
if received {
|
||||
received = false
|
||||
} else {
|
||||
close(stopC)
|
||||
return
|
||||
}
|
||||
case <-timeoutC:
|
||||
t.Errorf("timed out")
|
||||
close(stopC)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
for _, mc := range []*messageCounter{mcA, mcB, mcC} {
|
||||
if got, want := mc.counts, expectedCounts; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("message counts: %v\n", diff(got, want))
|
||||
}
|
||||
}
|
||||
}
|
54
vendor/cloud.google.com/go/pubsub/example_subscription_iterator_test.go
generated
vendored
Normal file
54
vendor/cloud.google.com/go/pubsub/example_subscription_iterator_test.go
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
// 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_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"cloud.google.com/go/pubsub"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/iterator"
|
||||
)
|
||||
|
||||
func ExampleClient_Subscriptions() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// List all subscriptions of the project.
|
||||
it := client.Subscriptions(ctx)
|
||||
_ = it // TODO: iterate using Next.
|
||||
}
|
||||
|
||||
func ExampleSubscriptionIterator_Next() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// List all subscriptions of the project.
|
||||
it := client.Subscriptions(ctx)
|
||||
for {
|
||||
sub, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
fmt.Println(sub)
|
||||
}
|
||||
}
|
299
vendor/cloud.google.com/go/pubsub/example_test.go
generated
vendored
Normal file
299
vendor/cloud.google.com/go/pubsub/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,299 @@
|
|||
// Copyright 2014 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_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/pubsub"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/iterator"
|
||||
)
|
||||
|
||||
func ExampleNewClient() {
|
||||
ctx := context.Background()
|
||||
_, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
// See the other examples to learn how to use the Client.
|
||||
}
|
||||
|
||||
func ExampleClient_CreateTopic() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
// Create a new topic with the given name.
|
||||
topic, err := client.CreateTopic(ctx, "topicName")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
_ = topic // TODO: use the topic.
|
||||
}
|
||||
|
||||
func ExampleClient_CreateSubscription() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
// Create a new topic with the given name.
|
||||
topic, err := client.CreateTopic(ctx, "topicName")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
// Create a new subscription to the previously created topic
|
||||
// with the given name.
|
||||
sub, err := client.CreateSubscription(ctx, "subName", topic, 10*time.Second, nil)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
_ = sub // TODO: use the subscription.
|
||||
}
|
||||
|
||||
func ExampleTopic_Delete() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
topic := client.Topic("topicName")
|
||||
if err := topic.Delete(ctx); err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleTopic_Exists() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
topic := client.Topic("topicName")
|
||||
ok, err := topic.Exists(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
if !ok {
|
||||
// Topic doesn't exist.
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleTopic_Publish() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
topic := client.Topic("topicName")
|
||||
msgIDs, err := topic.Publish(ctx, &pubsub.Message{
|
||||
Data: []byte("hello world"),
|
||||
})
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
fmt.Printf("Published a message with a message ID: %s\n", msgIDs[0])
|
||||
}
|
||||
|
||||
func ExampleTopic_Subscriptions() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
topic := client.Topic("topic-name")
|
||||
// List all subscriptions of the topic (maybe of multiple projects).
|
||||
for subs := topic.Subscriptions(ctx); ; {
|
||||
sub, err := subs.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
_ = sub // TODO: use the subscription.
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleSubscription_Delete() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
sub := client.Subscription("subName")
|
||||
if err := sub.Delete(ctx); err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleSubscription_Exists() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
sub := client.Subscription("subName")
|
||||
ok, err := sub.Exists(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
if !ok {
|
||||
// Subscription doesn't exist.
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleSubscription_Config() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
sub := client.Subscription("subName")
|
||||
config, err := sub.Config(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
fmt.Println(config)
|
||||
}
|
||||
|
||||
func ExampleSubscription_Pull() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
it, err := client.Subscription("subName").Pull(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// Ensure that the iterator is closed down cleanly.
|
||||
defer it.Stop()
|
||||
}
|
||||
|
||||
func ExampleSubscription_Pull_options() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
sub := client.Subscription("subName")
|
||||
// This program is expected to process and acknowledge messages
|
||||
// in 5 seconds. If not, Pub/Sub API will assume the message is not
|
||||
// acknowledged.
|
||||
it, err := sub.Pull(ctx, pubsub.MaxExtension(5*time.Second))
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// Ensure that the iterator is closed down cleanly.
|
||||
defer it.Stop()
|
||||
}
|
||||
|
||||
func ExampleSubscription_ModifyPushConfig() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
sub := client.Subscription("subName")
|
||||
if err := sub.ModifyPushConfig(ctx, &pubsub.PushConfig{Endpoint: "https://example.com/push"}); err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleMessageIterator_Next() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
it, err := client.Subscription("subName").Pull(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// Ensure that the iterator is closed down cleanly.
|
||||
defer it.Stop()
|
||||
// Consume 10 messages.
|
||||
for i := 0; i < 10; i++ {
|
||||
m, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
// There are no more messages. This will happen if it.Stop is called.
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
break
|
||||
}
|
||||
fmt.Printf("message %d: %s\n", i, m.Data)
|
||||
|
||||
// Acknowledge the message.
|
||||
m.Done(true)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleMessageIterator_Stop_defer() {
|
||||
// If all uses of the iterator occur within the lifetime of a single
|
||||
// function, stop it with defer.
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
it, err := client.Subscription("subName").Pull(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
// Ensure that the iterator is closed down cleanly.
|
||||
defer it.Stop()
|
||||
|
||||
// TODO: Use the iterator (see the example for MessageIterator.Next).
|
||||
}
|
||||
|
||||
func ExampleMessageIterator_Stop_goroutine() *pubsub.MessageIterator {
|
||||
// If you use the iterator outside the lifetime of a single function, you
|
||||
// must still stop it.
|
||||
// This (contrived) example returns an iterator that will yield messages
|
||||
// for ten seconds, and then stop.
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
it, err := client.Subscription("subName").Pull(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// Stop the iterator after receiving messages for ten seconds.
|
||||
go func() {
|
||||
time.Sleep(10 * time.Second)
|
||||
it.Stop()
|
||||
}()
|
||||
return it
|
||||
}
|
53
vendor/cloud.google.com/go/pubsub/example_topic_iterator_test.go
generated
vendored
Normal file
53
vendor/cloud.google.com/go/pubsub/example_topic_iterator_test.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
// 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_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"cloud.google.com/go/pubsub"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/iterator"
|
||||
)
|
||||
|
||||
func ExampleClient_Topics() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
it := client.Topics(ctx)
|
||||
_ = it // TODO: iterate using Next.
|
||||
}
|
||||
|
||||
func ExampleTopicIterator_Next() {
|
||||
ctx := context.Background()
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// List all topics.
|
||||
it := client.Topics(ctx)
|
||||
for {
|
||||
t, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
fmt.Println(t)
|
||||
}
|
||||
}
|
148
vendor/cloud.google.com/go/pubsub/fake_test.go
generated
vendored
Normal file
148
vendor/cloud.google.com/go/pubsub/fake_test.go
generated
vendored
Normal file
|
@ -0,0 +1,148 @@
|
|||
// 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 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"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"cloud.google.com/go/internal/testutil"
|
||||
pb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
)
|
||||
|
||||
type fakeServer struct {
|
||||
pb.PublisherServer
|
||||
pb.SubscriberServer
|
||||
|
||||
Addr string
|
||||
Acked map[string]bool // acked message IDs
|
||||
Deadlines map[string]int32 // deadlines by message ID
|
||||
pullResponses []*pullResponse
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
type pullResponse struct {
|
||||
msgs []*pb.ReceivedMessage
|
||||
err error
|
||||
}
|
||||
|
||||
func newFakeServer() (*fakeServer, error) {
|
||||
srv, err := testutil.NewServer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fake := &fakeServer{
|
||||
Addr: srv.Addr,
|
||||
Acked: map[string]bool{},
|
||||
Deadlines: map[string]int32{},
|
||||
}
|
||||
pb.RegisterPublisherServer(srv.Gsrv, fake)
|
||||
pb.RegisterSubscriberServer(srv.Gsrv, fake)
|
||||
srv.Start()
|
||||
return fake, nil
|
||||
}
|
||||
|
||||
// Each call to addStreamingPullMessages results in one StreamingPullResponse.
|
||||
func (s *fakeServer) addStreamingPullMessages(msgs []*pb.ReceivedMessage) {
|
||||
s.pullResponses = append(s.pullResponses, &pullResponse{msgs, nil})
|
||||
}
|
||||
|
||||
func (s *fakeServer) addStreamingPullError(err error) {
|
||||
s.pullResponses = append(s.pullResponses, &pullResponse{nil, err})
|
||||
}
|
||||
|
||||
func (s *fakeServer) wait() {
|
||||
s.wg.Wait()
|
||||
}
|
||||
|
||||
func (s *fakeServer) StreamingPull(stream pb.Subscriber_StreamingPullServer) error {
|
||||
// Receive initial request.
|
||||
_, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Consume and ignore subsequent requests.
|
||||
errc := make(chan error, 1)
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
defer s.wg.Done()
|
||||
for {
|
||||
req, err := stream.Recv()
|
||||
if err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
for _, id := range req.AckIds {
|
||||
s.Acked[id] = true
|
||||
}
|
||||
for i, id := range req.ModifyDeadlineAckIds {
|
||||
s.Deadlines[id] = req.ModifyDeadlineSeconds[i]
|
||||
}
|
||||
}
|
||||
}()
|
||||
// Send responses.
|
||||
for {
|
||||
if len(s.pullResponses) == 0 {
|
||||
// Nothing to send, so wait for the client to shut down the stream.
|
||||
err := <-errc // a real error, or at least EOF
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
pr := s.pullResponses[0]
|
||||
s.pullResponses = s.pullResponses[1:]
|
||||
if pr.err != nil {
|
||||
// Add a slight delay to ensure the server receives any
|
||||
// messages en route from the client before shutting down the stream.
|
||||
// This reduces flakiness of tests involving retry.
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
if pr.err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if pr.err != nil {
|
||||
return pr.err
|
||||
}
|
||||
// Return any error from Recv.
|
||||
select {
|
||||
case err := <-errc:
|
||||
return err
|
||||
default:
|
||||
}
|
||||
res := &pb.StreamingPullResponse{ReceivedMessages: pr.msgs}
|
||||
if err := stream.Send(res); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *fakeServer) GetSubscription(ctx context.Context, req *pb.GetSubscriptionRequest) (*pb.Subscription, error) {
|
||||
return &pb.Subscription{
|
||||
Name: req.Subscription,
|
||||
AckDeadlineSeconds: 10,
|
||||
PushConfig: &pb.PushConfig{},
|
||||
}, nil
|
||||
}
|
232
vendor/cloud.google.com/go/pubsub/integration_test.go
generated
vendored
Normal file
232
vendor/cloud.google.com/go/pubsub/integration_test.go
generated
vendored
Normal file
|
@ -0,0 +1,232 @@
|
|||
// Copyright 2014 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 (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"cloud.google.com/go/iam"
|
||||
"cloud.google.com/go/internal/testutil"
|
||||
"google.golang.org/api/option"
|
||||
)
|
||||
|
||||
// messageData is used to hold the contents of a message so that it can be compared against the contents
|
||||
// of another message without regard to irrelevant fields.
|
||||
type messageData struct {
|
||||
ID string
|
||||
Data []byte
|
||||
Attributes map[string]string
|
||||
}
|
||||
|
||||
func extractMessageData(m *Message) *messageData {
|
||||
return &messageData{
|
||||
ID: m.ID,
|
||||
Data: m.Data,
|
||||
Attributes: m.Attributes,
|
||||
}
|
||||
}
|
||||
|
||||
func TestAll(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Integration tests skipped in short mode")
|
||||
}
|
||||
ctx := context.Background()
|
||||
ts := testutil.TokenSource(ctx, ScopePubSub, ScopeCloudPlatform)
|
||||
if ts == nil {
|
||||
t.Skip("Integration tests skipped. See CONTRIBUTING.md for details")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
topicName := fmt.Sprintf("topic-%d", now.Unix())
|
||||
subName := fmt.Sprintf("subscription-%d", now.Unix())
|
||||
|
||||
client, err := NewClient(ctx, testutil.ProjID(), option.WithTokenSource(ts))
|
||||
if err != nil {
|
||||
t.Fatalf("Creating client error: %v", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
var topic *Topic
|
||||
if topic, err = client.CreateTopic(ctx, topicName); err != nil {
|
||||
t.Errorf("CreateTopic error: %v", err)
|
||||
}
|
||||
|
||||
var sub *Subscription
|
||||
if sub, err = client.CreateSubscription(ctx, subName, topic, 0, nil); err != nil {
|
||||
t.Errorf("CreateSub error: %v", err)
|
||||
}
|
||||
|
||||
exists, err := topic.Exists(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("TopicExists error: %v", err)
|
||||
}
|
||||
if !exists {
|
||||
t.Errorf("topic %s should exist, but it doesn't", topic)
|
||||
}
|
||||
|
||||
exists, err = sub.Exists(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("SubExists error: %v", err)
|
||||
}
|
||||
if !exists {
|
||||
t.Errorf("subscription %s should exist, but it doesn't", subName)
|
||||
}
|
||||
|
||||
msgs := []*Message{}
|
||||
for i := 0; i < 10; i++ {
|
||||
text := fmt.Sprintf("a message with an index %d", i)
|
||||
attrs := make(map[string]string)
|
||||
attrs["foo"] = "bar"
|
||||
msgs = append(msgs, &Message{
|
||||
Data: []byte(text),
|
||||
Attributes: attrs,
|
||||
})
|
||||
}
|
||||
|
||||
ids, err := topic.Publish(ctx, msgs...)
|
||||
if err != nil {
|
||||
t.Fatalf("Publish (1) error: %v", err)
|
||||
}
|
||||
|
||||
if len(ids) != len(msgs) {
|
||||
t.Errorf("unexpected number of message IDs received; %d, want %d", len(ids), len(msgs))
|
||||
}
|
||||
|
||||
want := make(map[string]*messageData)
|
||||
for i, m := range msgs {
|
||||
md := extractMessageData(m)
|
||||
md.ID = ids[i]
|
||||
want[md.ID] = md
|
||||
}
|
||||
|
||||
// Use a timeout to ensure that Pull does not block indefinitely if there are unexpectedly few messages available.
|
||||
timeoutCtx, _ := context.WithTimeout(ctx, time.Minute)
|
||||
it, err := sub.Pull(timeoutCtx)
|
||||
if err != nil {
|
||||
t.Fatalf("error constructing iterator: %v", err)
|
||||
}
|
||||
defer it.Stop()
|
||||
got := make(map[string]*messageData)
|
||||
for i := 0; i < len(want); i++ {
|
||||
m, err := it.Next()
|
||||
if err != nil {
|
||||
t.Fatalf("error getting next message: %v", err)
|
||||
}
|
||||
md := extractMessageData(m)
|
||||
got[md.ID] = md
|
||||
m.Done(true)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("messages: got: %v ; want: %v", got, want)
|
||||
}
|
||||
|
||||
// base64 test
|
||||
data := "=@~"
|
||||
_, err = topic.Publish(ctx, &Message{Data: []byte(data)})
|
||||
if err != nil {
|
||||
t.Fatalf("Publish error: %v", err)
|
||||
}
|
||||
|
||||
m, err := it.Next()
|
||||
if err != nil {
|
||||
t.Fatalf("Pull error: %v", err)
|
||||
}
|
||||
|
||||
if string(m.Data) != data {
|
||||
t.Errorf("unexpected message received; %s, want %s", string(m.Data), data)
|
||||
}
|
||||
m.Done(true)
|
||||
|
||||
if msg, ok := testIAM(ctx, topic.IAM(), "pubsub.topics.get"); !ok {
|
||||
t.Errorf("topic IAM: %s", msg)
|
||||
}
|
||||
if msg, ok := testIAM(ctx, sub.IAM(), "pubsub.subscriptions.get"); !ok {
|
||||
t.Errorf("sub IAM: %s", msg)
|
||||
}
|
||||
|
||||
err = sub.Delete(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("DeleteSub error: %v", err)
|
||||
}
|
||||
|
||||
err = topic.Delete(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("DeleteTopic error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// IAM tests.
|
||||
// NOTE: for these to succeed, the test runner identity must have the Pub/Sub Admin or Owner roles.
|
||||
// To set, visit https://console.developers.google.com, select "IAM & Admin" from the top-left
|
||||
// menu, choose the account, click the Roles dropdown, and select "Pub/Sub > Pub/Sub Admin".
|
||||
// TODO(jba): move this to a testing package within cloud.google.com/iam, so we can re-use it.
|
||||
func testIAM(ctx context.Context, h *iam.Handle, permission string) (msg string, ok bool) {
|
||||
// Attempting to add an non-existent identity (e.g. "alice@example.com") causes the service
|
||||
// to return an internal error, so use a real identity.
|
||||
const member = "domain:google.com"
|
||||
|
||||
var policy *iam.Policy
|
||||
var err error
|
||||
|
||||
if policy, err = h.Policy(ctx); err != nil {
|
||||
return fmt.Sprintf("Policy: %v", err), false
|
||||
}
|
||||
// The resource is new, so the policy should be empty.
|
||||
if got := policy.Roles(); len(got) > 0 {
|
||||
return fmt.Sprintf("initially: got roles %v, want none", got), false
|
||||
}
|
||||
// Add a member, set the policy, then check that the member is present.
|
||||
policy.Add(member, iam.Viewer)
|
||||
if err := h.SetPolicy(ctx, policy); err != nil {
|
||||
return fmt.Sprintf("SetPolicy: %v", err), false
|
||||
}
|
||||
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) {
|
||||
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.
|
||||
policy.Remove(member, iam.Viewer)
|
||||
if err := h.SetPolicy(ctx, policy); err != nil {
|
||||
return fmt.Sprintf("SetPolicy: %v", err), false
|
||||
}
|
||||
if policy, err = h.Policy(ctx); err != nil {
|
||||
return fmt.Sprintf("Policy: %v", err), false
|
||||
}
|
||||
if got := policy.Roles(); len(got) > 0 {
|
||||
return fmt.Sprintf("after Remove: got roles %v, want none", got), false
|
||||
}
|
||||
// Call TestPermissions.
|
||||
// Because this user is an admin, it has all the permissions on the
|
||||
// resource type. Note: the service fails if we ask for inapplicable
|
||||
// permissions (e.g. a subscription permission on a topic, or a topic
|
||||
// create permission on a topic rather than its parent).
|
||||
wantPerms := []string{permission}
|
||||
gotPerms, err := h.TestPermissions(ctx, wantPerms)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("TestPermissions: %v", err), false
|
||||
}
|
||||
if !reflect.DeepEqual(gotPerms, wantPerms) {
|
||||
return fmt.Sprintf("TestPermissions: got %v, want %v", gotPerms, wantPerms), false
|
||||
}
|
||||
return "", true
|
||||
}
|
527
vendor/cloud.google.com/go/pubsub/iterator.go
generated
vendored
Normal file
527
vendor/cloud.google.com/go/pubsub/iterator.go
generated
vendored
Normal file
|
@ -0,0 +1,527 @@
|
|||
// 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 (
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/support/bundler"
|
||||
pb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
type MessageIterator struct {
|
||||
impl interface {
|
||||
next() (*Message, error)
|
||||
stop()
|
||||
}
|
||||
}
|
||||
|
||||
type pollingMessageIterator struct {
|
||||
// kaTicker controls how often we send an ack deadline extension request.
|
||||
kaTicker *time.Ticker
|
||||
// ackTicker controls how often we acknowledge a batch of messages.
|
||||
ackTicker *time.Ticker
|
||||
|
||||
ka *keepAlive
|
||||
acker *acker
|
||||
nacker *bundler.Bundler
|
||||
puller *puller
|
||||
|
||||
// mu ensures that cleanup only happens once, and concurrent Stop
|
||||
// invocations block until cleanup completes.
|
||||
mu sync.Mutex
|
||||
|
||||
// closed is used to signal that Stop has been called.
|
||||
closed chan struct{}
|
||||
}
|
||||
|
||||
var useStreamingPull = false
|
||||
|
||||
// newMessageIterator starts a new MessageIterator. Stop must be called on the MessageIterator
|
||||
// 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) *MessageIterator {
|
||||
if !useStreamingPull {
|
||||
return &MessageIterator{
|
||||
impl: newPollingMessageIterator(ctx, s, subName, po),
|
||||
}
|
||||
}
|
||||
sp := s.newStreamingPuller(ctx, subName, int32(po.ackDeadline.Seconds()))
|
||||
err := sp.open()
|
||||
if grpc.Code(err) == codes.Unimplemented {
|
||||
log.Println("pubsub: streaming pull unimplemented; falling back to legacy pull")
|
||||
return &MessageIterator{
|
||||
impl: newPollingMessageIterator(ctx, s, subName, po),
|
||||
}
|
||||
}
|
||||
// TODO(jba): handle other non-nil error?
|
||||
log.Println("using streaming pull")
|
||||
return &MessageIterator{
|
||||
impl: newStreamingMessageIterator(ctx, sp, po),
|
||||
}
|
||||
}
|
||||
|
||||
func newPollingMessageIterator(ctx context.Context, s service, subName string, po *pullOptions) *pollingMessageIterator {
|
||||
// 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.
|
||||
keepAlivePeriod := po.ackDeadline - 5*time.Second
|
||||
kaTicker := time.NewTicker(keepAlivePeriod) // Stopped in it.Stop
|
||||
|
||||
// TODO: make ackTicker more configurable. Something less than
|
||||
// kaTicker is a reasonable default (there's no point extending
|
||||
// messages when they could be acked instead).
|
||||
ackTicker := time.NewTicker(keepAlivePeriod / 2) // Stopped in it.Stop
|
||||
|
||||
ka := &keepAlive{
|
||||
s: s,
|
||||
Ctx: ctx,
|
||||
Sub: subName,
|
||||
ExtensionTick: kaTicker.C,
|
||||
Deadline: po.ackDeadline,
|
||||
MaxExtension: po.maxExtension,
|
||||
}
|
||||
|
||||
ack := &acker{
|
||||
s: s,
|
||||
Ctx: ctx,
|
||||
Sub: subName,
|
||||
AckTick: ackTicker.C,
|
||||
Notify: ka.Remove,
|
||||
}
|
||||
|
||||
nacker := bundler.NewBundler("", func(ackIDs interface{}) {
|
||||
// NACK by setting the ack deadline to zero, to make the message
|
||||
// immediately available for redelivery.
|
||||
//
|
||||
// If the RPC fails, nothing we can do about it. In the worst case, the
|
||||
// deadline for these messages will expire and they will still get
|
||||
// redelivered.
|
||||
_ = s.modifyAckDeadline(ctx, subName, 0, ackIDs.([]string))
|
||||
})
|
||||
nacker.DelayThreshold = keepAlivePeriod / 10 // nack promptly
|
||||
nacker.BundleCountThreshold = 10
|
||||
|
||||
pull := newPuller(s, subName, ctx, po.maxPrefetch, ka.Add, ka.Remove)
|
||||
|
||||
ka.Start()
|
||||
ack.Start()
|
||||
return &pollingMessageIterator{
|
||||
kaTicker: kaTicker,
|
||||
ackTicker: ackTicker,
|
||||
ka: ka,
|
||||
acker: ack,
|
||||
nacker: nacker,
|
||||
puller: pull,
|
||||
closed: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Next returns the next Message to be processed. The caller must call
|
||||
// Message.Done when finished with it.
|
||||
// Once Stop has been called, calls to Next will return iterator.Done.
|
||||
func (it *MessageIterator) Next() (*Message, error) {
|
||||
return it.impl.next()
|
||||
}
|
||||
|
||||
func (it *pollingMessageIterator) next() (*Message, error) {
|
||||
m, err := it.puller.Next()
|
||||
if err == nil {
|
||||
m.done = it.done
|
||||
return m, nil
|
||||
}
|
||||
|
||||
select {
|
||||
// If Stop has been called, we return Done regardless the value of err.
|
||||
case <-it.closed:
|
||||
return nil, iterator.Done
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Client code must call Stop on a MessageIterator when finished with it.
|
||||
// Stop will block until Done has been called on all Messages that have been
|
||||
// returned by Next, or until the context with which the MessageIterator was created
|
||||
// is cancelled or exceeds its deadline.
|
||||
// Stop need only be called once, but may be called multiple times from
|
||||
// multiple goroutines.
|
||||
func (it *MessageIterator) Stop() {
|
||||
it.impl.stop()
|
||||
}
|
||||
|
||||
func (it *pollingMessageIterator) stop() {
|
||||
it.mu.Lock()
|
||||
defer it.mu.Unlock()
|
||||
|
||||
select {
|
||||
case <-it.closed:
|
||||
// Cleanup has already been performed.
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// We close this channel before calling it.puller.Stop to ensure that we
|
||||
// reliably return iterator.Done from Next.
|
||||
close(it.closed)
|
||||
|
||||
// Stop the puller. Once this completes, no more messages will be added
|
||||
// to it.ka.
|
||||
it.puller.Stop()
|
||||
|
||||
// Start acking messages as they arrive, ignoring ackTicker. This will
|
||||
// result in it.ka.Stop, below, returning as soon as possible.
|
||||
it.acker.FastMode()
|
||||
|
||||
// This will block until
|
||||
// (a) it.ka.Ctx is done, or
|
||||
// (b) all messages have been removed from keepAlive.
|
||||
// (b) will happen once all outstanding messages have been either ACKed or NACKed.
|
||||
it.ka.Stop()
|
||||
|
||||
// There are no more live messages, so kill off the acker.
|
||||
it.acker.Stop()
|
||||
it.nacker.Stop()
|
||||
it.kaTicker.Stop()
|
||||
it.ackTicker.Stop()
|
||||
}
|
||||
|
||||
func (it *pollingMessageIterator) done(ackID string, ack bool) {
|
||||
if ack {
|
||||
it.acker.Ack(ackID)
|
||||
// There's no need to call it.ka.Remove here, as acker will
|
||||
// call it via its Notify function.
|
||||
} else {
|
||||
it.ka.Remove(ackID)
|
||||
_ = it.nacker.Add(ackID, len(ackID)) // ignore error; this is just an optimization
|
||||
}
|
||||
}
|
||||
|
||||
type streamingMessageIterator struct {
|
||||
ctx context.Context
|
||||
po *pullOptions
|
||||
sp *streamingPuller
|
||||
kaTicker *time.Ticker // keep-alive (deadline extensions)
|
||||
ackTicker *time.Ticker // message acks
|
||||
nackTicker *time.Ticker // message nacks (more frequent than acks)
|
||||
failed chan struct{} // closed on stream error
|
||||
stopped chan struct{} // closed when Stop is called
|
||||
drained chan struct{} // closed when stopped && no more pending messages
|
||||
msgc chan *Message
|
||||
wg sync.WaitGroup
|
||||
|
||||
mu sync.Mutex
|
||||
keepAliveDeadlines map[string]time.Time
|
||||
pendingReq *pb.StreamingPullRequest
|
||||
err error // error from stream failure
|
||||
}
|
||||
|
||||
const messageBufferSize = 1000
|
||||
|
||||
func newStreamingMessageIterator(ctx context.Context, sp *streamingPuller, 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.
|
||||
keepAlivePeriod := po.ackDeadline - 5*time.Second
|
||||
kaTicker := time.NewTicker(keepAlivePeriod)
|
||||
|
||||
// TODO: make ackTicker more configurable. Something less than
|
||||
// kaTicker is a reasonable default (there's no point extending
|
||||
// messages when they could be acked instead).
|
||||
ackTicker := time.NewTicker(keepAlivePeriod / 2)
|
||||
nackTicker := time.NewTicker(keepAlivePeriod / 10)
|
||||
it := &streamingMessageIterator{
|
||||
ctx: ctx,
|
||||
sp: sp,
|
||||
po: po,
|
||||
kaTicker: kaTicker,
|
||||
ackTicker: ackTicker,
|
||||
nackTicker: nackTicker,
|
||||
failed: make(chan struct{}),
|
||||
stopped: make(chan struct{}),
|
||||
drained: make(chan struct{}),
|
||||
msgc: make(chan *Message, messageBufferSize),
|
||||
keepAliveDeadlines: map[string]time.Time{},
|
||||
pendingReq: &pb.StreamingPullRequest{},
|
||||
}
|
||||
it.wg.Add(2)
|
||||
go it.receiver()
|
||||
go it.sender()
|
||||
return it
|
||||
}
|
||||
|
||||
func (it *streamingMessageIterator) next() (*Message, error) {
|
||||
// If ctx has been cancelled or the iterator is done, return straight
|
||||
// away (even if there are buffered messages available).
|
||||
select {
|
||||
case <-it.ctx.Done():
|
||||
return nil, it.ctx.Err()
|
||||
|
||||
case <-it.failed:
|
||||
break
|
||||
|
||||
case <-it.stopped:
|
||||
break
|
||||
|
||||
default:
|
||||
// Wait for a message, but also for one of the above conditions.
|
||||
select {
|
||||
case msg := <-it.msgc:
|
||||
// Since active select cases are chosen at random, this can return
|
||||
// nil (from the channel close) even if it.failed or it.stopped is
|
||||
// closed.
|
||||
if msg == nil {
|
||||
break
|
||||
}
|
||||
msg.done = it.done
|
||||
return msg, nil
|
||||
|
||||
case <-it.ctx.Done():
|
||||
return nil, it.ctx.Err()
|
||||
|
||||
case <-it.failed:
|
||||
break
|
||||
|
||||
case <-it.stopped:
|
||||
break
|
||||
}
|
||||
}
|
||||
// Here if the iterator is done.
|
||||
it.mu.Lock()
|
||||
defer it.mu.Unlock()
|
||||
return nil, it.err
|
||||
}
|
||||
|
||||
func (it *streamingMessageIterator) stop() {
|
||||
it.mu.Lock()
|
||||
select {
|
||||
case <-it.stopped:
|
||||
it.mu.Unlock()
|
||||
it.wg.Wait()
|
||||
return
|
||||
default:
|
||||
close(it.stopped)
|
||||
}
|
||||
if it.err == nil {
|
||||
it.err = iterator.Done
|
||||
}
|
||||
// Before reading from the channel, see if we're already drained.
|
||||
it.checkDrained()
|
||||
it.mu.Unlock()
|
||||
// Nack all the pending messages.
|
||||
// Grab the lock separately for each message to allow the receiver
|
||||
// and sender goroutines to make progress.
|
||||
// Why this will eventually terminate:
|
||||
// - If the receiver is not blocked on a stream Recv, then
|
||||
// it will write all the messages it has received to the channel,
|
||||
// then exit, closing the channel.
|
||||
// - If the receiver is blocked, then this loop will eventually
|
||||
// nack all the messages in the channel. Once done is called
|
||||
// on the remaining messages, the iterator will be marked as drained,
|
||||
// which will trigger the sender to terminate. When it does, it
|
||||
// performs a CloseSend on the stream, which will result in the blocked
|
||||
// stream Recv returning.
|
||||
for m := range it.msgc {
|
||||
it.mu.Lock()
|
||||
delete(it.keepAliveDeadlines, m.ackID)
|
||||
it.addDeadlineMod(m.ackID, 0)
|
||||
it.checkDrained()
|
||||
it.mu.Unlock()
|
||||
}
|
||||
it.wg.Wait()
|
||||
}
|
||||
|
||||
// checkDrained closes the drained channel if the iterator has been stopped and all
|
||||
// pending messages have either been n/acked or expired.
|
||||
//
|
||||
// Called with the lock held.
|
||||
func (it *streamingMessageIterator) checkDrained() {
|
||||
select {
|
||||
case <-it.drained:
|
||||
return
|
||||
default:
|
||||
}
|
||||
select {
|
||||
case <-it.stopped:
|
||||
if len(it.keepAliveDeadlines) == 0 {
|
||||
close(it.drained)
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Called when a message is acked/nacked.
|
||||
func (it *streamingMessageIterator) done(ackID string, ack bool) {
|
||||
it.mu.Lock()
|
||||
defer it.mu.Unlock()
|
||||
delete(it.keepAliveDeadlines, ackID)
|
||||
if ack {
|
||||
it.pendingReq.AckIds = append(it.pendingReq.AckIds, ackID)
|
||||
} else {
|
||||
it.addDeadlineMod(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()
|
||||
if it.err == nil {
|
||||
it.err = err
|
||||
close(it.failed)
|
||||
}
|
||||
it.mu.Unlock()
|
||||
}
|
||||
|
||||
// receiver runs in a goroutine and handles all receives from the stream.
|
||||
func (it *streamingMessageIterator) receiver() {
|
||||
defer it.wg.Done()
|
||||
defer close(it.msgc)
|
||||
|
||||
for {
|
||||
// Stop retrieving messages if the context is done, the stream
|
||||
// failed, or the iterator's Stop method was called.
|
||||
select {
|
||||
case <-it.ctx.Done():
|
||||
return
|
||||
case <-it.failed:
|
||||
return
|
||||
case <-it.stopped:
|
||||
return
|
||||
default:
|
||||
}
|
||||
// Receive messages from stream. This may block indefinitely.
|
||||
msgs, err := it.sp.fetchMessages()
|
||||
|
||||
// The streamingPuller handles retries, so any error here
|
||||
// is fatal to the iterator.
|
||||
if err != nil {
|
||||
it.fail(err)
|
||||
return
|
||||
}
|
||||
// We received some messages. Remember them so we can
|
||||
// keep them alive.
|
||||
deadline := time.Now().Add(it.po.maxExtension)
|
||||
it.mu.Lock()
|
||||
for _, m := range msgs {
|
||||
it.keepAliveDeadlines[m.ackID] = deadline
|
||||
}
|
||||
it.mu.Unlock()
|
||||
// Deliver the messages to the channel.
|
||||
for _, m := range msgs {
|
||||
select {
|
||||
case <-it.ctx.Done():
|
||||
return
|
||||
case <-it.failed:
|
||||
return
|
||||
// Don't return if stopped. We want to send the remaining
|
||||
// messages on the channel, where they will be nacked.
|
||||
case it.msgc <- m:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sender runs in a goroutine and handles all sends to the stream.
|
||||
func (it *streamingMessageIterator) sender() {
|
||||
defer it.wg.Done()
|
||||
defer it.kaTicker.Stop()
|
||||
defer it.ackTicker.Stop()
|
||||
defer it.nackTicker.Stop()
|
||||
defer it.sp.closeSend()
|
||||
|
||||
done := false
|
||||
for !done {
|
||||
send := false
|
||||
select {
|
||||
case <-it.ctx.Done():
|
||||
// Context canceled or timed out: stop immediately, without
|
||||
// another RPC.
|
||||
return
|
||||
|
||||
case <-it.failed:
|
||||
// Stream failed: nothing to do, so stop immediately.
|
||||
return
|
||||
|
||||
case <-it.drained:
|
||||
// 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)
|
||||
done = true
|
||||
|
||||
case <-it.kaTicker.C:
|
||||
it.mu.Lock()
|
||||
send = it.handleKeepAlives()
|
||||
|
||||
case <-it.nackTicker.C:
|
||||
it.mu.Lock()
|
||||
send = (len(it.pendingReq.ModifyDeadlineAckIds) > 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{}
|
||||
it.mu.Unlock()
|
||||
err := it.sp.send(req)
|
||||
if err != nil {
|
||||
// The streamingPuller handles retries, so any error here
|
||||
// is fatal to the iterator.
|
||||
it.fail(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
it.mu.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
dl := trunc32(int64(it.po.ackDeadline.Seconds()))
|
||||
for _, m := range live {
|
||||
it.addDeadlineMod(m, dl)
|
||||
}
|
||||
it.checkDrained()
|
||||
return len(live) > 0
|
||||
}
|
324
vendor/cloud.google.com/go/pubsub/iterator_test.go
generated
vendored
Normal file
324
vendor/cloud.google.com/go/pubsub/iterator_test.go
generated
vendored
Normal file
|
@ -0,0 +1,324 @@
|
|||
// 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 (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"google.golang.org/api/iterator"
|
||||
)
|
||||
|
||||
func TestReturnsDoneOnStop(t *testing.T) {
|
||||
type testCase struct {
|
||||
abort func(*MessageIterator, context.CancelFunc)
|
||||
want error
|
||||
}
|
||||
|
||||
for _, tc := range []testCase{
|
||||
{
|
||||
abort: func(it *MessageIterator, cancel context.CancelFunc) {
|
||||
it.Stop()
|
||||
},
|
||||
want: iterator.Done,
|
||||
},
|
||||
{
|
||||
abort: func(it *MessageIterator, cancel context.CancelFunc) {
|
||||
cancel()
|
||||
},
|
||||
want: context.Canceled,
|
||||
},
|
||||
{
|
||||
abort: func(it *MessageIterator, cancel context.CancelFunc) {
|
||||
it.Stop()
|
||||
cancel()
|
||||
},
|
||||
want: iterator.Done,
|
||||
},
|
||||
{
|
||||
abort: func(it *MessageIterator, cancel context.CancelFunc) {
|
||||
cancel()
|
||||
it.Stop()
|
||||
},
|
||||
want: iterator.Done,
|
||||
},
|
||||
} {
|
||||
s := &blockingFetch{}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
it := newMessageIterator(ctx, s, "subname", &pullOptions{ackDeadline: time.Second * 10, maxExtension: time.Hour})
|
||||
defer it.Stop()
|
||||
tc.abort(it, cancel)
|
||||
|
||||
_, err := it.Next()
|
||||
if err != tc.want {
|
||||
t.Errorf("iterator Next error after abort: got:\n%v\nwant:\n%v", err, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// blockingFetch implements message fetching by not returning until its context is cancelled.
|
||||
type blockingFetch struct {
|
||||
service
|
||||
}
|
||||
|
||||
func (s *blockingFetch) fetchMessages(ctx context.Context, subName string, maxMessages int32) ([]*Message, error) {
|
||||
<-ctx.Done()
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
|
||||
func (s *blockingFetch) newStreamingPuller(ctx context.Context, subName string, ackDeadline int32) *streamingPuller {
|
||||
return nil
|
||||
}
|
||||
|
||||
// justInTimeFetch simulates the situation where the iterator is aborted just after the fetch RPC
|
||||
// succeeds, so the rest of puller.Next will continue to execute and return sucessfully.
|
||||
type justInTimeFetch struct {
|
||||
service
|
||||
}
|
||||
|
||||
func (s *justInTimeFetch) fetchMessages(ctx context.Context, subName string, maxMessages int32) ([]*Message, error) {
|
||||
<-ctx.Done()
|
||||
// The context was cancelled, but let's pretend that this happend just after our RPC returned.
|
||||
|
||||
var result []*Message
|
||||
for i := 0; i < int(maxMessages); i++ {
|
||||
val := fmt.Sprintf("msg%v", i)
|
||||
result = append(result, &Message{Data: []byte(val), ackID: val})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *justInTimeFetch) splitAckIDs(ids []string) ([]string, []string) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *justInTimeFetch) modifyAckDeadline(ctx context.Context, subName string, deadline time.Duration, ackIDs []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *justInTimeFetch) newStreamingPuller(ctx context.Context, subName string, ackDeadline int32) *streamingPuller {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestAfterAbortReturnsNoMoreThanOneMessage(t *testing.T) {
|
||||
// Each test case is excercised by making two concurrent blocking calls on a
|
||||
// MessageIterator, and then aborting the iterator.
|
||||
// The result should be one call to Next returning a message, and the other returning an error.
|
||||
type testCase struct {
|
||||
abort func(*MessageIterator, context.CancelFunc)
|
||||
// want is the error that should be returned from one Next invocation.
|
||||
want error
|
||||
}
|
||||
for n := 1; n < 3; n++ {
|
||||
for _, tc := range []testCase{
|
||||
{
|
||||
abort: func(it *MessageIterator, cancel context.CancelFunc) {
|
||||
it.Stop()
|
||||
},
|
||||
want: iterator.Done,
|
||||
},
|
||||
{
|
||||
abort: func(it *MessageIterator, cancel context.CancelFunc) {
|
||||
cancel()
|
||||
},
|
||||
want: context.Canceled,
|
||||
},
|
||||
{
|
||||
abort: func(it *MessageIterator, cancel context.CancelFunc) {
|
||||
it.Stop()
|
||||
cancel()
|
||||
},
|
||||
want: iterator.Done,
|
||||
},
|
||||
{
|
||||
abort: func(it *MessageIterator, cancel context.CancelFunc) {
|
||||
cancel()
|
||||
it.Stop()
|
||||
},
|
||||
want: iterator.Done,
|
||||
},
|
||||
} {
|
||||
s := &justInTimeFetch{}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
// if maxPrefetch == 1, there will be no messages in the puller buffer when Next is invoked the second time.
|
||||
// if maxPrefetch == 2, there will be 1 message in the puller buffer when Next is invoked the second time.
|
||||
po := &pullOptions{
|
||||
ackDeadline: time.Second * 10,
|
||||
maxExtension: time.Hour,
|
||||
maxPrefetch: int32(n),
|
||||
}
|
||||
it := newMessageIterator(ctx, s, "subname", po)
|
||||
defer it.Stop()
|
||||
|
||||
type result struct {
|
||||
m *Message
|
||||
err error
|
||||
}
|
||||
results := make(chan *result, 2)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
go func() {
|
||||
m, err := it.Next()
|
||||
results <- &result{m, err}
|
||||
if err == nil {
|
||||
m.Done(false)
|
||||
}
|
||||
}()
|
||||
}
|
||||
// Wait for goroutines to block on it.Next().
|
||||
time.Sleep(time.Millisecond)
|
||||
tc.abort(it, cancel)
|
||||
|
||||
result1 := <-results
|
||||
result2 := <-results
|
||||
|
||||
// There should be one error result, and one non-error result.
|
||||
// Make result1 be the non-error result.
|
||||
if result1.err != nil {
|
||||
result1, result2 = result2, result1
|
||||
}
|
||||
|
||||
if string(result1.m.Data) != "msg0" {
|
||||
t.Errorf("After abort, got message: %v, want %v", result1.m.Data, "msg0")
|
||||
}
|
||||
if result1.err != nil {
|
||||
t.Errorf("After abort, got : %v, want nil", result1.err)
|
||||
}
|
||||
if result2.m != nil {
|
||||
t.Errorf("After abort, got message: %v, want nil", result2.m)
|
||||
}
|
||||
if result2.err != tc.want {
|
||||
t.Errorf("After abort, got err: %v, want %v", result2.err, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type fetcherServiceWithModifyAckDeadline struct {
|
||||
fetcherService
|
||||
events chan string
|
||||
}
|
||||
|
||||
func (f *fetcherServiceWithModifyAckDeadline) modifyAckDeadline(_ context.Context, _ string, d time.Duration, ids []string) error {
|
||||
// Different versions of Go use different representations for time.Duration(0).
|
||||
var ds string
|
||||
if d == 0 {
|
||||
ds = "0s"
|
||||
} else {
|
||||
ds = d.String()
|
||||
}
|
||||
f.events <- fmt.Sprintf("modAck(%v, %s)", ids, ds)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fetcherServiceWithModifyAckDeadline) splitAckIDs(ackIDs []string) ([]string, []string) {
|
||||
return ackIDs, nil
|
||||
}
|
||||
|
||||
func (f *fetcherServiceWithModifyAckDeadline) newStreamingPuller(ctx context.Context, subName string, ackDeadline int32) *streamingPuller {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestMultipleStopCallsBlockUntilMessageDone(t *testing.T) {
|
||||
events := make(chan string, 3)
|
||||
s := &fetcherServiceWithModifyAckDeadline{
|
||||
fetcherService{
|
||||
results: []fetchResult{
|
||||
{
|
||||
msgs: []*Message{{ackID: "a"}, {ackID: "b"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
events,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
it := newMessageIterator(ctx, s, "subname", &pullOptions{ackDeadline: time.Second * 10, maxExtension: 0})
|
||||
|
||||
m, err := it.Next()
|
||||
if err != nil {
|
||||
t.Errorf("error calling Next: %v", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
it.Stop()
|
||||
events <- "stopped"
|
||||
}()
|
||||
go func() {
|
||||
it.Stop()
|
||||
events <- "stopped"
|
||||
}()
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
m.Done(false)
|
||||
|
||||
got := []string{<-events, <-events, <-events}
|
||||
want := []string{"modAck([a], 0s)", "stopped", "stopped"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("stopping iterator, got: %v ; want: %v", got, want)
|
||||
}
|
||||
|
||||
// The iterator is stopped, so should not return another message.
|
||||
m, err = it.Next()
|
||||
if m != nil {
|
||||
t.Errorf("message got: %v ; want: nil", m)
|
||||
}
|
||||
if err != iterator.Done {
|
||||
t.Errorf("err got: %v ; want: %v", err, iterator.Done)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFastNack(t *testing.T) {
|
||||
events := make(chan string, 3)
|
||||
s := &fetcherServiceWithModifyAckDeadline{
|
||||
fetcherService{
|
||||
results: []fetchResult{
|
||||
{
|
||||
msgs: []*Message{{ackID: "a"}, {ackID: "b"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
events,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
it := newMessageIterator(ctx, s, "subname", &pullOptions{
|
||||
ackDeadline: time.Second * 6,
|
||||
maxExtension: time.Second * 10,
|
||||
})
|
||||
// Get both messages.
|
||||
_, err := it.Next()
|
||||
if err != nil {
|
||||
t.Errorf("error calling Next: %v", err)
|
||||
}
|
||||
m2, err := it.Next()
|
||||
if err != nil {
|
||||
t.Errorf("error calling Next: %v", err)
|
||||
}
|
||||
// Ignore the first, nack the second.
|
||||
m2.Done(false)
|
||||
|
||||
got := []string{<-events, <-events}
|
||||
// The nack should happen before the deadline extension.
|
||||
want := []string{"modAck([b], 0s)", "modAck([a], 6s)"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got: %v ; want: %v", got, want)
|
||||
}
|
||||
}
|
182
vendor/cloud.google.com/go/pubsub/keepalive.go
generated
vendored
Normal file
182
vendor/cloud.google.com/go/pubsub/keepalive.go
generated
vendored
Normal file
|
@ -0,0 +1,182 @@
|
|||
// 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 (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// keepAlive keeps track of which Messages need to have their deadline extended, and
|
||||
// periodically extends them.
|
||||
// Messages are tracked by Ack ID.
|
||||
type keepAlive struct {
|
||||
s service
|
||||
Ctx context.Context // The context to use when extending deadlines.
|
||||
Sub string // The full name of the subscription.
|
||||
ExtensionTick <-chan time.Time // ExtensionTick supplies the frequency with which to make extension requests.
|
||||
Deadline time.Duration // How long to extend messages for each time they are extended. Should be greater than ExtensionTick frequency.
|
||||
MaxExtension time.Duration // How long to keep extending each message's ack deadline before automatically removing it.
|
||||
|
||||
mu sync.Mutex
|
||||
// key: ackID; value: time at which ack deadline extension should cease.
|
||||
items map[string]time.Time
|
||||
dr drain
|
||||
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// Start initiates the deadline extension loop. Stop must be called once keepAlive is no longer needed.
|
||||
func (ka *keepAlive) Start() {
|
||||
ka.items = make(map[string]time.Time)
|
||||
ka.dr = drain{Drained: make(chan struct{})}
|
||||
ka.wg.Add(1)
|
||||
go func() {
|
||||
defer ka.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-ka.Ctx.Done():
|
||||
// Don't bother waiting for items to be removed: we can't extend them any more.
|
||||
return
|
||||
case <-ka.dr.Drained:
|
||||
return
|
||||
case <-ka.ExtensionTick:
|
||||
live, expired := ka.getAckIDs()
|
||||
ka.wg.Add(1)
|
||||
go func() {
|
||||
defer ka.wg.Done()
|
||||
ka.extendDeadlines(live)
|
||||
}()
|
||||
|
||||
for _, id := range expired {
|
||||
ka.Remove(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Add adds an ack id to be kept alive.
|
||||
// It should not be called after Stop.
|
||||
func (ka *keepAlive) Add(ackID string) {
|
||||
ka.mu.Lock()
|
||||
defer ka.mu.Unlock()
|
||||
|
||||
ka.items[ackID] = time.Now().Add(ka.MaxExtension)
|
||||
ka.dr.SetPending(true)
|
||||
}
|
||||
|
||||
// Remove removes ackID from the list to be kept alive.
|
||||
func (ka *keepAlive) Remove(ackID string) {
|
||||
ka.mu.Lock()
|
||||
defer ka.mu.Unlock()
|
||||
|
||||
// Note: If users NACKs a message after it has been removed due to
|
||||
// expiring, Remove will be called twice with same ack id. This is OK.
|
||||
delete(ka.items, ackID)
|
||||
ka.dr.SetPending(len(ka.items) != 0)
|
||||
}
|
||||
|
||||
// Stop waits until all added ackIDs have been removed, and cleans up resources.
|
||||
// Stop may only be called once.
|
||||
func (ka *keepAlive) Stop() {
|
||||
ka.mu.Lock()
|
||||
ka.dr.Drain()
|
||||
ka.mu.Unlock()
|
||||
|
||||
ka.wg.Wait()
|
||||
}
|
||||
|
||||
// getAckIDs returns the set of ackIDs that are being kept alive.
|
||||
// The set is divided into two lists: one with IDs that should continue to be kept alive,
|
||||
// and the other with IDs that should be dropped.
|
||||
func (ka *keepAlive) getAckIDs() (live, expired []string) {
|
||||
ka.mu.Lock()
|
||||
defer ka.mu.Unlock()
|
||||
return getKeepAliveAckIDs(ka.items)
|
||||
}
|
||||
|
||||
func getKeepAliveAckIDs(items map[string]time.Time) (live, expired []string) {
|
||||
now := time.Now()
|
||||
for id, expiry := range items {
|
||||
if expiry.Before(now) {
|
||||
expired = append(expired, id)
|
||||
} else {
|
||||
live = append(live, id)
|
||||
}
|
||||
}
|
||||
return live, expired
|
||||
}
|
||||
|
||||
const maxExtensionAttempts = 2
|
||||
|
||||
func (ka *keepAlive) extendDeadlines(ackIDs []string) {
|
||||
head, tail := ka.s.splitAckIDs(ackIDs)
|
||||
for len(head) > 0 {
|
||||
for i := 0; i < maxExtensionAttempts; i++ {
|
||||
if ka.s.modifyAckDeadline(ka.Ctx, ka.Sub, ka.Deadline, head) == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
// NOTE: Messages whose deadlines we fail to extend will
|
||||
// eventually be redelivered and this is a documented behaviour
|
||||
// of the API.
|
||||
//
|
||||
// NOTE: If we fail to extend deadlines here, this
|
||||
// implementation will continue to attempt extending the
|
||||
// deadlines for those ack IDs the next time the extension
|
||||
// ticker ticks. By then the deadline will have expired.
|
||||
// Re-extending them is harmless, however.
|
||||
//
|
||||
// TODO: call Remove for ids which fail to be extended.
|
||||
|
||||
head, tail = ka.s.splitAckIDs(tail)
|
||||
}
|
||||
}
|
||||
|
||||
// A drain (once started) indicates via a channel when there is no work pending.
|
||||
type drain struct {
|
||||
started bool
|
||||
pending bool
|
||||
|
||||
// Drained is closed once there are no items outstanding if Drain has been called.
|
||||
Drained chan struct{}
|
||||
}
|
||||
|
||||
// Drain starts the drain process. This cannot be undone.
|
||||
func (d *drain) Drain() {
|
||||
d.started = true
|
||||
d.closeIfDrained()
|
||||
}
|
||||
|
||||
// SetPending sets whether there is work pending or not. It may be called multiple times before or after Drain.
|
||||
func (d *drain) SetPending(pending bool) {
|
||||
d.pending = pending
|
||||
d.closeIfDrained()
|
||||
}
|
||||
|
||||
func (d *drain) closeIfDrained() {
|
||||
if !d.pending && d.started {
|
||||
// Check to see if d.Drained is closed before closing it.
|
||||
// This allows SetPending(false) to be safely called multiple times.
|
||||
select {
|
||||
case <-d.Drained:
|
||||
default:
|
||||
close(d.Drained)
|
||||
}
|
||||
}
|
||||
}
|
319
vendor/cloud.google.com/go/pubsub/keepalive_test.go
generated
vendored
Normal file
319
vendor/cloud.google.com/go/pubsub/keepalive_test.go
generated
vendored
Normal file
|
@ -0,0 +1,319 @@
|
|||
// 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 (
|
||||
"errors"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestKeepAliveExtendsDeadline(t *testing.T) {
|
||||
ticker := make(chan time.Time)
|
||||
deadline := time.Nanosecond * 15
|
||||
s := &testService{modDeadlineCalled: make(chan modDeadlineCall)}
|
||||
|
||||
checkModDeadlineCall := func(ackIDs []string) {
|
||||
got := <-s.modDeadlineCalled
|
||||
sort.Strings(got.ackIDs)
|
||||
|
||||
want := modDeadlineCall{
|
||||
subName: "subname",
|
||||
deadline: deadline,
|
||||
ackIDs: ackIDs,
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("keepalive: got:\n%v\nwant:\n%v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
ka := &keepAlive{
|
||||
s: s,
|
||||
Ctx: context.Background(),
|
||||
Sub: "subname",
|
||||
ExtensionTick: ticker,
|
||||
Deadline: deadline,
|
||||
MaxExtension: time.Hour,
|
||||
}
|
||||
ka.Start()
|
||||
|
||||
ka.Add("a")
|
||||
ka.Add("b")
|
||||
ticker <- time.Time{}
|
||||
checkModDeadlineCall([]string{"a", "b"})
|
||||
ka.Add("c")
|
||||
ka.Remove("b")
|
||||
ticker <- time.Time{}
|
||||
checkModDeadlineCall([]string{"a", "c"})
|
||||
ka.Remove("a")
|
||||
ka.Remove("c")
|
||||
ka.Add("d")
|
||||
ticker <- time.Time{}
|
||||
checkModDeadlineCall([]string{"d"})
|
||||
|
||||
ka.Remove("d")
|
||||
ka.Stop()
|
||||
}
|
||||
|
||||
func TestKeepAliveStopsWhenNoItem(t *testing.T) {
|
||||
ticker := make(chan time.Time)
|
||||
stopped := make(chan bool)
|
||||
s := &testService{modDeadlineCalled: make(chan modDeadlineCall, 3)}
|
||||
ka := &keepAlive{
|
||||
s: s,
|
||||
Ctx: context.Background(),
|
||||
ExtensionTick: ticker,
|
||||
}
|
||||
|
||||
ka.Start()
|
||||
|
||||
// There should be no call to modifyAckDeadline since there is no item.
|
||||
ticker <- time.Time{}
|
||||
|
||||
go func() {
|
||||
ka.Stop() // No items; should not block
|
||||
if len(s.modDeadlineCalled) > 0 {
|
||||
t.Errorf("unexpected extension to non-existent items: %v", <-s.modDeadlineCalled)
|
||||
}
|
||||
close(stopped)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-stopped:
|
||||
case <-time.After(time.Second):
|
||||
t.Errorf("keepAlive timed out waiting for stop")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeepAliveStopsWhenItemsExpired(t *testing.T) {
|
||||
ticker := make(chan time.Time)
|
||||
stopped := make(chan bool)
|
||||
s := &testService{modDeadlineCalled: make(chan modDeadlineCall, 2)}
|
||||
ka := &keepAlive{
|
||||
s: s,
|
||||
Ctx: context.Background(),
|
||||
ExtensionTick: ticker,
|
||||
MaxExtension: time.Duration(0), // Should expire items at the first tick.
|
||||
}
|
||||
|
||||
ka.Start()
|
||||
ka.Add("a")
|
||||
ka.Add("b")
|
||||
|
||||
// Wait until the clock advances. Without this loop, this test fails on
|
||||
// Windows because the clock doesn't advance at all between ka.Add and the
|
||||
// expiration check after the tick is received.
|
||||
begin := time.Now()
|
||||
for time.Now().Equal(begin) {
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
|
||||
// There should be no call to modifyAckDeadline since both items are expired.
|
||||
ticker <- time.Time{}
|
||||
|
||||
go func() {
|
||||
ka.Stop() // No live items; should not block.
|
||||
if len(s.modDeadlineCalled) > 0 {
|
||||
t.Errorf("unexpected extension to expired items")
|
||||
}
|
||||
close(stopped)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-stopped:
|
||||
case <-time.After(time.Second):
|
||||
t.Errorf("timed out waiting for stop")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeepAliveBlocksUntilAllItemsRemoved(t *testing.T) {
|
||||
ticker := make(chan time.Time)
|
||||
eventc := make(chan string, 3)
|
||||
s := &testService{modDeadlineCalled: make(chan modDeadlineCall)}
|
||||
ka := &keepAlive{
|
||||
s: s,
|
||||
Ctx: context.Background(),
|
||||
ExtensionTick: ticker,
|
||||
MaxExtension: time.Hour, // Should not expire.
|
||||
}
|
||||
|
||||
ka.Start()
|
||||
ka.Add("a")
|
||||
ka.Add("b")
|
||||
|
||||
go func() {
|
||||
ticker <- time.Time{}
|
||||
|
||||
// We expect a call since both items should be extended.
|
||||
select {
|
||||
case args := <-s.modDeadlineCalled:
|
||||
sort.Strings(args.ackIDs)
|
||||
got := args.ackIDs
|
||||
want := []string{"a", "b"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("mismatching IDs:\ngot %v\nwant %v", got, want)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Errorf("timed out waiting for deadline extend call")
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
eventc <- "pre-remove-b"
|
||||
// Remove one item, Stop should still be waiting.
|
||||
ka.Remove("b")
|
||||
|
||||
ticker <- time.Time{}
|
||||
|
||||
// We expect a call since the item is still alive.
|
||||
select {
|
||||
case args := <-s.modDeadlineCalled:
|
||||
got := args.ackIDs
|
||||
want := []string{"a"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("mismatching IDs:\ngot %v\nwant %v", got, want)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Errorf("timed out waiting for deadline extend call")
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
eventc <- "pre-remove-a"
|
||||
// Remove the last item so that Stop can proceed.
|
||||
ka.Remove("a")
|
||||
}()
|
||||
|
||||
go func() {
|
||||
ka.Stop() // Should block all item are removed.
|
||||
eventc <- "post-stop"
|
||||
}()
|
||||
|
||||
for i, want := range []string{"pre-remove-b", "pre-remove-a", "post-stop"} {
|
||||
select {
|
||||
case got := <-eventc:
|
||||
if got != want {
|
||||
t.Errorf("event #%d:\ngot %v\nwant %v", i, got, want)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Errorf("time out waiting for #%d event: want %v", i, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// extendCallResult contains a list of ackIDs which are expected in an ackID
|
||||
// extension request, along with the result that should be returned.
|
||||
type extendCallResult struct {
|
||||
ackIDs []string
|
||||
err error
|
||||
}
|
||||
|
||||
// extendService implements modifyAckDeadline using a hard-coded list of extendCallResults.
|
||||
type extendService struct {
|
||||
service
|
||||
|
||||
calls []extendCallResult
|
||||
|
||||
t *testing.T // used for error logging.
|
||||
}
|
||||
|
||||
func (es *extendService) modifyAckDeadline(ctx context.Context, subName string, deadline time.Duration, ackIDs []string) error {
|
||||
if len(es.calls) == 0 {
|
||||
es.t.Fatalf("unexpected call to modifyAckDeadline: ackIDs: %v", ackIDs)
|
||||
}
|
||||
call := es.calls[0]
|
||||
es.calls = es.calls[1:]
|
||||
|
||||
if got, want := ackIDs, call.ackIDs; !reflect.DeepEqual(got, want) {
|
||||
es.t.Errorf("unexpected arguments to modifyAckDeadline: got: %v ; want: %v", got, want)
|
||||
}
|
||||
return call.err
|
||||
}
|
||||
|
||||
// Test implementation returns the first 2 elements as head, and the rest as tail.
|
||||
func (es *extendService) splitAckIDs(ids []string) ([]string, []string) {
|
||||
if len(ids) < 2 {
|
||||
return ids, nil
|
||||
}
|
||||
return ids[:2], ids[2:]
|
||||
}
|
||||
|
||||
func TestKeepAliveSplitsBatches(t *testing.T) {
|
||||
type testCase struct {
|
||||
calls []extendCallResult
|
||||
}
|
||||
for _, tc := range []testCase{
|
||||
{
|
||||
calls: []extendCallResult{
|
||||
{
|
||||
ackIDs: []string{"a", "b"},
|
||||
},
|
||||
{
|
||||
ackIDs: []string{"c", "d"},
|
||||
},
|
||||
{
|
||||
ackIDs: []string{"e", "f"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
calls: []extendCallResult{
|
||||
{
|
||||
ackIDs: []string{"a", "b"},
|
||||
err: errors.New("bang"),
|
||||
},
|
||||
// On error we retry once.
|
||||
{
|
||||
ackIDs: []string{"a", "b"},
|
||||
err: errors.New("bang"),
|
||||
},
|
||||
// We give up after failing twice, so we move on to the next set, "c" and "d".
|
||||
{
|
||||
ackIDs: []string{"c", "d"},
|
||||
err: errors.New("bang"),
|
||||
},
|
||||
// Again, we retry once.
|
||||
{
|
||||
ackIDs: []string{"c", "d"},
|
||||
},
|
||||
{
|
||||
ackIDs: []string{"e", "f"},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
s := &extendService{
|
||||
t: t,
|
||||
calls: tc.calls,
|
||||
}
|
||||
|
||||
ka := &keepAlive{
|
||||
s: s,
|
||||
Ctx: context.Background(),
|
||||
Sub: "subname",
|
||||
}
|
||||
|
||||
ka.extendDeadlines([]string{"a", "b", "c", "d", "e", "f"})
|
||||
|
||||
if len(s.calls) != 0 {
|
||||
t.Errorf("expected extend calls did not occur: %v", s.calls)
|
||||
}
|
||||
}
|
||||
}
|
84
vendor/cloud.google.com/go/pubsub/message.go
generated
vendored
Normal file
84
vendor/cloud.google.com/go/pubsub/message.go
generated
vendored
Normal file
|
@ -0,0 +1,84 @@
|
|||
// 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"
|
||||
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
pb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
)
|
||||
|
||||
// Message represents a Pub/Sub message.
|
||||
type Message struct {
|
||||
// ID identifies this message.
|
||||
// This ID is assigned by the server and is populated for Messages obtained from a subscription.
|
||||
// This field is read-only.
|
||||
ID string
|
||||
|
||||
// Data is the actual data in the message.
|
||||
Data []byte
|
||||
|
||||
// Attributes represents the key-value pairs the current message
|
||||
// is labelled with.
|
||||
Attributes map[string]string
|
||||
|
||||
// ackID is the identifier to acknowledge this message.
|
||||
ackID string
|
||||
|
||||
// The time at which the message was published.
|
||||
// This is populated by the server for Messages obtained from a subscription.
|
||||
// This field is read-only.
|
||||
PublishTime time.Time
|
||||
|
||||
calledDone bool
|
||||
|
||||
// The done method of the iterator that created this Message.
|
||||
done func(string, bool)
|
||||
}
|
||||
|
||||
func toMessage(resp *pb.ReceivedMessage) (*Message, error) {
|
||||
if resp.Message == nil {
|
||||
return &Message{ackID: resp.AckId}, nil
|
||||
}
|
||||
|
||||
pubTime, err := ptypes.Timestamp(resp.Message.PublishTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Message{
|
||||
ackID: resp.AckId,
|
||||
Data: resp.Message.Data,
|
||||
Attributes: resp.Message.Attributes,
|
||||
ID: resp.Message.MessageId,
|
||||
PublishTime: pubTime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Done completes the processing of a Message that was returned from a MessageIterator.
|
||||
// ack indicates whether the message should be acknowledged.
|
||||
// Client code must call Done when finished for each Message returned by an iterator.
|
||||
// Done may only be called on Messages returned by a MessageIterator.
|
||||
// If message acknowledgement fails, the Message will be redelivered.
|
||||
// Calls to Done have no effect after the first call.
|
||||
//
|
||||
// See MessageIterator.Next for an example.
|
||||
func (m *Message) Done(ack bool) {
|
||||
if m.calledDone {
|
||||
return
|
||||
}
|
||||
m.calledDone = true
|
||||
m.done(m.ackID, ack)
|
||||
}
|
136
vendor/cloud.google.com/go/pubsub/pubsub.go
generated
vendored
Normal file
136
vendor/cloud.google.com/go/pubsub/pubsub.go
generated
vendored
Normal file
|
@ -0,0 +1,136 @@
|
|||
// Copyright 2014 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 "cloud.google.com/go/pubsub"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
// ScopePubSub grants permissions to view and manage Pub/Sub
|
||||
// topics and subscriptions.
|
||||
ScopePubSub = "https://www.googleapis.com/auth/pubsub"
|
||||
|
||||
// ScopeCloudPlatform grants permissions to view and manage your data
|
||||
// across Google Cloud Platform services.
|
||||
ScopeCloudPlatform = "https://www.googleapis.com/auth/cloud-platform"
|
||||
)
|
||||
|
||||
const prodAddr = "https://pubsub.googleapis.com/"
|
||||
const userAgent = "gcloud-golang-pubsub/20160927"
|
||||
|
||||
// Client is a Google Pub/Sub client scoped to a single project.
|
||||
//
|
||||
// Clients should be reused rather than being created as needed.
|
||||
// A Client may be shared by multiple goroutines.
|
||||
type Client struct {
|
||||
projectID string
|
||||
s service
|
||||
}
|
||||
|
||||
// NewClient creates a new PubSub client.
|
||||
func NewClient(ctx context.Context, projectID string, opts ...option.ClientOption) (*Client, error) {
|
||||
var o []option.ClientOption
|
||||
// Environment variables for gcloud emulator:
|
||||
// https://cloud.google.com/sdk/gcloud/reference/beta/emulators/pubsub/
|
||||
if addr := os.Getenv("PUBSUB_EMULATOR_HOST"); addr != "" {
|
||||
conn, err := grpc.Dial(addr, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("grpc.Dial: %v", err)
|
||||
}
|
||||
o = []option.ClientOption{option.WithGRPCConn(conn)}
|
||||
} else {
|
||||
o = []option.ClientOption{option.WithUserAgent(userAgent)}
|
||||
}
|
||||
o = append(o, opts...)
|
||||
s, err := newPubSubService(ctx, o)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("constructing pubsub client: %v", err)
|
||||
}
|
||||
|
||||
c := &Client{
|
||||
projectID: projectID,
|
||||
s: s,
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Close closes any resources held by the client.
|
||||
//
|
||||
// Close need not be called at program exit.
|
||||
func (c *Client) Close() error {
|
||||
return c.s.close()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
115
vendor/cloud.google.com/go/pubsub/puller.go
generated
vendored
Normal file
115
vendor/cloud.google.com/go/pubsub/puller.go
generated
vendored
Normal file
|
@ -0,0 +1,115 @@
|
|||
// 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 (
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// puller fetches messages from the server in a batch.
|
||||
type puller struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
// keepAlive takes ownership of the lifetime of the message identified
|
||||
// by ackID, ensuring that its ack deadline does not expire. It should
|
||||
// be called each time a new message is fetched from the server, even
|
||||
// if it is not yet returned from Next.
|
||||
keepAlive func(ackID string)
|
||||
|
||||
// abandon should be called for each message which has previously been
|
||||
// passed to keepAlive, but will never be returned by Next.
|
||||
abandon func(ackID string)
|
||||
|
||||
// fetch fetches a batch of messages from the server.
|
||||
fetch func() ([]*Message, error)
|
||||
|
||||
mu sync.Mutex
|
||||
buf []*Message
|
||||
}
|
||||
|
||||
// newPuller constructs a new puller.
|
||||
// batchSize is the maximum number of messages to fetch at once.
|
||||
// No more than batchSize messages will be outstanding at any time.
|
||||
func newPuller(s service, subName string, ctx context.Context, batchSize int32, keepAlive, abandon func(ackID string)) *puller {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
return &puller{
|
||||
cancel: cancel,
|
||||
keepAlive: keepAlive,
|
||||
abandon: abandon,
|
||||
ctx: ctx,
|
||||
fetch: func() ([]*Message, error) { return s.fetchMessages(ctx, subName, batchSize) },
|
||||
}
|
||||
}
|
||||
|
||||
const maxPullAttempts = 2
|
||||
|
||||
// Next returns the next message from the server, fetching a new batch if necessary.
|
||||
// keepAlive is called with the ackIDs of newly fetched messages.
|
||||
// If p.Ctx has already been cancelled before Next is called, no new messages
|
||||
// will be fetched.
|
||||
func (p *puller) Next() (*Message, error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
// If ctx has been cancelled, return straight away (even if there are buffered messages available).
|
||||
select {
|
||||
case <-p.ctx.Done():
|
||||
return nil, p.ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
for len(p.buf) == 0 {
|
||||
var buf []*Message
|
||||
var err error
|
||||
|
||||
for i := 0; i < maxPullAttempts; i++ {
|
||||
// Once Stop has completed, all future calls to Next will immediately fail at this point.
|
||||
buf, err = p.fetch()
|
||||
if err == nil || err == context.Canceled || err == context.DeadlineExceeded {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, m := range buf {
|
||||
p.keepAlive(m.ackID)
|
||||
}
|
||||
p.buf = buf
|
||||
}
|
||||
|
||||
m := p.buf[0]
|
||||
p.buf = p.buf[1:]
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Stop aborts any pending calls to Next, and prevents any future ones from succeeding.
|
||||
// Stop also abandons any messages that have been pre-fetched.
|
||||
// Once Stop completes, no calls to Next will succeed.
|
||||
func (p *puller) Stop() {
|
||||
// Next may be executing in another goroutine. Cancel it, and then wait until it terminates.
|
||||
p.cancel()
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
for _, m := range p.buf {
|
||||
p.abandon(m.ackID)
|
||||
}
|
||||
p.buf = nil
|
||||
}
|
154
vendor/cloud.google.com/go/pubsub/puller_test.go
generated
vendored
Normal file
154
vendor/cloud.google.com/go/pubsub/puller_test.go
generated
vendored
Normal file
|
@ -0,0 +1,154 @@
|
|||
// 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 (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type fetchResult struct {
|
||||
msgs []*Message
|
||||
err error
|
||||
}
|
||||
|
||||
type fetcherService struct {
|
||||
service
|
||||
results []fetchResult
|
||||
unexpectedCall bool
|
||||
}
|
||||
|
||||
func (s *fetcherService) fetchMessages(ctx context.Context, subName string, maxMessages int32) ([]*Message, error) {
|
||||
if len(s.results) == 0 {
|
||||
s.unexpectedCall = true
|
||||
return nil, errors.New("bang")
|
||||
}
|
||||
ret := s.results[0]
|
||||
s.results = s.results[1:]
|
||||
return ret.msgs, ret.err
|
||||
}
|
||||
|
||||
func TestPuller(t *testing.T) {
|
||||
s := &fetcherService{
|
||||
results: []fetchResult{
|
||||
{
|
||||
msgs: []*Message{{ackID: "a"}, {ackID: "b"}},
|
||||
},
|
||||
{},
|
||||
{
|
||||
msgs: []*Message{{ackID: "c"}, {ackID: "d"}},
|
||||
},
|
||||
{
|
||||
msgs: []*Message{{ackID: "e"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pulled := make(chan string, 10)
|
||||
|
||||
pull := newPuller(s, "subname", context.Background(), 2, func(ackID string) { pulled <- ackID }, func(string) {})
|
||||
|
||||
got := []string{}
|
||||
for i := 0; i < 5; i++ {
|
||||
m, err := pull.Next()
|
||||
got = append(got, m.ackID)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected err from pull.Next: %v", err)
|
||||
}
|
||||
}
|
||||
_, err := pull.Next()
|
||||
if err == nil {
|
||||
t.Errorf("unexpected err from pull.Next: %v", err)
|
||||
}
|
||||
|
||||
want := []string{"a", "b", "c", "d", "e"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("pulled ack ids: got: %v ; want: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullerAddsToKeepAlive(t *testing.T) {
|
||||
s := &fetcherService{
|
||||
results: []fetchResult{
|
||||
{
|
||||
msgs: []*Message{{ackID: "a"}, {ackID: "b"}},
|
||||
},
|
||||
{
|
||||
msgs: []*Message{{ackID: "c"}, {ackID: "d"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pulled := make(chan string, 10)
|
||||
|
||||
pull := newPuller(s, "subname", context.Background(), 2, func(ackID string) { pulled <- ackID }, func(string) {})
|
||||
|
||||
got := []string{}
|
||||
for i := 0; i < 3; i++ {
|
||||
m, err := pull.Next()
|
||||
got = append(got, m.ackID)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected err from pull.Next: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
want := []string{"a", "b", "c"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("pulled ack ids: got: %v ; want: %v", got, want)
|
||||
}
|
||||
|
||||
close(pulled)
|
||||
// We should have seen "d" written to the channel too, even though it hasn't been returned yet.
|
||||
pulledIDs := []string{}
|
||||
for id := range pulled {
|
||||
pulledIDs = append(pulledIDs, id)
|
||||
}
|
||||
|
||||
want = append(want, "d")
|
||||
if !reflect.DeepEqual(pulledIDs, want) {
|
||||
t.Errorf("pulled ack ids: got: %v ; want: %v", pulledIDs, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullerRetriesOnce(t *testing.T) {
|
||||
bang := errors.New("bang")
|
||||
s := &fetcherService{
|
||||
results: []fetchResult{
|
||||
{
|
||||
err: bang,
|
||||
},
|
||||
{
|
||||
err: bang,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pull := newPuller(s, "subname", context.Background(), 2, func(string) {}, func(string) {})
|
||||
|
||||
_, err := pull.Next()
|
||||
if err != bang {
|
||||
t.Errorf("pull.Next err got: %v, want: %v", err, bang)
|
||||
}
|
||||
|
||||
if s.unexpectedCall {
|
||||
t.Errorf("unexpected retry")
|
||||
}
|
||||
if len(s.results) != 0 {
|
||||
t.Errorf("outstanding calls: got: %v, want: 0", len(s.results))
|
||||
}
|
||||
}
|
485
vendor/cloud.google.com/go/pubsub/service.go
generated
vendored
Normal file
485
vendor/cloud.google.com/go/pubsub/service.go
generated
vendored
Normal file
|
@ -0,0 +1,485 @@
|
|||
// 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 (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/iam"
|
||||
"cloud.google.com/go/internal/version"
|
||||
vkit "cloud.google.com/go/pubsub/apiv1"
|
||||
"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"
|
||||
)
|
||||
|
||||
type nextStringFunc func() (string, 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, topicName, subName string, ackDeadline time.Duration, pushConfig *PushConfig) 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
|
||||
|
||||
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, topicName, subName string, ackDeadline time.Duration, pushConfig *PushConfig) error {
|
||||
var rawPushConfig *pb.PushConfig
|
||||
if pushConfig != nil {
|
||||
rawPushConfig = &pb.PushConfig{
|
||||
Attributes: pushConfig.Attributes,
|
||||
PushEndpoint: pushConfig.Endpoint,
|
||||
}
|
||||
}
|
||||
_, err := s.subc.CreateSubscription(ctx, &pb.Subscription{
|
||||
Name: subName,
|
||||
Topic: topicName,
|
||||
PushConfig: rawPushConfig,
|
||||
AckDeadlineSeconds: trunc32(int64(ackDeadline.Seconds())),
|
||||
})
|
||||
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 nil, "", err
|
||||
}
|
||||
sub := &SubscriptionConfig{
|
||||
AckDeadline: time.Second * time.Duration(rawSub.AckDeadlineSeconds),
|
||||
PushConfig: PushConfig{
|
||||
Endpoint: rawSub.PushConfig.PushEndpoint,
|
||||
Attributes: rawSub.PushConfig.Attributes,
|
||||
},
|
||||
}
|
||||
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
|
||||
// subscription name, plus 3 bytes per ID (a tag byte and two size bytes). A
|
||||
// ModifyAckDeadlineRequest has an additional few bytes for the deadline. We
|
||||
// don't know the subscription name here, so we just assume the size exclusive
|
||||
// of ids is 100 bytes.
|
||||
//
|
||||
// With gRPC there is no way for the client to know the server's max message size (it is
|
||||
// configurable on the server). We know from experience that it
|
||||
// it 512K.
|
||||
const (
|
||||
maxPayload = 512 * 1024
|
||||
reqFixedOverhead = 100
|
||||
overheadPerID = 3
|
||||
)
|
||||
|
||||
// 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,
|
||||
})
|
||||
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 {
|
||||
msg, err := toMessage(m)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pubsub: cannot decode the retrieved message at index: %d, message: %+v", i, m)
|
||||
}
|
||||
msgs = append(msgs, msg)
|
||||
}
|
||||
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,
|
||||
})
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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.
|
||||
p.inFlight = true
|
||||
p.c.L.Unlock()
|
||||
spc, err := p.subc.StreamingPull(p.ctx)
|
||||
if err == nil {
|
||||
err = spc.Send(&pb.StreamingPullRequest{
|
||||
Subscription: p.subName,
|
||||
StreamAckDeadlineSeconds: p.ackDeadlineSecs,
|
||||
})
|
||||
}
|
||||
p.c.L.Lock()
|
||||
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()
|
||||
}
|
||||
// TODO(jba): better retry strategy.
|
||||
var err error
|
||||
for i := 0; i < 3; i++ {
|
||||
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 == io.EOF || grpc.Code(err) == codes.Unavailable) {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
p.openLocked()
|
||||
continue
|
||||
}
|
||||
// Not a retry-able error; fail permanently.
|
||||
// TODO(jba): for some errors, should we retry f (the Send or Recv)
|
||||
// but not re-open the stream?
|
||||
p.err = err
|
||||
return err
|
||||
}
|
||||
p.err = fmt.Errorf("retry exceeded; last error was %v", err)
|
||||
return p.err
|
||||
}
|
||||
|
||||
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) closeSend() {
|
||||
p.mu.Lock()
|
||||
p.closed = true
|
||||
p.mu.Unlock()
|
||||
p.spc.CloseSend()
|
||||
}
|
||||
|
||||
// Split req into a prefix that is smaller than maxSize, and a remainder.
|
||||
func splitRequest(req *pb.StreamingPullRequest, maxSize int) (prefix, remainder *pb.StreamingPullRequest) {
|
||||
const int32Bytes = 4
|
||||
|
||||
// Copy all fields before splitting the variable-sized ones.
|
||||
remainder = &pb.StreamingPullRequest{}
|
||||
*remainder = *req
|
||||
// Split message so it isn't too big.
|
||||
size := reqFixedOverhead
|
||||
i := 0
|
||||
for size < maxSize && (i < len(req.AckIds) || i < len(req.ModifyDeadlineAckIds)) {
|
||||
if i < len(req.AckIds) {
|
||||
size += overheadPerID + len(req.AckIds[i])
|
||||
}
|
||||
if i < len(req.ModifyDeadlineAckIds) {
|
||||
size += overheadPerID + len(req.ModifyDeadlineAckIds[i]) + int32Bytes
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
min := func(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
j := i
|
||||
if size > maxSize {
|
||||
j--
|
||||
}
|
||||
k := min(j, len(req.AckIds))
|
||||
remainder.AckIds = req.AckIds[k:]
|
||||
req.AckIds = req.AckIds[:k]
|
||||
k = min(j, len(req.ModifyDeadlineAckIds))
|
||||
remainder.ModifyDeadlineAckIds = req.ModifyDeadlineAckIds[k:]
|
||||
remainder.ModifyDeadlineSeconds = req.ModifyDeadlineSeconds[k:]
|
||||
req.ModifyDeadlineAckIds = req.ModifyDeadlineAckIds[:k]
|
||||
req.ModifyDeadlineSeconds = req.ModifyDeadlineSeconds[:k]
|
||||
return req, remainder
|
||||
}
|
68
vendor/cloud.google.com/go/pubsub/service_test.go
generated
vendored
Normal file
68
vendor/cloud.google.com/go/pubsub/service_test.go
generated
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
// 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 pubsub
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
pb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
)
|
||||
|
||||
func TestSplitRequest(t *testing.T) {
|
||||
split := func(a []string, i int) ([]string, []string) {
|
||||
if len(a) < i {
|
||||
return a, nil
|
||||
}
|
||||
return a[:i], a[i:]
|
||||
}
|
||||
ackIDs := []string{"aaaa", "bbbb", "cccc", "dddd", "eeee"}
|
||||
modDeadlines := []int32{1, 2, 3, 4, 5}
|
||||
for i, test := range []struct {
|
||||
ackIDs []string
|
||||
modAckIDs []string
|
||||
splitIndex int
|
||||
}{
|
||||
{ackIDs, ackIDs, 2},
|
||||
{nil, ackIDs, 3},
|
||||
{ackIDs, nil, 5},
|
||||
{nil, ackIDs[:1], 1},
|
||||
} {
|
||||
req := &pb.StreamingPullRequest{
|
||||
AckIds: test.ackIDs,
|
||||
ModifyDeadlineAckIds: test.modAckIDs,
|
||||
ModifyDeadlineSeconds: modDeadlines[:len(test.modAckIDs)],
|
||||
}
|
||||
a1, a2 := split(test.ackIDs, test.splitIndex)
|
||||
m1, m2 := split(test.modAckIDs, test.splitIndex)
|
||||
want1 := &pb.StreamingPullRequest{
|
||||
AckIds: a1,
|
||||
ModifyDeadlineAckIds: m1,
|
||||
ModifyDeadlineSeconds: modDeadlines[:len(m1)],
|
||||
}
|
||||
want2 := &pb.StreamingPullRequest{
|
||||
AckIds: a2,
|
||||
ModifyDeadlineAckIds: m2,
|
||||
ModifyDeadlineSeconds: modDeadlines[len(m1) : len(m1)+len(m2)],
|
||||
}
|
||||
got1, got2 := splitRequest(req, reqFixedOverhead+40)
|
||||
if !reflect.DeepEqual(got1, want1) {
|
||||
t.Errorf("#%d: first:\ngot %+v\nwant %+v", i, got1, want1)
|
||||
}
|
||||
if !reflect.DeepEqual(got2, want2) {
|
||||
t.Errorf("#%d: second:\ngot %+v\nwant %+v", i, got2, want2)
|
||||
}
|
||||
}
|
||||
}
|
277
vendor/cloud.google.com/go/pubsub/streaming_pull_test.go
generated
vendored
Normal file
277
vendor/cloud.google.com/go/pubsub/streaming_pull_test.go
generated
vendored
Normal file
|
@ -0,0 +1,277 @@
|
|||
// 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 pubsub
|
||||
|
||||
// TODO(jba): test keepalive
|
||||
// TODO(jba): test that expired messages are not kept alive
|
||||
// TODO(jba): test that when all messages expire, Stop returns.
|
||||
|
||||
import (
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
tspb "github.com/golang/protobuf/ptypes/timestamp"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
pb "google.golang.org/genproto/googleapis/pubsub/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
var (
|
||||
timestamp = &tspb.Timestamp{}
|
||||
testMessages = []*pb.ReceivedMessage{
|
||||
{AckId: "1", Message: &pb.PubsubMessage{Data: []byte{1}, PublishTime: timestamp}},
|
||||
{AckId: "2", Message: &pb.PubsubMessage{Data: []byte{2}, PublishTime: timestamp}},
|
||||
{AckId: "3", Message: &pb.PubsubMessage{Data: []byte{3}, PublishTime: timestamp}},
|
||||
}
|
||||
)
|
||||
|
||||
func TestStreamingPullBasic(t *testing.T) {
|
||||
client, server := newFake(t)
|
||||
server.addStreamingPullMessages(testMessages)
|
||||
testStreamingPullIteration(t, client, server, testMessages)
|
||||
}
|
||||
|
||||
func TestStreamingPullMultipleFetches(t *testing.T) {
|
||||
client, server := newFake(t)
|
||||
server.addStreamingPullMessages(testMessages[:1])
|
||||
server.addStreamingPullMessages(testMessages[1:])
|
||||
testStreamingPullIteration(t, client, server, testMessages)
|
||||
}
|
||||
|
||||
func testStreamingPullIteration(t *testing.T, client *Client, server *fakeServer, msgs []*pb.ReceivedMessage) {
|
||||
if !useStreamingPull {
|
||||
t.SkipNow()
|
||||
}
|
||||
sub := client.Subscription("s")
|
||||
iter, err := sub.Pull(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i := 0; i < len(msgs); i++ {
|
||||
got, err := iter.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got.Done(i%2 == 0) // ack evens, nack odds
|
||||
want, err := toMessage(msgs[i])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want.calledDone = true
|
||||
// Don't compare done; it's a function.
|
||||
got.done = nil
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("%d: got\n%#v\nwant\n%#v", i, got, want)
|
||||
}
|
||||
|
||||
}
|
||||
iter.Stop()
|
||||
server.wait()
|
||||
for i := 0; i < len(msgs); i++ {
|
||||
id := msgs[i].AckId
|
||||
if i%2 == 0 {
|
||||
if !server.Acked[id] {
|
||||
t.Errorf("msg %q should have been acked but wasn't", id)
|
||||
}
|
||||
} else {
|
||||
if dl, ok := server.Deadlines[id]; !ok || dl != 0 {
|
||||
t.Errorf("msg %q should have been nacked but wasn't", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamingPullStop(t *testing.T) {
|
||||
if !useStreamingPull {
|
||||
t.SkipNow()
|
||||
}
|
||||
// After Stop is called, Next returns iterator.Done.
|
||||
client, server := newFake(t)
|
||||
server.addStreamingPullMessages(testMessages)
|
||||
sub := client.Subscription("s")
|
||||
iter, err := sub.Pull(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
msg, err := iter.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
msg.Done(true)
|
||||
iter.Stop()
|
||||
// Next should always return the same error.
|
||||
for i := 0; i < 3; i++ {
|
||||
_, err = iter.Next()
|
||||
if want := iterator.Done; err != want {
|
||||
t.Fatalf("got <%v> %p, want <%v> %p", err, err, want, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamingPullError(t *testing.T) {
|
||||
if !useStreamingPull {
|
||||
t.SkipNow()
|
||||
}
|
||||
client, server := newFake(t)
|
||||
server.addStreamingPullError(grpc.Errorf(codes.Internal, ""))
|
||||
sub := client.Subscription("s")
|
||||
iter, err := sub.Pull(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Next should always return the same error.
|
||||
for i := 0; i < 3; i++ {
|
||||
_, err = iter.Next()
|
||||
if want := codes.Internal; grpc.Code(err) != want {
|
||||
t.Fatalf("got <%v>, want code %v", err, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamingPullCancel(t *testing.T) {
|
||||
if !useStreamingPull {
|
||||
t.SkipNow()
|
||||
}
|
||||
// Test that canceling the iterator's context behaves correctly.
|
||||
client, server := newFake(t)
|
||||
server.addStreamingPullMessages(testMessages)
|
||||
sub := client.Subscription("s")
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
iter, err := sub.Pull(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = iter.Next()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Here we have one message read (but not acked), and two
|
||||
// in the iterator's buffer.
|
||||
cancel()
|
||||
// Further calls to Next will return Canceled.
|
||||
_, err = iter.Next()
|
||||
if got, want := err, context.Canceled; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
// Despite the unacked message, Stop will still return promptly.
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
iter.Stop()
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatal("iter.Stop timed out")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamingPullRetry(t *testing.T) {
|
||||
if !useStreamingPull {
|
||||
t.SkipNow()
|
||||
}
|
||||
// Check that we retry on io.EOF or Unavailable.
|
||||
client, server := newFake(t)
|
||||
server.addStreamingPullMessages(testMessages[:1])
|
||||
server.addStreamingPullError(io.EOF)
|
||||
server.addStreamingPullError(io.EOF)
|
||||
server.addStreamingPullMessages(testMessages[1:2])
|
||||
server.addStreamingPullError(grpc.Errorf(codes.Unavailable, ""))
|
||||
server.addStreamingPullError(grpc.Errorf(codes.Unavailable, ""))
|
||||
server.addStreamingPullMessages(testMessages[2:])
|
||||
testStreamingPullIteration(t, client, server, testMessages)
|
||||
}
|
||||
|
||||
func TestStreamingPullConcurrent(t *testing.T) {
|
||||
if !useStreamingPull {
|
||||
t.SkipNow()
|
||||
}
|
||||
newMsg := func(i int) *pb.ReceivedMessage {
|
||||
return &pb.ReceivedMessage{
|
||||
AckId: strconv.Itoa(i),
|
||||
Message: &pb.PubsubMessage{Data: []byte{byte(i)}, PublishTime: timestamp},
|
||||
}
|
||||
}
|
||||
|
||||
// Multiple goroutines should be able to read from the same iterator.
|
||||
client, server := newFake(t)
|
||||
// Add a lot of messages, a few at a time, to make sure both threads get a chance.
|
||||
nMessages := 100
|
||||
for i := 0; i < nMessages; i += 2 {
|
||||
server.addStreamingPullMessages([]*pb.ReceivedMessage{newMsg(i), newMsg(i + 1)})
|
||||
}
|
||||
sub := client.Subscription("s")
|
||||
iter, err := sub.Pull(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
seenc := make(chan string)
|
||||
errc := make(chan error, 2)
|
||||
for i := 0; i < 2; i++ {
|
||||
go func() {
|
||||
for {
|
||||
msg, err := iter.Next()
|
||||
if err == iterator.Done {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
// Must ack before sending to channel, or Stop may hang.
|
||||
msg.Done(true)
|
||||
seenc <- msg.ackID
|
||||
}
|
||||
}()
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for i := 0; i < nMessages; i++ {
|
||||
select {
|
||||
case err := <-errc:
|
||||
t.Fatal(err)
|
||||
case id := <-seenc:
|
||||
if seen[id] {
|
||||
t.Fatalf("duplicate ID %q", id)
|
||||
}
|
||||
seen[id] = true
|
||||
}
|
||||
}
|
||||
iter.Stop()
|
||||
if len(seen) != nMessages {
|
||||
t.Fatalf("got %d messages, want %d", len(seen), nMessages)
|
||||
}
|
||||
}
|
||||
|
||||
func newFake(t *testing.T) (*Client, *fakeServer) {
|
||||
srv, err := newFakeServer()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
conn, err := grpc.Dial(srv.Addr, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client, err := NewClient(context.Background(), "projectID", option.WithGRPCConn(conn))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return client, srv
|
||||
}
|
265
vendor/cloud.google.com/go/pubsub/subscription.go
generated
vendored
Normal file
265
vendor/cloud.google.com/go/pubsub/subscription.go
generated
vendored
Normal file
|
@ -0,0 +1,265 @@
|
|||
// 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/iam"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// The default period for which to automatically extend Message acknowledgement deadlines.
|
||||
const DefaultMaxExtension = 10 * time.Minute
|
||||
|
||||
// The default maximum number of messages that are prefetched from the server.
|
||||
const DefaultMaxPrefetch = 100
|
||||
|
||||
// Subscription is a reference to a PubSub subscription.
|
||||
type Subscription struct {
|
||||
s service
|
||||
|
||||
// The fully qualified identifier for the subscription, in the format "projects/<projid>/subscriptions/<name>"
|
||||
name string
|
||||
}
|
||||
|
||||
// Subscription creates a reference to a subscription.
|
||||
func (c *Client) Subscription(id string) *Subscription {
|
||||
return &Subscription{
|
||||
s: c.s,
|
||||
name: fmt.Sprintf("projects/%s/subscriptions/%s", c.projectID, id),
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the globally unique printable name of the subscription.
|
||||
func (s *Subscription) String() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
// ID returns the unique identifier of the subscription within its project.
|
||||
func (s *Subscription) ID() string {
|
||||
slash := strings.LastIndex(s.name, "/")
|
||||
if slash == -1 {
|
||||
// name is not a fully-qualified name.
|
||||
panic("bad subscription name")
|
||||
}
|
||||
return s.name[slash+1:]
|
||||
}
|
||||
|
||||
// Subscriptions returns an iterator which returns all of the subscriptions for the client's project.
|
||||
func (c *Client) Subscriptions(ctx context.Context) *SubscriptionIterator {
|
||||
return &SubscriptionIterator{
|
||||
s: c.s,
|
||||
next: c.s.listProjectSubscriptions(ctx, c.fullyQualifiedProjectName()),
|
||||
}
|
||||
}
|
||||
|
||||
// SubscriptionIterator is an iterator that returns a series of subscriptions.
|
||||
type SubscriptionIterator struct {
|
||||
s service
|
||||
next nextStringFunc
|
||||
}
|
||||
|
||||
// Next returns the next subscription. If there are no more subscriptions, iterator.Done will be returned.
|
||||
func (subs *SubscriptionIterator) Next() (*Subscription, error) {
|
||||
subName, err := subs.next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Subscription{s: subs.s, name: subName}, nil
|
||||
}
|
||||
|
||||
// PushConfig contains configuration for subscriptions that operate in push mode.
|
||||
type PushConfig struct {
|
||||
// A URL locating the endpoint to which messages should be pushed.
|
||||
Endpoint string
|
||||
|
||||
// Endpoint configuration attributes. See https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.subscriptions#pushconfig for more details.
|
||||
Attributes map[string]string
|
||||
}
|
||||
|
||||
// Subscription config contains the configuration of a subscription.
|
||||
type SubscriptionConfig struct {
|
||||
Topic *Topic
|
||||
PushConfig PushConfig
|
||||
|
||||
// The default maximum time after a subscriber receives a message before
|
||||
// the subscriber should acknowledge the message. Note: messages which are
|
||||
// obtained via a MessageIterator need not be acknowledged within this
|
||||
// deadline, as the deadline will be automatically extended.
|
||||
AckDeadline time.Duration
|
||||
}
|
||||
|
||||
// Delete deletes the subscription.
|
||||
func (s *Subscription) Delete(ctx context.Context) error {
|
||||
return s.s.deleteSubscription(ctx, 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conf.Topic = &Topic{
|
||||
s: s.s,
|
||||
name: topicName,
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// Pull returns a MessageIterator that can be used to fetch Messages. The MessageIterator
|
||||
// will automatically extend the ack deadline of all fetched Messages, for the
|
||||
// period specified by DefaultMaxExtension. This may be overridden by supplying
|
||||
// a MaxExtension pull option.
|
||||
//
|
||||
// If ctx is cancelled or exceeds its deadline, outstanding acks or deadline
|
||||
// extensions will fail.
|
||||
//
|
||||
// The caller must call Stop on the MessageIterator once finished with it.
|
||||
func (s *Subscription) Pull(ctx context.Context, opts ...PullOption) (*MessageIterator, error) {
|
||||
config, err := s.Config(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
po := processPullOptions(opts)
|
||||
po.ackDeadline = config.AckDeadline
|
||||
return newMessageIterator(ctx, s.s, s.name, po), nil
|
||||
}
|
||||
|
||||
// ModifyPushConfig updates the endpoint URL and other attributes of a push subscription.
|
||||
func (s *Subscription) ModifyPushConfig(ctx context.Context, conf *PushConfig) error {
|
||||
if conf == nil {
|
||||
return errors.New("must supply non-nil PushConfig")
|
||||
}
|
||||
|
||||
return s.s.modifyPushConfig(ctx, s.name, conf)
|
||||
}
|
||||
|
||||
func (s *Subscription) IAM() *iam.Handle {
|
||||
return s.s.iamHandle(s.name)
|
||||
}
|
||||
|
||||
// A PullOption is an optional argument to Subscription.Pull.
|
||||
type PullOption interface {
|
||||
setOptions(o *pullOptions)
|
||||
}
|
||||
|
||||
type pullOptions struct {
|
||||
// maxExtension is the maximum period for which the iterator should
|
||||
// automatically extend the ack deadline for each message.
|
||||
maxExtension time.Duration
|
||||
|
||||
// maxPrefetch is the maximum number of Messages to have in flight, to
|
||||
// be returned by MessageIterator.Next.
|
||||
maxPrefetch int32
|
||||
|
||||
// ackDeadline is the default ack deadline for the subscription. Not
|
||||
// configurable via a PullOption.
|
||||
ackDeadline time.Duration
|
||||
}
|
||||
|
||||
func processPullOptions(opts []PullOption) *pullOptions {
|
||||
po := &pullOptions{
|
||||
maxExtension: DefaultMaxExtension,
|
||||
maxPrefetch: DefaultMaxPrefetch,
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o.setOptions(po)
|
||||
}
|
||||
|
||||
return po
|
||||
}
|
||||
|
||||
type maxPrefetch int32
|
||||
|
||||
func (max maxPrefetch) setOptions(o *pullOptions) {
|
||||
if o.maxPrefetch = int32(max); o.maxPrefetch < 1 {
|
||||
o.maxPrefetch = 1
|
||||
}
|
||||
}
|
||||
|
||||
// MaxPrefetch returns a PullOption that limits Message prefetching.
|
||||
//
|
||||
// For performance reasons, the pubsub library may prefetch a pool of Messages
|
||||
// to be returned serially from MessageIterator.Next. MaxPrefetch is used to limit the
|
||||
// the size of this pool.
|
||||
//
|
||||
// If num is less than 1, it will be treated as if it were 1.
|
||||
func MaxPrefetch(num int) PullOption {
|
||||
return maxPrefetch(trunc32(int64(num)))
|
||||
}
|
||||
|
||||
type maxExtension time.Duration
|
||||
|
||||
func (max maxExtension) setOptions(o *pullOptions) {
|
||||
if o.maxExtension = time.Duration(max); o.maxExtension < 0 {
|
||||
o.maxExtension = 0
|
||||
}
|
||||
}
|
||||
|
||||
// MaxExtension returns a PullOption that limits how long acks deadlines are
|
||||
// extended for.
|
||||
//
|
||||
// A MessageIterator will automatically extend the ack deadline of all fetched
|
||||
// Messages for the duration specified. Automatic deadline extension may be
|
||||
// disabled by specifying a duration of 0.
|
||||
func MaxExtension(duration time.Duration) PullOption {
|
||||
return maxExtension(duration)
|
||||
}
|
||||
|
||||
// CreateSubscription creates a new subscription on a topic.
|
||||
//
|
||||
// name is the name of the subscription to create. It must start with a letter,
|
||||
// and contain only letters ([A-Za-z]), numbers ([0-9]), dashes (-),
|
||||
// underscores (_), periods (.), tildes (~), plus (+) or percent signs (%). It
|
||||
// must be between 3 and 255 characters in length, and must not start with
|
||||
// "goog".
|
||||
//
|
||||
// topic is the topic from which the subscription should receive messages. It
|
||||
// need not belong to the same project as the subscription.
|
||||
//
|
||||
// ackDeadline is the maximum time after a subscriber receives a message before
|
||||
// the subscriber should acknowledge the message. It must be between 10 and 600
|
||||
// seconds (inclusive), and is rounded down to the nearest second. If the
|
||||
// provided ackDeadline is 0, then the default value of 10 seconds is used.
|
||||
// Note: messages which are obtained via a MessageIterator need not be
|
||||
// acknowledged within this deadline, as the deadline will be automatically
|
||||
// extended.
|
||||
//
|
||||
// pushConfig may be set to configure this subscription for push delivery.
|
||||
//
|
||||
// If the subscription already exists an error will be returned.
|
||||
func (c *Client) CreateSubscription(ctx context.Context, id string, topic *Topic, ackDeadline time.Duration, pushConfig *PushConfig) (*Subscription, error) {
|
||||
if ackDeadline == 0 {
|
||||
ackDeadline = 10 * time.Second
|
||||
}
|
||||
if d := ackDeadline.Seconds(); d < 10 || d > 600 {
|
||||
return nil, fmt.Errorf("ack deadline must be between 10 and 600 seconds; got: %v", d)
|
||||
}
|
||||
|
||||
sub := c.Subscription(id)
|
||||
err := c.s.createSubscription(ctx, topic.name, sub.name, ackDeadline, pushConfig)
|
||||
return sub, err
|
||||
}
|
151
vendor/cloud.google.com/go/pubsub/subscription_test.go
generated
vendored
Normal file
151
vendor/cloud.google.com/go/pubsub/subscription_test.go
generated
vendored
Normal file
|
@ -0,0 +1,151 @@
|
|||
// 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 (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"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
|
||||
for {
|
||||
switch sub, err := it.Next(); err {
|
||||
case nil:
|
||||
subs = append(subs, sub)
|
||||
case iterator.Done:
|
||||
return subs, nil
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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}
|
||||
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)
|
||||
}
|
||||
for i, s := range subs {
|
||||
if got, want := s.ID(), want[i]; got != want {
|
||||
t.Errorf("Subscription.ID() = %q; want %q", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()))
|
||||
if err != nil {
|
||||
t.Errorf("error listing subscriptions: %v", 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)
|
||||
}
|
||||
if len(s.subs) != 0 {
|
||||
t.Errorf("outstanding subs: %v", s.subs)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func subNames(subs []*Subscription) []string {
|
||||
var names []string
|
||||
|
||||
for _, sub := range subs {
|
||||
names = append(names, sub.name)
|
||||
|
||||
}
|
||||
return names
|
||||
}
|
132
vendor/cloud.google.com/go/pubsub/topic.go
generated
vendored
Normal file
132
vendor/cloud.google.com/go/pubsub/topic.go
generated
vendored
Normal file
|
@ -0,0 +1,132 @@
|
|||
// 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 (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"cloud.google.com/go/iam"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const MaxPublishBatchSize = 1000
|
||||
|
||||
// Topic is a reference to a PubSub topic.
|
||||
type Topic struct {
|
||||
s service
|
||||
|
||||
// The fully qualified identifier for the topic, in the format "projects/<projid>/topics/<name>"
|
||||
name string
|
||||
}
|
||||
|
||||
// CreateTopic creates a new topic.
|
||||
// The specified topic ID must start with a letter, and contain only letters
|
||||
// ([A-Za-z]), numbers ([0-9]), dashes (-), underscores (_), periods (.),
|
||||
// tildes (~), plus (+) or percent signs (%). It must be between 3 and 255
|
||||
// characters in length, and must not start with "goog".
|
||||
// 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
|
||||
}
|
||||
|
||||
// Topic creates a reference to a topic.
|
||||
func (c *Client) Topic(id string) *Topic {
|
||||
return &Topic{
|
||||
s: c.s,
|
||||
name: fmt.Sprintf("projects/%s/topics/%s", c.projectID, id),
|
||||
}
|
||||
}
|
||||
|
||||
// Topics returns an iterator which returns all of the topics for the client's project.
|
||||
func (c *Client) Topics(ctx context.Context) *TopicIterator {
|
||||
return &TopicIterator{
|
||||
s: c.s,
|
||||
next: c.s.listProjectTopics(ctx, c.fullyQualifiedProjectName()),
|
||||
}
|
||||
}
|
||||
|
||||
// TopicIterator is an iterator that returns a series of topics.
|
||||
type TopicIterator struct {
|
||||
s service
|
||||
next nextStringFunc
|
||||
}
|
||||
|
||||
// Next returns the next topic. If there are no more topics, iterator.Done will be returned.
|
||||
func (tps *TopicIterator) Next() (*Topic, error) {
|
||||
topicName, err := tps.next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Topic{s: tps.s, name: topicName}, nil
|
||||
}
|
||||
|
||||
// ID returns the unique idenfier of the topic within its project.
|
||||
func (t *Topic) ID() string {
|
||||
slash := strings.LastIndex(t.name, "/")
|
||||
if slash == -1 {
|
||||
// name is not a fully-qualified name.
|
||||
panic("bad topic name")
|
||||
}
|
||||
return t.name[slash+1:]
|
||||
}
|
||||
|
||||
// String returns the printable globally unique name for the topic.
|
||||
func (t *Topic) String() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
// Delete deletes the topic.
|
||||
func (t *Topic) Delete(ctx context.Context) error {
|
||||
return t.s.deleteTopic(ctx, t.name)
|
||||
}
|
||||
|
||||
// Exists reports whether the topic exists on the server.
|
||||
func (t *Topic) Exists(ctx context.Context) (bool, error) {
|
||||
if t.name == "_deleted-topic_" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return t.s.topicExists(ctx, t.name)
|
||||
}
|
||||
|
||||
// Subscriptions returns an iterator which returns the subscriptions for this topic.
|
||||
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.
|
||||
return &SubscriptionIterator{
|
||||
s: t.s,
|
||||
next: t.s.listTopicSubscriptions(ctx, t.name),
|
||||
}
|
||||
}
|
||||
|
||||
// Publish publishes the supplied Messages to the topic.
|
||||
// If successful, the server-assigned message IDs are returned in the same order as the supplied Messages.
|
||||
// At most MaxPublishBatchSize messages may be supplied.
|
||||
func (t *Topic) Publish(ctx context.Context, msgs ...*Message) ([]string, error) {
|
||||
if len(msgs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if len(msgs) > MaxPublishBatchSize {
|
||||
return nil, fmt.Errorf("pubsub: got %d messages, but maximum batch size is %d", len(msgs), MaxPublishBatchSize)
|
||||
}
|
||||
return t.s.publishMessages(ctx, t.name, msgs)
|
||||
}
|
||||
|
||||
func (t *Topic) IAM() *iam.Handle {
|
||||
return t.s.iamHandle(t.name)
|
||||
}
|
127
vendor/cloud.google.com/go/pubsub/topic_test.go
generated
vendored
Normal file
127
vendor/cloud.google.com/go/pubsub/topic_test.go
generated
vendored
Normal file
|
@ -0,0 +1,127 @@
|
|||
// 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 (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"google.golang.org/api/iterator"
|
||||
)
|
||||
|
||||
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}
|
||||
topics, err := slurpTopics(c.Topics(context.Background()))
|
||||
if err != nil {
|
||||
t.Errorf("error listing topics: %v", err)
|
||||
}
|
||||
got := topicNames(topics)
|
||||
if !reflect.DeepEqual(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.
|
||||
func slurpTopics(it *TopicIterator) ([]*Topic, error) {
|
||||
var topics []*Topic
|
||||
for {
|
||||
switch topic, err := it.Next(); err {
|
||||
case nil:
|
||||
topics = append(topics, topic)
|
||||
case iterator.Done:
|
||||
return topics, nil
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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}
|
||||
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"})
|
||||
}
|
||||
|
||||
func TestListCompletelyEmptyTopics(t *testing.T) {
|
||||
var want []string
|
||||
checkTopicListing(t, want)
|
||||
}
|
||||
|
||||
func topicNames(topics []*Topic) []string {
|
||||
var names []string
|
||||
|
||||
for _, topic := range topics {
|
||||
names = append(names, topic.name)
|
||||
|
||||
}
|
||||
return names
|
||||
}
|
63
vendor/cloud.google.com/go/pubsub/utils_test.go
generated
vendored
Normal file
63
vendor/cloud.google.com/go/pubsub/utils_test.go
generated
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
// 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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue