Repository: Azure/ARO-RP
Pull request #2091 (2022-04-27 11:18:43)

add minor version
### Which issue this PR addresses:

We need a quick and live way to check the list of OCP minor versions along with the resourceID and subscriptionID to easily identify out of support lifecycle clusters. Due to Geneva limitations, we need to provide the minor version along with the full version to be able to generate corresponding report on Geneva side. 

Also pushes the GA version to allow out of lifecycle detection from Geneva

### What this PR does / why we need it:

We need a quick and live way to check the list of OCP minor versions along with the resourceID and subscriptionID to easily identify out of support lifecycle clusters. Due to Geneva limitations, we need to provide the minor version along with the full version to be able to generate corresponding report on Geneva side. 

Also pushes the GA version to allow out of lifecycle detection from Geneva

### Test plan for issue:

- Are there unit tests? yes
- Are there integration/e2e tests? no

### Is there any documentation that needs to be updated for this PR?



gvanderpotte: > lgtm only concern is another column which our log aggregation seems to have trouble with

I think that log column aggregation and metric column aggregation are different processes, so we should be safe here. That said, this does not prevent us from doing cleaning of unused pre-aggregates
gvanderpotte: Added push of latest GA version from RP perspective. It is not super optimal from pure monitor perspective, as this could be pushed as an RP metric (as it is the same for all clusters in a given region). That said, this would force us to make a join on Geneva side (between metrics from different accounts), while the goal here is to have an easy way to detect to detect out of support lifecycle clusters
gvanderpotte: /azp run e2e
azure-pipelines[bot]: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>

Patch:
diff --git a/docs/upstream-differences.md b/docs/upstream-differences.md
index 1dbc92a10..f42a9a2c3 100644
--- a/docs/upstream-differences.md
+++ b/docs/upstream-differences.md
@@ -88,3 +88,5 @@ Once installer fork is ready:
 1. After this point, you should be able to create a dev cluster using the RP and it should use the new release.
 1. `make discoverycache`.
     * This command requires a running cluster with the new version.
