Repository: kubernetes-sigs/security-profiles-operator
Pull request #470 (2021-06-07 20:54:18)

Add Metrics for SELinux profiles
This PR adds metrics updates for the SELinux profile functionality (mentioned in https://github.com/kubernetes-sigs/security-profiles-operator/issues/276#issuecomment-839636908)

```
# HELP security_profiles_operator_selinux_profile_error_total Counter about selinux profile errors.
# TYPE security_profiles_operator_selinux_profile_error_total counter                                   
security_profiles_operator_selinux_profile_error_total{reason="CannotUpdatePolicyStatus"} 1

# HELP security_profiles_operator_selinux_profile_total Counter about selinux profile operations.
# TYPE security_profiles_operator_selinux_profile_total counter
security_profiles_operator_selinux_profile_total{operation="delete"} 1
security_profiles_operator_selinux_profile_total{operation="update"} 6
```

```release-note
Add Metrics for SELinux profiles
```


@saschagrunert @jhrozek @JAORMX 

k8s-ci-robot: @mrogers950: Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our [release note process](https://git.k8s.io/community/contributors/guide/release-notes.md) to remove it.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
k8s-ci-robot: Welcome @mrogers950! <br><br>It looks like this is your first PR to <a href='https://github.com/kubernetes-sigs/security-profiles-operator'>kubernetes-sigs/security-profiles-operator</a> 🎉. Please refer to our [pull request process documentation](https://git.k8s.io/community/contributors/guide/pull-requests.md) to help your PR have a smooth ride to approval. <br><br>You will be prompted by a bot to use commands during the review process. Do not be afraid to follow the prompts! It is okay to experiment. [Here is the bot commands documentation](https://go.k8s.io/bot-commands). <br><br>You can also check if kubernetes-sigs/security-profiles-operator has [its own contribution guidelines](https://github.com/kubernetes-sigs/security-profiles-operator/tree/master/CONTRIBUTING.md). <br><br>You may want to refer to our [testing guide](https://git.k8s.io/community/contributors/devel/sig-testing/testing.md) if you run into trouble with your tests not passing. <br><br>If you are having difficulty getting your pull request seen, please follow the [recommended escalation practices](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#why-is-my-pull-request-not-getting-reviewed). Also, for tips and tricks in the contribution process you may want to read the [Kubernetes contributor cheat sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet/README.md). We want to make sure your contribution gets all the attention it needs! <br><br>Thank you, and welcome to Kubernetes. :smiley:
k8s-ci-robot: Hi @mrogers950. Thanks for your PR.

I'm waiting for a [kubernetes-sigs](https://github.com/orgs/kubernetes-sigs/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://git.k8s.io/community/community-membership.md#member) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fsecurity-profiles-operator).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/security-profiles-operator/pull/470#" title="Author self-approved">mrogers950</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **pjbgf** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @pjbgf` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fsecurity-profiles-operator).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/security-profiles-operator/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["pjbgf"]} -->
JAORMX: /ok-to-test
k8s-ci-robot: @mrogers950: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-verify | cf62c9b0b91c25519e38fb8795c3c14857cc5ae4 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/470/pull-security-profiles-operator-verify/1402126008408084480) | `/test pull-security-profiles-operator-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=470). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amrogers950). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/security-profiles-operator/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @mrogers950: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-verify | cf62c9b0b91c25519e38fb8795c3c14857cc5ae4 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/470/pull-security-profiles-operator-verify/1402126008408084480) | `/test pull-security-profiles-operator-verify`
pull-security-profiles-operator-test-e2e | cf62c9b0b91c25519e38fb8795c3c14857cc5ae4 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/470/pull-security-profiles-operator-test-e2e/1402126008466804736) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=470). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amrogers950). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/security-profiles-operator/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
mrogers950: > Cool stuff! Docs have to be updated and linters have to be fixed now, too. :)

Thanks, I'll fix em
k8s-ci-robot: @mrogers950: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-test-e2e | cf62c9b0b91c25519e38fb8795c3c14857cc5ae4 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/470/pull-security-profiles-operator-test-e2e/1402126008466804736) | `/test pull-security-profiles-operator-test-e2e`
pull-security-profiles-operator-verify | d45839531d8b0b3603522a98944214571b6f9371 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/470/pull-security-profiles-operator-verify/1402288368980594688) | `/test pull-security-profiles-operator-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=470). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amrogers950). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/security-profiles-operator/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @mrogers950: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-verify | d45839531d8b0b3603522a98944214571b6f9371 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/470/pull-security-profiles-operator-verify/1402288368980594688) | `/test pull-security-profiles-operator-verify`
pull-security-profiles-operator-test-e2e | d45839531d8b0b3603522a98944214571b6f9371 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/470/pull-security-profiles-operator-test-e2e/1402288369068675072) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=470). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amrogers950). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/security-profiles-operator/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
mrogers950: Fixed the last linter failure
k8s-ci-robot: @mrogers950: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-test-e2e | 21486771f8bdbd0ca7671d70620536f9f090f597 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/470/pull-security-profiles-operator-test-e2e/1402307372377968640) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=470). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Amrogers950). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/security-profiles-operator/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
mrogers950: For some reason we lost an update increment (I didn't think that was possible with prometheus metrics):
Maybe this is a known flake?
```
# HELP security_profiles_operator_seccomp_profile_error_total Counter about seccomp profile errors.
# TYPE security_profiles_operator_seccomp_profile_error_total counter
security_profiles_operator_seccomp_profile_error_total{reason="CannotUpdateNodeStatus"} 1
# HELP security_profiles_operator_seccomp_profile_total Counter about seccomp profile operations.
# TYPE security_profiles_operator_seccomp_profile_total counter
security_profiles_operator_seccomp_profile_total{operation="delete"} 11
security_profiles_operator_seccomp_profile_total{operation="update"} 11
pod "pod" deleted

.. after installing/deleting the profile ...

# TYPE security_profiles_operator_seccomp_profile_total counter
security_profiles_operator_seccomp_profile_total{operation="delete"} 12
security_profiles_operator_seccomp_profile_total{operation="update"} 10
pod "pod" deleted
I0608 16:59:35.369576    6875 suite_test.go:452]  "msg"="Asserting metrics values"  
	assertion_compare.go:323: 
			Error Trace:	tc_metrics_test.go:77
			            				e2e_test.go:109
			            				suite.go:77
			Error:      	"10" is not greater than or equal to "11"
			Test:       	TestSuite/TestSecurityProfilesOperator/cluster-wide:_Seccomp:_Metrics
			Messages:   	[]
=== RUN   TestSuite/TestSecurityProfilesOperator/cluster-wide:_Seccomp:_Re-deploy_the_operator
```
mrogers950: /retest
JAORMX: Can you add a release note? 
JAORMX: @saschagrunert any idea why the Github actions didn't run in this PR?
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/security-profiles-operator/pull/470#" title="Author self-approved">mrogers950</a>*, *<a href="https://github.com/kubernetes-sigs/security-profiles-operator/pull/470#pullrequestreview-679253062" title="Approved">saschagrunert</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fsecurity-profiles-operator).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes-sigs/security-profiles-operator/blob/master/OWNERS)~~ [saschagrunert]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
codecov-commenter: # [Codecov](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/470?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#470](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/470?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (2148677) into [master](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/commit/12d2a6a26d06695506c81ebe5122a6d49ede4082?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (12d2a6a) will **increase** coverage by `1.22%`.
> The diff coverage is `96.00%`.


```diff
@@            Coverage Diff             @@
##           master     #470      +/-   ##
==========================================
+ Coverage   36.70%   37.92%   +1.22%     
==========================================
  Files          23       23              
  Lines        1087     1110      +23     
==========================================
+ Hits          399      421      +22     
- Misses        673      674       +1     
  Partials       15       15              
```


Patch:
diff --git a/installation-usage.md b/installation-usage.md
index 79a1ceae4..4e8a983a1 100644
--- a/installation-usage.md
+++ b/installation-usage.md
@@ -473,6 +473,8 @@ additional metrics are provided by the daemon, which are always prefixed with
 | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ------------------------------------- |
 | `seccomp_profile_total`       | `operation={delete,update}`                                                                                                                                                                                | Counter | Amount of seccomp profile operations. |
 | `seccomp_profile_error_total` | `reason={`<br>`SeccompNotSupportedOnNode,`<br>`InvalidSeccompProfile,`<br>`CannotSaveSeccompProfile,`<br>`CannotRemoveSeccompProfile,`<br>`CannotUpdateSeccompProfile,`<br>`CannotUpdateNodeStatus`<br>`}` | Counter | Amount of seccomp profile errors.     |
+| `selinux_profile_total`       | `operation={delete,update}`                                                                                                                                                                                | Counter | Amount of selinux profile operations. |
+| `selinux_profile_error_total` | `reason={`<br>`CannotSaveSelinuxPolicy,`<br>`CannotUpdatePolicyStatus,`<br>`CannotRemoveSelinuxPolicy,`<br>`CannotContactSelinuxd,`<br>`CannotWritePolicyFile,`<br>`CannotGetPolicyStatus`<br>`}` | Counter | Amount of selinux profile errors.     |
 
 ## Automatic ServiceMonitor deployment
 
diff --git a/internal/pkg/daemon/metrics/metrics.go b/internal/pkg/daemon/metrics/metrics.go
index 25300a707..0819fad91 100644
--- a/internal/pkg/daemon/metrics/metrics.go
+++ b/internal/pkg/daemon/metrics/metrics.go
@@ -33,13 +33,15 @@ import (
 // +kubebuilder:rbac:groups=authorization.k8s.io,resources=subjectaccessreviews,verbs=create
 
 const (
-	metricNamespace                      = "security_profiles_operator"
-	metricNameSeccompProfile             = "seccomp_profile_total"
-	metricNameSeccompProfileError        = "seccomp_profile_error_total"
-	metricLabelValueSeccompProfileUpdate = "update"
-	metricLabelValueSeccompProfileDelete = "delete"
-	metricLabelOperation                 = "operation"
-	metricsLabelReason                   = "reason"
+	metricNamespace               = "security_profiles_operator"
+	metricNameSeccompProfile      = "seccomp_profile_total"
+	metricNameSelinuxProfile      = "selinux_profile_total"
+	metricNameSeccompProfileError = "seccomp_profile_error_total"
+	metricNameSelinuxProfileError = "selinux_profile_error_total"
+	metricLabelValueProfileUpdate = "update"
+	metricLabelValueProfileDelete = "delete"
+	metricLabelOperation          = "operation"
+	metricsLabelReason            = "reason"
 
 	// HandlerPath is the default path for serving metrics.
 	HandlerPath = "/metrics-spod"
@@ -51,6 +53,8 @@ type Metrics struct {
 	log                       logr.Logger
 	metricSeccompProfile      *prometheus.CounterVec
 	metricSeccompProfileError *prometheus.CounterVec
+	metricSelinuxProfile      *prometheus.CounterVec
+	metricSelinuxProfileError *prometheus.CounterVec
 }
 
 // New returns a new Metrics instance.
@@ -74,6 +78,22 @@ func New() *Metrics {
 			},
 			[]string{metricsLabelReason},
 		),
+		metricSelinuxProfile: prometheus.NewCounterVec(
+			prometheus.CounterOpts{
+				Name:      metricNameSelinuxProfile,
+				Namespace: metricNamespace,
+				Help:      "Counter about selinux profile operations.",
+			},
+			[]string{metricLabelOperation},
+		),
+		metricSelinuxProfileError: prometheus.NewCounterVec(
+			prometheus.CounterOpts{
+				Name:      metricNameSelinuxProfileError,
+				Namespace: metricNamespace,
+				Help:      "Counter about selinux profile errors.",
+			},
+			[]string{metricsLabelReason},
+		),
 	}
 }
 
