Skip to content

Commit f89dbea

Browse files
authored
Cherrypick periodic reconciliation to rel 1.2 (#152)
* Run reconcilation periodically (#147) * Requeue after ENV * Don't requeue configMap * Reconcile every time * Requeue faster after error * After fix * Add nil check * Don't run reconcilation again after delete * Move code * Print delete timestamp * Negate deletion timestamp * Print delete timestamp * Print delete timestamp * Don't requeue on delettion * Validate on handler change * Go fmt revert * Add the problems * Add the problems * Switch cases * Unit tests * Add reconciliation duration parameter and periodic reconciliation test (#151) * Update apirule_controller.go * Update apirule_controller.go * Update apirule_controller.go * Reconcilation period setup * Add test for reconcilation period * Actually configure from params * Fix typo
1 parent e391242 commit f89dbea

File tree

10 files changed

+625
-31
lines changed

10 files changed

+625
-31
lines changed
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
package reconciliation_test
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"math/rand"
7+
"time"
8+
9+
"github.com/kyma-incubator/api-gateway/internal/helpers"
10+
istioint "github.com/kyma-incubator/api-gateway/internal/types/istio"
11+
12+
"encoding/json"
13+
14+
gatewayv1beta1 "github.com/kyma-incubator/api-gateway/api/v1beta1"
15+
. "github.com/onsi/ginkgo"
16+
. "github.com/onsi/gomega"
17+
corev1 "k8s.io/api/core/v1"
18+
apierrors "k8s.io/apimachinery/pkg/api/errors"
19+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
20+
"k8s.io/apimachinery/pkg/runtime"
21+
"k8s.io/apimachinery/pkg/types"
22+
"sigs.k8s.io/controller-runtime/pkg/client"
23+
"sigs.k8s.io/controller-runtime/pkg/controller"
24+
"sigs.k8s.io/controller-runtime/pkg/handler"
25+
"sigs.k8s.io/controller-runtime/pkg/manager"
26+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
27+
"sigs.k8s.io/controller-runtime/pkg/source"
28+
)
29+
30+
const (
31+
timeout = time.Second * 10
32+
33+
kind = "APIRule"
34+
testGatewayURL = "kyma-system/kyma-gateway"
35+
testOathkeeperSvcURL = "oathkeeper.kyma-system.svc.cluster.local"
36+
testOathkeeperPort uint32 = 1234
37+
testNamespace = "atgo-system"
38+
testNameBase = "test"
39+
testIDLength = 5
40+
)
41+
42+
var _ = Describe("APIRule Controller Reconciliation", func() {
43+
const testServiceName = "httpbin"
44+
const testServicePort uint32 = 443
45+
var testIssuer = "https://oauth2.example.com/"
46+
47+
BeforeEach(func() {
48+
// We configure `ory` in ConfigMap as the default for all tests
49+
cm := testConfigMap(helpers.JWT_HANDLER_ORY)
50+
err := c.Update(context.TODO(), cm)
51+
if apierrors.IsInvalid(err) {
52+
Fail(fmt.Sprintf("failed to update configmap, got an invalid object error: %v", err))
53+
}
54+
Expect(err).NotTo(HaveOccurred())
55+
})
56+
57+
It("Should update the apirule with error after the ConfigMap was changed to different one", func() {
58+
Context("From Ory to Istio", func() {
59+
// given
60+
apiRuleName := generateTestName(testNameBase, testIDLength)
61+
testServiceHost := fmt.Sprintf("httpbin-%s.kyma.local", apiRuleName)
62+
63+
rule := testRule("/img", []string{"GET"}, nil, testOryJWTHandler(testIssuer, []string{"test-scope"}))
64+
apiRule := testInstance(apiRuleName, testNamespace, testServiceName, testServiceHost, testServicePort, []gatewayv1beta1.Rule{rule})
65+
66+
By("Create ApiRule with Rule using JWT handler")
67+
err := c.Create(context.TODO(), apiRule)
68+
Expect(err).NotTo(HaveOccurred())
69+
defer func() {
70+
err := c.Delete(context.TODO(), apiRule)
71+
Expect(err).NotTo(HaveOccurred())
72+
}()
73+
74+
initialStateReq := reconcile.Request{NamespacedName: types.NamespacedName{Name: apiRuleName, Namespace: testNamespace}}
75+
Eventually(requests, timeout).Should(Receive(Equal(initialStateReq)))
76+
77+
// when
78+
By("Setting JWT handler config map to istio")
79+
cm := testConfigMap("istio")
80+
err = c.Update(context.TODO(), cm)
81+
Expect(err).NotTo(HaveOccurred())
82+
83+
cmRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: cm.Name, Namespace: cm.Namespace}}
84+
Eventually(requests, timeout).Should(Receive(Equal(cmRequest)))
85+
86+
updateApiRuleReq := reconcile.Request{NamespacedName: types.NamespacedName{Name: apiRuleName, Namespace: testNamespace}}
87+
Eventually(requests, timeout).Should(Receive(Equal(updateApiRuleReq)))
88+
89+
// then
90+
91+
expectedApiRule := gatewayv1beta1.APIRule{}
92+
err = c.Get(context.TODO(), client.ObjectKey{Name: apiRuleName, Namespace: testNamespace}, &expectedApiRule)
93+
Expect(err).NotTo(HaveOccurred())
94+
95+
Expect(expectedApiRule.Status.APIRuleStatus.Code).To(Equal(gatewayv1beta1.StatusError))
96+
})
97+
98+
Context("From Istio to Ory", func() {
99+
// given
100+
By("Setting JWT handler config map to istio")
101+
cm := testConfigMap("istio")
102+
err := c.Update(context.TODO(), cm)
103+
Expect(err).NotTo(HaveOccurred())
104+
105+
apiRuleName := generateTestName(testNameBase, testIDLength)
106+
testServiceHost := fmt.Sprintf("httpbin-%s.kyma.local", apiRuleName)
107+
108+
rule := testRule("/img", []string{"GET"}, nil, testIstioJWTHandler(testIssuer, "https://example.com/well-known/.jwks"))
109+
apiRule := testInstance(apiRuleName, testNamespace, testServiceName, testServiceHost, testServicePort, []gatewayv1beta1.Rule{rule})
110+
111+
By("Create ApiRule with Rule using JWT handler")
112+
err = c.Create(context.TODO(), apiRule)
113+
Expect(err).NotTo(HaveOccurred())
114+
defer func() {
115+
err := c.Delete(context.TODO(), apiRule)
116+
Expect(err).NotTo(HaveOccurred())
117+
}()
118+
119+
initialStateReq := reconcile.Request{NamespacedName: types.NamespacedName{Name: apiRuleName, Namespace: testNamespace}}
120+
Eventually(requests, timeout).Should(Receive(Equal(initialStateReq)))
121+
122+
// when
123+
By("Setting JWT handler config map to ory")
124+
cm = testConfigMap("ory")
125+
err = c.Update(context.TODO(), cm)
126+
Expect(err).NotTo(HaveOccurred())
127+
128+
cmRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: cm.Name, Namespace: cm.Namespace}}
129+
Eventually(requests, timeout).Should(Receive(Equal(cmRequest)))
130+
131+
updateApiRuleReq := reconcile.Request{NamespacedName: types.NamespacedName{Name: apiRuleName, Namespace: testNamespace}}
132+
Eventually(requests, timeout).Should(Receive(Equal(updateApiRuleReq)))
133+
134+
// then
135+
136+
expectedApiRule := gatewayv1beta1.APIRule{}
137+
err = c.Get(context.TODO(), client.ObjectKey{Name: apiRuleName, Namespace: testNamespace}, &expectedApiRule)
138+
Expect(err).NotTo(HaveOccurred())
139+
140+
Expect(expectedApiRule.Status.APIRuleStatus.Code).To(Equal(gatewayv1beta1.StatusError))
141+
})
142+
})
143+
})
144+
145+
func add(mgr manager.Manager, r reconcile.Reconciler) error {
146+
c, err := controller.New("api-gateway-controller", mgr, controller.Options{Reconciler: r})
147+
if err != nil {
148+
return err
149+
}
150+
151+
err = c.Watch(&source.Kind{Type: &gatewayv1beta1.APIRule{}}, &handler.EnqueueRequestForObject{})
152+
if err != nil {
153+
return err
154+
}
155+
156+
err = c.Watch(&source.Kind{Type: &corev1.ConfigMap{}}, &handler.EnqueueRequestForObject{})
157+
if err != nil {
158+
return err
159+
}
160+
161+
return nil
162+
}
163+
164+
func toCSVList(input []string) string {
165+
if len(input) == 0 {
166+
return ""
167+
}
168+
169+
res := `"` + input[0] + `"`
170+
171+
for i := 1; i < len(input); i++ {
172+
res = res + "," + `"` + input[i] + `"`
173+
}
174+
175+
return res
176+
}
177+
178+
func testRule(path string, methods []string, mutators []*gatewayv1beta1.Mutator, handler *gatewayv1beta1.Handler) gatewayv1beta1.Rule {
179+
return gatewayv1beta1.Rule{
180+
Path: path,
181+
Methods: methods,
182+
Mutators: mutators,
183+
AccessStrategies: []*gatewayv1beta1.Authenticator{
184+
{
185+
Handler: handler,
186+
},
187+
},
188+
}
189+
}
190+
191+
func testInstance(name, namespace, serviceName, serviceHost string, servicePort uint32, rules []gatewayv1beta1.Rule) *gatewayv1beta1.APIRule {
192+
var gateway = testGatewayURL
193+
194+
return &gatewayv1beta1.APIRule{
195+
ObjectMeta: metav1.ObjectMeta{
196+
Name: name,
197+
Namespace: namespace,
198+
},
199+
Spec: gatewayv1beta1.APIRuleSpec{
200+
Host: &serviceHost,
201+
Gateway: &gateway,
202+
Service: &gatewayv1beta1.Service{
203+
Name: &serviceName,
204+
Port: &servicePort,
205+
},
206+
Rules: rules,
207+
},
208+
}
209+
}
210+
211+
func testConfigMap(jwtHandler string) *corev1.ConfigMap {
212+
return &corev1.ConfigMap{
213+
ObjectMeta: metav1.ObjectMeta{
214+
Name: helpers.CM_NAME,
215+
Namespace: helpers.CM_NS,
216+
},
217+
Data: map[string]string{
218+
helpers.CM_KEY: fmt.Sprintf("jwtHandler: %s", jwtHandler),
219+
},
220+
}
221+
}
222+
223+
func testOryJWTHandler(issuer string, scopes []string) *gatewayv1beta1.Handler {
224+
225+
configJSON := fmt.Sprintf(`{
226+
"trusted_issuers": ["%s"],
227+
"jwks": [],
228+
"required_scope": [%s]
229+
}`, issuer, toCSVList(scopes))
230+
231+
return &gatewayv1beta1.Handler{
232+
Name: "jwt",
233+
Config: &runtime.RawExtension{
234+
Raw: []byte(configJSON),
235+
},
236+
}
237+
}
238+
239+
func testIstioJWTHandler(issuer string, jwksUri string) *gatewayv1beta1.Handler {
240+
bytes, err := json.Marshal(istioint.JwtConfig{
241+
Authentications: []istioint.JwtAuth{
242+
{
243+
Issuer: issuer,
244+
JwksUri: jwksUri,
245+
},
246+
},
247+
})
248+
Expect(err).To(BeNil())
249+
return &gatewayv1beta1.Handler{
250+
Name: "jwt",
251+
Config: &runtime.RawExtension{
252+
Raw: bytes,
253+
},
254+
}
255+
}
256+
257+
func generateTestName(name string, length int) string {
258+
259+
rand.Seed(time.Now().UnixNano())
260+
261+
letterRunes := []rune("abcdefghijklmnopqrstuvwxyz")
262+
263+
b := make([]rune, length)
264+
for i := range b {
265+
b[i] = letterRunes[rand.Intn(len(letterRunes))]
266+
}
267+
return name + "-" + string(b)
268+
}

0 commit comments

Comments
 (0)