Skip to content

Commit bd8d812

Browse files
committed
Reset network status when CSR is deleted
The signer controller is reflecting CSR approval status into network operator status during signing process, but when CSR is removed it's not getting network status back into original state because CSR is no longer available. When CSR signing reattempt happens, signer controller is not updating existing CertificateFailed condition type, instead it tries to add another CertificateFailed condition and leads to Duplicate value: "Failed" error, so fixing it just by updating existing CertificateFailed condition. Signed-off-by: Periyasamy Palanisamy <[email protected]>
1 parent cb5d120 commit bd8d812

File tree

2 files changed

+241
-1
lines changed

2 files changed

+241
-1
lines changed

pkg/controller/signer/signer-controller.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"log"
7+
"time"
78

89
cnoclient "github.com/openshift/cluster-network-operator/pkg/client"
910
"github.com/openshift/cluster-network-operator/pkg/controller/statusmanager"
@@ -88,6 +89,8 @@ func (r *ReconcileCSR) Reconcile(ctx context.Context, request reconcile.Request)
8889
if err != nil {
8990
if apierrors.IsNotFound(err) {
9091
// Request object not found, could have been deleted after reconcile request.
92+
// restore network status when CSR is deleted.
93+
r.status.SetNotDegraded(statusmanager.CertificateSigner)
9194
// Return and don't requeue as the CSR has been deleted.
9295
return reconcile.Result{}, nil
9396
}
@@ -227,7 +230,7 @@ func signerFailure(r *ReconcileCSR, csr *csrv1.CertificateSigningRequest, reason
227230

228231
// Update the status conditions on the CSR object
229232
func updateCSRStatusConditions(r *ReconcileCSR, csr *csrv1.CertificateSigningRequest, reason string, message string) {
230-
csr.Status.Conditions = append(csr.Status.Conditions, csrv1.CertificateSigningRequestCondition{
233+
setCertificateSigningRequestCondition(&csr.Status.Conditions, csrv1.CertificateSigningRequestCondition{
231234
Type: csrv1.CertificateFailed,
232235
Status: "True",
233236
Reason: reason,
@@ -240,3 +243,26 @@ func updateCSRStatusConditions(r *ReconcileCSR, csr *csrv1.CertificateSigningReq
240243
fmt.Sprintf("Unable to update csr: %v", err))
241244
}
242245
}
246+
247+
func setCertificateSigningRequestCondition(conditions *[]csrv1.CertificateSigningRequestCondition, newCondition csrv1.CertificateSigningRequestCondition) {
248+
if conditions == nil {
249+
conditions = &[]csrv1.CertificateSigningRequestCondition{}
250+
}
251+
var existingCondition *csrv1.CertificateSigningRequestCondition
252+
for i := range *conditions {
253+
if (*conditions)[i].Type == newCondition.Type {
254+
existingCondition = &(*conditions)[i]
255+
}
256+
}
257+
if existingCondition == nil {
258+
newCondition.LastTransitionTime = metav1.NewTime(time.Now())
259+
*conditions = append(*conditions, newCondition)
260+
return
261+
}
262+
if existingCondition.Status != newCondition.Status {
263+
existingCondition.Status = newCondition.Status
264+
existingCondition.LastTransitionTime = metav1.NewTime(time.Now())
265+
}
266+
existingCondition.Reason = newCondition.Reason
267+
existingCondition.Message = newCondition.Message
268+
}

pkg/controller/signer/signer_test.go

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
package signer
2+
3+
import (
4+
"context"
5+
"crypto/rand"
6+
"crypto/rsa"
7+
"crypto/x509"
8+
"encoding/pem"
9+
"fmt"
10+
"testing"
11+
12+
. "github.com/onsi/gomega"
13+
configv1 "github.com/openshift/api/config/v1"
14+
operv1 "github.com/openshift/api/operator/v1"
15+
cnoclient "github.com/openshift/cluster-network-operator/pkg/client"
16+
"github.com/openshift/cluster-network-operator/pkg/client/fake"
17+
"github.com/openshift/cluster-network-operator/pkg/controller/statusmanager"
18+
"github.com/openshift/cluster-network-operator/pkg/names"
19+
certificatev1 "k8s.io/api/certificates/v1"
20+
corev1 "k8s.io/api/core/v1"
21+
apierrors "k8s.io/apimachinery/pkg/api/errors"
22+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
"k8s.io/apimachinery/pkg/types"
24+
"k8s.io/client-go/kubernetes/scheme"
25+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
26+
)
27+
28+
var (
29+
csrName = "ipsec-csr"
30+
nodeName = "testnode"
31+
coName = "testing"
32+
)
33+
34+
//nolint:errcheck
35+
func init() {
36+
certificatev1.AddToScheme(scheme.Scheme)
37+
corev1.AddToScheme(scheme.Scheme)
38+
}
39+
40+
func TestSigner_DegradedCondition(t *testing.T) {
41+
g := NewGomegaWithT(t)
42+
client := fake.NewFakeClient()
43+
status := statusmanager.New(client, coName, names.StandAloneClusterName)
44+
signer := ReconcileCSR{client: client, status: status}
45+
46+
co := &configv1.ClusterOperator{ObjectMeta: metav1.ObjectMeta{Name: coName}}
47+
setCO(t, client, co)
48+
no := &operv1.Network{ObjectMeta: metav1.ObjectMeta{Name: names.OPERATOR_CONFIG}}
49+
setOC(t, client, no)
50+
51+
csr, err := generateCSR()
52+
g.Expect(err).NotTo(HaveOccurred())
53+
csrObj := &certificatev1.CertificateSigningRequest{}
54+
csrObj.Name = csrName
55+
csrObj.Spec.Request = []byte(csr)
56+
csrObj.Spec.SignerName = signerName
57+
csrObj.Spec.Usages = []certificatev1.KeyUsage{"ipsec tunnel"}
58+
csrObj.Spec.Username = fmt.Sprintf("system:ovn-node:%s", nodeName)
59+
csrObj.Status.Conditions = append(csrObj.Status.Conditions, certificatev1.CertificateSigningRequestCondition{
60+
Type: certificatev1.CertificateApproved,
61+
Status: "True",
62+
Reason: "AutoApproved",
63+
Message: "Automatically approved by " + signerName})
64+
65+
err = client.Default().CRClient().Create(context.TODO(), csrObj)
66+
g.Expect(err).NotTo(HaveOccurred())
67+
_, err = client.Default().Kubernetes().CertificatesV1().CertificateSigningRequests().Create(context.TODO(), csrObj, metav1.CreateOptions{})
68+
g.Expect(err).NotTo(HaveOccurred())
69+
70+
node := &corev1.Node{}
71+
node.Name = nodeName
72+
_, err = client.Default().Kubernetes().CoreV1().Nodes().Create(context.TODO(), node, metav1.CreateOptions{})
73+
g.Expect(err).NotTo(HaveOccurred())
74+
75+
randomByteArray := []byte("blahblahblah")
76+
caSecret := &corev1.Secret{}
77+
caSecret.Name = "signer-ca"
78+
caSecret.Namespace = "openshift-ovn-kubernetes"
79+
caSecret.Data = make(map[string][]byte)
80+
caSecret.Data["tls.crt"] = randomByteArray
81+
caSecret.Data["tls.key"] = randomByteArray
82+
err = client.Default().CRClient().Create(context.TODO(), caSecret)
83+
g.Expect(err).NotTo(HaveOccurred())
84+
85+
for range 3 {
86+
_, err = signer.Reconcile(context.TODO(),
87+
reconcile.Request{NamespacedName: types.NamespacedName{Name: csrName}})
88+
g.Expect(err).NotTo(HaveOccurred())
89+
90+
err = client.Default().CRClient().Get(context.TODO(), types.NamespacedName{Name: csrName}, csrObj)
91+
g.Expect(err).NotTo(HaveOccurred())
92+
g.Expect(csrObj.Status.Certificate).Should(BeEmpty())
93+
94+
co, _, err = getStatuses(client, "testing")
95+
if err != nil {
96+
t.Fatalf("error getting network.operator: %v", err)
97+
}
98+
g.Expect(err).NotTo(HaveOccurred())
99+
g.Expect(conditionsInclude(co.Status.Conditions, []configv1.ClusterOperatorStatusCondition{
100+
{
101+
Type: configv1.OperatorDegraded,
102+
Status: configv1.ConditionTrue,
103+
},
104+
})).To(BeTrue())
105+
g.Expect(conditionsInclude(co.Status.Conditions, []configv1.ClusterOperatorStatusCondition{
106+
{
107+
Type: configv1.OperatorUpgradeable,
108+
Status: configv1.ConditionTrue,
109+
},
110+
})).To(BeTrue())
111+
}
112+
113+
err = client.Default().CRClient().Delete(context.TODO(), csrObj)
114+
g.Expect(err).NotTo(HaveOccurred())
115+
_, err = signer.Reconcile(context.TODO(),
116+
reconcile.Request{NamespacedName: types.NamespacedName{Name: csrName}})
117+
g.Expect(err).NotTo(HaveOccurred())
118+
119+
co, _, err = getStatuses(client, "testing")
120+
if err != nil {
121+
t.Fatalf("error getting network.operator: %v", err)
122+
}
123+
g.Expect(err).NotTo(HaveOccurred())
124+
g.Expect(conditionsInclude(co.Status.Conditions, []configv1.ClusterOperatorStatusCondition{
125+
{
126+
Type: configv1.OperatorDegraded,
127+
Status: configv1.ConditionFalse,
128+
},
129+
})).To(BeTrue())
130+
g.Expect(conditionsInclude(co.Status.Conditions, []configv1.ClusterOperatorStatusCondition{
131+
{
132+
Type: configv1.OperatorUpgradeable,
133+
Status: configv1.ConditionTrue,
134+
},
135+
})).To(BeTrue())
136+
137+
}
138+
139+
func generateCSR() (string, error) {
140+
// Create private key.
141+
csrKey, err := rsa.GenerateKey(rand.Reader, 2048)
142+
if err != nil {
143+
return "", fmt.Errorf("failed to generate private key: %v", err)
144+
}
145+
// Create CSR with private key.
146+
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{}, csrKey)
147+
if err != nil {
148+
return "", err
149+
}
150+
// Encode CSR in PEM format.
151+
csrPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes})
152+
if csrPEM == nil {
153+
return "", fmt.Errorf("failed to encode CSR in PEM format")
154+
}
155+
return string(csrPEM), nil
156+
}
157+
158+
func setOC(t *testing.T, client cnoclient.Client, oc *operv1.Network) {
159+
t.Helper()
160+
g := NewGomegaWithT(t)
161+
_, err := client.Default().OpenshiftOperatorClient().OperatorV1().Networks().Update(context.TODO(), oc, metav1.UpdateOptions{})
162+
if apierrors.IsNotFound(err) {
163+
_, err = client.Default().OpenshiftOperatorClient().OperatorV1().Networks().Create(context.TODO(), oc, metav1.CreateOptions{})
164+
}
165+
g.Expect(err).NotTo(HaveOccurred())
166+
}
167+
168+
func setCO(t *testing.T, client cnoclient.Client, co *configv1.ClusterOperator) {
169+
t.Helper()
170+
g := NewGomegaWithT(t)
171+
err := client.Default().CRClient().Update(context.TODO(), co)
172+
if apierrors.IsNotFound(err) {
173+
err = client.Default().CRClient().Create(context.TODO(), co)
174+
}
175+
g.Expect(err).NotTo(HaveOccurred())
176+
}
177+
178+
func getStatuses(client cnoclient.Client, name string) (*configv1.ClusterOperator, *operv1.Network, error) {
179+
co := &configv1.ClusterOperator{ObjectMeta: metav1.ObjectMeta{Name: name}}
180+
err := client.ClientFor("").CRClient().Get(context.TODO(), types.NamespacedName{Name: name}, co)
181+
if err != nil {
182+
return nil, nil, err
183+
}
184+
oc, err := client.Default().OpenshiftOperatorClient().OperatorV1().Networks().Get(context.TODO(), names.OPERATOR_CONFIG, metav1.GetOptions{})
185+
return co, oc, err
186+
}
187+
188+
// Tests that the parts of newConditions that are set match what's in oldConditions (but
189+
// doesn't look at anything else in oldConditions)
190+
func conditionsInclude(oldConditions, newConditions []configv1.ClusterOperatorStatusCondition) bool {
191+
for _, newCondition := range newConditions {
192+
foundMatchingCondition := false
193+
194+
for _, oldCondition := range oldConditions {
195+
if newCondition.Type != oldCondition.Type || newCondition.Status != oldCondition.Status {
196+
continue
197+
}
198+
if newCondition.Reason != "" && newCondition.Reason != oldCondition.Reason {
199+
return false
200+
}
201+
if newCondition.Message != "" && newCondition.Message != oldCondition.Message {
202+
return false
203+
}
204+
foundMatchingCondition = true
205+
break
206+
}
207+
208+
if !foundMatchingCondition {
209+
return false
210+
}
211+
}
212+
213+
return true
214+
}

0 commit comments

Comments
 (0)