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 }