blob: fc06f85f18c74867582244b572f7e0c07fbb6a81 [file] [log] [blame]
// Copyright 2016 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tsmon
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
ds "github.com/luci/gae/service/datastore"
"github.com/luci/gae/service/info"
"github.com/luci/luci-go/appengine/gaetesting"
"github.com/luci/luci-go/common/clock/testclock"
"github.com/luci/luci-go/common/logging/gologger"
"github.com/luci/luci-go/common/tsmon"
"github.com/luci/luci-go/common/tsmon/monitor"
"github.com/luci/luci-go/server/router"
"golang.org/x/net/context"
. "github.com/smartystreets/goconvey/convey"
)
type fakeInfo struct {
info.RawInterface
}
func (i *fakeInfo) InstanceID() string { return "instance" }
func (i *fakeInfo) ModuleName() string { return "module" }
func TestFindGaps(t *testing.T) {
tests := []struct {
numbers []int
wantFirst5Gaps []int
}{
{[]int{1}, []int{0, 2, 3, 4, 5}},
{[]int{-1, 1}, []int{0, 2, 3, 4, 5}},
{[]int{1, 3, 5}, []int{0, 2, 4, 6, 7}},
{[]int{5, 3, 1}, []int{0, 2, 4, 6, 7}},
{[]int{3, 1, 5}, []int{0, 2, 4, 6, 7}},
{[]int{4}, []int{0, 1, 2, 3, 5}},
}
for i, test := range tests {
Convey(fmt.Sprintf("%d. %v", i, test.numbers), t, func() {
numbers := map[int]struct{}{}
for _, n := range test.numbers {
numbers[n] = struct{}{}
}
nextNum := gapFinder(numbers)
// Read 5 numbers from the channel.
var got []int
for i := 0; i < 5; i++ {
got = append(got, nextNum())
}
So(got, ShouldResemble, test.wantFirst5Gaps)
})
}
}
func buildGAETestContext() (context.Context, testclock.TestClock) {
c := gaetesting.TestingContext()
c, clock := testclock.UseTime(c, testclock.TestTimeUTC)
ds.GetTestable(c).Consistent(true)
c = info.MustNamespace(c, instanceNamespace)
c = gologger.StdConfig.Use(c)
c = info.AddFilters(c, func(c context.Context, base info.RawInterface) info.RawInterface {
return &fakeInfo{base}
})
c, _, _ = tsmon.WithFakes(c)
return c, clock
}
func buildTestState() (*State, *monitor.Fake) {
mon := &monitor.Fake{}
return &State{
testingMonitor: mon,
testingSettings: &tsmonSettings{
Enabled: true,
FlushIntervalSec: 60,
},
}, mon
}
func TestHousekeepingHandler(t *testing.T) {
Convey("Assigns task numbers to unassigned instances", t, func() {
c, _ := buildGAETestContext()
i, err := getOrCreateInstanceEntity(c)
So(err, ShouldBeNil)
So(i.TaskNum, ShouldEqual, -1)
rec := httptest.NewRecorder()
housekeepingHandler(&router.Context{
Context: c,
Writer: rec,
Request: &http.Request{},
})
So(rec.Code, ShouldEqual, http.StatusOK)
i, err = getOrCreateInstanceEntity(c)
So(err, ShouldBeNil)
So(i.TaskNum, ShouldEqual, 0)
})
Convey("Doesn't reassign the same task number", t, func() {
c, clock := buildGAETestContext()
otherInstance := instance{
ID: "foobar",
TaskNum: 0,
LastUpdated: clock.Now(),
}
So(ds.Put(c, &otherInstance), ShouldBeNil)
getOrCreateInstanceEntity(c)
rec := httptest.NewRecorder()
housekeepingHandler(&router.Context{
Context: c,
Writer: rec,
Request: &http.Request{},
})
So(rec.Code, ShouldEqual, http.StatusOK)
i, err := getOrCreateInstanceEntity(c)
So(err, ShouldBeNil)
So(i.TaskNum, ShouldEqual, 1)
})
Convey("Expires old instances", t, func() {
c, clock := buildGAETestContext()
for _, count := range []int{1, ds.Raw(c).Constraints().QueryBatchSize + 1} {
Convey(fmt.Sprintf("Count: %d", count), func() {
insts := make([]*instance, count)
keys := make([]*ds.Key, count)
for i := 0; i < count; i++ {
insts[i] = &instance{
ID: fmt.Sprintf("foobar_%d", i),
TaskNum: i,
LastUpdated: clock.Now(),
}
keys[i] = ds.KeyForObj(c, insts[i])
}
So(ds.Put(c, insts), ShouldBeNil)
exists, err := ds.Exists(c, keys)
So(err, ShouldBeNil)
So(exists.All(), ShouldBeTrue)
clock.Add(instanceExpirationTimeout + time.Second)
rec := httptest.NewRecorder()
housekeepingHandler(&router.Context{
Context: c,
Writer: rec,
Request: &http.Request{},
})
So(rec.Code, ShouldEqual, http.StatusOK)
exists, err = ds.Exists(c, keys)
So(err, ShouldBeNil)
So(exists.Any(), ShouldBeFalse)
})
}
})
}