Skip to content

Commit

Permalink
Merge pull request #96 from Kuadrant/storage-disk
Browse files Browse the repository at this point in the history
Disk storage
  • Loading branch information
eguzki authored Oct 2, 2023
2 parents d1245e2 + 5e81a02 commit c76af84
Show file tree
Hide file tree
Showing 32 changed files with 2,242 additions and 357 deletions.
13 changes: 11 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ jobs:
with:
go-version: 1.20.x
id: go
- name: Check out code
uses: actions/checkout@v3
- uses: actions/checkout@v3
- name: Run make test-unit
run: |
make test-unit
Expand All @@ -45,6 +44,16 @@ jobs:
id: go
- name: Check out code
uses: actions/checkout@v3
- name: Create k8s Kind Cluster
uses: helm/[email protected]
with:
version: v0.20.0
config: utils/kind-cluster.yaml
cluster_name: limitador-local
wait: 120s
- name: Check cluster info
run: |
kubectl cluster-info dump
- name: Run integration tests
run: |
make test-integration
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ rm -rf $$TMP_DIR ;\
endef

.PHONY: bundle
bundle: $(OPM) $(YQ) manifests kustomize operator-sdk ## Generate bundle manifests and metadata, then validate generated files.
bundle: $(KUSTOMIZE) $(OPERATOR_SDK) $(YQ) manifests ## Generate bundle manifests and metadata, then validate generated files.
$(OPERATOR_SDK) generate kustomize manifests -q
# Set desired operator image and related limitador image
V="$(RELATED_IMAGE_LIMITADOR)" $(YQ) eval '(select(.kind == "Deployment").spec.template.spec.containers[].env[] | select(.name == "RELATED_IMAGE_LIMITADOR").value) = strenv(V)' -i config/manager/manager.yaml
Expand Down
123 changes: 57 additions & 66 deletions api/v1alpha1/limitador_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ limitations under the License.
package v1alpha1

