/* * * Copyright 2020 gRPC 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 certprovider import ( "context" "errors" "fmt" "reflect" "sync" "testing" "time" ) var errProviderTestInternal = errors.New("provider internal error") // TestDistributor invokes the different methods on the Distributor type and // verifies the results. func (s) TestDistributor(t *testing.T) { dist := NewDistributor() // Read cert/key files from testdata. km, err := loadKeyMaterials() if err != nil { t.Fatal(err) } // wantKM1 has both local and root certs. wantKM1 := *km // wantKM2 has only local certs. Roots are nil-ed out. wantKM2 := *km wantKM2.Roots = nil // Create a goroutines which work in lockstep with the rest of the test. // This goroutine reads the key material from the distributor while the rest // of the test sets it. var wg sync.WaitGroup wg.Add(1) errCh := make(chan error) proceedCh := make(chan struct{}) go func() { defer wg.Done() // The first call to KeyMaterial() should timeout because no key // material has been set on the distributor as yet. ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout/2) defer cancel() if _, err := dist.KeyMaterial(ctx); err != context.DeadlineExceeded { errCh <- err return } proceedCh <- struct{}{} // This call to KeyMaterial() should return the key material with both // the local certs and the root certs. ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout) defer cancel() gotKM, err := dist.KeyMaterial(ctx) if err != nil { errCh <- err return } if !reflect.DeepEqual(gotKM, &wantKM1) { errCh <- fmt.Errorf("provider.KeyMaterial() = %+v, want %+v", gotKM, wantKM1) } proceedCh <- struct{}{} // This call to KeyMaterial() should eventually return key material with // only the local certs. ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout) defer cancel() for { gotKM, err := dist.KeyMaterial(ctx) if err != nil { errCh <- err return } if reflect.DeepEqual(gotKM, &wantKM2) { break } } proceedCh <- struct{}{} // This call to KeyMaterial() should return nil key material and a // non-nil error. ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout) defer cancel() for { gotKM, err := dist.KeyMaterial(ctx) if gotKM == nil && err == errProviderTestInternal { break } if err != nil { // If we have gotten any error other than // errProviderTestInternal, we should bail out. errCh <- err return } } proceedCh <- struct{}{} // This call to KeyMaterial() should eventually return errProviderClosed // error. ctx, cancel = context.WithTimeout(context.Background(), defaultTestTimeout) defer cancel() for { if _, err := dist.KeyMaterial(ctx); err == errProviderClosed { break } time.Sleep(100 * time.Millisecond) } }() waitAndDo(t, proceedCh, errCh, func() { dist.Set(&wantKM1, nil) }) waitAndDo(t, proceedCh, errCh, func() { dist.Set(&wantKM2, nil) }) waitAndDo(t, proceedCh, errCh, func() { dist.Set(&wantKM2, errProviderTestInternal) }) waitAndDo(t, proceedCh, errCh, func() { dist.Stop() }) } func waitAndDo(t *testing.T, proceedCh chan struct{}, errCh chan error, do func()) { t.Helper() timer := time.NewTimer(defaultTestTimeout) select { case <-timer.C: t.Fatalf("test timed out when waiting for event from distributor") case <-proceedCh: do() case err := <-errCh: t.Fatal(err) } }