go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/logdog/appengine/coordinator/endpoints/services/registerStream_test.go (about)

     1  // Copyright 2015 The LUCI Authors.
     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  package services
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"testing"
    22  	"time"
    23  
    24  	. "github.com/smartystreets/goconvey/convey"
    25  
    26  	"go.chromium.org/luci/common/clock"
    27  	. "go.chromium.org/luci/common/testing/assertions"
    28  	"go.chromium.org/luci/gae/filter/featureBreaker"
    29  	ds "go.chromium.org/luci/gae/service/datastore"
    30  	"go.chromium.org/luci/gae/service/taskqueue"
    31  	logdog "go.chromium.org/luci/logdog/api/endpoints/coordinator/services/v1"
    32  	"go.chromium.org/luci/logdog/api/logpb"
    33  	"go.chromium.org/luci/logdog/appengine/coordinator"
    34  	ct "go.chromium.org/luci/logdog/appengine/coordinator/coordinatorTest"
    35  	"go.chromium.org/luci/logdog/common/types"
    36  )
    37  
    38  func TestRegisterStream(t *testing.T) {
    39  	t.Parallel()
    40  
    41  	Convey(`With a testing configuration`, t, func() {
    42  		c, env := ct.Install()
    43  		env.AddProject(c, "proj-foo")
    44  
    45  		// By default, the testing user is a service.
    46  		env.ActAsService()
    47  
    48  		svr := New(ServerSettings{NumQueues: 2})
    49  
    50  		// The testable TQ object.
    51  		ts := taskqueue.GetTestable(c)
    52  		ts.CreatePullQueue(RawArchiveQueueName(0))
    53  		ts.CreatePullQueue(RawArchiveQueueName(1))
    54  
    55  		Convey(`Returns Forbidden error if not a service.`, func() {
    56  			env.ActAsNobody()
    57  
    58  			_, err := svr.RegisterStream(c, &logdog.RegisterStreamRequest{})
    59  			So(err, ShouldBeRPCPermissionDenied)
    60  		})
    61  
    62  		Convey(`When registering a testing log sream, "testing/+/foo/bar"`, func() {
    63  			tls := ct.MakeStream(c, "proj-foo", "some-realm", "testing/+/foo/bar")
    64  
    65  			req := logdog.RegisterStreamRequest{
    66  				Project:       string(tls.Project),
    67  				Secret:        tls.Prefix.Secret,
    68  				ProtoVersion:  logpb.Version,
    69  				Desc:          tls.DescBytes(),
    70  				TerminalIndex: -1,
    71  			}
    72  
    73  			Convey(`Returns FailedPrecondition when the Prefix is not registered.`, func() {
    74  				_, err := svr.RegisterStream(c, &req)
    75  				So(err, ShouldBeRPCFailedPrecondition)
    76  			})
    77  
    78  			Convey(`When the Prefix is registered`, func() {
    79  				tls.WithProjectNamespace(c, func(c context.Context) {
    80  					if err := ds.Put(c, tls.Prefix); err != nil {
    81  						panic(err)
    82  					}
    83  				})
    84  
    85  				expResp := &logdog.RegisterStreamResponse{
    86  					Id: string(tls.Stream.ID),
    87  					State: &logdog.InternalLogStreamState{
    88  						Secret:        tls.Prefix.Secret,
    89  						ProtoVersion:  logpb.Version,
    90  						TerminalIndex: -1,
    91  					},
    92  				}
    93  
    94  				Convey(`Can register the stream.`, func() {
    95  					created := ds.RoundTime(env.Clock.Now())
    96  
    97  					resp, err := svr.RegisterStream(c, &req)
    98  					So(err, ShouldBeRPCOK)
    99  					So(resp, ShouldResembleProto, expResp)
   100  					ds.GetTestable(c).CatchupIndexes()
   101  
   102  					So(tls.Get(c), ShouldBeNil)
   103  
   104  					// Registers the log stream.
   105  					So(tls.Stream.Created, ShouldResemble, created)
   106  					So(tls.Stream.ExpireAt, ShouldResemble, created.Add(coordinator.LogStreamExpiry))
   107  
   108  					// Registers the log stream state.
   109  					So(tls.State.Created, ShouldResemble, created)
   110  					So(tls.State.Updated, ShouldResemble, created)
   111  					So(tls.State.ExpireAt, ShouldResemble, created.Add(coordinator.LogStreamStateExpiry))
   112  					So(tls.State.Secret, ShouldResemble, req.Secret)
   113  					So(tls.State.TerminalIndex, ShouldEqual, -1)
   114  					So(tls.State.Terminated(), ShouldBeFalse)
   115  					// Pessimistic archival is scheduled.
   116  					So(tls.State.ArchivalState(), ShouldEqual, coordinator.ArchiveTasked)
   117  
   118  					// Should also register the log stream Prefix.
   119  					So(tls.Prefix.Created, ShouldResemble, created)
   120  					So(tls.Prefix.Secret, ShouldResemble, req.Secret)
   121  
   122  					Convey(`Can register the stream again (idempotent).`, func() {
   123  						env.Clock.Set(created.Add(10 * time.Minute))
   124  
   125  						resp, err := svr.RegisterStream(c, &req)
   126  						So(err, ShouldBeRPCOK)
   127  						So(resp, ShouldResembleProto, expResp)
   128  
   129  						tls.WithProjectNamespace(c, func(c context.Context) {
   130  							So(ds.Get(c, tls.Stream, tls.State), ShouldBeNil)
   131  						})
   132  						So(tls.State.Created, ShouldResemble, created)
   133  						So(tls.State.ExpireAt, ShouldResemble, created.Add(coordinator.LogStreamStateExpiry))
   134  						So(tls.Stream.Created, ShouldResemble, created)
   135  						So(tls.Stream.ExpireAt, ShouldResemble, created.Add(coordinator.LogStreamExpiry))
   136  
   137  						Convey(`Skips archival completely after 3 weeks`, func() {
   138  							// Three weeks and an hour later
   139  							threeWeeks := (time.Hour * 24 * 7 * 3) + time.Hour
   140  							env.Clock.Set(created.Add(threeWeeks))
   141  							// Make it so that any 2s sleep timers progress.
   142  							env.Clock.SetTimerCallback(func(d time.Duration, tmr clock.Timer) {
   143  								env.Clock.Add(3 * time.Second)
   144  							})
   145  
   146  							tls.WithProjectNamespace(c, func(c context.Context) {
   147  								So(ds.Get(c, tls.State), ShouldBeNil)
   148  							})
   149  							SkipSo(tls.State.ArchivedTime.After(created.Add(threeWeeks)), ShouldBeTrue)
   150  						})
   151  					})
   152  
   153  					Convey(`Will not re-register if secrets don't match.`, func() {
   154  						req.Secret[0] = 0xAB
   155  						_, err := svr.RegisterStream(c, &req)
   156  						So(err, ShouldBeRPCInvalidArgument, "invalid secret")
   157  					})
   158  				})
   159  
   160  				Convey(`Can register a terminal stream.`, func() {
   161  					// Make it so that any 2s sleep timers progress.
   162  					env.Clock.SetTimerCallback(func(d time.Duration, tmr clock.Timer) {
   163  						env.Clock.Add(3 * time.Second)
   164  					})
   165  					prefixCreated := ds.RoundTime(env.Clock.Now())
   166  					streamCreated := prefixCreated // same time as the prefix
   167  					req.TerminalIndex = 1337
   168  					expResp.State.TerminalIndex = 1337
   169  
   170  					resp, err := svr.RegisterStream(c, &req)
   171  					So(err, ShouldBeRPCOK)
   172  					So(resp, ShouldResembleProto, expResp)
   173  					ds.GetTestable(c).CatchupIndexes()
   174  
   175  					So(tls.Get(c), ShouldBeNil)
   176  
   177  					// Registers the log stream.
   178  					So(tls.Stream.Created, ShouldResemble, streamCreated)
   179  					So(tls.Stream.ExpireAt, ShouldResemble, streamCreated.Add(coordinator.LogStreamExpiry))
   180  
   181  					// Registers the log stream state.
   182  					So(tls.State.Created, ShouldResemble, streamCreated)
   183  					So(tls.State.ExpireAt, ShouldResemble, streamCreated.Add(coordinator.LogStreamStateExpiry))
   184  					// Tasking for archival should happen after creation.
   185  					So(tls.State.Updated.Before(streamCreated), ShouldBeFalse)
   186  					So(tls.State.Secret, ShouldResemble, req.Secret)
   187  					So(tls.State.TerminalIndex, ShouldEqual, 1337)
   188  					So(tls.State.TerminatedTime, ShouldResemble, streamCreated)
   189  					So(tls.State.Terminated(), ShouldBeTrue)
   190  					So(tls.State.ArchivalState(), ShouldEqual, coordinator.ArchiveTasked)
   191  
   192  					// Should also register the log stream Prefix.
   193  					So(tls.Prefix.Created, ShouldResemble, prefixCreated)
   194  					So(tls.Prefix.Secret, ShouldResemble, req.Secret)
   195  
   196  					// When we advance to our settle delay, an archival task is scheduled.
   197  					env.Clock.Add(10 * time.Minute)
   198  				})
   199  
   200  				Convey(`Will schedule the correct archival expiration delay`, func() {
   201  					Convey(`When there is no project config delay.`, func() {
   202  						// Make it so that any 2s sleep timers progress.
   203  						env.Clock.SetTimerCallback(func(d time.Duration, tmr clock.Timer) {
   204  							env.Clock.Add(3 * time.Second)
   205  						})
   206  
   207  						_, err := svr.RegisterStream(c, &req)
   208  						So(err, ShouldBeRPCOK)
   209  						ds.GetTestable(c).CatchupIndexes()
   210  					})
   211  
   212  					Convey(`When there is no service or project config delay.`, func() {
   213  						// Make it so that any 2s sleep timers progress.
   214  						env.Clock.SetTimerCallback(func(d time.Duration, tmr clock.Timer) {
   215  							env.Clock.Add(3 * time.Second)
   216  						})
   217  
   218  						_, err := svr.RegisterStream(c, &req)
   219  						So(err, ShouldBeRPCOK)
   220  						ds.GetTestable(c).CatchupIndexes()
   221  					})
   222  				})
   223  
   224  				Convey(`Returns internal server error if the datastore Get() fails.`, func() {
   225  					c, fb := featureBreaker.FilterRDS(c, nil)
   226  					fb.BreakFeatures(errors.New("test error"), "GetMulti")
   227  
   228  					_, err := svr.RegisterStream(c, &req)
   229  					So(err, ShouldBeRPCInternal)
   230  				})
   231  
   232  				Convey(`Returns internal server error if the Prefix Put() fails.`, func() {
   233  					c, fb := featureBreaker.FilterRDS(c, nil)
   234  					fb.BreakFeatures(errors.New("test error"), "PutMulti")
   235  
   236  					_, err := svr.RegisterStream(c, &req)
   237  					So(err, ShouldBeRPCInternal)
   238  				})
   239  
   240  				Convey(`Registration failure cases`, func() {
   241  					Convey(`Will not register a stream if its prefix has expired.`, func() {
   242  						env.Clock.Set(tls.Prefix.Expiration)
   243  
   244  						_, err := svr.RegisterStream(c, &req)
   245  						So(err, ShouldBeRPCFailedPrecondition, "prefix has expired")
   246  					})
   247  
   248  					Convey(`Will not register a stream without a protobuf version.`, func() {
   249  						req.ProtoVersion = ""
   250  						_, err := svr.RegisterStream(c, &req)
   251  						So(err, ShouldBeRPCInvalidArgument, "Unrecognized protobuf version")
   252  					})
   253  
   254  					Convey(`Will not register a stream with an unknown protobuf version.`, func() {
   255  						req.ProtoVersion = "unknown"
   256  						_, err := svr.RegisterStream(c, &req)
   257  						So(err, ShouldBeRPCInvalidArgument, "Unrecognized protobuf version")
   258  					})
   259  
   260  					Convey(`Will not register with an empty descriptor.`, func() {
   261  						req.Desc = nil
   262  
   263  						_, err := svr.RegisterStream(c, &req)
   264  						So(err, ShouldBeRPCInvalidArgument, "Invalid log stream descriptor")
   265  					})
   266  
   267  					Convey(`Will not register if the descriptor doesn't validate.`, func() {
   268  						tls.Desc.ContentType = ""
   269  						So(tls.Desc.Validate(true), ShouldNotBeNil)
   270  						req.Desc = tls.DescBytes()
   271  
   272  						_, err := svr.RegisterStream(c, &req)
   273  						So(err, ShouldBeRPCInvalidArgument, "Invalid log stream descriptor")
   274  					})
   275  				})
   276  			})
   277  		})
   278  	})
   279  }
   280  
   281  func BenchmarkRegisterStream(b *testing.B) {
   282  	c, env := ct.Install()
   283  
   284  	// By default, the testing user is a service.
   285  	env.ActAsService()
   286  
   287  	const (
   288  		prefix  = types.StreamName("testing")
   289  		project = "proj-foo"
   290  	)
   291  
   292  	env.AddProject(c, project)
   293  	tls := ct.MakeStream(c, project, "some-realm", prefix.Join(types.StreamName("foo/bar")))
   294  	tls.WithProjectNamespace(c, func(c context.Context) {
   295  		if err := ds.Put(c, tls.Prefix); err != nil {
   296  			b.Fatalf("failed to register prefix: %v", err)
   297  		}
   298  	})
   299  
   300  	svr := New(ServerSettings{NumQueues: 2})
   301  
   302  	b.ResetTimer()
   303  
   304  	for i := 0; i < b.N; i++ {
   305  		tls := ct.MakeStream(c, project, "some-realm", prefix.Join(types.StreamName(fmt.Sprintf("foo/bar/%d", i))))
   306  
   307  		req := logdog.RegisterStreamRequest{
   308  			Project:       string(tls.Project),
   309  			Secret:        tls.Prefix.Secret,
   310  			ProtoVersion:  logpb.Version,
   311  			Desc:          tls.DescBytes(),
   312  			TerminalIndex: -1,
   313  		}
   314  
   315  		_, err := svr.RegisterStream(c, &req)
   316  		if err != nil {
   317  			b.Fatalf("failed to get OK response (%s)", err)
   318  		}
   319  	}
   320  }