Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First version of the RateLimit reconciler #6

Merged
merged 7 commits into from
Jan 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions api/v1alpha1/ratelimit_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ type RateLimitSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file

// Foo is an example field of RateLimit. Edit RateLimit_types.go to remove/update
Foo string `json:"foo,omitempty"`
Conditions []string `json:"conditions"`
MaxValue int `json:"max_value"`
Namespace string `json:"namespace"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need Namespace? Is this is intended to "bind" this rate limit to some Limitador service in another namespace? In that case, I suggest having URL.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All k8s objects have a meta property namespace you can use to bind one rate limit object to the limitador service of the same namespace. JFYI.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming is confusing, sorry. A limitador namespace doesn't need to be a kubernetes namespace. Limitador also runs outside kubernetes. A limitador namespace is the "context" on which a limit applies, it could be a service, a product, a proxy, a kubernetes namespace or any other thing really. It's meant to be generic to support multiple use cases.

In a future version of Limitador we might want to rename this field so it's less confusing for users running it in Kubernetes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I see, it is an internal namespace.

Seconds int `json:"seconds"`
Variables []string `json:"variables"`
}

// RateLimitStatus defines the observed state of RateLimit
Expand Down
12 changes: 11 additions & 1 deletion api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 19 additions & 3 deletions config/crd/bases/limitador.3scale.net_ratelimits.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,26 @@ spec:
spec:
description: RateLimitSpec defines the desired state of RateLimit
properties:
foo:
description: Foo is an example field of RateLimit. Edit RateLimit_types.go
to remove/update
conditions:
items:
type: string
type: array
max_value:
type: integer
namespace:
type: string
seconds:
type: integer
variables:
items:
type: string
type: array
required:
- conditions
- max_value
- namespace
- seconds
- variables
type: object
status:
description: RateLimitStatus defines the observed state of RateLimit
Expand Down
9 changes: 7 additions & 2 deletions config/samples/limitador_v1alpha1_ratelimit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,10 @@ kind: RateLimit
metadata:
name: ratelimit-sample
spec:
# Add fields here
foo: bar
namespace: test_namespace
max_value: 10
seconds: 60
conditions:
- "req.method == GET"
variables:
- user_id
179 changes: 179 additions & 0 deletions controllers/limitador_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package controllers

import (
"context"
limitadorv1alpha1 "github.com/3scale/limitador-operator/api/v1alpha1"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"time"
)

var _ = Describe("Limitador controller", func() {
const (
LimitadorName = "limitador-test"
LimitadorNamespace = "default"
LimitadorReplicas = 2
LimitadorImage = "quay.io/3scale/limitador"
LimitadorVersion = "0.3.0"

timeout = time.Second * 10
interval = time.Millisecond * 250
)

replicas := LimitadorReplicas
version := LimitadorVersion
limitador := limitadorv1alpha1.Limitador{
TypeMeta: metav1.TypeMeta{
Kind: "Limitador",
APIVersion: "limitador.3scale.net/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: LimitadorName,
Namespace: LimitadorNamespace,
},
Spec: limitadorv1alpha1.LimitadorSpec{
Replicas: &replicas,
Version: &version,
},
}

Context("Creating a new Limitador object", func() {
BeforeEach(func() {
err := k8sClient.Delete(context.TODO(), limitador.DeepCopy())
Expect(err == nil || errors.IsNotFound(err))

Expect(k8sClient.Create(context.TODO(), limitador.DeepCopy())).Should(Succeed())
})

It("Should create a new deployment with the right number of replicas and version", func() {
createdLimitadorDeployment := appsv1.Deployment{}
Eventually(func() bool {
err := k8sClient.Get(
context.TODO(),
types.NamespacedName{
Namespace: LimitadorNamespace,
Name: LimitadorName,
},
&createdLimitadorDeployment)

return err == nil
}, timeout, interval).Should(BeTrue())

Expect(*createdLimitadorDeployment.Spec.Replicas).Should(
Equal((int32)(LimitadorReplicas)),
)
Expect(createdLimitadorDeployment.Spec.Template.Spec.Containers[0].Image).Should(
Equal(LimitadorImage + ":" + LimitadorVersion),
)
})

It("Should create a Limitador service", func() {
createdLimitadorService := v1.Service{}
Eventually(func() bool {
err := k8sClient.Get(
context.TODO(),
types.NamespacedName{
Namespace: "default", // Hardcoded for now
Name: "limitador", // Hardcoded for now
},
&createdLimitadorService)

return err == nil
}, timeout, interval).Should(BeTrue())
})
})

Context("Deleting a Limitador object", func() {
BeforeEach(func() {
err := k8sClient.Create(context.TODO(), limitador.DeepCopy())
Expect(err == nil || errors.IsAlreadyExists(err))

Expect(k8sClient.Delete(context.TODO(), limitador.DeepCopy())).Should(Succeed())
})

It("Should delete the limitador deployment", func() {
createdLimitadorDeployment := appsv1.Deployment{}
Eventually(func() bool {
err := k8sClient.Get(
context.TODO(),
types.NamespacedName{
Namespace: LimitadorNamespace,
Name: LimitadorName,
},
&createdLimitadorDeployment)

return errors.IsNotFound(err)
}, timeout, interval).Should(BeTrue())
})

It("Should delete the limitador service", func() {
createdLimitadorService := v1.Service{}
Eventually(func() bool {
err := k8sClient.Get(
context.TODO(),
types.NamespacedName{
Namespace: "default", // Hardcoded for now
Name: "limitador", // Hardcoded for now
},
&createdLimitadorService)

return errors.IsNotFound(err)
}, timeout, interval).Should(BeTrue())
})
})

Context("Updating a limitador object", func() {
BeforeEach(func() {
err := k8sClient.Delete(context.TODO(), limitador.DeepCopy())
Expect(err == nil || errors.IsNotFound(err))

Expect(k8sClient.Create(context.TODO(), limitador.DeepCopy())).Should(Succeed())
})

It("Should modify the limitador deployment", func() {
updatedLimitador := limitadorv1alpha1.Limitador{}
Eventually(func() bool {
err := k8sClient.Get(
context.TODO(),
types.NamespacedName{
Namespace: LimitadorNamespace,
Name: LimitadorName,
},
&updatedLimitador)

return err == nil
}, timeout, interval).Should(BeTrue())

replicas = LimitadorReplicas + 1
updatedLimitador.Spec.Replicas = &replicas
version = "latest"
updatedLimitador.Spec.Version = &version

Expect(k8sClient.Update(context.TODO(), &updatedLimitador)).Should(Succeed())
updatedLimitadorDeployment := appsv1.Deployment{}
Eventually(func() bool {
err := k8sClient.Get(
context.TODO(),
types.NamespacedName{
Namespace: LimitadorNamespace,
Name: LimitadorName,
},
&updatedLimitadorDeployment)

if err != nil {
return false
}

correctReplicas := *updatedLimitadorDeployment.Spec.Replicas == LimitadorReplicas+1
correctImage := updatedLimitadorDeployment.Spec.Template.Spec.Containers[0].Image == LimitadorImage+":latest"

return correctReplicas && correctImage
}, timeout, interval).Should(BeTrue())
})
})
})
Loading