+1. The list of the hard-coded namespaces in `pkg/util/namespace/namespace.go` needs to be updated regularly as every
+   minor version of upstream OCP introduces a new namespace or two.
\ No newline at end of file
diff --git a/pkg/frontend/validate.go b/pkg/frontend/validate.go
index 71c4defb8..bd804995e 100644
--- a/pkg/frontend/validate.go
+++ b/pkg/frontend/validate.go
@@ -77,7 +77,7 @@ func (f *frontend) validateOpenShiftUniqueKey(ctx context.Context, doc *api.Open
 var rxKubernetesString = regexp.MustCompile(`(?i)^[-a-z0-9.]{0,255}$`)
 
 func validateAdminKubernetesObjectsNonCustomer(method, groupKind, namespace, name string) error {
-	if !utilnamespace.IsOpenShift(namespace) {
+	if !utilnamespace.IsOpenShiftNamespace(namespace) {
 		return api.NewCloudError(http.StatusForbidden, api.CloudErrorCodeForbidden, "", "Access to the provided namespace '%s' is forbidden.", namespace)
 	}
 
diff --git a/pkg/monitor/cluster/clusterversions.go b/pkg/monitor/cluster/clusterversions.go
index cf85a4961..804c50d99 100644
--- a/pkg/monitor/cluster/clusterversions.go
+++ b/pkg/monitor/cluster/clusterversions.go
@@ -38,14 +38,26 @@ func (mon *Monitor) emitClusterVersions(ctx context.Context) error {
 		availableRP = version.GitCommit
 	}
 
+	actualVersion := actualVersion(cv)
+	actualMinorVersion := ""
+	if len(actualVersion) > 0 {
+		parsedVersion, err := version.ParseVersion(actualVersion)
+		if err != nil {
+			return err
+		}
+		actualMinorVersion = parsedVersion.MinorVersion()
+	}
+
 	mon.emitGauge("cluster.versions", 1, map[string]string{
-		"actualVersion":                        actualVersion(cv),
+		"actualVersion":                        actualVersion,
 		"desiredVersion":                       desiredVersion(cv),
 		"provisionedByResourceProviderVersion": mon.oc.Properties.ProvisionedBy,              // last successful Put or Patch
 		"resourceProviderVersion":              version.GitCommit,                            // RP version currently running
 		"operatorVersion":                      operatorVersion,                              // operator version in the cluster
 		"availableVersion":                     availableVersion(cv, version.UpgradeStreams), // current available version for upgrade from stream
 		"availableRP":                          availableRP,                                  // current RP version available for document update, empty when none
+		"latestGaMinorVersion":                 version.InstallStream.Version.MinorVersion(), // Latest GA in ARO Minor version
+		"actualMinorVersion":                   actualMinorVersion,                           // Minor version, empty if actual version is not in expected form
 	})
 
 	return nil
diff --git a/pkg/monitor/cluster/daemonsetstatuses.go b/pkg/monitor/cluster/daemonsetstatuses.go
index f7869d2ed..9d1db8fdb 100644
--- a/pkg/monitor/cluster/daemonsetstatuses.go
+++ b/pkg/monitor/cluster/daemonsetstatuses.go
@@ -24,7 +24,7 @@ func (mon *Monitor) emitDaemonsetStatuses(ctx context.Context) error {
 		count += int64(len(dss.Items))
 
 		for _, ds := range dss.Items {
-			if !namespace.IsOpenShift(ds.Namespace) {
+			if !namespace.IsOpenShiftNamespace(ds.Namespace) {
 				continue
 			}
 
diff --git a/pkg/monitor/cluster/deploymentstatuses.go b/pkg/monitor/cluster/deploymentstatuses.go
index ed02e290b..771fe8989 100644
--- a/pkg/monitor/cluster/deploymentstatuses.go
+++ b/pkg/monitor/cluster/deploymentstatuses.go
@@ -24,7 +24,7 @@ func (mon *Monitor) emitDeploymentStatuses(ctx context.Context) error {
 		count += int64(len(ds.Items))
 
 		for _, d := range ds.Items {
-			if !namespace.IsOpenShift(d.Namespace) {
+			if !namespace.IsOpenShiftNamespace(d.Namespace) {
 				continue
 			}
 
diff --git a/pkg/monitor/cluster/jobconditions.go b/pkg/monitor/cluster/jobconditions.go
index f45104ca3..9941f9380 100644
--- a/pkg/monitor/cluster/jobconditions.go
+++ b/pkg/monitor/cluster/jobconditions.go
@@ -30,7 +30,7 @@ func (mon *Monitor) emitJobConditions(ctx context.Context) error {
 		count += int64(len(jobs.Items))
 
 		for _, job := range jobs.Items {
-			if !namespace.IsOpenShift(job.Namespace) {
+			if !namespace.IsOpenShiftNamespace(job.Namespace) {
 				continue
 			}
 
diff --git a/pkg/monitor/cluster/podconditions.go b/pkg/monitor/cluster/podconditions.go
index 119db6454..48956d19b 100644
--- a/pkg/monitor/cluster/podconditions.go
+++ b/pkg/monitor/cluster/podconditions.go
@@ -51,7 +51,7 @@ func (mon *Monitor) emitPodConditions(ctx context.Context) error {
 
 func (mon *Monitor) _emitPodConditions(ps *corev1.PodList) {
 	for _, p := range ps.Items {
-		if !namespace.IsOpenShift(p.Namespace) {
+		if !namespace.IsOpenShiftNamespace(p.Namespace) {
 			continue
 		}
 
@@ -89,7 +89,7 @@ func (mon *Monitor) _emitPodConditions(ps *corev1.PodList) {
 
 func (mon *Monitor) _emitPodContainerStatuses(ps *corev1.PodList) {
 	for _, p := range ps.Items {
-		if !namespace.IsOpenShift(p.Namespace) {
+		if !namespace.IsOpenShiftNamespace(p.Namespace) {
 			continue
 		}
 
@@ -126,7 +126,7 @@ func (mon *Monitor) _emitPodContainerStatuses(ps *corev1.PodList) {
 
 func (mon *Monitor) _emitPodContainerRestartCounter(ps *corev1.PodList) {
 	for _, p := range ps.Items {
-		if !namespace.IsOpenShiftSystemNamespace(p.Namespace) {
+		if !namespace.IsOpenShiftNamespace(p.Namespace) {
 			continue
 		}
 
diff --git a/pkg/monitor/cluster/prometheusalerts.go b/pkg/monitor/cluster/prometheusalerts.go
index c0208e981..8df500943 100644
--- a/pkg/monitor/cluster/prometheusalerts.go
+++ b/pkg/monitor/cluster/prometheusalerts.go
@@ -80,7 +80,7 @@ func (mon *Monitor) emitPrometheusAlerts(ctx context.Context) error {
 	mon.emitGauge("prometheus.alerts.count", int64(len(alerts)), nil)
 
 	for _, alert := range alerts {
-		if !namespace.IsOpenShift(string(alert.Labels["namespace"])) {
+		if !namespace.IsOpenShiftNamespace(string(alert.Labels["namespace"])) {
 			continue
 		}
 
diff --git a/pkg/monitor/cluster/replicasetstatuses.go b/pkg/monitor/cluster/replicasetstatuses.go
index fc5fc8dc2..1b9d30bbd 100644
--- a/pkg/monitor/cluster/replicasetstatuses.go
+++ b/pkg/monitor/cluster/replicasetstatuses.go
@@ -24,7 +24,7 @@ func (mon *Monitor) emitReplicasetStatuses(ctx context.Context) error {
 		count += int64(len(rss.Items))
 
 		for _, rs := range rss.Items {
-			if !namespace.IsOpenShift(rs.Namespace) {
+			if !namespace.IsOpenShiftNamespace(rs.Namespace) {
 				continue
 			}
 
diff --git a/pkg/monitor/cluster/statefulsetstatuses.go b/pkg/monitor/cluster/statefulsetstatuses.go
index d348eec7d..57a95db9c 100644
--- a/pkg/monitor/cluster/statefulsetstatuses.go
+++ b/pkg/monitor/cluster/statefulsetstatuses.go
@@ -24,7 +24,7 @@ func (mon *Monitor) emitStatefulsetStatuses(ctx context.Context) error {
 		count += int64(len(sss.Items))
 
 		for _, ss := range sss.Items {
-			if !namespace.IsOpenShift(ss.Namespace) {
+			if !namespace.IsOpenShiftNamespace(ss.Namespace) {
 				continue
 			}
 
diff --git a/pkg/util/namespace/namespace.go b/pkg/util/namespace/namespace.go
index fd7fd785c..3b9a98979 100644
--- a/pkg/util/namespace/namespace.go
+++ b/pkg/util/namespace/namespace.go
@@ -3,23 +3,74 @@ package namespace
 // Copyright (c) Microsoft Corporation.
 // Licensed under the Apache License 2.0.
 
-import (
-	"strings"
-)
+// IsOpenShiftNamespace returns true if ns is a namespace in the defined hardcoded map.
+// We should only add new namespaces into this hardcoded list but never delete
+// the existing ones in the namespace list to avoid backward compatibility issues.
+func IsOpenShiftNamespace(ns string) bool {
+	nsmap := map[string]struct{}{
+		// Allow validation against non-namespaced objects via Geneva actions
+		"": {},
 
-// IsOpenShift returns true if ns is an openshift managed namespace, including the default namespace
-func IsOpenShift(ns string) bool {
-	return ns == "" ||
-		ns == "default" ||
-		ns == "openshift" ||
-		strings.HasPrefix(ns, "kube-") ||
-		strings.HasPrefix(ns, "openshift-")
-}
+		// ARO specific namespaces
+		"openshift-azure-logging":            {},
+		"openshift-azure-operator":           {},
+		"openshift-managed-upgrade-operator": {},
 
-// IsOpenShiftSystemNamespace returns true if ns is an openshift managed namespace, without the default namespace
-func IsOpenShiftSystemNamespace(ns string) bool {
-	return ns == "" ||
-		ns == "openshift" ||
-		strings.HasPrefix(ns, "kube-") ||
-		strings.HasPrefix(ns, "openshift-")
+		// OCP namespaces
+		"openshift":                                        {},
+		"openshift-apiserver":                              {},
+		"openshift-apiserver-operator":                     {},
+		"openshift-authentication-operator":                {},
+		"openshift-cloud-controller-manager":               {},
+		"openshift-cloud-controller-manager-operator":      {},
+		"openshift-cloud-credential-operator":              {},
+		"openshift-cluster-csi-drivers":                    {},
+		"openshift-cluster-machine-approver":               {},
+		"openshift-cluster-node-tuning-operator":           {},
+		"openshift-cluster-samples-operator":               {},
+		"openshift-cluster-storage-operator":               {},
+		"openshift-config":                                 {},
+		"openshift-config-managed":                         {},
+		"openshift-config-operator":                        {},
+		"openshift-console":                                {},
+		"openshift-console-operator":                       {},
+		"openshift-console-user-settings":                  {},
+		"openshift-controller-manager":                     {},
+		"openshift-controller-manager-operator":            {},
+		"openshift-dns":                                    {},
+		"openshift-dns-operator":                           {},
+		"openshift-etcd":                                   {},
+		"openshift-etcd-operator":                          {},
+		"openshift-host-network":                           {},
+		"openshift-image-registry":                         {},
+		"openshift-ingress":                                {},
+		"openshift-ingress-canary":                         {},
+		"openshift-ingress-operator":                       {},
+		"openshift-insights":                               {},
+		"openshift-kni-infra":                              {},
+		"openshift-kube-apiserver":                         {},
+		"openshift-kube-apiserver-operator":                {},
+		"openshift-kube-controller-manager":                {},
+		"openshift-kube-controller-manager-operator":       {},
+		"openshift-kube-scheduler":                         {},
+		"openshift-kube-scheduler-operator":                {},
+		"openshift-kube-storage-version-migrator":          {},
+		"openshift-kube-storage-version-migrator-operator": {},
+		"openshift-machine-api":                            {},
+		"openshift-machine-config-operator":                {},
+		"openshift-marketplace":                            {},
+		"openshift-monitoring":                             {},
+		"openshift-multus":                                 {},
+		"openshift-network-diagnostics":                    {},
+		"openshift-network-operator":                       {},
+		"openshift-oauth-apiserver":                        {},
+		"openshift-openstack-infra":                        {},
+		"openshift-operators":                              {},
+		"openshift-ovirt-infra":                            {},
+		"openshift-sdn":                                    {},
+		"openshift-service-ca":                             {},
+		"openshift-service-ca-operator":                    {},
+	}
+	_, ok := nsmap[ns]
+	return ok
 }
diff --git a/pkg/util/version/version.go b/pkg/util/version/version.go
index cccc8dd68..1820d022b 100644
--- a/pkg/util/version/version.go
+++ b/pkg/util/version/version.go
@@ -77,3 +77,7 @@ func (v *Version) Eq(w *Version) bool {
 	}
 	return true
 }
+
+func (v *Version) MinorVersion() string {
+	return fmt.Sprintf("%d.%d", v.V[0], v.V[1])
+}


Test patch:
diff --git a/pkg/frontend/admin_openshiftcluster_kubernetesobjects_test.go b/pkg/frontend/admin_openshiftcluster_kubernetesobjects_test.go
index 31ec22fcd..bae3bbd56 100644
--- a/pkg/frontend/admin_openshiftcluster_kubernetesobjects_test.go
+++ b/pkg/frontend/admin_openshiftcluster_kubernetesobjects_test.go
@@ -45,7 +45,7 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) {
 			name:         "cluster exist in db - get",
 			resourceID:   fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName", mockSubID),
 			objKind:      "ConfigMap",
-			objNamespace: "openshift-project",
+			objNamespace: "openshift",
 			objName:      "config",
 			mocks: func(tt *test, k *mock_adminactions.MockKubeActions) {
 				k.EXPECT().
@@ -60,7 +60,7 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) {
 			name:         "cluster exist in db - list",
 			resourceID:   fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName", mockSubID),
 			objKind:      "ConfigMap",
-			objNamespace: "openshift-project",
+			objNamespace: "openshift",
 			mocks: func(tt *test, k *mock_adminactions.MockKubeActions) {
 				k.EXPECT().
 					KubeList(gomock.Any(), tt.objKind, tt.objNamespace).
@@ -73,7 +73,7 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) {
 			method:       http.MethodGet,
 			name:         "no groupKind provided",
 			resourceID:   fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName", mockSubID),
-			objNamespace: "openshift-project",
+			objNamespace: "openshift",
 			objName:      "config",
 			mocks: func(tt *test, k *mock_adminactions.MockKubeActions) {
 			},
@@ -85,7 +85,7 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) {
 			name:         "secret requested",
 			resourceID:   fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName", mockSubID),
 			objKind:      "Secret",
-			objNamespace: "openshift-project",
+			objNamespace: "openshift",
 			objName:      "config",
 			mocks: func(tt *test, k *mock_adminactions.MockKubeActions) {
 			},
@@ -97,7 +97,7 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) {
 			name:         "cluster exist in db",
 			resourceID:   fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName", mockSubID),
 			objKind:      "ConfigMap",
-			objNamespace: "openshift-project",
+			objNamespace: "openshift",
 			objName:      "config",
 			mocks: func(tt *test, k *mock_adminactions.MockKubeActions) {
 				k.EXPECT().
@@ -110,7 +110,7 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) {
 			method:       http.MethodDelete,
 			name:         "no groupKind provided",
 			resourceID:   fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName", mockSubID),
-			objNamespace: "openshift-project",
+			objNamespace: "openshift",
 			objName:      "config",
 			mocks: func(tt *test, k *mock_adminactions.MockKubeActions) {
 			},
@@ -122,7 +122,7 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) {
 			name:         "no name provided",
 			resourceID:   fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName", mockSubID),
 			objKind:      "this",
-			objNamespace: "openshift-project",
+			objNamespace: "openshift",
 			mocks: func(tt *test, k *mock_adminactions.MockKubeActions) {
 			},
 			wantStatusCode: http.StatusBadRequest,
@@ -133,7 +133,7 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) {
 			name:         "secret requested",
 			resourceID:   fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName", mockSubID),
 			objKind:      "Secret",
-			objNamespace: "openshift-project",
+			objNamespace: "openshift",
 			objName:      "config",
 			mocks: func(tt *test, k *mock_adminactions.MockKubeActions) {
 			},
@@ -222,21 +222,21 @@ func TestValidateAdminKubernetesObjectsNonCustomer(t *testing.T) {
 		{
 			test:      "invalid groupKind",
 			groupKind: "$",
-			namespace: "openshift-ns",
+			namespace: "openshift",
 			name:      "Valid-NAME-01",
 			wantErr:   "400: InvalidParameter: : The provided groupKind '$' is invalid.",
 		},
 		{
 			test:      "forbidden groupKind",
 			groupKind: "Secret",
-			namespace: "openshift-ns",
+			namespace: "openshift",
 			name:      "Valid-NAME-01",
 			wantErr:   "403: Forbidden: : Access to secrets is forbidden.",
 		},
 		{
 			test:      "forbidden groupKind",
 			groupKind: "Anything.oauth.openshift.io",
-			namespace: "openshift-ns",
+			namespace: "openshift",
 			name:      "Valid-NAME-01",
 			wantErr:   "403: Forbidden: : Access to secrets is forbidden.",
 		},
@@ -266,7 +266,7 @@ func TestValidateAdminKubernetesObjectsNonCustomer(t *testing.T) {
 		},
 		{
 			test:      "empty groupKind",
-			namespace: "openshift-ns",
+			namespace: "openshift",
 			name:      "Valid-NAME-01",
 			wantErr:   "400: InvalidParameter: : The provided groupKind '' is invalid.",
 		},
@@ -275,12 +275,12 @@ func TestValidateAdminKubernetesObjectsNonCustomer(t *testing.T) {
 			groupKind: "Valid-kind.openshift.io",
 			namespace: "openshift-/",
 			name:      "Valid-NAME-01",
-			wantErr:   "400: InvalidParameter: : The provided namespace 'openshift-/' is invalid.",
+			wantErr:   "403: Forbidden: : Access to the provided namespace 'openshift-/' is forbidden.",
 		},
 		{
 			test:      "invalid name",
 			groupKind: "Valid-kind.openshift.io",
-			namespace: "openshift-ns",
+			namespace: "openshift",
 			name:      longName,
 			wantErr:   "400: InvalidParameter: : The provided name '" + longName + "' is invalid.",
 		},
@@ -288,14 +288,14 @@ func TestValidateAdminKubernetesObjectsNonCustomer(t *testing.T) {
 			test:      "post: empty name",
 			method:    http.MethodPost,
 			groupKind: "Valid-kind.openshift.io",
-			namespace: "openshift-ns",
+			namespace: "openshift",
 			wantErr:   "400: InvalidParameter: : The provided name '' is invalid.",
 		},
 		{
 			test:      "delete: empty name",
 			method:    http.MethodDelete,
 			groupKind: "Valid-kind.openshift.io",
-			namespace: "openshift-ns",
+			namespace: "openshift",
 			wantErr:   "400: InvalidParameter: : The provided name '' is invalid.",
 		},
 	} {
@@ -353,6 +353,10 @@ func TestAdminPostKubernetesObjects(t *testing.T) {
 			objInBody: &unstructured.Unstructured{
 				Object: map[string]interface{}{
 					"kind": "Secret",
+					"metadata": map[string]interface{}{
+						"namespace": "openshift",
+						"name":      "secret",
+					},
 				},
 			},
 			mocks:          func(tt *test, k *mock_adminactions.MockKubeActions) {},
diff --git a/pkg/monitor/cluster/clusterversions_test.go b/pkg/monitor/cluster/clusterversions_test.go
index 23fd60838..9ebd186d1 100644
--- a/pkg/monitor/cluster/clusterversions_test.go
+++ b/pkg/monitor/cluster/clusterversions_test.go
@@ -44,6 +44,7 @@ func TestEmitClusterVersion(t *testing.T) {
 		wantProvisionedByResourceProviderVersion string
 		wantAvailableVersion                     string
 		wantAvailableRP                          string
+		wantActualMinorVersion                   string
 	}{
 		{
 			name: "without spec",
@@ -79,6 +80,7 @@ func TestEmitClusterVersion(t *testing.T) {
 			wantProvisionedByResourceProviderVersion: "",
 			wantAvailableVersion:                     "4.5.39",
 			wantAvailableRP:                          "unknown",
+			wantActualMinorVersion:                   "4.5",
 		},
 		{
 			name: "with spec",
@@ -103,6 +105,7 @@ func TestEmitClusterVersion(t *testing.T) {
 			wantDesiredVersion:                       "4.5.4",
 			wantProvisionedByResourceProviderVersion: "",
 			wantAvailableRP:                          "unknown",
+			wantActualMinorVersion:                   "",
 		},
 		{
 			name: "with ProvisionedBy",
@@ -158,6 +161,8 @@ func TestEmitClusterVersion(t *testing.T) {
 				"resourceProviderVersion":              "unknown",
 				"availableVersion":                     tt.wantAvailableVersion,
 				"availableRP":                          tt.wantAvailableRP,
+				"latestGaMinorVersion":                 version.InstallStream.Version.MinorVersion(),
+				"actualMinorVersion":                   tt.wantActualMinorVersion,
 			})
 
 			err := mon.emitClusterVersions(ctx)
diff --git a/pkg/monitor/cluster/podconditions_test.go b/pkg/monitor/cluster/podconditions_test.go
index e29b1f372..8574bffb4 100644
--- a/pkg/monitor/cluster/podconditions_test.go
+++ b/pkg/monitor/cluster/podconditions_test.go
@@ -235,7 +235,7 @@ func TestEmitPodContainerRestartCounter(t *testing.T) {
 		&corev1.Pod{ // #6 Multi-container pod
 			ObjectMeta: metav1.ObjectMeta{
 				Name:      "multi-container-pod",
-				Namespace: "openshift-test",
+				Namespace: "openshift",
 			},
 			Status: corev1.PodStatus{
 				ContainerStatuses: []corev1.ContainerStatus{
@@ -285,7 +285,7 @@ func TestEmitPodContainerRestartCounter(t *testing.T) {
 
 	m.EXPECT().EmitGauge("pod.restartcounter", int64(restartCounterThreshold*2), map[string]string{
 		"name":      "multi-container-pod",
-		"namespace": "openshift-test",
+		"namespace": "openshift",
 	})
 
 	ps, _ := cli.CoreV1().Pods("").List(context.Background(), metav1.ListOptions{})
diff --git a/pkg/util/dynamichelper/dynamichelper_test.go b/pkg/util/dynamichelper/dynamichelper_test.go
index b5e1791b1..ee14985fd 100644
--- a/pkg/util/dynamichelper/dynamichelper_test.go
+++ b/pkg/util/dynamichelper/dynamichelper_test.go
@@ -11,6 +11,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	kruntime "k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
 
 	"github.com/Azure/ARO-RP/pkg/util/cmp"
 )
@@ -288,3 +289,76 @@ func TestMerge(t *testing.T) {
 		})
 	}
 }
+
+func TestMakeURLSegments(t *testing.T) {
+	for _, tt := range []struct {
+		gvr         *schema.GroupVersionResource
+		namespace   string
+		uname, name string
+		url         []string
+		want        []string
+	}{
+		{
+			uname: "Group is empty",
+			gvr: &schema.GroupVersionResource{
+				Group:    "",
+				Version:  "4.10",
+				Resource: "test-resource",
+			},
+			namespace: "openshift",
+			name:      "test-name-1",
+			want:      []string{"api", "4.10", "namespaces", "openshift", "test-resource", "test-name-1"},
+		},
+		{
+			uname: "Group is not empty",
+			gvr: &schema.GroupVersionResource{
+				Group:    "test-group",
+				Version:  "4.10",
+				Resource: "test-resource",
+			},
+			namespace: "openshift-apiserver",
+			name:      "test-name-2",
+			want:      []string{"apis", "test-group", "4.10", "namespaces", "openshift-apiserver", "test-resource", "test-name-2"},
+		},
+		{
+			uname: "Namespace is empty",
+			gvr: &schema.GroupVersionResource{
+				Group:    "test-group",
+				Version:  "4.10",
+				Resource: "test-resource",
+			},
+			namespace: "",
+			name:      "test-name-3",
+			want:      []string{"apis", "test-group", "4.10", "test-resource", "test-name-3"},
+		},
+		{
+			uname: "Namespace is not empty",
+			gvr: &schema.GroupVersionResource{
+				Group:    "test-group",
+				Version:  "4.10",
+				Resource: "test-resource",
+			},
+			namespace: "openshift-sdn",
+			name:      "test-name-3",
+			want:      []string{"apis", "test-group", "4.10", "namespaces", "openshift-sdn", "test-resource", "test-name-3"},
+		},
+		{
+			uname: "Name is empty",
+			gvr: &schema.GroupVersionResource{
+				Group:    "test-group",
+				Version:  "4.10",
+				Resource: "test-resource",
+			},
+			namespace: "openshift-ns",
+			name:      "",
+			want:      []string{"apis", "test-group", "4.10", "namespaces", "openshift-ns", "test-resource"},
+		},
+	} {
+		t.Run(tt.uname, func(t *testing.T) {
+			got := makeURLSegments(tt.gvr, tt.namespace, tt.name)
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Error(cmp.Diff(got, tt.want))
+			}
+		})
+	}
+}
diff --git a/pkg/util/namespace/namespace_test.go b/pkg/util/namespace/namespace_test.go
index ae2cd2729..23821b025 100644
--- a/pkg/util/namespace/namespace_test.go
+++ b/pkg/util/namespace/namespace_test.go
@@ -7,73 +7,62 @@ import (
 	"testing"
 )
 
-func TestIsOpenShift(t *testing.T) {
+func TestIsOpenShiftNamespace(t *testing.T) {
 	for _, tt := range []struct {
 		namespace string
 		want      bool
 	}{
 		{
-			want: true,
+			namespace: "",
+			want:      true,
 		},
 		{
-			namespace: "openshift-ns",
+			namespace: "openshift-apiserver",
 			want:      true,
 		},
 		{
-			namespace: "openshift",
+			namespace: "openshift-azure-operator",
 			want:      true,
 		},
 		{
-			namespace: "kube-ns",
+			namespace: "openshift-azure-logging",
 			want:      true,
 		},
 		{
-			namespace: "default",
-			want:      true,
+			namespace: "openshift-gitops",
+			want:      false,
 		},
 		{
-			namespace: "customer",
+			namespace: "openshift-authentication",
+			want:      false,
 		},
-	} {
-		t.Run(tt.namespace, func(t *testing.T) {
-			got := IsOpenShift(tt.namespace)
-			if tt.want != got {
-				t.Error(got)
-			}
-		})
-	}
-}
-
-func TestIsOpenShiftSystemNamespace(t *testing.T) {
-	for _, tt := range []struct {
-		namespace string
-		want      bool
-	}{
 		{
-			want: true,
+			namespace: "default",
+			want:      false,
 		},
 		{
-			namespace: "openshift-ns",
-			want:      true,
+			namespace: "customer",
+			want:      false,
 		},
 		{
-			namespace: "openshift",
-			want:      true,
+			namespace: "openshift-/",
+			want:      false,
 		},
 		{
-			namespace: "kube-ns",
-			want:      true,
+			namespace: "openshift-ns",
+			want:      false,
 		},
 		{
-			namespace: "default",
+			namespace: "kube-ns",
 			want:      false,
 		},
 		{
-			namespace: "customer",
+			namespace: "openshift-operator-lifecycle-manager",
+			want:      false,
 		},
 	} {
 		t.Run(tt.namespace, func(t *testing.T) {
-			got := IsOpenShiftSystemNamespace(tt.namespace)
+			got := IsOpenShiftNamespace(tt.namespace)
 			if tt.want != got {
 				t.Error(got)
 			}
diff --git a/test/e2e/adminapi_kubernetesobjects.go b/test/e2e/adminapi_kubernetesobjects.go
index e91302982..7f665e69a 100644
--- a/test/e2e/adminapi_kubernetesobjects.go
+++ b/test/e2e/adminapi_kubernetesobjects.go
@@ -26,7 +26,7 @@ var _ = Describe("[Admin API] Kubernetes objects action", func() {
 	const objName = "e2e-test-object"
 
 	When("in a standard openshift namespace", func() {
-		const namespace = "default"
+		const namespace = "openshift"
 
 		It("should be able to create, get, list, update and delete objects", func() {
 			defer func() {