You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

450 lines
13 KiB

  1. // Copyright 2014 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package pubsub
  15. import (
  16. "fmt"
  17. "testing"
  18. "time"
  19. gax "github.com/googleapis/gax-go"
  20. "golang.org/x/net/context"
  21. "cloud.google.com/go/iam"
  22. "cloud.google.com/go/internal"
  23. "cloud.google.com/go/internal/testutil"
  24. "cloud.google.com/go/internal/uid"
  25. "google.golang.org/api/iterator"
  26. "google.golang.org/api/option"
  27. "google.golang.org/grpc"
  28. "google.golang.org/grpc/codes"
  29. )
  30. var (
  31. topicIDs = uid.NewSpace("topic", nil)
  32. subIDs = uid.NewSpace("sub", nil)
  33. )
  34. // messageData is used to hold the contents of a message so that it can be compared against the contents
  35. // of another message without regard to irrelevant fields.
  36. type messageData struct {
  37. ID string
  38. Data []byte
  39. Attributes map[string]string
  40. }
  41. func extractMessageData(m *Message) *messageData {
  42. return &messageData{
  43. ID: m.ID,
  44. Data: m.Data,
  45. Attributes: m.Attributes,
  46. }
  47. }
  48. func integrationTestClient(t *testing.T, ctx context.Context) *Client {
  49. if testing.Short() {
  50. t.Skip("Integration tests skipped in short mode")
  51. }
  52. projID := testutil.ProjID()
  53. if projID == "" {
  54. t.Skip("Integration tests skipped. See CONTRIBUTING.md for details")
  55. }
  56. ts := testutil.TokenSource(ctx, ScopePubSub, ScopeCloudPlatform)
  57. if ts == nil {
  58. t.Skip("Integration tests skipped. See CONTRIBUTING.md for details")
  59. }
  60. client, err := NewClient(ctx, projID, option.WithTokenSource(ts))
  61. if err != nil {
  62. t.Fatalf("Creating client error: %v", err)
  63. }
  64. return client
  65. }
  66. func TestAll(t *testing.T) {
  67. t.Parallel()
  68. ctx := context.Background()
  69. client := integrationTestClient(t, ctx)
  70. defer client.Close()
  71. topic, err := client.CreateTopic(ctx, topicIDs.New())
  72. if err != nil {
  73. t.Errorf("CreateTopic error: %v", err)
  74. }
  75. defer topic.Stop()
  76. var sub *Subscription
  77. if sub, err = client.CreateSubscription(ctx, subIDs.New(), SubscriptionConfig{Topic: topic}); err != nil {
  78. t.Errorf("CreateSub error: %v", err)
  79. }
  80. exists, err := topic.Exists(ctx)
  81. if err != nil {
  82. t.Fatalf("TopicExists error: %v", err)
  83. }
  84. if !exists {
  85. t.Errorf("topic %v should exist, but it doesn't", topic)
  86. }
  87. exists, err = sub.Exists(ctx)
  88. if err != nil {
  89. t.Fatalf("SubExists error: %v", err)
  90. }
  91. if !exists {
  92. t.Errorf("subscription %s should exist, but it doesn't", sub.ID())
  93. }
  94. var msgs []*Message
  95. for i := 0; i < 10; i++ {
  96. text := fmt.Sprintf("a message with an index %d", i)
  97. attrs := make(map[string]string)
  98. attrs["foo"] = "bar"
  99. msgs = append(msgs, &Message{
  100. Data: []byte(text),
  101. Attributes: attrs,
  102. })
  103. }
  104. // Publish the messages.
  105. type pubResult struct {
  106. m *Message
  107. r *PublishResult
  108. }
  109. var rs []pubResult
  110. for _, m := range msgs {
  111. r := topic.Publish(ctx, m)
  112. rs = append(rs, pubResult{m, r})
  113. }
  114. want := make(map[string]*messageData)
  115. for _, res := range rs {
  116. id, err := res.r.Get(ctx)
  117. if err != nil {
  118. t.Fatal(err)
  119. }
  120. md := extractMessageData(res.m)
  121. md.ID = id
  122. want[md.ID] = md
  123. }
  124. // Use a timeout to ensure that Pull does not block indefinitely if there are unexpectedly few messages available.
  125. timeoutCtx, _ := context.WithTimeout(ctx, time.Minute)
  126. gotMsgs, err := pullN(timeoutCtx, sub, len(want), func(ctx context.Context, m *Message) {
  127. m.Ack()
  128. })
  129. if err != nil {
  130. t.Fatalf("Pull: %v", err)
  131. }
  132. got := make(map[string]*messageData)
  133. for _, m := range gotMsgs {
  134. md := extractMessageData(m)
  135. got[md.ID] = md
  136. }
  137. if !testutil.Equal(got, want) {
  138. t.Errorf("messages: got: %v ; want: %v", got, want)
  139. }
  140. if msg, ok := testIAM(ctx, topic.IAM(), "pubsub.topics.get"); !ok {
  141. t.Errorf("topic IAM: %s", msg)
  142. }
  143. if msg, ok := testIAM(ctx, sub.IAM(), "pubsub.subscriptions.get"); !ok {
  144. t.Errorf("sub IAM: %s", msg)
  145. }
  146. snap, err := sub.CreateSnapshot(ctx, "")
  147. if err != nil {
  148. t.Fatalf("CreateSnapshot error: %v", err)
  149. }
  150. timeoutCtx, _ = context.WithTimeout(ctx, time.Minute)
  151. err = internal.Retry(timeoutCtx, gax.Backoff{}, func() (bool, error) {
  152. snapIt := client.Snapshots(timeoutCtx)
  153. for {
  154. s, err := snapIt.Next()
  155. if err == nil && s.name == snap.name {
  156. return true, nil
  157. }
  158. if err == iterator.Done {
  159. return false, fmt.Errorf("cannot find snapshot: %q", snap.name)
  160. }
  161. if err != nil {
  162. return false, err
  163. }
  164. }
  165. })
  166. if err != nil {
  167. t.Error(err)
  168. }
  169. err = internal.Retry(timeoutCtx, gax.Backoff{}, func() (bool, error) {
  170. err := sub.SeekToSnapshot(timeoutCtx, snap.Snapshot)
  171. return err == nil, err
  172. })
  173. if err != nil {
  174. t.Error(err)
  175. }
  176. err = internal.Retry(timeoutCtx, gax.Backoff{}, func() (bool, error) {
  177. err := sub.SeekToTime(timeoutCtx, time.Now())
  178. return err == nil, err
  179. })
  180. if err != nil {
  181. t.Error(err)
  182. }
  183. err = internal.Retry(timeoutCtx, gax.Backoff{}, func() (bool, error) {
  184. snapHandle := client.Snapshot(snap.ID())
  185. err := snapHandle.Delete(timeoutCtx)
  186. return err == nil, err
  187. })
  188. if err != nil {
  189. t.Error(err)
  190. }
  191. if err := sub.Delete(ctx); err != nil {
  192. t.Errorf("DeleteSub error: %v", err)
  193. }
  194. if err := topic.Delete(ctx); err != nil {
  195. t.Errorf("DeleteTopic error: %v", err)
  196. }
  197. }
  198. // IAM tests.
  199. // NOTE: for these to succeed, the test runner identity must have the Pub/Sub Admin or Owner roles.
  200. // To set, visit https://console.developers.google.com, select "IAM & Admin" from the top-left
  201. // menu, choose the account, click the Roles dropdown, and select "Pub/Sub > Pub/Sub Admin".
  202. // TODO(jba): move this to a testing package within cloud.google.com/iam, so we can re-use it.
  203. func testIAM(ctx context.Context, h *iam.Handle, permission string) (msg string, ok bool) {
  204. // Attempting to add an non-existent identity (e.g. "alice@example.com") causes the service
  205. // to return an internal error, so use a real identity.
  206. const member = "domain:google.com"
  207. var policy *iam.Policy
  208. var err error
  209. if policy, err = h.Policy(ctx); err != nil {
  210. return fmt.Sprintf("Policy: %v", err), false
  211. }
  212. // The resource is new, so the policy should be empty.
  213. if got := policy.Roles(); len(got) > 0 {
  214. return fmt.Sprintf("initially: got roles %v, want none", got), false
  215. }
  216. // Add a member, set the policy, then check that the member is present.
  217. policy.Add(member, iam.Viewer)
  218. if err := h.SetPolicy(ctx, policy); err != nil {
  219. return fmt.Sprintf("SetPolicy: %v", err), false
  220. }
  221. if policy, err = h.Policy(ctx); err != nil {
  222. return fmt.Sprintf("Policy: %v", err), false
  223. }
  224. if got, want := policy.Members(iam.Viewer), []string{member}; !testutil.Equal(got, want) {
  225. return fmt.Sprintf("after Add: got %v, want %v", got, want), false
  226. }
  227. // Now remove that member, set the policy, and check that it's empty again.
  228. policy.Remove(member, iam.Viewer)
  229. if err := h.SetPolicy(ctx, policy); err != nil {
  230. return fmt.Sprintf("SetPolicy: %v", err), false
  231. }
  232. if policy, err = h.Policy(ctx); err != nil {
  233. return fmt.Sprintf("Policy: %v", err), false
  234. }
  235. if got := policy.Roles(); len(got) > 0 {
  236. return fmt.Sprintf("after Remove: got roles %v, want none", got), false
  237. }
  238. // Call TestPermissions.
  239. // Because this user is an admin, it has all the permissions on the
  240. // resource type. Note: the service fails if we ask for inapplicable
  241. // permissions (e.g. a subscription permission on a topic, or a topic
  242. // create permission on a topic rather than its parent).
  243. wantPerms := []string{permission}
  244. gotPerms, err := h.TestPermissions(ctx, wantPerms)
  245. if err != nil {
  246. return fmt.Sprintf("TestPermissions: %v", err), false
  247. }
  248. if !testutil.Equal(gotPerms, wantPerms) {
  249. return fmt.Sprintf("TestPermissions: got %v, want %v", gotPerms, wantPerms), false
  250. }
  251. return "", true
  252. }
  253. func TestSubscriptionUpdate(t *testing.T) {
  254. t.Parallel()
  255. ctx := context.Background()
  256. client := integrationTestClient(t, ctx)
  257. defer client.Close()
  258. topic, err := client.CreateTopic(ctx, topicIDs.New())
  259. if err != nil {
  260. t.Fatalf("CreateTopic error: %v", err)
  261. }
  262. defer topic.Stop()
  263. defer topic.Delete(ctx)
  264. var sub *Subscription
  265. if sub, err = client.CreateSubscription(ctx, subIDs.New(), SubscriptionConfig{Topic: topic}); err != nil {
  266. t.Fatalf("CreateSub error: %v", err)
  267. }
  268. defer sub.Delete(ctx)
  269. got, err := sub.Config(ctx)
  270. if err != nil {
  271. t.Fatal(err)
  272. }
  273. want := SubscriptionConfig{
  274. Topic: topic,
  275. AckDeadline: 10 * time.Second,
  276. RetainAckedMessages: false,
  277. RetentionDuration: defaultRetentionDuration,
  278. }
  279. if !testutil.Equal(got, want) {
  280. t.Fatalf("\ngot %+v\nwant %+v", got, want)
  281. }
  282. // Add a PushConfig and change other fields.
  283. projID := testutil.ProjID()
  284. pc := PushConfig{
  285. Endpoint: "https://" + projID + ".appspot.com/_ah/push-handlers/push",
  286. Attributes: map[string]string{"x-goog-version": "v1"},
  287. }
  288. got, err = sub.Update(ctx, SubscriptionConfigToUpdate{
  289. PushConfig: &pc,
  290. AckDeadline: 2 * time.Minute,
  291. RetainAckedMessages: true,
  292. RetentionDuration: 2 * time.Hour,
  293. })
  294. if err != nil {
  295. t.Fatal(err)
  296. }
  297. want = SubscriptionConfig{
  298. Topic: topic,
  299. PushConfig: pc,
  300. AckDeadline: 2 * time.Minute,
  301. RetainAckedMessages: true,
  302. RetentionDuration: 2 * time.Hour,
  303. }
  304. if !testutil.Equal(got, want) {
  305. t.Fatalf("\ngot %+v\nwant %+v", got, want)
  306. }
  307. // Remove the PushConfig, turning the subscription back into pull mode.
  308. // Change AckDeadline, but nothing else.
  309. pc = PushConfig{}
  310. got, err = sub.Update(ctx, SubscriptionConfigToUpdate{
  311. PushConfig: &pc,
  312. AckDeadline: 30 * time.Second,
  313. })
  314. if err != nil {
  315. t.Fatal(err)
  316. }
  317. want.PushConfig = pc
  318. want.AckDeadline = 30 * time.Second
  319. // service issue: PushConfig attributes are not removed.
  320. // TODO(jba): remove when issue resolved.
  321. want.PushConfig.Attributes = map[string]string{"x-goog-version": "v1"}
  322. if !testutil.Equal(got, want) {
  323. t.Fatalf("\ngot %+v\nwant %+v", got, want)
  324. }
  325. // If nothing changes, our client returns an error.
  326. _, err = sub.Update(ctx, SubscriptionConfigToUpdate{})
  327. if err == nil {
  328. t.Fatal("got nil, wanted error")
  329. }
  330. }
  331. func TestPublicTopic(t *testing.T) {
  332. t.Parallel()
  333. ctx := context.Background()
  334. client := integrationTestClient(t, ctx)
  335. defer client.Close()
  336. sub, err := client.CreateSubscription(ctx, subIDs.New(), SubscriptionConfig{
  337. Topic: client.TopicInProject("taxirides-realtime", "pubsub-public-data"),
  338. })
  339. if err != nil {
  340. t.Fatal(err)
  341. }
  342. defer sub.Delete(ctx)
  343. // Confirm that Receive works. It doesn't matter if we actually get any
  344. // messages.
  345. ctxt, cancel := context.WithTimeout(ctx, 5*time.Second)
  346. err = sub.Receive(ctxt, func(_ context.Context, msg *Message) {
  347. msg.Ack()
  348. cancel()
  349. })
  350. if err != nil {
  351. t.Fatal(err)
  352. }
  353. }
  354. func TestIntegration_Errors(t *testing.T) {
  355. // Test various edge conditions.
  356. t.Parallel()
  357. ctx := context.Background()
  358. client := integrationTestClient(t, ctx)
  359. defer client.Close()
  360. topic, err := client.CreateTopic(ctx, topicIDs.New())
  361. if err != nil {
  362. t.Fatalf("CreateTopic error: %v", err)
  363. }
  364. defer topic.Stop()
  365. defer topic.Delete(ctx)
  366. // Out-of-range retention duration.
  367. sub, err := client.CreateSubscription(ctx, subIDs.New(), SubscriptionConfig{
  368. Topic: topic,
  369. RetentionDuration: 1 * time.Second,
  370. })
  371. if want := codes.InvalidArgument; grpc.Code(err) != want {
  372. t.Errorf("got <%v>, want %s", err, want)
  373. }
  374. if err == nil {
  375. sub.Delete(ctx)
  376. }
  377. // Ack deadline less than minimum.
  378. sub, err = client.CreateSubscription(ctx, subIDs.New(), SubscriptionConfig{
  379. Topic: topic,
  380. AckDeadline: 5 * time.Second,
  381. })
  382. if want := codes.Unknown; grpc.Code(err) != want {
  383. t.Errorf("got <%v>, want %s", err, want)
  384. }
  385. if err == nil {
  386. sub.Delete(ctx)
  387. }
  388. // Updating a non-existent subscription.
  389. sub = client.Subscription(subIDs.New())
  390. _, err = sub.Update(ctx, SubscriptionConfigToUpdate{AckDeadline: 20 * time.Second})
  391. if want := codes.NotFound; grpc.Code(err) != want {
  392. t.Errorf("got <%v>, want %s", err, want)
  393. }
  394. // Deleting a non-existent subscription.
  395. err = sub.Delete(ctx)
  396. if want := codes.NotFound; grpc.Code(err) != want {
  397. t.Errorf("got <%v>, want %s", err, want)
  398. }
  399. // Updating out-of-range retention duration.
  400. sub, err = client.CreateSubscription(ctx, subIDs.New(), SubscriptionConfig{Topic: topic})
  401. if err != nil {
  402. t.Fatal(err)
  403. }
  404. defer sub.Delete(ctx)
  405. _, err = sub.Update(ctx, SubscriptionConfigToUpdate{RetentionDuration: 1000 * time.Hour})
  406. if want := codes.InvalidArgument; grpc.Code(err) != want {
  407. t.Errorf("got <%v>, want %s", err, want)
  408. }
  409. }