refactor GenerateResourceRequirements and provide unit tests (#1822)

* refactor GenerateResourceRequirements and provide unit tests
This commit is contained in:
Felix Kunde
2022-03-24 17:35:00 +01:00
committed by GitHub
parent 38db48c7f0
commit 36df1bc87c
13 changed files with 467 additions and 218 deletions
-57
View File
@@ -256,10 +256,6 @@ func (c *Cluster) Create() error {
c.KubeClient.SetPostgresCRDStatus(c.clusterName(), acidv1.ClusterStatusCreating)
c.eventRecorder.Event(c.GetReference(), v1.EventTypeNormal, "Create", "Started creation of new cluster resources")
if err = c.enforceMinResourceLimits(&c.Spec); err != nil {
return fmt.Errorf("could not enforce minimum resource limits: %v", err)
}
for _, role := range []PostgresRole{Master, Replica} {
if c.Endpoints[role] != nil {
@@ -676,50 +672,6 @@ func comparePorts(a, b []v1.ContainerPort) bool {
return true
}
func (c *Cluster) enforceMinResourceLimits(spec *acidv1.PostgresSpec) error {
var (
isSmaller bool
err error
)
if spec.Resources == nil {
return nil
}
// setting limits too low can cause unnecessary evictions / OOM kills
minCPULimit := c.OpConfig.MinCPULimit
minMemoryLimit := c.OpConfig.MinMemoryLimit
cpuLimit := spec.Resources.ResourceLimits.CPU
if cpuLimit != "" {
isSmaller, err = util.IsSmallerQuantity(cpuLimit, minCPULimit)
if err != nil {
return fmt.Errorf("could not compare defined CPU limit %s with configured minimum value %s: %v", cpuLimit, minCPULimit, err)
}
if isSmaller {
c.logger.Warningf("defined CPU limit %s is below required minimum %s and will be increased", cpuLimit, minCPULimit)
c.eventRecorder.Eventf(c.GetReference(), v1.EventTypeWarning, "ResourceLimits", "defined CPU limit %s is below required minimum %s and will be set to it", cpuLimit, minCPULimit)
spec.Resources.ResourceLimits.CPU = minCPULimit
}
}
memoryLimit := spec.Resources.ResourceLimits.Memory
if memoryLimit != "" {
isSmaller, err = util.IsSmallerQuantity(memoryLimit, minMemoryLimit)
if err != nil {
return fmt.Errorf("could not compare defined memory limit %s with configured minimum value %s: %v", memoryLimit, minMemoryLimit, err)
}
if isSmaller {
c.logger.Warningf("defined memory limit %s is below required minimum %s and will be increased", memoryLimit, minMemoryLimit)
c.eventRecorder.Eventf(c.GetReference(), v1.EventTypeWarning, "ResourceLimits", "defined memory limit %s is below required minimum %s and will be set to it", memoryLimit, minMemoryLimit)
spec.Resources.ResourceLimits.Memory = minMemoryLimit
}
}
return nil
}
// Update changes Kubernetes objects according to the new specification. Unlike the sync case, the missing object
// (i.e. service) is treated as an error
// logical backup cron jobs are an exception: a user-initiated Update can enable a logical backup job
@@ -799,12 +751,6 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
// Statefulset
func() {
if err := c.enforceMinResourceLimits(&c.Spec); err != nil {
c.logger.Errorf("could not sync resources: %v", err)
updateFailed = true
return
}
oldSs, err := c.generateStatefulSet(&oldSpec.Spec)
if err != nil {
c.logger.Errorf("could not generate old statefulset spec: %v", err)
@@ -812,9 +758,6 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
return
}
// update newSpec to for latter comparison with oldSpec
c.enforceMinResourceLimits(&newSpec.Spec)
newSs, err := c.generateStatefulSet(&newSpec.Spec)
if err != nil {
c.logger.Errorf("could not generate new statefulset spec: %v", err)
+6 -4
View File
@@ -210,9 +210,10 @@ func (c *Cluster) generateConnectionPoolerPodTemplate(role PostgresRole) (
connectionPoolerSpec = &acidv1.ConnectionPooler{}
}
gracePeriod := int64(c.OpConfig.PodTerminateGracePeriod.Seconds())
resources, err := generateResourceRequirements(
resources, err := c.generateResourceRequirements(
connectionPoolerSpec.Resources,
makeDefaultConnectionPoolerResources(&c.OpConfig))
makeDefaultConnectionPoolerResources(&c.OpConfig),
connectionPoolerContainer)
effectiveDockerImage := util.Coalesce(
connectionPoolerSpec.DockerImage,
@@ -627,8 +628,9 @@ func (c *Cluster) needSyncConnectionPoolerDefaults(Config *Config, spec *acidv1.
reasons = append(reasons, msg)
}
expectedResources, err := generateResourceRequirements(spec.Resources,
makeDefaultConnectionPoolerResources(&Config.OpConfig))
expectedResources, err := c.generateResourceRequirements(spec.Resources,
makeDefaultConnectionPoolerResources(&Config.OpConfig),
connectionPoolerContainer)
// An error to generate expected resources means something is not quite
// right, but for the purpose of robustness do not panic here, just report
+109 -94
View File
@@ -37,6 +37,8 @@ const (
patroniPGBinariesParameterName = "bin_dir"
patroniPGHBAConfParameterName = "pg_hba"
localHost = "127.0.0.1/32"
scalyrSidecarName = "scalyr-sidecar"
logicalBackupContainerName = "logical-backup"
connectionPoolerContainer = "connection-pooler"
pgPort = 5432
)
@@ -117,9 +119,7 @@ func (c *Cluster) podDisruptionBudgetName() string {
return c.OpConfig.PDBNameFormat.Format("cluster", c.Name)
}
func (c *Cluster) makeDefaultResources() acidv1.Resources {
config := c.OpConfig
func makeDefaultResources(config *config.Config) acidv1.Resources {
defaultRequests := acidv1.ResourceDescription{
CPU: config.Resources.DefaultCPURequest,
@@ -136,32 +136,61 @@ func (c *Cluster) makeDefaultResources() acidv1.Resources {
}
}
func generateResourceRequirements(resources *acidv1.Resources, defaultResources acidv1.Resources) (*v1.ResourceRequirements, error) {
var err error
func (c *Cluster) enforceMinResourceLimits(resources *v1.ResourceRequirements) error {
var (
isSmaller bool
err error
msg string
)
var specRequests, specLimits acidv1.ResourceDescription
if resources == nil {
specRequests = acidv1.ResourceDescription{}
specLimits = acidv1.ResourceDescription{}
} else {
specRequests = resources.ResourceRequests
specLimits = resources.ResourceLimits
// setting limits too low can cause unnecessary evictions / OOM kills
cpuLimit := resources.Limits[v1.ResourceCPU]
minCPULimit := c.OpConfig.MinCPULimit
if minCPULimit != "" {
isSmaller, err = util.IsSmallerQuantity(cpuLimit.String(), minCPULimit)
if err != nil {
return fmt.Errorf("could not compare defined CPU limit %s for %q container with configured minimum value %s: %v",
cpuLimit.String(), constants.PostgresContainerName, minCPULimit, err)
}
if isSmaller {
msg = fmt.Sprintf("defined CPU limit %s for %q container is below required minimum %s and will be increased",
cpuLimit.String(), constants.PostgresContainerName, minCPULimit)
c.logger.Warningf(msg)
c.eventRecorder.Eventf(c.GetReference(), v1.EventTypeWarning, "ResourceLimits", msg)
resources.Limits[v1.ResourceCPU], _ = resource.ParseQuantity(minCPULimit)
}
}
result := v1.ResourceRequirements{}
result.Requests, err = fillResourceList(specRequests, defaultResources.ResourceRequests)
if err != nil {
return nil, fmt.Errorf("could not fill resource requests: %v", err)
memoryLimit := resources.Limits[v1.ResourceMemory]
minMemoryLimit := c.OpConfig.MinMemoryLimit
if minMemoryLimit != "" {
isSmaller, err = util.IsSmallerQuantity(memoryLimit.String(), minMemoryLimit)
if err != nil {
return fmt.Errorf("could not compare defined memory limit %s for %q container with configured minimum value %s: %v",
memoryLimit.String(), constants.PostgresContainerName, minMemoryLimit, err)
}
if isSmaller {
msg = fmt.Sprintf("defined memory limit %s for %q container is below required minimum %s and will be increased",
memoryLimit.String(), constants.PostgresContainerName, minMemoryLimit)
c.logger.Warningf(msg)
c.eventRecorder.Eventf(c.GetReference(), v1.EventTypeWarning, "ResourceLimits", msg)
resources.Limits[v1.ResourceMemory], _ = resource.ParseQuantity(minMemoryLimit)
}
}
result.Limits, err = fillResourceList(specLimits, defaultResources.ResourceLimits)
if err != nil {
return nil, fmt.Errorf("could not fill resource limits: %v", err)
}
return nil
}
return &result, nil
func setMemoryRequestToLimit(resources *v1.ResourceRequirements, containerName string, logger *logrus.Entry) {
requests := resources.Requests[v1.ResourceMemory]
limits := resources.Limits[v1.ResourceMemory]
isSmaller := requests.Cmp(limits) == -1
if isSmaller {
logger.Warningf("memory request of %s for %q container is increased to match memory limit of %s",
requests.String(), containerName, limits.String())
resources.Requests[v1.ResourceMemory] = limits
}
}
func fillResourceList(spec acidv1.ResourceDescription, defaults acidv1.ResourceDescription) (v1.ResourceList, error) {
@@ -194,6 +223,44 @@ func fillResourceList(spec acidv1.ResourceDescription, defaults acidv1.ResourceD
return requests, nil
}
func (c *Cluster) generateResourceRequirements(
resources *acidv1.Resources,
defaultResources acidv1.Resources,
containerName string) (*v1.ResourceRequirements, error) {
var err error
specRequests := acidv1.ResourceDescription{}
specLimits := acidv1.ResourceDescription{}
result := v1.ResourceRequirements{}
if resources != nil {
specRequests = resources.ResourceRequests
specLimits = resources.ResourceLimits
}
result.Requests, err = fillResourceList(specRequests, defaultResources.ResourceRequests)
if err != nil {
return nil, fmt.Errorf("could not fill resource requests: %v", err)
}
result.Limits, err = fillResourceList(specLimits, defaultResources.ResourceLimits)
if err != nil {
return nil, fmt.Errorf("could not fill resource limits: %v", err)
}
// enforce minimum cpu and memory limits for Postgres containers only
if containerName == constants.PostgresContainerName {
if err = c.enforceMinResourceLimits(&result); err != nil {
return nil, fmt.Errorf("could not enforce minimum resource limits: %v", err)
}
}
if c.OpConfig.SetMemoryRequestToLimit {
setMemoryRequestToLimit(&result, containerName, c.logger)
}
return &result, nil
}
func generateSpiloJSONConfiguration(pg *acidv1.PostgresqlParam, patroni *acidv1.Patroni, pamRoleName string, EnablePgVersionEnvVar bool, logger *logrus.Entry) (string, error) {
config := spiloConfiguration{}
@@ -514,8 +581,8 @@ func generateContainer(
}
}
func generateSidecarContainers(sidecars []acidv1.Sidecar,
defaultResources acidv1.Resources, startIndex int, logger *logrus.Entry) ([]v1.Container, error) {
func (c *Cluster) generateSidecarContainers(sidecars []acidv1.Sidecar,
defaultResources acidv1.Resources, startIndex int) ([]v1.Container, error) {
if len(sidecars) > 0 {
result := make([]v1.Container, 0)
@@ -527,7 +594,7 @@ func generateSidecarContainers(sidecars []acidv1.Sidecar,
sidecar.Resources.DeepCopyInto(&resourcesSpec)
}
resources, err := generateResourceRequirements(&resourcesSpec, defaultResources)
resources, err := c.generateResourceRequirements(&resourcesSpec, defaultResources, sidecar.Name)
if err != nil {
return nil, err
}
@@ -1002,61 +1069,9 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
additionalVolumes = spec.AdditionalVolumes
)
// Improve me. Please.
if c.OpConfig.SetMemoryRequestToLimit {
// controller adjusts the default memory request at operator startup
var request, limit string
if spec.Resources == nil {
request = c.OpConfig.Resources.DefaultMemoryRequest
limit = c.OpConfig.Resources.DefaultMemoryLimit
} else {
request = spec.Resources.ResourceRequests.Memory
limit = spec.Resources.ResourceRequests.Memory
}
isSmaller, err := util.IsSmallerQuantity(request, limit)
if err != nil {
return nil, err
}
if isSmaller {
c.logger.Warningf("The memory request of %v for the Postgres container is increased to match the memory limit of %v.", request, limit)
spec.Resources.ResourceRequests.Memory = limit
}
// controller adjusts the Scalyr sidecar request at operator startup
// as this sidecar is managed separately
// adjust sidecar containers defined for that particular cluster
for _, sidecar := range spec.Sidecars {
// TODO #413
var sidecarRequest, sidecarLimit string
if sidecar.Resources == nil {
sidecarRequest = c.OpConfig.Resources.DefaultMemoryRequest
sidecarLimit = c.OpConfig.Resources.DefaultMemoryLimit
} else {
sidecarRequest = sidecar.Resources.ResourceRequests.Memory
sidecarLimit = sidecar.Resources.ResourceRequests.Memory
}
isSmaller, err := util.IsSmallerQuantity(sidecarRequest, sidecarLimit)
if err != nil {
return nil, err
}
if isSmaller {
c.logger.Warningf("The memory request of %v for the %v sidecar container is increased to match the memory limit of %v.", sidecar.Resources.ResourceRequests.Memory, sidecar.Name, sidecar.Resources.ResourceLimits.Memory)
sidecar.Resources.ResourceRequests.Memory = sidecar.Resources.ResourceLimits.Memory
}
}
}
defaultResources := c.makeDefaultResources()
resourceRequirements, err := generateResourceRequirements(spec.Resources, defaultResources)
defaultResources := makeDefaultResources(&c.OpConfig)
resourceRequirements, err := c.generateResourceRequirements(
spec.Resources, defaultResources, constants.PostgresContainerName)
if err != nil {
return nil, fmt.Errorf("could not generate resource requirements: %v", err)
}
@@ -1232,7 +1247,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
c.logger.Warningf("sidecars specified but disabled in configuration - next statefulset creation would fail")
}
if clusterSpecificSidecars, err = generateSidecarContainers(spec.Sidecars, defaultResources, 0, c.logger); err != nil {
if clusterSpecificSidecars, err = c.generateSidecarContainers(spec.Sidecars, defaultResources, 0); err != nil {
return nil, fmt.Errorf("could not generate sidecar containers: %v", err)
}
}
@@ -1243,7 +1258,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
for name, dockerImage := range c.OpConfig.SidecarImages {
globalSidecarsByDockerImage = append(globalSidecarsByDockerImage, acidv1.Sidecar{Name: name, DockerImage: dockerImage})
}
if globalSidecarContainersByDockerImage, err = generateSidecarContainers(globalSidecarsByDockerImage, defaultResources, len(clusterSpecificSidecars), c.logger); err != nil {
if globalSidecarContainersByDockerImage, err = c.generateSidecarContainers(globalSidecarsByDockerImage, defaultResources, len(clusterSpecificSidecars)); err != nil {
return nil, fmt.Errorf("could not generate sidecar containers: %v", err)
}
// make the resulting list reproducible
@@ -1256,7 +1271,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
// generate scalyr sidecar container
var scalyrSidecars []v1.Container
if scalyrSidecar, err :=
generateScalyrSidecarSpec(c.Name,
c.generateScalyrSidecarSpec(c.Name,
c.OpConfig.ScalyrAPIKey,
c.OpConfig.ScalyrServerURL,
c.OpConfig.ScalyrImage,
@@ -1264,8 +1279,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
c.OpConfig.ScalyrMemoryRequest,
c.OpConfig.ScalyrCPULimit,
c.OpConfig.ScalyrMemoryLimit,
defaultResources,
c.logger); err != nil {
defaultResources); err != nil {
return nil, fmt.Errorf("could not generate Scalyr sidecar: %v", err)
} else {
if scalyrSidecar != nil {
@@ -1375,12 +1389,12 @@ func (c *Cluster) generatePodAnnotations(spec *acidv1.PostgresSpec) map[string]s
return annotations
}
func generateScalyrSidecarSpec(clusterName, APIKey, serverURL, dockerImage string,
func (c *Cluster) generateScalyrSidecarSpec(clusterName, APIKey, serverURL, dockerImage string,
scalyrCPURequest string, scalyrMemoryRequest string, scalyrCPULimit string, scalyrMemoryLimit string,
defaultResources acidv1.Resources, logger *logrus.Entry) (*v1.Container, error) {
defaultResources acidv1.Resources) (*v1.Container, error) {
if APIKey == "" || dockerImage == "" {
if APIKey == "" && dockerImage != "" {
logger.Warning("Not running Scalyr sidecar: SCALYR_API_KEY must be defined")
c.logger.Warning("Not running Scalyr sidecar: SCALYR_API_KEY must be defined")
}
return nil, nil
}
@@ -1390,7 +1404,8 @@ func generateScalyrSidecarSpec(clusterName, APIKey, serverURL, dockerImage strin
scalyrCPULimit,
scalyrMemoryLimit,
)
resourceRequirementsScalyrSidecar, err := generateResourceRequirements(&resourcesScalyrSidecar, defaultResources)
resourceRequirementsScalyrSidecar, err := c.generateResourceRequirements(
&resourcesScalyrSidecar, defaultResources, scalyrSidecarName)
if err != nil {
return nil, fmt.Errorf("invalid resources for Scalyr sidecar: %v", err)
}
@@ -1408,7 +1423,7 @@ func generateScalyrSidecarSpec(clusterName, APIKey, serverURL, dockerImage strin
env = append(env, v1.EnvVar{Name: "SCALYR_SERVER_URL", Value: serverURL})
}
return &v1.Container{
Name: "scalyr-sidecar",
Name: scalyrSidecarName,
Image: dockerImage,
Env: env,
ImagePullPolicy: v1.PullIfNotPresent,
@@ -1991,15 +2006,15 @@ func (c *Cluster) generateLogicalBackupJob() (*batchv1beta1.CronJob, error) {
c.logger.Debug("Generating logical backup pod template")
// allocate for the backup pod the same amount of resources as for normal DB pods
defaultResources := c.makeDefaultResources()
resourceRequirements, err = generateResourceRequirements(c.Spec.Resources, defaultResources)
resourceRequirements, err = c.generateResourceRequirements(
c.Spec.Resources, makeDefaultResources(&c.OpConfig), logicalBackupContainerName)
if err != nil {
return nil, fmt.Errorf("could not generate resource requirements for logical backup pods: %v", err)
}
envVars := c.generateLogicalBackupPodEnvVars()
logicalBackupContainer := generateContainer(
"logical-backup",
logicalBackupContainerName,
&c.OpConfig.LogicalBackup.LogicalBackupDockerImage,
resourceRequirements,
envVars,
+314
View File
@@ -30,6 +30,7 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes/fake"
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/record"
)
func newFakeK8sTestClient() (k8sutil.KubernetesClient, *fake.Clientset) {
@@ -1667,6 +1668,319 @@ func TestEnableLoadBalancers(t *testing.T) {
}
}
func TestGenerateResourceRequirements(t *testing.T) {
testName := "TestGenerateResourceRequirements"
client, _ := newFakeK8sTestClient()
clusterName := "acid-test-cluster"
namespace := "default"
clusterNameLabel := "cluster-name"
roleLabel := "spilo-role"
sidecarName := "postgres-exporter"
// two test cases will call enforceMinResourceLimits which emits 2 events per call
// hence bufferSize of 4 is required
newEventRecorder := record.NewFakeRecorder(4)
configResources := config.Resources{
ClusterLabels: map[string]string{"application": "spilo"},
ClusterNameLabel: clusterNameLabel,
DefaultCPURequest: "100m",
DefaultCPULimit: "1",
DefaultMemoryRequest: "100Mi",
DefaultMemoryLimit: "500Mi",
MinCPULimit: "250m",
MinMemoryLimit: "250Mi",
PodRoleLabel: roleLabel,
}
tests := []struct {
subTest string
config config.Config
pgSpec acidv1.Postgresql
expectedResources acidv1.Resources
}{
{
subTest: "test generation of default resources when empty in manifest",
config: config.Config{
Resources: configResources,
PodManagementPolicy: "ordered_ready",
SetMemoryRequestToLimit: false,
},
pgSpec: acidv1.Postgresql{
ObjectMeta: metav1.ObjectMeta{
Name: clusterName,
Namespace: namespace,
},
Spec: acidv1.PostgresSpec{
TeamID: "acid",
Volume: acidv1.Volume{
Size: "1G",
},
},
},
expectedResources: acidv1.Resources{
ResourceRequests: acidv1.ResourceDescription{CPU: "100m", Memory: "100Mi"},
ResourceLimits: acidv1.ResourceDescription{CPU: "1", Memory: "500Mi"},
},
},
{
subTest: "test generation of default resources for sidecar",
config: config.Config{
Resources: configResources,
PodManagementPolicy: "ordered_ready",
SetMemoryRequestToLimit: false,
},
pgSpec: acidv1.Postgresql{
ObjectMeta: metav1.ObjectMeta{
Name: clusterName,
Namespace: namespace,
},
Spec: acidv1.PostgresSpec{
Sidecars: []acidv1.Sidecar{
acidv1.Sidecar{
Name: sidecarName,
},
},
TeamID: "acid",
Volume: acidv1.Volume{
Size: "1G",
},
},
},
expectedResources: acidv1.Resources{
ResourceRequests: acidv1.ResourceDescription{CPU: "100m", Memory: "100Mi"},
ResourceLimits: acidv1.ResourceDescription{CPU: "1", Memory: "500Mi"},
},
},
{
subTest: "test generation of resources when only requests are defined in manifest",
config: config.Config{
Resources: configResources,
PodManagementPolicy: "ordered_ready",
SetMemoryRequestToLimit: false,
},
pgSpec: acidv1.Postgresql{
ObjectMeta: metav1.ObjectMeta{
Name: clusterName,
Namespace: namespace,
},
Spec: acidv1.PostgresSpec{
Resources: &acidv1.Resources{
ResourceRequests: acidv1.ResourceDescription{CPU: "50m", Memory: "50Mi"},
},
TeamID: "acid",
Volume: acidv1.Volume{
Size: "1G",
},
},
},
expectedResources: acidv1.Resources{
ResourceRequests: acidv1.ResourceDescription{CPU: "50m", Memory: "50Mi"},
ResourceLimits: acidv1.ResourceDescription{CPU: "1", Memory: "500Mi"},
},
},
{
subTest: "test generation of resources when only memory is defined in manifest",
config: config.Config{
Resources: configResources,
PodManagementPolicy: "ordered_ready",
SetMemoryRequestToLimit: false,
},
pgSpec: acidv1.Postgresql{
ObjectMeta: metav1.ObjectMeta{
Name: clusterName,
Namespace: namespace,
},
Spec: acidv1.PostgresSpec{
Resources: &acidv1.Resources{
ResourceRequests: acidv1.ResourceDescription{Memory: "100Mi"},
ResourceLimits: acidv1.ResourceDescription{Memory: "1Gi"},
},
TeamID: "acid",
Volume: acidv1.Volume{
Size: "1G",
},
},
},
expectedResources: acidv1.Resources{
ResourceRequests: acidv1.ResourceDescription{CPU: "100m", Memory: "100Mi"},
ResourceLimits: acidv1.ResourceDescription{CPU: "1", Memory: "1Gi"},
},
},
{
subTest: "test SetMemoryRequestToLimit flag",
config: config.Config{
Resources: configResources,
PodManagementPolicy: "ordered_ready",
SetMemoryRequestToLimit: true,
},
pgSpec: acidv1.Postgresql{
ObjectMeta: metav1.ObjectMeta{
Name: clusterName,
Namespace: namespace,
},
Spec: acidv1.PostgresSpec{
TeamID: "acid",
Volume: acidv1.Volume{
Size: "1G",
},
},
},
expectedResources: acidv1.Resources{
ResourceRequests: acidv1.ResourceDescription{CPU: "100m", Memory: "500Mi"},
ResourceLimits: acidv1.ResourceDescription{CPU: "1", Memory: "500Mi"},
},
},
{
subTest: "test SetMemoryRequestToLimit flag for sidecar container, too",
config: config.Config{
Resources: configResources,
PodManagementPolicy: "ordered_ready",
SetMemoryRequestToLimit: true,
},
pgSpec: acidv1.Postgresql{
ObjectMeta: metav1.ObjectMeta{
Name: clusterName,
Namespace: namespace,
},
Spec: acidv1.PostgresSpec{
Sidecars: []acidv1.Sidecar{
acidv1.Sidecar{
Name: sidecarName,
Resources: &acidv1.Resources{
ResourceRequests: acidv1.ResourceDescription{CPU: "10m", Memory: "10Mi"},
ResourceLimits: acidv1.ResourceDescription{CPU: "100m", Memory: "100Mi"},
},
},
},
TeamID: "acid",
Volume: acidv1.Volume{
Size: "1G",
},
},
},
expectedResources: acidv1.Resources{
ResourceRequests: acidv1.ResourceDescription{CPU: "10m", Memory: "100Mi"},
ResourceLimits: acidv1.ResourceDescription{CPU: "100m", Memory: "100Mi"},
},
},
{
subTest: "test generating resources from manifest",
config: config.Config{
Resources: configResources,
PodManagementPolicy: "ordered_ready",
SetMemoryRequestToLimit: false,
},
pgSpec: acidv1.Postgresql{
ObjectMeta: metav1.ObjectMeta{
Name: clusterName,
Namespace: namespace,
},
Spec: acidv1.PostgresSpec{
Resources: &acidv1.Resources{
ResourceRequests: acidv1.ResourceDescription{CPU: "10m", Memory: "250Mi"},
ResourceLimits: acidv1.ResourceDescription{CPU: "400m", Memory: "800Mi"},
},
TeamID: "acid",
Volume: acidv1.Volume{
Size: "1G",
},
},
},
expectedResources: acidv1.Resources{
ResourceRequests: acidv1.ResourceDescription{CPU: "10m", Memory: "250Mi"},
ResourceLimits: acidv1.ResourceDescription{CPU: "400m", Memory: "800Mi"},
},
},
{
subTest: "test enforcing min cpu and memory limit",
config: config.Config{
Resources: configResources,
PodManagementPolicy: "ordered_ready",
SetMemoryRequestToLimit: false,
},
pgSpec: acidv1.Postgresql{
ObjectMeta: metav1.ObjectMeta{
Name: clusterName,
Namespace: namespace,
},
Spec: acidv1.PostgresSpec{
Resources: &acidv1.Resources{
ResourceRequests: acidv1.ResourceDescription{CPU: "100m", Memory: "100Mi"},
ResourceLimits: acidv1.ResourceDescription{CPU: "200m", Memory: "200Mi"},
},
TeamID: "acid",
Volume: acidv1.Volume{
Size: "1G",
},
},
},
expectedResources: acidv1.Resources{
ResourceRequests: acidv1.ResourceDescription{CPU: "100m", Memory: "100Mi"},
ResourceLimits: acidv1.ResourceDescription{CPU: "250m", Memory: "250Mi"},
},
},
{
subTest: "test min cpu and memory limit are not enforced on sidecar",
config: config.Config{
Resources: configResources,
PodManagementPolicy: "ordered_ready",
SetMemoryRequestToLimit: false,
},
pgSpec: acidv1.Postgresql{
ObjectMeta: metav1.ObjectMeta{
Name: clusterName,
Namespace: namespace,
},
Spec: acidv1.PostgresSpec{
Sidecars: []acidv1.Sidecar{
acidv1.Sidecar{
Name: sidecarName,
Resources: &acidv1.Resources{
ResourceRequests: acidv1.ResourceDescription{CPU: "10m", Memory: "10Mi"},
ResourceLimits: acidv1.ResourceDescription{CPU: "100m", Memory: "100Mi"},
},
},
},
TeamID: "acid",
Volume: acidv1.Volume{
Size: "1G",
},
},
},
expectedResources: acidv1.Resources{
ResourceRequests: acidv1.ResourceDescription{CPU: "10m", Memory: "10Mi"},
ResourceLimits: acidv1.ResourceDescription{CPU: "100m", Memory: "100Mi"},
},
},
}
for _, tt := range tests {
var cluster = New(
Config{
OpConfig: tt.config,
}, client, tt.pgSpec, logger, newEventRecorder)
cluster.Name = clusterName
cluster.Namespace = namespace
_, err := cluster.createStatefulSet()
if k8sutil.ResourceAlreadyExists(err) {
err = cluster.syncStatefulSet()
}
assert.NoError(t, err)
containers := cluster.Statefulset.Spec.Template.Spec.Containers
clusterResources, err := parseResourceRequirements(containers[0].Resources)
if len(containers) > 1 {
clusterResources, err = parseResourceRequirements(containers[1].Resources)
}
assert.NoError(t, err)
if !reflect.DeepEqual(tt.expectedResources, clusterResources) {
t.Errorf("%s - %s: expected %#v but got %#v", testName, tt.subTest, tt.expectedResources, clusterResources)
}
}
}
func TestGenerateCapabilities(t *testing.T) {
testName := "TestGenerateCapabilities"
-5
View File
@@ -76,11 +76,6 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error {
}
}
if err = c.enforceMinResourceLimits(&c.Spec); err != nil {
err = fmt.Errorf("could not enforce minimum resource limits: %v", err)
return err
}
c.logger.Debug("syncing statefulsets")
if err = c.syncStatefulSet(); err != nil {
if !k8sutil.ResourceAlreadyExists(err) {
+12
View File
@@ -594,3 +594,15 @@ func trimCronjobName(name string) string {
}
return name
}
func parseResourceRequirements(resourcesRequirement v1.ResourceRequirements) (acidv1.Resources, error) {
var resources acidv1.Resources
resourcesJSON, err := json.Marshal(resourcesRequirement)
if err != nil {
return acidv1.Resources{}, fmt.Errorf("could not marshal K8s resources requirements")
}
if err = json.Unmarshal(resourcesJSON, &resources); err != nil {
return acidv1.Resources{}, fmt.Errorf("could not unmarshal K8s resources requirements into acidv1.Resources struct")
}
return resources, nil
}