@@ -82,6 +102,8 @@ func (m *Metrics) Register() error {
 	for name, collector := range map[string]prometheus.Collector{
 		metricNameSeccompProfile:      m.metricSeccompProfile,
 		metricNameSeccompProfileError: m.metricSeccompProfileError,
+		metricNameSelinuxProfile:      m.metricSelinuxProfile,
+		metricNameSelinuxProfileError: m.metricSelinuxProfileError,
 	} {
 		m.log.Info(fmt.Sprintf("Registering metric: %s", name))
 		if err := m.impl.Register(collector); err != nil {
@@ -101,13 +123,13 @@ func (m *Metrics) Handler() http.Handler {
 // IncSeccompProfileUpdate increments the seccomp profile update counter.
 func (m *Metrics) IncSeccompProfileUpdate() {
 	m.metricSeccompProfile.
-		WithLabelValues(metricLabelValueSeccompProfileUpdate).Inc()
+		WithLabelValues(metricLabelValueProfileUpdate).Inc()
 }
 
 // IncSeccompProfileDelete increments the seccomp profile deletion counter.
 func (m *Metrics) IncSeccompProfileDelete() {
 	m.metricSeccompProfile.
-		WithLabelValues(metricLabelValueSeccompProfileDelete).Inc()
+		WithLabelValues(metricLabelValueProfileDelete).Inc()
 }
 
 // IncSeccompProfileError increments the seccomp profile error counter for the
@@ -115,3 +137,21 @@ func (m *Metrics) IncSeccompProfileDelete() {
 func (m *Metrics) IncSeccompProfileError(reason event.Reason) {
 	m.metricSeccompProfileError.WithLabelValues(string(reason)).Inc()
 }
+
+// IncSelinuxProfileUpdate increments the selinux profile update counter.
+func (m *Metrics) IncSelinuxProfileUpdate() {
+	m.metricSelinuxProfile.
+		WithLabelValues(metricLabelValueProfileUpdate).Inc()
+}
+
+// IncSelinuxProfileDelete increments the selinux profile deletion counter.
+func (m *Metrics) IncSelinuxProfileDelete() {
+	m.metricSelinuxProfile.
+		WithLabelValues(metricLabelValueProfileDelete).Inc()
+}
+
+// IncSelinuxProfileError increments the selinux profile error counter for the
+// provided reason.
+func (m *Metrics) IncSelinuxProfileError(reason event.Reason) {
+	m.metricSelinuxProfileError.WithLabelValues(string(reason)).Inc()
+}
diff --git a/internal/pkg/daemon/selinuxprofile/selinuxpolicy_controller.go b/internal/pkg/daemon/selinuxprofile/selinuxpolicy_controller.go
index c480c30f0..db16a20ab 100644
--- a/internal/pkg/daemon/selinuxprofile/selinuxpolicy_controller.go
+++ b/internal/pkg/daemon/selinuxprofile/selinuxpolicy_controller.go
@@ -43,6 +43,7 @@ import (
 	selinuxprofilev1alpha1 "sigs.k8s.io/security-profiles-operator/api/selinuxprofile/v1alpha1"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/config"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/controller"
+	"sigs.k8s.io/security-profiles-operator/internal/pkg/daemon/metrics"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/manager/spod/bindata"
 	"sigs.k8s.io/security-profiles-operator/internal/pkg/nodestatus"
 )
@@ -101,6 +102,7 @@ type ReconcileSP struct {
 	scheme         *runtime.Scheme
 	policyTemplate *template.Template
 	record         event.Recorder
+	metrics        *metrics.Metrics
 }
 
 // Name returns the name of the controller.
@@ -163,6 +165,7 @@ func (r *ReconcileSP) Reconcile(_ context.Context, request reconcile.Request) (r
 
 	if err := nodeStatus.SetNodeStatus(context.TODO(), statusv1alpha1.ProfileStateTerminating); err != nil {
 		reqLogger.Error(err, "cannot update SELinux profile status")
+		r.metrics.IncSelinuxProfileError(reasonCannotUpdatePolicyStatus)
 		r.record.Event(instance, event.Warning(reasonCannotUpdatePolicyStatus, err))
 		return reconcile.Result{}, errors.Wrap(err, "updating status for deleted SELinux profile")
 	}
@@ -177,12 +180,14 @@ func (r *ReconcileSP) Reconcile(_ context.Context, request reconcile.Request) (r
 
 	res, err := r.reconcileDeletePolicy(instance, nodeStatus, reqLogger)
 	if res.Requeue || err != nil {
+		r.metrics.IncSelinuxProfileError(reasonCannotRemovePolicy)
 		r.record.Event(instance, event.Warning(reasonCannotRemovePolicy, err))
 		return res, err
 	}
 
 	if err := nodeStatus.Remove(context.TODO(), r.client); err != nil {
 		reqLogger.Error(err, "cannot remove finalizer from SELinux profile")
+		r.metrics.IncSelinuxProfileError(reasonCannotUpdatePolicyStatus)
 		r.record.Event(instance, event.Warning(reasonCannotUpdatePolicyStatus, err))
 		return ctrl.Result{}, errors.Wrap(err, "deleting finalizer for deleted SELinux profile")
 	}
@@ -195,6 +200,7 @@ func (r *ReconcileSP) reconcilePolicy(
 ) (reconcile.Result, error) {
 	selinuxdReady, err := isSelinuxdReady()
 	if err != nil {
+		r.metrics.IncSelinuxProfileError(reasonCannotContactSelinuxd)
 		r.record.Event(sp, event.Warning(reasonCannotContactSelinuxd, err))
 		return reconcile.Result{}, errors.Wrap(err, "contacting selinuxd")
 	}
@@ -206,6 +212,7 @@ func (r *ReconcileSP) reconcilePolicy(
 
 	err = r.reconcilePolicyFile(sp, l)
 	if err != nil {
+		r.metrics.IncSelinuxProfileError(reasonCannotWritePolicyFile)
 		r.record.Event(sp, event.Warning(reasonCannotWritePolicyFile, err))
 		return reconcile.Result{}, errors.Wrap(err, "Creating policy file")
 	}
@@ -213,12 +220,14 @@ func (r *ReconcileSP) reconcilePolicy(
 	l.Info("Checking if policy deployed", "policyName", sp.Name)
 	polStatus, err := getPolicyStatus(sp)
 	if err != nil {
+		r.metrics.IncSelinuxProfileError(reasonCannotGetPolicyStatus)
 		r.record.Event(sp, event.Warning(reasonCannotGetPolicyStatus, err))
 		return reconcile.Result{}, errors.Wrap(err, "Looking up policy status")
 	}
 
 	if polStatus == nil {
 		if err := nodeStatus.SetNodeStatus(context.TODO(), statusv1alpha1.ProfileStateInProgress); err != nil {
+			r.metrics.IncSelinuxProfileError(reasonCannotUpdatePolicyStatus)
 			r.record.Event(sp, event.Warning(reasonCannotUpdatePolicyStatus, err))
 			return reconcile.Result{}, errors.Wrap(err, "setting node status to in progress")
 		}
@@ -231,16 +240,19 @@ func (r *ReconcileSP) reconcilePolicy(
 	case installedStatus:
 		polState = statusv1alpha1.ProfileStateInstalled
 		evstr := fmt.Sprintf("Successfully saved profile to disk on %s", os.Getenv(config.NodeNameEnvKey))
+		r.metrics.IncSelinuxProfileUpdate()
 		r.record.Event(sp, event.Normal(reasonInstalledPolicy, evstr))
 	case failedStatus:
 		polState = statusv1alpha1.ProfileStateError
-		evstr := fmt.Sprintf("Successfully saved profile to disk on %s", os.Getenv(config.NodeNameEnvKey))
+		evstr := fmt.Sprintf("Failed to save profile to disk on %s", os.Getenv(config.NodeNameEnvKey))
+		r.metrics.IncSelinuxProfileError(reasonCannotInstallPolicy)
 		r.record.Event(sp, event.Warning(reasonCannotInstallPolicy, errors.New(evstr)))
 	}
 
 	l.Info("Policy deployed", "status", polState)
 
 	if err := nodeStatus.SetNodeStatus(context.TODO(), polState); err != nil {
+		r.metrics.IncSelinuxProfileError(reasonCannotUpdatePolicyStatus)
 		r.record.Event(sp, event.Warning(reasonCannotUpdatePolicyStatus, err))
 		return reconcile.Result{}, errors.Wrap(err, "setting profile status")
 	}
@@ -309,6 +321,7 @@ func (r *ReconcileSP) reconcileDeletePolicy(
 	l.Info("Checking if policy is removed", "policyName", sp.Name)
 	polStatus, err := getPolicyStatus(sp)
 	if err != nil {
+		r.metrics.IncSelinuxProfileError(reasonCannotGetPolicyStatus)
 		return reconcile.Result{}, errors.Wrap(err, "Looking up policy status")
 	}
 
@@ -319,13 +332,15 @@ func (r *ReconcileSP) reconcileDeletePolicy(
 			return reconcile.Result{Requeue: true}, nil
 		case failedStatus:
 			if err := nodeStatus.SetNodeStatus(context.TODO(), statusv1alpha1.ProfileStateError); err != nil {
-				return reconcile.Result{}, errors.Wrap(err, "Updating SELinux policy with installation success")
+				r.metrics.IncSelinuxProfileError(reasonCannotRemovePolicy)
+				return reconcile.Result{}, errors.Wrap(err, "Updating SELinux policy with installation")
 			}
 
 			return reconcile.Result{}, nil
 		}
 	}
 
+	r.metrics.IncSelinuxProfileDelete()
 	l.Info("Policy removed")
 	return reconcile.Result{}, nil
 }
diff --git a/internal/pkg/daemon/selinuxprofile/setup.go b/internal/pkg/daemon/selinuxprofile/setup.go
index dc69b8f5a..c2bb9df35 100644
--- a/internal/pkg/daemon/selinuxprofile/setup.go
+++ b/internal/pkg/daemon/selinuxprofile/setup.go
@@ -47,6 +47,7 @@ func (r *ReconcileSP) Setup(
 	r.scheme = mgr.GetScheme()
 	r.policyTemplate = tmpl
 	r.record = event.NewAPIRecorder(mgr.GetEventRecorderFor("selinuxprofile"))
+	r.metrics = met
 
 	// Register the regular reconciler to manage SelinuxProfiles
 	return ctrl.NewControllerManagedBy(mgr).


Test patch:
diff --git a/internal/pkg/daemon/metrics/metrics_test.go b/internal/pkg/daemon/metrics/metrics_test.go
index 9ef0aea62..dbca1f975 100644
--- a/internal/pkg/daemon/metrics/metrics_test.go
+++ b/internal/pkg/daemon/metrics/metrics_test.go
@@ -82,7 +82,7 @@ func TestSeccompProfile(t *testing.T) {
 				m.IncSeccompProfileUpdate()
 			},
 			then: func(m *Metrics) {
-				ctr, err := m.metricSeccompProfile.GetMetricWithLabelValues(metricLabelValueSeccompProfileUpdate)
+				ctr, err := m.metricSeccompProfile.GetMetricWithLabelValues(metricLabelValueProfileUpdate)
 				require.Nil(t, err)
 				require.Equal(t, 1, getMetricValue(ctr))
 			},
@@ -92,7 +92,7 @@ func TestSeccompProfile(t *testing.T) {
 				m.IncSeccompProfileDelete()
 			},
 			then: func(m *Metrics) {
-				ctr, err := m.metricSeccompProfile.GetMetricWithLabelValues(metricLabelValueSeccompProfileDelete)
+				ctr, err := m.metricSeccompProfile.GetMetricWithLabelValues(metricLabelValueProfileDelete)
 				require.Nil(t, err)
 				require.Equal(t, 1, getMetricValue(ctr))
 			},
@@ -106,11 +106,49 @@ func TestSeccompProfile(t *testing.T) {
 				m.IncSeccompProfileDelete()
 			},
 			then: func(m *Metrics) {
-				ctrUpdate, err := m.metricSeccompProfile.GetMetricWithLabelValues(metricLabelValueSeccompProfileUpdate)
+				ctrUpdate, err := m.metricSeccompProfile.GetMetricWithLabelValues(metricLabelValueProfileUpdate)
 				require.Nil(t, err)
 				require.Equal(t, 3, getMetricValue(ctrUpdate))
 
-				ctrDelete, err := m.metricSeccompProfile.GetMetricWithLabelValues(metricLabelValueSeccompProfileDelete)
+				ctrDelete, err := m.metricSeccompProfile.GetMetricWithLabelValues(metricLabelValueProfileDelete)
+				require.Nil(t, err)
+				require.Equal(t, 2, getMetricValue(ctrDelete))
+			},
+		},
+		{ // Selinux single update
+			when: func(m *Metrics) {
+				m.IncSelinuxProfileUpdate()
+			},
+			then: func(m *Metrics) {
+				ctr, err := m.metricSelinuxProfile.GetMetricWithLabelValues(metricLabelValueProfileUpdate)
+				require.Nil(t, err)
+				require.Equal(t, 1, getMetricValue(ctr))
+			},
+		},
+		{ // Selinux single delete
+			when: func(m *Metrics) {
+				m.IncSelinuxProfileDelete()
+			},
+			then: func(m *Metrics) {
+				ctr, err := m.metricSelinuxProfile.GetMetricWithLabelValues(metricLabelValueProfileDelete)
+				require.Nil(t, err)
+				require.Equal(t, 1, getMetricValue(ctr))
+			},
+		},
+		{ // Selinux multiple update and delete
+			when: func(m *Metrics) {
+				m.IncSelinuxProfileUpdate()
+				m.IncSelinuxProfileUpdate()
+				m.IncSelinuxProfileDelete()
+				m.IncSelinuxProfileUpdate()
+				m.IncSelinuxProfileDelete()
+			},
+			then: func(m *Metrics) {
+				ctrUpdate, err := m.metricSelinuxProfile.GetMetricWithLabelValues(metricLabelValueProfileUpdate)
+				require.Nil(t, err)
+				require.Equal(t, 3, getMetricValue(ctrUpdate))
+
+				ctrDelete, err := m.metricSelinuxProfile.GetMetricWithLabelValues(metricLabelValueProfileDelete)
 				require.Nil(t, err)
 				require.Equal(t, 2, getMetricValue(ctrDelete))
 			},
diff --git a/test/e2e_test.go b/test/e2e_test.go
index 905eba44c..22615b16b 100644
--- a/test/e2e_test.go
+++ b/test/e2e_test.go
@@ -80,7 +80,7 @@ func (e *e2e) TestSecurityProfilesOperator() {
 		},
 		{
 			"Seccomp: Metrics",
-			e.testCaseMetrics,
+			e.testCaseSeccompMetrics,
 		},
 		{
 			"Seccomp: Re-deploy the operator",
@@ -94,6 +94,10 @@ func (e *e2e) TestSecurityProfilesOperator() {
 			"SELinux: base case (install policy, run pod and delete)",
 			e.testCaseSelinuxBaseUsage,
 		},
+		{
+			"SELinux: Metrics (update, delete)",
+			e.testCaseSelinuxMetrics,
+		},
 		{
 			"SPOD: Update SELinux flag",
 			e.testCaseSPODUpdateSelinux,
diff --git a/test/suite_test.go b/test/suite_test.go
index a36aff077..a3ae45e43 100644
--- a/test/suite_test.go
+++ b/test/suite_test.go
@@ -452,7 +452,7 @@ func (e *e2e) logf(format string, a ...interface{}) {
 	e.logger.Info(fmt.Sprintf(format, a...))
 }
 
-func (e *e2e) selinuxtOnlyTestCase() {
+func (e *e2e) selinuxOnlyTestCase() {
 	if !e.selinuxEnabled {
 		e.T().Skip("Skipping SELinux-related test")
 	}
diff --git a/test/tc_metrics_test.go b/test/tc_metrics_test.go
index a32f42e51..45cba4fe6 100644
--- a/test/tc_metrics_test.go
+++ b/test/tc_metrics_test.go
@@ -23,15 +23,18 @@ import (
 	"strings"
 )
 
-func (e *e2e) testCaseMetrics(nodes []string) {
+const (
+	curlCMD     = "curl -ks -H \"Authorization: Bearer `cat /var/run/secrets/kubernetes.io/serviceaccount/token`\" "
+	metricsURL  = "https://metrics.security-profiles-operator/"
+	curlSpodCMD = curlCMD + metricsURL + "metrics-spod"
+	curlCtrlCMD = curlCMD + metricsURL + "metrics"
+	profileName = "metrics-profile"
+)
+
+func (e *e2e) testCaseSeccompMetrics(nodes []string) {
 	e.seccompOnlyTestCase()
 
 	const (
-		curlCMD         = "curl -ks -H \"Authorization: Bearer `cat /var/run/secrets/kubernetes.io/serviceaccount/token`\" "
-		metricsURL      = "https://metrics.security-profiles-operator/"
-		curlSpodCMD     = curlCMD + metricsURL + "metrics-spod"
-		curlCtrlCMD     = curlCMD + metricsURL + "metrics"
-		profileName     = "metrics-profile"
 		operationDelete = `security_profiles_operator_seccomp_profile_total{operation="delete"}`
 		operationUpdate = `security_profiles_operator_seccomp_profile_total{operation="update"}`
 	)
@@ -74,6 +77,46 @@ spec:
 	e.GreaterOrEqual(newMetricUpdates, metricUpdates)
 }
 
+func (e *e2e) testCaseSelinuxMetrics(nodes []string) {
+	e.selinuxOnlyTestCase()
+
+	const (
+		operationDelete = `security_profiles_operator_selinux_profile_total{operation="delete"}`
+		operationUpdate = `security_profiles_operator_selinux_profile_total{operation="update"}`
+	)
+
+	e.logf("Retrieving spo metrics for getting assertions")
+	output := e.kubectlRunOperatorNS("pod", "--", "bash", "-c", curlSpodCMD)
+	metricDeletions := e.parseMetric(output, operationDelete)
+	metricUpdates := e.parseMetric(output, operationUpdate)
+
+	e.logf("Creating test errorlogger policy")
+	cleanup := e.writeAndCreate(errorloggerPolicy, "errorlogger-policy.yml")
+	defer cleanup()
+	e.logf("Waiting for profile to be reconciled")
+	e.kubectl("wait", "--timeout", defaultSelinuxOpTimeout,
+		"--for", "condition=ready", "selinuxprofile", "errorlogger")
+
+	rawPolicyName := e.getSELinuxPolicyName("errorlogger")
+	e.logf("assert errorlogger policy is installed")
+	e.assertSelinuxPolicyIsInstalled(nodes, rawPolicyName, maxNodeIterations, sleepBetweenIterations)
+
+	e.logf("Deleting errorlogger profile")
+	e.kubectl("delete", "selinuxprofile", "errorlogger")
+	e.logf("assert errorlogger policy was removed")
+	e.assertSelinuxPolicyIsInstalled(nodes, rawPolicyName, maxNodeIterations, sleepBetweenIterations)
+
+	e.logf("Retrieving spo metrics for validation")
+	outputSpod := e.kubectlRunOperatorNS("pod", "--", "bash", "-c", curlSpodCMD)
+	e.Contains(outputSpod, "promhttp_metric_handler_requests_total")
+
+	e.logf("Asserting metrics values")
+	newMetricDeletions := e.parseMetric(outputSpod, operationDelete)
+	newMetricUpdates := e.parseMetric(outputSpod, operationUpdate)
+	e.GreaterOrEqual(newMetricDeletions, metricDeletions)
+	e.GreaterOrEqual(newMetricUpdates, metricUpdates)
+}
+
 func (e *e2e) parseMetric(content, metric string) int {
 	scanner := bufio.NewScanner(strings.NewReader(content))
 	for scanner.Scan() {
diff --git a/test/tc_selinux_base_usage_test.go b/test/tc_selinux_base_usage_test.go
index fadbf7db3..e8efdf8b7 100644
--- a/test/tc_selinux_base_usage_test.go
+++ b/test/tc_selinux_base_usage_test.go
@@ -22,14 +22,24 @@ import (
 	"time"
 )
 
-func (e *e2e) testCaseSelinuxBaseUsage(nodes []string) {
-	e.selinuxtOnlyTestCase()
-
-	const maxNodeIterations = 3
-	const sleepBetweenIterations = 5 * time.Second
+const (
+	maxNodeIterations      = 3
+	sleepBetweenIterations = 5 * time.Second
+	errorloggerPolicy      = `
+apiVersion: security-profiles-operator.x-k8s.io/v1alpha1
+kind: SelinuxProfile
+metadata:
+  name: errorlogger
+spec:
+  policy: |
+    (blockinherit container)
+    (allow process var_log_t ( dir ( open read getattr lock search ioctl add_name remove_name write )))
+    (allow process var_log_t ( file ( getattr read write append ioctl lock map open create  )))
+    (allow process var_log_t ( sock_file ( getattr read write append open  )))
+`
 
 	// nolint:lll
-	const podWithPolicyFmt = `
+	podWithPolicyFmt = `
 apiVersion: v1
 kind: Pod
 metadata:
@@ -53,19 +63,10 @@ spec:
       path: /var/log
       type: Directory
 `
+)
 
-	const errorloggerPolicy = `
-apiVersion: security-profiles-operator.x-k8s.io/v1alpha1
-kind: SelinuxProfile
-metadata:
-  name: errorlogger
-spec:
-  policy: |
-    (blockinherit container)
-    (allow process var_log_t ( dir ( open read getattr lock search ioctl add_name remove_name write ))) 
-    (allow process var_log_t ( file ( getattr read write append ioctl lock map open create  ))) 
-    (allow process var_log_t ( sock_file ( getattr read write append open  ))) 
-`
+func (e *e2e) testCaseSelinuxBaseUsage(nodes []string) {
+	e.selinuxOnlyTestCase()
 
 	e.logf("The 'errorlogger' workload should be able to use SELinux policy")
 
@@ -79,28 +80,7 @@ spec:
 	rawPolicyName := e.getSELinuxPolicyName("errorlogger")
 
 	e.logf("assert policy is installed")
-
-	for i := 0; i < maxNodeIterations; i++ {
-		var missingPolName string
-
-		for _, node := range nodes {
-			policiesRaw := e.execNode(node, "semodule", "-l")
-			if !e.sliceContainsString(strings.Split(policiesRaw, "\n"), rawPolicyName) {
-				missingPolName = node
-				break
-			}
-		}
-
-		if missingPolName != "" {
-			if i == maxNodeIterations-1 {
-				e.Failf("The SelinuxProfile errorlogger wasn't found in the %s node with the name %s",
-					missingPolName, rawPolicyName)
-			} else {
-				e.logf("the policy was stil present, trying again")
-				time.Sleep(sleepBetweenIterations)
-			}
-		}
-	}
+	e.assertSelinuxPolicyIsInstalled(nodes, rawPolicyName, maxNodeIterations, sleepBetweenIterations)
 
 	e.logf("creating workload")
 
@@ -125,23 +105,52 @@ spec:
 	e.kubectl("delete", "selinuxprofile", "errorlogger")
 
 	e.logf("assert policy was removed")
-	for i := 0; i < maxNodeIterations; i++ {
-		var nodeHasPolName string
+	e.assertSelinuxPolicyIsRemoved(nodes, rawPolicyName, maxNodeIterations, sleepBetweenIterations)
+}
+
+func (e *e2e) assertSelinuxPolicyIsInstalled(nodes []string, policy string, nodeIterations int, sleep time.Duration) {
+	for i := 0; i < nodeIterations; i++ {
+		var missingPolName string
+
+		for _, node := range nodes {
+			policiesRaw := e.execNode(node, "semodule", "-l")
+			if !e.sliceContainsString(strings.Split(policiesRaw, "\n"), policy) {
+				missingPolName = node
+				break
+			}
+		}
+
+		if missingPolName != "" {
+			if i == nodeIterations-1 {
+				e.Failf("The SelinuxProfile errorlogger wasn't found in the %s node with the name %s",
+					missingPolName, policy)
+			} else {
+				e.logf("the policy was stil present, trying again")
+				time.Sleep(sleep)
+			}
+		}
+	}
+}
+
+func (e *e2e) assertSelinuxPolicyIsRemoved(nodes []string, policy string, nodeIterations int, sleep time.Duration) {
+	for i := 0; i < nodeIterations; i++ {
+		var missingPolName string
 
 		for _, node := range nodes {
 			policiesRaw := e.execNode(node, "semodule", "-l")
-			if e.sliceContainsString(strings.Split(policiesRaw, "\n"), rawPolicyName) {
-				nodeHasPolName = node
+			if e.sliceContainsString(strings.Split(policiesRaw, "\n"), policy) {
+				missingPolName = node
 				break
 			}
 		}
 
-		if nodeHasPolName != "" {
-			if i == maxNodeIterations-1 {
-				e.Failf("The SelinuxProfile errorlogger should have been removed from %s node", nodeHasPolName)
+		if missingPolName != "" {
+			if i == nodeIterations-1 {
+				e.Failf("The SelinuxProfile errorlogger was found in the %s node with the name %s",
+					missingPolName, policy)
 			} else {
 				e.logf("the policy was stil present, trying again")
-				time.Sleep(sleepBetweenIterations)
+				time.Sleep(sleep)
 			}
 		}
 	}
diff --git a/test/tc_selinux_sanity_check_test.go b/test/tc_selinux_sanity_check_test.go
index b6e1ac937..7340f888d 100644
--- a/test/tc_selinux_sanity_check_test.go
+++ b/test/tc_selinux_sanity_check_test.go
@@ -17,7 +17,7 @@ limitations under the License.
 package e2e_test
 
 func (e *e2e) testCaseSelinuxSanityCheck([]string) {
-	e.selinuxtOnlyTestCase()
+	e.selinuxOnlyTestCase()
 
 	const podWithoutPolicy = `
 apiVersion: v1
diff --git a/test/tc_spod_update_selinux_test.go b/test/tc_spod_update_selinux_test.go
index 3ef9292c1..36a8946a7 100644
--- a/test/tc_spod_update_selinux_test.go
+++ b/test/tc_spod_update_selinux_test.go
@@ -19,7 +19,7 @@ package e2e_test
 import "time"
 
 func (e *e2e) testCaseSPODUpdateSelinux(nodes []string) {
-	e.selinuxtOnlyTestCase()
+	e.selinuxOnlyTestCase()
 
 	e.logf("assert selinux is enabled in the spod object")
 	selinuxEnabledInSPODObj := e.kubectlOperatorNS("get", "spod", "spod", "-o", "jsonpath={.spec.enableSelinux}")