...

Source file src/cloud.google.com/go/logging/logadmin/sinks_test.go

Documentation: cloud.google.com/go/logging/logadmin

     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  // Note: log buckets are only created during integration tests. All buckets must allow logsink writerIdentity creator permissions.
    19  
    20  package logadmin
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  	"log"
    26  	"testing"
    27  	"time"
    28  
    29  	"cloud.google.com/go/iam"
    30  	"cloud.google.com/go/internal/testutil"
    31  	"cloud.google.com/go/internal/uid"
    32  	ltest "cloud.google.com/go/logging/internal/testing"
    33  	"cloud.google.com/go/storage"
    34  	"google.golang.org/api/iterator"
    35  	"google.golang.org/api/option"
    36  )
    37  
    38  var sinkIDs = uid.NewSpace("GO-CLIENT-TEST-SINK", nil)
    39  
    40  const testFilter = ""
    41  const testBucketTTLDays = 1
    42  
    43  var testSinkDestination string
    44  var testBucket string
    45  
    46  // Called just before TestMain calls m.Run.
    47  // Returns a cleanup function to be called after the tests finish.
    48  func initSinks(ctx context.Context) func() {
    49  	// Create a unique GCS bucket so concurrent tests don't interfere with each other.
    50  	bucketIDs := uid.NewSpace(testProjectID+"-log-sink", nil)
    51  	testBucket = bucketIDs.New()
    52  	testSinkDestination = "storage.googleapis.com/" + testBucket
    53  	var storageClient *storage.Client
    54  	if integrationTest {
    55  		// Create a unique bucket as a sink destination, and give the cloud logging account
    56  		// owner right.
    57  		ts := testutil.TokenSource(ctx, storage.ScopeFullControl)
    58  		var err error
    59  		storageClient, err = storage.NewClient(ctx, option.WithTokenSource(ts))
    60  		if err != nil {
    61  			log.Fatalf("new storage client: %v", err)
    62  		}
    63  		bucket := storageClient.Bucket(testBucket)
    64  		if err := bucket.Create(ctx, testProjectID, nil); err != nil {
    65  			log.Fatalf("creating storage bucket %q: %v", testBucket, err)
    66  		}
    67  
    68  		// Set the bucket's lifecycle to autodelete after a period of time
    69  		bucketAttrsToUpdate := storage.BucketAttrsToUpdate{
    70  			Lifecycle: &storage.Lifecycle{
    71  				Rules: []storage.LifecycleRule{
    72  					{
    73  						Action: storage.LifecycleAction{Type: storage.DeleteAction},
    74  						Condition: storage.LifecycleCondition{
    75  							AgeInDays: testBucketTTLDays,
    76  						},
    77  					},
    78  				},
    79  			},
    80  		}
    81  		_, err = bucket.Update(ctx, bucketAttrsToUpdate)
    82  		if err != nil {
    83  			log.Fatalf("updating bucket %q lifecycle rule: %v", testBucket, err)
    84  		}
    85  
    86  		// Grant destination permissions to sink's writer identity.
    87  		err = addBucketCreator(testBucket, ltest.SharedServiceAccount)
    88  		if err != nil {
    89  			log.Fatal(err)
    90  		}
    91  		log.Printf("successfully created bucket %s", testBucket)
    92  
    93  		err = addBucketIAMPolicy(testBucket, "group:cloud-logs@google.com", "roles/storage.admin")
    94  		if err != nil {
    95  			log.Fatal(err)
    96  		}
    97  	}
    98  	// Clean up from aborted tests.
    99  	it := client.Sinks(ctx)
   100  	for {
   101  		s, err := it.Next()
   102  		if err == iterator.Done {
   103  			break
   104  		}
   105  		if err != nil {
   106  			log.Printf("listing sinks: %v", err)
   107  			break
   108  		}
   109  		if sinkIDs.Older(s.ID, time.Hour) {
   110  			client.DeleteSink(ctx, s.ID) // ignore error
   111  		}
   112  	}
   113  	if integrationTest {
   114  		for _, bn := range bucketNames(ctx, storageClient) {
   115  			if bucketIDs.Older(bn, 36*time.Hour) {
   116  				storageClient.Bucket(bn).Delete(ctx) // ignore error
   117  			}
   118  		}
   119  		return func() {
   120  			// Cleanup the bucket we used on this test run.
   121  			defer storageClient.Close()
   122  			if err := storageClient.Bucket(testBucket).Delete(ctx); err != nil {
   123  				log.Printf("deleting %q: %v", testBucket, err)
   124  			}
   125  		}
   126  	}
   127  	return func() {}
   128  }
   129  
   130  // Collect the name of all buckets for the test project.
   131  func bucketNames(ctx context.Context, client *storage.Client) []string {
   132  	var names []string
   133  	it := client.Buckets(ctx, testProjectID)
   134  loop:
   135  	for {
   136  		b, err := it.Next()
   137  		switch err {
   138  		case nil:
   139  			names = append(names, b.Name)
   140  		case iterator.Done:
   141  			break loop
   142  		default:
   143  			log.Printf("listing buckets: %v", err)
   144  			break loop
   145  		}
   146  	}
   147  	return names
   148  }
   149  
   150  // addBucketIAMPolicy adds the specified IAM policy to the bucket pointed to by the bucketHandle.
   151  // Required for all new log sink service accounts.
   152  func addBucketIAMPolicy(bucketName string, identity string, role iam.RoleName) error {
   153  	if integrationTest {
   154  		ctx := context.Background()
   155  		client, err := storage.NewClient(ctx, option.WithTokenSource(testutil.TokenSource(ctx, storage.ScopeFullControl)))
   156  		if err != nil {
   157  			return fmt.Errorf("storage.NewClient: %v", err)
   158  		}
   159  		defer client.Close()
   160  
   161  		ctx, cancel := context.WithTimeout(ctx, time.Second*10)
   162  		defer cancel()
   163  
   164  		bucket := client.Bucket(bucketName)
   165  		policy, err := bucket.IAM().Policy(ctx)
   166  		if err != nil {
   167  			return fmt.Errorf("Bucket(%q).IAM().Policy: %v", bucketName, err)
   168  		}
   169  
   170  		policy.Add(identity, role)
   171  		if err := bucket.IAM().SetPolicy(ctx, policy); err != nil {
   172  			return fmt.Errorf("Bucket(%q).IAM().SetPolicy: %v", bucketName, err)
   173  		}
   174  	}
   175  
   176  	return nil
   177  }
   178  
   179  // addBucketCreator adds the bucket IAM member to permission role. Required for all new log sink service accounts.
   180  func addBucketCreator(bucketName string, identity string) error {
   181  	return addBucketIAMPolicy(bucketName, identity, "roles/storage.objectCreator")
   182  }
   183  
   184  func TestCreateSink(t *testing.T) {
   185  	ctx := context.Background()
   186  	sink := &Sink{
   187  		ID:              sinkIDs.New(),
   188  		Destination:     testSinkDestination,
   189  		Filter:          testFilter,
   190  		IncludeChildren: true,
   191  	}
   192  	got, err := client.CreateSink(ctx, sink)
   193  	if err != nil {
   194  		t.Fatal(err)
   195  	}
   196  	defer client.DeleteSink(ctx, sink.ID)
   197  
   198  	sink.WriterIdentity = ltest.SharedServiceAccount
   199  	if want := sink; !testutil.Equal(got, want) {
   200  		t.Errorf("got %+v, want %+v", got, want)
   201  	}
   202  	got, err = client.Sink(ctx, sink.ID)
   203  	if err != nil {
   204  		t.Fatal(err)
   205  	}
   206  	if want := sink; !testutil.Equal(got, want) {
   207  		t.Errorf("got %+v, want %+v", got, want)
   208  	}
   209  
   210  	// UniqueWriterIdentity
   211  	sink.ID = sinkIDs.New()
   212  	got, err = client.CreateSinkOpt(ctx, sink, SinkOptions{UniqueWriterIdentity: true})
   213  	if err != nil {
   214  		t.Fatal(err)
   215  	}
   216  	defer client.DeleteSink(ctx, sink.ID)
   217  
   218  	// Grant destination permissions to sink's writer identity.
   219  	err = addBucketCreator(testBucket, got.WriterIdentity)
   220  	if err != nil {
   221  		t.Fatal(err)
   222  	}
   223  	// The WriterIdentity should be different.
   224  	if got.WriterIdentity == sink.WriterIdentity {
   225  		t.Errorf("got %s, want something different", got.WriterIdentity)
   226  	}
   227  }
   228  
   229  func TestUpdateSink(t *testing.T) {
   230  	ctx := context.Background()
   231  	sink := &Sink{
   232  		ID:              sinkIDs.New() + "-" + t.Name(),
   233  		Destination:     testSinkDestination,
   234  		Filter:          testFilter,
   235  		IncludeChildren: true,
   236  		WriterIdentity:  ltest.SharedServiceAccount,
   237  	}
   238  
   239  	_, err := client.CreateSink(ctx, sink)
   240  	if err != nil {
   241  		t.Fatal(err)
   242  	}
   243  	defer client.DeleteSink(ctx, sink.ID)
   244  
   245  	got, err := client.UpdateSink(ctx, sink)
   246  	if err != nil {
   247  		t.Fatal(err)
   248  	}
   249  	if want := sink; !testutil.Equal(got, want) {
   250  		t.Errorf("got\n%+v\nwant\n%+v", got, want)
   251  	}
   252  	got, err = client.Sink(ctx, sink.ID)
   253  	if err != nil {
   254  		t.Fatal(err)
   255  	}
   256  	if want := sink; !testutil.Equal(got, want) {
   257  		t.Errorf("got\n%+v\nwant\n%+v", got, want)
   258  	}
   259  
   260  	// Updating an existing sink changes it.
   261  	sink.Filter = ""
   262  	sink.IncludeChildren = false
   263  	if _, err := client.UpdateSink(ctx, sink); err != nil {
   264  		t.Fatal(err)
   265  	}
   266  	got, err = client.Sink(ctx, sink.ID)
   267  	if err != nil {
   268  		t.Fatal(err)
   269  	}
   270  	if want := sink; !testutil.Equal(got, want) {
   271  		t.Errorf("got\n%+v\nwant\n%+v", got, want)
   272  	}
   273  }
   274  
   275  func TestUpdateSinkOpt(t *testing.T) {
   276  	ctx := context.Background()
   277  	id := sinkIDs.New()
   278  	origSink := &Sink{
   279  		ID:              id,
   280  		Destination:     testSinkDestination,
   281  		Filter:          testFilter,
   282  		IncludeChildren: true,
   283  		WriterIdentity:  ltest.SharedServiceAccount,
   284  	}
   285  
   286  	_, err := client.CreateSink(ctx, origSink)
   287  	if err != nil {
   288  		t.Fatal(err)
   289  	}
   290  	defer client.DeleteSink(ctx, origSink.ID)
   291  
   292  	// Updating with empty options is an error.
   293  	_, err = client.UpdateSinkOpt(ctx, &Sink{ID: id, Destination: testSinkDestination}, SinkOptions{})
   294  	if err == nil {
   295  		t.Errorf("got %v, want nil", err)
   296  	}
   297  
   298  	// Update selected fields.
   299  	got, err := client.UpdateSinkOpt(ctx, &Sink{ID: id}, SinkOptions{
   300  		UpdateFilter:          true,
   301  		UpdateIncludeChildren: true,
   302  	})
   303  	if err != nil {
   304  		t.Fatal(err)
   305  	}
   306  	want := *origSink
   307  	want.Filter = ""
   308  	want.IncludeChildren = false
   309  	if !testutil.Equal(got, &want) {
   310  		t.Errorf("got\n%+v\nwant\n%+v", got, want)
   311  	}
   312  
   313  	// Update writer identity.
   314  	got, err = client.UpdateSinkOpt(ctx, &Sink{ID: id, Filter: "foo"},
   315  		SinkOptions{UniqueWriterIdentity: true})
   316  	if err != nil {
   317  		t.Fatal(err)
   318  	}
   319  	// Grant destination permissions to sink's new writer identity.
   320  	err = addBucketCreator(testBucket, got.WriterIdentity)
   321  	if err != nil {
   322  		t.Fatal(err)
   323  	}
   324  	if got.WriterIdentity == want.WriterIdentity {
   325  		t.Errorf("got %s, want something different", got.WriterIdentity)
   326  	}
   327  	want.WriterIdentity = got.WriterIdentity
   328  	if !testutil.Equal(got, &want) {
   329  		t.Errorf("got\n%+v\nwant\n%+v", got, want)
   330  	}
   331  }
   332  
   333  func TestListSinks(t *testing.T) {
   334  	ctx := context.Background()
   335  	var sinks []*Sink
   336  	want := map[string]*Sink{}
   337  	for i := 0; i < 4; i++ {
   338  		s := &Sink{
   339  			ID:             sinkIDs.New(),
   340  			Destination:    testSinkDestination,
   341  			Filter:         testFilter,
   342  			WriterIdentity: "serviceAccount:cloud-logs@system.gserviceaccount.com",
   343  		}
   344  		sinks = append(sinks, s)
   345  		want[s.ID] = s
   346  	}
   347  	for _, s := range sinks {
   348  		_, err := client.CreateSink(ctx, s)
   349  		if err != nil {
   350  			t.Fatalf("Create(%q): %v", s.ID, err)
   351  		}
   352  		defer client.DeleteSink(ctx, s.ID)
   353  	}
   354  
   355  	got := map[string]*Sink{}
   356  	it := client.Sinks(ctx)
   357  	for {
   358  		s, err := it.Next()
   359  		if err == iterator.Done {
   360  			break
   361  		}
   362  		if err != nil {
   363  			t.Fatal(err)
   364  		}
   365  		// If tests run simultaneously, we may have more sinks than we
   366  		// created. So only check for our own.
   367  		if _, ok := want[s.ID]; ok {
   368  			got[s.ID] = s
   369  		}
   370  	}
   371  	if !testutil.Equal(got, want) {
   372  		t.Errorf("got %+v, want %+v", got, want)
   373  	}
   374  }
   375  

View as plain text