Skip to content

Commit beaa93e

Browse files
authored
Merge pull request #83 from KevFan/resourceRequirements
feat: resource requirements
2 parents c9bda1c + 5554b69 commit beaa93e

16 files changed

+501
-42
lines changed

.github/codecov.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ignore:
2+
- "**/*.deepcopy.go" # ignore controller-gen generated code

api/v1alpha1/limitador_types.go

+25
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/go-logr/logr"
2424
"github.com/google/go-cmp/cmp"
2525
corev1 "k8s.io/api/core/v1"
26+
"k8s.io/apimachinery/pkg/api/resource"
2627
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2728
"k8s.io/apimachinery/pkg/util/intstr"
2829

@@ -37,6 +38,19 @@ const (
3738
StatusConditionReady string = "Ready"
3839
)
3940

41+
var (
42+
defaultResourceRequirements = &corev1.ResourceRequirements{
43+
Requests: corev1.ResourceList{
44+
corev1.ResourceCPU: resource.MustParse("250m"),
45+
corev1.ResourceMemory: resource.MustParse("32Mi"),
46+
},
47+
Limits: corev1.ResourceList{
48+
corev1.ResourceCPU: resource.MustParse("500m"),
49+
corev1.ResourceMemory: resource.MustParse("64Mi"),
50+
},
51+
}
52+
)
53+
4054
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
4155
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
4256

@@ -65,6 +79,9 @@ type LimitadorSpec struct {
6579

6680
// +optional
6781
PodDisruptionBudget *PodDisruptionBudgetType `json:"pdb,omitempty"`
82+
83+
// +optional
84+
ResourceRequirements *corev1.ResourceRequirements `json:"resourceRequirements,omitempty"`
6885
}
6986

7087
//+kubebuilder:object:root=true
@@ -107,6 +124,14 @@ func (l *Limitador) Limits() []RateLimit {
107124
return l.Spec.Limits
108125
}
109126

127+
func (l *Limitador) GetResourceRequirements() *corev1.ResourceRequirements {
128+
if l.Spec.ResourceRequirements == nil {
129+
return defaultResourceRequirements
130+
}
131+
132+
return l.Spec.ResourceRequirements
133+
}
134+
110135
//+kubebuilder:object:root=true
111136

112137
// LimitadorList contains a list of Limitador

api/v1alpha1/limitador_types_test.go

