1// Copyright 2016 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 15// TODO(jba): document in CONTRIBUTING.md that service account must be given "Logs Configuration Writer" IAM role for sink tests to pass. 16// TODO(jba): [cont] (1) From top left menu, go to IAM & Admin. (2) In Roles dropdown for acct, select Logging > Logs Configuration Writer. (3) Save. 17// TODO(jba): Also, cloud-logs@google.com must have Owner permission on the GCS bucket named for the test project. 18 19package logadmin 20 21import ( 22 "context" 23 "log" 24 "testing" 25 "time" 26 27 "cloud.google.com/go/internal/testutil" 28 "cloud.google.com/go/internal/uid" 29 ltest "cloud.google.com/go/logging/internal/testing" 30 "cloud.google.com/go/storage" 31 "google.golang.org/api/iterator" 32 "google.golang.org/api/option" 33) 34 35var sinkIDs = uid.NewSpace("GO-CLIENT-TEST-SINK", nil) 36 37const testFilter = "" 38 39var testSinkDestination string 40 41// Called just before TestMain calls m.Run. 42// Returns a cleanup function to be called after the tests finish. 43func initSinks(ctx context.Context) func() { 44 // Create a unique GCS bucket so concurrent tests don't interfere with each other. 45 bucketIDs := uid.NewSpace(testProjectID+"-log-sink", nil) 46 testBucket := bucketIDs.New() 47 testSinkDestination = "storage.googleapis.com/" + testBucket 48 var storageClient *storage.Client 49 if integrationTest { 50 // Create a unique bucket as a sink destination, and give the cloud logging account 51 // owner right. 52 ts := testutil.TokenSource(ctx, storage.ScopeFullControl) 53 var err error 54 storageClient, err = storage.NewClient(ctx, option.WithTokenSource(ts)) 55 if err != nil { 56 log.Fatalf("new storage client: %v", err) 57 } 58 bucket := storageClient.Bucket(testBucket) 59 if err := bucket.Create(ctx, testProjectID, nil); err != nil { 60 log.Fatalf("creating storage bucket %q: %v", testBucket, err) 61 } 62 log.Printf("successfully created bucket %s", testBucket) 63 if err := bucket.ACL().Set(ctx, "group-cloud-logs@google.com", storage.RoleOwner); err != nil { 64 log.Fatalf("setting owner role: %v", err) 65 } 66 } 67 // Clean up from aborted tests. 68 it := client.Sinks(ctx) 69 for { 70 s, err := it.Next() 71 if err == iterator.Done { 72 break 73 } 74 if err != nil { 75 log.Printf("listing sinks: %v", err) 76 break 77 } 78 if sinkIDs.Older(s.ID, time.Hour) { 79 client.DeleteSink(ctx, s.ID) // ignore error 80 } 81 } 82 if integrationTest { 83 for _, bn := range bucketNames(ctx, storageClient) { 84 if bucketIDs.Older(bn, 24*time.Hour) { 85 storageClient.Bucket(bn).Delete(ctx) // ignore error 86 } 87 } 88 return func() { 89 storageClient.Close() 90 } 91 } 92 return func() {} 93} 94 95// Collect the name of all buckets for the test project. 96func bucketNames(ctx context.Context, client *storage.Client) []string { 97 var names []string 98 it := client.Buckets(ctx, testProjectID) 99loop: 100 for { 101 b, err := it.Next() 102 switch err { 103 case nil: 104 names = append(names, b.Name) 105 case iterator.Done: 106 break loop 107 default: 108 log.Printf("listing buckets: %v", err) 109 break loop 110 } 111 } 112 return names 113} 114 115func TestCreateSink(t *testing.T) { 116 ctx := context.Background() 117 sink := &Sink{ 118 ID: sinkIDs.New(), 119 Destination: testSinkDestination, 120 Filter: testFilter, 121 IncludeChildren: true, 122 } 123 got, err := client.CreateSink(ctx, sink) 124 if err != nil { 125 t.Fatal(err) 126 } 127 sink.WriterIdentity = ltest.SharedServiceAccount 128 if want := sink; !testutil.Equal(got, want) { 129 t.Errorf("got %+v, want %+v", got, want) 130 } 131 got, err = client.Sink(ctx, sink.ID) 132 if err != nil { 133 t.Fatal(err) 134 } 135 if want := sink; !testutil.Equal(got, want) { 136 t.Errorf("got %+v, want %+v", got, want) 137 } 138 139 // UniqueWriterIdentity 140 sink.ID = sinkIDs.New() 141 got, err = client.CreateSinkOpt(ctx, sink, SinkOptions{UniqueWriterIdentity: true}) 142 if err != nil { 143 t.Fatal(err) 144 } 145 // The WriterIdentity should be different. 146 if got.WriterIdentity == sink.WriterIdentity { 147 t.Errorf("got %s, want something different", got.WriterIdentity) 148 } 149} 150 151func TestUpdateSink(t *testing.T) { 152 ctx := context.Background() 153 sink := &Sink{ 154 ID: sinkIDs.New(), 155 Destination: testSinkDestination, 156 Filter: testFilter, 157 IncludeChildren: true, 158 WriterIdentity: ltest.SharedServiceAccount, 159 } 160 161 if _, err := client.CreateSink(ctx, sink); err != nil { 162 t.Fatal(err) 163 } 164 got, err := client.UpdateSink(ctx, sink) 165 if err != nil { 166 t.Fatal(err) 167 } 168 if want := sink; !testutil.Equal(got, want) { 169 t.Errorf("got\n%+v\nwant\n%+v", got, want) 170 } 171 got, err = client.Sink(ctx, sink.ID) 172 if err != nil { 173 t.Fatal(err) 174 } 175 if want := sink; !testutil.Equal(got, want) { 176 t.Errorf("got\n%+v\nwant\n%+v", got, want) 177 } 178 179 // Updating an existing sink changes it. 180 sink.Filter = "" 181 sink.IncludeChildren = false 182 if _, err := client.UpdateSink(ctx, sink); err != nil { 183 t.Fatal(err) 184 } 185 got, err = client.Sink(ctx, sink.ID) 186 if err != nil { 187 t.Fatal(err) 188 } 189 if want := sink; !testutil.Equal(got, want) { 190 t.Errorf("got\n%+v\nwant\n%+v", got, want) 191 } 192} 193 194func TestUpdateSinkOpt(t *testing.T) { 195 ctx := context.Background() 196 id := sinkIDs.New() 197 origSink := &Sink{ 198 ID: id, 199 Destination: testSinkDestination, 200 Filter: testFilter, 201 IncludeChildren: true, 202 WriterIdentity: ltest.SharedServiceAccount, 203 } 204 205 if _, err := client.CreateSink(ctx, origSink); err != nil { 206 t.Fatal(err) 207 } 208 209 // Updating with empty options is an error. 210 _, err := client.UpdateSinkOpt(ctx, &Sink{ID: id, Destination: testSinkDestination}, SinkOptions{}) 211 if err == nil { 212 t.Errorf("got %v, want nil", err) 213 } 214 215 // Update selected fields. 216 got, err := client.UpdateSinkOpt(ctx, &Sink{ID: id}, SinkOptions{ 217 UpdateFilter: true, 218 UpdateIncludeChildren: true, 219 }) 220 if err != nil { 221 t.Fatal(err) 222 } 223 want := *origSink 224 want.Filter = "" 225 want.IncludeChildren = false 226 if !testutil.Equal(got, &want) { 227 t.Errorf("got\n%+v\nwant\n%+v", got, want) 228 } 229 230 // Update writer identity. 231 got, err = client.UpdateSinkOpt(ctx, &Sink{ID: id, Filter: "foo"}, 232 SinkOptions{UniqueWriterIdentity: true}) 233 if err != nil { 234 t.Fatal(err) 235 } 236 if got.WriterIdentity == want.WriterIdentity { 237 t.Errorf("got %s, want something different", got.WriterIdentity) 238 } 239 want.WriterIdentity = got.WriterIdentity 240 if !testutil.Equal(got, &want) { 241 t.Errorf("got\n%+v\nwant\n%+v", got, want) 242 } 243} 244 245func TestListSinks(t *testing.T) { 246 ctx := context.Background() 247 var sinks []*Sink 248 want := map[string]*Sink{} 249 for i := 0; i < 4; i++ { 250 s := &Sink{ 251 ID: sinkIDs.New(), 252 Destination: testSinkDestination, 253 Filter: testFilter, 254 WriterIdentity: "serviceAccount:cloud-logs@system.gserviceaccount.com", 255 } 256 sinks = append(sinks, s) 257 want[s.ID] = s 258 } 259 for _, s := range sinks { 260 if _, err := client.CreateSink(ctx, s); err != nil { 261 t.Fatalf("Create(%q): %v", s.ID, err) 262 } 263 } 264 265 got := map[string]*Sink{} 266 it := client.Sinks(ctx) 267 for { 268 s, err := it.Next() 269 if err == iterator.Done { 270 break 271 } 272 if err != nil { 273 t.Fatal(err) 274 } 275 // If tests run simultaneously, we may have more sinks than we 276 // created. So only check for our own. 277 if _, ok := want[s.ID]; ok { 278 got[s.ID] = s 279 } 280 } 281 if !testutil.Equal(got, want) { 282 t.Errorf("got %+v, want %+v", got, want) 283 } 284} 285