import (
"fmt"
"reflect"

"github.com/go-logr/logr"
Expand Down Expand Up @@ -95,6 +94,8 @@ type Limitador struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

// +kubebuilder:validation:Optional
// +kubebuilder:validation:XValidation:rule="(!has(self.storage) || !has(self.storage.disk)) || (!has(self.replicas) || self.replicas < 2)",message="disk storage does not allow multiple replicas"
Spec LimitadorSpec `json:"spec,omitempty"`
Status LimitadorStatus `json:"status,omitempty"`
}
Expand Down Expand Up @@ -148,64 +149,16 @@ type LimitadorList struct {
// +kubebuilder:validation:Enum=NONE;DRAFT_VERSION_03
type RateLimitHeadersType string

// StorageType defines the valid options for storage
// +kubebuilder:validation:Enum=memory;redis;redis_cached
type StorageType string

const (
StorageTypeInMemory StorageType = "memory"
StorageTypeRedis StorageType = "redis"
StorageTypeRedisCached StorageType = "redis_cached"
)

// Storage contains the options for Limitador counters database or in-memory data storage
type Storage struct {
// +optional
Redis *Redis `json:"redis,omitempty"`

// +optional
RedisCached *RedisCached `json:"redis-cached,omitempty"`
}

func (s *Storage) Validate() bool {
return s.Redis != nil && s.Redis.ConfigSecretRef != nil ||
s.RedisCached != nil && s.RedisCached.ConfigSecretRef != nil
}

func (s *Storage) SecretRef() *corev1.ObjectReference {
if s.Redis != nil {
return s.Redis.ConfigSecretRef
}
return s.RedisCached.ConfigSecretRef
}

func (s *Storage) Config(url string) []string {
if s.Redis != nil {
return []string{string(StorageTypeRedis), url}
}

if s.RedisCached != nil {
params := []string{string(StorageTypeRedisCached), url}

if s.RedisCached.Options != nil {
options := reflect.ValueOf(*s.RedisCached.Options)
typesOf := options.Type()
for i := 0; i < options.NumField(); i++ {
if !options.Field(i).IsNil() {
var value interface{} = options.Field(i).Elem()
params = append(
params,
fmt.Sprintf(
"--%s %d",
helpers.ToKebabCase(typesOf.Field(i).Name),
value))
}
}
}
return params
}

return []string{string(StorageTypeInMemory)}
// +optional
Disk *DiskSpec `json:"disk,omitempty"`
}

type Redis struct {
Expand Down Expand Up @@ -241,6 +194,44 @@ type RedisCached struct {
Options *RedisCachedOptions `json:"options,omitempty"`
}

// PersistentVolumeClaimResources defines the resources configuration
// of the backup data destination PersistentVolumeClaim
type PersistentVolumeClaimResources struct {
// Storage Resource requests to be used on the PersistentVolumeClaim.
// To learn more about resource requests see:
// https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
Requests resource.Quantity `json:"requests"` // Should this be a string or a resoure.Quantity? it seems it is serialized as a string
}

type PVCGenericSpec struct {
// +optional
StorageClassName *string `json:"storageClassName,omitempty"`
// Resources represents the minimum resources the volume should have.
// Ignored when VolumeName field is set
// +optional
Resources *PersistentVolumeClaimResources `json:"resources,omitempty"`
// VolumeName is the binding reference to the PersistentVolume backing this claim.
// +optional
VolumeName *string `json:"volumeName,omitempty"`
}

// DiskOptimizeType defines the valid options for "optimize" option of the disk persistence type
// +kubebuilder:validation:Enum=throughput;disk
type DiskOptimizeType string

const (
DiskOptimizeTypeThroughput DiskOptimizeType = "throughput"
DiskOptimizeTypeDisk DiskOptimizeType = "disk"
)

type DiskSpec struct {
// +optional
PVC *PVCGenericSpec `json:"persistentVolumeClaim,omitempty"`

// +optional
Optimize *DiskOptimizeType `json:"optimize,omitempty"`
}

type Listener struct {
// +optional
HTTP *TransportProtocol `json:"http,omitempty"`
Expand Down Expand Up @@ -292,6 +283,21 @@ type Ports struct {
GRPC int32 `json:"grpc,omitempty"`
}

type PodDisruptionBudgetType struct {
// An eviction is allowed if at most "maxUnavailable" limitador pods
// are unavailable after the eviction, i.e. even in absence of
// the evicted pod. For example, one can prevent all voluntary evictions
// by specifying 0. This is a mutually exclusive setting with "minAvailable".
// +optional
MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"`
// An eviction is allowed if at least "minAvailable" limitador pods will
// still be available after the eviction, i.e. even in the absence of
// the evicted pod. So for example you can prevent all voluntary
// evictions by specifying "100%".
// +optional
MinAvailable *intstr.IntOrString `json:"minAvailable,omitempty"`
}

func (s *LimitadorStatus) Equals(other *LimitadorStatus, logger logr.Logger) bool {
if s.ObservedGeneration != other.ObservedGeneration {
diff := cmp.Diff(s.ObservedGeneration, other.ObservedGeneration)
Expand Down Expand Up @@ -320,18 +326,3 @@ func (s *LimitadorStatus) Equals(other *LimitadorStatus, logger logr.Logger) boo
func init() {
SchemeBuilder.Register(&Limitador{}, &LimitadorList{})
}

type PodDisruptionBudgetType struct {
// An eviction is allowed if at most "maxUnavailable" limitador pods
// are unavailable after the eviction, i.e. even in absence of
// the evicted pod. For example, one can prevent all voluntary evictions
// by specifying 0. This is a mutually exclusive setting with "minAvailable".
// +optional
MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"`
// An eviction is allowed if at least "minAvailable" limitador pods will
// still be available after the eviction, i.e. even in the absence of
// the evicted pod. So for example you can prevent all voluntary
// evictions by specifying "100%".
// +optional
MinAvailable *intstr.IntOrString `json:"minAvailable,omitempty"`
}
80 changes: 0 additions & 80 deletions api/v1alpha1/limitador_types_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package v1alpha1

import (
"fmt"
"strconv"
"testing"

"github.com/go-logr/logr"
Expand Down Expand Up @@ -94,84 +92,6 @@ func TestLimitadorLimits(t *testing.T) {
})
}

func TestStorageSecretRef(t *testing.T) {
t.Run("test redis secret ref is returned if not nil", func(subT *testing.T) {
var redisSecretRef = &corev1.ObjectReference{Name: "redis"}
s := Storage{Redis: &Redis{ConfigSecretRef: redisSecretRef}}
assert.DeepEqual(subT, s.SecretRef(), redisSecretRef)
})

t.Run("test redis cached ref is returned if redis nil", func(subT *testing.T) {
var redisCachedSecretRef = &corev1.ObjectReference{Name: "redisCached"}
s := Storage{RedisCached: &RedisCached{ConfigSecretRef: redisCachedSecretRef}}
assert.DeepEqual(subT, s.SecretRef(), redisCachedSecretRef)
})
}

func TestStorageValidate(t *testing.T) {
t.Run("test false if redis is nil", func(subT *testing.T) {
s := Storage{}
assert.Equal(subT, s.Validate(), false)
})

t.Run("test false if redis secret ref is nil", func(subT *testing.T) {
s := Storage{Redis: &Redis{}}
assert.Equal(subT, s.Validate(), false)
})

t.Run("test true if redis secret ref is not nil", func(subT *testing.T) {
s := Storage{Redis: &Redis{ConfigSecretRef: &corev1.ObjectReference{}}}
assert.Equal(subT, s.Validate(), true)
})

t.Run("test false if redis cached is nil", func(subT *testing.T) {
s := Storage{Redis: &Redis{}}
assert.Equal(subT, s.Validate(), false)
})

t.Run("test false if redis cached secret ref is nil", func(subT *testing.T) {
s := Storage{RedisCached: &RedisCached{}}
assert.Equal(subT, s.Validate(), false)
})

t.Run("test true if redis secret ref is not nil", func(subT *testing.T) {
s := Storage{RedisCached: &RedisCached{ConfigSecretRef: &corev1.ObjectReference{}}}
assert.Equal(subT, s.Validate(), true)
})
}

func TestStorageConfig(t *testing.T) {
const url = "test"

t.Run("test redis storage type returned if redis is not nil", func(subT *testing.T) {
s := Storage{Redis: &Redis{}}
assert.DeepEqual(subT, s.Config(url), []string{string(StorageTypeRedis), url})
})

t.Run("test redis cached storage type returned if redis cached is not nil", func(subT *testing.T) {
s := Storage{RedisCached: &RedisCached{}}
assert.DeepEqual(subT, s.Config(url), []string{string(StorageTypeRedisCached), url})
})

t.Run("test redis cached storage type with options returned", func(subT *testing.T) {
var option = 4040
s := Storage{RedisCached: &RedisCached{Options: &RedisCachedOptions{
TTL: &option,
Ratio: &option,
FlushPeriod: &option,
MaxCached: &option,
}}}
assert.DeepEqual(subT, s.Config(url), []string{string(StorageTypeRedisCached), url, fmt.Sprintf("--ttl %s", strconv.Itoa(option)),
fmt.Sprintf("--ratio %s", strconv.Itoa(option)), fmt.Sprintf("--flush-period %s", strconv.Itoa(option)),
fmt.Sprintf("--max-cached %s", strconv.Itoa(option))})
})

t.Run("test redis cached storage type returned if redis cached is not nil", func(subT *testing.T) {
s := Storage{}
assert.DeepEqual(subT, s.Config(url), []string{string(StorageTypeInMemory)})
})
}

func TestLimitadorStatusEquals(t *testing.T) {
var (
conditions = []metav1.Condition{
Expand Down
76 changes: 76 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

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

Loading

0 comments on commit c76af84

Please sign in to comment.