+212
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
package v1alpha1
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
"testing"
7+
8+
"github.com/go-logr/logr"
9+
"gotest.tools/assert"
10+
corev1 "k8s.io/api/core/v1"
11+
"k8s.io/apimachinery/pkg/api/resource"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
)
14+
15+
func TestLimitadorGetResourceRequirements(t *testing.T) {
16+
var resourceRequirements = &corev1.ResourceRequirements{
17+
Requests: corev1.ResourceList{
18+
corev1.ResourceCPU: resource.MustParse("1m"),
19+
corev1.ResourceMemory: resource.MustParse("1Mi"),
20+
},
21+
Limits: corev1.ResourceList{
22+
corev1.ResourceCPU: resource.MustParse("2m"),
23+
corev1.ResourceMemory: resource.MustParse("2Mi"),
24+
},
25+
}
26+
27+
t.Run("test default is returned when not specified in spec", func(subT *testing.T) {
28+
l := &Limitador{Spec: LimitadorSpec{}}
29+
assert.DeepEqual(subT, l.GetResourceRequirements(), defaultResourceRequirements)
30+
})
31+
32+
t.Run("test value in spec is returned when value is not nil", func(subT *testing.T) {
33+
l := &Limitador{Spec: LimitadorSpec{ResourceRequirements: resourceRequirements}}
34+
assert.DeepEqual(subT, l.GetResourceRequirements(), resourceRequirements)
35+
})
36+
}
37+
38+
func TestLimitadorGRPCPort(t *testing.T) {
39+
t.Run("test default is returned if spec listener is nil", func(subT *testing.T) {
40+
l := Limitador{}
41+
assert.Equal(subT, l.GRPCPort(), DefaultServiceGRPCPort)
42+
})
43+
44+
t.Run("test default is returned if spec listener is nil", func(subT *testing.T) {
45+
l := Limitador{Spec: LimitadorSpec{Listener: &Listener{}}}
46+
assert.Equal(subT, l.GRPCPort(), DefaultServiceGRPCPort)
47+
})
48+
49+
t.Run("test default is returned if spec GRPC Port is nil", func(subT *testing.T) {
50+
l := Limitador{Spec: LimitadorSpec{Listener: &Listener{GRPC: &TransportProtocol{}}}}
51+
assert.Equal(subT, l.GRPCPort(), DefaultServiceGRPCPort)
52+
})
53+
54+
t.Run("test value in spec is returned when specified", func(subT *testing.T) {
55+
var port = int32(8080)
56+
l := Limitador{Spec: LimitadorSpec{Listener: &Listener{GRPC: &TransportProtocol{Port: &port}}}}
57+
assert.Equal(subT, l.GRPCPort(), port)
58+
})
59+
}
60+
61+
func TestLimitadorHTTPPort(t *testing.T) {
62+
t.Run("test default is returned if spec listener is nil", func(subT *testing.T) {
63+
l := Limitador{}
64+
assert.Equal(subT, l.HTTPPort(), DefaultServiceHTTPPort)
65+
})
66+
67+
t.Run("test default is returned if spec HTTP is nil", func(subT *testing.T) {
68+
l := Limitador{Spec: LimitadorSpec{Listener: &Listener{}}}
69+
assert.Equal(subT, l.HTTPPort(), DefaultServiceHTTPPort)
70+
})
71+
72+
t.Run("test default is returned if spec HTTP Port is nil", func(subT *testing.T) {
73+
l := Limitador{Spec: LimitadorSpec{Listener: &Listener{HTTP: &TransportProtocol{}}}}
74+
assert.Equal(subT, l.HTTPPort(), DefaultServiceHTTPPort)
75+
})
76+
77+
t.Run("test value in spec is returned when specified", func(subT *testing.T) {
78+
var port = int32(8080)
79+
l := Limitador{Spec: LimitadorSpec{Listener: &Listener{HTTP: &TransportProtocol{Port: &port}}}}
80+
assert.Equal(subT, l.HTTPPort(), port)
81+
})
82+
}
83+
84+
func TestLimitadorLimits(t *testing.T) {
85+
t.Run("test default is returned if limits in spec is nil", func(subT *testing.T) {
86+
l := Limitador{}
87+
assert.DeepEqual(subT, l.Limits(), make([]RateLimit, 0))
88+
})
89+
90+
t.Run("test value in spec is returned if specified", func(subT *testing.T) {
91+
limits := []RateLimit{{Conditions: []string{"test"}}}
92+
l := Limitador{Spec: LimitadorSpec{Limits: limits}}
93+
assert.DeepEqual(subT, l.Limits(), limits)
94+
})
95+
}
96+
97+
func TestStorageSecretRef(t *testing.T) {
98+
t.Run("test redis secret ref is returned if not nil", func(subT *testing.T) {
99+
var redisSecretRef = &corev1.ObjectReference{Name: "redis"}
100+
s := Storage{Redis: &Redis{ConfigSecretRef: redisSecretRef}}
101+
assert.DeepEqual(subT, s.SecretRef(), redisSecretRef)
102+
})
103+
104+
t.Run("test redis cached ref is returned if redis nil", func(subT *testing.T) {
105+
var redisCachedSecretRef = &corev1.ObjectReference{Name: "redisCached"}
106+
s := Storage{RedisCached: &RedisCached{ConfigSecretRef: redisCachedSecretRef}}
107+
assert.DeepEqual(subT, s.SecretRef(), redisCachedSecretRef)
108+
})
109+
}
110+
111+
func TestStorageValidate(t *testing.T) {
112+
t.Run("test false if redis is nil", func(subT *testing.T) {
113+
s := Storage{}
114+
assert.Equal(subT, s.Validate(), false)
115+
})
116+
117+
t.Run("test false if redis secret ref is nil", func(subT *testing.T) {
118+
s := Storage{Redis: &Redis{}}
119+
assert.Equal(subT, s.Validate(), false)
120+
})
121+
122+
t.Run("test true if redis secret ref is not nil", func(subT *testing.T) {
123+
s := Storage{Redis: &Redis{ConfigSecretRef: &corev1.ObjectReference{}}}
124+
assert.Equal(subT, s.Validate(), true)
125+
})
126+
127+
t.Run("test false if redis cached is nil", func(subT *testing.T) {
128+
s := Storage{Redis: &Redis{}}
129+
assert.Equal(subT, s.Validate(), false)
130+
})
131+
132+
t.Run("test false if redis cached secret ref is nil", func(subT *testing.T) {
133+
s := Storage{RedisCached: &RedisCached{}}
134+
assert.Equal(subT, s.Validate(), false)
135+
})
136+
137+
t.Run("test true if redis secret ref is not nil", func(subT *testing.T) {
138+
s := Storage{RedisCached: &RedisCached{ConfigSecretRef: &corev1.ObjectReference{}}}
139+
assert.Equal(subT, s.Validate(), true)
140+
})
141+
}
142+
143+
func TestStorageConfig(t *testing.T) {
144+
const url = "test"
145+
146+
t.Run("test redis storage type returned if redis is not nil", func(subT *testing.T) {
147+
s := Storage{Redis: &Redis{}}
148+
assert.DeepEqual(subT, s.Config(url), []string{string(StorageTypeRedis), url})
149+
})
150+
151+
t.Run("test redis cached storage type returned if redis cached is not nil", func(subT *testing.T) {
152+
s := Storage{RedisCached: &RedisCached{}}
153+
assert.DeepEqual(subT, s.Config(url), []string{string(StorageTypeRedisCached), url})
154+
})
155+
156+
t.Run("test redis cached storage type with options returned", func(subT *testing.T) {
157+
var option = 4040
158+
s := Storage{RedisCached: &RedisCached{Options: &RedisCachedOptions{
159+
TTL: &option,
160+
Ratio: &option,
161+
FlushPeriod: &option,
162+
MaxCached: &option,
163+
}}}
164+
assert.DeepEqual(subT, s.Config(url), []string{string(StorageTypeRedisCached), url, fmt.Sprintf("--ttl %s", strconv.Itoa(option)),
165+
fmt.Sprintf("--ratio %s", strconv.Itoa(option)), fmt.Sprintf("--flush-period %s", strconv.Itoa(option)),
166+
fmt.Sprintf("--max-cached %s", strconv.Itoa(option))})
167+
})
168+
169+
t.Run("test redis cached storage type returned if redis cached is not nil", func(subT *testing.T) {
170+
s := Storage{}
171+
assert.DeepEqual(subT, s.Config(url), []string{string(StorageTypeInMemory)})
172+
})
173+
}
174+
175+
func TestLimitadorStatusEquals(t *testing.T) {
176+
var (
177+
conditions = []metav1.Condition{
178+
{
179+
Type: StatusConditionReady,
180+
},
181+
}
182+
service = &LimitadorService{
183+
Host: "test",
184+
Ports: Ports{},
185+
}
186+
status = &LimitadorStatus{
187+
ObservedGeneration: 0,
188+
Conditions: conditions,
189+
Service: service,
190+
}
191+
)
192+
193+
t.Run("test false if observed generation are different", func(subT *testing.T) {
194+
l := LimitadorStatus{ObservedGeneration: int64(1)}
195+
assert.Equal(subT, l.Equals(status, logr.Logger{}), false)
196+
})
197+
198+
t.Run("test false if condition are different", func(subT *testing.T) {
199+
l := LimitadorStatus{ObservedGeneration: status.ObservedGeneration}
200+
assert.Equal(subT, l.Equals(status, logr.Logger{}), false)
201+
})
202+
203+
t.Run("test false if service are different", func(subT *testing.T) {
204+
l := LimitadorStatus{ObservedGeneration: status.ObservedGeneration, Conditions: status.Conditions}
205+
assert.Equal(subT, l.Equals(status, logr.Logger{}), false)
206+
})
207+
208+
t.Run("test true if status are the same", func(subT *testing.T) {
209+
l := LimitadorStatus{ObservedGeneration: status.ObservedGeneration, Conditions: status.Conditions, Service: status.Service}
210+
assert.Equal(subT, l.Equals(status, logr.Logger{}), true)
211+
})
212+
}

