Convert service account token controller to use a work queue
This commit is contained in:
@@ -17,10 +17,15 @@ limitations under the License.
|
||||
package serviceaccount
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
"k8s.io/kubernetes/pkg/client/testing/core"
|
||||
@@ -63,7 +68,12 @@ func tokenSecretReferences() []api.ObjectReference {
|
||||
|
||||
// addTokenSecretReference adds a reference to the ServiceAccountToken that will be created
|
||||
func addTokenSecretReference(refs []api.ObjectReference) []api.ObjectReference {
|
||||
return append(refs, api.ObjectReference{Name: "default-token-fplln"})
|
||||
return addNamedTokenSecretReference(refs, "default-token-fplln")
|
||||
}
|
||||
|
||||
// addNamedTokenSecretReference adds a reference to the named ServiceAccountToken
|
||||
func addNamedTokenSecretReference(refs []api.ObjectReference, name string) []api.ObjectReference {
|
||||
return append(refs, api.ObjectReference{Name: name})
|
||||
}
|
||||
|
||||
// serviceAccount returns a service account with the given secret refs
|
||||
@@ -104,10 +114,15 @@ func opaqueSecret() *api.Secret {
|
||||
|
||||
// createdTokenSecret returns the ServiceAccountToken secret posted when creating a new token secret.
|
||||
// Named "default-token-fplln", since that is the first generated name after rand.Seed(1)
|
||||
func createdTokenSecret() *api.Secret {
|
||||
func createdTokenSecret(overrideName ...string) *api.Secret {
|
||||
return namedCreatedTokenSecret("default-token-fplln")
|
||||
}
|
||||
|
||||
// namedTokenSecret returns the ServiceAccountToken secret posted when creating a new token secret with the given name.
|
||||
func namedCreatedTokenSecret(name string) *api.Secret {
|
||||
return &api.Secret{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "default-token-fplln",
|
||||
Name: name,
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
api.ServiceAccountNameKey: "default",
|
||||
@@ -180,12 +195,20 @@ func serviceAccountTokenSecretWithNamespaceData(data []byte) *api.Secret {
|
||||
return secret
|
||||
}
|
||||
|
||||
type reaction struct {
|
||||
verb string
|
||||
resource string
|
||||
reactor func(t *testing.T) core.ReactionFunc
|
||||
}
|
||||
|
||||
func TestTokenCreation(t *testing.T) {
|
||||
testcases := map[string]struct {
|
||||
ClientObjects []runtime.Object
|
||||
|
||||
SecretsSyncPending bool
|
||||
ServiceAccountsSyncPending bool
|
||||
IsAsync bool
|
||||
MaxRetries int
|
||||
|
||||
Reactors []reaction
|
||||
|
||||
ExistingServiceAccount *api.ServiceAccount
|
||||
ExistingSecrets []*api.Secret
|
||||
@@ -209,16 +232,66 @@ func TestTokenCreation(t *testing.T) {
|
||||
core.NewUpdateAction(unversioned.GroupVersionResource{Resource: "serviceaccounts"}, api.NamespaceDefault, serviceAccount(addTokenSecretReference(emptySecretReferences()))),
|
||||
},
|
||||
},
|
||||
"new serviceaccount with no secrets with unsynced secret store": {
|
||||
"new serviceaccount with no secrets encountering create error": {
|
||||
ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()},
|
||||
|
||||
SecretsSyncPending: true,
|
||||
MaxRetries: 10,
|
||||
IsAsync: true,
|
||||
Reactors: []reaction{{
|
||||
verb: "create",
|
||||
resource: "secrets",
|
||||
reactor: func(t *testing.T) core.ReactionFunc {
|
||||
i := 0
|
||||
return func(core.Action) (bool, runtime.Object, error) {
|
||||
i++
|
||||
if i < 3 {
|
||||
return true, nil, apierrors.NewForbidden(api.Resource("secrets"), "foo", errors.New("No can do"))
|
||||
}
|
||||
return false, nil, nil
|
||||
}
|
||||
},
|
||||
}},
|
||||
|
||||
AddedServiceAccount: serviceAccount(emptySecretReferences()),
|
||||
ExpectedActions: []core.Action{
|
||||
// Attempt 1
|
||||
core.NewGetAction(unversioned.GroupVersionResource{Resource: "serviceaccounts"}, api.NamespaceDefault, "default"),
|
||||
core.NewCreateAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, createdTokenSecret()),
|
||||
core.NewUpdateAction(unversioned.GroupVersionResource{Resource: "serviceaccounts"}, api.NamespaceDefault, serviceAccount(addTokenSecretReference(emptySecretReferences()))),
|
||||
|
||||
// Attempt 2
|
||||
core.NewGetAction(unversioned.GroupVersionResource{Resource: "serviceaccounts"}, api.NamespaceDefault, "default"),
|
||||
core.NewCreateAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, namedCreatedTokenSecret("default-token-gziey")),
|
||||
|
||||
// Attempt 3
|
||||
core.NewGetAction(unversioned.GroupVersionResource{Resource: "serviceaccounts"}, api.NamespaceDefault, "default"),
|
||||
core.NewCreateAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, namedCreatedTokenSecret("default-token-oh43e")),
|
||||
core.NewUpdateAction(unversioned.GroupVersionResource{Resource: "serviceaccounts"}, api.NamespaceDefault, serviceAccount(addNamedTokenSecretReference(emptySecretReferences(), "default-token-oh43e"))),
|
||||
},
|
||||
},
|
||||
"new serviceaccount with no secrets encountering unending create error": {
|
||||
ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()},
|
||||
MaxRetries: 2,
|
||||
IsAsync: true,
|
||||
Reactors: []reaction{{
|
||||
verb: "create",
|
||||
resource: "secrets",
|
||||
reactor: func(t *testing.T) core.ReactionFunc {
|
||||
return func(core.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, apierrors.NewForbidden(api.Resource("secrets"), "foo", errors.New("No can do"))
|
||||
}
|
||||
},
|
||||
}},
|
||||
|
||||
AddedServiceAccount: serviceAccount(emptySecretReferences()),
|
||||
ExpectedActions: []core.Action{
|
||||
// Attempt
|
||||
core.NewGetAction(unversioned.GroupVersionResource{Resource: "serviceaccounts"}, api.NamespaceDefault, "default"),
|
||||
core.NewCreateAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, createdTokenSecret()),
|
||||
// Retry 1
|
||||
core.NewGetAction(unversioned.GroupVersionResource{Resource: "serviceaccounts"}, api.NamespaceDefault, "default"),
|
||||
core.NewCreateAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, namedCreatedTokenSecret("default-token-gziey")),
|
||||
// Retry 2
|
||||
core.NewGetAction(unversioned.GroupVersionResource{Resource: "serviceaccounts"}, api.NamespaceDefault, "default"),
|
||||
core.NewCreateAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, namedCreatedTokenSecret("default-token-oh43e")),
|
||||
},
|
||||
},
|
||||
"new serviceaccount with missing secrets": {
|
||||
@@ -231,14 +304,6 @@ func TestTokenCreation(t *testing.T) {
|
||||
core.NewUpdateAction(unversioned.GroupVersionResource{Resource: "serviceaccounts"}, api.NamespaceDefault, serviceAccount(addTokenSecretReference(missingSecretReferences()))),
|
||||
},
|
||||
},
|
||||
"new serviceaccount with missing secrets with unsynced secret store": {
|
||||
ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences()), createdTokenSecret()},
|
||||
|
||||
SecretsSyncPending: true,
|
||||
|
||||
AddedServiceAccount: serviceAccount(missingSecretReferences()),
|
||||
ExpectedActions: []core.Action{},
|
||||
},
|
||||
"new serviceaccount with non-token secrets": {
|
||||
ClientObjects: []runtime.Object{serviceAccount(regularSecretReferences()), createdTokenSecret(), opaqueSecret()},
|
||||
|
||||
@@ -275,18 +340,6 @@ func TestTokenCreation(t *testing.T) {
|
||||
core.NewUpdateAction(unversioned.GroupVersionResource{Resource: "serviceaccounts"}, api.NamespaceDefault, serviceAccount(addTokenSecretReference(emptySecretReferences()))),
|
||||
},
|
||||
},
|
||||
"updated serviceaccount with no secrets with unsynced secret store": {
|
||||
ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()},
|
||||
|
||||
SecretsSyncPending: true,
|
||||
|
||||
UpdatedServiceAccount: serviceAccount(emptySecretReferences()),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(unversioned.GroupVersionResource{Resource: "serviceaccounts"}, api.NamespaceDefault, "default"),
|
||||
core.NewCreateAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, createdTokenSecret()),
|
||||
core.NewUpdateAction(unversioned.GroupVersionResource{Resource: "serviceaccounts"}, api.NamespaceDefault, serviceAccount(addTokenSecretReference(emptySecretReferences()))),
|
||||
},
|
||||
},
|
||||
"updated serviceaccount with missing secrets": {
|
||||
ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences()), createdTokenSecret()},
|
||||
|
||||
@@ -297,14 +350,6 @@ func TestTokenCreation(t *testing.T) {
|
||||
core.NewUpdateAction(unversioned.GroupVersionResource{Resource: "serviceaccounts"}, api.NamespaceDefault, serviceAccount(addTokenSecretReference(missingSecretReferences()))),
|
||||
},
|
||||
},
|
||||
"updated serviceaccount with missing secrets with unsynced secret store": {
|
||||
ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences()), createdTokenSecret()},
|
||||
|
||||
SecretsSyncPending: true,
|
||||
|
||||
UpdatedServiceAccount: serviceAccount(missingSecretReferences()),
|
||||
ExpectedActions: []core.Action{},
|
||||
},
|
||||
"updated serviceaccount with non-token secrets": {
|
||||
ClientObjects: []runtime.Object{serviceAccount(regularSecretReferences()), createdTokenSecret(), opaqueSecret()},
|
||||
|
||||
@@ -375,6 +420,7 @@ func TestTokenCreation(t *testing.T) {
|
||||
|
||||
AddedSecret: serviceAccountTokenSecretWithoutTokenData(),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, "token-secret-1"),
|
||||
core.NewUpdateAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, serviceAccountTokenSecret()),
|
||||
},
|
||||
},
|
||||
@@ -384,6 +430,7 @@ func TestTokenCreation(t *testing.T) {
|
||||
|
||||
AddedSecret: serviceAccountTokenSecretWithoutCAData(),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, "token-secret-1"),
|
||||
core.NewUpdateAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, serviceAccountTokenSecret()),
|
||||
},
|
||||
},
|
||||
@@ -393,6 +440,7 @@ func TestTokenCreation(t *testing.T) {
|
||||
|
||||
AddedSecret: serviceAccountTokenSecretWithCAData([]byte("mismatched")),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, "token-secret-1"),
|
||||
core.NewUpdateAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, serviceAccountTokenSecret()),
|
||||
},
|
||||
},
|
||||
@@ -402,6 +450,7 @@ func TestTokenCreation(t *testing.T) {
|
||||
|
||||
AddedSecret: serviceAccountTokenSecretWithoutNamespaceData(),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, "token-secret-1"),
|
||||
core.NewUpdateAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, serviceAccountTokenSecret()),
|
||||
},
|
||||
},
|
||||
@@ -436,6 +485,7 @@ func TestTokenCreation(t *testing.T) {
|
||||
|
||||
UpdatedSecret: serviceAccountTokenSecretWithoutTokenData(),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, "token-secret-1"),
|
||||
core.NewUpdateAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, serviceAccountTokenSecret()),
|
||||
},
|
||||
},
|
||||
@@ -445,6 +495,7 @@ func TestTokenCreation(t *testing.T) {
|
||||
|
||||
UpdatedSecret: serviceAccountTokenSecretWithoutCAData(),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, "token-secret-1"),
|
||||
core.NewUpdateAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, serviceAccountTokenSecret()),
|
||||
},
|
||||
},
|
||||
@@ -454,6 +505,7 @@ func TestTokenCreation(t *testing.T) {
|
||||
|
||||
UpdatedSecret: serviceAccountTokenSecretWithCAData([]byte("mismatched")),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, "token-secret-1"),
|
||||
core.NewUpdateAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, serviceAccountTokenSecret()),
|
||||
},
|
||||
},
|
||||
@@ -463,6 +515,7 @@ func TestTokenCreation(t *testing.T) {
|
||||
|
||||
UpdatedSecret: serviceAccountTokenSecretWithoutNamespaceData(),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, "token-secret-1"),
|
||||
core.NewUpdateAction(unversioned.GroupVersionResource{Resource: "secrets"}, api.NamespaceDefault, serviceAccountTokenSecret()),
|
||||
},
|
||||
},
|
||||
@@ -501,6 +554,7 @@ func TestTokenCreation(t *testing.T) {
|
||||
}
|
||||
|
||||
for k, tc := range testcases {
|
||||
glog.Infof(k)
|
||||
|
||||
// Re-seed to reset name generation
|
||||
utilrand.Seed(1)
|
||||
@@ -508,12 +562,11 @@ func TestTokenCreation(t *testing.T) {
|
||||
generator := &testGenerator{Token: "ABC"}
|
||||
|
||||
client := fake.NewSimpleClientset(tc.ClientObjects...)
|
||||
for _, reactor := range tc.Reactors {
|
||||
client.Fake.PrependReactor(reactor.verb, reactor.resource, reactor.reactor(t))
|
||||
}
|
||||
|
||||
controller := NewTokensController(client, TokensControllerOptions{TokenGenerator: generator, RootCA: []byte("CA Data")})
|
||||
|
||||
// Tell the token controller whether its stores have been synced
|
||||
controller.serviceAccountsSynced = func() bool { return !tc.ServiceAccountsSyncPending }
|
||||
controller.secretsSynced = func() bool { return !tc.SecretsSyncPending }
|
||||
controller := NewTokensController(client, TokensControllerOptions{TokenGenerator: generator, RootCA: []byte("CA Data"), MaxRetries: tc.MaxRetries})
|
||||
|
||||
if tc.ExistingServiceAccount != nil {
|
||||
controller.serviceAccounts.Add(tc.ExistingServiceAccount)
|
||||
@@ -523,22 +576,72 @@ func TestTokenCreation(t *testing.T) {
|
||||
}
|
||||
|
||||
if tc.AddedServiceAccount != nil {
|
||||
controller.serviceAccountAdded(tc.AddedServiceAccount)
|
||||
controller.serviceAccounts.Add(tc.AddedServiceAccount)
|
||||
controller.queueServiceAccountSync(tc.AddedServiceAccount)
|
||||
}
|
||||
if tc.UpdatedServiceAccount != nil {
|
||||
controller.serviceAccountUpdated(nil, tc.UpdatedServiceAccount)
|
||||
controller.serviceAccounts.Add(tc.UpdatedServiceAccount)
|
||||
controller.queueServiceAccountUpdateSync(nil, tc.UpdatedServiceAccount)
|
||||
}
|
||||
if tc.DeletedServiceAccount != nil {
|
||||
controller.serviceAccountDeleted(tc.DeletedServiceAccount)
|
||||
controller.serviceAccounts.Delete(tc.DeletedServiceAccount)
|
||||
controller.queueServiceAccountSync(tc.DeletedServiceAccount)
|
||||
}
|
||||
if tc.AddedSecret != nil {
|
||||
controller.secretAdded(tc.AddedSecret)
|
||||
controller.secrets.Add(tc.AddedSecret)
|
||||
controller.queueSecretSync(tc.AddedSecret)
|
||||
}
|
||||
if tc.UpdatedSecret != nil {
|
||||
controller.secretUpdated(nil, tc.UpdatedSecret)
|
||||
controller.secrets.Add(tc.UpdatedSecret)
|
||||
controller.queueSecretUpdateSync(nil, tc.UpdatedSecret)
|
||||
}
|
||||
if tc.DeletedSecret != nil {
|
||||
controller.secretDeleted(tc.DeletedSecret)
|
||||
controller.secrets.Delete(tc.DeletedSecret)
|
||||
controller.queueSecretSync(tc.DeletedSecret)
|
||||
}
|
||||
|
||||
// This is the longest we'll wait for async tests
|
||||
timeout := time.Now().Add(30 * time.Second)
|
||||
waitedForAdditionalActions := false
|
||||
|
||||
for {
|
||||
if controller.syncServiceAccountQueue.Len() > 0 {
|
||||
controller.syncServiceAccount()
|
||||
}
|
||||
if controller.syncSecretQueue.Len() > 0 {
|
||||
controller.syncSecret()
|
||||
}
|
||||
|
||||
// The queues still have things to work on
|
||||
if controller.syncServiceAccountQueue.Len() > 0 || controller.syncSecretQueue.Len() > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// If we expect this test to work asynchronously...
|
||||
if tc.IsAsync {
|
||||
// if we're still missing expected actions within our test timeout
|
||||
if len(client.Actions()) < len(tc.ExpectedActions) && time.Now().Before(timeout) {
|
||||
// wait for the expected actions (without hotlooping)
|
||||
time.Sleep(time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
// if we exactly match our expected actions, wait a bit to make sure no other additional actions show up
|
||||
if len(client.Actions()) == len(tc.ExpectedActions) && !waitedForAdditionalActions {
|
||||
time.Sleep(time.Second)
|
||||
waitedForAdditionalActions = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if controller.syncServiceAccountQueue.Len() > 0 {
|
||||
t.Errorf("%s: unexpected items in service account queue: %d", k, controller.syncServiceAccountQueue.Len())
|
||||
}
|
||||
if controller.syncSecretQueue.Len() > 0 {
|
||||
t.Errorf("%s: unexpected items in secret queue: %d", k, controller.syncSecretQueue.Len())
|
||||
}
|
||||
|
||||
actions := client.Actions()
|
||||
@@ -556,7 +659,10 @@ func TestTokenCreation(t *testing.T) {
|
||||
}
|
||||
|
||||
if len(tc.ExpectedActions) > len(actions) {
|
||||
t.Errorf("%s: %d additional expected actions:%+v", k, len(tc.ExpectedActions)-len(actions), tc.ExpectedActions[len(actions):])
|
||||
t.Errorf("%s: %d additional expected actions", k, len(tc.ExpectedActions)-len(actions))
|
||||
for _, a := range tc.ExpectedActions[len(actions):] {
|
||||
t.Logf(" %+v", a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user