Skip to content

Commit fd8f5bf

Browse files
committed
Add more tests for manager
Signed-off-by: James Munnelly <[email protected]>
1 parent 5ebbbcb commit fd8f5bf

File tree

2 files changed

+377
-0
lines changed

2 files changed

+377
-0
lines changed

manager/manager_test.go

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,283 @@ package manager
22

33
import (
44
"context"
5+
"fmt"
56
"testing"
7+
"time"
68

79
cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
10+
cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
811
testpkg "github.com/cert-manager/cert-manager/pkg/controller/test"
912
"github.com/go-logr/logr/testr"
1013
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1114
"k8s.io/apimachinery/pkg/runtime"
15+
"k8s.io/apimachinery/pkg/util/wait"
1216
coretesting "k8s.io/client-go/testing"
1317

1418
internalapi "github.com/cert-manager/csi-lib/internal/api"
1519
internalapiutil "github.com/cert-manager/csi-lib/internal/api/util"
20+
"github.com/cert-manager/csi-lib/metadata"
21+
"github.com/cert-manager/csi-lib/storage"
22+
testutil "github.com/cert-manager/csi-lib/test/util"
1623
)
1724

25+
func TestManager_ManageVolumeImmediate_issueOnceAndSucceed(t *testing.T) {
26+
ctx := context.Background()
27+
28+
opts := newDefaultTestOptions(t)
29+
m, err := NewManager(opts)
30+
if err != nil {
31+
t.Fatal(err)
32+
}
33+
34+
// Setup a goroutine to issue one CertificateRequest
35+
stopCh := make(chan struct{})
36+
go testutil.IssueOneRequest(t, opts.Client, defaultTestNamespace, stopCh, selfSignedExampleCertificate, []byte("ca bytes"))
37+
defer close(stopCh)
38+
39+
// Register a new volume with the metadata store
40+
store := opts.MetadataReader.(storage.Interface)
41+
meta := metadata.Metadata{
42+
VolumeID: "vol-id",
43+
TargetPath: "/fake/path",
44+
}
45+
store.RegisterMetadata(meta)
46+
// Ensure we stop managing the volume after the test
47+
defer func() {
48+
store.RemoveVolume(meta.VolumeID)
49+
m.UnmanageVolume(meta.VolumeID)
50+
}()
51+
52+
// Attempt to issue the certificate & put it under management
53+
managed, err := m.ManageVolumeImmediate(ctx, meta.VolumeID)
54+
if !managed {
55+
t.Errorf("expected management to have started, but it did not")
56+
}
57+
if err != nil {
58+
t.Errorf("expected no error from ManageVolumeImmediate but got: %v", err)
59+
}
60+
61+
// Assert the certificate is under management
62+
if !m.IsVolumeReady(meta.VolumeID) {
63+
t.Errorf("expected volume to be marked as Ready but it is not")
64+
}
65+
}
66+
67+
func TestManager_PropagatesRequestConditionMessages(t *testing.T) {
68+
tests := []struct {
69+
approved string
70+
ready string
71+
expectedError string
72+
}{
73+
{
74+
approved: "",
75+
ready: "",
76+
expectedError: "waiting for request: request \"certificaterequest-name\" has not yet been approved by approval plugin",
77+
},
78+
{
79+
approved: "",
80+
ready: "pending",
81+
expectedError: "waiting for request: request \"certificaterequest-name\" has not yet been approved by approval plugin",
82+
},
83+
{
84+
approved: "",
85+
ready: "failed",
86+
expectedError: "waiting for request: request \"certificaterequest-name\" has failed: failed",
87+
},
88+
//"pending approval, ready == true": {}
89+
{
90+
approved: "denied",
91+
ready: "",
92+
expectedError: "waiting for request: request \"certificaterequest-name\" has been denied by the approval plugin: denied",
93+
},
94+
{
95+
approved: "denied",
96+
ready: "pending",
97+
expectedError: "waiting for request: request \"certificaterequest-name\" has been denied by the approval plugin: denied",
98+
},
99+
{
100+
approved: "denied",
101+
ready: "failed",
102+
expectedError: "waiting for request: request \"certificaterequest-name\" has been denied by the approval plugin: denied",
103+
},
104+
//"denied, ready == true": {},
105+
{
106+
approved: "approved",
107+
ready: "",
108+
expectedError: "waiting for request: request \"certificaterequest-name\" has no ready condition",
109+
},
110+
{
111+
approved: "approved",
112+
ready: "pending",
113+
expectedError: "waiting for request: request \"certificaterequest-name\" is pending: pending",
114+
},
115+
{
116+
approved: "approved",
117+
ready: "failed",
118+
expectedError: "waiting for request: request \"certificaterequest-name\" has failed: failed",
119+
},
120+
//"approved, ready == true": {},
121+
}
122+
for _, test := range tests {
123+
t.Run(fmt.Sprintf("approval=%q, readiness=%q", test.approved, test.ready), func(t *testing.T) {
124+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
125+
defer cancel()
126+
127+
opts := newDefaultTestOptions(t)
128+
m, err := NewManager(opts)
129+
if err != nil {
130+
t.Fatal(err)
131+
}
132+
defer m.Stop()
133+
m.requestNameGenerator = func() string { return "certificaterequest-name" }
134+
135+
// build conditions to set based on test configuration
136+
var conditions []cmapi.CertificateRequestCondition
137+
switch test.approved {
138+
case "":
139+
case "approved":
140+
conditions = append(conditions, cmapi.CertificateRequestCondition{Type: cmapi.CertificateRequestConditionApproved, Status: cmmeta.ConditionTrue, Reason: "SetByTest", Message: "approved"})
141+
case "denied":
142+
conditions = append(conditions, cmapi.CertificateRequestCondition{Type: cmapi.CertificateRequestConditionDenied, Status: cmmeta.ConditionTrue, Reason: "SetByTest", Message: "denied"})
143+
}
144+
switch test.ready {
145+
case "":
146+
case "pending":
147+
conditions = append(conditions, cmapi.CertificateRequestCondition{Type: cmapi.CertificateRequestConditionReady, Status: cmmeta.ConditionFalse, Reason: cmapi.CertificateRequestReasonPending, Message: "pending"})
148+
case "failed":
149+
conditions = append(conditions, cmapi.CertificateRequestCondition{Type: cmapi.CertificateRequestConditionReady, Status: cmmeta.ConditionFalse, Reason: cmapi.CertificateRequestReasonFailed, Message: "failed"})
150+
}
151+
// Automatically set the request to be approved & pending once created
152+
go testutil.SetCertificateRequestConditions(ctx, t, opts.Client, defaultTestNamespace, conditions...)
153+
154+
// Register a new volume with the metadata store
155+
store := opts.MetadataReader.(storage.Interface)
156+
meta := metadata.Metadata{
157+
VolumeID: "vol-id",
158+
TargetPath: "/fake/path",
159+
}
160+
store.RegisterMetadata(meta)
161+
// Ensure we stop managing the volume after the test
162+
defer func() {
163+
store.RemoveVolume(meta.VolumeID)
164+
m.UnmanageVolume(meta.VolumeID)
165+
}()
166+
167+
// Attempt to issue the certificate & put it under management
168+
managed, err := m.ManageVolumeImmediate(ctx, meta.VolumeID)
169+
if !managed {
170+
t.Errorf("expected volume to still be managed after failure")
171+
}
172+
if err == nil {
173+
t.Errorf("expected to get an error from ManageVolumeImmediate")
174+
}
175+
if err.Error() != test.expectedError {
176+
t.Errorf("expected '%s' but got: %s", test.expectedError, err.Error())
177+
}
178+
})
179+
}
180+
}
181+
182+
func TestManager_ResumesManagementOfExistingVolumes(t *testing.T) {
183+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
184+
defer cancel()
185+
186+
store := storage.NewMemoryFS()
187+
opts := defaultTestOptions(t, Options{MetadataReader: store})
188+
189+
m, err := NewManager(opts)
190+
if err != nil {
191+
t.Fatal(err)
192+
}
193+
194+
m.requestNameGenerator = func() string { return "certificaterequest-name" }
195+
// Automatically issue the request once created
196+
go testutil.IssueOneRequest(t, opts.Client, defaultTestNamespace, ctx.Done(), selfSignedExampleCertificate, []byte("ca bytes"))
197+
198+
// Register a new volume with the metadata store
199+
meta := metadata.Metadata{
200+
VolumeID: "vol-id",
201+
TargetPath: "/fake/path",
202+
}
203+
_, err = store.RegisterMetadata(meta)
204+
if err != nil {
205+
t.Fatal(err)
206+
}
207+
208+
// Attempt to issue the certificate & put it under management
209+
managed, err := m.ManageVolumeImmediate(ctx, meta.VolumeID)
210+
if !managed {
211+
t.Fatalf("expected volume to be managed")
212+
}
213+
if err != nil {
214+
t.Fatalf("unexpected error: %v", err)
215+
}
216+
217+
// Shutdown the manager
218+
m.Stop()
219+
220+
m, err = NewManager(opts)
221+
if err != nil {
222+
t.Fatal(err)
223+
}
224+
defer m.Stop()
225+
226+
if !m.IsVolumeReady(meta.VolumeID) {
227+
t.Errorf("expected volume to be monitored & ready but it is not")
228+
}
229+
}
230+
231+
func TestManager_ManageVolume_beginsManagingAndProceedsIfNotReady(t *testing.T) {
232+
ctx, cancel := context.WithCancel(context.Background())
233+
defer cancel()
234+
235+
opts := newDefaultTestOptions(t)
236+
m, err := NewManager(opts)
237+
if err != nil {
238+
t.Fatal(err)
239+
}
240+
241+
// Register a new volume with the metadata store
242+
store := opts.MetadataReader.(storage.Interface)
243+
meta := metadata.Metadata{
244+
VolumeID: "vol-id",
245+
TargetPath: "/fake/path",
246+
}
247+
store.RegisterMetadata(meta)
248+
// Ensure we stop managing the volume after the test
249+
defer func() {
250+
store.RemoveVolume(meta.VolumeID)
251+
m.UnmanageVolume(meta.VolumeID)
252+
}()
253+
254+
// Put the certificate under management
255+
managed := m.ManageVolume(meta.VolumeID)
256+
if !managed {
257+
t.Errorf("expected management to have started, but it did not")
258+
}
259+
260+
if err := wait.PollUntilWithContext(ctx, time.Millisecond*500, func(ctx context.Context) (done bool, err error) {
261+
reqs, err := opts.Client.CertmanagerV1().CertificateRequests("").List(ctx, metav1.ListOptions{})
262+
if err != nil {
263+
return false, err
264+
}
265+
if len(reqs.Items) == 1 {
266+
return true, nil
267+
}
268+
return false, nil
269+
}); err != nil {
270+
t.Errorf("failed waiting for CertificateRequest to exist: %v", err)
271+
}
272+
273+
// Assert the certificate is under management - it won't be ready as no issuer has issued the certificate
274+
if m.IsVolumeReady(meta.VolumeID) {
275+
t.Errorf("expected volume to not be Ready but it is")
276+
}
277+
if _, ok := m.managedVolumes[meta.VolumeID]; !ok {
278+
t.Errorf("expected volume to be part of managedVolumes map but it is not")
279+
}
280+
}
281+
18282
func TestManager_cleanupStaleRequests(t *testing.T) {
19283

20284
ctx := context.TODO()

manager/utils_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package manager
2+
3+
import (
4+
"crypto"
5+
"crypto/x509"
6+
"k8s.io/utils/clock"
7+
"math"
8+
"testing"
9+
"time"
10+
11+
cmfake "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake"
12+
"github.com/go-logr/logr/testr"
13+
"k8s.io/apimachinery/pkg/util/wait"
14+
fakeclock "k8s.io/utils/clock/testing"
15+
16+
"github.com/cert-manager/csi-lib/metadata"
17+
"github.com/cert-manager/csi-lib/storage"
18+
)
19+
20+
// Default namespace name used when constructing test fixtures.
21+
var defaultTestNamespace = "default-testns-name"
22+
23+
// Self signed certificate valid for 'example.com' (and probably expired by the time this is read).
24+
// This is used during test fixtures as the test driver attempts to parse the PEM certificate data,
25+
// so we can't just use any random bytes.
26+
var selfSignedExampleCertificate = []byte(`-----BEGIN CERTIFICATE-----
27+
MIICxjCCAa6gAwIBAgIRAI0W8ofWt2fD+J7Cha10KwwwDQYJKoZIhvcNAQELBQAw
28+
ADAeFw0yMjA5MTMwODI0MDBaFw0yMjEyMTIwODI0MDBaMAAwggEiMA0GCSqGSIb3
29+
DQEBAQUAA4IBDwAwggEKAoIBAQDR2ktXXbuJPZhudwfbwiYuKjb7BfehfuRZtme4
30+
HNvIhf0ABavuK4uRlKAKXRt1SZWMzm6P7NpTSOHjlxoBluZKFsgQbtNYYC8cBOMr
31+
1TuU9UwAD6U4Lw+obWQppwaEYIifdSVWUqphRT2I6EJONEB9ZUr0gHMKJ2sjl163
32+
WseSDyjPHkEM3wmpHpdDfYjNQRZ9sKB4J4/R8maW1IPpzltbryNQMfVJCYA7SjvJ
33+
KZK5cyhabqNVeBhjBSp+UczQVrJ4ruam3i4LFUbu7DVJ/60C8knhFxGJZ5uaPbOd
34+
eStraFOp50S3JbSpymq2m8c02ZsunUYiWCXGoh/UqrfYViVVAgMBAAGjOzA5MA4G
35+
A1UdDwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAAMBkGA1UdEQEB/wQPMA2CC2V4YW1w
36+
bGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQCkAvvWIUgdpuukL8nqX3850FtHl8r9
37+
I9oCra4Tv7fxsggFMhIbrVUjzE0NCB/kTjr5j/KFid9TFtbBo7bvYRKI1Qx12y28
38+
CTvY1y5BqFN/lT917B+8lrWyvxsbtQ0Xhvj9JgbLhGQutR4J+ee1sKZTPqP/sSGl
39+
PfY1JD5zWYWXWweLAR9hTp62SL6KVfsTT77jw0foehEKxfJbZY2wkdUS5GFMB8/a
40+
KQ+2l7/qPU8XL8whXEsifoJJ+U66v3cfsH0PIhTV2JKhagljdTVf333JBD/z49qv
41+
vnEIALrtIClFU6D/mTU5wyHhN29llwfjUgJrmYWqoWTZSiwGS6YmZpry
42+
-----END CERTIFICATE-----`)
43+
44+
func newDefaultTestOptions(t *testing.T) Options {
45+
return defaultTestOptions(t, Options{})
46+
}
47+
48+
func defaultTestOptions(t *testing.T, opts Options) Options {
49+
var store storage.Interface
50+
if opts.MetadataReader == nil {
51+
store = storage.NewMemoryFS()
52+
opts.MetadataReader = store
53+
} else {
54+
store = opts.MetadataReader.(storage.Interface)
55+
}
56+
if opts.Clock == nil {
57+
opts.Clock = fakeclock.NewFakeClock(time.Now())
58+
}
59+
if opts.Log == nil {
60+
log := testr.New(t)
61+
opts.Log = &log
62+
}
63+
if opts.Client == nil {
64+
opts.Client = cmfake.NewSimpleClientset()
65+
}
66+
if opts.NodeID == "" {
67+
opts.NodeID = "test-node-id"
68+
}
69+
if opts.GeneratePrivateKey == nil {
70+
opts.GeneratePrivateKey = nothingGeneratePrivateKey
71+
}
72+
if opts.GenerateRequest == nil {
73+
opts.GenerateRequest = generateRequestInNamespace(defaultTestNamespace)
74+
}
75+
if opts.SignRequest == nil {
76+
opts.SignRequest = nothingSignRequest
77+
}
78+
if opts.WriteKeypair == nil {
79+
opts.WriteKeypair = persistingWriteKeypair(store, opts.Clock)
80+
}
81+
if opts.RenewalBackoffConfig == nil {
82+
opts.RenewalBackoffConfig = &wait.Backoff{Steps: math.MaxInt32} // backoff is always 0s for speedy tests
83+
}
84+
return opts
85+
}
86+
87+
func nothingGeneratePrivateKey(meta metadata.Metadata) (crypto.PrivateKey, error) {
88+
return nil, nil
89+
}
90+
91+
func generateRequestInNamespace(ns string) GenerateRequestFunc {
92+
return func(meta metadata.Metadata) (*CertificateRequestBundle, error) {
93+
return &CertificateRequestBundle{
94+
Namespace: ns,
95+
}, nil
96+
}
97+
}
98+
99+
func nothingSignRequest(meta metadata.Metadata, key crypto.PrivateKey, request *x509.CertificateRequest) (csr []byte, err error) {
100+
return []byte{}, nil
101+
}
102+
103+
func persistingWriteKeypair(store storage.Interface, clock clock.Clock) WriteKeypairFunc {
104+
return func(meta metadata.Metadata, key crypto.PrivateKey, chain []byte, ca []byte) error {
105+
store.WriteFiles(meta, map[string][]byte{
106+
"ca": ca,
107+
"cert": chain,
108+
})
109+
nextIssuanceTime := clock.Now().Add(time.Hour)
110+
meta.NextIssuanceTime = &nextIssuanceTime
111+
return store.WriteMetadata(meta.VolumeID, meta)
112+
}
113+
}

0 commit comments

Comments
 (0)