Repository: Azure/ARO-RP
Pull request #2644 (2023-01-18 17:40:32)

Fix/do not adopt hive created clusters during pucm
### Which issue this PR addresses:

<!--
Please include a link to the ADO work item as well as any GitHub issues.

Usage: `Fixes #<GitHub issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes [Don't adopt Hive-created clusters during PUCM runs](https://issues.redhat.com/browse/ARO-1447).

### What this PR does / why we need it:

<!--
Include a brief summary of what the PR is intended to accomplish and how the PR
does it. (2-3 sentences)
-->

We don't want a PUCM run to update/change the Hive clusterdeployment resource for a cluster if that cluster was created by Hive. Doing adoption again would mean deleting existing clusterdeployment already created in Hive and would destroy the exiting state, we don't want that.

This PRs:
- modify the *adminUpdate()* behaviour to adopt clusters that were not created by Hive.
- add new info at cluster installation time (in cosmosDB and in a label in Cluster Deployment) to know if the cluster was created by hive or not. This is new info is used in the new adminUpdate() behaviour. 
- adds some unit tests.

### Test plan for issue:

<!--
How did you test that this PR works?

- Are there unit tests?
- Are there integration/e2e tests?
- If it is not possible to write automated tests, explain why and document how
  to manually test and verify the feature.
-->

Added Unit Tests. I created a separate commit to refactor *(m *manager) openShiftVersionFromVersion()* and extracted the funcitonality to a separate interface and struct in order to being able to mock that function in the newly created test *TestRunHiveInstallerSetsCreatedByHiveFieldToTrueInClusterDoc()* (needed to ensure the *createdByHive* field is properly set when a cluster is created by Hive). I also added a new test case for the test TestAdminUpdateSteps to skip adoption of clusters created by hive.

After the refactoring, a new struct field is added to the cluster manager with the functionality that previously was in *(m *manager) openShiftVersionFromVersion* in *pkg/cluster/install_version.go*. It has been moved to its own file, struct and interface. I have done that to be able to mock a function but this is a win by itself as the cluster manager is TOO big and complicated and we are using it as a GOD class. 

@UlrichSchlueter and I have tested the functionality manually and end-to-end with both a regular ARO cluster and a cluster created by Hive. We have ensured that the adoption is just performed in the cluster that was not created by hive (and a few more things). So all automated tests are green and the manual tests we performed were also "green" for the new functionality. 🟢 

### Is there any documentation that needs to be updated for this PR?

<!--
- If yes and the docs are in GitHub, include doc updates in the PR.
- If yes and the docs are not in GitHub (i.e. ADO wiki), include a link to the
  docs.
- If no, explain why (e.g. "tech debt cleanup, N/A").
-->
N/A.


Patch:
diff --git a/.pipelines/ci.yml b/.pipelines/ci.yml
index 61a10f7f0..545fe81b5 100644
--- a/.pipelines/ci.yml
+++ b/.pipelines/ci.yml
@@ -33,7 +33,7 @@ variables:
 jobs:
 - job: Python_Unit_Tests
   pool:
-    name: aro-ci-1es
+    name: 1es-aro-ci-pool
   variables:
     HOME: $(Agent.BuildDirectory)
   steps:
@@ -47,7 +47,7 @@ jobs:
 
 - job: Golang_Unit_Tests
   pool:
-    name: aro-ci-1es
+    name: 1es-aro-ci-pool
   variables:
     GOCACHE: /tmp/gocache
   steps:
@@ -110,7 +110,7 @@ jobs:
 
 - job: Lint_Admin_Portal
   pool:
-    name: aro-ci-1es
+    name: 1es-aro-ci-pool
   steps:
   - template: ./templates/template-checkout.yml
   - script: |
diff --git a/cmd/aro/operator.go b/cmd/aro/operator.go
index 85f9ce506..95f65266d 100644
--- a/cmd/aro/operator.go
+++ b/cmd/aro/operator.go
@@ -8,7 +8,6 @@ import (
 	"flag"
 	"fmt"
 
-	imageregistryclient "github.com/openshift/client-go/imageregistry/clientset/versioned"
 	machineclient "github.com/openshift/client-go/machine/clientset/versioned"
 	securityclient "github.com/openshift/client-go/security/clientset/versioned"
 	mcoclient "github.com/openshift/machine-config-operator/pkg/generated/clientset/versioned"
@@ -95,10 +94,6 @@ func operator(ctx context.Context, log *logrus.Entry) error {
 	if err != nil {
 		return err
 	}
-	imageregistrycli, err := imageregistryclient.NewForConfig(restConfig)
-	if err != nil {
-		return err
-	}
 	// TODO (NE): dh is sometimes passed, sometimes created later. Can we standardize?
 	dh, err := dynamichelper.New(log, restConfig)
 	if err != nil {
@@ -198,7 +193,7 @@ func operator(ctx context.Context, log *logrus.Entry) error {
 		}
 		if err = (storageaccounts.NewReconciler(
 			log.WithField("controller", storageaccounts.ControllerName),
-			client, maocli, kubernetescli, imageregistrycli)).SetupWithManager(mgr); err != nil {
+			client, maocli, kubernetescli)).SetupWithManager(mgr); err != nil {
 			return fmt.Errorf("unable to create controller %s: %v", storageaccounts.ControllerName, err)
 		}
 		if err = (muo.NewReconciler(
diff --git a/pkg/api/admin/openshiftcluster.go b/pkg/api/admin/openshiftcluster.go
index d50a99a04..bf536c001 100644
--- a/pkg/api/admin/openshiftcluster.go
+++ b/pkg/api/admin/openshiftcluster.go
@@ -310,4 +310,9 @@ type SystemData struct {
 
 type HiveProfile struct {
 	Namespace string `json:"namespace,omitempty"`
+
+	// CreatedByHive is used during PUCM to skip adoption and reconciliation
+	// of clusters that were created by Hive to avoid deleting existing
+	// ClusterDeployments.
+	CreatedByHive bool `json:"createdByHive,omitempty"`
 }
diff --git a/pkg/api/admin/openshiftcluster_convert.go b/pkg/api/admin/openshiftcluster_convert.go
index a8d55a2a3..b1c5b9ca4 100644
--- a/pkg/api/admin/openshiftcluster_convert.go
+++ b/pkg/api/admin/openshiftcluster_convert.go
@@ -124,7 +124,8 @@ func (c openShiftClusterConverter) ToExternal(oc *api.OpenShiftCluster) interfac
 	}
 
 	out.Properties.HiveProfile = HiveProfile{
-		Namespace: oc.Properties.HiveProfile.Namespace,
+		Namespace:     oc.Properties.HiveProfile.Namespace,
+		CreatedByHive: oc.Properties.HiveProfile.CreatedByHive,
 	}
 	out.SystemData = SystemData{
 		CreatedBy:          oc.SystemData.CreatedBy,
@@ -174,6 +175,7 @@ func (c openShiftClusterConverter) ToInternal(_oc interface{}, out *api.OpenShif
 	out.Properties.ArchitectureVersion = api.ArchitectureVersion(oc.Properties.ArchitectureVersion)
 	out.Properties.InfraID = oc.Properties.InfraID
 	out.Properties.HiveProfile.Namespace = oc.Properties.HiveProfile.Namespace
+	out.Properties.HiveProfile.CreatedByHive = oc.Properties.HiveProfile.CreatedByHive
 	out.Properties.ProvisioningState = api.ProvisioningState(oc.Properties.ProvisioningState)
 	out.Properties.LastProvisioningState = api.ProvisioningState(oc.Properties.LastProvisioningState)
 	out.Properties.FailedProvisioningState = api.ProvisioningState(oc.Properties.FailedProvisioningState)
diff --git a/pkg/api/openshiftcluster.go b/pkg/api/openshiftcluster.go
index 517a5b12c..c50b9ebb7 100644
--- a/pkg/api/openshiftcluster.go
+++ b/pkg/api/openshiftcluster.go
@@ -509,4 +509,9 @@ type HiveProfile struct {
 	MissingFields
 
 	Namespace string `json:"namespace,omitempty"`
+
+	// CreatedByHive is used during PUCM to skip adoption and reconciliation
+	// of clusters that were created by Hive to avoid deleting existing
+	// ClusterDeployments.
+	CreatedByHive bool `json:"createdByHive,omitempty"`
 }
diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go
index 63a832e0e..36b1ee8e2 100644
--- a/pkg/cluster/cluster.go
+++ b/pkg/cluster/cluster.go
@@ -105,6 +105,8 @@ type manager struct {
 	aroOperatorDeployer deploy.Operator
 
 	now func() time.Time
+
+	openShiftClusterDocumentVersioner openShiftClusterDocumentVersioner
 }
 
 // New returns a cluster manager
@@ -177,9 +179,10 @@ func New(ctx context.Context, log *logrus.Entry, _env env.Interface, db database
 		subnet:  subnet.NewManager(_env.Environment(), r.SubscriptionID, fpAuthorizer),
 		graph:   graph.NewManager(log, aead, storage),
 
-		installViaHive:     installViaHive,
-		adoptViaHive:       adoptByHive,
-		hiveClusterManager: hiveClusterManager,
-		now:                func() time.Time { return time.Now() },
+		installViaHive:                    installViaHive,
+		adoptViaHive:                      adoptByHive,
+		hiveClusterManager:                hiveClusterManager,
+		now:                               func() time.Time { return time.Now() },
+		openShiftClusterDocumentVersioner: new(openShiftClusterDocumentVersionerService),
 	}, nil
 }
diff --git a/pkg/cluster/install.go b/pkg/cluster/install.go
index 66a7dce8c..b08348a2e 100644
--- a/pkg/cluster/install.go
+++ b/pkg/cluster/install.go
@@ -19,6 +19,7 @@ import (
 	"k8s.io/client-go/kubernetes"
 
 	"github.com/Azure/ARO-RP/pkg/api"
+	"github.com/Azure/ARO-RP/pkg/database"
 	"github.com/Azure/ARO-RP/pkg/installer"
 	aroclient "github.com/Azure/ARO-RP/pkg/operator/clientset/versioned"
 	"github.com/Azure/ARO-RP/pkg/operator/deploy"
@@ -130,7 +131,7 @@ func (m *manager) adminUpdate() []steps.Step {
 	}
 
 	// Hive cluster adoption and reconciliation
-	if isEverything && m.adoptViaHive {
+	if isEverything && m.adoptViaHive && !m.clusterWasCreatedByHive() {
 		toRun = append(toRun,
 			steps.Action(m.hiveCreateNamespace),
 			steps.Action(m.hiveEnsureResources),
@@ -150,6 +151,14 @@ func (m *manager) adminUpdate() []steps.Step {
 	return toRun
 }
 
+func (m *manager) clusterWasCreatedByHive() bool {
+	if m.doc.OpenShiftCluster.Properties.HiveProfile.Namespace == "" {
+		return false
+	}
+
+	return m.doc.OpenShiftCluster.Properties.HiveProfile.CreatedByHive
+}
+
 func (m *manager) Update(ctx context.Context) error {
 	s := []steps.Step{
 		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.validateResources)),
@@ -193,6 +202,12 @@ func (m *manager) runIntegratedInstaller(ctx context.Context) error {
 }
 
 func (m *manager) runHiveInstaller(ctx context.Context) error {
+	var err error
+	m.doc, err = m.db.PatchWithLease(ctx, m.doc.Key, setFieldCreatedByHive(true))
+	if err != nil {
+		return err
+	}
+
 	version, err := m.openShiftVersionFromVersion(ctx)
 	if err != nil {
 		return err
@@ -204,6 +219,13 @@ func (m *manager) runHiveInstaller(ctx context.Context) error {
 	return m.hiveClusterManager.Install(ctx, m.subscriptionDoc, m.doc, version)
 }
 
+func setFieldCreatedByHive(createdByHive bool) database.OpenShiftClusterDocumentMutator {
+	return func(doc *api.OpenShiftClusterDocument) error {
+		doc.OpenShiftCluster.Properties.HiveProfile.CreatedByHive = createdByHive
+		return nil
+	}
+}
+
 func (m *manager) bootstrap() []steps.Step {
 	s := []steps.Step{
 		steps.AuthorizationRefreshingAction(m.fpAuthorizer, steps.Action(m.validateResources)),
diff --git a/pkg/cluster/install_version.go b/pkg/cluster/install_version.go
index 263e7da7a..8e7c5d518 100644
--- a/pkg/cluster/install_version.go
+++ b/pkg/cluster/install_version.go
@@ -5,65 +5,10 @@ package cluster
 
 import (
 	"context"
-	"fmt"
-	"net/http"
-	"strings"
 
 	"github.com/Azure/ARO-RP/pkg/api"
-	"github.com/Azure/ARO-RP/pkg/util/version"
 )
 
 func (m *manager) openShiftVersionFromVersion(ctx context.Context) (*api.OpenShiftVersion, error) {
-	requestedInstallVersion := m.doc.OpenShiftCluster.Properties.ClusterProfile.Version
-
-	docs, err := m.dbOpenShiftVersions.ListAll(ctx)
-	if err != nil {
-		return nil, err
-	}
-
-	activeOpenShiftVersions := make([]*api.OpenShiftVersion, 0)
-	for _, doc := range docs.OpenShiftVersionDocuments {
-		if doc.OpenShiftVersion.Properties.Enabled {
-			activeOpenShiftVersions = append(activeOpenShiftVersions, doc.OpenShiftVersion)
-		}
-	}
-
-	errUnsupportedVersion := api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "properties.clusterProfile.version", "The requested OpenShift version '%s' is not supported.", requestedInstallVersion)
-
-	// when we have no OpenShiftVersion entries in CosmoDB, default to building one using the InstallStream
-	if len(activeOpenShiftVersions) == 0 {
-		if requestedInstallVersion != version.InstallStream.Version.String() {
-			return nil, errUnsupportedVersion
-		}
-
-		installerPullSpec := m.env.LiveConfig().DefaultInstallerPullSpecOverride(ctx)
-		if installerPullSpec == "" {
-			// If no ENV var was set as an override, then use the default image name:tag format we build in the ARO-Installer build & push pipeline
-			installerPullSpec = fmt.Sprintf("%s/aro-installer:release-%d.%d", m.env.ACRDomain(), version.InstallStream.Version.V[0], version.InstallStream.Version.V[1])
-		}
-
-		openshiftPullSpec := version.InstallStream.PullSpec
-		if m.installViaHive {
-			openshiftPullSpec = strings.Replace(openshiftPullSpec, "quay.io", m.env.ACRDomain(), 1)
-		}
-
-		return &api.OpenShiftVersion{
-			Properties: api.OpenShiftVersionProperties{
-				Version:           version.InstallStream.Version.String(),
-				OpenShiftPullspec: openshiftPullSpec,
-				InstallerPullspec: installerPullSpec,
-				Enabled:           true,
-			}}, nil
-	}
-
-	for _, active := range activeOpenShiftVersions {
-		if requestedInstallVersion == active.Properties.Version {
-			if m.installViaHive {
-				active.Properties.OpenShiftPullspec = strings.Replace(active.Properties.OpenShiftPullspec, "quay.io", m.env.ACRDomain(), 1)
-			}
-			return active, nil
-		}
-	}
-
-	return nil, errUnsupportedVersion
+	return m.openShiftClusterDocumentVersioner.Get(ctx, m.doc, m.dbOpenShiftVersions, m.env, m.installViaHive)
 }
diff --git a/pkg/cluster/version.go b/pkg/cluster/version.go
new file mode 100644
index 000000000..a1fed1c93
--- /dev/null
+++ b/pkg/cluster/version.go
@@ -0,0 +1,90 @@
+package cluster
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/Azure/ARO-RP/pkg/api"
+	"github.com/Azure/ARO-RP/pkg/database"
+	"github.com/Azure/ARO-RP/pkg/env"
+	"github.com/Azure/ARO-RP/pkg/util/version"
+)
+
+// openShiftClusterDocumentVersioner is the interface that validates and obtains the version from an OpenShiftClusterDocument.
+type openShiftClusterDocumentVersioner interface {
+
+	// Get validates and obtains the OpenShift version of the OpenShiftClusterDocument doc using dbOpenShiftVersions, env and installViaHive parameters.
+	Get(ctx context.Context, doc *api.OpenShiftClusterDocument, dbOpenShiftVersions database.OpenShiftVersions, env env.Interface, installViaHive bool) (*api.OpenShiftVersion, error)
+}
+
+// openShiftClusterDocumentVersionerService is the default implementation of the openShiftClusterDocumentVersioner interface.
+type openShiftClusterDocumentVersionerService struct{}
+
+func (service *openShiftClusterDocumentVersionerService) Get(ctx context.Context, doc *api.OpenShiftClusterDocument, dbOpenShiftVersions database.OpenShiftVersions, env env.Interface, installViaHive bool) (*api.OpenShiftVersion, error) {
+	requestedInstallVersion := doc.OpenShiftCluster.Properties.ClusterProfile.Version
+
+	docs, err := dbOpenShiftVersions.ListAll(ctx)
+	if err != nil {
+		return nil, err
+	}
+
+	activeOpenShiftVersions := make([]*api.OpenShiftVersion, 0)
+	for _, doc := range docs.OpenShiftVersionDocuments {
+		if doc.OpenShiftVersion.Properties.Enabled {
+			activeOpenShiftVersions = append(activeOpenShiftVersions, doc.OpenShiftVersion)
+		}
+	}
+
+	errUnsupportedVersion := api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "properties.clusterProfile.version", "The requested OpenShift version '%s' is not supported.", requestedInstallVersion)
+
+	// when we have no OpenShiftVersion entries in CosmoDB, default to building one using the InstallStream
+	if len(activeOpenShiftVersions) == 0 {
+		if requestedInstallVersion != version.InstallStream.Version.String() {
+			return nil, errUnsupportedVersion
+		}
+
+		installerPullSpec := env.LiveConfig().DefaultInstallerPullSpecOverride(ctx)
+		if installerPullSpec == "" {
+			// If no ENV var was set as an override, then use the default image name:tag format we build in the ARO-Installer build & push pipeline
+			installerPullSpec = fmt.Sprintf("%s/aro-installer:release-%d.%d", env.ACRDomain(), version.InstallStream.Version.V[0], version.InstallStream.Version.V[1])
+		}
+
+		openshiftPullSpec := version.InstallStream.PullSpec
+		if installViaHive {
+			openshiftPullSpec = strings.Replace(openshiftPullSpec, "quay.io", env.ACRDomain(), 1)
+		}
+
+		return &api.OpenShiftVersion{
+			Properties: api.OpenShiftVersionProperties{
+				Version:           version.InstallStream.Version.String(),
+				OpenShiftPullspec: openshiftPullSpec,
+				InstallerPullspec: installerPullSpec,
+				Enabled:           true,
+			}}, nil
+	}
+
+	for _, active := range activeOpenShiftVersions {
+		if requestedInstallVersion == active.Properties.Version {
+			if installViaHive {
+				active.Properties.OpenShiftPullspec = strings.Replace(active.Properties.OpenShiftPullspec, "quay.io", env.ACRDomain(), 1)
+			}
+			return active, nil
+		}
+	}
+
+	return nil, errUnsupportedVersion
+}
+
+type FakeOpenShiftClusterDocumentVersionerService struct {
+	expectedOpenShiftVersion *api.OpenShiftVersion
+	expectedError            error
+}
+
+func (fake *FakeOpenShiftClusterDocumentVersionerService) Get(ctx context.Context, doc *api.OpenShiftClusterDocument, dbOpenShiftVersions database.OpenShiftVersions, env env.Interface, installViaHive bool) (*api.OpenShiftVersion, error) {
+	return fake.expectedOpenShiftVersion, fake.expectedError
+}
diff --git a/pkg/dbtoken/refresher.go b/pkg/dbtoken/refresher.go
index 5648910b4..a17d80d6b 100644
--- a/pkg/dbtoken/refresher.go
+++ b/pkg/dbtoken/refresher.go
@@ -68,7 +68,7 @@ func (r *refresher) checkRefreshAndReset() bool {
 func (r *refresher) Run(ctx context.Context) error {
 	defer utilrecover.Panic(r.log)
 
-	go heartbeat.EmitHeartbeat(r.log, r.m, r.metricPrefix+"DbTokenRefresh", nil, r.checkRefreshAndReset)
+	go heartbeat.EmitHeartbeat(r.log, r.m, r.metricPrefix+".dbtokenrefresh", nil, r.checkRefreshAndReset)
 
 	t := time.NewTicker(10 * time.Second)
 	defer t.Stop()
diff --git a/pkg/deploy/assets/env-development.json b/pkg/deploy/assets/env-development.json
index fc731ef49..6705f0acf 100644
--- a/pkg/deploy/assets/env-development.json
+++ b/pkg/deploy/assets/env-development.json
@@ -407,7 +407,7 @@
                                     "autoUpgradeMinorVersion": true,
                                     "settings": {},
                                     "protectedSettings": {
-                                        "script": "[base64(concat(base64ToString('c2V0IC1leAoK'),'PROXYIMAGE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('proxyImage')),''')\n','PROXYIMAGEAUTH=$(base64 -d \u003c\u003c\u003c''',base64(parameters('proxyImageAuth')),''')\n','PROXYCERT=''',parameters('proxyCert'),'''\n','PROXYCLIENTCERT=''',parameters('proxyClientCert'),'''\n','PROXYKEY=''',parameters('proxyKey'),'''\n','\n',base64ToString('eXVtIC15IHVwZGF0ZQp5dW0gLXkgaW5zdGFsbCBkb2NrZXIKCmZpcmV3YWxsLWNtZCAtLWFkZC1wb3J0PTQ0My90Y3AgLS1wZXJtYW5lbnQKCm1rZGlyIC9yb290Ly5kb2NrZXIKY2F0ID4vcm9vdC8uZG9ja2VyL2NvbmZpZy5qc29uIDw8RU9GCnsKCSJhdXRocyI6IHsKCQkiJHtQUk9YWUlNQUdFJSUvKn0iOiB7CgkJCSJhdXRoIjogIiRQUk9YWUlNQUdFQVVUSCIKCQl9Cgl9Cn0KRU9GCnN5c3RlbWN0bCBzdGFydCBkb2NrZXIuc2VydmljZQpkb2NrZXIgcHVsbCAiJFBST1hZSU1BR0UiCgpta2RpciAvZXRjL3Byb3h5CmJhc2U2NCAtZCA8PDwiJFBST1hZQ0VSVCIgPi9ldGMvcHJveHkvcHJveHkuY3J0CmJhc2U2NCAtZCA8PDwiJFBST1hZS0VZIiA+L2V0Yy9wcm94eS9wcm94eS5rZXkKYmFzZTY0IC1kIDw8PCIkUFJPWFlDTElFTlRDRVJUIiA+L2V0Yy9wcm94eS9wcm94eS1jbGllbnQuY3J0CmNob3duIC1SIDEwMDA6MTAwMCAvZXRjL3Byb3h5CmNobW9kIDA2MDAgL2V0Yy9wcm94eS9wcm94eS5rZXkKCmNhdCA+L2V0Yy9zeXNjb25maWcvcHJveHkgPDxFT0YKUFJPWFlfSU1BR0U9JyRQUk9YWUlNQUdFJwpFT0YKCmNhdCA+L2V0Yy9zeXN0ZW1kL3N5c3RlbS9wcm94eS5zZXJ2aWNlIDw8J0VPRicKW1VuaXRdCkFmdGVyPWRvY2tlci5zZXJ2aWNlClJlcXVpcmVzPWRvY2tlci5zZXJ2aWNlCgpbU2VydmljZV0KRW52aXJvbm1lbnRGaWxlPS9ldGMvc3lzY29uZmlnL3Byb3h5CkV4ZWNTdGFydFByZT0tL3Vzci9iaW4vZG9ja2VyIHJtIC1mICVuCkV4ZWNTdGFydD0vdXNyL2Jpbi9kb2NrZXIgcnVuIC0tcm0gLS1uYW1lICVuIC1wIDQ0Mzo4NDQzIC12IC9ldGMvcHJveHk6L3NlY3JldHMgJFBST1hZX0lNQUdFCkV4ZWNTdG9wPS91c3IvYmluL2RvY2tlciBzdG9wICVuClJlc3RhcnQ9YWx3YXlzClJlc3RhcnRTZWM9MQpTdGFydExpbWl0SW50ZXJ2YWw9MAoKW0luc3RhbGxdCldhbnRlZEJ5PW11bHRpLXVzZXIudGFyZ2V0CkVPRgoKc3lzdGVtY3RsIGVuYWJsZSBwcm94eS5zZXJ2aWNlCgpjYXQgPi9ldGMvY3Jvbi53ZWVrbHkvcHVsbC1pbWFnZSA8PCdFT0YnCiMhL2Jpbi9iYXNoCgpkb2NrZXIgcHVsbCAkUFJPWFlJTUFHRQpzeXN0ZW1jdGwgcmVzdGFydCBwcm94eS5zZXJ2aWNlCkVPRgpjaG1vZCAreCAvZXRjL2Nyb24ud2Vla2x5L3B1bGwtaW1hZ2UKCmNhdCA+L2V0Yy9jcm9uLndlZWtseS95dW11cGRhdGUgPDwnRU9GJwojIS9iaW4vYmFzaAoKeXVtIHVwZGF0ZSAteQpFT0YKY2htb2QgK3ggL2V0Yy9jcm9uLndlZWtseS95dW11cGRhdGUKCihzbGVlcCAzMDsgcmVib290KSAmCg==')))]"
+                                        "script": "[base64(concat(base64ToString('c2V0IC1leAoK'),'PROXYIMAGE=$(base64 -d \u003c\u003c\u003c''',base64(parameters('proxyImage')),''')\n','PROXYIMAGEAUTH=$(base64 -d \u003c\u003c\u003c''',base64(parameters('proxyImageAuth')),''')\n','PROXYCERT=''',parameters('proxyCert'),'''\n','PROXYCLIENTCERT=''',parameters('proxyClientCert'),'''\n','PROXYKEY=''',parameters('proxyKey'),'''\n','\n',base64ToString('eXVtIC15IC14IFdBTGludXhBZ2VudCAteCBXQUxpbnV4QWdlbnQtdWRldiB1cGRhdGUKeXVtIC15IGluc3RhbGwgZG9ja2VyCgpmaXJld2FsbC1jbWQgLS1hZGQtcG9ydD00NDMvdGNwIC0tcGVybWFuZW50Cgpta2RpciAvcm9vdC8uZG9ja2VyCmNhdCA+L3Jvb3QvLmRvY2tlci9jb25maWcuanNvbiA8PEVPRgp7CgkiYXV0aHMiOiB7CgkJIiR7UFJPWFlJTUFHRSUlLyp9IjogewoJCQkiYXV0aCI6ICIkUFJPWFlJTUFHRUFVVEgiCgkJfQoJfQp9CkVPRgpzeXN0ZW1jdGwgc3RhcnQgZG9ja2VyLnNlcnZpY2UKZG9ja2VyIHB1bGwgIiRQUk9YWUlNQUdFIgoKbWtkaXIgL2V0Yy9wcm94eQpiYXNlNjQgLWQgPDw8IiRQUk9YWUNFUlQiID4vZXRjL3Byb3h5L3Byb3h5LmNydApiYXNlNjQgLWQgPDw8IiRQUk9YWUtFWSIgPi9ldGMvcHJveHkvcHJveHkua2V5CmJhc2U2NCAtZCA8PDwiJFBST1hZQ0xJRU5UQ0VSVCIgPi9ldGMvcHJveHkvcHJveHktY2xpZW50LmNydApjaG93biAtUiAxMDAwOjEwMDAgL2V0Yy9wcm94eQpjaG1vZCAwNjAwIC9ldGMvcHJveHkvcHJveHkua2V5CgpjYXQgPi9ldGMvc3lzY29uZmlnL3Byb3h5IDw8RU9GClBST1hZX0lNQUdFPSckUFJPWFlJTUFHRScKRU9GCgpjYXQgPi9ldGMvc3lzdGVtZC9zeXN0ZW0vcHJveHkuc2VydmljZSA8PCdFT0YnCltVbml0XQpBZnRlcj1kb2NrZXIuc2VydmljZQpSZXF1aXJlcz1kb2NrZXIuc2VydmljZQoKW1NlcnZpY2VdCkVudmlyb25tZW50RmlsZT0vZXRjL3N5c2NvbmZpZy9wcm94eQpFeGVjU3RhcnRQcmU9LS91c3IvYmluL2RvY2tlciBybSAtZiAlbgpFeGVjU3RhcnQ9L3Vzci9iaW4vZG9ja2VyIHJ1biAtLXJtIC0tbmFtZSAlbiAtcCA0NDM6ODQ0MyAtdiAvZXRjL3Byb3h5Oi9zZWNyZXRzICRQUk9YWV9JTUFHRQpFeGVjU3RvcD0vdXNyL2Jpbi9kb2NrZXIgc3RvcCAlbgpSZXN0YXJ0PWFsd2F5cwpSZXN0YXJ0U2VjPTEKU3RhcnRMaW1pdEludGVydmFsPTAKCltJbnN0YWxsXQpXYW50ZWRCeT1tdWx0aS11c2VyLnRhcmdldApFT0YKCnN5c3RlbWN0bCBlbmFibGUgcHJveHkuc2VydmljZQoKY2F0ID4vZXRjL2Nyb24ud2Vla2x5L3B1bGwtaW1hZ2UgPDwnRU9GJwojIS9iaW4vYmFzaAoKZG9ja2VyIHB1bGwgJFBST1hZSU1BR0UKc3lzdGVtY3RsIHJlc3RhcnQgcHJveHkuc2VydmljZQpFT0YKY2htb2QgK3ggL2V0Yy9jcm9uLndlZWtseS9wdWxsLWltYWdlCgpjYXQgPi9ldGMvY3Jvbi53ZWVrbHkveXVtdXBkYXRlIDw8J0VPRicKIyEvYmluL2Jhc2gKCnl1bSB1cGRhdGUgLXkKRU9GCmNobW9kICt4IC9ldGMvY3Jvbi53ZWVrbHkveXVtdXBkYXRlCgooc2xlZXAgMzA7IHJlYm9vdCkgJgo=')))]"
                                     }
                                 }
                             }
diff --git a/pkg/deploy/assets/rp-production-predeploy-parameters.json b/pkg/deploy/assets/rp-production-predeploy-parameters.json
index fe79ef6fb..088062ec4 100644
--- a/pkg/deploy/assets/rp-production-predeploy-parameters.json
+++ b/pkg/deploy/assets/rp-production-predeploy-parameters.json
@@ -26,6 +26,9 @@
         "keyvaultPrefix": {
             "value": ""
         },
+        "rpNsgPortalSourceAddressPrefixes": {
+            "value": []
+        },
         "rpServicePrincipalId": {
             "value": ""
         }
diff --git a/pkg/deploy/assets/rp-production-predeploy.json b/pkg/deploy/assets/rp-production-predeploy.json
index 9305b6b4b..ea48571d1 100644
--- a/pkg/deploy/assets/rp-production-predeploy.json
+++ b/pkg/deploy/assets/rp-production-predeploy.json
@@ -85,6 +85,10 @@
             "type": "string",
             "maxLength": 20
         },
+        "rpNsgPortalSourceAddressPrefixes": {
+            "type": "array",
+            "defaultValue": []
+        },
         "rpServicePrincipalId": {
             "type": "string"
         }
@@ -371,6 +375,23 @@
                     ]
                 }
             }
+        },
+        {
+            "properties": {
+                "protocol": "Tcp",
+                "sourcePortRange": "*",
+                "destinationPortRange": "444",
+                "sourceAddressPrefixes": "[parameters('rpNsgPortalSourceAddressPrefixes')]",
+                "destinationAddressPrefix": "*",
+                "access": "Allow",
+                "priority": 142,
+                "direction": "Inbound"
+            },
+            "name": "rp-nsg/portal_in",
+            "type": "Microsoft.Network/networkSecurityGroups/securityRules",
+            "condition": "[not(empty(parameters('rpNsgPortalSourceAddressPrefixes')))]",
+            "apiVersion": "2020-08-01",
+            "location": "[resourceGroup().location]"
         }
     ]
 }
diff --git a/pkg/deploy/config.go b/pkg/deploy/config.go
index 105ddce71..6bea7740d 100644
--- a/pkg/deploy/config.go
+++ b/pkg/deploy/config.go
@@ -91,6 +91,7 @@ type Configuration struct {
 	RPMDSDAccount                      *string       `json:"rpMdsdAccount,omitempty" value:"required"`
 	RPMDSDConfigVersion                *string       `json:"rpMdsdConfigVersion,omitempty" value:"required"`
 	RPMDSDNamespace                    *string       `json:"rpMdsdNamespace,omitempty" value:"required"`
+	RPNSGPortalSourceAddressPrefixes   []string      `json:"rpNsgPortalSourceAddressPrefixes,omitempty"`
 	RPParentDomainName                 *string       `json:"rpParentDomainName,omitempty" value:"required"`
 	RPVMSSCapacity                     *int          `json:"rpVmssCapacity,omitempty"`
 	SSHPublicKey                       *string       `json:"sshPublicKey,omitempty"`
diff --git a/pkg/deploy/generator/resources.go b/pkg/deploy/generator/resources.go
index 9ec24b684..c8ad7791d 100644
--- a/pkg/deploy/generator/resources.go
+++ b/pkg/deploy/generator/resources.go
@@ -57,6 +57,19 @@ func (g *generator) securityGroup(name string, securityRules *[]mgmtnetwork.Secu
 	}
 }
 
+func (g *generator) securityRules(name string, properties *mgmtnetwork.SecurityRulePropertiesFormat, condition interface{}) *arm.Resource {
+	return &arm.Resource{
+		Resource: &mgmtnetwork.SecurityRule{
+			SecurityRulePropertiesFormat: properties,
+			Name:                         &name,
+			Type:                         to.StringPtr("Microsoft.Network/networkSecurityGroups/securityRules"),
+		},
+		Location:   "[resourceGroup().location]",
+		Condition:  condition,
+		APIVersion: azureclient.APIVersion("Microsoft.Network"),
+	}
+}
+
 func (g *generator) publicIPAddress(name string) *arm.Resource {
 	return &arm.Resource{
 		Resource: &mgmtnetwork.PublicIPAddress{
diff --git a/pkg/deploy/generator/resources_rp.go b/pkg/deploy/generator/resources_rp.go
index 3d92b2d98..46adfc496 100644
--- a/pkg/deploy/generator/resources_rp.go
+++ b/pkg/deploy/generator/resources_rp.go
@@ -37,6 +37,19 @@ func (g *generator) rpManagedIdentity() *arm.Resource {
 	}
 }
 
+func (g *generator) rpSecurityGroupForPortalSourceAddressPrefixes() *arm.Resource {
+	return g.securityRules("rp-nsg/portal_in", &mgmtnetwork.SecurityRulePropertiesFormat{
+		Protocol:                 mgmtnetwork.SecurityRuleProtocolTCP,
+		SourcePortRange:          to.StringPtr("*"),
+		DestinationPortRange:     to.StringPtr("444"),
+		SourceAddressPrefixes:    &[]string{},
+		DestinationAddressPrefix: to.StringPtr("*"),
+		Access:                   mgmtnetwork.SecurityRuleAccessAllow,
+		Priority:                 to.Int32Ptr(142),
+		Direction:                mgmtnetwork.SecurityRuleDirectionInbound,
+	}, "[not(empty(parameters('rpNsgPortalSourceAddressPrefixes')))]")
+}
+
 func (g *generator) rpSecurityGroup() *arm.Resource {
 	rules := []mgmtnetwork.SecurityRule{
 		{
diff --git a/pkg/deploy/generator/scripts/devProxyVMSS.sh b/pkg/deploy/generator/scripts/devProxyVMSS.sh
index aacd354b8..80ef47dd6 100644
--- a/pkg/deploy/generator/scripts/devProxyVMSS.sh
+++ b/pkg/deploy/generator/scripts/devProxyVMSS.sh
@@ -1,4 +1,4 @@
-yum -y update
+yum -y -x WALinuxAgent -x WALinuxAgent-udev update
 yum -y install docker
 
 firewall-cmd --add-port=443/tcp --permanent
diff --git a/pkg/deploy/generator/templates.go b/pkg/deploy/generator/templates.go
index 8596a3033..2877b4368 100644
--- a/pkg/deploy/generator/templates.go
+++ b/pkg/deploy/generator/templates.go
@@ -58,6 +58,7 @@ func (g *generator) templateFixup(t *arm.Template) ([]byte, error) {
 		b = bytes.Replace(b, []byte(`"isVirtualNetworkFilterEnabled": true`), []byte(`"isVirtualNetworkFilterEnabled": "[not(parameters('disableCosmosDBFirewall'))]"`), 1)
 		b = bytes.Replace(b, []byte(`"virtualNetworkRules": []`), []byte(`"virtualNetworkRules": "[if(parameters('disableCosmosDBFirewall'), createArray(), createArray(createObject('id', resourceId('Microsoft.Network/virtualNetworks/subnets', 'rp-vnet', 'rp-subnet')), createObject('id', resourceId(parameters('gatewayResourceGroupName'), 'Microsoft.Network/virtualNetworks/subnets', 'gateway-vnet', 'gateway-subnet'))))]"`), 1)
 		b = bytes.Replace(b, []byte(`"ipRules": []`), []byte(`"ipRules": "[if(parameters('disableCosmosDBFirewall'), createArray(), concat(parameters('ipRules'),createArray(createObject('ipAddressOrRange', '104.42.195.92'),createObject('ipAddressOrRange','40.76.54.131'),createObject('ipAddressOrRange','52.176.6.30'),createObject('ipAddressOrRange','52.169.50.45'),createObject('ipAddressOrRange','52.187.184.26'))))]"`), 1)
+		b = bytes.Replace(b, []byte(`"sourceAddressPrefixes": []`), []byte(`"sourceAddressPrefixes": "[parameters('rpNsgPortalSourceAddressPrefixes')]"`), 1)
 	}
 
 	return append(b, byte('\n')), nil
diff --git a/pkg/deploy/generator/templates_rp.go b/pkg/deploy/generator/templates_rp.go
index 1f3e5ca3d..9f7a3e092 100644
--- a/pkg/deploy/generator/templates_rp.go
+++ b/pkg/deploy/generator/templates_rp.go
@@ -278,6 +278,7 @@ func (g *generator) rpPredeployTemplate() *arm.Template {
 			"extraPortalKeyvaultAccessPolicies",
 			"extraServiceKeyvaultAccessPolicies",
 			"gatewayResourceGroupName",
+			"rpNsgPortalSourceAddressPrefixes",
 		)
 	} else {
 		params = append(params,
@@ -297,6 +298,9 @@ func (g *generator) rpPredeployTemplate() *arm.Template {
 			"extraServiceKeyvaultAccessPolicies":
 			p.Type = "array"
 			p.DefaultValue = []interface{}{}
+		case "rpNsgPortalSourceAddressPrefixes":
+			p.Type = "array"
+			p.DefaultValue = []string{}
 		case "keyvaultPrefix":
 			p.MaxLength = 24 - max(len(env.ClusterKeyvaultSuffix), len(env.ServiceKeyvaultSuffix), len(env.PortalKeyvaultSuffix))
 		}
@@ -315,6 +319,12 @@ func (g *generator) rpPredeployTemplate() *arm.Template {
 		g.rpServiceKeyvaultDynamic(),
 	)
 
+	if g.production {
+		t.Resources = append(t.Resources,
+			g.rpSecurityGroupForPortalSourceAddressPrefixes(),
+		)
+	}
+
 	return t
 }
 
diff --git a/pkg/hive/install.go b/pkg/hive/install.go
index 5105514e3..9e7571d46 100644
--- a/pkg/hive/install.go
+++ b/pkg/hive/install.go
@@ -25,6 +25,7 @@ import (
 )
 
 const (
+	createdByHiveLabelKey = "aro-created-by-Hive"
 	envSecretsName        = "aro-env-secret"
 	pullsecretSecretName  = "aro-pullsecret"
 	installConfigName     = "aro-installconfig"
@@ -182,6 +183,7 @@ func (c *clusterManager) clusterDeploymentForInstall(doc *api.OpenShiftClusterDo
 			Labels: map[string]string{
 				"hive.openshift.io/cluster-platform": "azure",
 				"hive.openshift.io/cluster-region":   doc.OpenShiftCluster.Location,
+				createdByHiveLabelKey:                "true",
 			},
 			Annotations: map[string]string{
 				"hive.openshift.io/try-install-once":                "true",
diff --git a/pkg/operator/controllers/machinehealthcheck/staticresources/machinehealthcheck.yaml b/pkg/operator/controllers/machinehealthcheck/staticresources/machinehealthcheck.yaml
index caf285367..86ff3f3dd 100644
--- a/pkg/operator/controllers/machinehealthcheck/staticresources/machinehealthcheck.yaml
+++ b/pkg/operator/controllers/machinehealthcheck/staticresources/machinehealthcheck.yaml
@@ -21,3 +21,4 @@ spec:
     timeout: "300s"
     status: "Unknown"
   maxUnhealthy: "1"
+  nodeStartupTimeout: "20m"
diff --git a/pkg/operator/controllers/storageaccounts/storageaccount_controller.go b/pkg/operator/controllers/storageaccounts/storageaccount_controller.go
index b3055fa21..43ac858e7 100644
--- a/pkg/operator/controllers/storageaccounts/storageaccount_controller.go
+++ b/pkg/operator/controllers/storageaccounts/storageaccount_controller.go
@@ -8,7 +8,6 @@ import (
 
 	"github.com/Azure/go-autorest/autorest/azure"
 	machinev1beta1 "github.com/openshift/api/machine/v1beta1"
-	imageregistryclient "github.com/openshift/client-go/imageregistry/clientset/versioned"
 	machineclient "github.com/openshift/client-go/machine/clientset/versioned"
 	"github.com/sirupsen/logrus"
 	corev1 "k8s.io/api/core/v1"
@@ -40,9 +39,8 @@ const (
 type Reconciler struct {
 	log *logrus.Entry
 
-	kubernetescli    kubernetes.Interface
-	maocli           machineclient.Interface
-	imageregistrycli imageregistryclient.Interface
+	kubernetescli kubernetes.Interface
+	maocli        machineclient.Interface
 
 	client client.Client
 }
@@ -54,19 +52,18 @@ type reconcileManager struct {
 	instance       *arov1alpha1.Cluster
 	subscriptionID string
 
-	imageregistrycli imageregistryclient.Interface
-	kubeSubnets      subnet.KubeManager
-	storage          storage.AccountsClient
+	client      client.Client
+	kubeSubnets subnet.KubeManager
+	storage     storage.AccountsClient
 }
 
 // NewReconciler creates a new Reconciler
-func NewReconciler(log *logrus.Entry, client client.Client, maocli machineclient.Interface, kubernetescli kubernetes.Interface, imageregistrycli imageregistryclient.Interface) *Reconciler {
+func NewReconciler(log *logrus.Entry, client client.Client, maocli machineclient.Interface, kubernetescli kubernetes.Interface) *Reconciler {
 	return &Reconciler{
-		log:              log,
-		kubernetescli:    kubernetescli,
-		imageregistrycli: imageregistrycli,
-		maocli:           maocli,
-		client:           client,
+		log:           log,
+		kubernetescli: kubernetescli,
+		maocli:        maocli,
+		client:        client,
 	}
 }
 
@@ -112,9 +109,9 @@ func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.
 		instance:       instance,
 		subscriptionID: resource.SubscriptionID,
 
-		imageregistrycli: r.imageregistrycli,
-		kubeSubnets:      subnet.NewKubeManager(r.maocli, resource.SubscriptionID),
-		storage:          storage.NewAccountsClient(&azEnv, resource.SubscriptionID, authorizer),
+		client:      r.client,
+		kubeSubnets: subnet.NewKubeManager(r.maocli, resource.SubscriptionID),
+		storage:     storage.NewAccountsClient(&azEnv, resource.SubscriptionID, authorizer),
 	}
 
 	return reconcile.Result{}, manager.reconcileAccounts(ctx)
diff --git a/pkg/operator/controllers/storageaccounts/storageaccounts.go b/pkg/operator/controllers/storageaccounts/storageaccounts.go
index 71e49d4e9..c44cc37be 100644
--- a/pkg/operator/controllers/storageaccounts/storageaccounts.go
+++ b/pkg/operator/controllers/storageaccounts/storageaccounts.go
@@ -10,7 +10,8 @@ import (
 
 	mgmtstorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage"
 	"github.com/Azure/go-autorest/autorest/to"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	imageregistryv1 "github.com/openshift/api/imageregistry/v1"
+	"k8s.io/apimachinery/pkg/types"
 
 	"github.com/Azure/ARO-RP/pkg/util/stringutils"
 )
@@ -28,7 +29,8 @@ func (r *reconcileManager) reconcileAccounts(ctx context.Context) error {
 		serviceSubnets = append(serviceSubnets, subnet.ResourceID)
 	}
 
-	rc, err := r.imageregistrycli.ImageregistryV1().Configs().Get(ctx, "cluster", metav1.GetOptions{})
+	rc := &imageregistryv1.Config{}
+	err = r.client.Get(ctx, types.NamespacedName{Name: "cluster"}, rc)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/util/cluster/cluster.go b/pkg/util/cluster/cluster.go
index 952cfa2ff..1fc57cd69 100644
--- a/pkg/util/cluster/cluster.go
+++ b/pkg/util/cluster/cluster.go
@@ -339,12 +339,15 @@ func (c *Cluster) Create(ctx context.Context, vnetResourceGroup, clusterName str
 
 func (c *Cluster) generateSubnets() (vnetPrefix string, masterSubnet string, workerSubnet string) {
 	// pick a random 23 in range [10.3.0.0, 10.127.255.0]
-	// 10.0.0.0 is used by dev-vnet to host CI
-	// 10.1.0.0 is used by rp-vnet to host Proxy VM
-	// 10.2.0.0 is used by dev-vpn-vnet to host VirtualNetworkGateway
+	// 10.0.0.0/16 is used by dev-vnet to host CI
+	// 10.1.0.0/24 is used by rp-vnet to host Proxy VM
+	// 10.2.0.0/24 is used by dev-vpn-vnet to host VirtualNetworkGateway
 	var x, y int
 	rand.Seed(time.Now().UnixNano())
-	for x == 0 && y == 0 {
+	// Local Dev clusters are limited to /16 dev-vnet
+	if !c.ci {
+		x, y = 0, 2*rand.Intn(128)
+	} else {
 		x, y = rand.Intn((124))+3, 2*rand.Intn(128)
 	}
 	vnetPrefix = fmt.Sprintf("10.%d.%d.0/23", x, y)
diff --git a/pkg/util/scheme/scheme.go b/pkg/util/scheme/scheme.go
index e97062c74..66e2ca4ff 100644
--- a/pkg/util/scheme/scheme.go
+++ b/pkg/util/scheme/scheme.go
@@ -6,6 +6,7 @@ package scheme
 import (
 	configv1 "github.com/openshift/api/config/v1"
 	consolev1 "github.com/openshift/api/console/v1"
+	imageregistryv1 "github.com/openshift/api/imageregistry/v1"
 	machinev1beta1 "github.com/openshift/api/machine/v1beta1"
 	operatorv1 "github.com/openshift/api/operator/v1"
 	securityv1 "github.com/openshift/api/security/v1"
@@ -53,4 +54,5 @@ func init() {
 	// under both API Groups
 	scheme.Scheme.AddKnownTypes(azureproviderv1beta1.SchemeGroupVersion, &machinev1beta1.AzureMachineProviderSpec{})
 	utilruntime.Must(hivev1.AddToScheme(scheme.Scheme))
+	utilruntime.Must(imageregistryv1.AddToScheme(scheme.Scheme))
 }
diff --git a/python/az/aro/HISTORY.rst b/python/az/aro/HISTORY.rst
index c5a7f9541..0f6e95fd7 100644
--- a/python/az/aro/HISTORY.rst
+++ b/python/az/aro/HISTORY.rst
@@ -54,4 +54,4 @@ Release History
 1.0.7
 ++++++
 * Add support for get-versions (lists available installation versions of OpenShift per location)
-* Adds an aro create 'install-version' parameter (for specifying the cluster installation version)
+* Adds an aro create 'version' parameter (for specifying the cluster installation version)
diff --git a/python/az/aro/azext_aro/_help.py b/python/az/aro/azext_aro/_help.py
index 06047ad78..dde7ed271 100644
--- a/python/az/aro/azext_aro/_help.py
+++ b/python/az/aro/azext_aro/_help.py
@@ -16,7 +16,7 @@ helps['aro create'] = """
   - name: Create a cluster.
     text: az aro create --resource-group MyResourceGroup --name MyCluster --vnet MyVnet --master-subnet MyMasterSubnet --worker-subnet MyWorkerSubnet
   - name: Create a cluster with a supported OpenShift version.
-    text: az aro create --resource-group MyResourceGroup --name MyCluster --vnet MyVnet --master-subnet MyMasterSubnet --worker-subnet MyWorkerSubnet --install-version X.Y.Z
+    text: az aro create --resource-group MyResourceGroup --name MyCluster --vnet MyVnet --master-subnet MyMasterSubnet --worker-subnet MyWorkerSubnet --version X.Y.Z
   - name: Create a cluster with 5 compute nodes and Red Hat pull secret.
     text: az aro create --resource-group MyResourceGroup --name MyCluster --vnet MyVnet --master-subnet MyMasterSubnet --worker-subnet MyWorkerSubnet --worker-count 5 --pull-secret @pullsecret.txt
   - name: Create a private cluster.
diff --git a/python/az/aro/azext_aro/_params.py b/python/az/aro/azext_aro/_params.py
index 955353624..c880d3ea1 100644
--- a/python/az/aro/azext_aro/_params.py
+++ b/python/az/aro/azext_aro/_params.py
@@ -16,7 +16,7 @@ from azext_aro._validators import validate_vnet_resource_group_name
 from azext_aro._validators import validate_worker_count
 from azext_aro._validators import validate_worker_vm_disk_size_gb
 from azext_aro._validators import validate_refresh_cluster_credentials
-from azext_aro._validators import validate_install_version_format
+from azext_aro._validators import validate_version_format
 from azure.cli.core.commands.parameters import name_type
 from azure.cli.core.commands.parameters import get_enum_type, get_three_state_flag
 from azure.cli.core.commands.parameters import resource_group_name_type
@@ -54,9 +54,9 @@ def load_arguments(self, _):
                    help='Client secret of cluster service principal.',
                    validator=validate_client_secret(isCreate=True))
 
-        c.argument('install_version',
+        c.argument('version',
                    help='OpenShift version to use for cluster creation.',
-                   validator=validate_install_version_format)
+                   validator=validate_version_format)
 
         c.argument('pod_cidr',
                    help='CIDR of pod network. Must be a minimum of /18 or larger.',
diff --git a/python/az/aro/azext_aro/_validators.py b/python/az/aro/azext_aro/_validators.py
index c63a182fd..9862ee78a 100644
--- a/python/az/aro/azext_aro/_validators.py
+++ b/python/az/aro/azext_aro/_validators.py
@@ -260,8 +260,6 @@ def validate_refresh_cluster_credentials(namespace):
         raise RequiredArgumentMissingError('--client-id and --client-secret must be not set with --refresh-credentials.')  # pylint: disable=line-too-long
 
 
-def validate_install_version_format(namespace):
-    if namespace.install_version is not None and not re.match(r'^' +
-                                                              r'[4-9]{1}\.[0-9]{1,2}\.[0-9]{1,2}' +
-                                                              r'$', namespace.install_version):
-        raise InvalidArgumentValueError('--install-version is invalid')
+def validate_version_format(namespace):
+    if namespace.version is not None and not re.match(r'^[4-9]{1}\.[0-9]{1,2}\.[0-9]{1,2}$', namespace.version):
+        raise InvalidArgumentValueError('--version is invalid')
diff --git a/python/az/aro/azext_aro/custom.py b/python/az/aro/azext_aro/custom.py
index 38d75a36e..2b91a2a40 100644
--- a/python/az/aro/azext_aro/custom.py
+++ b/python/az/aro/azext_aro/custom.py
@@ -12,7 +12,7 @@ from azure.cli.core.commands.client_factory import get_mgmt_service_client
 from azure.cli.core.commands.client_factory import get_subscription_id
 from azure.cli.core.profiles import ResourceType
 from azure.cli.core.util import sdk_no_wait
-from azure.cli.core.azclierror import FileOperationError, ResourceNotFoundError, UnauthorizedError
+from azure.cli.core.azclierror import FileOperationError, ResourceNotFoundError, UnauthorizedError, ValidationError
 from azext_aro._aad import AADManager
 from azext_aro._rbac import assign_role_to_resource, \
     has_role_assignment_on_resource
@@ -58,7 +58,7 @@ def aro_create(cmd,  # pylint: disable=too-many-locals
                apiserver_visibility=None,
                ingress_visibility=None,
                tags=None,
-               install_version=None,
+               version=None,
                no_wait=False):
     if not rp_mode_development():
         resource_client = get_mgmt_service_client(
@@ -106,7 +106,7 @@ def aro_create(cmd,  # pylint: disable=too-many-locals
             resource_group_id=(f"/subscriptions/{subscription_id}"
                                f"/resourceGroups/{cluster_resource_group or 'aro-' + random_id}"),
             fips_validated_modules='Enabled' if fips_validated_modules else 'Disabled',
-            version=install_version or '',
+            version=version or '',
 
         ),
         service_principal_profile=openshiftcluster.ServicePrincipalProfile(
@@ -278,13 +278,19 @@ def generate_random_id():
     return random_id
 
 
-def get_network_resources_from_subnets(cli_ctx, subnets):
+def get_network_resources_from_subnets(cli_ctx, subnets, fail):
     network_client = get_mgmt_service_client(cli_ctx, ResourceType.MGMT_NETWORK)
 
     subnet_resources = set()
     for sn in subnets:
         sid = parse_resource_id(sn)
 
+        if 'resource_group' not in sid or 'name' not in sid or 'resource_name' not in sid:
+            if fail:
+                raise ValidationError(f"""(ValidationError) Failed to validate subnet '{sn}'.
+                    Please retry, if issue persists: raise azure support ticket""")
+            logger.info("Failed to validate subnet '%s'", sn)
+
         subnet = network_client.subnets.get(resource_group_name=sid['resource_group'],
                                             virtual_network_name=sid['name'],
                                             subnet_name=sid['resource_name'])
@@ -298,7 +304,7 @@ def get_network_resources_from_subnets(cli_ctx, subnets):
     return subnet_resources
 
 
-def get_cluster_network_resources(cli_ctx, oc):
+def get_cluster_network_resources(cli_ctx, oc, fail):
     master_subnet = oc.master_profile.subnet_id
     worker_subnets = set()
 
@@ -316,11 +322,11 @@ def get_cluster_network_resources(cli_ctx, oc):
         name=master_parts['name'],
     )
 
-    return get_network_resources(cli_ctx, worker_subnets | {master_subnet}, vnet)
+    return get_network_resources(cli_ctx, worker_subnets | {master_subnet}, vnet, fail)
 
 
-def get_network_resources(cli_ctx, subnets, vnet):
-    subnet_resources = get_network_resources_from_subnets(cli_ctx, subnets)
+def get_network_resources(cli_ctx, subnets, vnet, fail):
+    subnet_resources = get_network_resources_from_subnets(cli_ctx, subnets, fail)
 
     resources = set()
     resources.add(vnet)
@@ -421,7 +427,7 @@ def resolve_rp_client_id():
 def ensure_resource_permissions(cli_ctx, oc, fail, sp_obj_ids):
     try:
         # Get cluster resources we need to assign permissions on, sort to ensure the same order of operations
-        resources = {ROLE_NETWORK_CONTRIBUTOR: sorted(get_cluster_network_resources(cli_ctx, oc)),
+        resources = {ROLE_NETWORK_CONTRIBUTOR: sorted(get_cluster_network_resources(cli_ctx, oc, fail)),
                      ROLE_READER: sorted(get_disk_encryption_resources(oc))}
     except (CloudError, HttpOperationError) as e:
         if fail:
diff --git a/vendor/github.com/openshift/client-go/imageregistry/clientset/versioned/fake/clientset_generated.go b/vendor/github.com/openshift/client-go/imageregistry/clientset/versioned/fake/clientset_generated.go
deleted file mode 100644
index 8f383a34c..000000000
--- a/vendor/github.com/openshift/client-go/imageregistry/clientset/versioned/fake/clientset_generated.go
+++ /dev/null
@@ -1,69 +0,0 @@
-// Code generated by client-gen. DO NOT EDIT.
-
-package fake
-
-import (
-	clientset "github.com/openshift/client-go/imageregistry/clientset/versioned"
-	imageregistryv1 "github.com/openshift/client-go/imageregistry/clientset/versioned/typed/imageregistry/v1"
-	fakeimageregistryv1 "github.com/openshift/client-go/imageregistry/clientset/versioned/typed/imageregistry/v1/fake"
-	"k8s.io/apimachinery/pkg/runtime"
-	"k8s.io/apimachinery/pkg/watch"
-	"k8s.io/client-go/discovery"
-	fakediscovery "k8s.io/client-go/discovery/fake"
-	"k8s.io/client-go/testing"
-)
-
-// NewSimpleClientset returns a clientset that will respond with the provided objects.
-// It's backed by a very simple object tracker that processes creates, updates and deletions as-is,
-// without applying any validations and/or defaults. It shouldn't be considered a replacement
-// for a real clientset and is mostly useful in simple unit tests.
-func NewSimpleClientset(objects ...runtime.Object) *Clientset {
-	o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())
-	for _, obj := range objects {
-		if err := o.Add(obj); err != nil {
-			panic(err)
-		}
-	}
-
-	cs := &Clientset{tracker: o}
-	cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake}
-	cs.AddReactor("*", "*", testing.ObjectReaction(o))
-	cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
-		gvr := action.GetResource()
-		ns := action.GetNamespace()
-		watch, err := o.Watch(gvr, ns)
-		if err != nil {
-			return false, nil, err
-		}
-		return true, watch, nil
-	})
-
-	return cs
-}
-
-// Clientset implements clientset.Interface. Meant to be embedded into a
-// struct to get a default implementation. This makes faking out just the method
-// you want to test easier.
-type Clientset struct {
-	testing.Fake
-	discovery *fakediscovery.FakeDiscovery
-	tracker   testing.ObjectTracker
-}
-
-func (c *Clientset) Discovery() discovery.DiscoveryInterface {
-	return c.discovery
-}
-
-func (c *Clientset) Tracker() testing.ObjectTracker {
-	return c.tracker
-}
-
-var (
-	_ clientset.Interface = &Clientset{}
-	_ testing.FakeClient  = &Clientset{}
-)
-
-// ImageregistryV1 retrieves the ImageregistryV1Client
-func (c *Clientset) ImageregistryV1() imageregistryv1.ImageregistryV1Interface {
-	return &fakeimageregistryv1.FakeImageregistryV1{Fake: &c.Fake}
-}
diff --git a/vendor/github.com/openshift/client-go/imageregistry/clientset/versioned/fake/doc.go b/vendor/github.com/openshift/client-go/imageregistry/clientset/versioned/fake/doc.go
deleted file mode 100644
index 3630ed1cd..000000000
--- a/vendor/github.com/openshift/client-go/imageregistry/clientset/versioned/fake/doc.go
+++ /dev/null
@@ -1,4 +0,0 @@
-// Code generated by client-gen. DO NOT EDIT.
-
-// This package has the automatically generated fake clientset.
-package fake
diff --git a/vendor/github.com/openshift/client-go/imageregistry/clientset/versioned/fake/register.go b/vendor/github.com/openshift/client-go/imageregistry/clientset/versioned/fake/register.go
deleted file mode 100644
index 4ddf2ee1e..000000000
--- a/vendor/github.com/openshift/client-go/imageregistry/clientset/versioned/fake/register.go
+++ /dev/null
@@ -1,40 +0,0 @@
-// Code generated by client-gen. DO NOT EDIT.
-
-package fake
-
-import (
-	imageregistryv1 "github.com/openshift/api/imageregistry/v1"
-	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	runtime "k8s.io/apimachinery/pkg/runtime"
-	schema "k8s.io/apimachinery/pkg/runtime/schema"
-	serializer "k8s.io/apimachinery/pkg/runtime/serializer"
-	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
-)
-
-var scheme = runtime.NewScheme()
-var codecs = serializer.NewCodecFactory(scheme)
-
-var localSchemeBuilder = runtime.SchemeBuilder{
-	imageregistryv1.AddToScheme,
-}
-
-// AddToScheme adds all types of this clientset into the given scheme. This allows composition
-// of clientsets, like in:
-//
-//   import (
-//     "k8s.io/client-go/kubernetes"
-//     clientsetscheme "k8s.io/client-go/kubernetes/scheme"
-//     aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
-//   )
-//
-//   kclientset, _ := kubernetes.NewForConfig(c)
-//   _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
-//
-// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
-// correctly.
-var AddToScheme = localSchemeBuilder.AddToScheme
-
-func init() {
-	v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"})
-	utilruntime.Must(AddToScheme(scheme))
-}
diff --git a/vendor/github.com/openshift/client-go/imageregistry/clientset/versioned/typed/imageregistry/v1/fake/doc.go b/vendor/github.com/openshift/client-go/imageregistry/clientset/versioned/typed/imageregistry/v1/fake/doc.go
deleted file mode 100644
index 2b5ba4c8e..000000000
--- a/vendor/github.com/openshift/client-go/imageregistry/clientset/versioned/typed/imageregistry/v1/fake/doc.go
+++ /dev/null
@@ -1,4 +0,0 @@
-// Code generated by client-gen. DO NOT EDIT.
-
-// Package fake has the automatically generated clients.
-package fake
diff --git a/vendor/github.com/openshift/client-go/imageregistry/clientset/versioned/typed/imageregistry/v1/fake/fake_config.go b/vendor/github.com/openshift/client-go/imageregistry/clientset/versioned/typed/imageregistry/v1/fake/fake_config.go
deleted file mode 100644
index 064421584..000000000
--- a/vendor/github.com/openshift/client-go/imageregistry/clientset/versioned/typed/imageregistry/v1/fake/fake_config.go
+++ /dev/null
@@ -1,117 +0,0 @@
-// Code generated by client-gen. DO NOT EDIT.
-
-package fake
-
-import (
-	"context"
-
-	imageregistryv1 "github.com/openshift/api/imageregistry/v1"
-	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	labels "k8s.io/apimachinery/pkg/labels"
-	schema "k8s.io/apimachinery/pkg/runtime/schema"
-	types "k8s.io/apimachinery/pkg/types"
-	watch "k8s.io/apimachinery/pkg/watch"
-	testing "k8s.io/client-go/testing"
-)
-
-// FakeConfigs implements ConfigInterface
-type FakeConfigs struct {
-	Fake *FakeImageregistryV1
-}
-
-var configsResource = schema.GroupVersionResource{Group: "imageregistry.operator.openshift.io", Version: "v1", Resource: "configs"}
-
-var configsKind = schema.GroupVersionKind{Group: "imageregistry.operator.openshift.io", Version: "v1", Kind: "Config"}
-
-// Get takes name of the config, and returns the corresponding config object, and an error if there is any.
-func (c *FakeConfigs) Get(ctx context.Context, name string, options v1.GetOptions) (result *imageregistryv1.Config, err error) {
-	obj, err := c.Fake.
-		Invokes(testing.NewRootGetAction(configsResource, name), &imageregistryv1.Config{})
-	if obj == nil {
-		return nil, err
-	}
-	return obj.(*imageregistryv1.Config), err
-}
-
-// List takes label and field selectors, and returns the list of Configs that match those selectors.
-func (c *FakeConfigs) List(ctx context.Context, opts v1.ListOptions) (result *imageregistryv1.ConfigList, err error) {
-	obj, err := c.Fake.
-		Invokes(testing.NewRootListAction(configsResource, configsKind, opts), &imageregistryv1.ConfigList{})
-	if obj == nil {
-		return nil, err
-	}
-
-	label, _, _ := testing.ExtractFromListOptions(opts)
-	if label == nil {
-		label = labels.Everything()
-	}
-	list := &imageregistryv1.ConfigList{ListMeta: obj.(*imageregistryv1.ConfigList).ListMeta}
-	for _, item := range obj.(*imageregistryv1.ConfigList).Items {
-		if label.Matches(labels.Set(item.Labels)) {
-			list.Items = append(list.Items, item)
-		}
-	}
-	return list, err
-}
-
-// Watch returns a watch.Interface that watches the requested configs.
-func (c *FakeConfigs) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
-	return c.Fake.
-		InvokesWatch(testing.NewRootWatchAction(configsResource, opts))
-}
-
-// Create takes the representation of a config and creates it.  Returns the server's representation of the config, and an error, if there is any.
-func (c *FakeConfigs) Create(ctx context.Context, config *imageregistryv1.Config, opts v1.CreateOptions) (result *imageregistryv1.Config, err error) {
-	obj, err := c.Fake.
-		Invokes(testing.NewRootCreateAction(configsResource, config), &imageregistryv1.Config{})
-	if obj == nil {
-		return nil, err
-	}
-	return obj.(*imageregistryv1.Config), err
-}
-
-// Update takes the representation of a config and updates it. Returns the server's representation of the config, and an error, if there is any.
-func (c *FakeConfigs) Update(ctx context.Context, config *imageregistryv1.Config, opts v1.UpdateOptions) (result *imageregistryv1.Config, err error) {
-	obj, err := c.Fake.
-		Invokes(testing.NewRootUpdateAction(configsResource, config), &imageregistryv1.Config{})
-	if obj == nil {
-		return nil, err
-	}
-	return obj.(*imageregistryv1.Config), err
-}
-
-// UpdateStatus was generated because the type contains a Status member.
-// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
-func (c *FakeConfigs) UpdateStatus(ctx context.Context, config *imageregistryv1.Config, opts v1.UpdateOptions) (*imageregistryv1.Config, error) {
-	obj, err := c.Fake.
-		Invokes(testing.NewRootUpdateSubresourceAction(configsResource, "status", config), &imageregistryv1.Config{})
-	if obj == nil {
-		return nil, err
-	}
-	return obj.(*imageregistryv1.Config), err
-}
-
-// Delete takes name of the config and deletes it. Returns an error if one occurs.
-func (c *FakeConfigs) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
-	_, err := c.Fake.
-		Invokes(testing.NewRootDeleteActionWithOptions(configsResource, name, opts), &imageregistryv1.Config{})
-	return err
-}
-
-// DeleteCollection deletes a collection of objects.
-func (c *FakeConfigs) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
-	action := testing.NewRootDeleteCollectionAction(configsResource, listOpts)
-
-	_, err := c.Fake.Invokes(action, &imageregistryv1.ConfigList{})
-	return err
-}
-
-// Patch applies the patch and returns the patched config.
-func (c *FakeConfigs) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *imageregistryv1.Config, err error) {
-	obj, err := c.Fake.
-		Invokes(testing.NewRootPatchSubresourceAction(configsResource, name, pt, data, subresources...), &imageregistryv1.Config{})
-	if obj == nil {
-		return nil, err
-	}
-	return obj.(*imageregistryv1.Config), err
-}
diff --git a/vendor/github.com/openshift/client-go/imageregistry/clientset/versioned/typed/imageregistry/v1/fake/fake_imagepruner.go b/vendor/github.com/openshift/client-go/imageregistry/clientset/versioned/typed/imageregistry/v1/fake/fake_imagepruner.go
deleted file mode 100644
index c224e7199..000000000
--- a/vendor/github.com/openshift/client-go/imageregistry/clientset/versioned/typed/imageregistry/v1/fake/fake_imagepruner.go
+++ /dev/null
@@ -1,117 +0,0 @@
-// Code generated by client-gen. DO NOT EDIT.
-
-package fake
-
-import (
-	"context"
-
-	imageregistryv1 "github.com/openshift/api/imageregistry/v1"
-	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	labels "k8s.io/apimachinery/pkg/labels"
-	schema "k8s.io/apimachinery/pkg/runtime/schema"
-	types "k8s.io/apimachinery/pkg/types"
-	watch "k8s.io/apimachinery/pkg/watch"
-	testing "k8s.io/client-go/testing"
-)
-
-// FakeImagePruners implements ImagePrunerInterface
-type FakeImagePruners struct {
-	Fake *FakeImageregistryV1
-}
-
-var imageprunersResource = schema.GroupVersionResource{Group: "imageregistry.operator.openshift.io", Version: "v1", Resource: "imagepruners"}
-
-var imageprunersKind = schema.GroupVersionKind{Group: "imageregistry.operator.openshift.io", Version: "v1", Kind: "ImagePruner"}
-
-// Get takes name of the imagePruner, and returns the corresponding imagePruner object, and an error if there is any.
-func (c *FakeImagePruners) Get(ctx context.Context, name string, options v1.GetOptions) (result *imageregistryv1.ImagePruner, err error) {
-	obj, err := c.Fake.
-		Invokes(testing.NewRootGetAction(imageprunersResource, name), &imageregistryv1.ImagePruner{})
-	if obj == nil {
-		return nil, err
-	}
-	return obj.(*imageregistryv1.ImagePruner), err
-}
-
-// List takes label and field selectors, and returns the list of ImagePruners that match those selectors.
-func (c *FakeImagePruners) List(ctx context.Context, opts v1.ListOptions) (result *imageregistryv1.ImagePrunerList, err error) {
-	obj, err := c.Fake.
-		Invokes(testing.NewRootListAction(imageprunersResource, imageprunersKind, opts), &imageregistryv1.ImagePrunerList{})
-	if obj == nil {
-		return nil, err
-	}
-
-	label, _, _ := testing.ExtractFromListOptions(opts)
-	if label == nil {
-		label = labels.Everything()
-	}
-	list := &imageregistryv1.ImagePrunerList{ListMeta: obj.(*imageregistryv1.ImagePrunerList).ListMeta}
-	for _, item := range obj.(*imageregistryv1.ImagePrunerList).Items {
-		if label.Matches(labels.Set(item.Labels)) {
-			list.Items = append(list.Items, item)
-		}
-	}
-	return list, err
-}
-
-// Watch returns a watch.Interface that watches the requested imagePruners.
-func (c *FakeImagePruners) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
-	return c.Fake.
-		InvokesWatch(testing.NewRootWatchAction(imageprunersResource, opts))
-}
-
-// Create takes the representation of a imagePruner and creates it.  Returns the server's representation of the imagePruner, and an error, if there is any.
-func (c *FakeImagePruners) Create(ctx context.Context, imagePruner *imageregistryv1.ImagePruner, opts v1.CreateOptions) (result *imageregistryv1.ImagePruner, err error) {
-	obj, err := c.Fake.
-		Invokes(testing.NewRootCreateAction(imageprunersResource, imagePruner), &imageregistryv1.ImagePruner{})
-	if obj == nil {
-		return nil, err
-	}
-	return obj.(*imageregistryv1.ImagePruner), err
-}
-
-// Update takes the representation of a imagePruner and updates it. Returns the server's representation of the imagePruner, and an error, if there is any.
-func (c *FakeImagePruners) Update(ctx context.Context, imagePruner *imageregistryv1.ImagePruner, opts v1.UpdateOptions) (result *imageregistryv1.ImagePruner, err error) {
-	obj, err := c.Fake.
-		Invokes(testing.NewRootUpdateAction(imageprunersResource, imagePruner), &imageregistryv1.ImagePruner{})
-	if obj == nil {
-		return nil, err
-	}
-	return obj.(*imageregistryv1.ImagePruner), err
-}
-
-// UpdateStatus was generated because the type contains a Status member.
-// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
-func (c *FakeImagePruners) UpdateStatus(ctx context.Context, imagePruner *imageregistryv1.ImagePruner, opts v1.UpdateOptions) (*imageregistryv1.ImagePruner, error) {
-	obj, err := c.Fake.
-		Invokes(testing.NewRootUpdateSubresourceAction(imageprunersResource, "status", imagePruner), &imageregistryv1.ImagePruner{})
-	if obj == nil {
-		return nil, err
-	}
-	return obj.(*imageregistryv1.ImagePruner), err
-}
-
-// Delete takes name of the imagePruner and deletes it. Returns an error if one occurs.
-func (c *FakeImagePruners) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
-	_, err := c.Fake.
-		Invokes(testing.NewRootDeleteActionWithOptions(imageprunersResource, name, opts), &imageregistryv1.ImagePruner{})
-	return err
-}
-
-// DeleteCollection deletes a collection of objects.
-func (c *FakeImagePruners) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
-	action := testing.NewRootDeleteCollectionAction(imageprunersResource, listOpts)
-
-	_, err := c.Fake.Invokes(action, &imageregistryv1.ImagePrunerList{})
-	return err
-}
-
-// Patch applies the patch and returns the patched imagePruner.
-func (c *FakeImagePruners) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *imageregistryv1.ImagePruner, err error) {
-	obj, err := c.Fake.
-		Invokes(testing.NewRootPatchSubresourceAction(imageprunersResource, name, pt, data, subresources...), &imageregistryv1.ImagePruner{})
-	if obj == nil {
-		return nil, err
-	}
-	return obj.(*imageregistryv1.ImagePruner), err
-}
diff --git a/vendor/github.com/openshift/client-go/imageregistry/clientset/versioned/typed/imageregistry/v1/fake/fake_imageregistry_client.go b/vendor/github.com/openshift/client-go/imageregistry/clientset/versioned/typed/imageregistry/v1/fake/fake_imageregistry_client.go
deleted file mode 100644
index 4c384a346..000000000
--- a/vendor/github.com/openshift/client-go/imageregistry/clientset/versioned/typed/imageregistry/v1/fake/fake_imageregistry_client.go
+++ /dev/null
@@ -1,28 +0,0 @@
-// Code generated by client-gen. DO NOT EDIT.
-
-package fake
-
-import (
-	v1 "github.com/openshift/client-go/imageregistry/clientset/versioned/typed/imageregistry/v1"
-	rest "k8s.io/client-go/rest"
-	testing "k8s.io/client-go/testing"
-)
-
-type FakeImageregistryV1 struct {
-	*testing.Fake
-}
-
-func (c *FakeImageregistryV1) Configs() v1.ConfigInterface {
-	return &FakeConfigs{c}
-}
-
-func (c *FakeImageregistryV1) ImagePruners() v1.ImagePrunerInterface {
-	return &FakeImagePruners{c}
-}
-
-// RESTClient returns a RESTClient that is used to communicate
-// with API server by this client implementation.
-func (c *FakeImageregistryV1) RESTClient() rest.Interface {
-	var ret *rest.RESTClient
-	return ret
-}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index f22222dd0..99c6ba3a0 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1077,10 +1077,8 @@ github.com/openshift/client-go/config/clientset/versioned/scheme
 github.com/openshift/client-go/config/clientset/versioned/typed/config/v1
 github.com/openshift/client-go/config/clientset/versioned/typed/config/v1/fake
 github.com/openshift/client-go/imageregistry/clientset/versioned
-github.com/openshift/client-go/imageregistry/clientset/versioned/fake
 github.com/openshift/client-go/imageregistry/clientset/versioned/scheme
 github.com/openshift/client-go/imageregistry/clientset/versioned/typed/imageregistry/v1
-github.com/openshift/client-go/imageregistry/clientset/versioned/typed/imageregistry/v1/fake
 github.com/openshift/client-go/machine/clientset/versioned
 github.com/openshift/client-go/machine/clientset/versioned/fake
 github.com/openshift/client-go/machine/clientset/versioned/scheme


Test patch:
diff --git a/pkg/cluster/adminupdate_test.go b/pkg/cluster/adminupdate_test.go
index 256061dcb..a118b17b5 100644
--- a/pkg/cluster/adminupdate_test.go
+++ b/pkg/cluster/adminupdate_test.go
@@ -206,6 +206,48 @@ func TestAdminUpdateSteps(t *testing.T) {
 				"[Action renewMDSDCertificate-fm]",
 			},
 		},
+		{
+			name: "adminUpdate() does not adopt Hive-created clusters",
+			fixture: func() (*api.OpenShiftClusterDocument, bool) {
+				doc := baseClusterDoc()
+				doc.OpenShiftCluster.Properties.ProvisioningState = api.ProvisioningStateAdminUpdating
+				doc.OpenShiftCluster.Properties.MaintenanceTask = api.MaintenanceTaskEverything
+				doc.OpenShiftCluster.Properties.HiveProfile.Namespace = "some_namespace"
+				doc.OpenShiftCluster.Properties.HiveProfile.CreatedByHive = true
+				return doc, true
+			},
+			shouldRunSteps: []string{
+				"[Action initializeKubernetesClients-fm]",
+				"[Action ensureBillingRecord-fm]",
+				"[Action ensureDefaults-fm]",
+				"[Action fixupClusterSPObjectID-fm]",
+				"[Action fixInfraID-fm]",
+				"[AuthorizationRefreshingAction [Action ensureResourceGroup-fm]]",
+				"[Action createOrUpdateDenyAssignment-fm]",
+				"[AuthorizationRefreshingAction [Action enableServiceEndpoints-fm]]",
+				"[Action populateRegistryStorageAccountName-fm]",
+				"[Action migrateStorageAccounts-fm]",
+				"[Action fixSSH-fm]",
+				"[Action populateDatabaseIntIP-fm]",
+				"[Action startVMs-fm]",
+				"[Condition apiServersReady-fm, timeout 30m0s]",
+				"[Action fixSREKubeconfig-fm]",
+				"[Action fixUserAdminKubeconfig-fm]",
+				"[Action createOrUpdateRouterIPFromCluster-fm]",
+				"[Action fixMCSCert-fm]",
+				"[Action fixMCSUserData-fm]",
+				"[Action ensureGatewayUpgrade-fm]",
+				"[Action configureAPIServerCertificate-fm]",
+				"[Action configureIngressCertificate-fm]",
+				"[Action populateRegistryStorageAccountName-fm]",
+				"[Action ensureMTUSize-fm]",
+				"[Action initializeOperatorDeployer-fm]",
+				"[Action ensureAROOperator-fm]",
+				"[Condition aroDeploymentReady-fm, timeout 20m0s]",
+				"[Condition ensureAROOperatorRunningDesiredVersion-fm, timeout 5m0s]",
+				"[Action updateProvisionedBy-fm]",
+			},
+		},
 	} {
 		t.Run(tt.name, func(t *testing.T) {
 			doc, adoptViaHive := tt.fixture()
diff --git a/pkg/cluster/install_test.go b/pkg/cluster/install_test.go
index ae4a8d729..355bde3c5 100644
--- a/pkg/cluster/install_test.go
+++ b/pkg/cluster/install_test.go
@@ -23,6 +23,7 @@ import (
 	"k8s.io/client-go/kubernetes/fake"
 
 	"github.com/Azure/ARO-RP/pkg/api"
+	mock_hive "github.com/Azure/ARO-RP/pkg/util/mocks/hive"
 	"github.com/Azure/ARO-RP/pkg/util/steps"
 	"github.com/Azure/ARO-RP/pkg/util/version"
 	testdatabase "github.com/Azure/ARO-RP/test/database"
@@ -223,7 +224,6 @@ func TestUpdateProvisionedBy(t *testing.T) {
 		t.Error(err)
 	}
 
-	// Check it was set to the correct value in the database
 	updatedClusterDoc, err := openShiftClustersDatabase.Get(ctx, strings.ToLower(key))
 	if err != nil {
 		t.Fatal(err)
@@ -293,3 +293,63 @@ func TestInstallationTimeMetrics(t *testing.T) {
 		})
 	}
 }
+
+func TestRunHiveInstallerSetsCreatedByHiveFieldToTrueInClusterDoc(t *testing.T) {
+	ctx := context.Background()
+	key := "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName1"
+
+	openShiftClustersDatabase, _ := testdatabase.NewFakeOpenShiftClusters()
+	fixture := testdatabase.NewFixture().WithOpenShiftClusters(openShiftClustersDatabase)
+	doc := &api.OpenShiftClusterDocument{
+		Key: strings.ToLower(key),
+		OpenShiftCluster: &api.OpenShiftCluster{
+			ID: key,
+			Properties: api.OpenShiftClusterProperties{
+				ProvisioningState: api.ProvisioningStateCreating,
+			},
+		},
+	}
+
+	fixture.AddOpenShiftClusterDocuments(doc)
+	err := fixture.Create()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	dequeuedDoc, err := openShiftClustersDatabase.Dequeue(ctx)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	controller := gomock.NewController(t)
+	defer controller.Finish()
+
+	hiveClusterManagerMock := mock_hive.NewMockClusterManager(controller)
+	hiveClusterManagerMock.EXPECT().Install(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
+
+	m := &manager{
+		doc: dequeuedDoc,
+		db:  openShiftClustersDatabase,
+		openShiftClusterDocumentVersioner: &FakeOpenShiftClusterDocumentVersionerService{
+			expectedOpenShiftVersion: nil,
+			expectedError:            nil,
+		},
+		hiveClusterManager: hiveClusterManagerMock,
+	}
+
+	err = m.runHiveInstaller(ctx)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	updatedDoc, err := openShiftClustersDatabase.Get(ctx, strings.ToLower(key))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expected := true
+	got := updatedDoc.OpenShiftCluster.Properties.HiveProfile.CreatedByHive
+	if got != expected {
+		t.Fatalf("expected updatedDoc.OpenShiftCluster.Properties.HiveProfile.CreatedByHive set to %v, but got %v", expected, got)
+	}
+}
diff --git a/pkg/cluster/install_version_test.go b/pkg/cluster/install_version_test.go
index 1e3244eea..011686f4c 100644
--- a/pkg/cluster/install_version_test.go
+++ b/pkg/cluster/install_version_test.go
@@ -47,6 +47,7 @@ func TestGetOpenShiftVersionFromVersion(t *testing.T) {
 						},
 					},
 				},
+				openShiftClusterDocumentVersioner: new(openShiftClusterDocumentVersionerService),
 			},
 			wantErrString: "",
 			want: &api.OpenShiftVersion{
@@ -90,6 +91,7 @@ func TestGetOpenShiftVersionFromVersion(t *testing.T) {
 						},
 					},
 				},
+				openShiftClusterDocumentVersioner: new(openShiftClusterDocumentVersionerService),
 			},
 			wantErrString: "400: InvalidParameter: properties.clusterProfile.version: The requested OpenShift version '4.11.5' is not supported.",
 			want:          nil,
diff --git a/pkg/operator/controllers/storageaccounts/storageaccounts_test.go b/pkg/operator/controllers/storageaccounts/storageaccounts_test.go
index 71fea1b32..e83acbd2c 100644
--- a/pkg/operator/controllers/storageaccounts/storageaccounts_test.go
+++ b/pkg/operator/controllers/storageaccounts/storageaccounts_test.go
@@ -12,14 +12,14 @@ import (
 	"github.com/Azure/go-autorest/autorest/to"
 	"github.com/golang/mock/gomock"
 	imageregistryv1 "github.com/openshift/api/imageregistry/v1"
-	imageregistryclient "github.com/openshift/client-go/imageregistry/clientset/versioned"
-	imageregistryfake "github.com/openshift/client-go/imageregistry/clientset/versioned/fake"
 	"github.com/sirupsen/logrus"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 
 	arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
 	mock_storage "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/mgmt/storage"
 	mock_subnet "github.com/Azure/ARO-RP/pkg/util/mocks/subnet"
+	_ "github.com/Azure/ARO-RP/pkg/util/scheme"
 	"github.com/Azure/ARO-RP/pkg/util/subnet"
 )
 
@@ -74,12 +74,11 @@ func TestReconcileManager(t *testing.T) {
 	log := logrus.NewEntry(logrus.StandardLogger())
 
 	for _, tt := range []struct {
-		name             string
-		mocks            func(*mock_storage.MockAccountsClient, *mock_subnet.MockKubeManager)
-		imageregistrycli imageregistryclient.Interface
-		instance         func(*arov1alpha1.Cluster)
-		operatorFlag     bool
-		wantErr          error
+		name         string
+		mocks        func(*mock_storage.MockAccountsClient, *mock_subnet.MockKubeManager)
+		instance     func(*arov1alpha1.Cluster)
+		operatorFlag bool
+		wantErr      error
 	}{
 		{
 			name:         "Operator Flag enabled - nothing to do",
@@ -102,20 +101,6 @@ func TestReconcileManager(t *testing.T) {
 				storage.EXPECT().GetProperties(gomock.Any(), clusterResourceGroupName, clusterStorageAccountName, gomock.Any()).Return(*result, nil)
 				storage.EXPECT().GetProperties(gomock.Any(), clusterResourceGroupName, registryStorageAccountName, gomock.Any()).Return(*result, nil)
 			},
-			imageregistrycli: imageregistryfake.NewSimpleClientset(
-				&imageregistryv1.Config{
-					ObjectMeta: metav1.ObjectMeta{
-						Name: "cluster",
-					},
-					Spec: imageregistryv1.ImageRegistrySpec{
-						Storage: imageregistryv1.ImageRegistryConfigStorage{
-							Azure: &imageregistryv1.ImageRegistryConfigStorageAzure{
-								AccountName: registryStorageAccountName,
-							},
-						},
-					},
-				},
-			),
 		},
 		{
 			name:         "Operator Flag disabled - nothing to do",
@@ -138,20 +123,6 @@ func TestReconcileManager(t *testing.T) {
 				storage.EXPECT().GetProperties(gomock.Any(), clusterResourceGroupName, clusterStorageAccountName, gomock.Any()).Return(*result, nil)
 				storage.EXPECT().GetProperties(gomock.Any(), clusterResourceGroupName, registryStorageAccountName, gomock.Any()).Return(*result, nil)
 			},
-			imageregistrycli: imageregistryfake.NewSimpleClientset(
-				&imageregistryv1.Config{
-					ObjectMeta: metav1.ObjectMeta{
-						Name: "cluster",
-					},
-					Spec: imageregistryv1.ImageRegistrySpec{
-						Storage: imageregistryv1.ImageRegistryConfigStorage{
-							Azure: &imageregistryv1.ImageRegistryConfigStorageAzure{
-								AccountName: registryStorageAccountName,
-							},
-						},
-					},
-				},
-			),
 		},
 		{
 			name:         "Operator Flag enabled - all rules to all accounts",
@@ -192,20 +163,6 @@ func TestReconcileManager(t *testing.T) {
 				storage.EXPECT().GetProperties(gomock.Any(), clusterResourceGroupName, registryStorageAccountName, gomock.Any()).Return(*result, nil)
 				storage.EXPECT().Update(gomock.Any(), clusterResourceGroupName, registryStorageAccountName, updated)
 			},
-			imageregistrycli: imageregistryfake.NewSimpleClientset(
-				&imageregistryv1.Config{
-					ObjectMeta: metav1.ObjectMeta{
-						Name: "cluster",
-					},
-					Spec: imageregistryv1.ImageRegistrySpec{
-						Storage: imageregistryv1.ImageRegistryConfigStorage{
-							Azure: &imageregistryv1.ImageRegistryConfigStorageAzure{
-								AccountName: registryStorageAccountName,
-							},
-						},
-					},
-				},
-			),
 		},
 	} {
 		t.Run(tt.name, func(t *testing.T) {
@@ -224,13 +181,27 @@ func TestReconcileManager(t *testing.T) {
 				tt.instance(instance)
 			}
 
+			rc := &imageregistryv1.Config{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "cluster",
+				},
+				Spec: imageregistryv1.ImageRegistrySpec{
+					Storage: imageregistryv1.ImageRegistryConfigStorage{
+						Azure: &imageregistryv1.ImageRegistryConfigStorageAzure{
+							AccountName: registryStorageAccountName,
+						},
+					},
+				},
+			}
+			clientFake := fake.NewClientBuilder().WithObjects(rc).Build()
+
 			r := reconcileManager{
-				log:              log,
-				instance:         instance,
-				subscriptionID:   subscriptionId,
-				storage:          storage,
-				kubeSubnets:      kubeSubnet,
-				imageregistrycli: tt.imageregistrycli,
+				log:            log,
+				instance:       instance,
+				subscriptionID: subscriptionId,
+				storage:        storage,
+				kubeSubnets:    kubeSubnet,
+				client:         clientFake,
 			}
 
 			err := r.reconcileAccounts(context.Background())
diff --git a/test/e2e/adminapi_cluster_get.go b/test/e2e/adminapi_cluster_get.go
index 6aa6c9562..e8b28b444 100644
--- a/test/e2e/adminapi_cluster_get.go
+++ b/test/e2e/adminapi_cluster_get.go
@@ -14,13 +14,11 @@ var _ = Describe("[Admin API] Get cluster action", func() {
 	BeforeEach(skipIfNotInDevelopmentEnv)
 
 	It("must return single cluster with admin fields", func(ctx context.Context) {
-		resourceID := resourceIDFromEnv()
-
 		By("requesting the cluster document via RP admin API")
-		oc := adminGetCluster(Default, ctx, resourceID)
+		oc := adminGetCluster(Default, ctx, clusterResourceID)
 
 		By("checking that we received the expected cluster")
-		Expect(oc.ID).To(Equal(resourceID))
+		Expect(oc.ID).To(Equal(clusterResourceID))
 
 		By("checking that fields available only in Admin API have values")
 		// Note: some fields will have empty values
diff --git a/test/e2e/adminapi_cluster_getlogs.go b/test/e2e/adminapi_cluster_getlogs.go
index 6f26a4a0f..eb6bd2f76 100644
--- a/test/e2e/adminapi_cluster_getlogs.go
+++ b/test/e2e/adminapi_cluster_getlogs.go
@@ -70,7 +70,7 @@ func testGetPodLogsOK(ctx context.Context, containerName, podName, namespace str
 		"podname":   []string{podName},
 	}
 	var logs string
-	resp, err := adminRequest(ctx, http.MethodGet, "/admin"+resourceIDFromEnv()+"/kubernetespodlogs", params, nil, &logs)
+	resp, err := adminRequest(ctx, http.MethodGet, "/admin"+clusterResourceID+"/kubernetespodlogs", params, nil, &logs)
 	Expect(err).NotTo(HaveOccurred())
 	Expect(resp.StatusCode).To(Equal(http.StatusOK))
 
@@ -87,7 +87,7 @@ func testGetPodLogsFromCustomerNamespaceForbidden(ctx context.Context, container
 	}
 
 	var logs string
-	resp, err := adminRequest(ctx, http.MethodGet, "/admin"+resourceIDFromEnv()+"/kubernetespodlogs", params, nil, logs)
+	resp, err := adminRequest(ctx, http.MethodGet, "/admin"+clusterResourceID+"/kubernetespodlogs", params, nil, logs)
 	Expect(err).NotTo(HaveOccurred())
 	Expect(resp.StatusCode).To(Equal(http.StatusForbidden))
 
diff --git a/test/e2e/adminapi_cluster_list.go b/test/e2e/adminapi_cluster_list.go
index c4ee6139b..52ac7aa90 100644
--- a/test/e2e/adminapi_cluster_list.go
+++ b/test/e2e/adminapi_cluster_list.go
@@ -17,23 +17,17 @@ var _ = Describe("[Admin API] List clusters action", func() {
 	BeforeEach(skipIfNotInDevelopmentEnv)
 
 	It("must return list of clusters with admin fields", func(ctx context.Context) {
-		resourceID := resourceIDFromEnv()
-
-		testAdminClustersList(Default, ctx, "/admin/providers/Microsoft.RedHatOpenShift/openShiftClusters", resourceID)
+		testAdminClustersList(Default, ctx, "/admin/providers/Microsoft.RedHatOpenShift/openShiftClusters", clusterResourceID)
 	})
 
 	It("must return list of clusters with admin fields by subscription", func(ctx context.Context) {
-		resourceID := resourceIDFromEnv()
-
 		path := fmt.Sprintf("/subscriptions/%s/providers/Microsoft.RedHatOpenShift/openShiftClusters", _env.SubscriptionID())
-		testAdminClustersList(Default, ctx, path, resourceID)
+		testAdminClustersList(Default, ctx, path, clusterResourceID)
 	})
 
 	It("must return list of clusters with admin fields by resource group", func(ctx context.Context) {
-		resourceID := resourceIDFromEnv()
-
 		path := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.RedHatOpenShift/openShiftClusters", _env.SubscriptionID(), vnetResourceGroup)
-		testAdminClustersList(Default, ctx, path, resourceID)
+		testAdminClustersList(Default, ctx, path, clusterResourceID)
 	})
 })
 
diff --git a/test/e2e/adminapi_cluster_update.go b/test/e2e/adminapi_cluster_update.go
index 838e53f73..f7d7e4ede 100644
--- a/test/e2e/adminapi_cluster_update.go
+++ b/test/e2e/adminapi_cluster_update.go
@@ -19,10 +19,9 @@ var _ = Describe("[Admin API] Cluster admin update action", func() {
 
 	It("must run cluster update operation on a cluster", func(ctx context.Context) {
 		var oc = &admin.OpenShiftCluster{}
-		resourceID := resourceIDFromEnv()
 
 		By("triggering the update via RP admin API")
-		resp, err := adminRequest(ctx, http.MethodPatch, resourceID, nil, json.RawMessage("{}"), oc)
+		resp, err := adminRequest(ctx, http.MethodPatch, clusterResourceID, nil, json.RawMessage("{}"), oc)
 		Expect(err).NotTo(HaveOccurred())
 		Expect(resp.StatusCode).To(Equal(http.StatusOK))
 
@@ -32,7 +31,7 @@ var _ = Describe("[Admin API] Cluster admin update action", func() {
 
 		By("waiting for the update to complete")
 		Eventually(func(g Gomega, ctx context.Context) {
-			oc = adminGetCluster(g, ctx, resourceID)
+			oc = adminGetCluster(g, ctx, clusterResourceID)
 
 			g.Expect(oc.Properties.ProvisioningState).To(Equal(admin.ProvisioningStateSucceeded))
 		}).WithContext(ctx).Should(Succeed())
diff --git a/test/e2e/adminapi_csr.go b/test/e2e/adminapi_csr.go
index 92ec92d18..af7c44f6a 100644
--- a/test/e2e/adminapi_csr.go
+++ b/test/e2e/adminapi_csr.go
@@ -65,7 +65,7 @@ func testCSRApproveOK(ctx context.Context, objName, namespace string) {
 	params := url.Values{
 		"csrName": []string{objName},
 	}
-	resp, err := adminRequest(ctx, http.MethodPost, "/admin"+resourceIDFromEnv()+"/approvecsr", params, nil, nil)
+	resp, err := adminRequest(ctx, http.MethodPost, "/admin"+clusterResourceID+"/approvecsr", params, nil, nil)
 	Expect(err).NotTo(HaveOccurred())
 	Expect(resp.StatusCode).To(Equal(http.StatusOK))
 
@@ -87,7 +87,7 @@ func testCSRApproveOK(ctx context.Context, objName, namespace string) {
 
 func testCSRMassApproveOK(ctx context.Context, namePrefix, namespace string, csrCount int) {
 	By("approving all CSRs via RP admin API")
-	resp, err := adminRequest(ctx, http.MethodPost, "/admin"+resourceIDFromEnv()+"/approvecsr", nil, nil, nil)
+	resp, err := adminRequest(ctx, http.MethodPost, "/admin"+clusterResourceID+"/approvecsr", nil, nil, nil)
 	Expect(err).NotTo(HaveOccurred())
 	Expect(resp.StatusCode).To(Equal(http.StatusOK))
 
diff --git a/test/e2e/adminapi_drainnode.go b/test/e2e/adminapi_drainnode.go
index 32f4901e5..faca81ea6 100644
--- a/test/e2e/adminapi_drainnode.go
+++ b/test/e2e/adminapi_drainnode.go
@@ -64,7 +64,7 @@ func testCordonNodeOK(ctx context.Context, nodeName string) {
 		"shouldCordon": []string{"true"},
 		"vmName":       []string{nodeName},
 	}
-	resp, err := adminRequest(ctx, http.MethodPost, "/admin"+resourceIDFromEnv()+"/cordonnode", params, nil, nil)
+	resp, err := adminRequest(ctx, http.MethodPost, "/admin"+clusterResourceID+"/cordonnode", params, nil, nil)
 	Expect(err).NotTo(HaveOccurred())
 	Expect(resp.StatusCode).To(Equal(http.StatusOK))
 
@@ -81,7 +81,7 @@ func testUncordonNodeOK(ctx context.Context, nodeName string) {
 		"shouldCordon": []string{"false"},
 		"vmName":       []string{nodeName},
 	}
-	resp, err := adminRequest(ctx, http.MethodPost, "/admin"+resourceIDFromEnv()+"/cordonnode", params, nil, nil)
+	resp, err := adminRequest(ctx, http.MethodPost, "/admin"+clusterResourceID+"/cordonnode", params, nil, nil)
 	Expect(err).NotTo(HaveOccurred())
 	Expect(resp.StatusCode).To(Equal(http.StatusOK))
 
@@ -97,7 +97,7 @@ func testDrainNodeOK(ctx context.Context, nodeName string, drainer *drain.Helper
 	params := url.Values{
 		"vmName": []string{nodeName},
 	}
-	resp, err := adminRequest(ctx, http.MethodPost, "/admin"+resourceIDFromEnv()+"/drainnode", params, nil, nil)
+	resp, err := adminRequest(ctx, http.MethodPost, "/admin"+clusterResourceID+"/drainnode", params, nil, nil)
 	Expect(err).NotTo(HaveOccurred())
 	Expect(resp.StatusCode).To(Equal(http.StatusOK))
 }
diff --git a/test/e2e/adminapi_kubernetesobjects.go b/test/e2e/adminapi_kubernetesobjects.go
index aad192ed8..c6e3c3dc8 100644
--- a/test/e2e/adminapi_kubernetesobjects.go
+++ b/test/e2e/adminapi_kubernetesobjects.go
@@ -106,7 +106,7 @@ func testSecretOperationsForbidden(objName, namespace string) {
 		By("creating a new secret via RP admin API")
 		obj := mockSecret(objName, namespace)
 		var cloudErr api.CloudError
-		resp, err := adminRequest(ctx, http.MethodPost, "/admin"+resourceIDFromEnv()+"/kubernetesobjects", nil, obj, &cloudErr)
+		resp, err := adminRequest(ctx, http.MethodPost, "/admin"+clusterResourceID+"/kubernetesobjects", nil, obj, &cloudErr)
 		Expect(err).NotTo(HaveOccurred())
 		Expect(resp.StatusCode).To(Equal(http.StatusForbidden))
 		Expect(cloudErr.Code).To(Equal(api.CloudErrorCodeForbidden))
@@ -120,7 +120,7 @@ func testSecretOperationsForbidden(objName, namespace string) {
 			"name":      []string{objName},
 		}
 		var cloudErr api.CloudError
-		resp, err := adminRequest(ctx, http.MethodGet, "/admin"+resourceIDFromEnv()+"/kubernetesobjects", params, nil, &cloudErr)
+		resp, err := adminRequest(ctx, http.MethodGet, "/admin"+clusterResourceID+"/kubernetesobjects", params, nil, &cloudErr)
 		Expect(err).NotTo(HaveOccurred())
 		Expect(resp.StatusCode).To(Equal(http.StatusForbidden))
 
@@ -135,7 +135,7 @@ func testSecretOperationsForbidden(objName, namespace string) {
 			"namespace": []string{namespace},
 		}
 		var cloudErr api.CloudError
-		resp, err := adminRequest(ctx, http.MethodGet, "/admin"+resourceIDFromEnv()+"/kubernetesobjects", params, nil, &cloudErr)
+		resp, err := adminRequest(ctx, http.MethodGet, "/admin"+clusterResourceID+"/kubernetesobjects", params, nil, &cloudErr)
 		Expect(err).NotTo(HaveOccurred())
 		Expect(resp.StatusCode).To(Equal(http.StatusForbidden))
 
@@ -151,7 +151,7 @@ func testSecretOperationsForbidden(objName, namespace string) {
 			"name":      []string{objName},
 		}
 		var cloudErr api.CloudError
-		resp, err := adminRequest(ctx, http.MethodDelete, "/admin"+resourceIDFromEnv()+"/kubernetesobjects", params, nil, &cloudErr)
+		resp, err := adminRequest(ctx, http.MethodDelete, "/admin"+clusterResourceID+"/kubernetesobjects", params, nil, &cloudErr)
 		Expect(err).NotTo(HaveOccurred())
 		Expect(resp.StatusCode).To(Equal(http.StatusForbidden))
 		Expect(cloudErr.Code).To(Equal(api.CloudErrorCodeForbidden))
@@ -161,7 +161,7 @@ func testSecretOperationsForbidden(objName, namespace string) {
 func testConfigMapCreateOK(ctx context.Context, objName, namespace string) {
 	By("creating a new object via RP admin API")
 	obj := mockConfigMap(objName, namespace)
-	resp, err := adminRequest(ctx, http.MethodPost, "/admin"+resourceIDFromEnv()+"/kubernetesobjects", nil, obj, nil)
+	resp, err := adminRequest(ctx, http.MethodPost, "/admin"+clusterResourceID+"/kubernetesobjects", nil, obj, nil)
 	Expect(err).NotTo(HaveOccurred())
 	Expect(resp.StatusCode).To(Equal(http.StatusOK))
 
@@ -181,7 +181,7 @@ func testConfigMapGetOK(ctx context.Context, objName, namespace string) {
 		"name":      []string{objName},
 	}
 	var obj corev1.ConfigMap
-	resp, err := adminRequest(ctx, http.MethodGet, "/admin"+resourceIDFromEnv()+"/kubernetesobjects", params, nil, &obj)
+	resp, err := adminRequest(ctx, http.MethodGet, "/admin"+clusterResourceID+"/kubernetesobjects", params, nil, &obj)
 	Expect(err).NotTo(HaveOccurred())
 	Expect(resp.StatusCode).To(Equal(http.StatusOK))
 
@@ -200,7 +200,7 @@ func testConfigMapListOK(ctx context.Context, objName, namespace string) {
 		"namespace": []string{namespace},
 	}
 	var obj corev1.ConfigMapList
-	resp, err := adminRequest(ctx, http.MethodGet, "/admin"+resourceIDFromEnv()+"/kubernetesobjects", params, nil, &obj)
+	resp, err := adminRequest(ctx, http.MethodGet, "/admin"+clusterResourceID+"/kubernetesobjects", params, nil, &obj)
 	Expect(err).NotTo(HaveOccurred())
 	Expect(resp.StatusCode).To(Equal(http.StatusOK))
 
@@ -217,7 +217,7 @@ func testConfigMapUpdateOK(ctx context.Context, objName, namespace string) {
 	obj := mockConfigMap(objName, namespace)
 	obj.Data["key"] = "new_value"
 
-	resp, err := adminRequest(ctx, http.MethodPost, "/admin"+resourceIDFromEnv()+"/kubernetesobjects", nil, obj, nil)
+	resp, err := adminRequest(ctx, http.MethodPost, "/admin"+clusterResourceID+"/kubernetesobjects", nil, obj, nil)
 	Expect(err).NotTo(HaveOccurred())
 	Expect(resp.StatusCode).To(Equal(http.StatusOK))
 
@@ -238,7 +238,7 @@ func testConfigMapForceDeleteForbidden(ctx context.Context, objName, namespace s
 		"force":     []string{"true"},
 	}
 	var cloudErr api.CloudError
-	resp, err := adminRequest(ctx, http.MethodDelete, "/admin"+resourceIDFromEnv()+"/kubernetesobjects", params, nil, &cloudErr)
+	resp, err := adminRequest(ctx, http.MethodDelete, "/admin"+clusterResourceID+"/kubernetesobjects", params, nil, &cloudErr)
 	Expect(err).NotTo(HaveOccurred())
 	Expect(resp.StatusCode).To(Equal(http.StatusForbidden))
 	Expect(cloudErr.Code).To(Equal(api.CloudErrorCodeForbidden))
@@ -252,7 +252,7 @@ func testConfigMapDeleteOK(ctx context.Context, objName, namespace string) {
 		"name":      []string{objName},
 		"force":     []string{"false"},
 	}
-	resp, err := adminRequest(ctx, http.MethodDelete, "/admin"+resourceIDFromEnv()+"/kubernetesobjects", params, nil, nil)
+	resp, err := adminRequest(ctx, http.MethodDelete, "/admin"+clusterResourceID+"/kubernetesobjects", params, nil, nil)
 	Expect(err).NotTo(HaveOccurred())
 	Expect(resp.StatusCode).To(Equal(http.StatusOK))
 
@@ -269,7 +269,7 @@ func testConfigMapCreateOrUpdateForbidden(ctx context.Context, operation, objNam
 	By(operation + " a new object via RP admin API")
 	obj := mockConfigMap(objName, namespace)
 	var cloudErr api.CloudError
-	resp, err := adminRequest(ctx, http.MethodPost, "/admin"+resourceIDFromEnv()+"/kubernetesobjects", nil, obj, &cloudErr)
+	resp, err := adminRequest(ctx, http.MethodPost, "/admin"+clusterResourceID+"/kubernetesobjects", nil, obj, &cloudErr)
 	Expect(err).NotTo(HaveOccurred())
 	Expect(resp.StatusCode).To(Equal(http.StatusForbidden))
 	Expect(cloudErr.Code).To(Equal(api.CloudErrorCodeForbidden))
@@ -284,7 +284,7 @@ func testConfigMapDeleteForbidden(ctx context.Context, objName, namespace string
 		"force":     []string{"false"},
 	}
 	var cloudErr api.CloudError
-	resp, err := adminRequest(ctx, http.MethodDelete, "/admin"+resourceIDFromEnv()+"/kubernetesobjects", params, nil, &cloudErr)
+	resp, err := adminRequest(ctx, http.MethodDelete, "/admin"+clusterResourceID+"/kubernetesobjects", params, nil, &cloudErr)
 	Expect(err).NotTo(HaveOccurred())
 	Expect(resp.StatusCode).To(Equal(http.StatusForbidden))
 	Expect(cloudErr.Code).To(Equal(api.CloudErrorCodeForbidden))
@@ -293,7 +293,7 @@ func testConfigMapDeleteForbidden(ctx context.Context, objName, namespace string
 func testPodCreateOK(ctx context.Context, containerName, objName, namespace string) {
 	By("creating a new pod via RP admin API")
 	obj := mockPod(containerName, objName, namespace, "")
-	resp, err := adminRequest(ctx, http.MethodPost, "/admin"+resourceIDFromEnv()+"/kubernetesobjects", nil, obj, nil)
+	resp, err := adminRequest(ctx, http.MethodPost, "/admin"+clusterResourceID+"/kubernetesobjects", nil, obj, nil)
 	Expect(err).NotTo(HaveOccurred())
 	Expect(resp.StatusCode).To(Equal(http.StatusOK))
 
@@ -313,7 +313,7 @@ func testPodForceDeleteOK(ctx context.Context, objName, namespace string) {
 		"name":      []string{objName},
 		"force":     []string{"true"},
 	}
-	resp, err := adminRequest(ctx, http.MethodDelete, "/admin"+resourceIDFromEnv()+"/kubernetesobjects", params, nil, nil)
+	resp, err := adminRequest(ctx, http.MethodDelete, "/admin"+clusterResourceID+"/kubernetesobjects", params, nil, nil)
 	Expect(err).NotTo(HaveOccurred())
 	Expect(resp.StatusCode).To(Equal(http.StatusOK))
 
diff --git a/test/e2e/adminapi_redeployvm.go b/test/e2e/adminapi_redeployvm.go
index 5244c7c68..e5edfc608 100644
--- a/test/e2e/adminapi_redeployvm.go
+++ b/test/e2e/adminapi_redeployvm.go
@@ -31,8 +31,6 @@ var _ = Describe("[Admin API] VM redeploy action", func() {
 	BeforeEach(skipIfNotInDevelopmentEnv)
 
 	It("must trigger a selected VM to redeploy", func(ctx context.Context) {
-		resourceID := resourceIDFromEnv()
-
 		By("getting the resource group where the VM instances live in")
 		oc, err := clients.OpenshiftClustersv20200430.Get(ctx, vnetResourceGroup, clusterName)
 		Expect(err).NotTo(HaveOccurred())
@@ -50,7 +48,7 @@ var _ = Describe("[Admin API] VM redeploy action", func() {
 		Expect(err).NotTo(HaveOccurred())
 
 		By("triggering VM redeployment via RP Admin API")
-		resp, err := adminRequest(ctx, http.MethodPost, "/admin"+resourceID+"/redeployvm", url.Values{"vmName": []string{*vm.Name}}, nil, nil)
+		resp, err := adminRequest(ctx, http.MethodPost, "/admin"+clusterResourceID+"/redeployvm", url.Values{"vmName": []string{*vm.Name}}, nil, nil)
 		Expect(err).NotTo(HaveOccurred())
 		Expect(resp.StatusCode).To(Equal(http.StatusOK))
 
diff --git a/test/e2e/adminapi_resources.go b/test/e2e/adminapi_resources.go
index f103cc0de..f83747545 100644
--- a/test/e2e/adminapi_resources.go
+++ b/test/e2e/adminapi_resources.go
@@ -23,8 +23,6 @@ var _ = Describe("[Admin API] List Azure resources action", func() {
 	BeforeEach(skipIfNotInDevelopmentEnv)
 
 	It("must list Azure resources for a cluster", func(ctx context.Context) {
-		resourceID := resourceIDFromEnv()
-
 		By("getting the resource group where cluster resources live in")
 		oc, err := clients.OpenshiftClustersv20200430.Get(ctx, vnetResourceGroup, clusterName)
 		Expect(err).NotTo(HaveOccurred())
@@ -77,7 +75,7 @@ var _ = Describe("[Admin API] List Azure resources action", func() {
 
 		By("getting the actual Azure resource IDs via RP admin API")
 		var actualResources []mgmtfeatures.GenericResourceExpanded
-		resp, err := adminRequest(ctx, http.MethodGet, "/admin"+resourceID+"/resources", nil, nil, &actualResources)
+		resp, err := adminRequest(ctx, http.MethodGet, "/admin"+clusterResourceID+"/resources", nil, nil, &actualResources)
 		Expect(err).NotTo(HaveOccurred())
 		Expect(resp.StatusCode).To(Equal(http.StatusOK))
 
diff --git a/test/e2e/hive.go b/test/e2e/hive.go
new file mode 100644
index 000000000..d57e4d295
--- /dev/null
+++ b/test/e2e/hive.go
@@ -0,0 +1,69 @@
+package e2e
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"strings"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"github.com/Azure/ARO-RP/pkg/api/admin"
+	"github.com/Azure/ARO-RP/pkg/hive"
+)
+
+var (
+	clusterPlatformLabelKey string = "hive.openshift.io/cluster-platform"
+	clusterRegionLabelKey   string = "hive.openshift.io/cluster-region"
+
+	controlPlaneAPIURLOverride = func(clusterDomain string, clusterLocation string) string {
+		if !strings.ContainsRune(clusterDomain, '.') {
+			clusterDomain += "." + clusterLocation + "." + os.Getenv("PARENT_DOMAIN_NAME")
+		}
+
+		return fmt.Sprintf("api-int.%s:6443", clusterDomain)
+	}
+)
+
+var _ = Describe("Hive-managed ARO cluster", func() {
+	var adminAPICluster *admin.OpenShiftCluster
+
+	BeforeEach(func(ctx context.Context) {
+		adminAPICluster = adminGetCluster(Default, ctx, clusterResourceID)
+
+		skipIfNotHiveManagedCluster(adminAPICluster)
+	})
+
+	It("has been properly created/adopted by Hive", func(ctx context.Context) {
+		By("verifying that a corresponding ClusterDeployment object exists in the expected namespace in the Hive cluster")
+		cd, err := clients.Hive.HiveV1().ClusterDeployments(adminAPICluster.Properties.HiveProfile.Namespace).Get(ctx, hive.ClusterDeploymentName, metav1.GetOptions{})
+		Expect(err).NotTo(HaveOccurred())
+
+		By("verifying that the ClusterDeployment object has the expected name and labels")
+		Expect(cd.ObjectMeta).NotTo(BeNil())
+		Expect(cd.ObjectMeta.Name).To(Equal(hive.ClusterDeploymentName))
+		Expect(cd.ObjectMeta.Labels).Should(HaveKey(clusterPlatformLabelKey))
+		Expect(cd.ObjectMeta.Labels[clusterPlatformLabelKey]).To(Equal("azure"))
+		Expect(cd.ObjectMeta.Labels).Should(HaveKey(clusterRegionLabelKey))
+		Expect(cd.ObjectMeta.Labels[clusterRegionLabelKey]).To(Equal(adminAPICluster.Location))
+
+		By("verifying that the ClusterDeployment object spec correctly includes the ARO cluster's Azure region and RG name")
+		Expect(cd.Spec).NotTo(BeNil())
+		Expect(cd.Spec.ClusterName).To(Equal(adminAPICluster.Name))
+		Expect(cd.Spec.Platform).NotTo(BeNil())
+		Expect(cd.Spec.Platform.Azure).NotTo(BeNil())
+		Expect(cd.Spec.Platform.Azure.BaseDomainResourceGroupName).To(Equal(adminAPICluster.Properties.ClusterProfile.ResourceGroupID))
+		Expect(cd.Spec.Platform.Azure.Region).To(Equal(adminAPICluster.Location))
+
+		By("verifying that the ClusterDeployment object spec includes the expected ControlPlaneConfig overrides")
+		Expect(cd.Spec.ControlPlaneConfig).NotTo(BeNil())
+		Expect(cd.Spec.ControlPlaneConfig.APIServerIPOverride).To(Equal(adminAPICluster.Properties.NetworkProfile.APIServerPrivateEndpointIP))
+		Expect(cd.Spec.ControlPlaneConfig.APIURLOverride).To(Equal(controlPlaneAPIURLOverride(adminAPICluster.Properties.ClusterProfile.Domain, adminAPICluster.Location)))
+	})
+})
diff --git a/test/e2e/operator.go b/test/e2e/operator.go
index 25a805ce0..d6e5f947e 100644
--- a/test/e2e/operator.go
+++ b/test/e2e/operator.go
@@ -311,9 +311,14 @@ var _ = Describe("ARO Operator - Azure Subnet Reconciler", func() {
 })
 
 var _ = Describe("ARO Operator - MUO Deployment", func() {
+	const (
+		managedUpgradeOperatorNamespace  = "openshift-managed-upgrade-operator"
+		managedUpgradeOperatorDeployment = "managed-upgrade-operator"
+	)
+
 	It("must be deployed by default with FIPS crypto mandated", func(ctx context.Context) {
 		By("getting MUO pods")
-		pods, err := clients.Kubernetes.CoreV1().Pods("openshift-managed-upgrade-operator").List(ctx, metav1.ListOptions{
+		pods, err := clients.Kubernetes.CoreV1().Pods(managedUpgradeOperatorNamespace).List(ctx, metav1.ListOptions{
 			LabelSelector: "name=managed-upgrade-operator",
 		})
 		Expect(err).NotTo(HaveOccurred())
@@ -321,12 +326,39 @@ var _ = Describe("ARO Operator - MUO Deployment", func() {
 
 		By("verifying that MUO has FIPS crypto mandated by reading logs")
 		Eventually(func(g Gomega, ctx context.Context) {
-			b, err := clients.Kubernetes.CoreV1().Pods("openshift-managed-upgrade-operator").GetLogs(pods.Items[0].Name, &corev1.PodLogOptions{}).DoRaw(ctx)
+			b, err := clients.Kubernetes.CoreV1().Pods(managedUpgradeOperatorNamespace).GetLogs(pods.Items[0].Name, &corev1.PodLogOptions{}).DoRaw(ctx)
 			g.Expect(err).NotTo(HaveOccurred())
 
 			g.Expect(string(b)).To(ContainSubstring(`msg="FIPS crypto mandated: true"`))
 		}).WithContext(ctx).Should(Succeed())
 	})
+
+	It("must be restored if deleted", func(ctx context.Context) {
+		deleteMUODeployment := func(ctx context.Context) error {
+			return clients.Kubernetes.
+				AppsV1().
+				Deployments(managedUpgradeOperatorNamespace).
+				Delete(ctx, managedUpgradeOperatorDeployment, metav1.DeleteOptions{})
+		}
+
+		muoDeploymentExists := func(g Gomega, ctx context.Context) {
+			_, err := clients.Kubernetes.
+				AppsV1().
+				Deployments(managedUpgradeOperatorNamespace).
+				Get(ctx, managedUpgradeOperatorDeployment, metav1.GetOptions{})
+
+			g.Expect(err).ToNot(HaveOccurred())
+		}
+
+		By("waiting for the MUO deployment to be ready")
+		Eventually(muoDeploymentExists).WithContext(ctx).Should(Succeed())
+
+		By("deleting the MUO deployment")
+		Expect(deleteMUODeployment(ctx)).Should(Succeed())
+
+		By("waiting for the MUO deployment to be reconciled")
+		Eventually(muoDeploymentExists).WithContext(ctx).Should(Succeed())
+	})
 })
 
 var _ = Describe("ARO Operator - ImageConfig Reconciler", func() {
diff --git a/test/e2e/setup.go b/test/e2e/setup.go
index c234b3991..770a2930f 100644
--- a/test/e2e/setup.go
+++ b/test/e2e/setup.go
@@ -23,6 +23,7 @@ import (
 	configclient "github.com/openshift/client-go/config/clientset/versioned"
 	machineclient "github.com/openshift/client-go/machine/clientset/versioned"
 	projectclient "github.com/openshift/client-go/project/clientset/versioned"
+	hiveclient "github.com/openshift/hive/pkg/client/clientset/versioned"
 	mcoclient "github.com/openshift/machine-config-operator/pkg/generated/clientset/versioned"
 	"github.com/sirupsen/logrus"
 	"github.com/tebeka/selenium"
@@ -32,6 +33,7 @@ import (
 	"k8s.io/client-go/tools/clientcmd/api"
 	"k8s.io/client-go/tools/clientcmd/api/latest"
 
+	"github.com/Azure/ARO-RP/pkg/api/admin"
 	"github.com/Azure/ARO-RP/pkg/env"
 	"github.com/Azure/ARO-RP/pkg/hive"
 	aroclient "github.com/Azure/ARO-RP/pkg/operator/clientset/versioned"
@@ -60,16 +62,17 @@ type clientSet struct {
 	NetworkSecurityGroups network.SecurityGroupsClient
 	Subnet                network.SubnetsClient
 
+	RestConfig         *rest.Config
+	HiveRestConfig     *rest.Config
+	Kubernetes         kubernetes.Interface
+	MachineAPI         machineclient.Interface
+	MachineConfig      mcoclient.Interface
+	AROClusters        aroclient.Interface
+	ConfigClient       configclient.Interface
+	Project            projectclient.Interface
+	Hive               hiveclient.Interface
 	HiveAKS            kubernetes.Interface
 	HiveClusterManager hive.ClusterManager
-
-	RestConfig    *rest.Config
-	Kubernetes    kubernetes.Interface
-	MachineAPI    machineclient.Interface
-	MachineConfig mcoclient.Interface
-	AROClusters   aroclient.Interface
-	ConfigClient  configclient.Interface
-	Project       projectclient.Interface
 }
 
 var (
@@ -77,6 +80,7 @@ var (
 	_env              env.Core
 	vnetResourceGroup string
 	clusterName       string
+	clusterResourceID string
 	clients           *clientSet
 
 	dockerSucceeded bool
@@ -96,6 +100,12 @@ func skipIfDockerNotWorking() {
 	}
 }
 
+func skipIfNotHiveManagedCluster(adminAPICluster *admin.OpenShiftCluster) {
+	if adminAPICluster.Properties.HiveProfile == (admin.HiveProfile{}) {
+		Skip("skipping tests because this ARO cluster has not been created/adopted by Hive")
+	}
+}
+
 func SaveScreenshotAndExit(wd selenium.WebDriver, e error) {
 	log.Infof("Error : %s", e.Error())
 	log.Info("Taking Screenshot and saving page source")
@@ -295,17 +305,23 @@ func newClientSet(ctx context.Context) (*clientSet, error) {
 		return nil, err
 	}
 
-	aksCfg, err := liveCfg.HiveRestConfig(ctx, 1)
+	hiveShard := 1
+	hiveRestConfig, err := liveCfg.HiveRestConfig(ctx, hiveShard)
+	if err != nil {
+		return nil, err
+	}
+
+	hiveClientSet, err := hiveclient.NewForConfig(hiveRestConfig)
 	if err != nil {
 		return nil, err
 	}
 
-	hiveAKS, err := kubernetes.NewForConfig(aksCfg)
+	hiveAKS, err := kubernetes.NewForConfig(hiveRestConfig)
 	if err != nil {
 		return nil, err
 	}
 
-	hiveCM, err := hive.NewFromConfig(log, _env, aksCfg)
+	hiveCM, err := hive.NewFromConfig(log, _env, hiveRestConfig)
 	if err != nil {
 		return nil, err
 	}
@@ -323,16 +339,17 @@ func newClientSet(ctx context.Context) (*clientSet, error) {
 		Subnet:                network.NewSubnetsClient(_env.Environment(), _env.SubscriptionID(), authorizer),
 		NetworkSecurityGroups: network.NewSecurityGroupsClient(_env.Environment(), _env.SubscriptionID(), authorizer),
 
+		RestConfig:         restconfig,
+		HiveRestConfig:     hiveRestConfig,
+		Kubernetes:         cli,
+		MachineAPI:         machineapicli,
+		MachineConfig:      mcocli,
+		AROClusters:        arocli,
+		Project:            projectcli,
+		ConfigClient:       configcli,
+		Hive:               hiveClientSet,
 		HiveAKS:            hiveAKS,
 		HiveClusterManager: hiveCM,
-
-		RestConfig:    restconfig,
-		Kubernetes:    cli,
-		MachineAPI:    machineapicli,
-		MachineConfig: mcocli,
-		AROClusters:   arocli,
-		Project:       projectcli,
-		ConfigClient:  configcli,
 	}, nil
 }
 
@@ -419,6 +436,8 @@ func setup(ctx context.Context) error {
 		}
 	}
 
+	clusterResourceID = resourceIDFromEnv()
+
 	clients, err = newClientSet(ctx)
 	if err != nil {
 		return err