Skip to content

Commit

Permalink
Implement new StatusConfig field
Browse files Browse the repository at this point in the history
Users can define how specific resource statuses affect the overall OperatorPolicy status and compliance events.

ref: https://issues.redhat.com/browse/ACM-11023
Signed-off-by: Jason Zhang <jaszhang@redhat.com>
  • Loading branch information
JeffeyL authored and zyjjay committed May 27, 2024
1 parent 5b60de6 commit 75726b3
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 10 deletions.
14 changes: 11 additions & 3 deletions api/v1beta1/operatorpolicy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,12 @@ func (rb RemovalBehavior) ApplyDefaults() RemovalBehavior {

// StatusConfig defines how resource statuses affect the OperatorPolicy status and compliance
type StatusConfig struct {
// +kubebuilder:default=NonCompliant
CatalogSourceUnhealthy StatusConfigAction `json:"catalogSourceUnhealthy,omitempty"`
// +kubebuilder:default=NonCompliant
DeploymentsUnavailable StatusConfigAction `json:"deploymentsUnavailable,omitempty"`
UpgradesAvailable StatusConfigAction `json:"upgradesAvailable,omitempty"`
UpgradesProgressing StatusConfigAction `json:"upgradesProgressing,omitempty"`
// +kubebuilder:default=NonCompliant
UpgradesAvailable StatusConfigAction `json:"upgradesAvailable,omitempty"`
}

// OperatorPolicySpec defines the desired state of OperatorPolicy
Expand All @@ -130,7 +132,7 @@ type OperatorPolicySpec struct {
// in 'inform' mode, and which installPlans are approved when in 'enforce' mode
Versions []policyv1.NonEmptyString `json:"versions,omitempty"`

//+kubebuilder:default={}
// +kubebuilder:default={}
// RemovalBehavior defines what resources will be removed by enforced mustnothave policies.
// When in inform mode, any resources that would be deleted if the policy was enforced will
// be causes for NonCompliance, but resources that would be kept will be considered Compliant.
Expand All @@ -143,6 +145,12 @@ type OperatorPolicySpec struct {
// approval is not affected by this setting. This setting has no effect when the policy is in
// 'mustnothave' mode. Allowed values are "None" or "Automatic".
UpgradeApproval string `json:"upgradeApproval"`

// +kubebuilder:default={}
// StatusConfig defines how resource statuses affect the OperatorPolicy status and compliance.
// Options include StatusMessageOnly, which does not affect compliance but reports a Status,
// and NonCompliant, which will update the OperatorPolicy compliance as well.
StatusConfig StatusConfig `json:"statusConfig,omitempty"`
}

// OperatorPolicyStatus defines the observed state of OperatorPolicy
Expand Down
1 change: 1 addition & 0 deletions api/v1beta1/zz_generated.deepcopy.go

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

27 changes: 21 additions & 6 deletions controllers/operatorpolicy_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,14 +210,19 @@ func calculateComplianceCondition(policy *policyv1beta1.OperatorPolicy) metav1.C
}
}

modifyCompliance := policy.Spec.StatusConfig.UpgradesAvailable == "NonCompliant"
idx, cond = policy.Status.GetCondition(installPlanConditionType)

if idx == -1 {
messages = append(messages, "the status of the InstallPlan is unknown")
foundNonCompliant = true

if modifyCompliance {
foundNonCompliant = true
}
} else {
messages = append(messages, cond.Message)

if cond.Status != metav1.ConditionTrue {
if cond.Status != metav1.ConditionTrue && modifyCompliance {
foundNonCompliant = true
}
}
Expand Down Expand Up @@ -246,27 +251,37 @@ func calculateComplianceCondition(policy *policyv1beta1.OperatorPolicy) metav1.C
}
}

modifyCompliance = policy.Spec.StatusConfig.DeploymentsUnavailable == "NonCompliant"
idx, cond = policy.Status.GetCondition(deploymentConditionType)

