blob: 73be9284b63705a963b143e2b694421e377678f5 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package principal
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"reflect"
"testing"
"time"
"v.io/v23/security"
)
// manualTrigger provides a gc trigger that can be signaled manually
type manualTrigger struct {
gcHasRun bool
ch chan time.Time
nextCh chan bool
}
func newManualTrigger() *manualTrigger {
return &manualTrigger{
ch: make(chan time.Time),
nextCh: make(chan bool),
}
}
// manualTrigger is the trigger that should be provided in GC policy config.
// It returns a chan time.Time that resolves immediately after next is called.
func (mt *manualTrigger) waitForNextGc() <-chan time.Time {
if !mt.gcHasRun {
mt.gcHasRun = true
} else {
mt.nextCh <- true
}
return mt.ch
}
// next should be called to trigger the next policy trigger event.
func (mt *manualTrigger) next() {
mt.ch <- time.Time{}
<-mt.nextCh
}
// Test just to confirm it signals in order as expected.
func TestManualTrigger(t *testing.T) {
mt := newManualTrigger()
countTriggers := 0
go func() {
for i := 0; i < 100; i++ {
<-mt.waitForNextGc()
countTriggers++
}
}()
for i := 1; i <= 99; i++ {
mt.next()
if countTriggers != i {
t.Errorf("Expected %d triggers, got %d", i, countTriggers)
}
}
}
func newSigner() security.Signer {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
panic(err)
}
return security.NewInMemoryECDSASigner(key)
}
func TestBlessingsCache(t *testing.T) {
notificationCh := make(chan []BlessingsCacheMessage, 1)
notifier := func(msg []BlessingsCacheMessage) {
notificationCh <- msg
}
mt := newManualTrigger()
// Create a BlessingsCache with a GC policy that we can trigger on demand.
onDemandGCPolicy := &BlessingsCacheGCPolicy{
nextTrigger: mt.waitForNextGc,
}
bc := NewBlessingsCache(notifier, onDemandGCPolicy)
// Blessings for the tests.
p, err := security.CreatePrincipal(newSigner(), nil, nil)
if err != nil {
t.Fatal("Failed to create principal: ", err)
}
blessA, err := p.BlessSelf("A")
if err != nil {
t.Fatal("Failed to bless A: ", err)
}
blessB, err := p.BlessSelf("B")
if err != nil {
t.Fatal("Failed to bless B: ", err)
}
blessC, err := p.BlessSelf("C")
if err != nil {
t.Fatal("Failed to bless C: ", err)
}
// First do puts and make sure the ids are reasonable.
idA := bc.Put(blessA)
expectAddMessage(t, notificationCh, 1, blessA)
idB := bc.Put(blessB)
expectAddMessage(t, notificationCh, 2, blessB)
if idA == idB {
t.Errorf("A and B unexpectedly had same id: %v", idA)
}
idA2 := bc.Put(blessA)
expectNoMessage(t, notificationCh)
if idA2 != idA {
t.Errorf("A and A2 expected to have same id, but they were %v and %v",
idA, idA2)
}
// Now perform GC. Check that the values are still in the cache.
mt.next()
expectNoMessage(t, notificationCh)
idGc1A := bc.Put(blessA)
idGc1B := bc.Put(blessB)
expectNoMessage(t, notificationCh)
if idA != idGc1A {
t.Errorf("Expected to get same id after one gc of A, but got %v and %v",
idA, idGc1A)
}
if idB != idGc1B {
t.Errorf("Expected to get same id after one gc of B, but got %v and %v",
idB, idGc1B)
}
// Now perform GC to clear the dirty bits.
mt.next()
expectNoMessage(t, notificationCh)
// Update B and add C.
idGc2B := bc.Put(blessB)
expectNoMessage(t, notificationCh)
if idB != idGc2B {
t.Errorf("Expected to get same id after two gcs of B, but got %v and %v",
idB, idGc2B)
}
idC := bc.Put(blessC)
expectAddMessage(t, notificationCh, 3, blessC)
if idC == idA || idC == idB {
t.Error("C was unexpectedly the same as A or B")
}
// Perform GC. A should be removed but B and C should stay.
mt.next()
expectDeleteMessage(t, notificationCh, BlessingsCacheDeleteMessage{CacheId: 1, DeleteAfter: 3})
if idB != bc.Put(blessB) {
t.Errorf("B seems to have been cleaned up as it was given a new id")
}
if idC != bc.Put(blessC) {
t.Errorf("C seems to have been cleaned up as it was given a new id")
}
expectNoMessage(t, notificationCh)
// Perform GC twice to remove the other items.
mt.next()
expectNoMessage(t, notificationCh)
mt.next()
expectDeleteMessage(t, notificationCh,
BlessingsCacheDeleteMessage{CacheId: 2, DeleteAfter: 4},
BlessingsCacheDeleteMessage{CacheId: 3, DeleteAfter: 2})
// No notifications should occur on further GCs.
mt.next()
mt.next()
mt.next()
// Note that this should only be reached after the second GC is done because
// the GC trigger channel has size 1.
expectNoMessage(t, notificationCh)
bc.Stop()
}
func expectNoMessage(t *testing.T, notificationCh chan []BlessingsCacheMessage) {
select {
case <-notificationCh:
t.Errorf("Got message when none expected")
default:
}
}
func expectAddMessage(t *testing.T, notificationCh chan []BlessingsCacheMessage, id BlessingsId, bless security.Blessings) {
select {
case notifications := <-notificationCh:
if len(notifications) != 1 {
t.Fatalf("Got invalid add message with %d messages", len(notifications))
}
addMsg := notifications[0].(BlessingsCacheMessageAdd).Value
if got, want := addMsg.CacheId, id; got != want {
t.Errorf("Unexpected id in add message: %v. Wanted: %v", got, want)
}
if got, want := addMsg.Blessings, bless; !reflect.DeepEqual(got, want) {
t.Errorf("Blessings unexpectedly not equal. Got %v, want %v", got, want)
}
case <-time.After(10 * time.Second):
t.Fatalf("Timed out waiting for notification")
}
}
func expectDeleteMessage(t *testing.T, notificationCh chan []BlessingsCacheMessage, expected ...BlessingsCacheDeleteMessage) {
select {
case notifications := <-notificationCh:
if len(notifications) != len(expected) {
t.Fatalf("Got %d notifications but expected %d delete notifications", len(notifications), len(expected))
}
for _, notification := range notifications {
delNotif := notification.(BlessingsCacheMessageDelete).Value
var foundMatch bool
for _, expectedNotif := range expected {
if reflect.DeepEqual(delNotif, expectedNotif) {
foundMatch = true
}
}
if !foundMatch {
t.Errorf("Unexpected delete notification: %v", delNotif)
}
}
case <-time.After(10 * time.Second):
t.Fatalf("Timed out waiting for notification")
}
}