Repository: Azure/ARO-RP
Pull request #1244 (2021-01-08 19:10:18)

Dynamic Validator Refactor for Operator Compatibility
Things to note: 
1. Removed the dependency from dynamic on any document specific types, so it should be able to be called from anywhere that matches the arguments for `NewValidator`.
2. OpenshiftCluster-validatedynamic still contains validation specific to the context of the RP.  If some of this functionality needs to be moved elsewhere, it would [ideally] move into dynamic.go.  
3. OpenshiftCluster-validatedynamic utilizes the caching mechanism of the virtualnetworkclient in some of it's vnet specific validation.  This can be changed or modified to use the raw `virtualNetworkClient` in `cache.go` instead of borrowing.  

### Which issue this PR addresses:
Refactor openshiftcluster-dynamicvalidator into an interface that allows some functions to be used outside of the context of the RP.  

### What this PR does / why we need it:
This should allow us to call validation methods from the context of the operator to expose them as conditions.  

### Test plan for issue:
Unit tests 
E2E tests

### Is there any documentation that needs to be updated for this PR?
No



Patch:
diff --git a/cmd/aro/rp.go b/cmd/aro/rp.go
index 73bc7c15b..615e2312c 100644
--- a/cmd/aro/rp.go
+++ b/cmd/aro/rp.go
@@ -120,7 +120,7 @@ func rp(ctx context.Context, log *logrus.Entry) error {
 		return err
 	}
 
-	f, err := frontend.NewFrontend(ctx, log.WithField("component", "frontend"), _env, dbAsyncOperations, dbOpenShiftClusters, dbSubscriptions, api.APIs, m, feAead, adminactions.NewKubeActions, adminactions.NewAzureActions)
+	f, err := frontend.NewFrontend(ctx, log.WithField("component", "frontend"), _env, dbAsyncOperations, dbOpenShiftClusters, dbSubscriptions, api.APIs, m, feAead, adminactions.New)
 	if err != nil {
 		return err
 	}
diff --git a/deploy/rp-production.json b/deploy/rp-production.json
index ed0af45ce..e8936939d 100644
--- a/deploy/rp-production.json
+++ b/deploy/rp-production.json
@@ -227,7 +227,6 @@
             "condition": "[parameters('fullDeploy')]",
             "apiVersion": "2019-07-01",
             "dependsOn": [
-                "[resourceId('Microsoft.Network/publicIPAddresses', 'portal-pip')]",
                 "[resourceId('Microsoft.Network/publicIPAddresses', 'rp-pip')]"
             ]
         },
diff --git a/pkg/api/openshiftcluster.go b/pkg/api/openshiftcluster.go
index 288064133..6327a497a 100644
--- a/pkg/api/openshiftcluster.go
+++ b/pkg/api/openshiftcluster.go
@@ -71,8 +71,6 @@ type OpenShiftClusterProperties struct {
 	FailedProvisioningState ProvisioningState   `json:"failedProvisioningState,omitempty"`
 	LastAdminUpdateError    string              `json:"lastAdminUpdateError,omitempty"`
 
-	CreatedAt time.Time `json:"createdAt,omitempty"`
-
 	// CreatedBy is the RP version (Git commit hash) that created this cluster
 	CreatedBy string `json:"createdBy,omitempty"`
 
diff --git a/pkg/api/validate/cache.go b/pkg/api/validate/cache.go
new file mode 100644
index 000000000..afa904ae5
--- /dev/null
+++ b/pkg/api/validate/cache.go
@@ -0,0 +1,48 @@
+package validate
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"context"
+
+	mgmtnetwork "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-07-01/network"
+)
+
+type virtualNetworksGetClient interface {
+	Get(context.Context, string, string, string) (mgmtnetwork.VirtualNetwork, error)
+}
+
+type virtualNetworksCacheKey struct {
+	resourceGroupName  string
+	virtualNetworkName string
+	expand             string
+}
+
+type virtualNetworksCache struct {
+	c virtualNetworksGetClient
+	m map[virtualNetworksCacheKey]mgmtnetwork.VirtualNetwork
+}
+
+func (vnc *virtualNetworksCache) Get(ctx context.Context, resourceGroupName string, virtualNetworkName string, expand string) (mgmtnetwork.VirtualNetwork, error) {
+	if _, ok := vnc.m[virtualNetworksCacheKey{resourceGroupName, virtualNetworkName, expand}]; !ok {
+		vnet, err := vnc.c.Get(ctx, resourceGroupName, virtualNetworkName, expand)
+		if err != nil {
+			return vnet, err
+		}
+
+		vnc.m[virtualNetworksCacheKey{resourceGroupName, virtualNetworkName, expand}] = vnet
+	}
+
+	return vnc.m[virtualNetworksCacheKey{resourceGroupName, virtualNetworkName, expand}], nil
+}
+
+// newVirtualNetworksCache returns a new virtualNetworksCache.  It knows nothing
+// about updates and is not thread-safe, but it does retry in the face of
+// errors.
+func newVirtualNetworksCache(c virtualNetworksGetClient) virtualNetworksGetClient {
+	return &virtualNetworksCache{
+		c: c,
+		m: map[virtualNetworksCacheKey]mgmtnetwork.VirtualNetwork{},
+	}
+}
diff --git a/pkg/api/validate/dynamic.go b/pkg/api/validate/dynamic.go
new file mode 100644
index 000000000..2a4965317
--- /dev/null
+++ b/pkg/api/validate/dynamic.go
@@ -0,0 +1,228 @@
+package validate
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"sort"
+	"strings"
+	"time"
+
+	mgmtnetwork "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-07-01/network"
+	"github.com/Azure/go-autorest/autorest"
+	"github.com/Azure/go-autorest/autorest/azure"
+	"github.com/sirupsen/logrus"
+	"k8s.io/apimachinery/pkg/util/wait"
+
+	"github.com/Azure/ARO-RP/pkg/api"
+	"github.com/Azure/ARO-RP/pkg/env"
+	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/authorization"
+	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/network"
+	utilpermissions "github.com/Azure/ARO-RP/pkg/util/permissions"
+	"github.com/Azure/ARO-RP/pkg/util/refreshable"
+	"github.com/Azure/ARO-RP/pkg/util/steps"
+	"github.com/Azure/ARO-RP/pkg/util/subnet"
+)
+
+// SlimDynamic validate in the operator context.
+type SlimDynamic interface {
+	ValidateVnetPermissions(ctx context.Context) error
+	ValidateRouteTablesPermissions(ctx context.Context) error
+	ValidateVnetDns(ctx context.Context) error
+	// etc
+	// does Quota code go in here too?
+}
+
+type dynamic struct {
+	log             *logrus.Entry
+	vnetr           *azure.Resource
+	masterSubnetID  string
+	workerSubnetIDs []string
+
+	code string
+	typ  string
+
+	permissions     authorization.PermissionsClient
+	virtualNetworks virtualNetworksGetClient
+}
+
+func NewValidator(log *logrus.Entry, env env.Interface, masterSubnetID string, workerSubnetIDs []string, subscriptionID string, authorizer refreshable.Authorizer, code string, typ string) (*dynamic, error) {
+	vnetID, _, err := subnet.Split(masterSubnetID)
+	if err != nil {
+		return nil, err
+	}
+
+	vnetr, err := azure.ParseResourceID(vnetID)
+	if err != nil {
+		return nil, err
+	}
+
+	return &dynamic{
+		log:             log,
+		vnetr:           &vnetr,
+		masterSubnetID:  masterSubnetID,
+		workerSubnetIDs: workerSubnetIDs,
+
+		code: code,
+		typ:  typ,
+
+		permissions:     authorization.NewPermissionsClient(env.Environment(), subscriptionID, authorizer),
+		virtualNetworks: newVirtualNetworksCache(network.NewVirtualNetworksClient(env.Environment(), subscriptionID, authorizer)),
+	}, nil
+}
+
+func (dv *dynamic) ValidateVnetPermissions(ctx context.Context) error {
+	dv.log.Printf("ValidateVnetPermissions (%s)", dv.typ)
+
+	err := dv.validateActions(ctx, dv.vnetr, []string{
+		"Microsoft.Network/virtualNetworks/join/action",
+		"Microsoft.Network/virtualNetworks/read",
+		"Microsoft.Network/virtualNetworks/write",
+		"Microsoft.Network/virtualNetworks/subnets/join/action",
+		"Microsoft.Network/virtualNetworks/subnets/read",
+		"Microsoft.Network/virtualNetworks/subnets/write",
+	})
+
+	if err == wait.ErrWaitTimeout {
+		return api.NewCloudError(http.StatusBadRequest, dv.code, "", "The %s does not have Network Contributor permission on vnet '%s'.", dv.typ, dv.vnetr)
+	}
+	if detailedErr, ok := err.(autorest.DetailedError); ok &&
+		detailedErr.StatusCode == http.StatusNotFound {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidLinkedVNet, "", "The vnet '%s' could not be found.", dv.vnetr)
+	}
+	return err
+}
+
+func (dv *dynamic) ValidateRouteTablesPermissions(ctx context.Context) error {
+	vnet, err := dv.virtualNetworks.Get(ctx, dv.vnetr.ResourceGroup, dv.vnetr.ResourceName, "")
+	if err != nil {
+		return err
+	}
+
+	m := map[string]string{}
+
+	rtID, err := getRouteTableID(&vnet, "properties.masterProfile.subnetId", dv.masterSubnetID)
+	if err != nil {
+		return err
+	}
+
+	if rtID != "" {
+		m[strings.ToLower(rtID)] = "properties.masterProfile.subnetId"
+	}
+
+	for i, s := range dv.workerSubnetIDs {
+		path := fmt.Sprintf("properties.workerProfiles[%d].subnetId", i)
+
+		rtID, err := getRouteTableID(&vnet, path, s)
+		if err != nil {
+			return err
+		}
+
+		if _, ok := m[strings.ToLower(rtID)]; ok || rtID == "" {
+			continue
+		}
+		m[strings.ToLower(rtID)] = path
+	}
+
+	rts := make([]string, 0, len(m))
+	for rt := range m {
+		rts = append(rts, rt)
+	}
+
+	sort.Slice(rts, func(i, j int) bool { return strings.Compare(m[rts[i]], m[rts[j]]) < 0 })
+
+	for _, rt := range rts {
+		err := dv.validateRouteTablePermissions(ctx, rt, m[rt])
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (dv *dynamic) validateRouteTablePermissions(ctx context.Context, rtID string, path string) error {
+	dv.log.Printf("validateRouteTablePermissions(%s, %s)", dv.typ, path)
+
+	rtr, err := azure.ParseResourceID(rtID)
+	if err != nil {
+		return err
+	}
+
+	err = dv.validateActions(ctx, &rtr, []string{
+		"Microsoft.Network/routeTables/join/action",
+		"Microsoft.Network/routeTables/read",
+		"Microsoft.Network/routeTables/write",
+	})
+	if err == wait.ErrWaitTimeout {
+		return api.NewCloudError(http.StatusBadRequest, dv.code, "", "The %s does not have Network Contributor permission on route table '%s'.", dv.typ, rtID)
+	}
+	if detailedErr, ok := err.(autorest.DetailedError); ok &&
+		detailedErr.StatusCode == http.StatusNotFound {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidLinkedRouteTable, "", "The route table '%s' could not be found.", rtID)
+	}
+	return err
+}
+
+func (dv *dynamic) ValidateVnetDNS(ctx context.Context) error {
+	dv.log.Print("validateVnetDns")
+
+	vnet, err := dv.virtualNetworks.Get(ctx, dv.vnetr.ResourceGroup, dv.vnetr.ResourceName, "")
+	if err != nil {
+		return err
+	}
+
+	if vnet.DhcpOptions != nil &&
+		vnet.DhcpOptions.DNSServers != nil &&
+		len(*vnet.DhcpOptions.DNSServers) > 0 {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidLinkedVNet, "", "The provided vnet '%s' is invalid: custom DNS servers are not supported.", *vnet.ID)
+	}
+
+	return nil
+}
+
+func (dv *dynamic) validateActions(ctx context.Context, r *azure.Resource, actions []string) error {
+	timeoutCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
+	defer cancel()
+
+	return wait.PollImmediateUntil(10*time.Second, func() (bool, error) {
+		permissions, err := dv.permissions.ListForResource(ctx, r.ResourceGroup, r.Provider, "", r.ResourceType, r.ResourceName)
+
+		if detailedErr, ok := err.(autorest.DetailedError); ok &&
+			detailedErr.StatusCode == http.StatusForbidden {
+			return false, steps.ErrWantRefresh
+		}
+		if err != nil {
+			return false, err
+		}
+
+		for _, action := range actions {
+			ok, err := utilpermissions.CanDoAction(permissions, action)
+			if !ok || err != nil {
+				// TODO(jminter): I don't understand if there are genuinely
+				// cases where CanDoAction can return false then true shortly
+				// after. I'm a little skeptical; if it can't happen we can
+				// simplify this code.  We should add a metric on this.
+				return false, err
+			}
+		}
+
+		return true, nil
+	}, timeoutCtx.Done())
+}
+
+func getRouteTableID(vnet *mgmtnetwork.VirtualNetwork, path, subnetID string) (string, error) {
+	s := findSubnet(vnet, subnetID)
+	if s == nil {
+		return "", api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidLinkedVNet, path, "The subnet '%s' could not be found.", subnetID)
+	}
+
+	if s.RouteTable == nil {
+		return "", nil
+	}
+
+	return *s.RouteTable.ID, nil
+}
diff --git a/pkg/api/validate/openshiftcluster_validatedynamic.go b/pkg/api/validate/openshiftcluster_validatedynamic.go
index 559062d3a..13961adbb 100644
--- a/pkg/api/validate/openshiftcluster_validatedynamic.go
+++ b/pkg/api/validate/openshiftcluster_validatedynamic.go
@@ -8,185 +8,127 @@ import (
 	"fmt"
 	"net"
 	"net/http"
-	"strconv"
 	"strings"
-	"time"
 
 	mgmtnetwork "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-07-01/network"
 	mgmtfeatures "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2019-07-01/features"
-	"github.com/Azure/go-autorest/autorest"
-	"github.com/Azure/go-autorest/autorest/azure"
 	"github.com/apparentlymart/go-cidr/cidr"
 	jwt "github.com/form3tech-oss/jwt-go"
 	"github.com/sirupsen/logrus"
-	"k8s.io/apimachinery/pkg/util/wait"
 
 	"github.com/Azure/ARO-RP/pkg/api"
 	"github.com/Azure/ARO-RP/pkg/env"
-	arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
 	"github.com/Azure/ARO-RP/pkg/util/aad"
-	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/authorization"
 	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/features"
-	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/network"
-	utilpermissions "github.com/Azure/ARO-RP/pkg/util/permissions"
 	"github.com/Azure/ARO-RP/pkg/util/refreshable"
 	"github.com/Azure/ARO-RP/pkg/util/subnet"
 )
 
-// OpenShiftClusterFullDynamicValidator is the dynamic validator interface
-// for the RP execution contexts
-type OpenShiftClusterFullDynamicValidator interface {
+// OpenShiftClusterDynamicValidator is the dynamic validator interface
+type OpenShiftClusterDynamicValidator interface {
 	Dynamic(context.Context) error
 }
 
-// OpenShiftClusterSlimDynamicValidator is the dynamic validator interface
-// for the cluster execution context
-type OpenShiftClusterSlimDynamicValidator interface {
-	Dynamic(context.Context) error
-}
-
-// NewOpenShiftClusterFullDynamicValidator creates a new OpenShiftClusterFullDynamicValidator
-func NewOpenShiftClusterFullDynamicValidator(log *logrus.Entry, env env.Interface, oc *api.OpenShiftCluster, subscriptionDoc *api.SubscriptionDocument, fpAuthorizer refreshable.Authorizer) OpenShiftClusterFullDynamicValidator {
-	return &openShiftClusterFullDynamicValidator{
+// NewOpenShiftClusterDynamicValidator creates a new OpenShiftClusterDynamicValidator
+func NewOpenShiftClusterDynamicValidator(log *logrus.Entry, env env.Interface, oc *api.OpenShiftCluster, subscriptionDoc *api.SubscriptionDocument, fpAuthorizer refreshable.Authorizer) OpenShiftClusterDynamicValidator {
+	return &openShiftClusterDynamicValidator{
+		log: log,
 		env: env,
 
 		oc:              oc,
 		subscriptionDoc: subscriptionDoc,
 		fpAuthorizer:    fpAuthorizer,
-
-		log: log,
+		providers:       features.NewProvidersClient(env.Environment(), subscriptionDoc.ID, fpAuthorizer),
 	}
 }
 
-// NewOpenShiftClusterSlimDynamicValidator creates a new OpenShiftClusterSlimDynamicValidator
-func NewOpenShiftClusterSlimDynamicValidator(log *logrus.Entry, clientID string, clientSecret api.SecureString, subscriptionID string, tenantID string, clusterSpec *arov1alpha1.ClusterSpec) OpenShiftClusterSlimDynamicValidator {
-	spp := &api.ServicePrincipalProfile{
-		ClientID:     clientID,
-		ClientSecret: clientSecret,
-	}
-	return &openShiftClusterSlimDynamicValidator{
-		log:            log,
-		spp:            spp,
-		tenantID:       tenantID,
-		subscriptionID: subscriptionID,
-		clusterSpec:    clusterSpec,
-	}
+type azureClaim struct {
+	Roles []string `json:"roles,omitempty"`
+}
+
+func (*azureClaim) Valid() error {
+	return fmt.Errorf("unimplemented")
 }
 
-// openShiftClusterFullDynamicValidator is dynamic validator used inside RP context
-// and can operate within cluster and firstParty service principals.
-// It includes openShiftClusterSlimDynamicValidatora
-type openShiftClusterFullDynamicValidator struct {
+type openShiftClusterDynamicValidator struct {
+	log *logrus.Entry
 	env env.Interface
 
 	oc              *api.OpenShiftCluster
 	subscriptionDoc *api.SubscriptionDocument
 	fpAuthorizer    refreshable.Authorizer
-
-	log *logrus.Entry
+	providers       features.ProvidersClient
 }
 
-// openShiftClusterSlimDynamicValidator is dynamic validator used inside clusters
-// context and can operate ONLY with cluster service principal scope
-type openShiftClusterSlimDynamicValidator struct {
-	log *logrus.Entry
-
-	spp             *api.ServicePrincipalProfile
-	clusterSpec     *arov1alpha1.ClusterSpec
-	tenantID        string
-	subscriptionID  string
-	environmentName string
-}
-
-// Dynamic validates an OpenShift cluster in the context of cluster
-func (dv *openShiftClusterSlimDynamicValidator) Dynamic(ctx context.Context) error {
-	azureEnv, err := azure.EnvironmentFromName(dv.environmentName)
-	if err != nil {
-		return err
-	}
-	spAuthorizer, err := validateServicePrincipalProfile(ctx, dv.log, dv.spp, dv.tenantID, azureEnv.ResourceManagerEndpoint)
-	if err != nil {
-		return err
-	}
-
-	spPermissions := authorization.NewPermissionsClient(&azureEnv, dv.subscriptionID, spAuthorizer)
+// Dynamic validates an OpenShift cluster
+func (dv *openShiftClusterDynamicValidator) Dynamic(ctx context.Context) error {
+	// Get all subnets
+	mSubnetID := dv.oc.Properties.MasterProfile.SubnetID
+	wSubnetIDs := []string{}
 
-	vnetr, err := azure.ParseResourceID(dv.clusterSpec.VNetID)
-	if err != nil {
-		return err
+	for _, s := range dv.oc.Properties.WorkerProfiles {
+		wSubnetIDs = append(wSubnetIDs, s.SubnetID)
 	}
 
-	err = validateVnetPermissions(ctx, dv.log, spAuthorizer, spPermissions, dv.clusterSpec.VNetID, &vnetr, api.CloudErrorCodeInvalidServicePrincipalPermissions, "provided service principal")
+	// FP validation
+	fpDynamic, err := NewValidator(dv.log, dv.env, mSubnetID, wSubnetIDs, dv.subscriptionDoc.ID, dv.fpAuthorizer, api.CloudErrorCodeInvalidResourceProviderPermissions, "resource provider")
 	if err != nil {
 		return err
 	}
 
-	return nil
-}
-
-// Dynamic validates an OpenShift cluster
-func (dv *openShiftClusterFullDynamicValidator) Dynamic(ctx context.Context) error {
-	// TODO: Dynamic() should work on an enriched oc (in update), and it
-	// currently doesn't.  One sticking point is handling subnet overlap
-	// calculations.
-
-	r, err := azure.ParseResourceID(dv.oc.ID)
+	err = fpDynamic.ValidateVnetPermissions(ctx)
 	if err != nil {
 		return err
 	}
 
-	fpPermissions := authorization.NewPermissionsClient(dv.env.Environment(), dv.subscriptionDoc.ID, dv.fpAuthorizer)
-	spAuthorizer, err := validateServicePrincipalProfile(ctx, dv.log, &dv.oc.Properties.ServicePrincipalProfile, dv.subscriptionDoc.Subscription.Properties.TenantID, dv.env.Environment().ResourceManagerEndpoint)
+	err = fpDynamic.ValidateRouteTablesPermissions(ctx)
 	if err != nil {
 		return err
 	}
 
-	spPermissions := authorization.NewPermissionsClient(dv.env.Environment(), r.SubscriptionID, spAuthorizer)
-	spProviders := features.NewProvidersClient(dv.env.Environment(), r.SubscriptionID, spAuthorizer)
-	spVirtualNetworks := network.NewVirtualNetworksClient(dv.env.Environment(), r.SubscriptionID, spAuthorizer)
-
-	vnetID, _, err := subnet.Split(dv.oc.Properties.MasterProfile.SubnetID)
+	// SP validation
+	spAuthorizer, err := validateServicePrincipalProfile(ctx, dv.log, dv.env, dv.oc, dv.subscriptionDoc)
 	if err != nil {
 		return err
 	}
 
-	vnetr, err := azure.ParseResourceID(vnetID)
+	spDynamic, err := NewValidator(dv.log, dv.env, mSubnetID, wSubnetIDs, dv.subscriptionDoc.ID, spAuthorizer, api.CloudErrorCodeInvalidServicePrincipalPermissions, "provided service principal")
 	if err != nil {
 		return err
 	}
 
-	err = validateVnetPermissions(ctx, dv.log, spAuthorizer, spPermissions, vnetID, &vnetr, api.CloudErrorCodeInvalidServicePrincipalPermissions, "provided service principal")
+	err = spDynamic.ValidateVnetPermissions(ctx)
 	if err != nil {
 		return err
 	}
 
-	err = validateVnetPermissions(ctx, dv.log, dv.fpAuthorizer, fpPermissions, vnetID, &vnetr, api.CloudErrorCodeInvalidResourceProviderPermissions, "resource provider")
+	err = spDynamic.ValidateRouteTablesPermissions(ctx)
 	if err != nil {
 		return err
 	}
 
-	// Get after validating permissions
-	vnet, err := spVirtualNetworks.Get(ctx, vnetr.ResourceGroup, vnetr.ResourceName, "")
+	// Additional checks - use any dynamic because they both have the correct permissions
+	err = spDynamic.ValidateVnetDNS(ctx)
 	if err != nil {
 		return err
 	}
 
-	err = validateRouteTablePermissions(ctx, dv.log, dv.oc, spAuthorizer, spPermissions, &vnet, api.CloudErrorCodeInvalidServicePrincipalPermissions, "provided service principal")
+	vnet, err := spDynamic.virtualNetworks.Get(ctx, spDynamic.vnetr.ResourceGroup, spDynamic.vnetr.ResourceName, "")
 	if err != nil {
 		return err
 	}
 
-	err = validateRouteTablePermissions(ctx, dv.log, dv.oc, dv.fpAuthorizer, fpPermissions, &vnet, api.CloudErrorCodeInvalidResourceProviderPermissions, "resource provider")
+	err = dv.validateCIDRRanges(ctx, &vnet)
 	if err != nil {
 		return err
 	}
 
-	err = validateVnet(ctx, dv.log, dv.oc, &vnet)
+	err = dv.validateVnetLocation(ctx, &vnet)
 	if err != nil {
 		return err
 	}
 
-	err = validateProviders(ctx, dv.log, spProviders)
+	err = dv.validateProviders(ctx)
 	if err != nil {
 		return err
 	}
@@ -194,129 +136,77 @@ func (dv *openShiftClusterFullDynamicValidator) Dynamic(ctx context.Context) err
 	return nil
 }
 
-type azureClaim struct {
-	Roles []string `json:"roles,omitempty"`
-}
-
-func (*azureClaim) Valid() error {
-	return fmt.Errorf("unimplemented")
-}
-
-func validateServicePrincipalProfile(ctx context.Context, log *logrus.Entry, spp *api.ServicePrincipalProfile, tenantID, resource string) (refreshable.Authorizer, error) {
-	log.Print("validateServicePrincipalProfile")
-
-	token, err := aad.GetToken(ctx, log, spp, tenantID, resource)
-	if err != nil {
-		return nil, err
-	}
-
-	p := &jwt.Parser{}
-	c := &azureClaim{}
-	_, _, err = p.ParseUnverified(token.OAuthToken(), c)
-	if err != nil {
-		return nil, err
-	}
+func (dv *openShiftClusterDynamicValidator) validateVnetLocation(ctx context.Context, vnet *mgmtnetwork.VirtualNetwork) error {
+	dv.log.Print("validateVnetLocation")
 
-	for _, role := range c.Roles {
-		if role == "Application.ReadWrite.OwnedBy" {
-			return nil, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidServicePrincipalCredentials, "properties.servicePrincipalProfile", "The provided service principal must not have the Application.ReadWrite.OwnedBy permission.")
-		}
+	if !strings.EqualFold(*vnet.Location, dv.oc.Location) {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidLinkedVNet, "", "The vnet location '%s' must match the cluster location '%s'.", *vnet.Location, dv.oc.Location)
 	}
 
-	return refreshable.NewAuthorizer(token), nil
+	return nil
 }
 
-func validateVnetPermissions(ctx context.Context, log *logrus.Entry, authorizer refreshable.Authorizer, client authorization.PermissionsClient, vnetID string, vnetr *azure.Resource, code, typ string) error {
-	log.Printf("validateVnetPermissions (%s)", typ)
-
-	err := validateActions(ctx, log, vnetr, []string{
-		"Microsoft.Network/virtualNetworks/join/action",
-		"Microsoft.Network/virtualNetworks/read",
-		"Microsoft.Network/virtualNetworks/write",
-		"Microsoft.Network/virtualNetworks/subnets/join/action",
-		"Microsoft.Network/virtualNetworks/subnets/read",
-		"Microsoft.Network/virtualNetworks/subnets/write",
-	}, authorizer, client)
-	if err == wait.ErrWaitTimeout {
-		return api.NewCloudError(http.StatusBadRequest, code, "", "The %s does not have Network Contributor permission on vnet '%s'.", typ, vnetID)
-	}
-	if detailedErr, ok := err.(autorest.DetailedError); ok &&
-		detailedErr.StatusCode == http.StatusNotFound {
-		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidLinkedVNet, "", "The vnet '%s' could not be found.", vnetID)
-	}
-	return err
-}
+func (dv *openShiftClusterDynamicValidator) validateCIDRRanges(ctx context.Context, vnet *mgmtnetwork.VirtualNetwork) error {
+	dv.log.Print("validateCIDRRanges")
 
-func validateRouteTablePermissions(ctx context.Context, log *logrus.Entry, oc *api.OpenShiftCluster, authorizer refreshable.Authorizer, client authorization.PermissionsClient, vnet *mgmtnetwork.VirtualNetwork, code, typ string) error {
-	err := validateRouteTablePermissionsSubnet(ctx, log, authorizer, client, vnet, oc.Properties.MasterProfile.SubnetID, "properties.masterProfile.subnetId", code, typ)
-	if err != nil {
-		return err
-	}
+	var subnets []string
+	var CIDRArray []*net.IPNet
 
-	for i, s := range oc.Properties.WorkerProfiles {
-		err := validateRouteTablePermissionsSubnet(ctx, log, authorizer, client, vnet, s.SubnetID, "properties.workerProfiles["+strconv.Itoa(i)+"].subnetId", code, typ)
-		if err != nil {
-			return err
+	// unique names of subnets from all node pools
+	for i, subnet := range dv.oc.Properties.WorkerProfiles {
+		exists := false
+		for _, s := range subnets {
+			if strings.EqualFold(strings.ToLower(subnet.SubnetID), strings.ToLower(s)) {
+				exists = true
+				break
+			}
 		}
-	}
-	return nil
-}
 
-func validateRouteTablePermissionsSubnet(ctx context.Context, log *logrus.Entry, authorizer refreshable.Authorizer, client authorization.PermissionsClient, vnet *mgmtnetwork.VirtualNetwork, subnetID, path, code, typ string) error {
-	log.Printf("validateRouteTablePermissionsSubnet(%s, %s)", typ, path)
+		if !exists {
+			subnets = append(subnets, subnet.SubnetID)
+			path := fmt.Sprintf("properties.workerProfiles[%d].subnetId", i)
+			c, err := dv.validateSubnet(ctx, vnet, path, subnet.SubnetID)
+			if err != nil {
+				return err
+			}
 
-	var s *mgmtnetwork.Subnet
-	for _, ss := range *vnet.Subnets {
-		if strings.EqualFold(*ss.ID, subnetID) {
-			s = &ss
-			break
+			CIDRArray = append(CIDRArray, c)
 		}
 	}
-	if s == nil {
-		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidLinkedVNet, path, "The subnet '%s' could not be found.", subnetID)
-	}
 
-	if s.RouteTable == nil {
-		return nil
+	masterCIDR, err := dv.validateSubnet(ctx, vnet, "properties.MasterProfile.subnetId", dv.oc.Properties.MasterProfile.SubnetID)
+	if err != nil {
+		return err
 	}
-
-	rtr, err := azure.ParseResourceID(*s.RouteTable.ID)
+	_, podCIDR, err := net.ParseCIDR(dv.oc.Properties.NetworkProfile.PodCIDR)
 	if err != nil {
 		return err
 	}
 
-	err = validateActions(ctx, log, &rtr, []string{
-		"Microsoft.Network/routeTables/join/action",
-		"Microsoft.Network/routeTables/read",
-		"Microsoft.Network/routeTables/write",
-	}, authorizer, client)
-	if err == wait.ErrWaitTimeout {
-		return api.NewCloudError(http.StatusBadRequest, code, "", "The %s does not have Network Contributor permission on route table '%s'.", typ, *s.RouteTable.ID)
+	_, serviceCIDR, err := net.ParseCIDR(dv.oc.Properties.NetworkProfile.ServiceCIDR)
+	if err != nil {
+		return err
 	}
-	if detailedErr, ok := err.(autorest.DetailedError); ok &&
-		detailedErr.StatusCode == http.StatusNotFound {
-		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidLinkedRouteTable, "", "The route table '%s' could not be found.", *s.RouteTable.ID)
+
+	CIDRArray = append(CIDRArray, masterCIDR, podCIDR, serviceCIDR)
+
+	err = cidr.VerifyNoOverlap(CIDRArray, &net.IPNet{IP: net.IPv4zero, Mask: net.IPMask(net.IPv4zero)})
+	if err != nil {
+		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidLinkedVNet, "", "The provided CIDRs must not overlap: '%s'.", err)
 	}
-	return err
+
+	return nil
 }
 
-func validateSubnet(ctx context.Context, log *logrus.Entry, oc *api.OpenShiftCluster, vnet *mgmtnetwork.VirtualNetwork, path, subnetID string) (*net.IPNet, error) {
-	log.Printf("validateSubnet (%s)", path)
+func (dv *openShiftClusterDynamicValidator) validateSubnet(ctx context.Context, vnet *mgmtnetwork.VirtualNetwork, path, subnetID string) (*net.IPNet, error) {
+	dv.log.Printf("validateSubnet (%s)", path)
 
-	var s *mgmtnetwork.Subnet
-	if vnet.Subnets != nil {
-		for _, ss := range *vnet.Subnets {
-			if strings.EqualFold(*ss.ID, subnetID) {
-				s = &ss
-				break
-			}
-		}
-	}
+	s := findSubnet(vnet, subnetID)
 	if s == nil {
 		return nil, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidLinkedVNet, path, "The provided subnet '%s' could not be found.", subnetID)
 	}
 
-	if strings.EqualFold(oc.Properties.MasterProfile.SubnetID, subnetID) {
+	if strings.EqualFold(dv.oc.Properties.MasterProfile.SubnetID, subnetID) {
 		if s.PrivateLinkServiceNetworkPolicies == nil ||
 			!strings.EqualFold(*s.PrivateLinkServiceNetworkPolicies, "Disabled") {
 			return nil, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidLinkedVNet, path, "The provided subnet '%s' is invalid: must have privateLinkServiceNetworkPolicies disabled.", subnetID)
@@ -337,14 +227,14 @@ func validateSubnet(ctx context.Context, log *logrus.Entry, oc *api.OpenShiftClu
 		return nil, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidLinkedVNet, path, "The provided subnet '%s' is invalid: must have Microsoft.ContainerRegistry serviceEndpoint.", subnetID)
 	}
 
-	if oc.Properties.ProvisioningState == api.ProvisioningStateCreating {
+	if dv.oc.Properties.ProvisioningState == api.ProvisioningStateCreating {
 		if s.SubnetPropertiesFormat != nil &&
 			s.SubnetPropertiesFormat.NetworkSecurityGroup != nil {
 			return nil, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidLinkedVNet, path, "The provided subnet '%s' is invalid: must not have a network security group attached.", subnetID)
 		}
 
 	} else {
-		nsgID, err := subnet.NetworkSecurityGroupID(oc, *s.ID)
+		nsgID, err := subnet.NetworkSecurityGroupID(dv.oc, *s.ID)
 		if err != nil {
 			return nil, err
 		}
@@ -360,81 +250,19 @@ func validateSubnet(ctx context.Context, log *logrus.Entry, oc *api.OpenShiftClu
 	if err != nil {
 		return nil, err
 	}
-	{
-		ones, _ := net.Mask.Size()
-		if ones > 27 {
-			return nil, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidLinkedVNet, path, "The provided subnet '%s' is invalid: must be /27 or larger.", subnetID)
-		}
-	}
-
-	return net, nil
-}
 
-// validateVnet checks that the vnet does not have custom dns servers set
-// and the subnets do not overlap with cluster pod/service CIDR blocks
-func validateVnet(ctx context.Context, log *logrus.Entry, oc *api.OpenShiftCluster, vnet *mgmtnetwork.VirtualNetwork) error {
-	log.Print("validateVnet")
-	var err error
-
-	if !strings.EqualFold(*vnet.Location, oc.Location) {
-		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidLinkedVNet, "", "The vnet location '%s' must match the cluster location '%s'.", *vnet.Location, oc.Location)
+	ones, _ := net.Mask.Size()
+	if ones > 27 {
+		return nil, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidLinkedVNet, path, "The provided subnet '%s' is invalid: must be /27 or larger.", subnetID)
 	}
 
-	// unique names of subnets from all node pools
-	var subnets []string
-	var CIDRArray []*net.IPNet
-	for i, subnet := range oc.Properties.WorkerProfiles {
-		exists := false
-		for _, s := range subnets {
-			if strings.EqualFold(strings.ToLower(subnet.SubnetID), strings.ToLower(s)) {
-				exists = true
-				break
-			}
-		}
-		if !exists {
-			subnets = append(subnets, subnet.SubnetID)
-			c, err := validateSubnet(ctx, log, oc, vnet, "properties.workerProfiles["+strconv.Itoa(i)+"].subnetId", subnet.SubnetID)
-			if err != nil {
-				return err
-			}
-			CIDRArray = append(CIDRArray, c)
-		}
-	}
-	masterSubnetCIDR, err := validateSubnet(ctx, log, oc, vnet, "properties.masterProfile.subnetId", oc.Properties.MasterProfile.SubnetID)
-	if err != nil {
-		return err
-	}
-
-	_, podCIDR, err := net.ParseCIDR(oc.Properties.NetworkProfile.PodCIDR)
-	if err != nil {
-		return err
-	}
-
-	_, serviceCIDR, err := net.ParseCIDR(oc.Properties.NetworkProfile.ServiceCIDR)
-	if err != nil {
-		return err
-	}
-
-	CIDRArray = append(CIDRArray, masterSubnetCIDR, podCIDR, serviceCIDR)
-
-	err = cidr.VerifyNoOverlap(CIDRArray, &net.IPNet{IP: net.IPv4zero, Mask: net.IPMask(net.IPv4zero)})
-	if err != nil {
-		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidLinkedVNet, "", "The provided CIDRs must not overlap: '%s'.", err)
-	}
-
-	if vnet.DhcpOptions != nil &&
-		vnet.DhcpOptions.DNSServers != nil &&
-		len(*vnet.DhcpOptions.DNSServers) > 0 {
-		return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidLinkedVNet, "", "The provided vnet '%s' is invalid: custom DNS servers are not supported.", *vnet.ID)
-	}
-
-	return nil
+	return net, nil
 }
 
-func validateProviders(ctx context.Context, log *logrus.Entry, spProviders features.ProvidersClient) error {
-	log.Print("validateProviders")
+func (dv *openShiftClusterDynamicValidator) validateProviders(ctx context.Context) error {
+	dv.log.Print("validateProviders")
 
-	providers, err := spProviders.List(ctx, nil, "")
+	providers, err := dv.providers.List(ctx, nil, "")
 	if err != nil {
 		return err
 	}
@@ -460,28 +288,38 @@ func validateProviders(ctx context.Context, log *logrus.Entry, spProviders featu
 	return nil
 }
 
-func validateActions(ctx context.Context, log *logrus.Entry, r *azure.Resource, actions []string, authorizer refreshable.Authorizer, client authorization.PermissionsClient) error {
-	timeoutCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
-	defer cancel()
+func validateServicePrincipalProfile(ctx context.Context, log *logrus.Entry, env env.Interface, oc *api.OpenShiftCluster, sub *api.SubscriptionDocument) (refreshable.Authorizer, error) {
+	log.Print("validateServicePrincipalProfile")
 
-	return wait.PollImmediateUntil(10*time.Second, func() (bool, error) {
-		permissions, err := client.ListForResource(ctx, r.ResourceGroup, r.Provider, "", r.ResourceType, r.ResourceName)
-		if detailedErr, ok := err.(autorest.DetailedError); ok &&
-			detailedErr.StatusCode == http.StatusForbidden {
-			_, err = authorizer.RefreshWithContext(ctx, log)
-			return false, err
-		}
-		if err != nil {
-			return false, err
+	token, err := aad.GetToken(ctx, log, oc, sub, env.Environment().ResourceManagerEndpoint)
+	if err != nil {
+		return nil, err
+	}
+
+	p := &jwt.Parser{}
+	c := &azureClaim{}
+	_, _, err = p.ParseUnverified(token.OAuthToken(), c)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, role := range c.Roles {
+		if role == "Application.ReadWrite.OwnedBy" {
+			return nil, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidServicePrincipalCredentials, "properties.servicePrincipalProfile", "The provided service principal must not have the Application.ReadWrite.OwnedBy permission.")
 		}
+	}
+
+	return refreshable.NewAuthorizer(token), nil
+}
 
-		for _, action := range actions {
-			ok, err := utilpermissions.CanDoAction(permissions, action)
-			if !ok || err != nil {
-				return false, err
+func findSubnet(vnet *mgmtnetwork.VirtualNetwork, subnetID string) *mgmtnetwork.Subnet {
+	if vnet.Subnets != nil {
+		for _, s := range *vnet.Subnets {
+			if strings.EqualFold(*s.ID, subnetID) {
+				return &s
 			}
 		}
+	}
 
-		return true, nil
-	}, timeoutCtx.Done())
+	return nil
 }
diff --git a/pkg/api/validate/quota.go b/pkg/api/validate/quota.go
index c1fad9519..d90f72cf2 100644
--- a/pkg/api/validate/quota.go
+++ b/pkg/api/validate/quota.go
@@ -101,7 +101,7 @@ func NewAzureQuotaValidator(ctx context.Context, log *logrus.Entry, env env.Inte
 		return nil, err
 	}
 
-	spAuthorizer, err := validateServicePrincipalProfile(ctx, log, &oc.Properties.ServicePrincipalProfile, subscriptionDoc.Subscription.Properties.TenantID, env.Environment().ResourceManagerEndpoint)
+	spAuthorizer, err := validateServicePrincipalProfile(ctx, log, env, oc, subscriptionDoc)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/cluster/createdat.go b/pkg/cluster/createdat.go
deleted file mode 100644
index 826de0076..000000000
--- a/pkg/cluster/createdat.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package cluster
-
-// Copyright (c) Microsoft Corporation.
-// Licensed under the Apache License 2.0.
-
-import (
-	"context"
-
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-
-	"github.com/Azure/ARO-RP/pkg/api"
-)
-
-// populateCreatedAt updates DB document with cluster creation date from the default namespace
-// This is a temporary piece of code to populate the relevant field for older clusters.
-// TODO(mikalai): Remove the function after a round of admin updates
-func (m *manager) populateCreatedAt(ctx context.Context) error {
-	if !m.doc.OpenShiftCluster.Properties.CreatedAt.IsZero() {
-		return nil
-	}
-
-	ns, err := m.kubernetescli.CoreV1().Namespaces().Get(ctx, "default", metav1.GetOptions{})
-	if err != nil {
-		return err
-	}
-
-	m.doc, err = m.db.PatchWithLease(ctx, m.doc.Key, func(doc *api.OpenShiftClusterDocument) error {
-		doc.OpenShiftCluster.Properties.CreatedAt = ns.CreationTimestamp.Time
-		return nil
-	})
-	return err
-}
diff --git a/pkg/cluster/deploystorage.go b/pkg/cluster/deploystorage.go
index 47b10499f..375933e37 100644
--- a/pkg/cluster/deploystorage.go
+++ b/pkg/cluster/deploystorage.go
@@ -47,7 +47,7 @@ func (m *manager) clusterSPObjectID(ctx context.Context) (string, error) {
 	var clusterSPObjectID string
 	spp := &m.doc.OpenShiftCluster.Properties.ServicePrincipalProfile
 
-	token, err := aad.GetToken(ctx, m.log, &m.doc.OpenShiftCluster.Properties.ServicePrincipalProfile, m.subscriptionDoc.Subscription.Properties.TenantID, m.env.Environment().GraphEndpoint)
+	token, err := aad.GetToken(ctx, m.log, m.doc.OpenShiftCluster, m.subscriptionDoc, m.env.Environment().GraphEndpoint)
 	if err != nil {
 		return "", err
 	}
diff --git a/pkg/cluster/fixpullsecret.go b/pkg/cluster/fixpullsecret.go
new file mode 100644
index 000000000..ef9b46a34
--- /dev/null
+++ b/pkg/cluster/fixpullsecret.go
@@ -0,0 +1,40 @@
+package cluster
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"context"
+
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/util/retry"
+
+	"github.com/Azure/ARO-RP/pkg/util/pullsecret"
+)
+
+func (m *manager) fixPullSecret(ctx context.Context) error {
+	// TODO: this function does not currently reapply a pull secret in
+	// development mode.
+
+	return retry.RetryOnConflict(retry.DefaultRetry, func() error {
+		ps, err := m.kubernetescli.CoreV1().Secrets("openshift-config").Get(ctx, "pull-secret", metav1.GetOptions{})
+		if err != nil {
+			return err
+		}
+
+		pullSecret, changed, err := pullsecret.SetRegistryProfiles(string(ps.Data[v1.DockerConfigJsonKey]), m.doc.OpenShiftCluster.Properties.RegistryProfiles...)
+		if err != nil {
+			return err
+		}
+
+		if !changed {
+			return nil
+		}
+
+		ps.Data[v1.DockerConfigJsonKey] = []byte(pullSecret)
+
+		_, err = m.kubernetescli.CoreV1().Secrets("openshift-config").Update(ctx, ps, metav1.UpdateOptions{})
+		return err
+	})
+}
diff --git a/pkg/cluster/install.go b/pkg/cluster/install.go
index 6ec42fc5d..1e529c915 100644
--- a/pkg/cluster/install.go
+++ b/pkg/cluster/install.go
@@ -33,13 +33,12 @@ func (m *manager) AdminUpdate(ctx context.Context) error {
 		steps.Condition(m.apiServersReady, 30*time.Minute),
 		steps.Action(m.ensureBillingRecord), // belt and braces
 		steps.Action(m.fixSSH),
-		steps.Action(m.populateCreatedAt), // TODO(mikalai): Remove after a round of admin updates
+		steps.Action(m.fixPullSecret), // TODO(mj): Remove when operator deployed
 		steps.Action(m.fixSREKubeconfig),
 		steps.Action(m.ensureAROOperator),
 		steps.Condition(m.aroDeploymentReady, 20*time.Minute),
 		steps.Action(m.configureAPIServerCertificate),
 		steps.Action(m.configureIngressCertificate),
-		steps.Action(m.createOrUpdateRouterIP),
 		steps.Action(m.updateProvisionedBy), // Run this last so we capture the resource provider only once the upgrade has been fully performed
 	}
 
@@ -104,8 +103,7 @@ func (m *manager) Install(ctx context.Context) error {
 			steps.Action(m.disableUpdates),
 			steps.Action(m.disableSamples),
 			steps.Action(m.disableOperatorHubSources),
-			steps.Action(m.createOrUpdateRouterIP),
-			steps.Action(m.updateClusterData),
+			steps.Action(m.updateRouterIP),
 			steps.Action(m.configureIngressCertificate),
 			steps.Condition(m.ingressControllerReady, 30*time.Minute),
 			steps.Action(m.finishInstallation),
diff --git a/pkg/cluster/ipaddresses.go b/pkg/cluster/ipaddresses.go
index 0fff09638..fe94b57bc 100644
--- a/pkg/cluster/ipaddresses.go
+++ b/pkg/cluster/ipaddresses.go
@@ -15,7 +15,7 @@ import (
 	"github.com/Azure/ARO-RP/pkg/util/stringutils"
 )
 
-func (m *manager) updateClusterData(ctx context.Context) error {
+func (m *manager) updateRouterIP(ctx context.Context) error {
 	g, err := m.loadGraph(ctx)
 	if err != nil {
 		return err
@@ -24,23 +24,11 @@ func (m *manager) updateClusterData(ctx context.Context) error {
 	installConfig := g.get(&installconfig.InstallConfig{}).(*installconfig.InstallConfig)
 	kubeadminPassword := g.get(&password.KubeadminPassword{}).(*password.KubeadminPassword)
 
-	m.doc, err = m.db.PatchWithLease(ctx, m.doc.Key, func(doc *api.OpenShiftClusterDocument) error {
-		doc.OpenShiftCluster.Properties.APIServerProfile.URL = "https://api." + installConfig.Config.ObjectMeta.Name + "." + installConfig.Config.BaseDomain + ":6443/"
-		doc.OpenShiftCluster.Properties.ConsoleProfile.URL = "https://console-openshift-console.apps." + installConfig.Config.ObjectMeta.Name + "." + installConfig.Config.BaseDomain + "/"
-		doc.OpenShiftCluster.Properties.KubeadminPassword = api.SecureString(kubeadminPassword.Password)
-		return nil
-	})
-	return err
-}
-
-func (m *manager) createOrUpdateRouterIP(ctx context.Context) error {
 	svc, err := m.kubernetescli.CoreV1().Services("openshift-ingress").Get(ctx, "router-default", metav1.GetOptions{})
-	// default ingress must be present in the cluster
 	if err != nil {
 		return err
 	}
 
-	// This must be present always. If not - we have an issue
 	if len(svc.Status.LoadBalancer.Ingress) == 0 {
 		return fmt.Errorf("routerIP not found")
 	}
@@ -53,7 +41,10 @@ func (m *manager) createOrUpdateRouterIP(ctx context.Context) error {
 	}
 
 	m.doc, err = m.db.PatchWithLease(ctx, m.doc.Key, func(doc *api.OpenShiftClusterDocument) error {
+		doc.OpenShiftCluster.Properties.APIServerProfile.URL = "https://api." + installConfig.Config.ObjectMeta.Name + "." + installConfig.Config.BaseDomain + ":6443/"
 		doc.OpenShiftCluster.Properties.IngressProfiles[0].IP = routerIP
+		doc.OpenShiftCluster.Properties.ConsoleProfile.URL = "https://console-openshift-console.apps." + installConfig.Config.ObjectMeta.Name + "." + installConfig.Config.BaseDomain + "/"
+		doc.OpenShiftCluster.Properties.KubeadminPassword = api.SecureString(kubeadminPassword.Password)
 		return nil
 	})
 	return err
diff --git a/pkg/cluster/validate.go b/pkg/cluster/validate.go
index 7b8f6dc11..ce6ba088f 100644
--- a/pkg/cluster/validate.go
+++ b/pkg/cluster/validate.go
@@ -11,7 +11,7 @@ import (
 )
 
 func (m *manager) validateResources(ctx context.Context) error {
-	ocDynamicValidator := validate.NewOpenShiftClusterFullDynamicValidator(
+	ocDynamicValidator := validate.NewOpenShiftClusterDynamicValidator(
 		m.log, m.env, m.doc.OpenShiftCluster, m.subscriptionDoc, m.fpAuthorizer,
 	)
 	return ocDynamicValidator.Dynamic(ctx)
diff --git a/pkg/deploy/bindata.go b/pkg/deploy/bindata.go
index 044b944fe..a4ef67e25 100644
--- a/pkg/deploy/bindata.go
+++ b/pkg/deploy/bindata.go
@@ -371,7 +371,7 @@ func rpProductionSubscriptionJson() (*asset, error) {
 	return a, nil
 }
 
-var _rpProductionJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x7d\x69\x6f\xea\x4a\xb6\xf6\xf7\xfe\x15\x51\xde\x2b\xe5\x9c\xf7\xee\x24\xb6\x81\x9d\xb8\xa5\xfe\x00\x06\x1b\x3b\x60\xf0\x54\x36\xee\xdb\x6a\x79\xc2\x38\x94\x87\xf6\x00\x21\x57\xfd\xdf\xaf\xca\x03\xf3\x14\x92\xec\xb3\x77\x9f\xb0\xa5\x2d\x82\xab\x56\x55\xad\x5a\xeb\x59\x4f\x8d\xfe\xdf\xbf\x5c\x5d\x5d\x5d\x5d\xff\x57\x62\x4d\x1c\xdf\xb8\xfe\xeb\xd5\xf5\x24\x4d\xa3\xe4\xaf\xf7\xf7\xc5\x2f\x77\xbe\x11\x18\xae\xe3\x3b\x41\x7a\x67\xbc\x66\xb1\x73\x67\x85\x7e\xf9\x2c\xb9\x27\x30\xbc\x71\x8b\xe1\xb7\x18\x7e\x6f\x3b\x11\x0c\x17\x28\x9d\xec\xf8\x11\x34\x52\xe7\xee\x39\x09\x83\xff\x77\xfd\xad\x28\xc1\x0a\x83\xd4\x09\x52\xe0\xc4\x89\x17\x06\xa8\x20\xfc\x0e\x43\xff\xaa\x04\x91\x11\x1b\xbe\x93\x3a\x71\x72\xfd\xd7\xab\xa2\x5a\xf9\xef\x86\x15\x8b\x4e\x12\x66\xb1\xe5\xb0\xf6\xc6\xa3\xfc\x71\xba\x88\x1c\x24\x2d\x49\x63\x2f\x70\xaf\x97\x0f\xff\xfd\x6d\x4d\x84\xed\x7b\x41\x33\xf2\x28\xa3\x95\x05\x36\x74\xde\x29\x05\x7a\x4e\x90\x52\x4e\x9c\x52\xa1\xef\x87\x01\x6f\xf8\x17\x4a\xb4\x60\x96\xa4\x4e\x3c\x34\x62\x27\x48\xdb\xa1\x6f\x78\xef\x10\x66\x1b\xa9\x61\x1a\x89\xd3\xb4\xac\x30\x0b\xd2\xcb\x05\x39\x2f\x69\x6c\x50\x61\xe2\x87\x49\xbb\xc5\x0e\x93\x93\x52\xbe\x6d\x3e\xb5\x9d\xb1\x91\xc1\x14\x18\x30\xcb\x53\xed\x2f\x65\x1c\x49\x4e\x3c\xf3\x2c\x67\x18\x7b\x81\xe5\x45\x06\xbc\xb4\x77\xc7\x19\x84\xed\xdc\xfa\x0e\xe7\x37\xc3\x10\x9e\xa8\xe7\xd8\x80\x89\xb3\xb7\x80\xa9\xb3\x98\xa1\x94\xc3\xd8\x19\x7b\x2f\x97\x55\xd2\xb7\x7d\x3a\xce\x5d\xc0\x56\x62\x78\xa9\x8c\xc4\xa6\xc2\x60\xec\xb9\x2b\x2f\xba\x50\x4c\x27\x98\x79\x71\x18\x20\x77\xbd\x4c\x48\x14\xc6\xa9\x01\x9b\x96\xe5\x24\x09\x13\x87\x59\xc4\xda\xa7\x0d\xe5\x88\xa4\xc2\xa9\x2e\x35\x81\x42\x46\x07\x3a\x33\x23\x75\xec\xf7\xd5\x27\x8e\x58\xdf\x70\x2f\xf4\x9d\x38\xea\x87\xf6\xe9\xbc\x17\x79\x4c\x1c\x7d\x0c\x50\xc4\x1f\xe6\x79\x49\x32\x19\x66\x26\xf4\xac\x27\xe7\x88\xef\x1d\x95\x90\x86\xb1\xe1\x56\x98\x55\xb4\xec\x42\x49\x99\x99\x58\xb1\x17\xa5\x5e\x18\x54\xf1\x22\x37\x84\xcb\x15\x35\xf3\x25\xef\xf5\xdd\x9d\x29\xa5\x46\x60\x1b\xb1\xfd\xcf\x36\x91\xfc\x73\x56\x3b\x54\x54\x92\xbc\xb1\xa2\x7f\x59\x93\x71\x1d\x97\x2d\x46\x36\xff\xf7\x65\x9a\x2d\x51\xc9\x34\xdb\x91\x9f\x3f\x08\x8a\x92\x97\x55\xbd\xde\x48\xf2\xef\xad\x06\x46\x71\x18\x39\x71\xea\x39\xbb\x0e\x56\x3c\xcf\x4d\x82\x1d\x36\x21\x0c\x2d\x03\xf5\x47\xdf\x49\x27\xa1\x5d\x96\x90\x7a\xd6\x71\xf9\x55\x6d\xe2\xe8\x36\xf2\xa2\x6d\xf5\x56\xfa\xe8\x7b\x56\x1c\x26\xe1\x38\xbd\xe3\x9d\x74\x1e\xc6\xd3\xfb\x65\xb9\xb6\x1d\x3b\x49\xe2\x24\xdb\x59\xab\xea\xa0\xec\x7f\x8f\xd7\x6d\xe4\xb7\xdf\xef\xaa\x87\xff\xd8\xce\x65\x85\x81\xed\x2d\xb3\xad\x58\xca\x6f\x37\xab\xc0\x73\xf3\xfb\x4e\x36\x23\xf2\xd6\xb8\x0e\x81\xe1\xe4\x2d\xf6\x70\x8b\xe1\x7b\xfb\xff\xd7\xee\xa8\x02\x7d\xbf\x3a\xeb\x53\x3b\x6b\x5c\xb2\x07\x76\x58\x50\x80\x2c\xce\x35\xb0\xe9\xf0\xeb\x9f\x5d\x19\xe7\x96\xb5\x99\x76\xb3\xa3\x4e\x66\xc8\x33\x79\xf6\x46\xb7\xb1\xf6\x6f\x37\x67\x98\xc0\xcd\xb7\xab\x9b\xc2\xe9\x77\xbb\x68\x6f\x31\xa9\xe1\xa2\x0a\x05\x19\x84\x47\x13\xff\xfb\xe0\xd3\x7f\x1f\x2e\x65\x1d\x87\x2a\xe5\x5f\xef\x4d\x7d\x40\xc8\xaf\xd9\x01\x2b\x67\xfe\xa9\x3a\xa1\xac\xd6\x89\x8e\xd8\xf9\xf5\x1f\xbb\xa2\xaf\x4d\xc3\x9a\x3a\x81\x5d\xb6\x7a\x18\x86\xf0\x22\x27\x5a\x33\x8f\x52\xe2\x7b\x2a\x05\x43\xc3\x6e\x19\xd0\x08\x2c\x2f\x70\xc5\x0c\x3a\x9f\xee\xd8\x07\x00\xe5\x03\xed\x6b\xd5\x26\x27\x4e\xee\x0f\x01\x58\xe9\xf5\xd0\x2c\xbf\x54\xe9\x90\xf9\x1d\x37\xa8\xe3\xc6\xb9\xa7\x9f\x3f\xad\x6d\x7b\x4c\x6a\xbb\x59\x65\x92\x77\xb7\x2a\x8a\x43\x73\x97\x26\x7e\x54\x43\x72\xe9\x3b\x75\xcf\x7f\xfd\x88\x9a\xa7\xa1\x95\xf7\xc2\xb5\x6c\x6d\x73\x85\x9d\xe4\xa8\x62\x6d\x0f\xd1\x5e\x33\xab\x22\x7a\xbb\xe0\xd6\xa7\xb2\x56\x26\x34\x0c\x63\x34\xbc\xad\xd7\x6b\xe7\x99\xca\x2a\xfd\x7b\x03\x06\x34\xe3\x0c\x3a\x7f\x44\xb8\xf8\x29\xdd\x7a\x0b\xbd\xbf\x5c\x7b\x6f\xed\x7e\xb8\x6b\x57\xb1\x1e\x3d\xb9\xcd\xe7\x7d\xff\x4c\x4e\x5e\xff\x08\x42\xf2\xe5\xe8\x5f\x8e\xfe\xe6\x56\xfd\xb1\x8e\x9e\x24\x93\x5f\xd7\xcd\x09\xe2\x4d\x5e\x4e\x10\x04\xf1\x71\x6e\x8e\x54\xf7\x1e\x92\x5f\xf4\xcc\xa7\x8f\xd8\xd7\xfa\xa6\x8b\x40\xfd\x94\x8a\xa3\x73\x11\x34\xc8\x7c\xd3\x89\x07\xe3\x61\xd5\x8e\x53\x9d\x11\x3b\xff\xca\x9c\x24\x1d\x1a\xe9\x04\xd5\xe6\x7e\xe2\x18\x30\x9d\xbc\xde\xc7\x8e\x61\x2f\x0e\x5b\xe0\x99\x24\xab\x70\xa3\x3f\x62\x48\x7e\xb1\x86\xeb\xbf\x90\x86\x77\xd9\xc1\x1f\xad\xeb\x33\x90\x26\x5a\xf9\xfd\x9b\x55\xfd\x71\xda\x7a\x0b\x4e\x9c\x35\xc5\x9a\x23\xf9\xf9\xb3\xab\x1b\x01\xe1\xe7\x9c\x59\xdd\x59\x35\x89\x9c\xc0\x4e\x06\xc1\x5e\x74\xfc\x94\x99\xac\x77\xce\x4f\xfe\x65\x7f\x2f\xbe\x63\xa2\x78\x43\xab\xc5\xe2\xd3\x81\x9a\xa7\x9e\x13\x6f\x4c\x2d\xef\x49\x63\x19\x91\x61\x79\xe9\xe2\xfa\xaf\x57\xb5\x77\xcd\x3c\x67\x91\x1b\x1b\xb6\x33\x0c\xa1\x67\xed\x2e\xf3\x2d\x93\xf9\xc5\xc2\xe7\x75\xdf\x08\x32\x03\xee\x9a\xff\x1e\x27\xba\x9e\x79\x71\x9a\x19\xb0\x6f\x58\x13\x2f\x70\x86\x71\x38\xf6\xf6\xec\xce\x58\x26\x0f\x93\x53\x49\x8a\x96\x87\x7e\x94\xa5\x4e\xcc\x1b\xbe\xb3\x5c\xb3\xbf\xfe\xbb\x15\x06\x96\x91\xfe\x86\xba\xf0\xe6\xdb\xd5\xa6\xae\x8b\xd5\xb7\x9b\xdf\xbf\x5d\xdd\xdc\x1e\x9f\xf7\x2c\xb6\x81\x28\x89\x13\x57\xdd\x66\xc1\x30\xb3\x6f\xb3\xc4\x89\x8f\x65\x83\x5e\x90\xbd\xbc\x8d\xa9\x5f\xdb\x5e\x62\x98\xd0\x19\x1a\x49\x32\x0f\x63\xbb\x99\xa5\x13\x27\x48\xbd\xa5\xfb\xa6\x71\xe6\x9c\x40\x39\x84\x44\x67\x11\xcd\xa8\x5a\xc8\x3d\xcc\x4f\xb6\x3f\xa7\xa5\xae\xa4\x2f\xe3\x53\xe8\x3b\xf7\x2b\x8d\xdd\xdf\x25\xc9\xe4\xde\xc8\xd2\x49\x18\x7b\xaf\x8e\xfd\xcf\x29\xaa\xc0\xe9\x59\xe7\xa5\xe0\xa9\xb3\x68\x1b\xa9\xb1\xe3\x3e\xeb\x4b\xd3\x27\xd9\x6e\xf5\x39\x3c\x45\x5d\x7d\xfe\x71\xe9\x14\xf7\x5b\x22\x67\xb5\x2a\x7e\x96\xb1\x7b\xbe\xe1\x3a\xa2\x33\x76\x62\x27\xb0\x4e\x0f\x2a\x8a\x7e\x4e\x26\x05\x7e\x88\x8e\xdd\x35\x4e\x52\xf0\x70\x3c\x2e\x93\x77\x3b\xbd\x53\x89\x0b\x94\xbb\x7e\xb8\xed\x81\xfe\xa9\xb4\xb3\x55\x64\x78\xb8\x23\xef\x08\x8c\xc0\x70\x0c\xc3\xf1\xef\x97\x11\x97\x30\x69\x7b\xc9\xf4\xb4\x0a\xac\xd8\x31\x52\x67\x10\x55\xd1\x8c\x8e\x43\xbf\xd8\xf3\x71\xa2\xbe\xc5\x5e\x38\xfb\xac\x52\xae\xae\x76\xb6\x37\xc8\x65\xbc\x1e\xc6\x8e\xef\x65\xfe\x3f\x7b\xa2\x74\x62\x14\xf6\x41\xf6\x14\x14\x11\xed\x2c\x7b\x2a\x08\xe4\xf0\xac\x11\xea\x8f\x9c\x61\x3e\xca\xc1\x8a\x92\xd8\x20\x75\xe2\xb1\x61\x39\x67\xae\x90\x56\x9f\x33\x3a\x72\x8d\x87\xa1\x60\x71\x1b\x78\xd6\x39\x6b\x63\x6f\x20\xba\x6b\x79\x3c\xdf\x88\x17\x67\xc1\xfb\x32\x93\x17\xbd\xb1\xcd\xeb\x9f\xf3\x71\xfc\x6a\x9f\x2e\xbc\xc8\xca\xcb\x7e\x03\x6c\x5f\x5d\xa8\x9c\x8d\xfc\x49\x66\x06\xce\xee\xf6\xb5\xb3\xf3\x9f\x65\xbc\x25\x43\x29\xff\x4c\xee\x8b\x42\x2b\xfb\x9d\x05\x4e\x5a\x7e\x2d\x1e\x9c\x1d\x6a\xd6\x3f\x27\x26\x5b\xf6\x56\xfe\x12\x2b\xd9\x14\xb0\xc9\x6e\xdf\x3e\x87\xb8\x57\xea\xb6\x6d\x44\x5e\xf4\x63\xf4\xb1\x0e\x32\xad\x37\x2c\xf0\x9e\xfa\x5c\xa6\x87\xab\x3f\x7e\x0e\xf2\xd0\xe7\x34\xc7\xd9\xfe\x1c\xe7\x3c\x97\xcb\x3f\x2f\xe5\xe9\xd2\x8f\xcb\x39\xfc\x74\xbf\xe4\x43\x31\xd4\xf6\x0c\x37\x08\x93\xd4\xb3\xce\x1b\x84\x98\x61\x98\xb6\x57\x79\x4e\xc7\x52\x27\x40\x5c\xdf\x3e\x93\xd6\x17\xc4\x42\x89\xbd\x8d\xa1\x4d\xb5\x77\x7f\x6b\x7c\xb3\x6f\x97\x65\x3e\xd6\xb9\x3f\x1e\x72\xdf\xa4\x20\xe7\x25\x75\x02\x44\xe7\xce\x52\xcf\x32\xf5\xa7\x84\x67\x2b\x39\xc5\xe5\xae\x2e\x0e\xcf\xeb\xfc\x79\xe5\xcf\xcd\xfc\x6c\x44\x67\xd5\xaa\x33\x03\x77\x35\x87\x43\x65\x49\x1a\xfa\x52\xbe\x83\xf5\x2d\x79\xbb\x46\x60\x43\x27\x5e\x9f\x63\x59\x1e\xa9\x38\x29\xc0\xc8\xd2\x50\x29\x46\xf7\x7d\x2f\x08\xd7\xa4\xbc\x81\x7a\x24\x4e\x9a\x7a\x41\xbe\x07\xe8\x7f\xcf\x44\xee\x7c\x52\xcf\xb1\x52\xc7\x96\xd6\x32\x9f\x3f\xe8\x2b\x36\xfa\xe6\x96\x6f\x1a\x89\xf3\xbd\xfe\x5b\xe9\x00\xc5\x5f\x72\x28\xe5\x9b\x65\x7f\xbb\xb1\x08\x80\xb1\x14\x0e\x9d\x66\xf8\x74\xf3\xfb\xb7\x9b\x7e\xbb\x4f\x8b\x03\x5e\xee\xf0\x6d\x45\xec\xfd\xed\xbf\xca\x0c\x57\xb7\xf6\xd5\xff\x64\x18\x56\xb3\xd6\xff\xbf\xb9\xb9\xf9\x56\x8a\x5f\x77\xa6\xcd\x9d\xfc\x37\xbf\xff\xfe\xed\xe6\xe6\xe6\xf7\xff\x09\x6e\x90\x78\xa9\x4d\x0d\x78\x9a\x65\x40\x47\x94\xd8\x01\x7f\x69\x09\x5b\xfb\xfc\x77\x0a\xe9\xf0\x80\x15\x07\x7c\xbf\xc3\xcb\x97\x17\xb1\x76\x06\x60\xa3\x80\x26\x25\x8a\x1d\x69\xa0\x88\x54\x87\x6d\x5f\x26\x7e\xe3\xcc\xce\x86\x70\xaa\xa7\x48\x72\x47\x1c\x36\xc5\x0e\x2f\xb7\x07\xfd\x26\xcb\xf3\xcd\x7e\xe7\xb2\x62\x0e\x9c\x9f\xd9\x28\x70\x38\x10\xe5\x66\xaf\x49\x51\x1d\x49\x62\xc4\x81\x32\x64\xdb\xd2\x65\xa5\xed\x3b\xf2\xb0\xa7\x28\xaa\xc7\x76\x78\xf9\x52\xcd\x6d\x9e\x86\xd8\x23\xbe\xd3\xeb\x80\xa6\xdc\x69\x7f\x44\x5b\xb6\x0f\x4c\x6c\x14\x27\x0e\xd9\x7e\x93\xb9\xb0\x67\xca\x43\x14\x5b\x02\xfb\x83\xf6\xc5\xf2\xfa\xa1\xbd\x2d\xee\x63\x8c\x68\xf7\x58\xc5\xa6\x37\xb4\xfb\x2c\xdf\x1c\xb2\x45\xb7\x52\x1d\x51\xa6\x06\xfd\xfe\xe0\x1d\x05\x1e\x3b\x47\xb6\x51\x74\xbb\x29\x37\x5b\x4d\xa9\xd3\xa4\xa8\x81\xc2\xcb\x97\x97\xb8\xe7\x68\xd8\x46\x41\x4f\x9d\x11\x68\x2a\x3d\x79\x28\x76\x68\x56\xbb\xac\x8c\xcd\x43\x52\xfb\x55\xd8\x6c\x29\x7c\xbb\xd7\xf9\x1b\x12\xb1\x57\x23\xe5\xf9\xbc\x9b\x3c\x73\x89\x76\xfd\xc2\x0c\x6f\x6e\xee\x5d\x27\x70\x66\x86\x6f\xfb\x7f\xf5\x0d\xe4\xf7\xff\x6c\x10\x65\xaa\xde\x80\x6a\xca\x6f\xc2\xdb\x43\xeb\x1e\x6b\xd5\x96\x94\x96\x44\x89\xec\x10\x09\x7e\x8b\x3b\xaf\x9f\x44\xf9\xed\xf7\xbb\xf5\x3f\x59\x7b\xdd\x80\x4b\x90\xcd\x3d\xf9\x6d\x9d\xbb\x5d\x7b\xc4\x7f\xd6\x24\xa3\xff\xb6\x43\x21\x15\x40\xdc\x94\x9a\xa9\x23\xb5\x70\x8b\x11\x27\x36\xa3\xb8\x3d\xcd\x75\x01\x46\xf7\x0d\xb5\x81\x3b\x1d\x3a\xd0\xd5\x06\x46\xb9\x51\x62\xfb\xa0\x6e\x33\x20\xd3\xa9\x66\x6a\x52\xcd\x98\x97\x9b\x50\x84\x1c\x2d\x4a\xcd\x99\xce\x00\xa2\x57\xe3\x66\x66\x4d\x24\xf4\x05\xb9\x30\x09\x12\x33\xbb\xa3\x27\x87\xd1\x5f\x35\xc2\x5e\x98\x35\xdb\xb7\x16\xcd\xd9\x3e\x39\x7d\xb9\x39\xe7\x14\x5d\x12\x15\xc5\xed\x11\x22\xb4\xbd\x22\xbf\xed\x5b\x33\xdb\xa7\x17\xfb\xe4\xa0\xdf\x29\x37\x7c\x66\x19\x9a\x30\x09\x38\x65\x29\x0e\x5a\x01\x37\xb3\x9e\x43\x57\x67\x58\x9c\x65\xc0\xc2\xf2\xc9\xc5\x13\x85\xbd\xf6\xdb\x53\x62\x20\x4d\x5d\x3d\xe0\x66\xa6\xd4\x9a\x8e\x7c\x90\xd9\x1e\xf6\xdf\x66\xad\x05\xcd\xe7\xd0\x15\xa6\x22\xd5\x6f\x37\x1b\x7d\xa9\xd5\x11\x20\xa9\x8a\x80\x93\x25\x85\x1c\x68\x18\xce\x29\x18\xde\x02\x1d\x9e\x1d\x78\xad\xce\x48\x13\x27\x23\x9f\x7e\xd5\xa5\x16\x34\x03\x3d\xb2\x7c\x32\x33\x55\x90\xd9\x54\x8b\xd0\x35\xee\xd5\x50\xc9\x8c\x65\xf0\xc8\x22\xf0\x89\xcd\xf0\x21\xeb\x46\x0b\xa4\x5b\xdd\x2b\xea\xdb\x23\x5e\xa2\x91\x47\x2e\x2c\x06\x9b\x69\x38\x39\x1d\x79\xe1\x13\x15\x70\x73\x94\xa6\xa7\xc2\xd4\x62\xc8\x85\x4d\xb5\x42\xbb\x2b\xce\xad\xd7\x70\xd6\x23\xc4\xa4\xe7\xeb\x50\x67\xc8\xc5\x48\x6b\x2d\x4c\x22\x82\xa3\x9a\x90\x99\x35\x2e\xe8\xd5\x5a\xf8\xc8\x23\xa1\xc5\x80\xa4\x87\x73\x82\x2c\xe1\x5d\xa5\x63\xa5\x12\x06\xf4\x9e\x02\x04\x51\x99\xa7\xfc\x3c\x42\x65\xb9\x3d\x09\x8f\x4c\xad\x35\xb3\x02\xc1\x35\xba\x22\x66\x75\xfb\xdf\x7b\x0b\x72\x3e\x52\xf9\x78\xa4\xda\xd0\x5a\x34\x52\x43\xe5\x17\x66\x8d\x9f\xe9\x81\x90\x8d\x08\x32\xed\x11\x29\x74\xb4\xfe\xcc\x54\xe1\xb3\xe5\x93\xaf\x26\xa1\x63\x3d\x9f\x7e\x1d\x9d\x2f\xd3\x37\xbb\x00\x9a\x81\xe8\x19\x9a\x90\x19\xea\xe3\x4c\xf7\x5f\x70\x64\x4b\x23\x1f\x62\x3d\x3f\x85\x8e\x10\x3e\xe9\x3e\xb9\x60\x19\x1a\xb3\x19\x90\x5a\x5d\xc1\x35\xd4\xba\xeb\xbc\x76\xb2\xde\x33\x20\x07\x8b\xd6\xd4\x9c\x87\x2e\xdb\x5d\xda\x68\x64\x06\x3c\x36\x52\x5f\x12\x96\x99\x60\x76\xb7\xf5\x3a\xf0\x1e\x67\x3a\x33\xcf\x74\x1f\x4c\xcd\x1a\x37\xb1\xba\xdc\xcc\xf0\xc1\xb3\x4d\x35\x66\x96\x6f\xcd\xac\x2e\xf0\x7a\x04\x98\xeb\xea\x7c\xa6\x6b\x2d\x68\x52\xf8\x42\x57\x5f\xe0\x48\xe3\x61\x4f\x7d\x99\xd8\x0c\x78\xb5\x29\xac\xd6\xf3\x1b\xb3\x91\xc6\x3d\x1b\x54\x23\x6f\x1f\xe7\x8d\xdc\x51\xc0\xc1\x91\x9a\x3c\xb1\x54\x2b\xd2\xbd\x96\xa9\x2e\x9a\x53\x87\xa8\xea\x2a\x92\x2c\x85\x27\x36\xd5\xc4\x59\x1a\xb7\x07\x8b\x16\x66\x30\x20\x63\xbb\x7c\xa2\xab\x60\xce\xb6\x3b\xf3\xc1\xa2\x05\xcd\x2e\x0f\x59\x06\xd4\x0d\x4d\x70\xfb\x72\xe2\xea\xfe\xf4\x49\x67\xc8\x4c\x17\xc2\xa7\x11\x41\x63\x6c\xbb\x3e\xd3\x35\xf1\xb9\x57\x43\x6d\x6c\x2c\x74\xa4\xd3\x45\x63\xda\x23\xe8\xef\xb6\xc6\xc1\x5e\xc0\x41\x8b\x79\x74\x87\xed\x79\x20\x2a\x24\xc3\xcd\x23\x73\xa4\x45\xb8\xe5\x2b\xe9\x88\x78\x89\x34\x21\xca\x46\x2a\x0e\x87\x6a\x99\x5e\xe5\x13\x43\x88\x3c\xd4\x3e\x5b\xe3\x92\xa1\xba\xd2\x93\xc5\xd0\xcf\x06\x41\x07\xba\xd6\xcf\x36\xfb\x95\x9f\x99\x12\xd9\xb0\x55\xbc\x2c\x9f\x9c\x38\x01\x58\xe8\x12\xfe\x6c\x32\xd3\x27\x5d\x6d\x4c\x46\xfe\x0b\xd4\xdb\x78\x43\xd7\xfa\x4f\x7a\xad\x15\x8c\x88\x09\x1c\x11\x09\xe9\xa8\xe0\x95\x72\xab\x3a\x81\x67\xb3\xc6\xc1\xed\x3a\x8d\x08\x72\xa1\x7f\x54\x9d\x54\x7e\x66\xf9\xca\xd1\x3a\x99\xfe\xe3\x13\xd2\x15\xe5\x46\xcf\x23\x4d\x70\x87\x1e\x09\x6d\xa6\x3f\x73\x34\x90\x16\xfa\x24\x5f\x7b\xbe\x30\xb3\x19\x21\x45\xb6\x6a\x06\x42\x9a\xdb\xe4\x1e\x5d\x6f\xa7\x59\xb6\x4d\x13\xa7\x3d\xb5\xc0\xc6\x9e\xca\x45\x76\xf3\x74\xfb\x36\xed\x1f\xce\x7a\x04\x8f\xfc\x63\x66\x2d\x1e\x6b\x94\x0f\xb2\x91\xca\x25\xba\x2a\x14\x3a\xf5\xed\xb9\x4e\xf0\xa1\xae\xf2\xf1\x50\x83\xd0\x9a\x47\xb4\x8c\x8d\x9e\x28\x5f\x9f\x59\x5e\x6b\x62\x77\x45\x68\x6a\x2d\x8c\x65\x60\xc6\x76\x93\x97\x9e\x57\xc7\xc7\xc8\xbe\x98\xc7\x27\xd4\x4e\x96\xc2\x1b\xe8\x99\x55\x13\x27\x26\x33\x77\x47\x5a\xf4\xaa\xab\x7d\x64\x33\x13\x53\xa5\x09\x96\xa1\xbf\x5b\x04\x78\xee\xa9\xf8\xcc\xf4\x21\x66\xd6\x58\x77\xdd\xae\x7a\x32\x9b\xf5\xe5\x66\xd6\x97\x5a\x95\x2d\xa4\x7a\x97\x9f\xa2\x7c\xb9\x9d\x69\x3c\x1c\xd5\xc0\xc2\xd0\xc4\x06\xcb\x88\xb3\x11\x91\x42\xcb\x6b\x61\x3a\x85\x4f\x74\x02\x61\x22\x8e\xfc\xfe\x87\xf8\x91\x15\xb4\x50\x7b\x21\x4b\x09\x61\xf9\xfd\x65\x24\xb5\x1e\x59\xc6\x5e\xe8\x5a\xd3\xd5\x7c\xda\xb3\x02\x21\x7d\x12\x36\xed\xc1\xaa\xc1\xd7\x51\x0d\x61\xac\x30\xeb\xb7\x3b\xa9\xce\xc0\xd7\xbc\x0f\x90\xdd\xd7\x38\x98\xdb\xa0\x3f\x5a\xb7\x87\x58\xd7\xb8\x4c\x57\xe7\x59\x6e\xd3\x80\x9c\x8f\x34\x11\x43\xbf\xb1\x6d\xcc\x1d\x53\xa4\x67\xa8\xf5\x99\xdd\xe5\x70\x5d\x28\xfa\xab\x2a\x83\xa5\xb0\x14\x7d\x47\x6d\xa6\xdc\xc8\x37\x34\x0e\xda\x04\x9d\x98\x14\xfe\x6c\xaa\x02\xc2\xd3\x89\xce\x08\x45\x0c\x68\x63\x18\xdf\xee\xcf\x6c\x86\x9f\xe7\xf9\x18\xb0\x30\x55\x3a\xcb\xe3\xb0\x8f\xe2\x0e\xa8\x21\x0c\xec\xa9\x7c\x8a\x62\x68\x4f\xa5\xa7\x3a\x85\xcf\xcd\x1a\x87\x0d\x65\x01\xe3\x29\x12\x1b\xd5\x9a\x48\xe6\x5c\xd7\xb8\x74\xa4\x36\x90\xfd\x3e\xe9\x3e\x5c\xe8\x9a\x8d\xec\x21\x1d\x11\xf8\x14\xc9\x1e\xa9\xe2\xb4\x57\xe0\x38\xd9\x7f\x66\x17\x7d\x6f\x7f\x5e\xca\xe7\x27\x36\xd5\xfc\xef\x1e\x01\xb0\xd1\x82\xdc\xee\xe7\x5d\x5f\xda\xd5\x9d\x29\x29\x0d\x01\x00\xd1\xa6\x5c\x38\x40\x3e\xc4\x76\xf9\x86\x55\x13\xa1\xa9\x0a\x4f\x14\x10\x27\xfa\xa2\xf5\x2f\xb3\x06\x16\xa6\x4f\x27\x7a\x33\xe4\x94\x52\x5f\x3a\x4d\x32\x86\xfa\x82\xe9\x1a\xeb\x6a\x18\x3f\x94\x15\x8c\x1c\x69\x1c\xe2\x12\xa6\x38\x85\x7d\xd0\x01\x92\x26\x84\x9c\xec\xd3\xa9\x2e\xb5\x52\x93\x10\x23\x3d\x98\x3e\x51\x4a\x19\x87\x99\x68\x66\x6b\x5c\x36\x52\x5f\xa6\x94\x0b\x25\x5d\xc5\x67\xb6\x0f\xc6\x36\x01\x13\x9d\xe1\x27\x96\x2f\xb8\xda\x3c\xe4\x14\x1f\xa4\x66\x4d\x87\x2c\x2d\x72\xb2\x02\x64\xd0\xa1\x79\xa5\x19\x3e\xa9\x18\x09\x00\xdd\x02\x80\xc6\x9e\x28\xa5\x31\x31\x55\xc5\x45\x71\xcb\x26\xe8\x45\x5e\xcf\x3c\x6e\x37\x17\x03\x99\x7d\xed\x37\x2b\x3f\xa5\xbf\xb3\xcc\xcb\x4c\x27\x60\xc6\x52\x78\x54\xfc\x4d\x3f\x8f\x08\x12\x37\x03\xc1\x2d\xa7\x20\x5e\x59\x8a\x9d\x2a\x38\xa0\x14\x8c\x97\x24\xd0\x52\x10\xef\x90\x14\xc1\xa3\xdc\xe8\xd5\xd1\x78\x4c\x57\xf1\x67\x9b\x99\xbb\xc8\x8f\x51\xac\xd5\x19\xf2\xd9\x20\xc0\xa2\x17\xf0\xd0\x0a\xf4\x68\x44\x28\x4f\x23\x2d\x74\x47\x2a\xbf\x58\x95\x87\xa5\x66\xae\x8b\xa6\xc7\x51\x93\x57\x1d\xd9\x97\xaa\xb8\x5c\x8d\x7f\xec\x79\xe1\x6c\xdc\x9d\x07\x6c\x7b\xfe\x38\xa4\xd8\xa9\xa0\xf0\x92\x32\x05\xb2\x8c\x03\x49\xc0\x00\x27\x52\x6c\xc4\xba\xe1\x93\xac\x88\xbc\xa4\xe0\x2d\x11\x53\x48\xd6\x13\x1f\x14\xd8\xe2\x64\x85\xee\x8a\x92\x02\x7b\x8b\x88\xec\x2d\xc4\x87\xb5\x34\xcf\xec\x22\x9c\x8d\x25\xf6\xa9\xaa\x1f\xdb\x6d\xe1\x08\x77\x58\x4f\xe4\xc5\x0e\x5e\xe6\xdd\xfb\x5c\x52\x3a\x90\x17\x14\x9b\x46\xe5\x9e\xc5\x07\x18\xa4\x4b\xc4\x4f\xf4\x89\xad\xbe\x60\x6c\x17\x61\x11\x07\x6d\xaa\x35\x35\x6b\x76\x66\x32\xe4\xa4\xf0\x0b\x3d\x32\x11\x9f\x2d\x6c\x36\xd5\x19\x2c\xb3\x18\x84\x8d\x58\x6a\xa8\x82\xcb\xfa\x2b\x9c\xe6\x3a\x29\xad\x02\xbd\x05\x94\x17\x45\xa1\x39\x5a\x9c\xc2\x51\x4f\xe3\x89\xd1\xa2\x41\x8c\x34\x90\xd8\x54\xa3\xc4\xc1\x46\xa6\x6b\xc2\x0c\x61\xa7\xe5\x03\xcc\x5a\x90\x0b\x8b\xc2\x91\x6c\x8f\xa5\x46\x3e\xcb\x70\x0b\x5d\xa5\x63\xca\x6b\xba\x86\x3a\x72\x55\x3c\x71\xb9\x6e\xba\xc4\xeb\xb1\xd4\x4c\xcd\xae\xe0\xf2\x52\xcb\xd6\xe4\xc4\xb5\x99\x09\x34\xbd\xd6\xab\xc9\x00\x68\x51\xcd\x97\x7e\x3b\x71\x75\xf5\x25\xe7\x98\x0e\x03\x31\xb6\xdd\x79\x60\x19\x3d\xa2\x7c\x71\x66\xfa\xca\xd3\x88\x98\xa4\x26\x21\xb8\xfd\xf6\x68\xde\xa7\x9a\x05\x77\x20\xf0\xa9\x29\x35\xe6\xba\x8a\x3d\x59\x04\x98\x22\x7b\x63\x29\x3c\xd3\xa5\x66\xd0\x97\xe6\x33\x51\x69\x74\xd8\x0e\x4f\x2b\x50\xe4\xc4\x29\x6c\x0b\x40\xa4\x7b\x8b\xd6\x9c\x5b\xec\xe6\x2f\xec\xf4\x02\xdd\x76\xf9\x69\x2f\x68\xc1\x92\xcb\x22\x3e\xbe\xe2\xb1\x0b\xb1\x27\x02\xa8\x0a\x00\xf4\x01\xdd\x92\x44\x45\xe7\x54\x0a\x7f\xb5\xfd\x7e\x66\xfb\x34\x6e\x76\x85\xac\x8c\x3d\x99\xe9\x03\xac\x57\x2b\xcb\xec\xf6\x67\x56\xd0\x4c\x4d\x55\x7c\xd5\x29\x16\xb5\x7d\x66\x13\x75\x17\xe1\xa8\xc9\x90\xc1\x20\xe0\x1b\x16\xf1\x32\xd3\xd7\xda\x61\x11\x42\xde\xc7\x94\xcf\x87\xa6\x4a\x4e\xd9\x76\x93\xe8\xb7\x9b\xbb\xf5\x44\xfc\x4b\x0b\x5d\x24\xc7\xac\x01\x34\xbe\x49\x0d\x42\x8c\x2c\xaf\x92\x45\x2f\xcc\x05\xbe\xb0\xca\xb8\xce\x3f\x23\x1f\x12\x10\x9e\x3d\xb2\x9e\xd8\xca\x6d\x7a\x4a\x0b\x92\xc2\xb7\x04\x08\x06\x62\xe7\x85\x66\xbd\x25\x3e\x4e\x2c\xff\x31\xb5\x82\xe6\x6c\xa4\x8a\xa9\xa1\xd6\xd3\x11\xd1\x49\x47\x01\xc8\x74\xe6\x05\x16\xe5\x47\xcf\x06\x43\xd6\x4c\xaf\x99\x2a\x1e\xea\xf7\xe6\x7c\xf0\xdc\x99\xaf\xd5\xb5\x92\xf1\x44\xf9\x78\xac\x33\x70\xc1\x52\xab\xb8\x95\xe3\x22\x45\x96\x18\x81\xcd\x0a\x1d\x35\x5e\x75\x8d\x23\x0c\x95\x87\x3d\x5f\xd8\xe2\x97\x4b\xac\x5d\xe6\x5f\xea\x8b\x00\x0b\xdb\x87\xcf\xba\x84\x78\x27\x89\xc6\x32\x0b\x43\x15\xf7\xc5\x3f\x13\xa8\x8d\xc8\xa6\xb1\x27\x41\xd5\x11\x1e\x93\xa8\xbf\x6c\x82\x5c\x18\x0b\xc4\x23\x5e\x22\xd3\x57\x32\x9b\xa1\x17\x3a\x01\x30\x6a\x0a\x86\xa2\xbb\xc5\x73\x09\x11\xea\x45\x9f\x97\x75\x6e\x3e\x0e\x3b\x28\x5d\xc4\x8b\x34\xdf\xd1\x70\x6e\x28\x77\xc0\xb8\xf2\xc1\xa1\x44\x12\x23\x8d\x9d\x59\x01\xc8\xca\xfa\xa2\xb1\xa6\x9f\xfb\x64\xb3\xcc\x83\x91\x02\xe8\xc0\xa1\x0c\xfb\x24\xeb\xe1\x2d\x96\x42\xb1\x0d\x5f\xb0\xf4\x7c\x2a\x2b\xa2\x2c\xd2\xa4\x24\x63\x2f\xb4\x86\x57\xb6\xc7\x3e\x51\x3e\xa8\x97\xe3\x2d\x59\xc1\x5e\xc6\x02\x06\x24\x40\x93\x8c\xa4\xbc\xd0\x43\xa9\xd4\x33\x93\x46\xbd\x9a\x98\x58\x0b\xf2\x59\xd7\x38\xe4\xeb\xcf\x23\x09\xf7\x6c\xb5\x31\x35\x19\x25\x1b\xd5\x38\x64\x37\xd0\xe9\x16\xe3\x20\x19\x61\x37\x10\x87\xca\x14\x0e\x44\x9c\xec\x0a\x38\x3f\x16\x95\x86\x2a\x01\x6e\x28\x4f\x71\x5a\x86\x02\xc9\x2d\xc4\xbc\xce\x5b\xbf\x07\x6b\xf5\xe1\x65\xac\xc1\x81\x0e\x29\x49\x4a\xa3\xab\x61\x76\x5b\xc1\xc9\x96\x80\xf1\x43\xa0\x34\x94\xa1\x42\x4b\x32\x1a\xef\x31\x64\x60\xcd\x8f\x97\xad\x4c\x41\x17\xc5\x93\xa1\x64\x4d\xe5\x0e\x89\xfc\x9e\x93\xb1\xfa\xa9\xb2\xda\x22\xe0\x14\x0d\xcb\xf1\x62\x43\x1f\xa5\x8d\xe5\x38\xa1\x6b\x93\x9c\x6f\xb0\x1d\x7c\x28\x4f\xa1\x22\xe3\x1c\x27\x4f\xed\xb1\x88\xf1\x72\x99\x77\x8c\xc6\xa2\x28\x3e\x8b\x32\xb6\xc7\x3f\x8f\xd6\x61\x20\x28\x38\xad\xe0\xad\x96\x80\x29\xa4\x00\xb8\xa1\x02\x5b\x7d\x93\xb0\x5f\x0f\xe7\xe3\x87\xf2\x54\xe7\x44\x7c\x35\x7e\xcf\xdb\x8d\xfa\xbf\x53\x3d\xd3\x69\x05\xf2\xc7\x75\x80\x03\x59\x04\x64\x57\x54\x1a\x34\x98\xd2\x63\x01\x23\x07\xe2\x14\x76\x35\x1c\xb5\x49\xe7\x50\x7d\x0a\x7e\x17\x1e\xd4\x01\xe8\x80\x81\x80\xfa\xea\x5c\xbd\xe7\x36\xaf\x90\xc8\xd7\x0f\xc9\x54\xa6\x64\x5f\x04\x24\x27\x43\x5e\x11\x94\x46\x5b\x94\xb1\x80\xa3\x26\xa1\x59\xe3\xb1\x22\xd6\x4f\x83\xbd\xfe\x86\xfc\x9e\x40\x7e\xac\x47\x7a\x11\x03\xdd\x61\xfb\x25\xe7\x28\x28\x7e\x8b\x90\x1b\xca\x50\xa4\xe5\xa9\x08\x94\xe9\xbc\xb0\xcf\x0e\xce\x28\x53\x72\x80\xda\x21\xd2\x40\x92\x29\x6b\x83\x0f\x94\x69\xca\x98\x5e\x3c\x53\x30\x12\x28\x53\x9e\xde\x63\xef\x7d\x19\xe3\x5b\xb9\x7f\x7a\x9b\x69\xf3\x36\xc9\x39\xc6\x22\x79\x15\x07\xc9\x7d\x55\x52\x1a\x88\x7f\x0d\x84\xbc\x3c\x21\x34\x18\xf2\xd5\x66\x72\xee\x15\x71\xcb\xb1\xd0\x12\x17\x73\x8c\xd0\x35\xb1\xb6\xc5\x53\x2b\x0e\x35\x5d\xe2\x9f\x54\xf2\x80\x25\xe6\x35\x1f\x87\x94\x8d\xe4\x05\x14\x4c\x81\x59\x8c\xf1\x5a\x7a\x20\x42\xeb\x19\x9f\x9a\x04\x1f\xeb\x1a\xbb\x4a\x2f\x44\x92\xae\xd1\x38\xe2\xef\xd6\xeb\xbe\xe7\xe1\x93\x8a\x57\xdc\x0c\xd8\xd4\x14\x64\xb6\x0f\x17\x26\xd1\x48\x11\x7f\x17\x7d\x98\xe8\x95\x3f\xec\xf6\xcd\x93\xa8\x4d\xe0\x08\xe7\xb1\x91\xc6\x61\x4a\x97\x83\x43\x09\x9b\xd9\x1a\xbf\xe8\x11\x5c\x64\x7a\x64\x55\x9e\x6b\xf9\x98\xdb\x53\x47\x2e\xa7\xd4\x37\xf2\x0c\x25\x12\xb7\x6a\xec\x6c\xe4\xc3\xac\x47\x2c\xc7\x65\x0b\x5b\xad\xbb\x5a\x33\x74\x11\xf7\x41\xf5\xb0\x02\x38\x37\x09\x98\xd9\x54\xb3\x90\x5f\xe3\xf3\xb1\x8b\xac\x02\xcc\xf2\xe1\xb3\x85\x15\x73\x74\x16\x01\x67\xa6\xd7\xb2\x10\x9f\xe9\x49\xf8\x9a\xbd\x35\xa1\x44\x2d\x7f\xcf\xc7\xc4\x2c\x05\x06\x2c\x3d\x7f\x62\x11\x3f\xd0\xb8\xb4\xfa\x6e\x4a\xcd\x85\xbe\x28\xd3\x6a\xa3\x1d\x3e\x36\xf0\x96\xf8\x92\x16\x1c\x62\x2d\x6d\x4d\x9f\x58\xe8\x79\xd7\xfa\xbe\xfe\xdd\x29\xeb\xc4\x75\xf0\x8e\xac\x94\xfc\xb1\x2c\x4f\x40\x7d\xd1\x39\xc0\xfd\xca\x34\x62\xc0\xcd\xcc\x40\xa4\x4d\x5f\x04\x96\x3f\x2f\xe5\xe8\x92\x8c\x35\x14\xc4\x97\x00\xe0\xfa\x55\x5a\x84\xb5\x3a\xe2\xac\x1d\x7e\x66\x06\xfc\x0c\xc9\x2d\x74\x89\x23\x4c\xea\xeb\x9a\x0e\x4d\xaa\xa5\x8d\x34\x2e\x33\xd4\x46\x50\xe5\x53\xba\x5c\x64\xfb\x34\xa6\x2b\x29\x74\x94\xe3\xf5\x51\x08\x12\xb7\x7c\x1e\x8a\x6a\x83\x30\x34\x6e\x66\xfa\x38\x1a\x4b\x15\xf5\x02\xfc\x10\x00\xae\x2d\x2a\x60\x00\xa6\x10\xd5\x91\x17\x95\x86\xb2\x9d\x57\xf1\xc9\x44\x97\x9a\xd3\x1d\x1f\xab\xf4\x09\x78\x34\x0e\x7a\xd6\x01\x37\x33\x19\xc0\x15\x73\x69\x8d\xe7\xbd\x79\x14\x38\x50\x70\xb1\x25\x4f\x79\x9a\x9a\x82\xba\xae\xf2\xb2\xcd\x90\xf3\x03\xf6\xf5\x8a\x9e\xa1\xfe\xa7\x20\x07\xcb\xb1\x0a\x39\x52\x5f\x6a\x23\x0d\xbe\xae\x7e\x13\x65\x5d\xed\x93\x7d\x21\x92\x11\x47\xb0\x3b\x2f\x91\xa9\x42\x4c\x52\x1b\x68\x0c\x4d\x8c\xd4\x39\xd9\xcf\xc7\x5b\xd5\xbc\xc5\x8b\x4d\x41\x7b\x62\x06\x22\xd4\x3b\x5c\x63\xa8\xe2\xb8\xd9\x15\xa3\x9e\x06\x5e\x91\xcf\x1d\xe5\x19\x9b\xbe\x55\x71\xa9\x25\xf6\xa1\x71\x8f\x86\x73\x74\xd5\x66\x0d\x83\x1d\x84\xd7\xbb\xe3\x21\xeb\x49\xc8\xb1\xaf\x31\x16\x40\x8b\xd3\x30\xbe\x2f\xa1\x7e\xa0\x97\x71\x72\x28\x2b\xf8\x50\x86\x45\xcc\x2a\x64\xe4\xe9\x5b\x4a\x07\xb6\xe5\x0e\xa4\x65\x28\xe6\x69\x05\x8c\xe4\x51\x9c\x96\xa7\x34\x8f\x70\x53\xec\xd0\x8a\xa0\x70\x2d\x05\x03\x63\x41\xe1\xdb\x32\x9e\xcb\x5d\xca\x59\x7b\xbe\x8c\xff\xab\xbc\x24\x2f\x28\x70\xa0\x61\x8d\x96\xac\x28\x5b\x38\x5b\x9f\x0a\xd8\x0b\x50\x70\x91\x56\x60\xab\xa5\x4c\xc1\x00\x74\xc4\xa1\xac\xd0\x9c\x3c\xcd\xd3\x07\xd4\x74\x35\xde\x59\xf2\xa2\x36\x16\xec\x1b\x07\x71\xf3\x1c\x9f\x57\x75\x6c\xe3\xad\x7c\x2c\x04\x38\xa8\x77\x26\x13\xbb\x43\xce\x75\xb5\x21\x1b\x0c\xf4\x6d\x9a\x13\xa8\x29\xb2\xd9\xcd\x18\xae\xc0\xe6\x93\x02\x5b\x63\x59\x21\x3b\x79\xec\xa2\x39\x21\xff\x2e\x59\x4f\xcb\xb1\x65\xf9\x7b\x19\x67\x0e\xc7\xb3\x1d\x1e\x5b\x72\xf5\x35\xfe\x8b\xc6\xb7\x1c\x06\x86\xa2\x67\x3d\xa9\x38\xc8\x0c\x4d\xb4\xa9\x29\xed\xdb\x0c\x58\x0c\xd5\xca\x6e\x57\xe9\x73\x1b\xd5\x40\x64\xf9\xe0\x75\xef\x73\x37\x32\x95\x0a\xe3\x01\xf6\xb4\xe9\xa7\x22\x63\xa8\x2f\x70\xc9\x1b\x6b\xf0\x75\x44\x90\x99\xee\xc3\x60\x6d\x1c\x41\x3b\x0c\x78\x56\x0a\x3f\x10\x2c\x5f\x21\x7b\x07\xb0\xba\x5c\x33\x80\xb2\xbb\x9e\x47\x20\x7b\x35\xf0\x6a\x79\xc5\x1c\xd3\x6a\x1c\xcd\xe1\xef\xc5\x67\x5d\x6a\xb5\x04\x9c\x1b\xaf\xd9\xfd\x58\x52\x84\x0a\xe3\x20\xdb\xa1\x11\xc6\x0e\x34\x8c\x16\x24\x40\x56\x36\x5d\x71\xe6\xb6\x8c\xe1\x88\xcf\x8c\x0b\xdb\x2c\xcb\x54\x15\xf7\x98\x7d\xaf\x95\xdd\x91\x31\xbc\x85\xfc\x6b\x3b\x3f\xe2\x8f\x60\x4a\x03\x99\x16\x97\xe3\x81\xb5\x7c\x9b\x36\x49\xad\xf2\x21\x2c\x2b\xfc\x02\xc8\x4a\x87\x6e\xaf\xcb\x5c\xda\xe0\xf2\x37\xcc\xed\xfb\x56\xd5\xd6\x39\xdb\x16\xb0\xfe\x6b\x58\xe7\xdb\xc2\x6b\x55\x96\xbd\x3d\x26\x6c\x87\x9b\x7f\x53\xeb\xf1\x0a\xf5\xc7\x1a\xef\x20\xaa\xb9\xa7\xf9\xf7\x23\xcf\xaa\xf2\x89\x75\x2e\x33\xf0\xd6\xbe\x07\x61\x99\x66\x6d\xbe\x24\xaf\x5f\x6b\xdd\xb6\x66\x56\xfb\x00\x5f\x40\xcf\x50\x5b\xa8\xe6\x2b\xff\xdc\x2c\xb1\x5a\x8c\x4c\x15\xcc\x6c\x4d\xcc\xb1\x5d\x21\xc0\xf3\x50\xee\xa3\xf1\xf2\x93\xe2\x83\xd7\x1c\xa3\xdb\xf8\xc4\xec\xda\x13\x47\xeb\xaf\x7e\xa3\x79\x38\x7a\xc5\x5e\x28\x58\x70\x0e\x99\x81\xa9\xa1\x89\x1c\xc2\x69\x2b\xd0\x27\x66\x1b\x9b\x23\x7f\x91\xd4\x06\x4a\x9f\x98\x34\xf6\x04\x08\x3a\xb3\x19\x30\x15\x82\x29\x69\xa2\xb1\x1b\x33\x4d\x6d\x8d\x47\x3e\x86\x64\x04\xba\x26\xec\x9d\xb3\xdf\xe3\x4b\xa9\x49\x34\x22\x9b\x21\x17\xc8\xc7\xf3\x3c\x53\xbe\x0f\x00\xaf\x88\x80\x1b\x17\x7d\x4f\xb7\x05\x8c\x04\x88\x7f\x0a\xc5\xba\x84\xa4\xab\x22\x3b\xd2\xc4\xa1\xc5\x80\x4c\x21\x26\x91\x1e\x88\x6d\xb3\x8b\xda\x03\x16\xbb\xf9\xd7\x6c\x47\xc6\x29\x61\x8a\xbd\x0b\xab\xf7\xd9\x31\xca\xb3\xf6\xbb\xa0\x4c\x01\x23\x01\x37\x28\xb1\x73\x7d\xbc\x97\xcf\xb7\x2b\x3e\x98\x4a\x0c\x8d\xc9\xb5\x16\x34\x21\x1f\x1a\xaa\x8e\x21\x4c\x3d\x54\xdf\x4f\xc6\xda\x65\x1f\xac\xcd\x43\x1e\x9d\x27\xd8\x37\x6f\xa9\xf8\xe0\xc5\x56\xe1\x42\xd7\xfa\x7b\x9f\x53\x30\x95\x2b\xfc\xd5\x84\x88\xde\x58\x73\x2d\xf8\x1d\xb9\xc6\xf3\x9f\x4d\xa2\xe1\x1b\xaa\x35\x1b\x69\xdc\x6c\x6d\x5d\xe3\x87\x70\xea\xb5\xf1\x89\xcb\x29\xee\xf2\xf7\x0a\x8b\xe5\x15\x46\x2f\xcc\x35\x3c\x5a\xc5\x69\xf2\x20\x9e\x6d\xa7\xc9\xed\x0a\xf0\x82\xa0\xf0\x1b\x38\x2a\x00\xb1\x25\x4c\x69\x34\x7e\x5d\xda\x4f\xc9\x0f\xd6\xb0\x7c\x4f\xdc\xdf\xc1\xce\xa5\xff\xac\xe5\xdb\x89\xe9\xab\x67\x34\x27\x68\x18\x3e\x14\x3b\xcb\xdf\x52\xb6\xcd\x05\xeb\xf8\x99\xcf\xdf\xac\xd9\x93\xe1\x23\xce\xda\x98\x98\xed\xf0\xf0\x33\xea\x0d\xe3\x80\x95\x2d\x17\x75\x60\x56\xfd\xff\x13\x63\x59\x3e\xd6\x47\xf8\xbf\xe4\xa5\x20\x02\xca\x14\x8c\x95\x0e\x29\x81\x0e\xdd\x2f\xfa\x02\xc8\x0a\x4e\x76\x95\x29\x09\x14\x9a\xe4\x44\xba\x8f\xf8\x9e\x20\xe3\x9c\x22\x28\x2f\xa8\xaf\x69\x05\xe7\x8b\xe7\x1d\xd8\x51\x16\xd6\x1e\x39\x4b\xde\xca\x89\x88\xe7\xd1\xad\xa1\x02\xc5\x96\xdc\x29\x7f\xef\xc0\x0e\x37\x8f\x5a\x2a\x04\x92\x08\xc8\x4a\xf6\x58\x54\x5e\x68\x30\xa5\x15\x51\x11\xc7\x22\xce\x0d\x01\x68\x8d\x25\x45\x94\x11\x7e\x55\xb2\xd7\xd2\x6c\xd4\xe1\x17\xc2\x4c\xa1\xec\x87\xa7\x95\xbe\x26\x43\x05\x17\x97\x75\x5d\x9b\xc7\xc9\xca\x39\xdd\x0c\xd9\x9c\xd2\xa1\x25\x34\x16\xab\xf8\x78\x55\xff\x4f\xc6\xdf\xca\x6e\x3e\x71\x1e\xe3\xc4\x18\xed\xb2\x39\x8e\x62\x1c\xc6\x90\x0b\xe4\x4b\xab\x71\x25\xf2\xc3\xd6\x42\x97\xb1\xf4\x00\xc7\x4d\x59\x0a\xf7\x73\xce\xb2\x9e\xe7\x10\xd7\x09\x40\xb6\xe4\xb9\xea\x64\x66\xd5\xc4\x8a\xff\xb2\xab\xdf\x2b\xac\x5e\x61\x38\xc2\xff\xf7\x72\xde\x33\xfd\x77\xbd\x1c\x03\x00\x8e\xd6\xf0\xd2\x1f\xe9\x55\x59\x5b\x1c\x7c\x3b\x1d\x2d\x77\x80\x2a\x00\x91\x16\xe9\x43\xb2\xc5\x16\xe8\xd0\x94\x00\xd0\xf8\xb6\xc2\x74\xf1\xc7\xf0\xec\x3d\x7e\xb4\x26\x53\x52\x68\x92\x97\x31\x91\xde\x3f\x47\xd4\x74\xf9\xb6\x80\x0d\x9e\x5d\x8c\x6f\x0b\x6b\xdc\x9c\x5d\xf4\x9f\xd9\xef\xc5\x3a\xf5\x1f\xca\xb9\xe7\x4b\xbc\x38\x27\xb6\xbc\x23\x7e\x18\x0c\x3f\x2b\xd6\x88\x5a\x55\x1b\xc6\xb6\x1c\xcd\x46\x7e\x04\x47\x35\x71\x6c\x3d\x47\xc4\x48\xe3\xc6\x26\x43\x06\x5a\x4d\xf8\x6e\xbd\x36\xab\x58\x39\xb3\xf2\x35\x96\xb5\x3d\x35\xc4\x4b\x64\x06\xa0\x6e\xaa\x64\x56\xae\xd5\x57\xfb\x49\x56\xe3\x66\x06\x66\x2c\xb3\xc5\xb3\x99\xcd\x58\x95\xf3\x2b\x8d\x9b\xb3\x0c\x8d\x9b\x04\xfe\x5a\xed\x25\xd1\xa9\xd6\xc4\x09\x78\x38\x22\x50\xec\x15\xdc\x62\xbd\xa0\x95\xcf\x39\x8f\x88\x09\xc2\x87\x86\x4e\xed\xec\x11\x79\x60\x19\x71\x86\xfa\xb2\xc2\xb8\x7c\xbf\x04\x53\xee\x87\x91\x9a\xd3\xb5\xf5\xa9\xd5\x78\xbc\x5c\x0f\xad\xea\x3f\x22\xe8\xac\xa8\x3b\x37\xb1\x08\x90\x18\x6a\x03\xb2\x0c\x9f\x8c\x54\x7c\x62\x7b\xad\xbc\xfd\xf9\x7a\x7d\xb9\x47\x86\xad\xe6\x3d\x89\x97\xd9\x88\xa0\x93\x12\x43\xca\xfa\x0b\x6e\x15\xa7\x8b\x35\x7a\xb1\x90\x4f\xe1\x53\x96\x6e\xbe\x88\xcd\x68\x6a\x12\x0d\x48\xb9\x61\x68\x11\x2f\x50\xd7\x9a\x6e\xff\xb5\xf9\xc0\x76\x39\x38\xf2\xc9\x99\x4d\x4d\x5d\xce\x0d\xff\x76\xf3\xfb\xef\x1f\x77\xc4\xfc\x83\x8f\x33\x9d\x75\x0f\x43\x38\x73\xe2\x28\x0e\x67\x5e\x79\xfc\x63\xf3\x5d\x08\x7b\x72\x5d\x7b\xb6\x13\xa4\xc5\xed\x12\x7b\xae\x8c\xa8\x4e\xb5\x28\x89\x13\x37\x93\xc4\x73\x03\x67\xef\x1d\x15\xd9\xda\x73\xb6\x90\x78\xec\x30\xce\xa1\xc3\x74\xfd\xe2\x5c\x76\x29\x61\x71\xbf\x5f\xec\xcd\xb7\xab\xea\x9c\x94\x11\x87\xb7\xc5\x35\x10\x07\xf7\x01\xff\xfe\x8f\xfc\x40\xcb\xae\xfa\xce\xba\xaf\x65\xfd\xb2\x89\xfc\x44\xd2\xc1\x1b\x27\x76\xaf\x4c\xd9\xbd\xd6\x85\x2a\x2e\xb4\xb8\xdf\xbc\x2e\x43\xb2\x0c\xe8\x48\x4e\xfa\x41\x17\xbc\xec\xbb\xa9\xa5\xf6\x61\x37\xb5\x34\xcb\x2b\x1e\xf2\xb2\xef\xe3\x10\x3a\x45\x07\xf9\x4e\x90\x9f\xa6\x75\x33\xcf\xde\xd9\xd6\xec\xd9\x9b\x5a\xdb\xf7\xe6\x82\xfc\x1c\x9b\x38\xbc\xba\xbf\x12\x1d\xc3\x76\xe2\x3d\x1a\x3d\x56\xaf\x03\xe7\x7c\xd7\xcf\xf7\x5e\x24\x6f\xe3\x5c\xe7\xea\x0c\xe7\x9b\x64\x49\xc5\xb1\xbd\xfb\xcd\xe3\x7b\x48\x5a\x92\x99\xc5\xfb\x00\x7e\x3b\xe7\x98\x1f\xf6\xed\xca\x0b\x6c\xe7\x65\x30\x3e\x2b\xf9\xcd\xdd\x1e\x3c\xfb\x90\x0b\x6e\x96\xaf\x42\xd8\xb9\xf9\x60\xdb\x8f\x2e\x33\xe1\xa5\xf7\x7d\xbe\x7e\xce\xf0\xda\x03\xdd\xf7\x89\x17\x2b\xd5\xcf\xbc\xb2\xfe\xd4\x8d\x3f\x89\x33\x73\xe2\x02\xdc\xf7\xdc\xa2\x75\xf2\xa4\xea\x75\x62\x85\xd1\x91\x7b\xee\x2e\xf7\x9d\x1d\x71\xfb\x6e\xd9\x73\x66\x06\xcc\x72\x03\xa1\xf3\xdb\xd1\x82\xfc\xd6\xa2\xeb\xa1\xdc\xd8\x77\x21\xc9\xf5\xdc\x0b\xec\x70\x5e\xbe\xe9\xe3\x7a\x28\xe3\xfb\x93\xa5\x46\xec\x3a\x69\x75\xaa\x4d\xbe\xe8\xea\xad\xab\xe2\xf6\x11\x2f\x75\x62\xcf\x38\x1c\xe5\x0c\x08\x07\xe3\xa3\xc7\x62\x4f\x1c\x25\x46\xbd\x6b\xa4\x61\x7e\x44\xb5\xe7\x24\x89\x3c\x31\x82\x53\x77\x9b\xa4\x93\xd8\x49\x26\x21\x44\xdd\x5a\xc3\x4e\x24\x6e\xda\x85\xc9\x1a\x70\xb8\x6e\x4a\x41\x06\xe1\xa9\x5b\xd7\x4a\x17\xed\xae\xae\x19\xa1\x26\x8e\x35\x3d\x79\xf5\x8a\x93\xc6\x9e\x55\xbe\x22\xe5\xba\xed\x45\xcd\x99\xe1\x41\xc3\xf4\x20\xb2\xd3\xb3\x33\x27\x91\x91\x5f\x92\x73\xed\x2f\x3b\x2e\x38\xb3\xe3\x36\xb5\xe5\xf9\x4e\xd3\x75\x63\xc7\x5d\xc2\x54\x73\xe6\xc4\x67\xdc\x21\x53\x76\x7f\x18\x54\x26\x54\xbc\x69\x43\xae\xd4\x4f\x55\xcf\xdf\x7a\x2e\x7b\x8f\x2b\x5c\x5d\xd4\x57\xd7\xa1\x6d\xa4\xc6\xdd\x2e\xa0\x15\x67\x9c\xfb\x61\xe0\xa5\x61\x7c\x27\x79\x81\x0b\x9d\xca\x21\xfa\x19\x4c\xbd\x08\x3a\xfd\x5c\xd5\x54\x65\xe3\x67\xb1\x4f\x23\x4b\xc3\xbe\x97\x7a\xae\x91\x3a\x87\x41\xc5\xb0\x2e\x7e\xdf\x45\x91\xb5\x3c\xe5\xb8\x7d\x29\xc3\x06\xe4\x1f\x7b\x89\x50\x8e\xfd\x2b\x6d\xb0\x41\xe2\xb9\x93\x34\xb9\x5f\x93\x5e\xa1\x55\x71\x89\xce\xad\xe1\x9e\xb8\x4b\x6c\xee\x98\xdd\x30\x9c\xee\x74\xcc\x99\x54\xfe\xbc\xbb\x03\xd7\xb9\xa8\xb1\xe6\x35\xb7\x06\x74\xe2\xf4\x28\x09\x3e\x23\xca\x2d\xd5\x50\x38\x59\x13\xc9\x3c\xca\x48\x5d\x18\x9a\xc6\xce\xcb\xd8\x3e\x2a\x0a\x3e\x7e\x24\x69\x7d\x63\x40\x3a\x87\x23\xbd\x21\xf4\xee\xb9\x8d\xf5\x97\x0d\xbd\x07\x82\xea\x76\xec\xfd\xde\xfd\xb3\x87\xde\xef\x0f\x5f\xa1\xf7\x2b\xf4\x7e\x85\xde\xff\xbc\xd0\x6b\x3b\xf9\x45\x2b\xf6\x57\xd8\x5d\x3e\xfe\x29\xc3\xee\x9f\x6f\xc4\xfb\x35\xe0\xad\x9d\xba\xf9\xfe\x8f\x8e\xba\xe0\x2b\xea\xee\x7c\xbe\xa2\xee\x21\x7d\x7d\x45\xdd\xd5\xe2\x4b\xe0\xa4\x5f\x11\x77\xf9\xf8\xc7\x45\xdc\xed\xce\x38\x79\x1f\xd1\xb7\xab\x9b\xfb\x43\xeb\x45\xdb\x0b\x44\x9b\xb5\x6f\x87\x56\xe6\x3b\x41\xda\x6e\xdd\x6f\x49\x4e\xb6\x96\xdd\x0e\x5c\x84\x74\x75\x43\x3e\x60\x0f\x24\x61\x36\x6e\x1f\x1e\x08\xec\xb6\x6e\x8e\x1b\xb7\x46\xad\xd6\xb8\x1d\xe3\x0d\x87\x7c\xb0\x1e\x1c\xa2\x61\xdc\x5c\x7d\xbb\xba\x69\x79\x10\x7a\x81\x7b\x75\x7f\xb5\x2a\xf5\xaa\x14\x77\x45\x85\x41\xf1\x8a\x9b\x70\xdf\x32\xd4\x1e\x9b\x3a\x52\xf3\xfb\x7c\x3d\xd6\x76\xe2\x64\x7b\xb5\x6c\x5b\xec\x49\x6e\x83\xb8\xc9\x91\xab\x48\xdf\xab\xbe\xbd\x4b\x5a\xa8\xce\x6d\x67\xec\x05\x5e\x71\xef\xd1\x72\x59\x66\x1b\x6d\x4e\x2d\x13\xae\x84\xe4\x06\xd9\x30\x6d\xd2\xb2\x1f\x1f\x6f\xc7\x4e\xbd\x71\x5b\x27\xf0\xef\xb7\x64\xed\xd1\xbc\x1d\x93\x0f\xf5\xda\x83\x83\x37\xea\x0d\xec\xc0\x2a\x5b\xb4\xf1\xba\xf3\xeb\xbf\x9f\xd7\xe9\xc7\x45\x2d\x63\xda\xd6\xba\xe4\xf1\xd5\xad\x0f\xc4\x00\xf2\x16\xc3\x6f\xa3\xd8\x99\x79\xce\xfc\x63\xb0\xe0\xfd\xe6\xf0\x4e\x76\x7e\x0a\xd6\x0f\x81\x78\xbe\x32\xb7\x59\xc3\xc3\x17\x04\x9e\xe1\x9c\x15\x28\xda\x41\xa2\x87\xc1\xf1\x37\x54\x7f\x3a\xd6\x37\x3e\x68\x69\xcf\x28\xae\x16\x96\x4a\x5a\x78\x88\x90\x97\x17\x10\xe7\x77\xb9\x1d\x19\xd7\xe4\x89\x71\xec\x2e\xff\x77\x4f\xd4\xf7\x53\xb8\xdd\x9d\x29\xfb\x48\x51\x79\xb3\xf6\x67\xbf\x1d\x6b\xa3\x69\x48\xbf\xeb\xd5\x3f\xc5\xe5\x0b\x8b\x90\x1c\x2b\x43\x83\xc5\xdc\x00\x3f\xf0\x35\x6e\xfb\xc4\x57\x51\x38\x48\x4e\x50\xab\x65\x51\x67\xbf\x8f\xf9\xd4\x15\xc3\x05\xa4\x75\x02\x3b\x0a\xbd\xe0\x48\xc7\xac\x7f\xce\xbc\x44\xb7\x94\xbd\xe9\x6f\x4f\xce\x02\x9c\xf1\x46\xb8\xa5\x90\xca\xff\xde\x76\xb9\xf7\xf5\xff\x3f\x6f\xa3\xd6\x19\xf7\x4e\x9f\xae\xe7\x7b\xd4\x91\x0f\x3e\xa8\x30\xf1\xc3\xa4\xdd\xfa\x65\x74\x72\x34\xc5\xe1\xfc\x67\xbe\xf1\xad\xbc\xf2\xff\x43\x47\x0b\xe5\x20\xe1\xfc\x78\xb0\xb5\x55\xe8\xe7\x7c\xbd\xd6\x4f\x1c\x2a\xea\x08\x6b\x89\x5f\x36\x54\x94\xd5\xff\x79\x43\x45\xe4\xfc\x01\xd1\x22\x8a\xbd\x99\x91\x2e\xa3\x45\x59\xcf\xfc\x9d\x61\x85\xae\xaf\xdb\xc5\xcb\xac\x0e\xbc\x25\xff\x44\x19\x1b\xaf\x7d\x74\x3e\x0b\x07\x22\xa7\x98\x2f\xc0\x76\xc7\xd5\x5f\x70\x60\x40\x18\xce\xc1\x46\x63\x8b\xbb\xb0\x8f\xcc\x60\xa1\x2c\x74\x18\xcf\x8d\xd8\x76\x6c\x39\x36\xc6\x63\xcf\x3a\x91\x9c\x31\x52\x67\x6e\x2c\xe4\xd8\x08\x12\x2f\xad\xf6\x22\xef\x49\x9d\x25\x8e\xe8\xf8\x61\xea\x94\x39\x92\x23\x69\xe3\x3c\xe1\x66\xe5\x0f\x43\xda\x25\x2f\x86\x59\xf9\x5e\x65\x41\x7b\x67\xef\xcf\xdb\x3d\x5c\x86\xa4\xfb\xc8\x71\x62\x2f\x70\x6f\x3f\xc8\x34\xb7\xfe\x1e\x16\xd2\x3f\x73\x43\xe2\xc7\xbf\xe9\xf1\xa3\xf7\xe9\xbe\xbd\x1b\xff\x71\x99\x83\x7f\x79\xe0\x8f\xf1\xc0\xd9\xa1\xb7\x30\x9d\xed\x7a\x6b\x9d\xbf\xee\x81\xef\x22\x89\xff\xb9\xae\xb7\xe5\x2a\x1f\xe5\x81\x7b\xbb\xf1\xf3\x5c\x6f\xea\x05\xb9\xc9\x31\xf9\x3c\xce\x6a\x0a\xec\xad\x73\xad\x56\x18\x24\x5e\x92\x3a\x81\xb5\x38\xf5\xbe\x54\xbb\x78\xff\x39\xb5\xca\xd1\x73\x66\x0e\x2c\x56\xc8\xe2\x30\x70\xcf\x5b\x4b\x3a\x3d\xe2\x3b\xc2\x86\xab\xcc\xd5\x32\xe3\x39\x4a\xdc\xa8\xd0\x2e\xd7\xda\x53\xc5\xad\x79\xc2\xc1\x78\xec\xc4\x6b\x8b\x81\x07\xdf\x60\xeb\x45\xa2\x11\xb8\x0e\xed\xc1\xb4\x78\xdd\xce\x72\x61\x07\xc7\xea\x77\x75\xe2\x0e\x27\x1b\x77\x24\xf1\xad\x8e\xdd\x3d\x7c\xbf\x6b\xd4\xef\xf0\x1a\xfe\xad\x41\xdc\xe1\x0f\xdf\xef\xbe\xdf\xd5\xb0\xfc\xfb\x77\xf2\xae\x81\xdd\xd5\x1b\xf9\x1f\x8f\x0f\x77\xf8\x63\xfd\x8e\xf8\x7e\xf3\xed\xca\x1b\xff\xe6\xfc\x2b\x33\x60\xb2\xb1\x34\xe1\xbc\xa4\xb1\x51\x8d\xba\xd9\x61\x92\xaf\x4b\x14\xff\x7d\xbb\xba\xf9\x86\xbe\x1d\x4f\xbe\xdf\x07\xbc\x64\x13\xfa\x8a\x36\x75\x4e\x6d\x2b\xd8\xf4\x0d\x31\x83\x97\xbd\x3d\xfe\x87\xbe\xe9\xee\x4c\x9b\x28\x46\x03\x4f\xce\xa2\x65\x24\x8e\xdd\x77\x52\x03\x99\x89\x1a\x7b\xa9\xb3\x11\xd4\xce\x42\xed\xbf\x9f\x9c\x96\x7e\xdf\x62\xcc\xc7\x90\xfa\x72\xac\xb5\x07\x36\x4a\x2c\xe8\xbc\x44\x4e\xec\x95\xef\x71\xbd\xa6\xc2\xd8\xb9\xfa\x4d\x12\x7a\xbf\xff\x98\xd5\x04\xf2\x16\x7b\xfc\xc1\x7c\x6d\x13\xdd\x3f\x80\x22\x55\x95\x39\x49\x2d\x6e\x9a\xe2\xe0\x66\x1f\x4b\xd8\x63\xab\x61\x54\x01\xec\x01\x99\xe9\x24\x0e\x33\x77\x12\x65\xf9\x3b\xa9\x1a\x18\x76\x29\xfb\x78\xd3\x62\x29\xd2\x25\x6a\xc5\x7b\x17\x1a\x93\x7f\xc1\x76\xf9\xdb\xcf\x37\x7a\xfd\x38\x8b\xfc\xc3\xd7\xb5\x2e\xb7\xd6\x66\xb2\x08\xac\x41\xbe\xc7\xe9\xc8\xbb\xe5\xae\x23\x23\x4e\x73\xb5\x3f\x39\x87\x79\x47\x99\x32\x9d\x9c\x9e\x22\xbe\xbe\xf7\x8e\xcc\xd6\x1c\xd8\x7e\x73\xb5\x4e\xa7\xba\x46\x32\x39\x10\x25\x0e\xb4\xa1\x44\x42\x39\x45\x44\xe8\x3b\x56\x7f\xc4\xb0\xb7\xfa\xe8\x27\xba\x1a\xfa\xbe\xd5\x19\x1f\xea\x7e\xf7\x56\x18\xa4\x86\x17\xec\x6e\xc8\xfa\x93\x78\xe2\x86\x36\xce\x71\xcb\x43\x18\x78\x61\xf9\x3f\x3b\x12\x94\xdb\x4f\xfe\x23\x10\xe0\x27\xf3\xeb\x52\xb5\x5f\xfe\xfc\xe5\xcf\x7b\x6c\xec\x93\xfc\xb9\xdc\x5b\xfa\xe7\x09\xe9\xb7\xf8\x4f\xe6\xf6\x55\x0f\x7c\xf9\xfd\x97\xdf\xef\x31\xb2\x4f\xf2\xfb\x41\xe4\x04\xd2\xc4\x1b\xa7\x54\xb1\x43\xed\x07\x02\xc0\x86\xc4\x1f\x0c\x05\x59\xe0\xfd\x2b\x73\x9e\x9c\x53\x93\xa3\x9b\x89\x3f\xe4\x15\xe1\xe7\xa9\x67\x99\xfc\x7e\x7a\x4c\x3b\xd5\xe7\xf8\x86\x98\x13\x6b\xe6\x9f\x50\xe9\x72\xbf\xe3\xc6\x4e\x7e\xd6\x3e\xda\xd1\xd5\xe7\xe7\x6b\x4a\xf1\x0a\xe4\x8f\xa8\xfc\x61\x2b\xff\x25\x98\xe9\x0e\x58\x7c\xc5\xaa\xaf\x58\xb5\xc7\xda\x3e\x29\x56\x0d\xf3\x77\x85\x7f\x31\xd4\xcd\x3c\x6f\x9c\x14\xae\xff\xe0\x49\x61\xf4\xbd\xe8\xb8\x2f\xb0\xf8\x02\x8b\x3d\x26\xf6\x49\x60\x21\xad\x1d\xf1\xf9\xcf\x18\xd5\xfe\x64\x64\x60\x43\xc1\x5f\xbe\xfd\xe5\xdb\x7b\x2c\xed\xd0\x51\xcc\x0f\xbc\x64\xf3\x0c\xbb\x3b\x7a\xe5\xe7\xfb\x0f\x31\xae\xea\xff\x43\x0f\x20\x1a\x96\x6d\x3f\x10\xc6\xc3\x6d\xad\xf6\xd8\xb8\xad\x3f\x3a\xe3\x5b\xd3\xae\x13\xb7\xe3\xef\xd8\xf7\xb1\x69\x3c\xe2\x86\xf3\x70\xe6\x01\xc4\xd3\x5a\xff\x95\x0e\x1f\x7e\x88\x21\xde\xd0\xc8\xc8\xca\xdd\x02\x9b\xe7\x69\xff\xb4\x16\x57\xb7\xc9\x07\x93\x7c\x34\x6f\x71\xbb\x3e\xbe\xad\x3f\x3c\x3e\xdc\x1a\x04\x89\xdf\x5a\xdf\x1f\x1e\x6b\x75\x9b\xc0\x89\x8b\x2c\x6e\xfc\xa7\xb2\xb8\x5f\xe1\x14\xfa\xf9\x28\xfc\x75\xf6\xfc\x17\x3f\x7b\xfe\x9f\x05\xfd\xbf\xea\xfe\x9c\xcf\x38\x59\x7e\x39\x80\x6c\x9f\x37\x5f\xdd\x57\xff\x21\x27\xde\x7f\xaf\xa2\x6b\x9b\x97\xae\x50\x01\x6f\x87\x8c\xed\x1a\xfe\x38\x9c\xf8\x6c\xdd\xfc\x50\x1c\x31\x9d\xb1\x33\x36\x30\xfc\x96\x30\x08\xf2\xb6\x8e\x93\x0f\xb7\x8f\x35\xe3\xf1\x96\x78\x20\xc6\xe3\x5a\xcd\x72\x6a\x78\xfd\x4f\x19\xd0\x3f\x74\xe7\xe9\xe7\x99\xca\x21\x9c\xf9\x4b\xf1\xf7\xbf\xff\xf2\x7f\x01\x00\x00\xff\xff\x19\xe4\xd3\xb1\x55\xc7\x00\x00")
+var _rpProductionJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x7d\x69\x6f\xea\x4a\xb6\xf6\xf7\xfe\x15\x51\xde\x2b\xe5\x9c\xf7\xee\x24\xb6\x81\x9d\xb8\xa5\xfe\x00\x06\x1b\x3b\x60\xf0\x54\x36\xee\xdb\x6a\x79\xc2\x38\x94\x87\xf6\x00\x21\x57\xfd\xdf\xaf\xca\x03\xf3\x14\x92\xec\xb3\x77\x9f\xb0\xa5\x2d\x82\xab\x56\x55\xad\x5a\xeb\x59\x4f\x8d\xfe\xdf\xbf\x5c\x5d\x5d\x5d\x5d\xff\x57\x62\x4d\x1c\xdf\xb8\xfe\xeb\xd5\xf5\x24\x4d\xa3\xe4\xaf\xf7\xf7\xc5\x2f\x77\xbe\x11\x18\xae\xe3\x3b\x41\x7a\x67\xbc\x66\xb1\x73\x67\x85\x7e\xf9\x2c\xb9\x27\x30\xbc\x71\x8b\xe1\xb7\x18\x7e\x6f\x3b\x11\x0c\x17\x28\x9d\xec\xf8\x11\x34\x52\xe7\xee\x39\x09\x83\xff\x77\xfd\xad\x28\xc1\x0a\x83\xd4\x09\x52\xe0\xc4\x89\x17\x06\xa8\x20\xfc\x0e\x43\xff\xaa\x04\x91\x11\x1b\xbe\x93\x3a\x71\x72\xfd\xd7\xab\xa2\x5a\xf9\xef\x86\x15\x8b\x4e\x12\x66\xb1\xe5\xb0\xf6\xc6\xa3\xfc\x71\xba\x88\x1c\x24\x2d\x49\x63\x2f\x70\xaf\x97\x0f\xff\xfd\x6d\x4d\x84\xed\x7b\x41\x33\xf2\x28\xa3\x95\x05\x36\x74\xde\x29\x05\x7a\x4e\x90\x52\x4e\x9c\x52\xa1\xef\x87\x01\x6f\xf8\x17\x4a\xb4\x60\x96\xa4\x4e\x3c\x34\x62\x27\x48\xdb\xa1\x6f\x78\xef\x10\x66\x1b\xa9\x61\x1a\x89\xd3\xb4\xac\x30\x0b\xd2\xcb\x05\x39\x2f\x69\x6c\x50\x61\xe2\x87\x49\xbb\xc5\x0e\x93\x93\x52\xbe\x6d\x3e\xb5\x9d\xb1\x91\xc1\x14\x18\x30\xcb\x53\xed\x2f\x65\x1c\x49\x4e\x3c\xf3\x2c\x67\x18\x7b\x81\xe5\x45\x06\xbc\xb4\x77\xc7\x19\x84\xed\xdc\xfa\x0e\xe7\x37\xc3\x10\x9e\xa8\xe7\xd8\x80\x89\xb3\xb7\x80\xa9\xb3\x98\xa1\x94\xc3\xd8\x19\x7b\x2f\x97\x55\xd2\xb7\x7d\x3a\xce\x5d\xc0\x56\x62\x78\xa9\x8c\xc4\xa6\xc2\x60\xec\xb9\x2b\x2f\xba\x50\x4c\x27\x98\x79\x71\x18\x20\x77\xbd\x4c\x48\x14\xc6\xa9\x01\x9b\x96\xe5\x24\x09\x13\x87\x59\xc4\xda\xa7\x0d\xe5\x88\xa4\xc2\xa9\x2e\x35\x81\x42\x46\x07\x3a\x33\x23\x75\xec\xf7\xd5\x27\x8e\x58\xdf\x70\x2f\xf4\x9d\x38\xea\x87\xf6\xe9\xbc\x17\x79\x4c\x1c\x7d\x0c\x50\xc4\x1f\xe6\x79\x49\x32\x19\x66\x26\xf4\xac\x27\xe7\x88\xef\x1d\x95\x90\x86\xb1\xe1\x56\x98\x55\xb4\xec\x42\x49\x99\x99\x58\xb1\x17\xa5\x5e\x18\x54\xf1\x22\x37\x84\xcb\x15\x35\xf3\x25\xef\xf5\xdd\x9d\x29\xa5\x46\x60\x1b\xb1\xfd\xcf\x36\x91\xfc\x73\x56\x3b\x54\x54\x92\xbc\xb1\xa2\x7f\x59\x93\x71\x1d\x97\x2d\x46\x36\xff\xf7\x65\x9a\x2d\x51\xc9\x34\xdb\x91\x9f\x3f\x08\x8a\x92\x97\x55\xbd\xde\x48\xf2\xef\xad\x06\x46\x71\x18\x39\x71\xea\x39\xbb\x0e\x56\x3c\xcf\x4d\x82\x1d\x36\x21\x0c\x2d\x03\xf5\x47\xdf\x49\x27\xa1\x5d\x96\x90\x7a\xd6\x71\xf9\x55\x6d\xe2\xe8\x36\xf2\xa2\x6d\xf5\x56\xfa\xe8\x7b\x56\x1c\x26\xe1\x38\xbd\xe3\x9d\x74\x1e\xc6\xd3\xfb\x65\xb9\xb6\x1d\x3b\x49\xe2\x24\xdb\x59\xab\xea\xa0\xec\x7f\x8f\xd7\x6d\xe4\xb7\xdf\xef\xaa\x87\xff\xd8\xce\x65\x85\x81\xed\x2d\xb3\xad\x58\xca\x6f\x37\xab\xc0\x73\xf3\xfb\x4e\x36\x23\xf2\xd6\xb8\x0e\x81\xe1\xe4\x2d\xf6\x70\x8b\xe1\x7b\xfb\xff\xd7\xee\xa8\x02\x7d\xbf\x3a\xeb\x53\x3b\x6b\x5c\xb2\x07\x76\x58\x50\x80\x2c\xce\x35\xb0\xe9\xf0\xeb\x9f\x5d\x19\xe7\x96\xb5\x99\x76\xb3\xa3\x4e\x66\xc8\x33\x79\xf6\x46\xb7\xb1\xf6\x6f\x37\x67\x98\xc0\xcd\xb7\xab\x9b\xc2\xe9\x77\xbb\x68\x6f\x31\xa9\xe1\xa2\x0a\x05\x19\x84\x47\x13\xff\xfb\xe0\xd3\x7f\x1f\x2e\x65\x1d\x87\x2a\xe5\x5f\xef\x4d\x7d\x40\xc8\xaf\xd9\x01\x2b\x67\xfe\xa9\x3a\xa1\xac\xd6\x89\x8e\xd8\xf9\xf5\x1f\xbb\xa2\xaf\x4d\xc3\x9a\x3a\x81\x5d\xb6\x7a\x18\x86\xf0\x22\x27\x5a\x33\x8f\x52\xe2\x7b\x2a\x05\x43\xc3\x6e\x19\xd0\x08\x2c\x2f\x70\xc5\x0c\x3a\x9f\xee\xd8\x07\x00\xe5\x03\xed\x6b\xd5\x26\x27\x4e\xee\x0f\x01\x58\xe9\xf5\xd0\x2c\xbf\x54\xe9\x90\xf9\x1d\x37\xa8\xe3\xc6\xb9\xa7\x9f\x3f\xad\x6d\x7b\x4c\x6a\xbb\x59\x65\x92\x77\xb7\x2a\x8a\x43\x73\x97\x26\x7e\x54\x43\x72\xe9\x3b\x75\xcf\x7f\xfd\x88\x9a\xa7\xa1\x95\xf7\xc2\xb5\x6c\x6d\x73\x85\x9d\xe4\xa8\x62\x6d\x0f\xd1\x5e\x33\xab\x22\x7a\xbb\xe0\xd6\xa7\xb2\x56\x26\x34\x0c\x63\x34\xbc\xad\xd7\x6b\xe7\x99\xca\x2a\xfd\x7b\x03\x06\x34\xe3\x0c\x3a\x7f\x44\xb8\xf8\x29\xdd\x7a\x0b\xbd\xbf\x5c\x7b\x6f\xed\x7e\xb8\x6b\x57\xb1\x1e\x3d\xb9\xcd\xe7\x7d\xff\x4c\x4e\x5e\xff\x08\x42\xf2\xe5\xe8\x5f\x8e\xfe\xe6\x56\xfd\xb1\x8e\x9e\x24\x93\x5f\xd7\xcd\x09\xe2\x4d\x5e\x4e\x10\x04\xf1\x71\x6e\x8e\x54\xf7\x1e\x92\x5f\xf4\xcc\xa7\x8f\xd8\xd7\xfa\xa6\x8b\x40\xfd\x94\x8a\xa3\x73\x11\x34\xc8\x7c\xd3\x89\x07\xe3\x61\xd5\x8e\x53\x9d\x11\x3b\xff\xca\x9c\x24\x1d\x1a\xe9\x04\xd5\xe6\x7e\xe2\x18\x30\x9d\xbc\xde\xc7\x8e\x61\x2f\x0e\x5b\xe0\x99\x24\xab\x70\xa3\x3f\x62\x48\x7e\xb1\x86\xeb\xbf\x90\x86\x77\xd9\xc1\x1f\xad\xeb\x33\x90\x26\x5a\xf9\xfd\x9b\x55\xfd\x71\xda\x7a\x0b\x4e\x9c\x35\xc5\x9a\x23\xf9\xf9\xb3\xab\x1b\x01\xe1\xe7\x9c\x59\xdd\x59\x35\x89\x9c\xc0\x4e\x06\xc1\x5e\x74\x7c\xe7\x54\xe2\x5f\xf6\x2b\xfc\x1d\x73\xba\x1b\x0a\x28\xd6\x89\xf6\x4f\x97\x5d\xa7\x9e\x13\x6f\xcc\x02\xef\x49\x63\x19\x91\x61\x79\xe9\xe2\xfa\xaf\x57\xb5\x77\x4d\x12\x67\x91\x1b\x1b\xb6\x33\x0c\xa1\x67\xed\xae\xc8\x2d\x93\xf9\xc5\x1a\xe5\x75\xdf\x08\x32\x03\xee\x5a\xea\x1e\x7b\xbf\x9e\x79\x71\x9a\x19\xb0\x6f\x58\x13\x2f\x70\x86\x71\x38\xf6\xf6\x6c\xa4\x58\x26\x0f\x93\x53\x49\x8a\x96\x87\x7e\x94\xa5\x4e\xcc\x1b\xbe\xb3\x5c\x5e\xbf\xfe\xbb\x15\x06\x96\x91\xfe\x86\xba\xf0\xe6\xdb\xd5\xa6\xae\x8b\x85\xb2\x9b\xdf\xbf\x5d\xdd\xdc\x1e\x9f\xa2\x2c\x76\x6c\x28\x89\x13\x57\xdd\x66\xc1\x30\xb3\x6f\xb3\xc4\x89\x8f\x65\x83\x5e\x90\xbd\xbc\x8d\x54\x5f\xdb\x5e\x62\x98\xd0\x19\x1a\x49\x32\x0f\x63\xbb\x99\xa5\x13\x27\x48\xbd\xa5\xa7\xa5\x71\xe6\x9c\x00\x24\x04\x1a\x67\x71\xc2\xa8\x5a\x73\x3d\x4c\x25\xb6\x3f\xa7\xa5\xae\xa4\x2f\x43\x49\xe8\x3b\xf7\x2b\x8d\xdd\xdf\x25\xc9\xe4\xde\xc8\xd2\x49\x18\x7b\xaf\x8e\xfd\xcf\x29\xaa\xc0\xe9\x09\xe2\xa5\xe0\xa9\xb3\x68\x1b\xa9\xb1\xe3\x3e\xeb\xab\xc8\x27\x89\x69\xf5\x39\x3c\x9b\x5c\x7d\xfe\x71\xe9\x6c\xf4\x5b\x82\x5c\xb5\x80\x7d\x96\xb1\x7b\xbe\xe1\x3a\xa2\x33\x76\x62\x27\xb0\x4e\xf3\xff\xa2\x9f\x93\x49\x81\x1f\xa2\x63\x77\x8d\x93\x6c\x39\x1c\x8f\xcb\xe4\xdd\x4e\xef\x54\xe2\x02\xe5\xae\x1f\x6e\x7b\xa0\x7f\x2a\xed\x6c\x05\xe2\x0f\x77\xe4\x1d\x81\x11\x18\x8e\x61\x38\xfe\xfd\x32\x8e\x11\x26\x6d\x2f\x99\x9e\x56\x81\x15\x3b\x46\xea\x0c\xa2\x2a\xf0\xd0\x71\xe8\x17\xdb\x33\x4e\xd4\xb7\xd8\xb6\x66\x9f\x55\xca\xd5\xd5\xce\x4e\x04\xb9\x0c\xad\xc3\xd8\xf1\xbd\xcc\xff\x67\x4f\x94\x4e\x0c\x98\x3e\xc8\x9e\x82\x22\xa2\x9d\x65\x4f\x05\xd7\x1b\x9e\x35\x98\xfc\x91\x93\xc1\x47\xe9\x52\x51\x12\x1b\xa4\x4e\x3c\x36\x2c\xe7\xcc\xc5\xcc\xea\x73\x46\x47\xae\x51\x26\x14\x2c\x6e\x03\xcf\x3a\x67\x19\xeb\x0d\x9c\x74\x2d\x8f\xe7\x1b\xf1\xe2\x2c\x78\x5f\x66\xf2\xa2\x37\xb6\x79\xfd\x73\x3e\x8e\x5f\xed\xd3\x85\x17\x59\x79\xd9\x6f\x80\xed\xab\x0b\x95\xb3\x91\x3f\xc9\xcc\xc0\xd9\xdd\x69\x76\x76\xfe\xb3\x8c\xb7\x64\x28\xe5\x9f\xc9\x7d\x51\x68\x65\xbf\xb3\xc0\x49\xcb\xaf\xc5\x83\xb3\x43\xcd\xfa\xe7\xc4\xbc\xc8\xde\xca\x5f\x62\x25\x9b\x02\x36\xd9\xed\xdb\xa7\xfb\xf6\x4a\xdd\xb6\x8d\xc8\x8b\x7e\x8c\x3e\xd6\x41\xa6\xf5\x86\xb5\xd8\x53\x9f\xcb\xf4\x70\xf5\xc7\x4f\x17\x1e\xfa\x9c\xe6\x38\xdb\x9f\xe3\x9c\xe7\x72\xf9\xe7\xa5\x3c\x5d\xfa\x71\x39\x87\x9f\xee\x97\x7c\x28\x86\xda\x9e\xe1\x06\x61\x92\x7a\xd6\x79\x83\x10\x33\x0c\xd3\xf6\x2a\xcf\xe9\x58\xea\x04\x88\xeb\xdb\x67\xd2\xfa\x82\x58\x28\xb1\xb7\x31\xb4\xa9\xb6\xd9\x6f\x8d\x6f\xf6\x6d\x88\xcc\xc7\x3a\xf7\xc7\x43\xee\x9b\x14\xe4\xbc\xa4\x4e\x80\xe8\xdc\x59\xea\x59\xa6\xfe\x94\xf0\x6c\x25\xa7\xb8\xdc\xd5\xc5\xe1\x79\x9d\x3f\xaf\xfc\xb9\x99\x1f\x63\xe8\xac\x5a\x75\x66\xe0\xae\xa6\x5b\xa8\x2c\x49\x43\x5f\xca\x37\x9b\xbe\x25\x6f\xd7\x08\x6c\xe8\xc4\xeb\xd3\x21\xcb\xd3\x0f\x27\x05\x18\x59\x1a\x2a\xc5\xe8\xbe\xef\x05\xe1\x9a\x94\x37\x50\x8f\xc4\x49\x53\x2f\xc8\xb7\xeb\xfc\xef\x99\xc8\x9d\xcf\xbf\x39\x56\xea\xd8\xd2\x5a\xe6\xf3\x07\x7d\xc5\x9e\xdc\xdc\xf2\x4d\x23\x71\xbe\xd7\x7f\x2b\x1d\xa0\xf8\x4b\x0e\xa5\x7c\x5f\xeb\x6f\x37\x16\x01\x30\x96\xc2\xa1\xd3\x0c\x9f\x6e\x7e\xff\x76\xd3\x6f\xf7\x69\x71\xc0\xcb\x1d\xbe\xad\x88\xbd\xbf\xfd\x57\x99\xe1\xea\xd6\xbe\xfa\x9f\x0c\xc3\x6a\xd6\xfa\xff\x37\x37\x37\xdf\x4a\xf1\xeb\xce\xb4\xb9\xe9\xfe\xe6\xf7\xdf\xbf\xdd\xdc\xdc\xfc\xfe\x3f\xc1\x0d\x12\x2f\xb5\xa9\x01\x4f\xb3\x0c\xe8\x88\x12\x3b\xe0\x2f\x2d\x61\x6b\x4b\xfe\x4e\x21\x1d\x1e\xb0\xe2\x80\xef\x77\x78\xf9\xf2\x22\xd6\xb6\xeb\x6f\x14\xd0\xa4\x44\xb1\x23\x0d\x14\x91\xea\xb0\xed\xcb\xc4\x6f\x1c\xaf\xd9\x10\x4e\xf5\x14\x49\xee\x88\xc3\xa6\xd8\xe1\xe5\xf6\xa0\xdf\x64\x79\xbe\xd9\xef\x5c\x56\xcc\x81\xa3\x2e\x1b\x05\x0e\x07\xa2\xdc\xec\x35\x29\xaa\x23\x49\x8c\x38\x50\x86\x6c\x5b\xba\xac\xb4\x7d\xa7\x13\xf6\x14\x45\xf5\xd8\x0e\x2f\x5f\xaa\xb9\xcd\x83\x0b\x7b\xc4\x77\x7a\x1d\xd0\x94\x3b\xed\x8f\x68\xcb\xf6\xd9\x86\x8d\xe2\xc4\x21\xdb\x6f\x32\x17\xf6\x4c\x79\xde\x61\x4b\x60\x7f\xd0\xbe\x58\x5e\x3f\xb4\xb7\xc5\x7d\x8c\x11\xed\x9e\x80\xd8\xf4\x86\x76\x9f\xe5\x9b\x43\xb6\xe8\x56\xaa\x23\xca\xd4\xa0\xdf\x1f\xbc\xa3\xc0\x63\x47\xbe\x36\x8a\x6e\x37\xe5\x66\xab\x29\x75\x9a\x14\x35\x50\x78\xf9\xf2\x12\xf7\x9c\xe2\xda\x28\xe8\xa9\x33\x02\x4d\xa5\x27\x0f\xc5\x0e\xcd\x6a\x97\x95\xb1\x79\x9e\x69\xbf\x0a\x9b\x2d\x85\x6f\xf7\x3a\x7f\x43\x22\xf6\x6a\xa4\x3c\x4a\x77\x93\x67\x2e\xd1\xae\x5f\x98\xe1\xcd\xcd\xbd\xeb\x04\xce\xcc\xf0\x6d\xff\xaf\xbe\x81\xfc\xfe\x9f\x0d\xa2\x4c\xd5\x1b\x50\x4d\xf9\x4d\x78\x7b\x68\x89\x62\xad\xda\x92\xd2\x92\x28\x91\x1d\x22\xc1\x6f\x71\xe7\xf5\x43\x23\xbf\xfd\x7e\xb7\xfe\x27\x6b\xaf\x1b\x70\x09\xb2\xb9\x27\xbf\xad\x73\xb7\x6b\x8f\xf8\xcf\x9a\x64\xf4\xdf\x76\x28\xa4\x02\x88\x9b\x52\x33\x75\xa4\x16\x6e\x31\xe2\xc4\x66\x14\xb7\xa7\xb9\x2e\xc0\xe8\xbe\xa1\x36\x70\xa7\x43\x07\xba\xda\xc0\x28\x37\x4a\x6c\x1f\xd4\x6d\x06\x64\x3a\xd5\x4c\x4d\xaa\x19\xf3\x72\x13\x8a\x90\xa3\x45\xa9\x39\xd3\x19\x40\xf4\x6a\xdc\xcc\xac\x89\x84\xbe\x20\x17\x26\x41\x62\x66\x77\xf4\xe4\x30\xfa\xab\x46\xd8\x0b\xb3\x66\xfb\xd6\xa2\x39\xdb\x27\xa7\x2f\x37\xe7\x9c\xa2\x4b\xa2\xa2\xb8\x3d\x42\x84\xb6\x57\xe4\xb7\x7d\x6b\x66\xfb\xf4\x62\x9f\x1c\xf4\x3b\xe5\x86\xcf\x2c\x43\x13\x26\x01\xa7\x2c\xc5\x41\x2b\xe0\x66\xd6\x73\xe8\xea\x0c\x8b\xb3\x0c\x58\x58\x3e\xb9\x78\xa2\xb0\xd7\x7e\x7b\x4a\x0c\xa4\xa9\xab\x07\xdc\xcc\x94\x5a\xd3\x91\x0f\x32\xdb\xc3\xfe\xdb\xac\xb5\xa0\xf9\x1c\xba\xc2\x54\xa4\xfa\xed\x66\xa3\x2f\xb5\x3a\x02\x24\x55\x11\x70\xb2\xa4\x90\x03\x0d\xc3\x39\x05\xc3\x5b\xa0\xc3\xb3\x03\xaf\xd5\x19\x69\xe2\x64\xe4\xd3\xaf\xba\xd4\x82\x66\xa0\x47\x96\x4f\x66\xa6\x0a\x32\x9b\x6a\x11\xba\xc6\xbd\x1a\x2a\x99\xb1\x0c\x1e\x59\x04\x3e\xb1\x19\x3e\x64\xdd\x68\x81\x74\xab\x7b\x45\x7d\x7b\xc4\x4b\x34\xf2\xc8\x85\xc5\x60\x33\x0d\x27\xa7\x23\x2f\x7c\xa2\x02\x6e\x8e\xd2\xf4\x54\x98\x5a\x0c\xb9\xb0\xa9\x56\x68\x77\xc5\xb9\xf5\x1a\xce\x7a\x84\x98\xf4\x7c\x1d\xea\x0c\xb9\x18\x69\xad\x85\x49\x44\x70\x54\x13\x32\xb3\xc6\x05\xbd\x5a\x0b\x1f\x79\x24\xb4\x18\x90\xf4\x70\x4e\x90\x25\xbc\xab\x74\xac\x54\xc2\x80\xde\x53\x80\x20\x2a\xf3\x94\x9f\x47\xa8\x2c\xb7\x27\xe1\x91\xa9\xb5\x66\x56\x20\xb8\x46\x57\xc4\xac\x6e\xff\x7b\x6f\x41\xce\x47\x2a\x1f\x8f\x54\x1b\x5a\x8b\x46\x6a\xa8\xfc\xc2\xac\xf1\x33\x3d\x10\xb2\x11\x41\xa6\x3d\x22\x85\x8e\xd6\x9f\x99\x2a\x7c\xb6\x7c\xf2\xd5\x24\x74\xac\xe7\xd3\xaf\xa3\xf3\x65\xfa\x66\x17\x40\x33\x10\x3d\x43\x13\x32\x43\x7d\x9c\xe9\xfe\x0b\x8e\x6c\x69\xe4\x43\xac\xe7\xa7\xd0\x11\xc2\x27\xdd\x27\x17\x2c\x43\x63\x36\x03\x52\xab\x2b\xb8\x86\x5a\x77\x9d\xd7\x4e\xd6\x7b\x06\xe4\x60\xd1\x9a\x9a\xf3\xd0\x65\xbb\x4b\x1b\x8d\xcc\x80\xc7\x46\xea\x4b\xc2\x32\x13\xcc\xee\xb6\x5e\x07\xde\xe3\x4c\x67\xe6\x99\xee\x83\xa9\x59\xe3\x26\x56\x97\x9b\x19\x3e\x78\xb6\xa9\xc6\xcc\xf2\xad\x99\xd5\x05\x5e\x8f\x00\x73\x5d\x9d\xcf\x74\xad\x05\x4d\x0a\x5f\xe8\xea\x0b\x1c\x69\x3c\xec\xa9\x2f\x13\x9b\x01\xaf\x36\x85\xd5\x7a\x7e\x63\x36\xd2\xb8\x67\x83\x6a\xe4\xed\xe3\xbc\x91\x3b\x0a\x38\x38\x52\x93\x27\x96\x6a\x45\xba\xd7\x32\xd5\x45\x73\xea\x10\x55\x5d\x45\x92\xa5\xf0\xc4\xa6\x9a\x38\x4b\xe3\xf6\x60\xd1\xc2\x0c\x06\x64\x6c\x97\x4f\x74\x15\xcc\xd9\x76\x67\x3e\x58\xb4\xa0\xd9\xe5\x21\xcb\x80\xba\xa1\x09\x6e\x5f\x4e\x5c\xdd\x9f\x3e\xe9\x0c\x99\xe9\x42\xf8\x34\x22\x68\x8c\x6d\xd7\x67\xba\x26\x3e\xf7\x6a\xa8\x8d\x8d\x85\x8e\x74\xba\x68\x4c\x7b\x04\xfd\xdd\xd6\x38\xd8\x0b\x38\x68\x31\x8f\xee\xb0\x3d\x0f\x44\x85\x64\xb8\x79\x64\x8e\xb4\x08\xb7\x7c\x25\x1d\x11\x2f\x91\x26\x44\xd9\x48\xc5\xe1\x50\x2d\xd3\xab\x7c\x62\x08\x91\x87\xda\x67\x6b\x5c\x32\x54\x57\x7a\xb2\x18\xfa\xd9\x20\xe8\x40\xd7\xfa\xd9\x66\xbf\xf2\x33\x53\x22\x1b\xb6\x8a\x97\xe5\x93\x13\x27\x00\x0b\x5d\xc2\x9f\x4d\x66\xfa\xa4\xab\x8d\xc9\xc8\x7f\x81\x7a\x1b\x6f\xe8\x5a\xff\x49\xaf\xb5\x82\x11\x31\x81\x23\x22\x21\x1d\x15\xbc\x52\x6e\x55\x27\xf0\x6c\xd6\x38\xb8\x5d\xa7\x11\x41\x2e\xf4\x8f\xaa\x93\xca\xcf\x2c\x5f\x39\x5a\x27\xd3\x7f\x7c\x42\xba\xa2\xdc\xe8\x79\xa4\x09\xee\xd0\x23\xa1\xcd\xf4\x67\x8e\x06\xd2\x42\x9f\xe4\x6b\xcf\x17\x66\x36\x23\xa4\xc8\x56\xcd\x40\x48\x73\x9b\xdc\xa3\xeb\xed\x34\xcb\xb6\x69\xe2\xb4\xa7\x16\xd8\xd8\x53\xb9\xc8\x6e\x9e\x6e\xdf\xa6\xfd\xc3\x59\x8f\xe0\x91\x7f\xcc\xac\xc5\x63\x8d\xf2\x41\x36\x52\xb9\x44\x57\x85\x42\xa7\xbe\x3d\xd7\x09\x3e\xd4\x55\x3e\x1e\x6a\x10\x5a\xf3\x88\x96\xb1\xd1\x13\xe5\xeb\x33\xcb\x6b\x4d\xec\xae\x08\x4d\xad\x85\xb1\x0c\xcc\xd8\x6e\xf2\xd2\xf3\xea\xf8\x18\xd9\x17\xf3\xf8\x84\xda\xc9\x52\x78\x03\x3d\xb3\x6a\xe2\xc4\x64\xe6\xee\x48\x8b\x5e\x75\xb5\x8f\x6c\x66\x62\xaa\x34\xc1\x32\xf4\x77\x8b\x00\xcf\x3d\x15\x9f\x99\x3e\xc4\xcc\x1a\xeb\xae\xdb\x55\x4f\x66\xb3\xbe\xdc\xcc\xfa\x52\xab\xb2\x85\x54\xef\xf2\x53\x94\x2f\xb7\x33\x8d\x87\xa3\x1a\x58\x18\x9a\xd8\x60\x19\x71\x36\x22\x52\x68\x79\x2d\x4c\xa7\xf0\x89\x4e\x20\x4c\xc4\x91\xdf\xff\x10\x3f\xb2\x82\x16\x6a\x2f\x64\x29\x21\x2c\xbf\xbf\x8c\xa4\xd6\x23\xcb\xd8\x0b\x5d\x6b\xba\x9a\x4f\x7b\x56\x20\xa4\x4f\xc2\xa6\x3d\x58\x35\xf8\x3a\xaa\x21\x8c\x15\x66\xfd\x76\x27\xd5\x19\xf8\x9a\xf7\x01\xb2\xfb\x1a\x07\x73\x1b\xf4\x47\xeb\xf6\x10\xeb\x1a\x97\xe9\xea\x3c\xcb\x6d\x1a\x90\xf3\x91\x26\x62\xe8\x37\xb6\x8d\xb9\x63\x8a\xf4\x0c\xb5\x3e\xb3\xbb\x1c\xae\x0b\x45\x7f\x55\x65\xb0\x14\x96\xa2\xef\xa8\xcd\x94\x1b\xf9\x86\xc6\x41\x9b\xa0\x13\x93\xc2\x9f\x4d\x55\x40\x78\x3a\xd1\x19\xa1\x88\x01\x6d\x0c\xe3\xdb\xfd\x99\xcd\xf0\xf3\x3c\x1f\x03\x16\xa6\x4a\x67\x79\x1c\xf6\x51\xdc\x01\x35\x84\x81\x3d\x95\x4f\x51\x0c\xed\xa9\xf4\x54\xa7\xf0\xb9\x59\xe3\xb0\xa1\x2c\x60\x3c\x45\x62\xa3\x5a\x13\xc9\x9c\xeb\x1a\x97\x8e\xd4\x06\xb2\xdf\x27\xdd\x87\x0b\x5d\xb3\x91\x3d\xa4\x23\x02\x9f\x22\xd9\x23\x55\x9c\xf6\x0a\x1c\x27\xfb\xcf\xec\xa2\xef\xed\xcf\x4b\xf9\xfc\xc4\xa6\x9a\xff\xdd\x23\x00\x36\x5a\x90\xdb\xfd\xbc\xeb\x4b\xbb\xba\x33\x25\xa5\x21\x00\x20\xda\x94\x0b\x07\xc8\x87\xd8\x2e\xdf\xb0\x6a\x22\x34\x55\xe1\x89\x02\xe2\x44\x5f\xb4\xfe\x65\xd6\xc0\xc2\xf4\xe9\x44\x6f\x86\x9c\x52\xea\x4b\xa7\x49\xc6\x50\x5f\x30\x5d\x63\x5d\x0d\xe3\x87\xb2\x82\x91\x23\x8d\x43\x5c\xc2\x14\xa7\xb0\x0f\x3a\x40\xd2\x84\x90\x93\x7d\x3a\xd5\xa5\x56\x6a\x12\x62\xa4\x07\xd3\x27\x4a\x29\xe3\x30\x13\xcd\x6c\x8d\xcb\x46\xea\xcb\x94\x72\xa1\xa4\xab\xf8\xcc\xf6\xc1\xd8\x26\x60\xa2\x33\xfc\xc4\xf2\x05\x57\x9b\x87\x9c\xe2\x83\xd4\xac\xe9\x90\xa5\x45\x4e\x56\x80\x0c\x3a\x34\xaf\x34\xc3\x27\x15\x23\x01\xa0\x5b\x00\xd0\xd8\x13\xa5\x34\x26\xa6\xaa\xb8\x28\x6e\xd9\x04\xbd\xc8\xeb\x99\xc7\xed\xe6\x62\x20\xb3\xaf\xfd\x66\xe5\xa7\xf4\x77\x96\x79\x99\xe9\x04\xcc\x58\x0a\x8f\x8a\xbf\xe9\xe7\x11\x41\xe2\x66\x20\xb8\xe5\x14\xc4\x2b\x4b\xb1\x53\x05\x07\x94\x82\xf1\x92\x04\x5a\x0a\xe2\x1d\x92\x22\x78\x94\x1b\xbd\x3a\x1a\x8f\xe9\x2a\xfe\x6c\x33\x73\x17\xf9\x31\x8a\xb5\x3a\x43\x3e\x1b\x04\x58\xf4\x02\x1e\x5a\x81\x1e\x8d\x08\xe5\x69\xa4\x85\xee\x48\xe5\x17\xab\xf2\xb0\xd4\xcc\x75\xd1\xf4\x38\x6a\xf2\xaa\x23\xfb\x52\x15\x97\xab\xf1\x8f\x3d\x2f\x9c\x8d\xbb\xf3\x80\x6d\xcf\x1f\x87\x14\x3b\x15\x14\x5e\x52\xa6\x40\x96\x71\x20\x09\x18\xe0\x44\x8a\x8d\x58\x37\x7c\x92\x15\x91\x97\x14\xbc\x25\x62\x0a\xc9\x7a\xe2\x83\x02\x5b\x9c\xac\xd0\x5d\x51\x52\x60\x6f\x11\x91\xbd\x85\xf8\xb0\x96\xe6\x99\x5d\x84\xb3\xb1\xc4\x3e\x55\xf5\x63\xbb\x2d\x1c\xe1\x0e\xeb\x89\xbc\xd8\xc1\xcb\xbc\x7b\x9f\x4b\x4a\x07\xf2\x82\x62\xd3\xa8\xdc\xb3\xf8\x00\x83\x74\x89\xf8\x89\x3e\xb1\xd5\x17\x8c\xed\x22\x2c\xe2\xa0\x4d\xb5\xa6\x66\xcd\xce\x4c\x86\x9c\x14\x7e\xa1\x47\x26\xe2\xb3\x85\xcd\xa6\x3a\x83\x65\x16\x83\xb0\x11\x4b\x0d\x55\x70\x59\x7f\x85\xd3\x5c\x27\xa5\x55\xa0\xb7\x80\xf2\xa2\x28\x34\x47\x8b\x53\x38\xea\x69\x3c\x31\x5a\x34\x88\x91\x06\x12\x9b\x6a\x94\x38\xd8\xc8\x74\x4d\x98\x21\xec\xb4\x7c\x80\x59\x0b\x72\x61\x51\x38\x92\xed\xb1\xd4\xc8\x67\x19\x6e\xa1\xab\x74\x4c\x79\x4d\xd7\x50\x47\xae\x8a\x27\x2e\xd7\x4d\x97\x78\x3d\x96\x9a\xa9\xd9\x15\x5c\x5e\x6a\xd9\x9a\x9c\xb8\x36\x33\x81\xa6\xd7\x7a\x35\x19\x00\x2d\xaa\xf9\xd2\x6f\x27\xae\xae\xbe\xe4\x1c\xd3\x61\x20\xc6\xb6\x3b\x0f\x2c\xa3\x47\x94\x2f\xce\x4c\x5f\x79\x1a\x11\x93\xd4\x24\x04\xb7\xdf\x1e\xcd\xfb\x54\xb3\xe0\x0e\x04\x3e\x35\xa5\xc6\x5c\x57\xb1\x27\x8b\x00\x53\x64\x6f\x2c\x85\x67\xba\xd4\x0c\xfa\xd2\x7c\x26\x2a\x8d\x0e\xdb\xe1\x69\x05\x8a\x9c\x38\x85\x6d\x01\x88\x74\x6f\xd1\x9a\x73\x8b\xdd\xfc\x85\x9d\x5e\xa0\xdb\x2e\x3f\xed\x05\x2d\x58\x72\x59\xc4\xc7\x57\x3c\x76\x21\xf6\x44\x00\x55\x01\x80\x3e\xa0\x5b\x92\xa8\xe8\x9c\x4a\xe1\xaf\xb6\xdf\xcf\x6c\x9f\xc6\xcd\xae\x90\x95\xb1\x27\x33\x7d\x80\xf5\x6a\x65\x99\xdd\xfe\xcc\x0a\x9a\xa9\xa9\x8a\xaf\x3a\xc5\xa2\xb6\xcf\x6c\xa2\xee\x22\x1c\x35\x19\x32\x18\x04\x7c\xc3\x22\x5e\x66\xfa\x5a\x3b\x2c\x42\xc8\xfb\x98\xf2\xf9\xd0\x54\xc9\x29\xdb\x6e\x12\xfd\x76\x73\xb7\x9e\x88\x7f\x69\xa1\x8b\xe4\x98\x35\x80\xc6\x37\xa9\x41\x88\x91\xe5\x55\xb2\xe8\x85\xb9\xc0\x17\x56\x19\xd7\xf9\x67\xe4\x43\x02\xc2\xb3\x47\xd6\x13\x5b\xb9\x4d\x4f\x69\x41\x52\xf8\x96\x00\xc1\x40\xec\xbc\xd0\xac\xb7\xc4\xc7\x89\xe5\x3f\xa6\x56\xd0\x9c\x8d\x54\x31\x35\xd4\x7a\x3a\x22\x3a\xe9\x28\x00\x99\xce\xbc\xc0\xa2\xfc\xe8\xd9\x60\xc8\x9a\xe9\x35\x53\xc5\x43\xfd\xde\x9c\x0f\x9e\x3b\xf3\xb5\xba\x56\x32\x9e\x28\x1f\x8f\x75\x06\x2e\x58\x6a\x15\xb7\x72\x5c\xa4\xc8\x12\x23\xb0\x59\xa1\xa3\xc6\xab\xae\x71\x84\xa1\xf2\xb0\xe7\x0b\x5b\xfc\x72\x89\xb5\xcb\xfc\x4b\x7d\x11\x60\x61\xfb\xf0\x59\x97\x10\xef\x24\xd1\x58\x66\x61\xa8\xe2\xbe\xf8\x67\x02\xb5\x11\xd9\x34\xf6\x24\xa8\x3a\xc2\x63\x12\xf5\x97\x4d\x90\x0b\x63\x81\x78\xc4\x4b\x64\xfa\x4a\x66\x33\xf4\x42\x27\x00\x46\x4d\xc1\x50\x74\xb7\x78\x2e\x21\x42\xbd\xe8\xf3\xb2\xce\xcd\xc7\x61\x07\xa5\x8b\x78\x91\xe6\x3b\x1a\xce\x0d\xe5\x0e\x18\x57\x3e\x38\x94\x48\x62\xa4\xb1\x33\x2b\x00\x59\x59\x5f\x34\xd6\xf4\x73\x9f\x6c\x96\x79\x30\x52\x00\x1d\x38\x94\x61\x9f\x64\x3d\xbc\xc5\x52\x28\xb6\xe1\x0b\x96\x9e\x4f\x65\x45\x94\x45\x9a\x94\x64\xec\x85\xd6\xf0\xca\xf6\xd8\x27\xca\x07\xf5\x72\xbc\x25\x2b\xd8\xcb\x58\xc0\x80\x04\x68\x92\x91\x94\x17\x7a\x28\x95\x7a\x66\xd2\xa8\x57\x13\x13\x6b\x41\x3e\xeb\x1a\x87\x7c\xfd\x79\x24\xe1\x9e\xad\x36\xa6\x26\xa3\x64\xa3\x1a\x87\xec\x06\x3a\xdd\x62\x1c\x24\x23\xec\x06\xe2\x50\x99\xc2\x81\x88\x93\x5d\x01\xe7\xc7\xa2\xd2\x50\x25\xc0\x0d\xe5\x29\x4e\xcb\x50\x20\xb9\x85\x98\xd7\x79\xeb\xf7\x60\xad\x3e\xbc\x8c\x35\x38\xd0\x21\x25\x49\x69\x74\x35\xcc\x6e\x2b\x38\xd9\x12\x30\x7e\x08\x94\x86\x32\x54\x68\x49\x46\xe3\x3d\x86\x0c\xac\xf9\xf1\xb2\x95\x29\xe8\xa2\x78\x32\x94\xac\xa9\xdc\x21\x91\xdf\x73\x32\x56\x3f\x55\x56\x5b\x04\x9c\xa2\x61\x39\x5e\x6c\xe8\xa3\xb4\xb1\x1c\x27\x74\x6d\x92\xf3\x0d\xb6\x83\x0f\xe5\x29\x54\x64\x9c\xe3\xe4\xa9\x3d\x16\x31\x5e\x2e\xf3\x8e\xd1\x58\x14\xc5\x67\x51\xc6\xf6\xf8\xe7\xd1\x3a\x0c\x04\x05\xa7\x15\xbc\xd5\x12\x30\x85\x14\x00\x37\x54\x60\xab\x6f\x12\xf6\xeb\xe1\x7c\xfc\x50\x9e\xea\x9c\x88\xaf\xc6\xef\x79\xbb\x51\xff\x77\xaa\x67\x3a\xad\x40\xfe\xb8\x0e\x70\x20\x8b\x80\xec\x8a\x4a\x83\x06\x53\x7a\x2c\x60\xe4\x40\x9c\xc2\xae\x86\xa3\x36\xe9\x1c\xaa\x4f\xc1\xef\xc2\x83\x3a\x00\x1d\x30\x10\x50\x5f\x9d\xab\xf7\xdc\xe6\x15\x12\xf9\xfa\x21\x99\xca\x94\xec\x8b\x80\xe4\x64\xc8\x2b\x82\xd2\x68\x8b\x32\x16\x70\xd4\x24\x34\x6b\x3c\x56\xc4\xfa\x69\xb0\xd7\xdf\x90\xdf\x13\xc8\x8f\xf5\x48\x2f\x62\xa0\x3b\x6c\xbf\xe4\x1c\x05\xc5\x6f\x11\x72\x43\x19\x8a\xb4\x3c\x15\x81\x32\x9d\x17\xf6\xd9\xc1\x19\x65\x4a\x0e\x50\x3b\x44\x1a\x48\x32\x65\x6d\xf0\x81\x32\x4d\x19\xd3\x8b\x67\x0a\x46\x02\x65\xca\xd3\x7b\xec\xbd\x2f\x63\x7c\x2b\xf7\x4f\x6f\x33\x6d\xde\x26\x39\xc7\x58\x24\xaf\xe2\x20\xb9\xaf\x4a\x4a\x03\xf1\xaf\x81\x90\x97\x27\x84\x06\x43\xbe\xda\x4c\xce\xbd\x22\x6e\x39\x16\x5a\xe2\x62\x8e\x11\xba\x26\xd6\xb6\x78\x6a\xc5\xa1\xa6\x4b\xfc\x93\x4a\x1e\xb0\xc4\xbc\xe6\xe3\x90\xb2\x91\xbc\x80\x82\x29\x30\x8b\x31\x5e\x4b\x0f\x44\x68\x3d\xe3\x53\x93\xe0\x63\x5d\x63\x57\xe9\x85\x48\xd2\x35\x1a\x47\xfc\xdd\x7a\xdd\xf7\x3c\x7c\x52\xf1\x8a\x9b\x01\x9b\x9a\x82\xcc\xf6\xe1\xc2\x24\x1a\x29\xe2\xef\xa2\x0f\x13\xbd\xf2\x87\xdd\xbe\x79\x12\xb5\x09\x1c\xe1\x3c\x36\xd2\x38\x4c\xe9\x72\x70\x28\x61\x33\x5b\xe3\x17\x3d\x82\x8b\x4c\x8f\xac\xca\x73\x2d\x1f\x73\x7b\xea\xc8\xe5\x94\xfa\x46\x9e\xa1\x44\xe2\x56\x8d\x9d\x8d\x7c\x98\xf5\x88\xe5\xb8\x6c\x61\xab\x75\x57\x6b\x86\x2e\xe2\x3e\xa8\x1e\x56\x00\xe7\x26\x01\x33\x9b\x6a\x16\xf2\x6b\x7c\x3e\x76\x91\x55\x80\x59\x3e\x7c\xb6\xb0\x62\x8e\xce\x22\xe0\xcc\xf4\x5a\x16\xe2\x33\x3d\x09\x5f\xb3\xb7\x26\x94\xa8\xe5\xef\xf9\x98\x98\xa5\xc0\x80\xa5\xe7\x4f\x2c\xe2\x07\x1a\x97\x56\xdf\x4d\xa9\xb9\xd0\x17\x65\x5a\x6d\xb4\xc3\xc7\x06\xde\x12\x5f\xd2\x82\x43\xac\xa5\xad\xe9\x13\x0b\x3d\xef\x5a\xdf\xd7\xbf\x3b\x65\x9d\xb8\x0e\xde\x91\x95\x92\x3f\x96\xe5\x09\xa8\x2f\x3a\x07\xb8\x5f\x99\x46\x0c\xb8\x99\x19\x88\xb4\xe9\x8b\xc0\xf2\xe7\xa5\x1c\x5d\x92\xb1\x86\x82\xf8\x12\x00\x5c\xbf\x4a\x8b\xb0\x56\x47\x9c\xb5\xc3\xcf\xcc\x80\x9f\x21\xb9\x85\x2e\x71\x84\x49\x7d\x5d\xd3\xa1\x49\xb5\xb4\x91\xc6\x65\x86\xda\x08\xaa\x7c\x4a\x97\x8b\x6c\x9f\xc6\x74\x25\x85\x8e\x72\xbc\x3e\x0a\x41\xe2\x96\xcf\x43\x51\x6d\x10\x86\xc6\xcd\x4c\x1f\x47\x63\xa9\xa2\x5e\x80\x1f\x02\xc0\xb5\x45\x05\x0c\xc0\x14\xa2\x3a\xf2\xa2\xd2\x50\xb6\xf3\x2a\x3e\x99\xe8\x52\x73\xba\xe3\x63\x95\x3e\x01\x8f\xc6\x41\xcf\x3a\xe0\x66\x26\x03\xb8\x62\x2e\xad\xf1\xbc\x37\x8f\x02\x07\x0a\x2e\xb6\xe4\x29\x4f\x53\x53\x50\xd7\x55\x5e\xb6\x19\x72\x7e\xc0\xbe\x5e\xd1\x33\xd4\xff\x14\xe4\x60\x39\x56\x21\x47\xea\x4b\x6d\xa4\xc1\xd7\xd5\x6f\xa2\xac\xab\x7d\xb2\x2f\x44\x32\xe2\x08\x76\xe7\x25\x32\x55\x88\x49\x6a\x03\x8d\xa1\x89\x91\x3a\x27\xfb\xf9\x78\xab\x9a\xb7\x78\xb1\x29\x68\x4f\xcc\x40\x84\x7a\x87\x6b\x0c\x55\x1c\x37\xbb\x62\xd4\xd3\xc0\x2b\xf2\xb9\xa3\x3c\x63\xd3\xb7\x2a\x2e\xb5\xc4\x3e\x34\xee\xd1\x70\x8e\xae\xda\xac\x61\xb0\x83\xf0\x7a\x77\x3c\x64\x3d\x09\x39\xf6\x35\xc6\x02\x68\x71\x1a\xc6\xf7\x25\xd4\x0f\xf4\x32\x4e\x0e\x65\x05\x1f\xca\xb0\x88\x59\x85\x8c\x3c\x7d\x4b\xe9\xc0\xb6\xdc\x81\xb4\x0c\xc5\x3c\xad\x80\x91\x3c\x8a\xd3\xf2\x94\xe6\x11\x6e\x8a\x1d\x5a\x11\x14\xae\xa5\x60\x60\x2c\x28\x7c\x5b\xc6\x73\xb9\x4b\x39\x6b\xcf\x97\xf1\x7f\x95\x97\xe4\x05\x05\x0e\x34\xac\xd1\x92\x15\x65\x0b\x67\xeb\x53\x01\x7b\x01\x0a\x2e\xd2\x0a\x6c\xb5\x94\x29\x18\x80\x8e\x38\x94\x15\x9a\x93\xa7\x79\xfa\x80\x9a\xae\xc6\x3b\x4b\x5e\xd4\xc6\x82\x7d\xe3\x20\x6e\x9e\xe3\xf3\xaa\x8e\x6d\xbc\x95\x8f\x85\x00\x07\xf5\xce\x64\x62\x77\xc8\xb9\xae\x36\x64\x83\x81\xbe\x4d\x73\x02\x35\x45\x36\xbb\x19\xc3\x15\xd8\x7c\x52\x60\x6b\x2c\x2b\x64\x27\x8f\x5d\x34\x27\xe4\xdf\x25\xeb\x69\x39\xb6\x2c\x7f\x2f\xe3\xcc\xe1\x78\xb6\xc3\x63\x4b\xae\xbe\xc6\x7f\xd1\xf8\x96\xc3\xc0\x50\xf4\xac\x27\x15\x07\x99\xa1\x89\x36\x35\xa5\x7d\x9b\x01\x8b\xa1\x5a\xd9\xed\x2a\x7d\x6e\xa3\x1a\x88\x2c\x1f\xbc\xee\x7d\xee\x46\xa6\x52\x61\x3c\xc0\x9e\x36\xfd\x54\x64\x0c\xf5\x05\x2e\x79\x63\x0d\xbe\x8e\x08\x32\xd3\x7d\x18\xac\x8d\x23\x68\x87\x01\xcf\x4a\xe1\x07\x82\xe5\x2b\x64\xef\x00\x56\x97\x6b\x06\x50\x76\xd7\xf3\x08\x64\xaf\x06\x5e\x2d\xaf\x98\x63\x5a\x8d\xa3\x39\xfc\xbd\xf8\xac\x4b\xad\x96\x80\x73\xe3\x35\xbb\x1f\x4b\x8a\x50\x61\x1c\x64\x3b\x34\xc2\xd8\x81\x86\xd1\x82\x04\xc8\xca\xa6\x2b\xce\xdc\x96\x31\x1c\xf1\x99\x71\x61\x9b\x65\x99\xaa\xe2\x1e\xb3\xef\xb5\xb2\x3b\x32\x86\xb7\x90\x7f\x6d\xe7\x47\xfc\x11\x4c\x69\x20\xd3\xe2\x72\x3c\xb0\x96\x6f\xd3\x26\xa9\x55\x3e\x84\x65\x85\x5f\x00\x59\xe9\xd0\xed\x75\x99\x4b\x1b\x5c\xfe\x86\xb9\x7d\xdf\xaa\xda\x3a\x67\xdb\x02\xd6\x7f\x0d\xeb\x7c\x5b\x78\xad\xca\xb2\xb7\xc7\x84\xed\x70\xf3\x6f\x6a\x3d\x5e\xa1\xfe\x58\xe3\x1d\x44\x35\xf7\x34\xff\x7e\xe4\x59\x55\x3e\xb1\xce\x65\x06\xde\xda\xf7\x20\x2c\xd3\xac\xcd\x97\xe4\xf5\x6b\xad\xdb\xd6\xcc\x6a\x1f\xe0\x0b\xe8\x19\x6a\x0b\xd5\x7c\xe5\x9f\x9b\x25\x56\x8b\x91\xa9\x82\x99\xad\x89\x39\xb6\x2b\x04\x78\x1e\xca\x7d\x34\x5e\x7e\x52\x7c\xf0\x9a\x63\x74\x1b\x9f\x98\x5d\x7b\xe2\x68\xfd\xd5\x6f\x34\x0f\x47\xaf\xd8\x0b\x05\x0b\xce\x21\x33\x30\x35\x34\x91\x43\x38\x6d\x05\xfa\xc4\x6c\x63\x73\xe4\x2f\x92\xda\x40\xe9\x13\x93\xc6\x9e\x00\x41\x67\x36\x03\xa6\x42\x30\x25\x4d\x34\x76\x63\xa6\xa9\xad\xf1\xc8\xc7\x90\x8c\x40\xd7\x84\xbd\x73\xf6\x7b\x7c\x29\x35\x89\x46\x64\x33\xe4\x02\xf9\x78\x9e\x67\xca\xf7\x01\xe0\x15\x11\x70\xe3\xa2\xef\xe9\xb6\x80\x91\x00\xf1\x4f\xa1\x58\x97\x90\x74\x55\x64\x47\x9a\x38\xb4\x18\x90\x29\xc4\x24\xd2\x03\xb1\x6d\x76\x51\x7b\xc0\x62\x37\xff\x9a\xed\xc8\x38\x25\x4c\xb1\x77\x61\xf5\x3e\x3b\x46\x79\xd6\x7e\x17\x94\x29\x60\x24\xe0\x06\x25\x76\xae\x8f\xf7\xf2\xf9\x76\xc5\x07\x53\x89\xa1\x31\xb9\xd6\x82\x26\xe4\x43\x43\xd5\x31\x84\xa9\x87\xea\xfb\xc9\x58\xbb\xec\x83\xb5\x79\xc8\xa3\xf3\x04\xfb\xe6\x2d\x15\x1f\xbc\xd8\x2a\x5c\xe8\x5a\x7f\xef\x73\x0a\xa6\x72\x85\xbf\x9a\x10\xd1\x1b\x6b\xae\x05\xbf\x23\xd7\x78\xfe\xb3\x49\x34\x7c\x43\xb5\x66\x23\x8d\x9b\xad\xad\x6b\xfc\x10\x4e\xbd\x36\x3e\x71\x39\xc5\x5d\xfe\x5e\x61\xb1\xbc\xc2\xe8\x85\xb9\x86\x47\xab\x38\x4d\x1e\xc4\xb3\xed\x34\xb9\x5d\x01\x5e\x10\x14\x7e\x03\x47\x05\x20\xb6\x84\x29\x8d\xc6\xaf\x4b\xfb\x29\xf9\xc1\x1a\x96\xef\x89\xfb\x3b\xd8\xb9\xf4\x9f\xb5\x7c\x3b\x31\x7d\xf5\x8c\xe6\x04\x0d\xc3\x87\x62\x67\xf9\x5b\xca\xb6\xb9\x60\x1d\x3f\xf3\xf9\x9b\x35\x7b\x32\x7c\xc4\x59\x1b\x13\xb3\x1d\x1e\x7e\x46\xbd\x61\x1c\xb0\xb2\xe5\xa2\x0e\xcc\xaa\xff\x7f\x62\x2c\xcb\xc7\xfa\x08\xff\x97\xbc\x14\x44\x40\x99\x82\xb1\xd2\x21\x25\xd0\xa1\xfb\x45\x5f\x00\x59\xc1\xc9\xae\x32\x25\x81\x42\x93\x9c\x48\xf7\x11\xdf\x13\x64\x9c\x53\x04\xe5\x05\xf5\x35\xad\xe0\x7c\xf1\xbc\x03\x3b\xca\xc2\xda\x23\x67\xc9\x5b\x39\x11\xf1\x3c\xba\x35\x54\xa0\xd8\x92\x3b\xe5\xef\x1d\xd8\xe1\xe6\x51\x4b\x85\x40\x12\x01\x59\xc9\x1e\x8b\xca\x0b\x0d\xa6\xb4\x22\x2a\xe2\x58\xc4\xb9\x21\x00\xad\xb1\xa4\x88\x32\xc2\xaf\x4a\xf6\x5a\x9a\x8d\x3a\xfc\x42\x98\x29\x94\xfd\xf0\xb4\xd2\xd7\x64\xa8\xe0\xe2\xb2\xae\x6b\xf3\x38\x59\x39\xa7\x9b\x21\x9b\x53\x3a\xb4\x84\xc6\x62\x15\x1f\xaf\xea\xff\xc9\xf8\x5b\xd9\xcd\x27\xce\x63\x9c\x18\xa3\x5d\x36\xc7\x51\x8c\xc3\x18\x72\x81\x7c\x69\x35\xae\x44\x7e\xd8\x5a\xe8\x32\x96\x1e\xe0\xb8\x29\x4b\xe1\x7e\xce\x59\xd6\xf3\x1c\xe2\x3a\x01\xc8\x96\x3c\x57\x9d\xcc\xac\x9a\x58\xf1\x5f\x76\xf5\x7b\x85\xd5\x2b\x0c\x47\xf8\xff\x5e\xce\x7b\xa6\xff\xae\x97\x63\x00\xc0\xd1\x1a\x5e\xfa\x23\xbd\x2a\x6b\x8b\x83\x6f\xa7\xa3\xe5\x0e\x50\x05\x20\xd2\x22\x7d\x48\xb6\xd8\x02\x1d\x9a\x12\x00\x1a\xdf\x56\x98\x2e\xfe\x18\x9e\xbd\xc7\x8f\xd6\x64\x4a\x0a\x4d\xf2\x32\x26\xd2\xfb\xe7\x88\x9a\x2e\xdf\x16\xb0\xc1\xb3\x8b\xf1\x6d\x61\x8d\x9b\xb3\x8b\xfe\x33\xfb\xbd\x58\xa7\xfe\x43\x39\xf7\x7c\x89\x17\xe7\xc4\x96\x77\xc4\x0f\x83\xe1\x67\xc5\x1a\x51\xab\x6a\xc3\xd8\x96\xa3\xd9\xc8\x8f\xe0\xa8\x26\x8e\xad\xe7\x88\x18\x69\xdc\xd8\x64\xc8\x40\xab\x09\xdf\xad\xd7\x66\x15\x2b\x67\x56\xbe\xc6\xb2\xb6\xa7\x86\x78\x89\xcc\x00\xd4\x4d\x95\xcc\xca\xb5\xfa\x6a\x3f\xc9\x6a\xdc\xcc\xc0\x8c\x65\xb6\x78\x36\xb3\x19\xab\x72\x7e\xa5\x71\x73\x96\xa1\x71\x93\xc0\x5f\xab\xbd\x24\x3a\xd5\x9a\x38\x01\x0f\x47\x04\x8a\xbd\x82\x5b\xac\x17\xb4\xf2\x39\xe7\x11\x31\x41\xf8\xd0\xd0\xa9\x9d\x3d\x22\x0f\x2c\x23\xce\x50\x5f\x56\x18\x97\xef\x97\x60\xca\xfd\x30\x52\x73\xba\xb6\x3e\xb5\x1a\x8f\x97\xeb\xa1\x55\xfd\x47\x04\x9d\x15\x75\xe7\x26\x16\x01\x12\x43\x6d\x40\x96\xe1\x93\x91\x8a\x4f\x6c\xaf\x95\xb7\x3f\x5f\xaf\x2f\xf7\xc8\xb0\xd5\xbc\x27\xf1\x32\x1b\x11\x74\x52\x62\x48\x59\x7f\xc1\xad\xe2\x74\xb1\x46\x2f\x16\xf2\x29\x7c\xca\xd2\xcd\x17\xb1\x19\x4d\x4d\xa2\x01\x29\x37\x0c\x2d\xe2\x05\xea\x5a\xd3\xed\xbf\x36\x1f\xd8\x2e\x07\x47\x3e\x39\xb3\xa9\xa9\xcb\xb9\xe1\xdf\x6e\x7e\xff\xfd\xe3\x8e\x98\x7f\xf0\x71\xa6\xb3\xee\x61\x08\x67\x4e\x1c\xc5\xe1\xcc\x2b\x8f\x7f\x6c\xbe\xb6\x60\x4f\xae\x6b\xcf\x76\x82\xb4\xb8\x5d\x62\xcf\x95\x11\xd5\xa9\x16\x25\x71\xe2\x66\x92\x78\x6e\xe0\xec\xbd\xa3\x22\x5b\x7b\xce\x16\x12\x8f\x1d\xc6\x39\x74\x98\xae\x5f\x9c\xcb\x2e\x25\x2c\xee\xf7\x8b\xbd\xf9\x76\x55\x9d\x93\x32\xe2\xf0\xb6\xb8\x06\xe2\xe0\x3e\xe0\xdf\xff\x91\x1f\x68\xd9\x55\xdf\x59\x57\xab\xac\x5f\x36\x91\x9f\x48\x3a\x78\xe3\xc4\xee\xed\x26\xbb\x37\xb0\x50\xc5\x85\x16\xf7\x9b\xd7\x65\x48\x96\x01\x1d\xc9\x49\x3f\xe8\x2e\x96\x7d\x97\xaa\xd4\x3e\xec\x52\x95\x66\x79\xc5\x43\x5e\xf6\x7d\x1c\x42\xa7\xe8\x20\xdf\x09\xf2\xd3\xb4\x6e\xe6\xd9\x3b\xdb\x9a\x3d\x7b\x53\x6b\xfb\x5e\x32\x90\x9f\x63\x13\x87\x57\xf7\x57\xa2\x63\xd8\x4e\xbc\x47\xa3\xc7\xea\x75\xe0\x9c\xef\xfa\xf9\xde\x8b\xe4\x6d\x9c\xeb\x5c\x9d\xe1\x7c\x93\x2c\xa9\x38\xb6\x77\xbf\x79\x7c\x0f\x49\x4b\x32\xb3\xb8\xba\xff\xb7\x73\x8e\xf9\x61\xdf\xae\xbc\xc0\x76\x5e\x06\xe3\xb3\x92\xdf\xdc\xed\xc1\xb3\x0f\xb9\xe0\x66\xf9\xd6\x82\x9d\x9b\x0f\xb6\xfd\xe8\x32\x13\x5e\x7a\xdf\xe7\xeb\xe7\x0c\xaf\x3d\xd0\x7d\x9f\x78\x07\x52\xfd\xcc\xdb\xe5\x4f\xdd\xf8\x93\x38\x33\x27\x2e\xc0\x7d\xcf\x85\x57\x27\x4f\xaa\x5e\x27\x56\x18\x1d\xb9\x92\xee\x72\xdf\xd9\x11\xb7\xef\x42\x3c\x67\x66\xc0\x2c\x37\x10\x3a\xbf\xc8\x2c\xc8\x6f\x2d\xba\x1e\xca\x8d\x7d\x17\x92\x5c\xcf\xbd\xc0\x0e\xe7\xe5\x4b\x39\xae\x87\x32\xbe\x3f\x59\x6a\xc4\xae\x93\x56\xa7\xda\xe4\x8b\x6e\xc9\xba\x2a\x6e\x1f\xf1\x52\x27\xf6\x8c\xc3\x51\xce\x80\x70\x30\x3e\x7a\x2c\xf6\xc4\x51\x62\xd4\xbb\x46\x1a\xe6\x47\x54\x7b\x4e\x92\xc8\x13\x23\x38\x75\xb7\x49\x3a\x89\x9d\x64\x12\x42\xd4\xad\x35\xec\x44\xe2\xa6\x5d\x98\xac\x01\x87\xeb\xa6\x14\x64\x10\x9e\xba\x20\xad\x74\xd1\xee\xea\x9a\x11\x6a\xe2\x58\xd3\x93\x57\xaf\x38\x69\xec\x59\xe5\xdb\x4c\xae\xdb\x5e\xd4\x9c\x19\x1e\x34\x4c\x0f\x22\x3b\x3d\x3b\x73\x12\x19\xf9\x25\x39\xd7\xfe\xb2\xe3\x82\x33\x3b\x6e\x53\x5b\x9e\xef\x34\x5d\x37\x76\xdc\x25\x4c\x35\x67\x4e\x7c\xc6\x1d\x32\x65\xf7\x87\x41\x65\x42\xc5\x4b\x31\xe4\x4a\xfd\x54\xf5\xfc\xad\xe7\xb2\xf7\xb8\xc2\xd5\x45\x7d\x75\x1d\xda\x46\x6a\xdc\xed\x02\x5a\x71\xc6\xb9\x1f\x06\x5e\x1a\xc6\x77\x92\x17\xb8\xd0\xa9\x1c\xa2\x9f\xc1\xd4\x8b\xa0\xd3\xcf\x55\x4d\x55\x36\x7e\x16\xfb\x34\xb2\x34\xec\x7b\xa9\xe7\x1a\xa9\x73\x18\x54\x0c\xeb\xe2\x57\x53\x14\x59\xcb\x53\x8e\xdb\x97\x32\x6c\x40\xfe\xb1\xf7\xfd\xe4\xd8\xbf\xd2\x06\x1b\x24\x9e\x3b\x49\x93\xfb\x35\xe9\x15\x5a\x15\x97\xe8\xdc\x1a\xee\x89\xbb\xc4\xe6\x8e\xd9\x0d\xc3\xe9\x4e\xc7\x9c\x49\xe5\xcf\xbb\xe6\x6f\x9d\x8b\x1a\x6b\x5e\x73\x6b\x40\x27\x4e\x8f\x92\xe0\x33\xa2\xdc\x52\x0d\x85\x93\x35\x91\xcc\xa3\x8c\xd4\x85\xa1\x69\xec\xbc\x37\xed\xa3\xa2\xe0\xe3\x47\x92\xd6\x37\x06\xa4\x73\x38\xd2\x1b\x42\xef\x9e\x8b\x53\x7f\xd9\xd0\x7b\x20\xa8\x6e\xc7\xde\xef\xdd\x3f\x7b\xe8\xfd\xfe\xf0\x15\x7a\xbf\x42\xef\x57\xe8\xfd\xcf\x0b\xbd\xb6\x93\x5f\xb4\x62\x7f\x85\xdd\xe5\xe3\x9f\x32\xec\xfe\xf9\x46\xbc\x5f\x03\xde\xda\xa9\x4b\xea\xff\xe8\xa8\x0b\xbe\xa2\xee\xce\xe7\x2b\xea\x1e\xd2\xd7\x57\xd4\x5d\x2d\xbe\x04\x4e\xfa\x15\x71\x97\x8f\x7f\x5c\xc4\xdd\xee\x8c\x93\xf7\x11\x7d\xbb\xba\xb9\x3f\xb4\x5e\xb4\xbd\x40\xb4\x59\xfb\x76\x68\x65\xbe\x13\xa4\xed\xd6\xfd\x96\xe4\x64\x6b\xd9\xed\xc0\x45\x48\x57\x37\xe4\x03\xf6\x40\x12\x66\xe3\xf6\xe1\x81\xc0\x6e\xeb\xe6\xb8\x71\x6b\xd4\x6a\x8d\xdb\x31\xde\x70\xc8\x07\xeb\xc1\x21\x1a\xc6\xcd\xd5\xb7\xab\x9b\x96\x07\xa1\x17\xb8\x57\xf7\x57\xab\x52\xaf\x4a\x71\x57\x54\x18\x14\x6f\xa3\x09\xf7\x2d\x43\xed\xb1\xa9\x23\x35\xbf\xcf\xd7\x63\x6d\x27\x4e\xb6\x57\xcb\xb6\xc5\x9e\xe4\x36\x88\x9b\x1c\xb9\x8a\xf4\xbd\xea\xdb\xbb\xa4\x85\xea\xdc\x76\xc6\x5e\xe0\x15\xf7\x1e\x2d\x97\x65\xb6\xd1\xe6\xd4\x32\xe1\x4a\x48\x6e\x90\x0d\xd3\x26\x2d\xfb\xf1\xf1\x76\xec\xd4\x1b\xb7\x75\x02\xff\x7e\x4b\xd6\x1e\xcd\xdb\x31\xf9\x50\xaf\x3d\x38\x78\xa3\xde\xc0\x0e\xac\xb2\x45\x1b\x6f\x26\xbf\xfe\xfb\x79\x9d\x7e\x5c\xd4\x32\xa6\x6d\xad\x4b\x1e\x5f\xdd\xfa\x40\x0c\x20\x6f\x31\xfc\x36\x8a\x9d\x99\xe7\xcc\x3f\x06\x0b\xde\x6f\x0e\xef\x64\xe7\xa7\x60\xfd\x10\x88\xe7\x2b\x73\x9b\x35\x3c\x7c\x41\xe0\x19\xce\x59\x81\xa2\x1d\x24\x7a\x18\x1c\x7f\x99\xf4\xa7\x63\x7d\xe3\x83\x96\xf6\x8c\xe2\x6a\x61\xa9\xa4\x85\x87\x08\x79\x79\x01\x71\x7e\x97\xdb\x91\x71\x4d\x9e\x18\xc7\xee\xf2\x7f\xf7\x44\x7d\x3f\x85\xdb\xdd\x99\xb2\x8f\x14\x95\x37\x6b\x7f\xf6\x8b\xac\x36\x9a\x86\xf4\xbb\x5e\xfd\x53\x5c\xbe\xb0\x08\xc9\xb1\x32\x34\x58\xcc\x0d\xf0\x03\xdf\xb8\xb6\x4f\x7c\x15\x85\x83\xe4\x04\xb5\x5a\x16\x75\xf6\xab\x93\x4f\x5d\x31\x5c\x40\x5a\x27\xb0\xa3\xd0\x0b\x8e\x74\xcc\xfa\xe7\xcc\x4b\x74\x4b\xd9\x9b\xfe\xf6\xe4\x2c\xc0\x19\x2f\x6f\x5b\x0a\xa9\xfc\xef\x6d\x97\x7b\x5f\xff\xff\xf3\x36\x6a\x9d\x71\xef\xf4\xe9\x7a\xbe\x47\x1d\xf9\xe0\x83\x0a\x13\x3f\x4c\xda\xad\x5f\x46\x27\x47\x53\x1c\xce\x7f\xe6\xcb\xd9\xca\x2b\xff\x3f\x74\xb4\x50\x0e\x12\xce\x8f\x07\x5b\x5b\x85\x7e\xce\x37\x61\xfd\xc4\xa1\xa2\x8e\xb0\x96\xf8\x65\x43\x45\x59\xfd\x9f\x37\x54\x44\xce\x1f\x10\x2d\xa2\xd8\x9b\x19\xe9\x32\x5a\x94\xf5\xcc\xdf\x19\x56\xe8\xfa\xba\x5d\xbc\xcc\xea\xc0\x0b\xed\x4f\x94\xb1\xf1\x86\x46\xe7\xb3\x70\x20\x72\x8a\xf9\x02\x6c\x77\x5c\xfd\x05\x07\x06\x84\xe1\x1c\x6c\x34\xb6\xb8\x0b\xfb\xc8\x0c\x16\xca\x42\x87\xf1\xdc\x88\x6d\xc7\x96\x63\x63\x3c\xf6\xac\x13\xc9\x19\x23\x75\xe6\xc6\x42\x8e\x8d\x20\xf1\xd2\x6a\x2f\xf2\x9e\xd4\x59\xe2\x88\x8e\x1f\xa6\x4e\x99\x23\x39\x92\x36\xce\x13\x6e\x56\xfe\x30\xa4\x5d\xf2\x62\x98\x95\xef\x55\x16\xb4\x77\xf6\xfe\xbc\xdd\xc3\x65\x48\xba\x8f\x1c\x27\xf6\x02\xf7\xf6\x83\x4c\x73\xeb\xef\x61\x21\xfd\x33\x37\x24\x7e\xfc\x4b\x19\x3f\x7a\x9f\xee\xdb\xbb\xf1\x1f\x97\x39\xf8\x97\x07\xfe\x18\x0f\x9c\x1d\x7a\x0b\xd3\xd9\xae\xb7\xd6\xf9\xeb\x1e\xf8\x2e\x92\xf8\x9f\xeb\x7a\x5b\xae\xf2\x51\x1e\xb8\xb7\x1b\x3f\xcf\xf5\xa6\x5e\x90\x9b\x1c\x93\xcf\xe3\xac\xa6\xc0\xde\x3a\xd7\x6a\x85\x41\xe2\x25\xa9\x13\x58\x8b\x53\xef\x4b\xb5\x8b\x57\x95\x53\xab\x1c\x3d\x67\xe6\xc0\x62\x85\x2c\x0e\x03\xf7\xbc\xb5\xa4\xd3\x23\xbe\x23\x6c\xb8\xca\x5c\x2d\x33\x9e\xa3\xc4\x8d\x0a\xed\x72\xad\x3d\x55\xdc\x9a\x27\x1c\x8c\xc7\x4e\xbc\xb6\x18\x78\xf0\x0d\xb6\x5e\x24\x1a\x81\xeb\xd0\x1e\x4c\x8b\xd7\xed\x2c\x17\x76\x70\xac\x7e\x57\x27\xee\x70\xb2\x71\x47\x12\xdf\xea\xd8\xdd\xc3\xf7\xbb\x46\xfd\x0e\xaf\xe1\xdf\x1a\xc4\x1d\xfe\xf0\xfd\xee\xfb\x5d\x0d\xcb\xbf\x7f\x27\xef\x1a\xd8\x5d\xbd\x91\xff\xf1\xf8\x70\x87\x3f\xd6\xef\x88\xef\x37\xdf\xae\xbc\xf1\x6f\xce\xbf\x32\x03\x26\x1b\x4b\x13\xce\x4b\x1a\x1b\xd5\xa8\x9b\x1d\x26\xf9\xba\x44\xf1\xdf\xb7\xab\x9b\x6f\xe8\xdb\xf1\xe4\xfb\x7d\xc0\x4b\x36\xa1\xaf\x68\x53\xe7\xd4\xb6\x82\x4d\xdf\x10\x33\x78\xd9\x8b\xde\x7f\xe8\x9b\xee\xce\xb4\x89\x62\x34\xf0\xe4\x2c\x5a\x46\xe2\xd8\x7d\x27\x35\x90\x99\xa8\xb1\x97\x3a\x1b\x41\xed\x2c\xd4\xfe\xfb\xc9\x69\xe9\xf7\x2d\xc6\x7c\x0c\xa9\x2f\xc7\x5a\x7b\x60\xa3\xc4\x82\xce\x4b\xe4\xc4\x5e\xf9\x1e\xd7\x6b\x2a\x8c\x9d\xab\xdf\x24\xa1\xf7\xfb\x8f\x59\x4d\x20\x6f\xb1\xc7\x1f\xcc\xd7\x36\xd1\xfd\x03\x28\x52\x55\x99\x93\xd4\xe2\xa6\x29\x0e\x6e\xf6\xb1\x84\x3d\xb6\x1a\x46\x15\xc0\x1e\x90\x99\x4e\xe2\x30\x73\x27\x51\x96\xbf\x93\xaa\x81\x61\x97\xb2\x8f\x37\x2d\x96\x22\x5d\xa2\x56\xbc\x77\xa1\x31\xf9\x17\x6c\x97\xbf\xfd\x7c\xa3\xd7\x8f\xb3\xc8\x3f\x7c\x5d\xeb\x72\x6b\x6d\x26\x8b\xc0\x1a\xe4\x7b\x9c\x8e\xbc\x5b\xee\x3a\x32\xe2\x34\x57\xfb\x93\x73\x98\x77\x94\x29\xd3\xc9\xe9\x29\xe2\xeb\x7b\xef\xc8\x6c\xcd\x81\xed\x37\x57\xeb\x74\xaa\x6b\x24\x93\x03\x51\xe2\x40\x1b\x4a\x24\x94\x53\x44\x84\xbe\x63\xf5\x47\x0c\x7b\xab\x8f\x7e\xa2\xab\xa1\xef\x5b\x9d\xf1\xa1\xee\x77\x6f\x85\x41\x6a\x78\xc1\xee\x86\xac\x3f\x89\x27\x6e\x68\xe3\x1c\xb7\x3c\x84\x81\x17\x96\xff\xb3\x23\x41\xb9\xfd\xe4\x3f\x02\x01\x7e\x32\xbf\x2e\x55\xfb\xe5\xcf\x5f\xfe\xbc\xc7\xc6\x3e\xc9\x9f\xcb\xbd\xa5\x7f\x9e\x90\x7e\x8b\xff\x64\x6e\x5f\xf5\xc0\x97\xdf\x7f\xf9\xfd\x1e\x23\xfb\x24\xbf\x1f\x44\x4e\x20\x4d\xbc\x71\x4a\x15\x3b\xd4\x7e\x20\x00\x6c\x48\xfc\xc1\x50\x90\x05\xde\xbf\x32\xe7\xc9\x39\x35\x39\xba\x99\xf8\x43\x5e\x11\x7e\x9e\x7a\x96\xc9\xef\xa7\xc7\xb4\x53\x7d\x8e\x6f\x88\x39\xb1\x66\xfe\x09\x95\x2e\xf7\x3b\x6e\xec\xe4\x67\xed\xa3\x1d\x5d\x7d\x7e\xbe\xa6\x14\xaf\x40\xfe\x88\xca\x1f\xb6\xf2\x5f\x82\x99\xee\x80\xc5\x57\xac\xfa\x8a\x55\x7b\xac\xed\x93\x62\xd5\x30\x7f\x57\xf8\x17\x43\xdd\xcc\xf3\xc6\x49\xe1\xfa\x0f\x9e\x14\x46\xdf\x8b\x8e\xfb\x02\x8b\x2f\xb0\xd8\x63\x62\x9f\x04\x16\xd2\xda\x11\x9f\xff\x8c\x51\xed\x4f\x46\x06\x36\x14\xfc\xe5\xdb\x5f\xbe\xbd\xc7\xd2\x0e\x1d\xc5\xfc\xc0\x4b\x36\xcf\xb0\xbb\xa3\x57\x7e\xbe\xff\x10\xe3\xaa\xfe\x3f\xf4\x00\xa2\x61\xd9\xf6\x03\x61\x3c\xdc\xd6\x6a\x8f\x8d\xdb\xfa\xa3\x33\xbe\x35\xed\x3a\x71\x3b\xfe\x8e\x7d\x1f\x9b\xc6\x23\x6e\x38\x0f\x67\x1e\x40\x3c\xad\xf5\x5f\xe9\xf0\xe1\x87\x18\xe2\x0d\x8d\x8c\xac\xdc\x2d\xb0\x79\x9e\xf6\x4f\x6b\x71\x75\x9b\x7c\x30\xc9\x47\xf3\x16\xb7\xeb\xe3\xdb\xfa\xc3\xe3\xc3\xad\x41\x90\xf8\xad\xf5\xfd\xe1\xb1\x56\xb7\x09\x9c\xb8\xc8\xe2\xc6\x7f\x2a\x8b\xfb\x15\x4e\xa1\x9f\x8f\xc2\x5f\x67\xcf\x7f\xf1\xb3\xe7\xff\x59\xd0\xff\xab\xee\xcf\xf9\x8c\x93\xe5\x97\x03\xc8\xf6\x79\xf3\xd5\x7d\xf5\x1f\x72\xe2\xfd\xf7\x2a\xba\xb6\x79\xe9\x0a\x15\xf0\x76\xc8\xd8\xae\xe1\x8f\xc3\x89\xcf\xd6\xcd\x0f\xc5\x11\xd3\x19\x3b\x63\x03\xc3\x6f\x09\x83\x20\x6f\xeb\x38\xf9\x70\xfb\x58\x33\x1e\x6f\x89\x07\x62\x3c\xae\xd5\x2c\xa7\x86\xd7\xff\x94\x01\xfd\x43\x77\x9e\x7e\x9e\xa9\x1c\xc2\x99\xbf\x14\x7f\xff\xfb\x2f\xff\x17\x00\x00\xff\xff\x4f\x26\x60\xc2\x00\xc7\x00\x00")
 
 func rpProductionJsonBytes() ([]byte, error) {
 	return bindataRead(
diff --git a/pkg/deploy/generator/resources.go b/pkg/deploy/generator/resources.go
index 9792ae6b2..9b00e4772 100644
--- a/pkg/deploy/generator/resources.go
+++ b/pkg/deploy/generator/resources.go
@@ -647,7 +647,6 @@ func (g *generator) lb() *arm.Resource {
 		Condition:  g.conditionStanza("fullDeploy"),
 		APIVersion: azureclient.APIVersion("Microsoft.Network"),
 		DependsOn: []string{
-			"[resourceId('Microsoft.Network/publicIPAddresses', 'portal-pip')]",
 			"[resourceId('Microsoft.Network/publicIPAddresses', 'rp-pip')]",
 		},
 	}
diff --git a/pkg/frontend/admin_openshiftcluster_kubernetesobjects.go b/pkg/frontend/admin_openshiftcluster_kubernetesobjects.go
index 006181760..1c25ab435 100644
--- a/pkg/frontend/admin_openshiftcluster_kubernetesobjects.go
+++ b/pkg/frontend/admin_openshiftcluster_kubernetesobjects.go
@@ -54,15 +54,15 @@ func (f *frontend) _getAdminKubernetesObjects(ctx context.Context, r *http.Reque
 		return nil, err
 	}
 
-	k, err := f.kubeActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc)
+	a, err := f.adminActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc)
 	if err != nil {
 		return nil, err
 	}
 
 	if name != "" {
-		return k.KubeGet(ctx, groupKind, namespace, name)
+		return a.K8sGet(ctx, groupKind, namespace, name)
 	}
-	return k.KubeList(ctx, groupKind, namespace)
+	return a.K8sList(ctx, groupKind, namespace)
 }
 
 func (f *frontend) deleteAdminKubernetesObjects(w http.ResponseWriter, r *http.Request) {
@@ -100,12 +100,12 @@ func (f *frontend) _deleteAdminKubernetesObjects(ctx context.Context, r *http.Re
 		return err
 	}
 
-	k, err := f.kubeActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc)
+	a, err := f.adminActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc)
 	if err != nil {
 		return err
 	}
 
-	return k.KubeDelete(ctx, groupKind, namespace, name)
+	return a.K8sDelete(ctx, groupKind, namespace, name)
 }
 
 func (f *frontend) postAdminKubernetesObjects(w http.ResponseWriter, r *http.Request) {
@@ -154,10 +154,10 @@ func (f *frontend) _postAdminKubernetesObjects(ctx context.Context, r *http.Requ
 		return err
 	}
 
-	k, err := f.kubeActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc)
+	a, err := f.adminActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc)
 	if err != nil {
 		return err
 	}
 
-	return k.KubeCreateOrUpdate(ctx, obj)
+	return a.K8sCreateOrUpdate(ctx, obj)
 }
diff --git a/pkg/frontend/admin_openshiftcluster_redeployvm.go b/pkg/frontend/admin_openshiftcluster_redeployvm.go
index 1e6fe6a68..3a3a91bb2 100644
--- a/pkg/frontend/admin_openshiftcluster_redeployvm.go
+++ b/pkg/frontend/admin_openshiftcluster_redeployvm.go
@@ -51,7 +51,7 @@ func (f *frontend) _postAdminOpenShiftClusterRedeployVM(ctx context.Context, r *
 		return err
 	}
 
-	a, err := f.azureActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc)
+	a, err := f.adminActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/frontend/admin_openshiftcluster_resources_list.go b/pkg/frontend/admin_openshiftcluster_resources_list.go
index a3b0490be..262f98073 100644
--- a/pkg/frontend/admin_openshiftcluster_resources_list.go
+++ b/pkg/frontend/admin_openshiftcluster_resources_list.go
@@ -47,7 +47,7 @@ func (f *frontend) _listAdminOpenShiftClusterResources(
 		return nil, err
 	}
 
-	a, err := f.azureActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc)
+	a, err := f.adminActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/frontend/admin_openshiftcluster_serialconsole.go b/pkg/frontend/admin_openshiftcluster_serialconsole.go
index 3f9a6a016..6fa5bc115 100644
--- a/pkg/frontend/admin_openshiftcluster_serialconsole.go
+++ b/pkg/frontend/admin_openshiftcluster_serialconsole.go
@@ -51,7 +51,7 @@ func (f *frontend) _getAdminOpenShiftClusterSerialConsole(ctx context.Context, w
 		return err
 	}
 
-	a, err := f.azureActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc)
+	a, err := f.adminActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/frontend/admin_openshiftcluster_upgrade.go b/pkg/frontend/admin_openshiftcluster_upgrade.go
index ed995c2f3..adc7ea8de 100644
--- a/pkg/frontend/admin_openshiftcluster_upgrade.go
+++ b/pkg/frontend/admin_openshiftcluster_upgrade.go
@@ -47,10 +47,10 @@ func (f *frontend) _postAdminOpenShiftUpgrade(ctx context.Context, r *http.Reque
 		return err
 	}
 
-	k, err := f.kubeActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc)
+	a, err := f.adminActionsFactory(log, f.env, doc.OpenShiftCluster, subscriptionDoc)
 	if err != nil {
 		return err
 	}
 
-	return k.Upgrade(ctx, upgradeY)
+	return a.Upgrade(ctx, upgradeY)
 }
diff --git a/pkg/frontend/adminactions/adminactions.go b/pkg/frontend/adminactions/adminactions.go
new file mode 100644
index 000000000..80fbd66c1
--- /dev/null
+++ b/pkg/frontend/adminactions/adminactions.go
@@ -0,0 +1,129 @@
+package adminactions
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"context"
+	"net/http"
+
+	configclient "github.com/openshift/client-go/config/clientset/versioned"
+	"github.com/sirupsen/logrus"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/client-go/kubernetes"
+
+	"github.com/Azure/ARO-RP/pkg/api"
+	"github.com/Azure/ARO-RP/pkg/env"
+	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/compute"
+	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/features"
+	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/network"
+	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/storage"
+	"github.com/Azure/ARO-RP/pkg/util/dynamichelper"
+	"github.com/Azure/ARO-RP/pkg/util/restconfig"
+	"github.com/Azure/ARO-RP/pkg/util/stringutils"
+)
+
+// Interface for adminactions
+type Interface interface {
+	K8sGet(ctx context.Context, groupKind, namespace, name string) ([]byte, error)
+	K8sList(ctx context.Context, groupKind, namespace string) ([]byte, error)
+	K8sCreateOrUpdate(ctx context.Context, obj *unstructured.Unstructured) error
+	K8sDelete(ctx context.Context, groupKind, namespace, name string) error
+	ResourcesList(ctx context.Context) ([]byte, error)
+	Upgrade(ctx context.Context, upgradeY bool) error
+	VMRedeployAndWait(ctx context.Context, vmName string) error
+	VMSerialConsole(ctx context.Context, w http.ResponseWriter,
+		log *logrus.Entry, vmName string) error
+}
+
+type adminactions struct {
+	log *logrus.Entry
+	env env.Interface
+	oc  *api.OpenShiftCluster
+	dh  dynamichelper.Interface
+
+	kubernetescli kubernetes.Interface
+	configcli     configclient.Interface
+
+	resources       features.ResourcesClient
+	virtualMachines compute.VirtualMachinesClient
+	virtualNetworks network.VirtualNetworksClient
+	routeTables     network.RouteTablesClient
+	storageAccounts storage.AccountsClient
+}
+
+// New returns an adminactions Interface
+func New(log *logrus.Entry, env env.Interface, oc *api.OpenShiftCluster,
+	subscriptionDoc *api.SubscriptionDocument) (Interface, error) {
+
+	restConfig, err := restconfig.RestConfig(env, oc)
+	if err != nil {
+		return nil, err
+	}
+
+	dh, err := dynamichelper.New(log, restConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	kubernetescli, err := kubernetes.NewForConfig(restConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	configcli, err := configclient.NewForConfig(restConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	fpAuth, err := env.FPAuthorizer(subscriptionDoc.Subscription.Properties.TenantID,
+		env.Environment().ResourceManagerEndpoint)
+	if err != nil {
+		return nil, err
+	}
+
+	return &adminactions{
+		log: log,
+		env: env,
+		oc:  oc,
+		dh:  dh,
+
+		kubernetescli: kubernetescli,
+		configcli:     configcli,
+
+		resources:       features.NewResourcesClient(env.Environment(), subscriptionDoc.ID, fpAuth),
+		virtualMachines: compute.NewVirtualMachinesClient(env.Environment(), subscriptionDoc.ID, fpAuth),
+		virtualNetworks: network.NewVirtualNetworksClient(env.Environment(), subscriptionDoc.ID, fpAuth),
+		routeTables:     network.NewRouteTablesClient(env.Environment(), subscriptionDoc.ID, fpAuth),
+		storageAccounts: storage.NewAccountsClient(env.Environment(), subscriptionDoc.ID, fpAuth),
+	}, nil
+}
+
+func (a *adminactions) K8sGet(ctx context.Context, groupKind, namespace, name string) ([]byte, error) {
+	un, err := a.dh.Get(ctx, groupKind, namespace, name)
+	if err != nil {
+		return nil, err
+	}
+	return un.MarshalJSON()
+}
+
+func (a *adminactions) K8sList(ctx context.Context, groupKind, namespace string) ([]byte, error) {
+	ul, err := a.dh.List(ctx, groupKind, namespace)
+	if err != nil {
+		return nil, err
+	}
+	return ul.MarshalJSON()
+}
+
+func (a *adminactions) K8sCreateOrUpdate(ctx context.Context, obj *unstructured.Unstructured) error {
+	return a.dh.CreateOrUpdate(ctx, obj)
+}
+
+func (a *adminactions) K8sDelete(ctx context.Context, groupKind, namespace, name string) error {
+	return a.dh.Delete(ctx, groupKind, namespace, name)
+}
+
+func (a *adminactions) VMRedeployAndWait(ctx context.Context, vmName string) error {
+	clusterRGName := stringutils.LastTokenByte(a.oc.Properties.ClusterProfile.ResourceGroupID, '/')
+	return a.virtualMachines.RedeployAndWait(ctx, clusterRGName, vmName)
+}
diff --git a/pkg/frontend/adminactions/azureactions.go b/pkg/frontend/adminactions/azureactions.go
deleted file mode 100644
index 7d0e3df6a..000000000
--- a/pkg/frontend/adminactions/azureactions.go
+++ /dev/null
@@ -1,66 +0,0 @@
-package adminactions
-
-// Copyright (c) Microsoft Corporation.
-// Licensed under the Apache License 2.0.
-
-import (
-	"context"
-	"net/http"
-
-	"github.com/sirupsen/logrus"
-
-	"github.com/Azure/ARO-RP/pkg/api"
-	"github.com/Azure/ARO-RP/pkg/env"
-	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/compute"
-	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/features"
-	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/network"
-	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/storage"
-	"github.com/Azure/ARO-RP/pkg/util/stringutils"
-)
-
-// AzureActions contains those actions which rely solely on Azure clients, not using any k8s clients
-type AzureActions interface {
-	ResourcesList(ctx context.Context) ([]byte, error)
-	VMRedeployAndWait(ctx context.Context, vmName string) error
-	VMSerialConsole(ctx context.Context, w http.ResponseWriter, log *logrus.Entry, vmName string) error
-}
-
-type azureActions struct {
-	log *logrus.Entry
-	env env.Interface
-	oc  *api.OpenShiftCluster
-
-	resources       features.ResourcesClient
-	virtualMachines compute.VirtualMachinesClient
-	virtualNetworks network.VirtualNetworksClient
-	routeTables     network.RouteTablesClient
-	storageAccounts storage.AccountsClient
-}
-
-// NewAzureActions returns an azureActions
-func NewAzureActions(log *logrus.Entry, env env.Interface, oc *api.OpenShiftCluster,
-	subscriptionDoc *api.SubscriptionDocument) (AzureActions, error) {
-
-	fpAuth, err := env.FPAuthorizer(subscriptionDoc.Subscription.Properties.TenantID,
-		env.Environment().ResourceManagerEndpoint)
-	if err != nil {
-		return nil, err
-	}
-
-	return &azureActions{
-		log: log,
-		env: env,
-		oc:  oc,
-
-		resources:       features.NewResourcesClient(env.Environment(), subscriptionDoc.ID, fpAuth),
-		virtualMachines: compute.NewVirtualMachinesClient(env.Environment(), subscriptionDoc.ID, fpAuth),
-		virtualNetworks: network.NewVirtualNetworksClient(env.Environment(), subscriptionDoc.ID, fpAuth),
-		routeTables:     network.NewRouteTablesClient(env.Environment(), subscriptionDoc.ID, fpAuth),
-		storageAccounts: storage.NewAccountsClient(env.Environment(), subscriptionDoc.ID, fpAuth),
-	}, nil
-}
-
-func (a *azureActions) VMRedeployAndWait(ctx context.Context, vmName string) error {
-	clusterRGName := stringutils.LastTokenByte(a.oc.Properties.ClusterProfile.ResourceGroupID, '/')
-	return a.virtualMachines.RedeployAndWait(ctx, clusterRGName, vmName)
-}
diff --git a/pkg/frontend/adminactions/generate.go b/pkg/frontend/adminactions/generate.go
index 0462f2241..eb9b8c6f5 100644
--- a/pkg/frontend/adminactions/generate.go
+++ b/pkg/frontend/adminactions/generate.go
@@ -4,5 +4,5 @@ package adminactions
 // Licensed under the Apache License 2.0.
 
 //go:generate rm -rf ../../util/mocks/$GOPACKAGE
-//go:generate go run ../../../vendor/github.com/golang/mock/mockgen -destination=../../util/mocks/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/frontend/$GOPACKAGE KubeActions,AzureActions
+//go:generate go run ../../../vendor/github.com/golang/mock/mockgen -destination=../../util/mocks/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/frontend/$GOPACKAGE Interface
 //go:generate go run ../../../vendor/golang.org/x/tools/cmd/goimports -local=github.com/Azure/ARO-RP -e -w ../../util/mocks/$GOPACKAGE/$GOPACKAGE.go
diff --git a/pkg/frontend/adminactions/kubeactions.go b/pkg/frontend/adminactions/kubeactions.go
deleted file mode 100644
index cfe9ca893..000000000
--- a/pkg/frontend/adminactions/kubeactions.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package adminactions
-
-// Copyright (c) Microsoft Corporation.
-// Licensed under the Apache License 2.0.
-
-import (
-	"context"
-
-	configclient "github.com/openshift/client-go/config/clientset/versioned"
-	"github.com/sirupsen/logrus"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-
-	"github.com/Azure/ARO-RP/pkg/api"
-	"github.com/Azure/ARO-RP/pkg/env"
-	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/network"
-	"github.com/Azure/ARO-RP/pkg/util/dynamichelper"
-	"github.com/Azure/ARO-RP/pkg/util/restconfig"
-)
-
-// KubeActions are those that involve k8s objects, and thus depend upon k8s clients being createable
-type KubeActions interface {
-	KubeGet(ctx context.Context, groupKind, namespace, name string) ([]byte, error)
-	KubeList(ctx context.Context, groupKind, namespace string) ([]byte, error)
-	KubeCreateOrUpdate(ctx context.Context, obj *unstructured.Unstructured) error
-	KubeDelete(ctx context.Context, groupKind, namespace, name string) error
-	Upgrade(ctx context.Context, upgradeY bool) error
-}
-
-type kubeActions struct {
-	log       *logrus.Entry
-	oc        *api.OpenShiftCluster
-	dh        dynamichelper.Interface
-	configcli configclient.Interface
-
-	virtualNetworks network.VirtualNetworksClient
-}
-
-// NewKubeActions returns a kubeActions
-func NewKubeActions(log *logrus.Entry, env env.Interface, oc *api.OpenShiftCluster,
-	subscriptionDoc *api.SubscriptionDocument) (KubeActions, error) {
-
-	restConfig, err := restconfig.RestConfig(env, oc)
-	if err != nil {
-		return nil, err
-	}
-
-	dh, err := dynamichelper.New(log, restConfig)
-	if err != nil {
-		return nil, err
-	}
-
-	configcli, err := configclient.NewForConfig(restConfig)
-	if err != nil {
-		return nil, err
-	}
-
-	fpAuth, err := env.FPAuthorizer(subscriptionDoc.Subscription.Properties.TenantID,
-		env.Environment().ResourceManagerEndpoint)
-	if err != nil {
-		return nil, err
-	}
-
-	return &kubeActions{
-		log:       log,
-		oc:        oc,
-		dh:        dh,
-		configcli: configcli,
-
-		virtualNetworks: network.NewVirtualNetworksClient(env.Environment(), subscriptionDoc.ID, fpAuth),
-	}, nil
-}
-
-func (k *kubeActions) KubeGet(ctx context.Context, groupKind, namespace, name string) ([]byte, error) {
-	un, err := k.dh.Get(ctx, groupKind, namespace, name)
-	if err != nil {
-		return nil, err
-	}
-	return un.MarshalJSON()
-}
-
-func (k *kubeActions) KubeList(ctx context.Context, groupKind, namespace string) ([]byte, error) {
-	ul, err := k.dh.List(ctx, groupKind, namespace)
-	if err != nil {
-		return nil, err
-	}
-	return ul.MarshalJSON()
-}
-
-func (k *kubeActions) KubeCreateOrUpdate(ctx context.Context, obj *unstructured.Unstructured) error {
-	return k.dh.CreateOrUpdate(ctx, obj)
-}
-
-func (k *kubeActions) KubeDelete(ctx context.Context, groupKind, namespace, name string) error {
-	return k.dh.Delete(ctx, groupKind, namespace, name)
-}
diff --git a/pkg/frontend/adminactions/resources_list.go b/pkg/frontend/adminactions/resources_list.go
index 7bb6e6ca6..eac6269dd 100644
--- a/pkg/frontend/adminactions/resources_list.go
+++ b/pkg/frontend/adminactions/resources_list.go
@@ -17,7 +17,7 @@ import (
 	"github.com/Azure/ARO-RP/pkg/util/subnet"
 )
 
-func (a *azureActions) ResourcesList(ctx context.Context) ([]byte, error) {
+func (a *adminactions) ResourcesList(ctx context.Context) ([]byte, error) {
 	clusterRGName := stringutils.LastTokenByte(a.oc.Properties.ClusterProfile.ResourceGroupID, '/')
 
 	resources, err := a.resources.ListByResourceGroup(ctx, clusterRGName, "", "", nil)
@@ -59,7 +59,7 @@ func (a *azureActions) ResourcesList(ctx context.Context) ([]byte, error) {
 	return json.Marshal(armResources)
 }
 
-func (a *azureActions) appendAzureNetworkResources(ctx context.Context, armResources []arm.Resource) ([]arm.Resource, error) {
+func (a *adminactions) appendAzureNetworkResources(ctx context.Context, armResources []arm.Resource) ([]arm.Resource, error) {
 	vNetID, _, err := subnet.Split(a.oc.Properties.MasterProfile.SubnetID)
 	if err != nil {
 		return nil, err
diff --git a/pkg/frontend/adminactions/vmserialconsole.go b/pkg/frontend/adminactions/serialtool.go
similarity index 97%
rename from pkg/frontend/adminactions/vmserialconsole.go
rename to pkg/frontend/adminactions/serialtool.go
index f236417b7..07c9601e9 100644
--- a/pkg/frontend/adminactions/vmserialconsole.go
+++ b/pkg/frontend/adminactions/serialtool.go
@@ -21,7 +21,7 @@ import (
 	"github.com/Azure/ARO-RP/pkg/util/stringutils"
 )
 
-func (a *azureActions) VMSerialConsole(ctx context.Context, w http.ResponseWriter,
+func (a *adminactions) VMSerialConsole(ctx context.Context, w http.ResponseWriter,
 	log *logrus.Entry, vmName string) error {
 
 	clusterRGName := stringutils.LastTokenByte(a.oc.Properties.ClusterProfile.ResourceGroupID, '/')
diff --git a/pkg/frontend/adminactions/upgrade.go b/pkg/frontend/adminactions/upgrade.go
index 28d158943..bd78fe588 100644
--- a/pkg/frontend/adminactions/upgrade.go
+++ b/pkg/frontend/adminactions/upgrade.go
@@ -22,13 +22,13 @@ import (
 	"github.com/Azure/ARO-RP/pkg/util/version"
 )
 
-func (k *kubeActions) Upgrade(ctx context.Context, upgradeY bool) error {
-	err := preUpgradeChecks(ctx, k.oc, k.virtualNetworks)
+func (a *adminactions) Upgrade(ctx context.Context, upgradeY bool) error {
+	err := preUpgradeChecks(ctx, a.oc, a.virtualNetworks)
 	if err != nil {
 		return err
 	}
 
-	return upgrade(ctx, k.log, k.configcli, version.Streams, upgradeY)
+	return upgrade(ctx, a.log, a.configcli, version.Streams, upgradeY)
 }
 
 func upgrade(ctx context.Context, log *logrus.Entry, configcli configclient.Interface, streams []*version.Stream, upgradeY bool) error {
diff --git a/pkg/frontend/frontend.go b/pkg/frontend/frontend.go
index 15c4172cb..0d680c224 100644
--- a/pkg/frontend/frontend.go
+++ b/pkg/frontend/frontend.go
@@ -37,11 +37,8 @@ func (err statusCodeError) Error() string {
 	return fmt.Sprintf("%d", err)
 }
 
-type kubeActionsFactory func(*logrus.Entry, env.Interface, *api.OpenShiftCluster,
-	*api.SubscriptionDocument) (adminactions.KubeActions, error)
-
-type azureActionsFactory func(*logrus.Entry, env.Interface, *api.OpenShiftCluster,
-	*api.SubscriptionDocument) (adminactions.AzureActions, error)
+type adminActionsFactory func(*logrus.Entry, env.Interface, *api.OpenShiftCluster,
+	*api.SubscriptionDocument) (adminactions.Interface, error)
 
 type frontend struct {
 	baseLog *logrus.Entry
@@ -56,8 +53,7 @@ type frontend struct {
 	aead encryption.AEAD
 
 	ocEnricher          clusterdata.OpenShiftClusterEnricher
-	kubeActionsFactory  kubeActionsFactory
-	azureActionsFactory azureActionsFactory
+	adminActionsFactory adminActionsFactory
 
 	l net.Listener
 	s *http.Server
@@ -66,8 +62,6 @@ type frontend struct {
 
 	startTime time.Time
 	ready     atomic.Value
-
-	now func() time.Time
 }
 
 // Runnable represents a runnable object
@@ -85,8 +79,7 @@ func NewFrontend(ctx context.Context,
 	apis map[string]*api.Version,
 	m metrics.Interface,
 	aead encryption.AEAD,
-	kubeActionsFactory kubeActionsFactory,
-	azureActionsFactory azureActionsFactory) (Runnable, error) {
+	adminActionsFactory adminActionsFactory) (Runnable, error) {
 	f := &frontend{
 		baseLog:             baseLog,
 		env:                 _env,
@@ -96,16 +89,13 @@ func NewFrontend(ctx context.Context,
 		apis:                apis,
 		m:                   m,
 		aead:                aead,
-		kubeActionsFactory:  kubeActionsFactory,
-		azureActionsFactory: azureActionsFactory,
+		adminActionsFactory: adminActionsFactory,
 
 		ocEnricher: clusterdata.NewBestEffortEnricher(baseLog, _env, m),
 
 		bucketAllocator: &bucket.Random{},
 
 		startTime: time.Now(),
-
-		now: time.Now,
 	}
 
 	l, err := f.env.Listen()
diff --git a/pkg/frontend/openshiftcluster_putorpatch.go b/pkg/frontend/openshiftcluster_putorpatch.go
index 468b7d89c..132166e86 100644
--- a/pkg/frontend/openshiftcluster_putorpatch.go
+++ b/pkg/frontend/openshiftcluster_putorpatch.go
@@ -73,7 +73,6 @@ func (f *frontend) _putOrPatchOpenShiftCluster(ctx context.Context, r *http.Requ
 				Properties: api.OpenShiftClusterProperties{
 					ArchitectureVersion: version.InstallArchitectureVersion,
 					ProvisioningState:   api.ProvisioningStateSucceeded,
-					CreatedAt:           f.now().UTC(),
 					CreatedBy:           version.GitCommit,
 					ProvisionedBy:       version.GitCommit,
 					ClusterProfile: api.ClusterProfile{
diff --git a/pkg/monitor/cluster/summary.go b/pkg/monitor/cluster/summary.go
index 70ee9cf61..caf1e10ca 100644
--- a/pkg/monitor/cluster/summary.go
+++ b/pkg/monitor/cluster/summary.go
@@ -46,7 +46,6 @@ func (mon *Monitor) emitSummary(ctx context.Context) error {
 		"masterCount":       strconv.Itoa(masterCount),
 		"workerCount":       strconv.Itoa(workerCount),
 		"provisioningState": mon.oc.Properties.ProvisioningState.String(),
-		"createdAt":         mon.oc.Properties.CreatedAt.String(),
 	})
 
 	return nil
diff --git a/pkg/operator/apis/aro.openshift.io/v1alpha1/cluster_types.go b/pkg/operator/apis/aro.openshift.io/v1alpha1/cluster_types.go
index bc994fd7d..1be22e4b2 100644
--- a/pkg/operator/apis/aro.openshift.io/v1alpha1/cluster_types.go
+++ b/pkg/operator/apis/aro.openshift.io/v1alpha1/cluster_types.go
@@ -39,8 +39,9 @@ type ClusterSpec struct {
 	Location        string              `json:"location,omitempty"`
 	GenevaLogging   GenevaLoggingSpec   `json:"genevaLogging,omitempty"`
 	InternetChecker InternetCheckerSpec `json:"internetChecker,omitempty"`
-	VNetID          string              `json:"vnetId,omitempty"`
-	Features        FeaturesSpec        `json:"features,omitempty"`
+	VnetID          string              `json:"vnetId,omitempty"`
+
+	Features FeaturesSpec `json:"features,omitempty"`
 }
 
 // FeaturesSpec defines ARO operator feature gates
diff --git a/pkg/operator/deploy/deploy.go b/pkg/operator/deploy/deploy.go
index 84bc18be1..33b596081 100644
--- a/pkg/operator/deploy/deploy.go
+++ b/pkg/operator/deploy/deploy.go
@@ -167,7 +167,7 @@ func (o *operator) resources() ([]runtime.Object, error) {
 				ACRDomain:     o.env.ACRDomain(),
 				AZEnvironment: o.env.Environment().Name,
 				Location:      o.env.Location(),
-				VNetID:        vnetID,
+				VnetID:        vnetID,
 				GenevaLogging: arov1alpha1.GenevaLoggingSpec{
 					ConfigVersion:            o.env.ClustersGenevaLoggingConfigVersion(),
 					MonitoringGCSEnvironment: o.env.ClustersGenevaLoggingEnvironment(),
diff --git a/pkg/util/aad/aad.go b/pkg/util/aad/aad.go
index 74979a6ec..071bad8be 100644
--- a/pkg/util/aad/aad.go
+++ b/pkg/util/aad/aad.go
@@ -20,8 +20,10 @@ import (
 
 // GetToken authenticates in the customer's tenant as the cluster service
 // principal and returns a token.
-func GetToken(ctx context.Context, log *logrus.Entry, spp *api.ServicePrincipalProfile, tenantID, resource string) (*adal.ServicePrincipalToken, error) {
-	conf := auth.NewClientCredentialsConfig(spp.ClientID, string(spp.ClientSecret), tenantID)
+func GetToken(ctx context.Context, log *logrus.Entry, oc *api.OpenShiftCluster, subscriptionDoc *api.SubscriptionDocument, resource string) (*adal.ServicePrincipalToken, error) {
+	spp := &oc.Properties.ServicePrincipalProfile
+
+	conf := auth.NewClientCredentialsConfig(spp.ClientID, string(spp.ClientSecret), subscriptionDoc.Subscription.Properties.TenantID)
 	conf.Resource = resource
 
 	sp, err := conf.ServicePrincipalToken()
diff --git a/pkg/util/dns/dns.go b/pkg/util/dns/dns.go
index 6294b608a..a370755c5 100644
--- a/pkg/util/dns/dns.go
+++ b/pkg/util/dns/dns.go
@@ -90,22 +90,6 @@ func (m *manager) CreateOrUpdateRouter(ctx context.Context, oc *api.OpenShiftClu
 		return err
 	}
 
-	var isCreate bool
-	rs, err := m.recordsets.Get(ctx, m.env.ResourceGroup(), m.env.Domain(), "*.apps."+prefix, mgmtdns.A)
-	if detailedErr, ok := err.(autorest.DetailedError); ok &&
-		detailedErr.StatusCode == http.StatusNotFound {
-		isCreate = true
-	}
-
-	// If record exists and routerIP already match - skip CreateOrUpdate
-	if err == nil && !isCreate {
-		for _, a := range *rs.ARecords {
-			if *a.Ipv4Address == routerIP {
-				return nil
-			}
-		}
-	}
-
 	_, err = m.recordsets.CreateOrUpdate(ctx, m.env.ResourceGroup(), m.env.Domain(), "*.apps."+prefix, mgmtdns.A, mgmtdns.RecordSet{
 		RecordSetProperties: &mgmtdns.RecordSetProperties{
 			TTL: to.Int64Ptr(300),
diff --git a/pkg/util/dns/generate.go b/pkg/util/dns/generate.go
deleted file mode 100644
index ac1cde51f..000000000
--- a/pkg/util/dns/generate.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package dns
-
-// Copyright (c) Microsoft Corporation.
-// Licensed under the Apache License 2.0.
-
-//go:generate rm -rf ../mocks/$GOPACKAGE
-//go:generate go run ../../../vendor/github.com/golang/mock/mockgen -destination=../mocks/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/util/$GOPACKAGE Manager
-//go:generate go run ../../../vendor/golang.org/x/tools/cmd/goimports -local=github.com/Azure/ARO-RP -e -w ../mocks/$GOPACKAGE/$GOPACKAGE.go
diff --git a/pkg/util/dynamichelper/discovery/bindata.go b/pkg/util/dynamichelper/discovery/bindata.go
index f62c4d632..8844432d3 100644
--- a/pkg/util/dynamichelper/discovery/bindata.go
+++ b/pkg/util/dynamichelper/discovery/bindata.go
@@ -330,7 +330,7 @@ func aroOpenshiftIoV1alpha1ServerresourcesJson() (*asset, error) {
 	return a, nil
 }
 
-var _assets_version = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x32\xd1\x33\xd5\x33\x32\xe7\x02\x04\x00\x00\xff\xff\x56\xad\xf7\x82\x07\x00\x00\x00")
+var _assets_version = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x32\xd1\x33\xd5\x33\x32\xe2\x02\x04\x00\x00\xff\xff\x13\x59\x80\xff\x07\x00\x00\x00")
 
 func assets_versionBytes() ([]byte, error) {
 	return bindataRead(
diff --git a/pkg/util/dynamichelper/discovery/cache/assets_version b/pkg/util/dynamichelper/discovery/cache/assets_version
index eee92bed3..b9befeb98 100644
--- a/pkg/util/dynamichelper/discovery/cache/assets_version
+++ b/pkg/util/dynamichelper/discovery/cache/assets_version
@@ -1,1 +1,1 @@
-4.5.27
+4.5.22
diff --git a/pkg/util/mocks/adminactions/adminactions.go b/pkg/util/mocks/adminactions/adminactions.go
index 3bb3a628b..e48e5dc2d 100644
--- a/pkg/util/mocks/adminactions/adminactions.go
+++ b/pkg/util/mocks/adminactions/adminactions.go
@@ -1,5 +1,5 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/Azure/ARO-RP/pkg/frontend/adminactions (interfaces: KubeActions,AzureActions)
+// Source: github.com/Azure/ARO-RP/pkg/frontend/adminactions (interfaces: Interface)
 
 // Package mock_adminactions is a generated GoMock package.
 package mock_adminactions
@@ -14,126 +14,89 @@ import (
 	unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 )
 
-// MockKubeActions is a mock of KubeActions interface
-type MockKubeActions struct {
+// MockInterface is a mock of Interface interface
+type MockInterface struct {
 	ctrl     *gomock.Controller
-	recorder *MockKubeActionsMockRecorder
+	recorder *MockInterfaceMockRecorder
 }
 
-// MockKubeActionsMockRecorder is the mock recorder for MockKubeActions
-type MockKubeActionsMockRecorder struct {
-	mock *MockKubeActions
+// MockInterfaceMockRecorder is the mock recorder for MockInterface
+type MockInterfaceMockRecorder struct {
+	mock *MockInterface
 }
 
-// NewMockKubeActions creates a new mock instance
-func NewMockKubeActions(ctrl *gomock.Controller) *MockKubeActions {
-	mock := &MockKubeActions{ctrl: ctrl}
-	mock.recorder = &MockKubeActionsMockRecorder{mock}
+// NewMockInterface creates a new mock instance
+func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
+	mock := &MockInterface{ctrl: ctrl}
+	mock.recorder = &MockInterfaceMockRecorder{mock}
 	return mock
 }
 
 // EXPECT returns an object that allows the caller to indicate expected use
-func (m *MockKubeActions) EXPECT() *MockKubeActionsMockRecorder {
+func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
 	return m.recorder
 }
 
-// KubeCreateOrUpdate mocks base method
-func (m *MockKubeActions) KubeCreateOrUpdate(arg0 context.Context, arg1 *unstructured.Unstructured) error {
+// K8sCreateOrUpdate mocks base method
+func (m *MockInterface) K8sCreateOrUpdate(arg0 context.Context, arg1 *unstructured.Unstructured) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "KubeCreateOrUpdate", arg0, arg1)
+	ret := m.ctrl.Call(m, "K8sCreateOrUpdate", arg0, arg1)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
-// KubeCreateOrUpdate indicates an expected call of KubeCreateOrUpdate
-func (mr *MockKubeActionsMockRecorder) KubeCreateOrUpdate(arg0, arg1 interface{}) *gomock.Call {
+// K8sCreateOrUpdate indicates an expected call of K8sCreateOrUpdate
+func (mr *MockInterfaceMockRecorder) K8sCreateOrUpdate(arg0, arg1 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KubeCreateOrUpdate", reflect.TypeOf((*MockKubeActions)(nil).KubeCreateOrUpdate), arg0, arg1)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "K8sCreateOrUpdate", reflect.TypeOf((*MockInterface)(nil).K8sCreateOrUpdate), arg0, arg1)
 }
 
-// KubeDelete mocks base method
-func (m *MockKubeActions) KubeDelete(arg0 context.Context, arg1, arg2, arg3 string) error {
+// K8sDelete mocks base method
+func (m *MockInterface) K8sDelete(arg0 context.Context, arg1, arg2, arg3 string) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "KubeDelete", arg0, arg1, arg2, arg3)
+	ret := m.ctrl.Call(m, "K8sDelete", arg0, arg1, arg2, arg3)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
-// KubeDelete indicates an expected call of KubeDelete
-func (mr *MockKubeActionsMockRecorder) KubeDelete(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
+// K8sDelete indicates an expected call of K8sDelete
+func (mr *MockInterfaceMockRecorder) K8sDelete(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KubeDelete", reflect.TypeOf((*MockKubeActions)(nil).KubeDelete), arg0, arg1, arg2, arg3)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "K8sDelete", reflect.TypeOf((*MockInterface)(nil).K8sDelete), arg0, arg1, arg2, arg3)
 }
 
-// KubeGet mocks base method
-func (m *MockKubeActions) KubeGet(arg0 context.Context, arg1, arg2, arg3 string) ([]byte, error) {
+// K8sGet mocks base method
+func (m *MockInterface) K8sGet(arg0 context.Context, arg1, arg2, arg3 string) ([]byte, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "KubeGet", arg0, arg1, arg2, arg3)
+	ret := m.ctrl.Call(m, "K8sGet", arg0, arg1, arg2, arg3)
 	ret0, _ := ret[0].([]byte)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
-// KubeGet indicates an expected call of KubeGet
-func (mr *MockKubeActionsMockRecorder) KubeGet(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
+// K8sGet indicates an expected call of K8sGet
+func (mr *MockInterfaceMockRecorder) K8sGet(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KubeGet", reflect.TypeOf((*MockKubeActions)(nil).KubeGet), arg0, arg1, arg2, arg3)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "K8sGet", reflect.TypeOf((*MockInterface)(nil).K8sGet), arg0, arg1, arg2, arg3)
 }
 
-// KubeList mocks base method
-func (m *MockKubeActions) KubeList(arg0 context.Context, arg1, arg2 string) ([]byte, error) {
+// K8sList mocks base method
+func (m *MockInterface) K8sList(arg0 context.Context, arg1, arg2 string) ([]byte, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "KubeList", arg0, arg1, arg2)
+	ret := m.ctrl.Call(m, "K8sList", arg0, arg1, arg2)
 	ret0, _ := ret[0].([]byte)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
-// KubeList indicates an expected call of KubeList
-func (mr *MockKubeActionsMockRecorder) KubeList(arg0, arg1, arg2 interface{}) *gomock.Call {
+// K8sList indicates an expected call of K8sList
+func (mr *MockInterfaceMockRecorder) K8sList(arg0, arg1, arg2 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KubeList", reflect.TypeOf((*MockKubeActions)(nil).KubeList), arg0, arg1, arg2)
-}
-
-// Upgrade mocks base method
-func (m *MockKubeActions) Upgrade(arg0 context.Context, arg1 bool) error {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Upgrade", arg0, arg1)
-	ret0, _ := ret[0].(error)
-	return ret0
-}
-
-// Upgrade indicates an expected call of Upgrade
-func (mr *MockKubeActionsMockRecorder) Upgrade(arg0, arg1 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Upgrade", reflect.TypeOf((*MockKubeActions)(nil).Upgrade), arg0, arg1)
-}
-
-// MockAzureActions is a mock of AzureActions interface
-type MockAzureActions struct {
-	ctrl     *gomock.Controller
-	recorder *MockAzureActionsMockRecorder
-}
-
-// MockAzureActionsMockRecorder is the mock recorder for MockAzureActions
-type MockAzureActionsMockRecorder struct {
-	mock *MockAzureActions
-}
-
-// NewMockAzureActions creates a new mock instance
-func NewMockAzureActions(ctrl *gomock.Controller) *MockAzureActions {
-	mock := &MockAzureActions{ctrl: ctrl}
-	mock.recorder = &MockAzureActionsMockRecorder{mock}
-	return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use
-func (m *MockAzureActions) EXPECT() *MockAzureActionsMockRecorder {
-	return m.recorder
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "K8sList", reflect.TypeOf((*MockInterface)(nil).K8sList), arg0, arg1, arg2)
 }
 
 // ResourcesList mocks base method
-func (m *MockAzureActions) ResourcesList(arg0 context.Context) ([]byte, error) {
+func (m *MockInterface) ResourcesList(arg0 context.Context) ([]byte, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "ResourcesList", arg0)
 	ret0, _ := ret[0].([]byte)
@@ -142,13 +105,27 @@ func (m *MockAzureActions) ResourcesList(arg0 context.Context) ([]byte, error) {
 }
 
 // ResourcesList indicates an expected call of ResourcesList
-func (mr *MockAzureActionsMockRecorder) ResourcesList(arg0 interface{}) *gomock.Call {
+func (mr *MockInterfaceMockRecorder) ResourcesList(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResourcesList", reflect.TypeOf((*MockInterface)(nil).ResourcesList), arg0)
+}
+
+// Upgrade mocks base method
+func (m *MockInterface) Upgrade(arg0 context.Context, arg1 bool) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Upgrade", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Upgrade indicates an expected call of Upgrade
+func (mr *MockInterfaceMockRecorder) Upgrade(arg0, arg1 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResourcesList", reflect.TypeOf((*MockAzureActions)(nil).ResourcesList), arg0)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Upgrade", reflect.TypeOf((*MockInterface)(nil).Upgrade), arg0, arg1)
 }
 
 // VMRedeployAndWait mocks base method
-func (m *MockAzureActions) VMRedeployAndWait(arg0 context.Context, arg1 string) error {
+func (m *MockInterface) VMRedeployAndWait(arg0 context.Context, arg1 string) error {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "VMRedeployAndWait", arg0, arg1)
 	ret0, _ := ret[0].(error)
@@ -156,13 +133,13 @@ func (m *MockAzureActions) VMRedeployAndWait(arg0 context.Context, arg1 string)
 }
 
 // VMRedeployAndWait indicates an expected call of VMRedeployAndWait
-func (mr *MockAzureActionsMockRecorder) VMRedeployAndWait(arg0, arg1 interface{}) *gomock.Call {
+func (mr *MockInterfaceMockRecorder) VMRedeployAndWait(arg0, arg1 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VMRedeployAndWait", reflect.TypeOf((*MockAzureActions)(nil).VMRedeployAndWait), arg0, arg1)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VMRedeployAndWait", reflect.TypeOf((*MockInterface)(nil).VMRedeployAndWait), arg0, arg1)
 }
 
 // VMSerialConsole mocks base method
-func (m *MockAzureActions) VMSerialConsole(arg0 context.Context, arg1 http.ResponseWriter, arg2 *logrus.Entry, arg3 string) error {
+func (m *MockInterface) VMSerialConsole(arg0 context.Context, arg1 http.ResponseWriter, arg2 *logrus.Entry, arg3 string) error {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "VMSerialConsole", arg0, arg1, arg2, arg3)
 	ret0, _ := ret[0].(error)
@@ -170,7 +147,7 @@ func (m *MockAzureActions) VMSerialConsole(arg0 context.Context, arg1 http.Respo
 }
 
 // VMSerialConsole indicates an expected call of VMSerialConsole
-func (mr *MockAzureActionsMockRecorder) VMSerialConsole(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
+func (mr *MockInterfaceMockRecorder) VMSerialConsole(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VMSerialConsole", reflect.TypeOf((*MockAzureActions)(nil).VMSerialConsole), arg0, arg1, arg2, arg3)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VMSerialConsole", reflect.TypeOf((*MockInterface)(nil).VMSerialConsole), arg0, arg1, arg2, arg3)
 }
diff --git a/pkg/util/mocks/dns/dns.go b/pkg/util/mocks/dns/dns.go
deleted file mode 100644
index 2c0fb0513..000000000
--- a/pkg/util/mocks/dns/dns.go
+++ /dev/null
@@ -1,93 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/Azure/ARO-RP/pkg/util/dns (interfaces: Manager)
-
-// Package mock_dns is a generated GoMock package.
-package mock_dns
-
-import (
-	context "context"
-	reflect "reflect"
-
-	gomock "github.com/golang/mock/gomock"
-
-	api "github.com/Azure/ARO-RP/pkg/api"
-)
-
-// MockManager is a mock of Manager interface
-type MockManager struct {
-	ctrl     *gomock.Controller
-	recorder *MockManagerMockRecorder
-}
-
-// MockManagerMockRecorder is the mock recorder for MockManager
-type MockManagerMockRecorder struct {
-	mock *MockManager
-}
-
-// NewMockManager creates a new mock instance
-func NewMockManager(ctrl *gomock.Controller) *MockManager {
-	mock := &MockManager{ctrl: ctrl}
-	mock.recorder = &MockManagerMockRecorder{mock}
-	return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use
-func (m *MockManager) EXPECT() *MockManagerMockRecorder {
-	return m.recorder
-}
-
-// Create mocks base method
-func (m *MockManager) Create(arg0 context.Context, arg1 *api.OpenShiftCluster) error {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Create", arg0, arg1)
-	ret0, _ := ret[0].(error)
-	return ret0
-}
-
-// Create indicates an expected call of Create
-func (mr *MockManagerMockRecorder) Create(arg0, arg1 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockManager)(nil).Create), arg0, arg1)
-}
-
-// CreateOrUpdateRouter mocks base method
-func (m *MockManager) CreateOrUpdateRouter(arg0 context.Context, arg1 *api.OpenShiftCluster, arg2 string) error {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "CreateOrUpdateRouter", arg0, arg1, arg2)
-	ret0, _ := ret[0].(error)
-	return ret0
-}
-
-// CreateOrUpdateRouter indicates an expected call of CreateOrUpdateRouter
-func (mr *MockManagerMockRecorder) CreateOrUpdateRouter(arg0, arg1, arg2 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdateRouter", reflect.TypeOf((*MockManager)(nil).CreateOrUpdateRouter), arg0, arg1, arg2)
-}
-
-// Delete mocks base method
-func (m *MockManager) Delete(arg0 context.Context, arg1 *api.OpenShiftCluster) error {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Delete", arg0, arg1)
-	ret0, _ := ret[0].(error)
-	return ret0
-}
-
-// Delete indicates an expected call of Delete
-func (mr *MockManagerMockRecorder) Delete(arg0, arg1 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockManager)(nil).Delete), arg0, arg1)
-}
-
-// Update mocks base method
-func (m *MockManager) Update(arg0 context.Context, arg1 *api.OpenShiftCluster, arg2 string) error {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Update", arg0, arg1, arg2)
-	ret0, _ := ret[0].(error)
-	return ret0
-}
-
-// Update indicates an expected call of Update
-func (mr *MockManagerMockRecorder) Update(arg0, arg1, arg2 interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockManager)(nil).Update), arg0, arg1, arg2)
-}
diff --git a/pkg/util/steps/refreshing.go b/pkg/util/steps/refreshing.go
index 2199c311e..fbb925dc6 100644
--- a/pkg/util/steps/refreshing.go
+++ b/pkg/util/steps/refreshing.go
@@ -5,6 +5,7 @@ package steps
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"time"
 
@@ -15,6 +16,8 @@ import (
 	"github.com/Azure/ARO-RP/pkg/util/refreshable"
 )
 
+var ErrWantRefresh = errors.New("want refresh")
+
 // AuthorizationRefreshingAction returns a wrapper Step which will refresh
 // `authorizer` if the step returns an Azure AuthenticationError and rerun it.
 // The step will be retried until `retryTimeout` is hit. Any other error will be
@@ -68,7 +71,8 @@ func (s authorizationRefreshingActionStep) run(ctx context.Context, log *logrus.
 		// Don't refresh if we have timed out
 		if timeoutCtx.Err() == nil &&
 			(azureerrors.HasAuthorizationFailedError(err) ||
-				azureerrors.HasLinkedAuthorizationFailedError(err)) {
+				azureerrors.HasLinkedAuthorizationFailedError(err) ||
+				err == ErrWantRefresh) {
 			log.Print(err)
 			// Try refreshing auth.
 			if s.authorizer == nil {
diff --git a/pkg/util/version/const.go b/pkg/util/version/const.go
index f5710cdaf..7560a93cd 100644
--- a/pkg/util/version/const.go
+++ b/pkg/util/version/const.go
@@ -13,8 +13,8 @@ var GitCommit = "unknown"
 
 // InstallStream describes stream we are defaulting to for all new clusters
 var InstallStream = &Stream{
-	Version:  NewVersion(4, 5, 27),
-	PullSpec: "quay.io/openshift-release-dev/ocp-release@sha256:412276155bfe186c35322a788321ebf110130a272e18f55a1a2510f15ee0bb04",
+	Version:  NewVersion(4, 5, 22),
+	PullSpec: "quay.io/openshift-release-dev/ocp-release@sha256:adb5ef06c54ff75ca9033f222ac5e57f2fd82e49bdd84f737d460ae542c8af60",
 }
 
 // Streams describes list of streams we support for upgrades
@@ -23,11 +23,11 @@ var (
 		InstallStream,
 		{
 			Version:  NewVersion(4, 4, 31),
-			PullSpec: "quay.io/openshift-release-dev/ocp-release@sha256:52ca6e018793f068f994ff1e85d86283fd4a9875390dffde55c97fd59d03a009",
+			PullSpec: "quay.io/openshift-release-dev/ocp-release@sha256:679db43a28a42fc41784ea3d4976d9d60cd194757cfdbea6137d6d0093db8c8d",
 		},
 		{
 			Version:  NewVersion(4, 3, 40),
-			PullSpec: "quay.io/openshift-release-dev/ocp-release@sha256:9ff90174a170379e90a9ead6e0d8cf6f439004191f80762764a5ca3dbaab01dc",
+			PullSpec: "quay.io/openshift-release-dev/ocp-release@sha256:59cc585be7b4ad069a18f6f1a3309391e172192744ee65fa6e499c8b337edda4",
 		},
 	}
 )


Test patch:
diff --git a/pkg/api/validate/dynamic_test.go b/pkg/api/validate/dynamic_test.go
new file mode 100644
index 000000000..5489a0b92
--- /dev/null
+++ b/pkg/api/validate/dynamic_test.go
@@ -0,0 +1,529 @@
+package validate
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"context"
+	"errors"
+	"net/http"
+	"strings"
+	"testing"
+
+	mgmtnetwork "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-07-01/network"
+	mgmtauthorization "github.com/Azure/azure-sdk-for-go/services/preview/authorization/mgmt/2018-09-01-preview/authorization"
+	"github.com/Azure/go-autorest/autorest"
+	"github.com/Azure/go-autorest/autorest/azure"
+	"github.com/Azure/go-autorest/autorest/to"
+	"github.com/golang/mock/gomock"
+	"github.com/sirupsen/logrus"
+
+	mock_authorization "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/mgmt/authorization"
+	mock_network "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/mgmt/network"
+)
+
+func TestValidateVnetPermissions(t *testing.T) {
+	ctx := context.Background()
+
+	resourceGroupName := "testGroup"
+	vnetName := "testVnet"
+	subscriptionID := "0000000-0000-0000-0000-000000000000"
+	vnetID := "/subscriptions/" + subscriptionID + "/resourceGroups/" + resourceGroupName + "/providers/Microsoft.Network/virtualNetworks/" + vnetName
+	resourceType := "virtualNetworks"
+	resourceProvider := "Microsoft.Network"
+
+	controller := gomock.NewController(t)
+	defer controller.Finish()
+
+	for _, tt := range []struct {
+		name    string
+		mocks   func(*mock_authorization.MockPermissionsClient, func())
+		wantErr string
+	}{
+		{
+			name: "pass",
+			mocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel func()) {
+				permissionsClient.EXPECT().
+					ListForResource(gomock.Any(), resourceGroupName, resourceProvider, "", resourceType, vnetName).
+					Return([]mgmtauthorization.Permission{
+						{
+							Actions: &[]string{
+								"Microsoft.Network/virtualNetworks/join/action",
+								"Microsoft.Network/virtualNetworks/read",
+								"Microsoft.Network/virtualNetworks/write",
+								"Microsoft.Network/virtualNetworks/subnets/join/action",
+								"Microsoft.Network/virtualNetworks/subnets/read",
+								"Microsoft.Network/virtualNetworks/subnets/write",
+							},
+							NotActions: &[]string{},
+						},
+					}, nil)
+			},
+		},
+		{
+			name: "fail: missing permissions",
+			mocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel func()) {
+				permissionsClient.EXPECT().
+					ListForResource(gomock.Any(), resourceGroupName, resourceProvider, "", resourceType, vnetName).
+					Do(func(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) {
+						cancel()
+					}).
+					Return(
+						[]mgmtauthorization.Permission{
+							{
+								Actions:    &[]string{},
+								NotActions: &[]string{},
+							},
+						},
+						nil,
+					)
+			},
+			wantErr: "400: InvalidResourceProviderPermissions: : The resource provider does not have Network Contributor permission on vnet '" + vnetID + "'.",
+		},
+		{
+			name: "fail: not found",
+			mocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel func()) {
+				permissionsClient.EXPECT().
+					ListForResource(gomock.Any(), resourceGroupName, resourceProvider, "", resourceType, vnetName).
+					Do(func(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) {
+						cancel()
+					}).
+					Return(
+						nil,
+						autorest.DetailedError{
+							StatusCode: http.StatusNotFound,
+						},
+					)
+			},
+			wantErr: "400: InvalidLinkedVNet: : The vnet '" + vnetID + "' could not be found.",
+		},
+	} {
+		t.Run(tt.name, func(t *testing.T) {
+			ctx, cancel := context.WithCancel(ctx)
+			defer cancel()
+
+			permissionsClient := mock_authorization.NewMockPermissionsClient(controller)
+			tt.mocks(permissionsClient, cancel)
+
+			dv := &dynamic{
+				log:         logrus.NewEntry(logrus.StandardLogger()),
+				permissions: permissionsClient,
+				vnetr: &azure.Resource{
+					ResourceGroup:  resourceGroupName,
+					ResourceType:   resourceType,
+					Provider:       resourceProvider,
+					ResourceName:   vnetName,
+					SubscriptionID: subscriptionID,
+				},
+				code: "InvalidResourceProviderPermissions",
+				typ:  "resource provider",
+			}
+
+			err := dv.ValidateVnetPermissions(ctx)
+			if err != nil && err.Error() != tt.wantErr ||
+				err == nil && tt.wantErr != "" {
+				t.Error(err)
+			}
+		})
+	}
+}
+
+func TestGetRouteTableID(t *testing.T) {
+	resourceGroupID := "/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup"
+	vnetID := resourceGroupID + "/providers/Microsoft.Network/virtualNetworks/testVnet"
+	genericSubnet := vnetID + "/subnet/genericSubnet"
+	routeTableID := resourceGroupID + "/providers/Microsoft.Network/routeTables/testRouteTable"
+
+	for _, tt := range []struct {
+		name       string
+		modifyVnet func(*mgmtnetwork.VirtualNetwork)
+		wantErr    string
+	}{
+		{
+			name: "pass",
+		},
+		{
+			name: "pass: no route table",
+			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
+				(*vnet.Subnets)[0].RouteTable = nil
+			},
+		},
+		{
+			name: "fail: can't find subnet",
+			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
+				vnet.Subnets = nil
+			},
+			wantErr: "400: InvalidLinkedVNet: : The subnet '" + genericSubnet + "' could not be found.",
+		},
+	} {
+		vnet := &mgmtnetwork.VirtualNetwork{
+			ID: &vnetID,
+			VirtualNetworkPropertiesFormat: &mgmtnetwork.VirtualNetworkPropertiesFormat{
+				Subnets: &[]mgmtnetwork.Subnet{
+					{
+						ID: &genericSubnet,
+						SubnetPropertiesFormat: &mgmtnetwork.SubnetPropertiesFormat{
+							RouteTable: &mgmtnetwork.RouteTable{
+								ID: &routeTableID,
+							},
+						},
+					},
+				},
+			},
+		}
+
+		if tt.modifyVnet != nil {
+			tt.modifyVnet(vnet)
+		}
+
+		// purposefully hardcoding path to "" so it is not needed in the wantErr message
+		_, err := getRouteTableID(vnet, "", genericSubnet)
+		if err != nil && err.Error() != tt.wantErr ||
+			err == nil && tt.wantErr != "" {
+			t.Error(err)
+		}
+	}
+}
+
+func TestValidateVnetDNS(t *testing.T) {
+	ctx := context.Background()
+
+	controller := gomock.NewController(t)
+	defer controller.Finish()
+
+	resourceGroupName := "testGroup"
+	vnetName := "testVnet"
+	vnetID := "/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/" + resourceGroupName + "/providers/Microsoft.Network/virtualNetworks/" + vnetName
+
+	for _, tt := range []struct {
+		name      string
+		vnetMocks func(*mock_network.MockVirtualNetworksClient, mgmtnetwork.VirtualNetwork)
+		wantErr   string
+	}{
+		{
+			name: "pass",
+			vnetMocks: func(vnetClient *mock_network.MockVirtualNetworksClient, vnet mgmtnetwork.VirtualNetwork) {
+				vnetClient.EXPECT().
+					Get(gomock.Any(), resourceGroupName, vnetName, "").
+					Return(vnet, nil)
+			},
+		},
+		{
+			name: "fail: dhcp options set",
+			vnetMocks: func(vnetClient *mock_network.MockVirtualNetworksClient, vnet mgmtnetwork.VirtualNetwork) {
+				vnet.DhcpOptions = &mgmtnetwork.DhcpOptions{
+					DNSServers: &[]string{
+						"8.8.8.8",
+					},
+				}
+				vnetClient.EXPECT().
+					Get(gomock.Any(), resourceGroupName, vnetName, "").
+					Return(vnet, nil)
+			},
+			wantErr: "400: InvalidLinkedVNet: : The provided vnet '" + vnetID + "' is invalid: custom DNS servers are not supported.",
+		},
+		{
+			name: "fail: failed to get vnet",
+			vnetMocks: func(vnetClient *mock_network.MockVirtualNetworksClient, vnet mgmtnetwork.VirtualNetwork) {
+				vnetClient.EXPECT().
+					Get(gomock.Any(), resourceGroupName, vnetName, "").
+					Return(vnet, errors.New("failed to get vnet"))
+			},
+			wantErr: "failed to get vnet",
+		},
+	} {
+		vnet := mgmtnetwork.VirtualNetwork{
+			ID: to.StringPtr(vnetID),
+			VirtualNetworkPropertiesFormat: &mgmtnetwork.VirtualNetworkPropertiesFormat{
+				DhcpOptions: nil,
+			},
+		}
+
+		vnetClient := mock_network.NewMockVirtualNetworksClient(controller)
+		tt.vnetMocks(vnetClient, vnet)
+
+		dv := &dynamic{
+			log:             logrus.NewEntry(logrus.StandardLogger()),
+			virtualNetworks: vnetClient,
+			vnetr: &azure.Resource{
+				ResourceGroup: resourceGroupName,
+				ResourceName:  vnetName,
+			},
+		}
+
+		err := dv.ValidateVnetDNS(ctx)
+		if err != nil && err.Error() != tt.wantErr ||
+			err == nil && tt.wantErr != "" {
+			t.Error(err)
+		}
+	}
+}
+
+func TestValidateRouteTablePermissions(t *testing.T) {
+	ctx := context.Background()
+
+	resourceGroupName := "testGroup"
+	resourceGroupID := "/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/" + resourceGroupName
+	routeTableName := "testRouteTable"
+	routeTableID := resourceGroupID + "/providers/Microsoft.Network/routeTables/" + routeTableName
+
+	controller := gomock.NewController(t)
+	defer controller.Finish()
+
+	for _, tt := range []struct {
+		name    string
+		rtID    string
+		mocks   func(*mock_authorization.MockPermissionsClient, func())
+		wantErr string
+	}{
+		{
+			name: "pass",
+			rtID: routeTableID,
+			mocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel func()) {
+				permissionsClient.EXPECT().
+					ListForResource(gomock.Any(), resourceGroupName, "Microsoft.Network", "", "routeTables", routeTableName).
+					Return([]mgmtauthorization.Permission{
+						{
+							Actions: &[]string{
+								"Microsoft.Network/routeTables/join/action",
+								"Microsoft.Network/routeTables/read",
+								"Microsoft.Network/routeTables/write",
+							},
+							NotActions: &[]string{},
+						},
+					}, nil)
+			},
+		},
+		{
+			name:    "fail: cannot parse resource id",
+			rtID:    "invalid_route_table_id",
+			wantErr: "parsing failed for invalid_route_table_id. Invalid resource Id format",
+		},
+		{
+			name: "fail: missing permissions",
+			rtID: routeTableID,
+			mocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel func()) {
+				permissionsClient.EXPECT().
+					ListForResource(gomock.Any(), resourceGroupName, "Microsoft.Network", "", "routeTables", routeTableName).
+					Do(func(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) {
+						cancel()
+					}).
+					Return([]mgmtauthorization.Permission{
+						{
+							Actions:    &[]string{},
+							NotActions: &[]string{},
+						},
+					}, nil)
+			},
+			wantErr: "400: InvalidResourceProviderPermissions: : The resource provider does not have Network Contributor permission on route table '" + routeTableID + "'.",
+		},
+		{
+			name: "fail: not found",
+			rtID: routeTableID,
+			mocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel func()) {
+				permissionsClient.EXPECT().
+					ListForResource(gomock.Any(), resourceGroupName, "Microsoft.Network", "", "routeTables", routeTableName).
+					Do(func(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) {
+						cancel()
+					}).
+					Return(
+						nil,
+						autorest.DetailedError{
+							StatusCode: http.StatusNotFound,
+						},
+					)
+			},
+			wantErr: "400: InvalidLinkedRouteTable: : The route table '" + routeTableID + "' could not be found.",
+		},
+	} {
+		ctx, cancel := context.WithCancel(ctx)
+		defer cancel()
+
+		permissionsClient := mock_authorization.NewMockPermissionsClient(controller)
+		if tt.mocks != nil {
+			tt.mocks(permissionsClient, cancel)
+		}
+
+		dv := &dynamic{
+			log:         logrus.NewEntry(logrus.StandardLogger()),
+			permissions: permissionsClient,
+			code:        "InvalidResourceProviderPermissions",
+			typ:         "resource provider",
+		}
+
+		// purposefully hardcoding path to "" so it is not needed in the wantErr message
+		err := dv.validateRouteTablePermissions(ctx, tt.rtID, "")
+		if err != nil && err.Error() != tt.wantErr ||
+			err == nil && tt.wantErr != "" {
+			t.Error(err)
+		}
+	}
+}
+
+func TestValidateRouteTablesPermissions(t *testing.T) {
+	ctx := context.Background()
+
+	subscriptionID := "0000000-0000-0000-0000-000000000000"
+	resourceGroupName := "testGroup"
+	resourceGroupID := "/subscriptions/" + subscriptionID + "/resourceGroups/" + resourceGroupName
+	vnetName := "testVnet"
+	vnetID := resourceGroupID + "/providers/Microsoft.Network/virtualNetworks/" + vnetName
+	masterSubnet := vnetID + "/subnet/masterSubnet"
+	workerSubnet := vnetID + "/subnet/workerSubnet"
+	masterRtID := resourceGroupID + "/providers/Microsoft.Network/routeTables/masterRt"
+	workerRtID := resourceGroupID + "/providers/Microsoft.Network/routeTables/workerRt"
+
+	controller := gomock.NewController(t)
+	defer controller.Finish()
+
+	for _, tt := range []struct {
+		name            string
+		permissionMocks func(*mock_authorization.MockPermissionsClient, func())
+		vnetMocks       func(*mock_network.MockVirtualNetworksClient, mgmtnetwork.VirtualNetwork)
+		wantErr         string
+	}{
+		{
+			name: "fail: failed to get vnet",
+			vnetMocks: func(vnetClient *mock_network.MockVirtualNetworksClient, vnet mgmtnetwork.VirtualNetwork) {
+				vnetClient.EXPECT().
+					Get(gomock.Any(), resourceGroupName, vnetName, "").
+					Return(vnet, errors.New("failed to get vnet"))
+			},
+			wantErr: "failed to get vnet",
+		},
+		{
+			name: "fail: master subnet doesn't exist",
+			vnetMocks: func(vnetClient *mock_network.MockVirtualNetworksClient, vnet mgmtnetwork.VirtualNetwork) {
+				vnet.Subnets = nil
+				vnetClient.EXPECT().
+					Get(gomock.Any(), resourceGroupName, vnetName, "").
+					Return(vnet, nil)
+			},
+			wantErr: "400: InvalidLinkedVNet: properties.masterProfile.subnetId: The subnet '" + masterSubnet + "' could not be found.",
+		},
+		{
+			name: "fail: worker subnet ID doesn't exist",
+			vnetMocks: func(vnetClient *mock_network.MockVirtualNetworksClient, vnet mgmtnetwork.VirtualNetwork) {
+				(*vnet.Subnets)[1].ID = to.StringPtr("not valid")
+				vnetClient.EXPECT().
+					Get(gomock.Any(), resourceGroupName, vnetName, "").
+					Return(vnet, nil)
+			},
+			wantErr: "400: InvalidLinkedVNet: properties.workerProfiles[0].subnetId: The subnet '" + workerSubnet + "' could not be found.",
+		},
+		{
+			name: "fail: permissions don't exist",
+			vnetMocks: func(vnetClient *mock_network.MockVirtualNetworksClient, vnet mgmtnetwork.VirtualNetwork) {
+				vnetClient.EXPECT().
+					Get(gomock.Any(), resourceGroupName, vnetName, "").
+					Return(vnet, nil)
+			},
+			permissionMocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel func()) {
+				permissionsClient.EXPECT().
+					ListForResource(gomock.Any(), strings.ToLower(resourceGroupName), strings.ToLower("Microsoft.Network"), "", strings.ToLower("routeTables"), gomock.Any()).
+					Do(func(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) {
+						cancel()
+					}).
+					Return(
+						[]mgmtauthorization.Permission{
+							{
+								Actions:    &[]string{},
+								NotActions: &[]string{},
+							},
+						},
+						nil,
+					)
+			},
+			wantErr: "400: InvalidResourceProviderPermissions: : The resource provider does not have Network Contributor permission on route table '" + strings.ToLower(masterRtID) + "'.",
+		},
+		{
+			name: "pass",
+			vnetMocks: func(vnetClient *mock_network.MockVirtualNetworksClient, vnet mgmtnetwork.VirtualNetwork) {
+				vnetClient.EXPECT().
+					Get(gomock.Any(), resourceGroupName, vnetName, "").
+					Return(vnet, nil)
+			},
+			permissionMocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel func()) {
+				permissionsClient.EXPECT().
+					ListForResource(gomock.Any(), strings.ToLower(resourceGroupName), strings.ToLower("Microsoft.Network"), "", strings.ToLower("routeTables"), gomock.Any()).
+					AnyTimes().
+					Return([]mgmtauthorization.Permission{
+						{
+							Actions: &[]string{
+								"Microsoft.Network/routeTables/join/action",
+								"Microsoft.Network/routeTables/read",
+								"Microsoft.Network/routeTables/write",
+							},
+							NotActions: &[]string{},
+						},
+					}, nil)
+			},
+		},
+	} {
+		t.Run(tt.name, func(t *testing.T) {
+			ctx, cancel := context.WithCancel(ctx)
+			defer cancel()
+
+			permissionsClient := mock_authorization.NewMockPermissionsClient(controller)
+			vnetClient := mock_network.NewMockVirtualNetworksClient(controller)
+
+			vnet := &mgmtnetwork.VirtualNetwork{
+				ID: &vnetID,
+				VirtualNetworkPropertiesFormat: &mgmtnetwork.VirtualNetworkPropertiesFormat{
+					Subnets: &[]mgmtnetwork.Subnet{
+						{
+							ID: &masterSubnet,
+							SubnetPropertiesFormat: &mgmtnetwork.SubnetPropertiesFormat{
+								RouteTable: &mgmtnetwork.RouteTable{
+									ID: &masterRtID,
+								},
+							},
+						},
+						{
+							ID: &workerSubnet,
+							SubnetPropertiesFormat: &mgmtnetwork.SubnetPropertiesFormat{
+								RouteTable: &mgmtnetwork.RouteTable{
+									ID: &workerRtID,
+								},
+							},
+						},
+					},
+				},
+			}
+
+			dv := &dynamic{
+				log:             logrus.NewEntry(logrus.StandardLogger()),
+				permissions:     permissionsClient,
+				virtualNetworks: vnetClient,
+
+				vnetr: &azure.Resource{
+					ResourceGroup:  resourceGroupName,
+					ResourceName:   vnetName,
+					SubscriptionID: subscriptionID,
+					Provider:       "Microsoft.Network",
+					ResourceType:   "virtualNetworks",
+				},
+
+				masterSubnetID:  masterSubnet,
+				workerSubnetIDs: []string{workerSubnet},
+
+				code: "InvalidResourceProviderPermissions",
+				typ:  "resource provider",
+			}
+
+			if tt.permissionMocks != nil {
+				tt.permissionMocks(permissionsClient, cancel)
+			}
+
+			if tt.vnetMocks != nil {
+				tt.vnetMocks(vnetClient, *vnet)
+			}
+
+			err := dv.ValidateRouteTablesPermissions(ctx)
+			if err != nil && err.Error() != tt.wantErr ||
+				err == nil && tt.wantErr != "" {
+				t.Error(err)
+			}
+		})
+	}
+}
diff --git a/pkg/api/validate/openshiftcluster_validatedynamic_test.go b/pkg/api/validate/openshiftcluster_validatedynamic_test.go
index d10bfc92e..f6ca3fe36 100644
--- a/pkg/api/validate/openshiftcluster_validatedynamic_test.go
+++ b/pkg/api/validate/openshiftcluster_validatedynamic_test.go
@@ -6,22 +6,16 @@ package validate
 import (
 	"context"
 	"errors"
-	"net/http"
 	"testing"
 
 	mgmtnetwork "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-07-01/network"
-	mgmtauthorization "github.com/Azure/azure-sdk-for-go/services/preview/authorization/mgmt/2018-09-01-preview/authorization"
 	mgmtfeatures "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2019-07-01/features"
-	"github.com/Azure/go-autorest/autorest"
-	"github.com/Azure/go-autorest/autorest/azure"
 	"github.com/Azure/go-autorest/autorest/to"
 	"github.com/golang/mock/gomock"
 	"github.com/sirupsen/logrus"
 
 	"github.com/Azure/ARO-RP/pkg/api"
-	mock_authorization "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/mgmt/authorization"
 	mock_features "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/mgmt/features"
-	mockrefreshable "github.com/Azure/ARO-RP/pkg/util/mocks/refreshable"
 )
 
 func TestValidateProviders(t *testing.T) {
@@ -36,7 +30,7 @@ func TestValidateProviders(t *testing.T) {
 		wantErr string
 	}{
 		{
-			name: "all registered",
+			name: "pass",
 			mocks: func(providersClient *mock_features.MockProvidersClient) {
 				providersClient.EXPECT().
 					List(gomock.Any(), nil, "").
@@ -69,7 +63,7 @@ func TestValidateProviders(t *testing.T) {
 			},
 		},
 		{
-			name: "compute not registered",
+			name: "fail: compute not registered",
 			mocks: func(providersClient *mock_features.MockProvidersClient) {
 				providersClient.EXPECT().
 					List(gomock.Any(), nil, "").
@@ -103,7 +97,7 @@ func TestValidateProviders(t *testing.T) {
 			wantErr: "400: ResourceProviderNotRegistered: : The resource provider 'Microsoft.Compute' is not registered.",
 		},
 		{
-			name: "storage missing",
+			name: "fail: storage missing",
 			mocks: func(providersClient *mock_features.MockProvidersClient) {
 				providersClient.EXPECT().
 					List(gomock.Any(), nil, "").
@@ -147,7 +141,51 @@ func TestValidateProviders(t *testing.T) {
 
 			tt.mocks(providerClient)
 
-			err := validateProviders(ctx, logrus.NewEntry(logrus.StandardLogger()), providerClient)
+			dv := &openShiftClusterDynamicValidator{
+				log:       logrus.NewEntry(logrus.StandardLogger()),
+				providers: providerClient,
+			}
+
+			err := dv.validateProviders(ctx)
+			if err != nil && err.Error() != tt.wantErr ||
+				err == nil && tt.wantErr != "" {
+				t.Error(err)
+			}
+		})
+	}
+}
+
+func TestValidateVnetLocation(t *testing.T) {
+	ctx := context.Background()
+
+	for _, tt := range []struct {
+		name     string
+		location string
+		wantErr  string
+	}{
+		{
+			name:     "pass",
+			location: "eastus",
+		},
+		{
+			name:     "fail: location differs",
+			location: "neverland",
+			wantErr:  "400: InvalidLinkedVNet: : The vnet location 'neverland' must match the cluster location 'eastus'.",
+		},
+	} {
+		t.Run(tt.name, func(t *testing.T) {
+			dv := &openShiftClusterDynamicValidator{
+				log: logrus.NewEntry(logrus.StandardLogger()),
+				oc: &api.OpenShiftCluster{
+					Location: "eastus",
+				},
+			}
+
+			vnet := &mgmtnetwork.VirtualNetwork{
+				Location: to.StringPtr(tt.location),
+			}
+
+			err := dv.validateVnetLocation(ctx, vnet)
 			if err != nil && err.Error() != tt.wantErr ||
 				err == nil && tt.wantErr != "" {
 				t.Error(err)
@@ -156,17 +194,13 @@ func TestValidateProviders(t *testing.T) {
 	}
 }
 
-func TestValidateVnet(t *testing.T) {
+func TestValidateSubnet(t *testing.T) {
 	ctx := context.Background()
 
 	resourceGroupID := "/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup"
 	vnetID := resourceGroupID + "/providers/Microsoft.Network/virtualNetworks/testVnet"
-	masterSubnet := vnetID + "/subnet/masterSubnet"
-	workerSubnet := vnetID + "/subnet/workerSubnet"
-	workerSubnet2 := vnetID + "/subnet/workerSubnet2"
+	genericSubnet := vnetID + "/subnet/genericSubnet"
 	masterNSGv1 := resourceGroupID + "/providers/Microsoft.Network/networkSecurityGroups/aro-controlplane-nsg"
-	workerNSGv1 := resourceGroupID + "/providers/Microsoft.Network/networkSecurityGroups/aro-node-nsg"
-	commonNSGv2 := resourceGroupID + "/providers/Microsoft.Network/networkSecurityGroups/aro-nsg"
 
 	for _, tt := range []struct {
 		name       string
@@ -178,508 +212,248 @@ func TestValidateVnet(t *testing.T) {
 			name: "pass",
 		},
 		{
-			name: "pass (creating)",
-			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				(*vnet.Subnets)[0].NetworkSecurityGroup = nil
-				(*vnet.Subnets)[1].NetworkSecurityGroup = nil
-			},
+			name: "pass (master subnet)",
 			modifyOC: func(oc *api.OpenShiftCluster) {
-				oc.Properties.ProvisioningState = api.ProvisioningStateCreating
-			},
-		},
-		{
-			name: "missing subnet (master)",
-			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				vnet.Location = to.StringPtr("westeurope")
-			},
-			wantErr: "400: InvalidLinkedVNet: : The vnet location 'westeurope' must match the cluster location 'eastus'.",
-		},
-		{
-			name: "missing subnet (master)",
-			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				*vnet.Subnets = (*vnet.Subnets)[1:]
-			},
-			wantErr: "400: InvalidLinkedVNet: properties.masterProfile.subnetId: The provided subnet '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/virtualNetworks/testVnet/subnet/masterSubnet' could not be found.",
-		},
-		{
-			name: "missing subnet (worker)",
-			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				*vnet.Subnets = (*vnet.Subnets)[:1]
-			},
-			wantErr: `400: InvalidLinkedVNet: properties.workerProfiles[0].subnetId: The provided subnet '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/virtualNetworks/testVnet/subnet/workerSubnet' could not be found.`,
-		},
-		{
-			name: "invalid PLS network policy (master)",
-			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				(*vnet.Subnets)[0].PrivateLinkServiceNetworkPolicies = nil
-			},
-			wantErr: "400: InvalidLinkedVNet: properties.masterProfile.subnetId: The provided subnet '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/virtualNetworks/testVnet/subnet/masterSubnet' is invalid: must have privateLinkServiceNetworkPolicies disabled.",
-		},
-		{
-			name: "invalid service endpoint (master)",
-			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				(*vnet.Subnets)[0].ServiceEndpoints = nil
-			},
-			wantErr: "400: InvalidLinkedVNet: properties.masterProfile.subnetId: The provided subnet '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/virtualNetworks/testVnet/subnet/masterSubnet' is invalid: must have Microsoft.ContainerRegistry serviceEndpoint.",
-		},
-		{
-			name: "invalid service endpoint (worker)",
-			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				(*vnet.Subnets)[1].ServiceEndpoints = nil
-			},
-			wantErr: `400: InvalidLinkedVNet: properties.workerProfiles[0].subnetId: The provided subnet '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/virtualNetworks/testVnet/subnet/workerSubnet' is invalid: must have Microsoft.ContainerRegistry serviceEndpoint.`,
-		},
-		{
-			name: "invalid master nsg arch v1",
-			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				(*vnet.Subnets)[0].NetworkSecurityGroup = nil
-			},
-			wantErr: `400: InvalidLinkedVNet: properties.masterProfile.subnetId: The provided subnet '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/virtualNetworks/testVnet/subnet/masterSubnet' is invalid: must have network security group '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/networkSecurityGroups/aro-controlplane-nsg' attached.`,
-		},
-		{
-			name: "invalid master nsg arch v2",
-			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				(*vnet.Subnets)[0].NetworkSecurityGroup = nil
-				(*vnet.Subnets)[1].NetworkSecurityGroup.ID = &commonNSGv2
-			},
-			modifyOC: func(oc *api.OpenShiftCluster) {
-				oc.Properties.ArchitectureVersion = api.ArchitectureVersionV2
-			},
-			wantErr: `400: InvalidLinkedVNet: properties.masterProfile.subnetId: The provided subnet '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/virtualNetworks/testVnet/subnet/masterSubnet' is invalid: must have network security group '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/networkSecurityGroups/aro-nsg' attached.`,
-		},
-		{
-			name: "invalid worker nsg arch v1",
-			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				(*vnet.Subnets)[1].NetworkSecurityGroup = nil
-			},
-			wantErr: `400: InvalidLinkedVNet: properties.workerProfiles[0].subnetId: The provided subnet '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/virtualNetworks/testVnet/subnet/workerSubnet' is invalid: must have network security group '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/networkSecurityGroups/aro-node-nsg' attached.`,
-		},
-		{
-			name: "invalid worker nsg arch v2",
-			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				(*vnet.Subnets)[0].NetworkSecurityGroup.ID = &commonNSGv2
-				(*vnet.Subnets)[1].NetworkSecurityGroup = nil
-			},
-			modifyOC: func(oc *api.OpenShiftCluster) {
-				oc.Properties.ArchitectureVersion = api.ArchitectureVersionV2
+				oc.Properties.MasterProfile = api.MasterProfile{
+					SubnetID: genericSubnet,
+				}
 			},
-			wantErr: `400: InvalidLinkedVNet: properties.workerProfiles[0].subnetId: The provided subnet '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/virtualNetworks/testVnet/subnet/workerSubnet' is invalid: must have network security group '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/networkSecurityGroups/aro-nsg' attached.`,
 		},
 		{
-			name: "invalid master nsg (creating)",
-			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				(*vnet.Subnets)[1].NetworkSecurityGroup = nil
-			},
+			name: "pass (cluster in creating provisioning status)",
 			modifyOC: func(oc *api.OpenShiftCluster) {
 				oc.Properties.ProvisioningState = api.ProvisioningStateCreating
 			},
-			wantErr: `400: InvalidLinkedVNet: properties.masterProfile.subnetId: The provided subnet '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/virtualNetworks/testVnet/subnet/masterSubnet' is invalid: must not have a network security group attached.`,
-		},
-		{
-			name: "invalid worker nsg (creating)",
 			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
 				(*vnet.Subnets)[0].NetworkSecurityGroup = nil
 			},
-			modifyOC: func(oc *api.OpenShiftCluster) {
-				oc.Properties.ProvisioningState = api.ProvisioningStateCreating
-			},
-			wantErr: `400: InvalidLinkedVNet: properties.workerProfiles[0].subnetId: The provided subnet '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/virtualNetworks/testVnet/subnet/workerSubnet' is invalid: must not have a network security group attached.`,
-		},
-		{
-			name: "invalid master subnet size",
-			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				(*vnet.Subnets)[0].AddressPrefix = to.StringPtr("10.0.0.0/28")
-			},
-			wantErr: "400: InvalidLinkedVNet: properties.masterProfile.subnetId: The provided subnet '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/virtualNetworks/testVnet/subnet/masterSubnet' is invalid: must be /27 or larger.",
 		},
 		{
-			name: "invalid worker subnet size",
+			name: "fail: subnet doe not exist on vnet",
 			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				(*vnet.Subnets)[1].AddressPrefix = to.StringPtr("10.0.0.0/28")
+				vnet.Subnets = nil
 			},
-			wantErr: `400: InvalidLinkedVNet: properties.workerProfiles[0].subnetId: The provided subnet '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/virtualNetworks/testVnet/subnet/workerSubnet' is invalid: must be /27 or larger.`,
+			wantErr: "400: InvalidLinkedVNet: : The provided subnet '" + genericSubnet + "' could not be found.",
 		},
 		{
-			name: "master and worker subnets overlap",
-			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				(*vnet.Subnets)[0].AddressPrefix = (*vnet.Subnets)[1].AddressPrefix
+			name: "fail: private link service network policies enabled on master subnet",
+			modifyOC: func(oc *api.OpenShiftCluster) {
+				oc.Properties.MasterProfile = api.MasterProfile{
+					SubnetID: genericSubnet,
+				}
 			},
-			wantErr: "400: InvalidLinkedVNet: : The provided CIDRs must not overlap: '10.0.1.0/24 overlaps with 10.0.1.0/24'.",
-		},
-		{
-			name: "master and pod subnets overlap",
 			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				(*vnet.Subnets)[0].AddressPrefix = to.StringPtr("10.0.2.0/24")
+				(*vnet.Subnets)[0].PrivateLinkServiceNetworkPolicies = to.StringPtr("Enabled")
 			},
-			wantErr: "400: InvalidLinkedVNet: : The provided CIDRs must not overlap: '10.0.2.0/24 overlaps with 10.0.2.0/24'.",
+			wantErr: "400: InvalidLinkedVNet: : The provided subnet '" + genericSubnet + "' is invalid: must have privateLinkServiceNetworkPolicies disabled.",
 		},
 		{
-			name: "worker and pod subnets overlap",
+			name: "fail: container registry endpoint doesn't exist",
 			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				(*vnet.Subnets)[1].AddressPrefix = to.StringPtr("10.0.2.0/24")
+				(*vnet.Subnets)[0].ServiceEndpoints = nil
 			},
-			wantErr: "400: InvalidLinkedVNet: : The provided CIDRs must not overlap: '10.0.2.0/24 overlaps with 10.0.2.0/24'.",
+			wantErr: "400: InvalidLinkedVNet: : The provided subnet '" + genericSubnet + "' is invalid: must have Microsoft.ContainerRegistry serviceEndpoint.",
 		},
 		{
-			name: "master and service subnets overlap",
+			name: "fail: network provisioning state not succeeded",
 			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				(*vnet.Subnets)[0].AddressPrefix = to.StringPtr("10.0.3.0/24")
+				(*(*vnet.Subnets)[0].ServiceEndpoints)[0].ProvisioningState = mgmtnetwork.Failed
 			},
-			wantErr: "400: InvalidLinkedVNet: : The provided CIDRs must not overlap: '10.0.3.0/24 overlaps with 10.0.3.0/24'.",
+			wantErr: "400: InvalidLinkedVNet: : The provided subnet '" + genericSubnet + "' is invalid: must have Microsoft.ContainerRegistry serviceEndpoint.",
 		},
 		{
-			name: "worker and service subnets overlap",
-			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				(*vnet.Subnets)[1].AddressPrefix = to.StringPtr("10.0.3.0/24")
+			name: "fail: provisioning state creating: subnet has NSG",
+			modifyOC: func(oc *api.OpenShiftCluster) {
+				oc.Properties.ProvisioningState = api.ProvisioningStateCreating
 			},
-			wantErr: "400: InvalidLinkedVNet: : The provided CIDRs must not overlap: '10.0.3.0/24 overlaps with 10.0.3.0/24'.",
+			wantErr: "400: InvalidLinkedVNet: : The provided subnet '" + genericSubnet + "' is invalid: must not have a network security group attached.",
 		},
 		{
-			name: "two worker pools on the same subnet",
+			name: "fail: invalid architecture version returns no NSG",
 			modifyOC: func(oc *api.OpenShiftCluster) {
-				oc.Properties.WorkerProfiles = append(oc.Properties.WorkerProfiles, api.WorkerProfile{
-					SubnetID: workerSubnet,
-				})
+				oc.Properties.ArchitectureVersion = 9001
 			},
-			wantErr: "",
+			wantErr: "unknown architecture version 9001",
 		},
 		{
-			name: "two worker pools on the on two different subnets which do not overlap with pod or service cidr",
-			modifyOC: func(oc *api.OpenShiftCluster) {
-				oc.Properties.WorkerProfiles = append(oc.Properties.WorkerProfiles, api.WorkerProfile{
-					SubnetID: workerSubnet2,
-				})
+			name: "fail: nsg id doesn't match expected",
+			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
+				(*vnet.Subnets)[0].NetworkSecurityGroup.ID = to.StringPtr("not matching")
 			},
-			wantErr: "",
+			wantErr: "400: InvalidLinkedVNet: : The provided subnet '" + genericSubnet + "' is invalid: must have network security group '" + masterNSGv1 + "' attached.",
 		},
 		{
-			name: "two worker pools on the on two different subnets one of which overlaps with pod or service cidr",
+			name: "fail: invalid subnet CIDR",
 			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				(*vnet.Subnets)[2].AddressPrefix = to.StringPtr("10.0.3.0/24")
-			},
-			modifyOC: func(oc *api.OpenShiftCluster) {
-				oc.Properties.WorkerProfiles = append(oc.Properties.WorkerProfiles, api.WorkerProfile{
-					SubnetID: workerSubnet2,
-				})
+				(*vnet.Subnets)[0].AddressPrefix = to.StringPtr("not-valid")
 			},
-			wantErr: "400: InvalidLinkedVNet: : The provided CIDRs must not overlap: '10.0.3.0/24 overlaps with 10.0.3.0/24'.",
+			wantErr: "invalid CIDR address: not-valid",
 		},
 		{
-			name: "custom dns set",
+			name: "fail: too small subnet CIDR",
 			modifyVnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				vnet.DhcpOptions.DNSServers = &[]string{
-					"172.16.1.1",
-				}
+				(*vnet.Subnets)[0].AddressPrefix = to.StringPtr("10.0.0.0/28")
 			},
-			wantErr: "400: InvalidLinkedVNet: : The provided vnet '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/virtualNetworks/testVnet' is invalid: custom DNS servers are not supported.",
+			wantErr: "400: InvalidLinkedVNet: : The provided subnet '" + genericSubnet + "' is invalid: must be /27 or larger.",
 		},
 	} {
-		t.Run(tt.name, func(t *testing.T) {
-			oc := &api.OpenShiftCluster{
-				Location: "eastus",
-				Properties: api.OpenShiftClusterProperties{
-					ClusterProfile: api.ClusterProfile{
-						ResourceGroupID: resourceGroupID,
-					},
-					NetworkProfile: api.NetworkProfile{
-						PodCIDR:     "10.0.2.0/24",
-						ServiceCIDR: "10.0.3.0/24",
-					},
-					MasterProfile: api.MasterProfile{
-						SubnetID: masterSubnet,
-					},
-					WorkerProfiles: []api.WorkerProfile{
-						{
-							SubnetID: workerSubnet,
-						},
-					},
+		oc := &api.OpenShiftCluster{
+			Properties: api.OpenShiftClusterProperties{
+				ClusterProfile: api.ClusterProfile{
+					ResourceGroupID: resourceGroupID,
 				},
-			}
-
-			vnet := &mgmtnetwork.VirtualNetwork{
-				Location: to.StringPtr("eastus"),
-				ID:       &vnetID,
-				VirtualNetworkPropertiesFormat: &mgmtnetwork.VirtualNetworkPropertiesFormat{
-					DhcpOptions: &mgmtnetwork.DhcpOptions{
-						DNSServers: &[]string{},
-					},
-					Subnets: &[]mgmtnetwork.Subnet{
-						{
-							ID: &masterSubnet,
-							SubnetPropertiesFormat: &mgmtnetwork.SubnetPropertiesFormat{
-								AddressPrefix: to.StringPtr("10.0.0.0/24"),
-								NetworkSecurityGroup: &mgmtnetwork.SecurityGroup{
-									ID: &masterNSGv1,
-								},
-								ServiceEndpoints: &[]mgmtnetwork.ServiceEndpointPropertiesFormat{
-									{
-										Service:           to.StringPtr("Microsoft.ContainerRegistry"),
-										ProvisioningState: mgmtnetwork.Succeeded,
-									},
-								},
-								PrivateLinkServiceNetworkPolicies: to.StringPtr("Disabled"),
-							},
-						},
-						{
-							ID: &workerSubnet,
-							SubnetPropertiesFormat: &mgmtnetwork.SubnetPropertiesFormat{
-								AddressPrefix: to.StringPtr("10.0.1.0/24"),
-								NetworkSecurityGroup: &mgmtnetwork.SecurityGroup{
-									ID: &workerNSGv1,
-								},
-								ServiceEndpoints: &[]mgmtnetwork.ServiceEndpointPropertiesFormat{
-									{
-										Service:           to.StringPtr("Microsoft.ContainerRegistry"),
-										ProvisioningState: mgmtnetwork.Succeeded,
-									},
-								},
+			},
+		}
+		vnet := &mgmtnetwork.VirtualNetwork{
+			ID: &vnetID,
+			VirtualNetworkPropertiesFormat: &mgmtnetwork.VirtualNetworkPropertiesFormat{
+				Subnets: &[]mgmtnetwork.Subnet{
+					{
+						ID: &genericSubnet,
+						SubnetPropertiesFormat: &mgmtnetwork.SubnetPropertiesFormat{
+							AddressPrefix: to.StringPtr("10.0.0.0/24"),
+							NetworkSecurityGroup: &mgmtnetwork.SecurityGroup{
+								ID: &masterNSGv1,
 							},
-						},
-						{
-							ID: &workerSubnet2,
-							SubnetPropertiesFormat: &mgmtnetwork.SubnetPropertiesFormat{
-								AddressPrefix: to.StringPtr("10.0.4.0/24"),
-								NetworkSecurityGroup: &mgmtnetwork.SecurityGroup{
-									ID: &workerNSGv1,
-								},
-								ServiceEndpoints: &[]mgmtnetwork.ServiceEndpointPropertiesFormat{
-									{
-										Service:           to.StringPtr("Microsoft.ContainerRegistry"),
-										ProvisioningState: mgmtnetwork.Succeeded,
-									},
+							ServiceEndpoints: &[]mgmtnetwork.ServiceEndpointPropertiesFormat{
+								{
+									Service:           to.StringPtr("Microsoft.ContainerRegistry"),
+									ProvisioningState: mgmtnetwork.Succeeded,
 								},
 							},
+							PrivateLinkServiceNetworkPolicies: to.StringPtr("Disabled"),
 						},
 					},
 				},
-			}
-
-			if tt.modifyOC != nil {
-				tt.modifyOC(oc)
-			}
-			if tt.modifyVnet != nil {
-				tt.modifyVnet(vnet)
-			}
-
-			err := validateVnet(ctx, logrus.NewEntry(logrus.StandardLogger()), oc, vnet)
-			if err != nil && err.Error() != tt.wantErr ||
-				err == nil && tt.wantErr != "" {
-				t.Error(err)
-			}
-		})
-	}
-}
-func TestValidateVnetPermissions(t *testing.T) {
-	ctx := context.Background()
-
-	resourceGroupID := "/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup"
-	vnetID := resourceGroupID + "/providers/Microsoft.Network/virtualNetworks/testVnet"
-
-	controller := gomock.NewController(t)
-	defer controller.Finish()
-
-	for _, tt := range []struct {
-		name    string
-		mocks   func(*mock_authorization.MockPermissionsClient, func())
-		wantErr string
-	}{
-		{
-			name: "pass",
-			mocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel func()) {
-				permissionsClient.EXPECT().
-					ListForResource(gomock.Any(), "", "", "", "", "").
-					Return([]mgmtauthorization.Permission{
-						{
-							Actions: &[]string{
-								"Microsoft.Network/virtualNetworks/join/action",
-								"Microsoft.Network/virtualNetworks/read",
-								"Microsoft.Network/virtualNetworks/write",
-								"Microsoft.Network/virtualNetworks/subnets/join/action",
-								"Microsoft.Network/virtualNetworks/subnets/read",
-								"Microsoft.Network/virtualNetworks/subnets/write",
-							},
-							NotActions: &[]string{},
-						},
-					}, nil)
 			},
-		},
-		{
-			name: "fail: missing permissions",
-			mocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel func()) {
-				permissionsClient.EXPECT().
-					ListForResource(gomock.Any(), "", "", "", "", "").
-					Do(func(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) {
-						cancel()
-					}).
-					Return(
-						[]mgmtauthorization.Permission{
-							{
-								Actions:    &[]string{},
-								NotActions: &[]string{},
-							},
-						},
-						nil,
-					)
-			},
-			wantErr: "400: InvalidResourceProviderPermissions: : The resource provider does not have Network Contributor permission on vnet '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/virtualNetworks/testVnet'.",
-		},
-		{
-			name: "fail: not found",
-			mocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel func()) {
-				permissionsClient.EXPECT().
-					ListForResource(gomock.Any(), "", "", "", "", "").
-					Do(func(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) {
-						cancel()
-					}).
-					Return(
-						nil,
-						autorest.DetailedError{
-							StatusCode: http.StatusNotFound,
-						},
-					)
-			},
-			wantErr: "400: InvalidLinkedVNet: : The vnet '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/virtualNetworks/testVnet' could not be found.",
-		},
-	} {
-		t.Run(tt.name, func(t *testing.T) {
-			ctx, cancel := context.WithCancel(ctx)
-			defer cancel()
+		}
 
-			permissionsClient := mock_authorization.NewMockPermissionsClient(controller)
+		if tt.modifyOC != nil {
+			tt.modifyOC(oc)
+		}
+		if tt.modifyVnet != nil {
+			tt.modifyVnet(vnet)
+		}
 
-			tt.mocks(permissionsClient, cancel)
+		dv := &openShiftClusterDynamicValidator{
+			log: logrus.NewEntry(logrus.StandardLogger()),
+			oc:  oc,
+		}
 
-			err := validateVnetPermissions(ctx, logrus.NewEntry(logrus.StandardLogger()), mockrefreshable.NewMockAuthorizer(controller), permissionsClient, vnetID, &azure.Resource{}, api.CloudErrorCodeInvalidResourceProviderPermissions, "resource provider")
-			if err != nil && err.Error() != tt.wantErr ||
-				err == nil && tt.wantErr != "" {
-				t.Error(err)
-			}
-		})
+		// purposefully hardcoding path to "" so it is not needed in the wantErr message
+		_, err := dv.validateSubnet(ctx, vnet, "", genericSubnet)
+		if err != nil && err.Error() != tt.wantErr ||
+			err == nil && tt.wantErr != "" {
+			t.Error(err)
+		}
 	}
 }
 
-func TestValidateRouteTablePermissionsSubnet(t *testing.T) {
+func TestValidateCIDRRanges(t *testing.T) {
 	ctx := context.Background()
 
 	resourceGroupID := "/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup"
 	vnetID := resourceGroupID + "/providers/Microsoft.Network/virtualNetworks/testVnet"
 	masterSubnet := vnetID + "/subnet/masterSubnet"
-	rtID := resourceGroupID + "/providers/Microsoft.Network/routeTables/testRT"
-
-	controller := gomock.NewController(t)
-	defer controller.Finish()
+	workerSubnet := vnetID + "/subnet/workerSubnet"
+	masterNSGv1 := resourceGroupID + "/providers/Microsoft.Network/networkSecurityGroups/aro-controlplane-nsg"
+	workerNSGv1 := resourceGroupID + "/providers/Microsoft.Network/networkSecurityGroups/aro-node-nsg"
 
 	for _, tt := range []struct {
-		name    string
-		mocks   func(*mock_authorization.MockPermissionsClient, func())
-		vnet    func(*mgmtnetwork.VirtualNetwork)
-		subnet  string
-		wantErr string
+		name       string
+		modifyOC   func(*api.OpenShiftCluster)
+		modifyVnet func(*mgmtnetwork.VirtualNetwork)
+		wantErr    string
 	}{
 		{
 			name: "pass",
-			mocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel func()) {
-				permissionsClient.EXPECT().
-					ListForResource(gomock.Any(), "testGroup", "Microsoft.Network", "", "routeTables", "testRT").
-					Return([]mgmtauthorization.Permission{
-						{
-							Actions: &[]string{
-								"Microsoft.Network/routeTables/join/action",
-								"Microsoft.Network/routeTables/read",
-								"Microsoft.Network/routeTables/write",
-							},
-							NotActions: &[]string{},
-						},
-					}, nil)
-			},
-			subnet: masterSubnet,
-		},
-		{
-			name: "pass (no route table)",
-			vnet: func(vnet *mgmtnetwork.VirtualNetwork) {
-				(*vnet.Subnets)[0].RouteTable = nil
-			},
-			subnet: masterSubnet,
 		},
 		{
-			name: "fail: missing permissions",
-			mocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel func()) {
-				permissionsClient.EXPECT().
-					ListForResource(gomock.Any(), "testGroup", "Microsoft.Network", "", "routeTables", "testRT").
-					Do(func(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) {
-						cancel()
-					}).
-					Return(
-						[]mgmtauthorization.Permission{
-							{
-								Actions:    &[]string{},
-								NotActions: &[]string{},
-							},
-						},
-						nil,
-					)
-			},
-			subnet:  masterSubnet,
-			wantErr: "400: InvalidResourceProviderPermissions: : The resource provider does not have Network Contributor permission on route table '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/routeTables/testRT'.",
-		},
-		{
-			name: "fail: not found",
-			mocks: func(permissionsClient *mock_authorization.MockPermissionsClient, cancel func()) {
-				permissionsClient.EXPECT().
-					ListForResource(gomock.Any(), "testGroup", "Microsoft.Network", "", "routeTables", "testRT").
-					Do(func(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) {
-						cancel()
-					}).
-					Return(
-						nil,
-						autorest.DetailedError{
-							StatusCode: http.StatusNotFound,
-						},
-					)
+			name: "fail: conflicting ranges",
+			modifyOC: func(oc *api.OpenShiftCluster) {
+				oc.Properties.NetworkProfile.ServiceCIDR = "10.0.0.0/24"
 			},
-			subnet:  masterSubnet,
-			wantErr: "400: InvalidLinkedRouteTable: : The route table '/subscriptions/0000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Network/routeTables/testRT' could not be found.",
-		},
-		{
-			name:    "fail: subnet not found",
-			subnet:  "doesnotexist",
-			wantErr: "400: InvalidLinkedVNet: properties.masterProfile.subnetId: The subnet 'doesnotexist' could not be found.",
+			wantErr: "400: InvalidLinkedVNet: : The provided CIDRs must not overlap: '10.0.0.0/24 overlaps with 10.0.0.0/24'.",
 		},
 	} {
-		t.Run(tt.name, func(t *testing.T) {
-			ctx, cancel := context.WithCancel(ctx)
-			defer cancel()
+		oc := &api.OpenShiftCluster{
+			Properties: api.OpenShiftClusterProperties{
+				ClusterProfile: api.ClusterProfile{
+					ResourceGroupID: resourceGroupID,
+				},
+				NetworkProfile: api.NetworkProfile{
+					PodCIDR:     "10.0.2.0/24",
+					ServiceCIDR: "10.0.3.0/24",
+				},
+				MasterProfile: api.MasterProfile{
+					SubnetID: masterSubnet,
+				},
+				WorkerProfiles: []api.WorkerProfile{
+					{
+						SubnetID: workerSubnet,
+					},
+					{
+						SubnetID: workerSubnet,
+					},
+				},
+			},
+		}
 
-			vnet := &mgmtnetwork.VirtualNetwork{
-				VirtualNetworkPropertiesFormat: &mgmtnetwork.VirtualNetworkPropertiesFormat{
-					Subnets: &[]mgmtnetwork.Subnet{
-						{
-							ID: &masterSubnet,
-							SubnetPropertiesFormat: &mgmtnetwork.SubnetPropertiesFormat{
-								RouteTable: &mgmtnetwork.RouteTable{
-									ID: &rtID,
+		vnet := &mgmtnetwork.VirtualNetwork{
+			ID: &vnetID,
+			VirtualNetworkPropertiesFormat: &mgmtnetwork.VirtualNetworkPropertiesFormat{
+				Subnets: &[]mgmtnetwork.Subnet{
+					{
+						ID: &masterSubnet,
+						SubnetPropertiesFormat: &mgmtnetwork.SubnetPropertiesFormat{
+							AddressPrefix: to.StringPtr("10.0.0.0/24"),
+							NetworkSecurityGroup: &mgmtnetwork.SecurityGroup{
+								ID: &masterNSGv1,
+							},
+							ServiceEndpoints: &[]mgmtnetwork.ServiceEndpointPropertiesFormat{
+								{
+									Service:           to.StringPtr("Microsoft.ContainerRegistry"),
+									ProvisioningState: mgmtnetwork.Succeeded,
+								},
+							},
+							PrivateLinkServiceNetworkPolicies: to.StringPtr("Disabled"),
+						},
+					},
+					{
+						ID: &workerSubnet,
+						SubnetPropertiesFormat: &mgmtnetwork.SubnetPropertiesFormat{
+							AddressPrefix: to.StringPtr("10.0.1.0/24"),
+							NetworkSecurityGroup: &mgmtnetwork.SecurityGroup{
+								ID: &workerNSGv1,
+							},
+							ServiceEndpoints: &[]mgmtnetwork.ServiceEndpointPropertiesFormat{
+								{
+									Service:           to.StringPtr("Microsoft.ContainerRegistry"),
+									ProvisioningState: mgmtnetwork.Succeeded,
 								},
 							},
 						},
 					},
 				},
-			}
-
-			permissionsClient := mock_authorization.NewMockPermissionsClient(controller)
+			},
+		}
 
-			if tt.mocks != nil {
-				tt.mocks(permissionsClient, cancel)
-			}
+		if tt.modifyOC != nil {
+			tt.modifyOC(oc)
+		}
+		if tt.modifyVnet != nil {
+			tt.modifyVnet(vnet)
+		}
 
-			if tt.vnet != nil {
-				tt.vnet(vnet)
-			}
+		dv := &openShiftClusterDynamicValidator{
+			log: logrus.NewEntry(logrus.StandardLogger()),
+			oc:  oc,
+		}
 
-			err := validateRouteTablePermissionsSubnet(ctx, logrus.NewEntry(logrus.StandardLogger()), mockrefreshable.NewMockAuthorizer(controller), permissionsClient, vnet, tt.subnet, "properties.masterProfile.subnetId", api.CloudErrorCodeInvalidResourceProviderPermissions, "resource provider")
-			if err != nil && err.Error() != tt.wantErr ||
-				err == nil && tt.wantErr != "" {
-				t.Error(err)
-			}
-		})
+		err := dv.validateCIDRRanges(ctx, vnet)
+		if err != nil && err.Error() != tt.wantErr ||
+			err == nil && tt.wantErr != "" {
+			t.Error(err)
+		}
 	}
 }
diff --git a/pkg/cluster/createdat_test.go b/pkg/cluster/createdat_test.go
deleted file mode 100644
index b05ad9c16..000000000
--- a/pkg/cluster/createdat_test.go
+++ /dev/null
@@ -1,136 +0,0 @@
-package cluster
-
-// Copyright (c) Microsoft Corporation.
-// Licensed under the Apache License 2.0.
-
-import (
-	"context"
-	"strings"
-	"testing"
-	"time"
-
-	"github.com/sirupsen/logrus"
-	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	k8sfake "k8s.io/client-go/kubernetes/fake"
-
-	"github.com/Azure/ARO-RP/pkg/api"
-	testdatabase "github.com/Azure/ARO-RP/test/database"
-)
-
-func TestPopulateCreatedAt(t *testing.T) {
-	ctx := context.Background()
-	resourceID := "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourceGroup/providers/microsoft.redhatopenshift/openshiftclusters/resourceName"
-	mockCreationTimestamp := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)
-
-	for _, tt := range []struct {
-		name     string
-		ns       *corev1.Namespace
-		doc      *api.OpenShiftClusterDocument
-		wantTime time.Time
-		wantErr  string
-	}{
-		{
-			name: "doc without timestamp",
-			ns: &corev1.Namespace{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:              "default",
-					CreationTimestamp: metav1.NewTime(mockCreationTimestamp),
-				},
-			},
-			doc: &api.OpenShiftClusterDocument{
-				Key: strings.ToLower(resourceID),
-				OpenShiftCluster: &api.OpenShiftCluster{
-					ID: resourceID,
-					Properties: api.OpenShiftClusterProperties{
-						ProvisioningState: api.ProvisioningStateAdminUpdating,
-					},
-				},
-			},
-			wantTime: mockCreationTimestamp,
-		},
-		{
-			name: "doc with timestamp",
-			ns: &corev1.Namespace{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:              "default",
-					CreationTimestamp: metav1.NewTime(mockCreationTimestamp),
-				},
-			},
-			doc: &api.OpenShiftClusterDocument{
-				Key: strings.ToLower(resourceID),
-				OpenShiftCluster: &api.OpenShiftCluster{
-					ID: resourceID,
-					Properties: api.OpenShiftClusterProperties{
-						CreatedAt:         time.Date(1970, 1, 1, 0, 0, 0, 1, time.UTC),
-						ProvisioningState: api.ProvisioningStateAdminUpdating,
-					},
-				},
-			},
-			wantTime: time.Date(1970, 1, 1, 0, 0, 0, 1, time.UTC),
-		},
-		{
-			name: "default namespace doesn't exist",
-			ns:   &corev1.Namespace{},
-			doc: &api.OpenShiftClusterDocument{
-				Key: strings.ToLower(resourceID),
-				OpenShiftCluster: &api.OpenShiftCluster{
-					ID: resourceID,
-					Properties: api.OpenShiftClusterProperties{
-						ProvisioningState: api.ProvisioningStateAdminUpdating,
-					},
-				},
-			},
-			wantErr: "namespaces \"default\" not found",
-		},
-	} {
-		t.Run(tt.name, func(t *testing.T) {
-			fakeOpenShiftClustersDatabase, _ := testdatabase.NewFakeOpenShiftClusters()
-			fixture := testdatabase.NewFixture().WithOpenShiftClusters(fakeOpenShiftClustersDatabase)
-			fixture.AddOpenShiftClusterDocuments(tt.doc)
-			err := fixture.Create()
-			if err != nil {
-				t.Fatal(err)
-			}
-
-			clusterdoc, err := fakeOpenShiftClustersDatabase.Dequeue(ctx)
-			if err != nil {
-				t.Fatal(err)
-			}
-
-			m := &manager{
-				log:           logrus.NewEntry(logrus.StandardLogger()),
-				kubernetescli: k8sfake.NewSimpleClientset(tt.ns),
-				doc:           clusterdoc,
-				db:            fakeOpenShiftClustersDatabase,
-			}
-
-			err = m.populateCreatedAt(ctx)
-			if err != nil && err.Error() != tt.wantErr ||
-				err == nil && tt.wantErr != "" {
-				t.Fatal(err)
-			}
-
-			if err == nil {
-				if tt.wantErr != "" {
-					t.Error(err)
-				}
-
-				doc, err := fakeOpenShiftClustersDatabase.Get(ctx, strings.ToLower(resourceID))
-				if err != nil {
-					t.Fatal(err)
-				}
-				if tt.wantTime != doc.OpenShiftCluster.Properties.CreatedAt {
-					t.Error(doc.OpenShiftCluster.Properties.CreatedAt)
-				}
-				if tt.wantTime != m.doc.OpenShiftCluster.Properties.CreatedAt {
-					t.Error(m.doc.OpenShiftCluster.Properties.CreatedAt)
-				}
-			} else {
-				if err.Error() != tt.wantErr {
-					t.Error(err)
-				}
-			}
-		})
-	}
-}
diff --git a/pkg/cluster/fixpullsecret_test.go b/pkg/cluster/fixpullsecret_test.go
new file mode 100644
index 000000000..0fa2ca310
--- /dev/null
+++ b/pkg/cluster/fixpullsecret_test.go
@@ -0,0 +1,115 @@
+package cluster
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"context"
+	"testing"
+
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/client-go/kubernetes/fake"
+	ktesting "k8s.io/client-go/testing"
+
+	"github.com/Azure/ARO-RP/pkg/api"
+)
+
+func TestFixPullSecret(t *testing.T) {
+	ctx := context.Background()
+
+	for _, tt := range []struct {
+		name        string
+		current     []byte
+		rps         []*api.RegistryProfile
+		want        string
+		wantUpdated bool
+	}{
+		{
+			name: "missing pull secret",
+			rps: []*api.RegistryProfile{
+				{
+					Name:     "arosvc.azurecr.io",
+					Username: "fred",
+					Password: "enter",
+				},
+			},
+			want:        `{"auths":{"arosvc.azurecr.io":{"auth":"ZnJlZDplbnRlcg=="}}}`,
+			wantUpdated: true,
+		},
+		{
+			name:    "modified pull secret",
+			current: []byte(`{"auths":{"arosvc.azurecr.io":{"auth":""}}}`),
+			rps: []*api.RegistryProfile{
+				{
+					Name:     "arosvc.azurecr.io",
+					Username: "fred",
+					Password: "enter",
+				},
+			},
+			want:        `{"auths":{"arosvc.azurecr.io":{"auth":"ZnJlZDplbnRlcg=="}}}`,
+			wantUpdated: true,
+		},
+		{
+			name:    "no change",
+			current: []byte(`{"auths":{"arosvc.azurecr.io":{"auth":"ZnJlZDplbnRlcg=="}}}`),
+			rps: []*api.RegistryProfile{
+				{
+					Name:     "arosvc.azurecr.io",
+					Username: "fred",
+					Password: "enter",
+				},
+			},
+			want: `{"auths":{"arosvc.azurecr.io":{"auth":"ZnJlZDplbnRlcg=="}}}`,
+		},
+	} {
+		t.Run(tt.name, func(t *testing.T) {
+			var updated bool
+
+			fakecli := fake.NewSimpleClientset(&v1.Secret{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "pull-secret",
+					Namespace: "openshift-config",
+				},
+				Data: map[string][]byte{
+					v1.DockerConfigJsonKey: tt.current,
+				},
+			})
+
+			fakecli.PrependReactor("update", "secrets", func(action ktesting.Action) (handled bool, ret runtime.Object, err error) {
+				updated = true
+				return false, nil, nil
+			})
+
+			m := &manager{
+				kubernetescli: fakecli,
+				doc: &api.OpenShiftClusterDocument{
+					OpenShiftCluster: &api.OpenShiftCluster{
+						Properties: api.OpenShiftClusterProperties{
+							RegistryProfiles: tt.rps,
+						},
+					},
+				},
+			}
+
+			err := m.fixPullSecret(ctx)
+			if err != nil {
+				t.Error(err)
+			}
+
+			if updated != tt.wantUpdated {
+				t.Fatal(updated)
+			}
+
+			s, err := m.kubernetescli.CoreV1().Secrets("openshift-config").Get(ctx, "pull-secret", metav1.GetOptions{})
+			if err != nil {
+				t.Error(err)
+			}
+
+			if string(s.Data[v1.DockerConfigJsonKey]) != tt.want {
+				t.Error(string(s.Data[v1.DockerConfigJsonKey]))
+			}
+		})
+	}
+}
diff --git a/pkg/cluster/ipaddresses_test.go b/pkg/cluster/ipaddresses_test.go
deleted file mode 100644
index 10a01b0bb..000000000
--- a/pkg/cluster/ipaddresses_test.go
+++ /dev/null
@@ -1,115 +0,0 @@
-package cluster
-
-// Copyright (c) Microsoft Corporation.
-// Licensed under the Apache License 2.0.
-
-import (
-	"context"
-	"strings"
-	"testing"
-
-	"github.com/golang/mock/gomock"
-	v1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/client-go/kubernetes/fake"
-
-	"github.com/Azure/ARO-RP/pkg/api"
-	mock_dnsmanager "github.com/Azure/ARO-RP/pkg/util/mocks/dns"
-	testdatabase "github.com/Azure/ARO-RP/test/database"
-)
-
-func TestUpdateOrCreateRouterIP(t *testing.T) {
-	ctx := context.Background()
-
-	type test struct {
-		name          string
-		kubernetescli *fake.Clientset
-		mocks         func(*mock_dnsmanager.MockManager)
-		wantErr       string
-	}
-
-	for _, tt := range []*test{
-		{
-			name: "create/update success",
-			mocks: func(dm *mock_dnsmanager.MockManager) {
-				dm.EXPECT().
-					CreateOrUpdateRouter(gomock.Any(), gomock.Any(), gomock.Any()).
-					Return(nil)
-			},
-			kubernetescli: fake.NewSimpleClientset(&v1.Service{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "router-default",
-					Namespace: "openshift-ingress",
-				},
-				Status: v1.ServiceStatus{
-					LoadBalancer: v1.LoadBalancerStatus{
-						Ingress: []v1.LoadBalancerIngress{{
-							IP: "1.2.3.4",
-						}},
-					},
-				},
-			}),
-		},
-		{
-			name: "create/update failed",
-			kubernetescli: fake.NewSimpleClientset(&v1.Service{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "router-default",
-					Namespace: "openshift-ingress",
-				},
-			}),
-			wantErr: "routerIP not found",
-		},
-	} {
-		t.Run(tt.name, func(t *testing.T) {
-			controller := gomock.NewController(t)
-			defer controller.Finish()
-
-			dm := mock_dnsmanager.NewMockManager(controller)
-			if tt.mocks != nil {
-				tt.mocks(dm)
-			}
-
-			key := "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName1"
-
-			openShiftClustersDatabase, _ := testdatabase.NewFakeOpenShiftClusters()
-			fixture := testdatabase.NewFixture().WithOpenShiftClusters(openShiftClustersDatabase)
-			fixture.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{
-				Key: strings.ToLower(key),
-				OpenShiftCluster: &api.OpenShiftCluster{
-					ID: key,
-					Properties: api.OpenShiftClusterProperties{
-						IngressProfiles: []api.IngressProfile{
-							{
-								Visibility: api.VisibilityPublic,
-							},
-						},
-						ProvisioningState: api.ProvisioningStateCreating,
-					},
-				},
-			})
-			err := fixture.Create()
-			if err != nil {
-				t.Fatal(err)
-			}
-
-			clusterdoc, err := openShiftClustersDatabase.Dequeue(ctx)
-			if err != nil {
-				t.Fatal(err)
-			}
-
-			m := &manager{
-				kubernetescli: tt.kubernetescli,
-				dns:           dm,
-				db:            openShiftClustersDatabase,
-				doc:           clusterdoc,
-			}
-
-			err = m.createOrUpdateRouterIP(ctx)
-			if err != nil && err.Error() != tt.wantErr ||
-				err == nil && tt.wantErr != "" {
-				t.Error(err)
-			}
-		})
-	}
-}
diff --git a/pkg/frontend/admin_openshiftcluster_kubernetesobjects_test.go b/pkg/frontend/admin_openshiftcluster_kubernetesobjects_test.go
index fb6d3d57a..520e23479 100644
--- a/pkg/frontend/admin_openshiftcluster_kubernetesobjects_test.go
+++ b/pkg/frontend/admin_openshiftcluster_kubernetesobjects_test.go
@@ -32,7 +32,7 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) {
 		objKind        string
 		objNamespace   string
 		objName        string
-		mocks          func(*test, *mock_adminactions.MockKubeActions)
+		mocks          func(*test, *mock_adminactions.MockInterface)
 		method         string
 		wantStatusCode int
 		wantResponse   []byte
@@ -47,9 +47,9 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) {
 			objKind:      "ConfigMap",
 			objNamespace: "openshift-project",
 			objName:      "config",
-			mocks: func(tt *test, k *mock_adminactions.MockKubeActions) {
-				k.EXPECT().
-					KubeGet(gomock.Any(), tt.objKind, tt.objNamespace, tt.objName).
+			mocks: func(tt *test, a *mock_adminactions.MockInterface) {
+				a.EXPECT().
+					K8sGet(gomock.Any(), tt.objKind, tt.objNamespace, tt.objName).
 					Return([]byte(`{"Kind": "test"}`), nil)
 
 			},
@@ -62,9 +62,9 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) {
 			resourceID:   fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName", mockSubID),
 			objKind:      "ConfigMap",
 			objNamespace: "openshift-project",
-			mocks: func(tt *test, k *mock_adminactions.MockKubeActions) {
-				k.EXPECT().
-					KubeList(gomock.Any(), tt.objKind, tt.objNamespace).
+			mocks: func(tt *test, a *mock_adminactions.MockInterface) {
+				a.EXPECT().
+					K8sList(gomock.Any(), tt.objKind, tt.objNamespace).
 					Return([]byte(`{"Kind": "test"}`), nil)
 
 			},
@@ -77,7 +77,7 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) {
 			resourceID:   fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName", mockSubID),
 			objNamespace: "openshift-project",
 			objName:      "config",
-			mocks: func(tt *test, k *mock_adminactions.MockKubeActions) {
+			mocks: func(tt *test, a *mock_adminactions.MockInterface) {
 			},
 			wantStatusCode: http.StatusBadRequest,
 			wantError:      "400: InvalidParameter: : The provided groupKind '' is invalid.",
@@ -89,7 +89,7 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) {
 			objKind:      "Secret",
 			objNamespace: "openshift-project",
 			objName:      "config",
-			mocks: func(tt *test, k *mock_adminactions.MockKubeActions) {
+			mocks: func(tt *test, a *mock_adminactions.MockInterface) {
 			},
 			wantStatusCode: http.StatusForbidden,
 			wantError:      "403: Forbidden: : Access to secrets is forbidden.",
@@ -101,9 +101,9 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) {
 			objKind:      "ConfigMap",
 			objNamespace: "openshift-project",
 			objName:      "config",
-			mocks: func(tt *test, k *mock_adminactions.MockKubeActions) {
-				k.EXPECT().
-					KubeDelete(gomock.Any(), tt.objKind, tt.objNamespace, tt.objName).
+			mocks: func(tt *test, a *mock_adminactions.MockInterface) {
+				a.EXPECT().
+					K8sDelete(gomock.Any(), tt.objKind, tt.objNamespace, tt.objName).
 					Return(nil)
 
 			},
@@ -115,7 +115,7 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) {
 			resourceID:   fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName", mockSubID),
 			objNamespace: "openshift-project",
 			objName:      "config",
-			mocks: func(tt *test, k *mock_adminactions.MockKubeActions) {
+			mocks: func(tt *test, a *mock_adminactions.MockInterface) {
 			},
 			wantStatusCode: http.StatusBadRequest,
 			wantError:      "400: InvalidParameter: : The provided groupKind '' is invalid.",
@@ -126,7 +126,7 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) {
 			resourceID:   fmt.Sprintf("/subscriptions/%s/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName", mockSubID),
 			objKind:      "this",
 			objNamespace: "openshift-project",
-			mocks: func(tt *test, k *mock_adminactions.MockKubeActions) {
+			mocks: func(tt *test, a *mock_adminactions.MockInterface) {
 			},
 			wantStatusCode: http.StatusBadRequest,
 			wantError:      "400: InvalidParameter: : The provided name '' is invalid.",
@@ -138,7 +138,7 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) {
 			objKind:      "Secret",
 			objNamespace: "openshift-project",
 			objName:      "config",
-			mocks: func(tt *test, k *mock_adminactions.MockKubeActions) {
+			mocks: func(tt *test, a *mock_adminactions.MockInterface) {
 			},
 			wantStatusCode: http.StatusForbidden,
 			wantError:      "403: Forbidden: : Access to secrets is forbidden.",
@@ -148,8 +148,8 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) {
 			ti := newTestInfra(t).WithOpenShiftClusters().WithSubscriptions()
 			defer ti.done()
 
-			k := mock_adminactions.NewMockKubeActions(ti.controller)
-			tt.mocks(tt, k)
+			a := mock_adminactions.NewMockInterface(ti.controller)
+			tt.mocks(tt, a)
 
 			ti.fixture.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{
 				Key: strings.ToLower(tt.resourceID),
@@ -175,9 +175,9 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) {
 			}
 
 			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster,
-				*api.SubscriptionDocument) (adminactions.KubeActions, error) {
-				return k, nil
-			}, nil)
+				*api.SubscriptionDocument) (adminactions.Interface, error) {
+				return a, nil
+			})
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -301,7 +301,7 @@ func TestAdminPostKubernetesObjects(t *testing.T) {
 	type test struct {
 		name           string
 		resourceID     string
-		mocks          func(*test, *mock_adminactions.MockKubeActions)
+		mocks          func(*test, *mock_adminactions.MockInterface)
 		wantStatusCode int
 		objInBody      *unstructured.Unstructured
 		wantError      string
@@ -320,8 +320,8 @@ func TestAdminPostKubernetesObjects(t *testing.T) {
 					},
 				},
 			},
-			mocks: func(tt *test, k *mock_adminactions.MockKubeActions) {
-				k.EXPECT().KubeCreateOrUpdate(gomock.Any(), tt.objInBody).
+			mocks: func(tt *test, a *mock_adminactions.MockInterface) {
+				a.EXPECT().K8sCreateOrUpdate(gomock.Any(), tt.objInBody).
 					Return(nil)
 			},
 			wantStatusCode: http.StatusOK,
@@ -334,7 +334,7 @@ func TestAdminPostKubernetesObjects(t *testing.T) {
 					"kind": "Secret",
 				},
 			},
-			mocks:          func(tt *test, k *mock_adminactions.MockKubeActions) {},
+			mocks:          func(tt *test, a *mock_adminactions.MockInterface) {},
 			wantStatusCode: http.StatusForbidden,
 			wantError:      "403: Forbidden: : Access to secrets is forbidden.",
 		},
@@ -343,8 +343,8 @@ func TestAdminPostKubernetesObjects(t *testing.T) {
 			ti := newTestInfra(t).WithOpenShiftClusters().WithSubscriptions()
 			defer ti.done()
 
-			k := mock_adminactions.NewMockKubeActions(ti.controller)
-			tt.mocks(tt, k)
+			a := mock_adminactions.NewMockInterface(ti.controller)
+			tt.mocks(tt, a)
 
 			ti.fixture.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{
 				Key: strings.ToLower(tt.resourceID),
@@ -370,9 +370,9 @@ func TestAdminPostKubernetesObjects(t *testing.T) {
 			}
 
 			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster,
-				*api.SubscriptionDocument) (adminactions.KubeActions, error) {
-				return k, nil
-			}, nil)
+				*api.SubscriptionDocument) (adminactions.Interface, error) {
+				return a, nil
+			})
 			if err != nil {
 				t.Fatal(err)
 			}
diff --git a/pkg/frontend/admin_openshiftcluster_list_test.go b/pkg/frontend/admin_openshiftcluster_list_test.go
index 5ac1e614e..c2781e0f6 100644
--- a/pkg/frontend/admin_openshiftcluster_list_test.go
+++ b/pkg/frontend/admin_openshiftcluster_list_test.go
@@ -118,7 +118,7 @@ func TestAdminListOpenShiftCluster(t *testing.T) {
 				ti.openShiftClustersClient.SetError(tt.throwsError)
 			}
 
-			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, aead, nil, nil)
+			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, aead, nil)
 			if err != nil {
 				t.Fatal(err)
 			}
diff --git a/pkg/frontend/admin_openshiftcluster_redeployvm_test.go b/pkg/frontend/admin_openshiftcluster_redeployvm_test.go
index df7190057..a5dc1a734 100644
--- a/pkg/frontend/admin_openshiftcluster_redeployvm_test.go
+++ b/pkg/frontend/admin_openshiftcluster_redeployvm_test.go
@@ -32,7 +32,7 @@ func TestAdminRedeployVM(t *testing.T) {
 		resourceID     string
 		fixture        func(*testdatabase.Fixture)
 		vmName         string
-		mocks          func(*test, *mock_adminactions.MockAzureActions)
+		mocks          func(*test, *mock_adminactions.MockInterface)
 		wantStatusCode int
 		wantResponse   []byte
 		wantError      string
@@ -66,7 +66,7 @@ func TestAdminRedeployVM(t *testing.T) {
 					},
 				})
 			},
-			mocks: func(tt *test, a *mock_adminactions.MockAzureActions) {
+			mocks: func(tt *test, a *mock_adminactions.MockInterface) {
 				a.EXPECT().VMRedeployAndWait(gomock.Any(), tt.vmName).Return(nil)
 			},
 			wantStatusCode: http.StatusOK,
@@ -76,7 +76,7 @@ func TestAdminRedeployVM(t *testing.T) {
 			ti := newTestInfra(t).WithOpenShiftClusters().WithSubscriptions()
 			defer ti.done()
 
-			a := mock_adminactions.NewMockAzureActions(ti.controller)
+			a := mock_adminactions.NewMockInterface(ti.controller)
 			tt.mocks(tt, a)
 
 			err := ti.buildFixtures(tt.fixture)
@@ -84,8 +84,8 @@ func TestAdminRedeployVM(t *testing.T) {
 				t.Fatal(err)
 			}
 
-			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster,
-				*api.SubscriptionDocument) (adminactions.AzureActions, error) {
+			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster,
+				*api.SubscriptionDocument) (adminactions.Interface, error) {
 				return a, nil
 			})
 
diff --git a/pkg/frontend/admin_openshiftcluster_resources_list_test.go b/pkg/frontend/admin_openshiftcluster_resources_list_test.go
index fbcd7bcf2..9ba1f7ec9 100644
--- a/pkg/frontend/admin_openshiftcluster_resources_list_test.go
+++ b/pkg/frontend/admin_openshiftcluster_resources_list_test.go
@@ -30,7 +30,7 @@ func TestAdminListResourcesList(t *testing.T) {
 		name           string
 		resourceID     string
 		fixture        func(f *testdatabase.Fixture)
-		mocks          func(*test, *mock_adminactions.MockAzureActions)
+		mocks          func(*test, *mock_adminactions.MockInterface)
 		wantStatusCode int
 		wantResponse   []byte
 		wantError      string
@@ -65,7 +65,7 @@ func TestAdminListResourcesList(t *testing.T) {
 					},
 				})
 			},
-			mocks: func(tt *test, a *mock_adminactions.MockAzureActions) {
+			mocks: func(tt *test, a *mock_adminactions.MockInterface) {
 
 				a.EXPECT().
 					ResourcesList(gomock.Any()).
@@ -80,7 +80,7 @@ func TestAdminListResourcesList(t *testing.T) {
 			ti := newTestInfra(t).WithSubscriptions().WithOpenShiftClusters()
 			defer ti.done()
 
-			a := mock_adminactions.NewMockAzureActions(ti.controller)
+			a := mock_adminactions.NewMockInterface(ti.controller)
 			tt.mocks(tt, a)
 
 			err := ti.buildFixtures(tt.fixture)
@@ -88,8 +88,8 @@ func TestAdminListResourcesList(t *testing.T) {
 				t.Fatal(err)
 			}
 
-			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster,
-				*api.SubscriptionDocument) (adminactions.AzureActions, error) {
+			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster,
+				*api.SubscriptionDocument) (adminactions.Interface, error) {
 				return a, nil
 			})
 
diff --git a/pkg/frontend/adminactions/resources_list_test.go b/pkg/frontend/adminactions/resources_list_test.go
index b26cae8b9..d7f80474e 100644
--- a/pkg/frontend/adminactions/resources_list_test.go
+++ b/pkg/frontend/adminactions/resources_list_test.go
@@ -143,7 +143,7 @@ func TestResourcesList(t *testing.T) {
 			routeTables := mock_network.NewMockRouteTablesClient(controller)
 			tt.mocks(tt, resources, virtualMachines, virtualNetworks, routeTables)
 
-			a := azureActions{
+			a := adminactions{
 				log: logrus.NewEntry(logrus.StandardLogger()),
 				env: env,
 				oc: &api.OpenShiftCluster{
diff --git a/pkg/frontend/adminactions/upgrade_test.go b/pkg/frontend/adminactions/upgrade_test.go
index 953a75b65..1ab153108 100644
--- a/pkg/frontend/adminactions/upgrade_test.go
+++ b/pkg/frontend/adminactions/upgrade_test.go
@@ -173,12 +173,12 @@ func TestUpgradeCluster(t *testing.T) {
 				return false, nil, nil
 			})
 
-			k := &kubeActions{
+			a := &adminactions{
 				log:       logrus.NewEntry(logrus.StandardLogger()),
 				configcli: tt.fakecli,
 			}
 
-			err := upgrade(ctx, k.log, k.configcli, []*version.Stream{stream43, stream44, stream45}, tt.upgradeY)
+			err := upgrade(ctx, a.log, a.configcli, []*version.Stream{stream43, stream44, stream45}, tt.upgradeY)
 			if err != nil && err.Error() != tt.wantErr ||
 				err == nil && tt.wantErr != "" {
 				t.Error(err)
@@ -188,7 +188,7 @@ func TestUpgradeCluster(t *testing.T) {
 				t.Fatal(updated)
 			}
 
-			cv, err := k.configcli.ConfigV1().ClusterVersions().Get(ctx, "version", metav1.GetOptions{})
+			cv, err := a.configcli.ConfigV1().ClusterVersions().Get(ctx, "version", metav1.GetOptions{})
 			if err != nil {
 				t.Error(err)
 			}
@@ -252,7 +252,7 @@ func TestCheckCustomDNS(t *testing.T) {
 				tt.mocks(virtualNetworks)
 			}
 
-			k := &kubeActions{
+			a := &adminactions{
 				log:             logrus.NewEntry(logrus.StandardLogger()),
 				virtualNetworks: virtualNetworks,
 			}
@@ -265,7 +265,7 @@ func TestCheckCustomDNS(t *testing.T) {
 				},
 			}
 
-			err := checkCustomDNS(ctx, oc, k.virtualNetworks)
+			err := checkCustomDNS(ctx, oc, a.virtualNetworks)
 			if err != nil && err.Error() != tt.wantErr ||
 				err == nil && tt.wantErr != "" {
 				t.Error(err)
diff --git a/pkg/frontend/asyncoperationresult_get_test.go b/pkg/frontend/asyncoperationresult_get_test.go
index 247e323ef..514d89466 100644
--- a/pkg/frontend/asyncoperationresult_get_test.go
+++ b/pkg/frontend/asyncoperationresult_get_test.go
@@ -122,7 +122,7 @@ func TestGetAsyncOperationResult(t *testing.T) {
 				t.Fatal(err)
 			}
 
-			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, nil, nil, nil)
+			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, nil, nil)
 			if err != nil {
 				t.Fatal(err)
 			}
diff --git a/pkg/frontend/asyncoperationsstatus_get_test.go b/pkg/frontend/asyncoperationsstatus_get_test.go
index 9d09a073e..2854bc964 100644
--- a/pkg/frontend/asyncoperationsstatus_get_test.go
+++ b/pkg/frontend/asyncoperationsstatus_get_test.go
@@ -160,7 +160,7 @@ func TestGetAsyncOperationsStatus(t *testing.T) {
 				ti.asyncOperationsClient.SetError(tt.dbError)
 			}
 
-			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, nil, nil, nil)
+			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, nil, nil)
 			if err != nil {
 				t.Fatal(err)
 			}
diff --git a/pkg/frontend/openshiftcluster_delete_test.go b/pkg/frontend/openshiftcluster_delete_test.go
index 14063735a..0f3095d3d 100644
--- a/pkg/frontend/openshiftcluster_delete_test.go
+++ b/pkg/frontend/openshiftcluster_delete_test.go
@@ -114,7 +114,7 @@ func TestDeleteOpenShiftCluster(t *testing.T) {
 				ti.subscriptionsClient.SetError(tt.dbError)
 			}
 
-			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, nil, nil, nil)
+			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, nil, nil)
 			if err != nil {
 				t.Fatal(err)
 			}
diff --git a/pkg/frontend/openshiftcluster_get_test.go b/pkg/frontend/openshiftcluster_get_test.go
index 52d68e079..617b592ca 100644
--- a/pkg/frontend/openshiftcluster_get_test.go
+++ b/pkg/frontend/openshiftcluster_get_test.go
@@ -92,7 +92,7 @@ func TestGetOpenShiftCluster(t *testing.T) {
 				ti.openShiftClustersClient.SetError(tt.dbError)
 			}
 
-			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, nil, nil, nil)
+			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, nil, nil)
 			if err != nil {
 				t.Fatal(err)
 			}
diff --git a/pkg/frontend/openshiftcluster_list_test.go b/pkg/frontend/openshiftcluster_list_test.go
index 21f55f582..5217e52ee 100644
--- a/pkg/frontend/openshiftcluster_list_test.go
+++ b/pkg/frontend/openshiftcluster_list_test.go
@@ -193,7 +193,7 @@ func TestListOpenShiftCluster(t *testing.T) {
 
 					aead := testdatabase.NewFakeAEAD()
 
-					f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, aead, nil, nil)
+					f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, aead, nil)
 					if err != nil {
 						t.Fatal(err)
 					}
diff --git a/pkg/frontend/openshiftcluster_putorpatch_test.go b/pkg/frontend/openshiftcluster_putorpatch_test.go
index b9e2baffb..3f8e80274 100644
--- a/pkg/frontend/openshiftcluster_putorpatch_test.go
+++ b/pkg/frontend/openshiftcluster_putorpatch_test.go
@@ -9,7 +9,6 @@ import (
 	"net/http"
 	"strings"
 	"testing"
-	"time"
 
 	"github.com/Azure/ARO-RP/pkg/api"
 	admin "github.com/Azure/ARO-RP/pkg/api/admin"
@@ -204,7 +203,7 @@ func TestPutOrPatchOpenShiftClusterAdminAPI(t *testing.T) {
 				t.Fatal(err)
 			}
 
-			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, apis, &noop.Noop{}, nil, nil, nil)
+			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, apis, &noop.Noop{}, nil, nil)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -282,7 +281,6 @@ func TestPutOrPatchOpenShiftCluster(t *testing.T) {
 	}
 
 	mockSubID := "00000000-0000-0000-0000-000000000000"
-	mockCurrentTime := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)
 
 	type test struct {
 		name           string
@@ -333,7 +331,6 @@ func TestPutOrPatchOpenShiftCluster(t *testing.T) {
 							ArchitectureVersion: version.InstallArchitectureVersion,
 							ProvisioningState:   api.ProvisioningStateCreating,
 							ProvisionedBy:       version.GitCommit,
-							CreatedAt:           mockCurrentTime,
 							CreatedBy:           version.GitCommit,
 							ClusterProfile: api.ClusterProfile{
 								Version: "4.3.0",
@@ -875,13 +872,12 @@ func TestPutOrPatchOpenShiftCluster(t *testing.T) {
 				t.Fatal(err)
 			}
 
-			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, apis, &noop.Noop{}, nil, nil, nil)
+			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, apis, &noop.Noop{}, nil, nil)
 			if err != nil {
 				t.Fatal(err)
 			}
 			f.(*frontend).bucketAllocator = bucket.Fixed(1)
 			f.(*frontend).ocEnricher = ti.enricher
-			f.(*frontend).now = func() time.Time { return mockCurrentTime }
 
 			go f.Run(ctx, nil, nil)
 
diff --git a/pkg/frontend/openshiftclustercredentials_post_test.go b/pkg/frontend/openshiftclustercredentials_post_test.go
index dcda300d8..8524e33ec 100644
--- a/pkg/frontend/openshiftclustercredentials_post_test.go
+++ b/pkg/frontend/openshiftclustercredentials_post_test.go
@@ -266,7 +266,7 @@ func TestPostOpenShiftClusterCredentials(t *testing.T) {
 				t.Fatal(err)
 			}
 
-			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, apis, &noop.Noop{}, nil, nil, nil)
+			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, apis, &noop.Noop{}, nil, nil)
 			if err != nil {
 				t.Fatal(err)
 			}
diff --git a/pkg/frontend/security_test.go b/pkg/frontend/security_test.go
index ada6d2de5..e42cd4451 100644
--- a/pkg/frontend/security_test.go
+++ b/pkg/frontend/security_test.go
@@ -68,7 +68,7 @@ func TestSecurity(t *testing.T) {
 	pool := x509.NewCertPool()
 	pool.AddCert(servercerts[0])
 
-	f, err := NewFrontend(ctx, logrus.NewEntry(logrus.StandardLogger()), _env, nil, nil, nil, api.APIs, &noop.Noop{}, nil, nil, nil)
+	f, err := NewFrontend(ctx, logrus.NewEntry(logrus.StandardLogger()), _env, nil, nil, nil, api.APIs, &noop.Noop{}, nil, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/pkg/frontend/subscriptions_put_test.go b/pkg/frontend/subscriptions_put_test.go
index d6d495db5..532a422cb 100644
--- a/pkg/frontend/subscriptions_put_test.go
+++ b/pkg/frontend/subscriptions_put_test.go
@@ -244,7 +244,7 @@ func TestPutSubscription(t *testing.T) {
 				t.Fatal(err)
 			}
 
-			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, nil, nil, nil)
+			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, nil, nil)
 			if err != nil {
 				t.Fatal(err)
 			}
diff --git a/pkg/monitor/cluster/summary_test.go b/pkg/monitor/cluster/summary_test.go
index d8509aef9..bb1a8463c 100644
--- a/pkg/monitor/cluster/summary_test.go
+++ b/pkg/monitor/cluster/summary_test.go
@@ -6,7 +6,6 @@ package cluster
 import (
 	"context"
 	"testing"
-	"time"
 
 	"github.com/golang/mock/gomock"
 	configv1 "github.com/openshift/api/config/v1"
@@ -69,8 +68,6 @@ func TestEmitSummary(t *testing.T) {
 
 	m := mock_metrics.NewMockInterface(controller)
 
-	mockCreatedAt := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)
-
 	mon := &Monitor{
 		configcli: configcli,
 		cli:       cli,
@@ -78,7 +75,6 @@ func TestEmitSummary(t *testing.T) {
 		oc: &api.OpenShiftCluster{
 			Properties: api.OpenShiftClusterProperties{
 				ProvisioningState: api.ProvisioningStateFailed,
-				CreatedAt:         mockCreatedAt,
 			},
 		},
 		hourlyRun: true,
@@ -90,7 +86,6 @@ func TestEmitSummary(t *testing.T) {
 		"masterCount":       "1",
 		"workerCount":       "2",
 		"provisioningState": api.ProvisioningStateFailed.String(),
-		"createdAt":         mockCreatedAt.String(),
 	})
 
 	err := mon.emitSummary(ctx)
diff --git a/pkg/util/dns/dns_test.go b/pkg/util/dns/dns_test.go
index 8e63fe3a8..69e24a86d 100644
--- a/pkg/util/dns/dns_test.go
+++ b/pkg/util/dns/dns_test.go
@@ -278,30 +278,24 @@ func TestCreateOrUpdateRouter(t *testing.T) {
 	}
 
 	type test struct {
-		name     string
-		routerIP string
-		oc       *api.OpenShiftCluster
-		mocks    func(*test, *mock_dns.MockRecordSetsClient)
-		wantErr  string
+		name    string
+		oc      *api.OpenShiftCluster
+		mocks   func(*test, *mock_dns.MockRecordSetsClient)
+		wantErr string
 	}
 
 	for _, tt := range []*test{
 		{
-			name:     "managed - create",
-			routerIP: "1.2.3.4",
-			oc:       managedOc,
+			name: "managed",
+			oc:   managedOc,
 			mocks: func(tt *test, recordsets *mock_dns.MockRecordSetsClient) {
-				recordsets.EXPECT().
-					Get(ctx, "rpResourcegroup", "domain", "*.apps.domain", mgmtdns.A).
-					Return(mgmtdns.RecordSet{}, fmt.Errorf("random error"))
-
 				recordsets.EXPECT().
 					CreateOrUpdate(ctx, "rpResourcegroup", "domain", "*.apps.domain", mgmtdns.A, mgmtdns.RecordSet{
 						RecordSetProperties: &mgmtdns.RecordSetProperties{
 							TTL: to.Int64Ptr(300),
 							ARecords: &[]mgmtdns.ARecord{
 								{
-									Ipv4Address: to.StringPtr(tt.routerIP),
+									Ipv4Address: to.StringPtr("1.2.3.4"),
 								},
 							},
 						},
@@ -310,21 +304,16 @@ func TestCreateOrUpdateRouter(t *testing.T) {
 			},
 		},
 		{
-			name:     "managed, error",
-			routerIP: "1.2.3.4",
-			oc:       managedOc,
+			name: "managed, error",
+			oc:   managedOc,
 			mocks: func(tt *test, recordsets *mock_dns.MockRecordSetsClient) {
-				recordsets.EXPECT().
-					Get(ctx, "rpResourcegroup", "domain", "*.apps.domain", mgmtdns.A).
-					Return(mgmtdns.RecordSet{}, fmt.Errorf("random error"))
-
 				recordsets.EXPECT().
 					CreateOrUpdate(ctx, "rpResourcegroup", "domain", "*.apps.domain", mgmtdns.A, mgmtdns.RecordSet{
 						RecordSetProperties: &mgmtdns.RecordSetProperties{
 							TTL: to.Int64Ptr(300),
 							ARecords: &[]mgmtdns.ARecord{
 								{
-									Ipv4Address: to.StringPtr(tt.routerIP),
+									Ipv4Address: to.StringPtr("1.2.3.4"),
 								},
 							},
 						},
@@ -333,57 +322,6 @@ func TestCreateOrUpdateRouter(t *testing.T) {
 			},
 			wantErr: "random error",
 		},
-		{
-			name:     "managed, update match",
-			routerIP: "1.2.3.4",
-			oc:       managedOc,
-			mocks: func(tt *test, recordsets *mock_dns.MockRecordSetsClient) {
-				recordsets.EXPECT().
-					Get(ctx, "rpResourcegroup", "domain", "*.apps.domain", mgmtdns.A).
-					Return(mgmtdns.RecordSet{
-						RecordSetProperties: &mgmtdns.RecordSetProperties{
-							TTL: to.Int64Ptr(300),
-							ARecords: &[]mgmtdns.ARecord{
-								{
-									Ipv4Address: to.StringPtr(tt.routerIP),
-								},
-							},
-						},
-					}, nil)
-			},
-		},
-		{
-			name:     "managed, update missmatch",
-			routerIP: "2.2.3.4",
-			oc:       managedOc,
-			mocks: func(tt *test, recordsets *mock_dns.MockRecordSetsClient) {
-				recordsets.EXPECT().
-					Get(ctx, "rpResourcegroup", "domain", "*.apps.domain", mgmtdns.A).
-					Return(mgmtdns.RecordSet{
-						RecordSetProperties: &mgmtdns.RecordSetProperties{
-							TTL: to.Int64Ptr(300),
-							ARecords: &[]mgmtdns.ARecord{
-								{
-									Ipv4Address: to.StringPtr("1.2.3.4"),
-								},
-							},
-						},
-					}, nil)
-
-				recordsets.EXPECT().
-					CreateOrUpdate(ctx, "rpResourcegroup", "domain", "*.apps.domain", mgmtdns.A, mgmtdns.RecordSet{
-						RecordSetProperties: &mgmtdns.RecordSetProperties{
-							TTL: to.Int64Ptr(300),
-							ARecords: &[]mgmtdns.ARecord{
-								{
-									Ipv4Address: to.StringPtr(tt.routerIP),
-								},
-							},
-						},
-					}, "", "").
-					Return(mgmtdns.RecordSet{}, nil)
-			},
-		},
 		{
 			name: "unmanaged",
 			oc:   unmanagedOc,
@@ -407,7 +345,7 @@ func TestCreateOrUpdateRouter(t *testing.T) {
 				recordsets: recordsets,
 			}
 
-			err := m.CreateOrUpdateRouter(ctx, tt.oc, tt.routerIP)
+			err := m.CreateOrUpdateRouter(ctx, tt.oc, "1.2.3.4")
 			if err != nil && err.Error() != tt.wantErr ||
 				err == nil && tt.wantErr != "" {
 				t.Error(err)