if idx == -1 {
messages = append(messages, "the status of the Deployments are unknown")
foundNonCompliant = true

if modifyCompliance {
foundNonCompliant = true
}
} else {
messages = append(messages, cond.Message)

if cond.Status != metav1.ConditionTrue {
if cond.Status != metav1.ConditionTrue && modifyCompliance {
foundNonCompliant = true
}
}

modifyCompliance = policy.Spec.StatusConfig.CatalogSourceUnhealthy == "NonCompliant"
idx, cond = policy.Status.GetCondition(catalogSrcConditionType)

if idx == -1 {
messages = append(messages, "the status of the CatalogSource is unknown")
foundNonCompliant = true

if modifyCompliance {
foundNonCompliant = true
}
} else {
messages = append(messages, cond.Message)

// Note: the CatalogSource condition has a different polarity
if cond.Status != metav1.ConditionFalse {
if cond.Status != metav1.ConditionFalse && modifyCompliance {
foundNonCompliant = true
}
}
Expand Down
104 changes: 104 additions & 0 deletions controllers/operatorpolicy_status_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package controllers

import (
"testing"

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

policyv1beta1 "open-cluster-management.io/config-policy-controller/api/v1beta1"
)

func TestStatusConfigCompliance(t *testing.T) {
t.Parallel()

testPolicy := &policyv1beta1.OperatorPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "test-policy",
Namespace: "default",
},
Spec: policyv1beta1.OperatorPolicySpec{
Severity: "low",
RemediationAction: "enforce",
ComplianceType: "musthave",
Subscription: runtime.RawExtension{
Raw: []byte(`{
"source": "my-catalog",
"sourceNamespace": "my-ns",
"name": "my-operator",
"channel": "stable",
"startingCSV": "my-operator-v1"
}`),
},
StatusConfig: policyv1beta1.StatusConfig{
CatalogSourceUnhealthy: "NonCompliant",
DeploymentsUnavailable: "NonCompliant",
UpgradesAvailable: "NonCompliant",
},
},
Status: policyv1beta1.OperatorPolicyStatus{
Conditions: []metav1.Condition{
{
Type: "ValidPolicySpec",
Status: "True",
},
{
Type: "OperatorGroupCompliant",
Status: "True",
},
{
Type: "SubscriptionCompliant",
Status: "True",
},
{
Type: "InstallPlanCompliant",
Status: "True",
},
{
Type: "ClusterServiceVersionCompliant",
Status: "True",
},
{
Type: "CustomResourceDefinitionCompliant",
Status: "True",
},
{
Type: "DeploymentCompliant",
Status: "True",
},
{
Type: "CatalogSourcesUnhealthy",
Status: "False",
},
},
},
}

// upgradesAvailable
testPolicy.Status.Conditions[3].Status = "False"
complianceCond := calculateComplianceCondition(testPolicy)
assert.Equal(t, complianceCond.Reason, "NonCompliant")

testPolicy.Spec.StatusConfig.UpgradesAvailable = "StatusMessageOnly"
complianceCond = calculateComplianceCondition(testPolicy)
assert.Equal(t, complianceCond.Reason, "Compliant")

// CatalogSourcesUnhealthy
testPolicy.Status.Conditions[7].Status = "True"
complianceCond = calculateComplianceCondition(testPolicy)
assert.Equal(t, complianceCond.Reason, "NonCompliant")

testPolicy.Spec.StatusConfig.CatalogSourceUnhealthy = "StatusMessageOnly"
complianceCond = calculateComplianceCondition(testPolicy)
assert.Equal(t, complianceCond.Reason, "Compliant")

// DeploymentsUnavailable
testPolicy.Status.Conditions[6].Status = "False"
complianceCond = calculateComplianceCondition(testPolicy)
assert.Equal(t, complianceCond.Reason, "NonCompliant")

testPolicy.Spec.StatusConfig.DeploymentsUnavailable = "StatusMessageOnly"
complianceCond = calculateComplianceCondition(testPolicy)
assert.Equal(t, complianceCond.Reason, "Compliant")
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,35 @@ spec:
- critical
- Critical
type: string
statusConfig:
default: {}
description: |-
StatusConfig defines how resource statuses affect the OperatorPolicy status and compliance.
Options include StatusMessageOnly, which does not affect compliance but reports a Status,
and NonCompliant, which will update the OperatorPolicy compliance as well.
properties:
catalogSourceUnhealthy:
default: NonCompliant
description: 'StatusConfigAction : StatusMessageOnly or NonCompliant'
enum:
- StatusMessageOnly
- NonCompliant
type: string
deploymentsUnavailable:
default: NonCompliant
description: 'StatusConfigAction : StatusMessageOnly or NonCompliant'
enum:
- StatusMessageOnly
- NonCompliant
type: string
upgradesAvailable:
default: NonCompliant
description: 'StatusConfigAction : StatusMessageOnly or NonCompliant'
enum:
- StatusMessageOnly
- NonCompliant
type: string
type: object
subscription:
description: |-
Include the namespace, and any `spec` fields for the Subscription.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,35 @@ spec:
- critical
- Critical
type: string
statusConfig:
default: {}
description: |-
StatusConfig defines how resource statuses affect the OperatorPolicy status and compliance.
Options include StatusMessageOnly, which does not affect compliance but reports a Status,
and NonCompliant, which will update the OperatorPolicy compliance as well.
properties:
catalogSourceUnhealthy:
default: NonCompliant
description: 'StatusConfigAction : StatusMessageOnly or NonCompliant'
enum:
- StatusMessageOnly
- NonCompliant
type: string
deploymentsUnavailable:
default: NonCompliant
description: 'StatusConfigAction : StatusMessageOnly or NonCompliant'
enum:
- StatusMessageOnly
- NonCompliant
type: string
upgradesAvailable:
default: NonCompliant
description: 'StatusConfigAction : StatusMessageOnly or NonCompliant'
enum:
- StatusMessageOnly
- NonCompliant
type: string
type: object
subscription:
description: |-
Include the namespace, and any `spec` fields for the Subscription.
Expand Down
61 changes: 60 additions & 1 deletion test/e2e/case38_install_operator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1163,6 +1163,37 @@ var _ = Describe("Testing OperatorPolicy", Ordered, Label("supports-hosted"), fu
"CatalogSource was found but is unhealthy",
)
})
It("Should become Compliant when StatusConfig is modified", func() {
By("Patching the policy StatusConfig to StatusMessageOnly")
utils.Kubectl("patch", "operatorpolicy", OpPlcName, "-n", opPolTestNS, "--type=json", "-p",
`[{"op": "replace", "path": "/spec/statusConfig/catalogSourceUnhealthy",
"value": "StatusMessageOnly"}]`)

By("Checking the conditions and relatedObj in the policy")
check(
OpPlcName,
false,
[]policyv1.RelatedObject{{
Object: policyv1.ObjectResource{
Kind: "CatalogSource",
APIVersion: "operators.coreos.com/v1alpha1",
Metadata: policyv1.ObjectMetadata{
Name: catSrcName,
Namespace: opPolTestNS,
},
},
Compliant: "NonCompliant",
Reason: "Resource found as expected but is unhealthy",
}},
metav1.Condition{
Type: "CatalogSourcesUnhealthy",
Status: metav1.ConditionTrue,
Reason: "CatalogSourcesFoundUnhealthy",
Message: "CatalogSource was found but is unhealthy",
},
"CatalogSource was found but is unhealthy",
)
})
})
Describe("Testing InstallPlan approval and status behavior", Ordered, func() {
const (
Expand Down Expand Up @@ -1295,7 +1326,7 @@ var _ = Describe("Testing OperatorPolicy", Ordered, Label("supports-hosted"), fu
"an InstallPlan to update .* is available for approval",
)
})
It("Should do the initial install when enforced, and stop at the next version", func(ctx SpecContext) {
It("Should do the upgrade when enforced, and stop at the next version", func(ctx SpecContext) {
ipList, err := targetK8sDynamic.Resource(gvrInstallPlan).Namespace(opPolTestNS).
List(ctx, metav1.ListOptions{})
Expect(err).NotTo(HaveOccurred())
Expand Down Expand Up @@ -2987,6 +3018,34 @@ var _ = Describe("Testing OperatorPolicy", Ordered, Label("supports-hosted"), fu
Expect(remBehavior).To(HaveKeyWithValue("customResourceDefinitions", "Keep"))
})
})
Describe("Testing defaulted values of statusConfig in an OperatorPolicy", func() {
const (
opPolYAML = "../resources/case38_operator_install/operator-policy-no-group.yaml"
opPolName = "oppol-no-group"
)

BeforeEach(func() {
preFunc()

createObjWithParent(parentPolicyYAML, parentPolicyName,
opPolYAML, opPolTestNS, gvrPolicy, gvrOperatorPolicy)
})

It("Should have applied defaults to the statusConfig field", func(ctx SpecContext) {
policy, err := clientManagedDynamic.Resource(gvrOperatorPolicy).Namespace(opPolTestNS).
Get(ctx, opPolName, metav1.GetOptions{})
Expect(err).NotTo(HaveOccurred())
Expect(policy).NotTo(BeNil())

statusConfig, found, err := unstructured.NestedStringMap(policy.Object, "spec", "statusConfig")
Expect(found).To(BeTrue())
Expect(err).NotTo(HaveOccurred())

Expect(statusConfig).To(HaveKeyWithValue("catalogSourceUnhealthy", "NonCompliant"))
Expect(statusConfig).To(HaveKeyWithValue("deploymentsUnavailable", "NonCompliant"))
Expect(statusConfig).To(HaveKeyWithValue("upgradesAvailable", "NonCompliant"))
})
})
Describe("Testing operator policies that specify the same subscription", Ordered, func() {
const (
musthaveYAML = "../resources/case38_operator_install/operator-policy-no-group.yaml"
Expand Down

0 comments on commit 75726b3

Please sign in to comment.