Skip to content

Commit

Permalink
overrides: Allow for volume size overrides (PROJQUAY-1090) (#586)
Browse files Browse the repository at this point in the history
- Add override field to Operator CRD to allow for volume size overrides
- Add tests to reflect changes in API
  • Loading branch information
jonathankingfc authored Dec 3, 2021
1 parent ca6d86d commit a1a1550
Show file tree
Hide file tree
Showing 6 changed files with 306 additions and 18 deletions.
77 changes: 68 additions & 9 deletions apis/quay/v1/quayregistry_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

Expand All @@ -38,15 +39,15 @@ type ComponentKind string

const (
ComponentBase ComponentKind = "base"
ComponentPostgres = "postgres"
ComponentClair = "clair"
ComponentRedis = "redis"
ComponentHPA = "horizontalpodautoscaler"
ComponentObjectStorage = "objectstorage"
ComponentRoute = "route"
ComponentMirror = "mirror"
ComponentMonitoring = "monitoring"
ComponentTLS = "tls"
ComponentPostgres ComponentKind = "postgres"
ComponentClair ComponentKind = "clair"
ComponentRedis ComponentKind = "redis"
ComponentHPA ComponentKind = "horizontalpodautoscaler"
ComponentObjectStorage ComponentKind = "objectstorage"
ComponentRoute ComponentKind = "route"
ComponentMirror ComponentKind = "mirror"
ComponentMonitoring ComponentKind = "monitoring"
ComponentTLS ComponentKind = "tls"
)

var allComponents = []ComponentKind{
Expand All @@ -68,6 +69,11 @@ var requiredComponents = []ComponentKind{
ComponentRedis,
}

var supportsVolumeOverride = []ComponentKind{
ComponentPostgres,
ComponentClair,
}

const (
ManagedKeysName = "quay-registry-managed-secret-keys"
QuayConfigTLSSecretName = "quay-config-tls"
Expand All @@ -88,6 +94,13 @@ type Component struct {
// Managed indicates whether or not the Operator is responsible for the lifecycle of this component.
// Default is true.
Managed bool `json:"managed"`
// Overrides holds information regarding component specific configurations.
Overrides *Override `json:"overrides,omitempty"`
}

// Override describes configuration overrides for the given managed component
type Override struct {
VolumeSize *resource.Quantity `json:"volumeSize,omitempty"`
}

type ConditionType string
Expand Down Expand Up @@ -127,6 +140,7 @@ const (
ConditionReasonObjectStorageComponentDependencyError ConditionReason = "ObjectStorageComponentDependencyError"
ConditionReasonMonitoringComponentDependencyError ConditionReason = "MonitoringComponentDependencyError"
ConditionReasonConfigInvalid ConditionReason = "ConfigInvalid"
ConditionReasonComponentOverrideInvalid ConditionReason = "ComponentOverrideInvalid"
)

// Condition is a single condition of a QuayRegistry.
Expand Down Expand Up @@ -330,6 +344,33 @@ func EnsureDefaultComponents(ctx *quaycontext.QuayRegistryContext, quay *QuayReg
return updatedQuay, nil
}

// ValidateOverrides validates that the overrides set for each component are valid.
func ValidateOverrides(quay *QuayRegistry) error {
for _, component := range quay.Spec.Components {

// No overrides provided
if component.Overrides == nil {
continue
}

// If the component is unmanaged, we cannot set overrides
if !ComponentIsManaged(quay.Spec.Components, component.Kind) {
if component.Overrides.VolumeSize != nil {
return errors.New("cannot set overrides on unmanaged component " + string(component.Kind))
}
}

// Check that component supports override
if component.Overrides.VolumeSize != nil && !ComponentSupportsOverride(component.Kind, "volumeSize") {
return fmt.Errorf("component %s does not support volumeSize overrides", component.Kind)
}

}

return nil

}

// EnsureRegistryEndpoint sets the `status.registryEndpoint` field and returns `ok` if it was unchanged.
func EnsureRegistryEndpoint(ctx *quaycontext.QuayRegistryContext, quay *QuayRegistry, config map[string]interface{}) (*QuayRegistry, bool) {
updatedQuay := quay.DeepCopy()
Expand Down Expand Up @@ -493,6 +534,24 @@ func FieldGroupNamesForManagedComponents(quay *QuayRegistry) ([]string, error) {
return fgns, nil
}

// ComponentSupportsOverride returns whether or not a given component supports the given override.
func ComponentSupportsOverride(component ComponentKind, override string) bool {

// Using a switch statement for possible implementation of future overrides
switch override {
case "volumeSize":
for _, c := range supportsVolumeOverride {
if c == component {
return true
}
}
default:
return false
}

return false
}

func init() {
SchemeBuilder.Register(&QuayRegistry{}, &QuayRegistryList{})
}
62 changes: 62 additions & 0 deletions apis/quay/v1/quayregistry_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

quaycontext "github.com/quay/quay-operator/pkg/context"
Expand Down Expand Up @@ -351,6 +352,67 @@ var componentsMatchTests = []struct {
},
}

var validateOverridesTests = []struct {
name string
quay QuayRegistry
expectedErr error
}{
{
"NoOverridesProvided",
QuayRegistry{
Spec: QuayRegistrySpec{
Components: []Component{
{Kind: "postgres", Managed: true},
{Kind: "redis", Managed: true},
{Kind: "clair", Managed: true},
{Kind: "objectstorage", Managed: true},
{Kind: "route", Managed: true},
{Kind: "tls", Managed: true},
{Kind: "horizontalpodautoscaler", Managed: true},
{Kind: "mirror", Managed: true},
{Kind: "monitoring", Managed: true},
},
},
},
nil,
},
{
"InvalidVolumeSizeOverride",
QuayRegistry{
Spec: QuayRegistrySpec{
Components: []Component{
{Kind: "postgres", Managed: true},
{Kind: "redis", Managed: true},
{Kind: "clair", Managed: true},
{Kind: "objectstorage", Managed: true},
{Kind: "route", Managed: true},
{Kind: "tls", Managed: true, Overrides: &Override{VolumeSize: &resource.Quantity{}}},
{Kind: "horizontalpodautoscaler", Managed: true},
{Kind: "mirror", Managed: true},
{Kind: "monitoring", Managed: true},
},
},
},
errors.New("component tls does not support volumeSize overrides"),
},
}

func TestValidOverrides(t *testing.T) {
assert := assert.New(t)

for _, test := range validateOverridesTests {
t.Run(test.name, func(t *testing.T) {
err := ValidateOverrides(&test.quay)
if test.expectedErr != nil {
assert.NotNil(err, test.name)
assert.Equal(test.expectedErr, err)
} else {
assert.Equal(test.expectedErr, err)
}
})
}
}

func TestComponentsMatch(t *testing.T) {
assert := assert.New(t)

Expand Down
10 changes: 10 additions & 0 deletions config/crd/bases/quay.redhat.com_quayregistries.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ spec:
managed:
description: Managed indicates whether or not the Operator is responsible for the lifecycle of this component. Default is true.
type: boolean
overrides:
description: Overrides holds information regarding component specific configurations.
properties:
volumeSize:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
type: object
required:
- kind
- managed
Expand Down
7 changes: 7 additions & 0 deletions controllers/quay/quayregistry_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,13 @@ func (r *QuayRegistryReconciler) Reconcile(ctx context.Context, req ctrl.Request
return ctrl.Result{}, nil
}

err = v1.ValidateOverrides(updatedQuay)
if err != nil {
msg := fmt.Sprintf("could not validate overrides on spec.components: %s", err)

return r.reconcileWithCondition(&quay, v1.ConditionTypeRolloutBlocked, metav1.ConditionTrue, v1.ConditionReasonComponentOverrideInvalid, msg)
}

if !v1.ComponentsMatch(quay.Spec.Components, updatedQuay.Spec.Components) {
log.Info("updating QuayRegistry `spec.components` to include defaults")
if err = r.Client.Update(ctx, updatedQuay); err != nil {
Expand Down
37 changes: 36 additions & 1 deletion pkg/middleware/middleware.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package middleware

import (
"fmt"
"strings"

route "github.com/openshift/api/route/v1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -60,7 +62,7 @@ func Process(ctx *quaycontext.QuayRegistryContext, quay *v1.QuayRegistry, obj cl
}

if !strings.Contains(dep.GetName(), "quay-config-editor") {
return obj, nil
return dep, nil
}

fgns, err := v1.FieldGroupNamesForManagedComponents(quay)
Expand All @@ -72,6 +74,28 @@ func Process(ctx *quaycontext.QuayRegistryContext, quay *v1.QuayRegistry, obj cl
return dep, nil
}

// If the current object is a PVC, check for volume override
if pvc, ok := obj.(*corev1.PersistentVolumeClaim); ok {
var volumeSizeOverride *resource.Quantity
switch quayComponentLabel {
case "postgres":
volumeSizeOverride = getVolumeSizeOverrideForComponent(quay, v1.ComponentPostgres)
case "clair-postgres":
volumeSizeOverride = getVolumeSizeOverrideForComponent(quay, v1.ComponentClair)
}

// If override was provided
if volumeSizeOverride != nil {
// Ensure that volume size is not being reduced
if pvc.Spec.Resources.Requests.Storage() != nil && volumeSizeOverride.Cmp(*pvc.Spec.Resources.Requests.Storage()) == -1 {
return nil, fmt.Errorf("cannot shrink volume override size from %s to %s", pvc.Spec.Resources.Requests.Storage().String(), volumeSizeOverride.String())
}
pvc.Spec.Resources.Requests = corev1.ResourceList{corev1.ResourceStorage: *volumeSizeOverride}
}

return pvc, nil
}

rt, ok := obj.(*route.Route)
if !ok {
return obj, nil
Expand Down Expand Up @@ -137,3 +161,14 @@ func FlattenSecret(configBundle *corev1.Secret) (*corev1.Secret, error) {

return flattenedSecret, nil
}

func getVolumeSizeOverrideForComponent(quay *v1.QuayRegistry, componentKind v1.ComponentKind) (volumeSizeOverride *resource.Quantity) {
for _, component := range quay.Spec.Components {
if component.Kind == componentKind {
if component.Overrides != nil && component.Overrides.VolumeSize != nil {
volumeSizeOverride = component.Overrides.VolumeSize
}
}
}
return volumeSizeOverride
}
Loading

0 comments on commit a1a1550

Please sign in to comment.