api/v1alpha1/zz_generated.deepcopy.go

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bundle/manifests/limitador.kuadrant.io_limitadors.yaml

+26
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,32 @@ spec:
106106
type: string
107107
replicas:
108108
type: integer
109+
resourceRequirements:
110+
description: ResourceRequirements describes the compute resource requirements.
111+
properties:
112+
limits:
113+
additionalProperties:
114+
anyOf:
115+
- type: integer
116+
- type: string
117+
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
118+
x-kubernetes-int-or-string: true
119+
description: 'Limits describes the maximum amount of compute resources
120+
allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'
121+
type: object
122+
requests:
123+
additionalProperties:
124+
anyOf:
125+
- type: integer
126+
- type: string
127+
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
128+
x-kubernetes-int-or-string: true
129+
description: 'Requests describes the minimum amount of compute
130+
resources required. If Requests is omitted for a container,
131+
it defaults to Limits if that is explicitly specified, otherwise
132+
to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'
133+
type: object
134+
type: object
109135
storage:
110136
description: Storage contains the options for Limitador counters database
111137
or in-memory data storage

config/crd/bases/limitador.kuadrant.io_limitadors.yaml

+26
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,32 @@ spec:
107107
type: string
108108
replicas:
109109
type: integer
110+
resourceRequirements:
111+
description: ResourceRequirements describes the compute resource requirements.
112+
properties:
113+
limits:
114+
additionalProperties:
115+
anyOf:
116+
- type: integer
117+
- type: string
118+
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
119+
x-kubernetes-int-or-string: true
120+
description: 'Limits describes the maximum amount of compute resources
121+
allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'
122+
type: object
123+
requests:
124+
additionalProperties:
125+
anyOf:
126+
- type: integer
127+
- type: string
128+
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
129+
x-kubernetes-int-or-string: true
130+
description: 'Requests describes the minimum amount of compute
131+
resources required. If Requests is omitted for a container,
132+
it defaults to Limits if that is explicitly specified, otherwise
133+
to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'
134+
type: object
135+
type: object
110136
storage:
111137
description: Storage contains the options for Limitador counters database
112138
or in-memory data storage

controllers/limitador_controller.go

+1
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ func (r *LimitadorReconciler) reconcileDeployment(ctx context.Context, limitador
194194
reconcilers.DeploymentContainerListMutator,
195195
reconcilers.DeploymentImageMutator,
196196
reconcilers.DeploymentCommandMutator,
197+
reconcilers.DeploymentResourcesMutator,
197198
)
198199

199200
deployment := limitador.Deployment(limitadorObj, storageConfigSecret)

0 commit comments

Comments
 (0)