Repository: Azure/ARO-RP
Pull request #1180 (2020-11-18 17:02:58)

retry on HashConflictOnDifferentRoleAssignmentIds error in hack/cluster
should fix an e2e flake


Patch:
diff --git a/deploy/cluster-predeploy.json b/deploy/cluster-predeploy.json
index f4e9422ff..4f37948e7 100644
--- a/deploy/cluster-predeploy.json
+++ b/deploy/cluster-predeploy.json
@@ -82,66 +82,6 @@
                 "[resourceid('Microsoft.Network/virtualNetworks/subnets', 'dev-vnet', concat(parameters('clusterName'), '-master'))]"
             ],
             "location": "[resourceGroup().location]"
-        },
-        {
-            "name": "[concat('dev-vnet', '/Microsoft.Authorization/', guid(resourceId('Microsoft.Network/virtualNetworks', 'dev-vnet'), parameters('clusterServicePrincipalId'), '4d97b98b-1d4f-4787-a291-c67834d212e7'))]",
-            "type": "Microsoft.Network/virtualNetworks/providers/roleAssignments",
-            "properties": {
-                "scope": "[resourceId('Microsoft.Network/virtualNetworks', 'dev-vnet')]",
-                "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
-                "principalId": "[parameters('clusterServicePrincipalId')]",
-                "principalType": "ServicePrincipal"
-            },
-            "condition": null,
-            "apiVersion": "2018-09-01-preview",
-            "dependsOn": [
-                "[resourceId('Microsoft.Network/virtualNetworks', 'dev-vnet')]"
-            ]
-        },
-        {
-            "name": "[concat('dev-vnet', '/Microsoft.Authorization/', guid(resourceId('Microsoft.Network/virtualNetworks', 'dev-vnet'), parameters('fpServicePrincipalId'), '4d97b98b-1d4f-4787-a291-c67834d212e7'))]",
-            "type": "Microsoft.Network/virtualNetworks/providers/roleAssignments",
-            "properties": {
-                "scope": "[resourceId('Microsoft.Network/virtualNetworks', 'dev-vnet')]",
-                "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
-                "principalId": "[parameters('fpServicePrincipalId')]",
-                "principalType": "ServicePrincipal"
-            },
-            "condition": null,
-            "apiVersion": "2018-09-01-preview",
-            "dependsOn": [
-                "[resourceId('Microsoft.Network/virtualNetworks', 'dev-vnet')]"
-            ]
-        },
-        {
-            "name": "[concat(concat(parameters('clusterName'), '-rt'), '/Microsoft.Authorization/', guid(resourceId('Microsoft.Network/routeTables', concat(parameters('clusterName'), '-rt')), parameters('clusterServicePrincipalId'), '4d97b98b-1d4f-4787-a291-c67834d212e7'))]",
-            "type": "Microsoft.Network/routeTables/providers/roleAssignments",
-            "properties": {
-                "scope": "[resourceId('Microsoft.Network/routeTables', concat(parameters('clusterName'), '-rt'))]",
-                "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
-                "principalId": "[parameters('clusterServicePrincipalId')]",
-                "principalType": "ServicePrincipal"
-            },
-            "condition": null,
-            "apiVersion": "2018-09-01-preview",
-            "dependsOn": [
-                "[resourceId('Microsoft.Network/routeTables', concat(parameters('clusterName'), '-rt'))]"
-            ]
-        },
-        {
-            "name": "[concat(concat(parameters('clusterName'), '-rt'), '/Microsoft.Authorization/', guid(resourceId('Microsoft.Network/routeTables', concat(parameters('clusterName'), '-rt')), parameters('fpServicePrincipalId'), '4d97b98b-1d4f-4787-a291-c67834d212e7'))]",
-            "type": "Microsoft.Network/routeTables/providers/roleAssignments",
-            "properties": {
-                "scope": "[resourceId('Microsoft.Network/routeTables', concat(parameters('clusterName'), '-rt'))]",
-                "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
-                "principalId": "[parameters('fpServicePrincipalId')]",
-                "principalType": "ServicePrincipal"
-            },
-            "condition": null,
-            "apiVersion": "2018-09-01-preview",
-            "dependsOn": [
-                "[resourceId('Microsoft.Network/routeTables', concat(parameters('clusterName'), '-rt'))]"
-            ]
         }
     ]
 }
diff --git a/pkg/api/validate/openshiftcluster_validatedynamic.go b/pkg/api/validate/openshiftcluster_validatedynamic.go
index ebd2d19c3..bbd26506f 100644
--- a/pkg/api/validate/openshiftcluster_validatedynamic.go
+++ b/pkg/api/validate/openshiftcluster_validatedynamic.go
@@ -322,10 +322,6 @@ func (dv *openShiftClusterDynamicValidator) validateSubnet(ctx context.Context,
 func (dv *openShiftClusterDynamicValidator) validateVnet(ctx context.Context, vnet *mgmtnetwork.VirtualNetwork) error {
 	dv.log.Print("validateVnet")
 
-	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)
-	}
-
 	master, err := dv.validateSubnet(ctx, vnet, "properties.masterProfile.subnetId", dv.oc.Properties.MasterProfile.SubnetID)
 	if err != nil {
 		return err
diff --git a/pkg/deploy/bindata.go b/pkg/deploy/bindata.go
index 0a4e29a0e..c84a018db 100644
--- a/pkg/deploy/bindata.go
+++ b/pkg/deploy/bindata.go
@@ -91,7 +91,7 @@ func (fi bindataFileInfo) Sys() interface{} {
 	return nil
 }
 
-var _clusterPredeployJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x57\x5b\x6f\xdb\x36\x14\x7e\xf7\xaf\x10\xb4\x01\x72\x00\xcb\x92\xbc\x6c\x8e\xf3\x16\x2c\xc3\x10\x60\xeb\x82\x36\xe8\x4b\x90\x07\x9a\x3c\x76\xb8\xd2\x24\x41\x52\xee\xd2\xc2\xff\x7d\xa0\x68\x39\xba\x50\x8e\xe3\x5c\xb6\xb4\xb6\x9f\xc4\xcb\xb9\x7c\xe7\xfb\x0e\xc9\xaf\xbd\x20\x08\x82\xf0\x47\x8d\x6f\x61\x81\xc2\xd3\x20\xbc\x35\x46\xea\xd3\x24\x71\x23\xc3\x05\xe2\x68\x0e\x0b\xe0\x66\x88\xbe\xe4\x0a\x86\x58\x2c\xd6\x73\x3a\x19\xa5\xd9\xcf\x71\x9a\xc5\x69\x96\x10\x90\x4c\xdc\xd9\x75\x57\xb0\x90\x0c\x19\x18\xfe\xad\x05\xff\x21\x1c\x38\x0f\x58\x70\x03\xdc\x7c\x04\xa5\xa9\xe0\xd6\x51\x36\x4c\xed\xbf\x5c\x20\x91\x42\x0b\x30\xa0\x74\x78\x1a\xb8\xb0\xdc\x46\x96\x6b\x03\xea\x1d\x5a\x40\x6d\xa2\x98\x34\x77\xd2\x8e\x86\xda\x28\xca\xe7\xe1\x66\x72\x35\x68\x19\xf8\x00\x6a\x49\x31\x5c\x2a\xca\x31\x95\x88\x5d\x90\xfd\xcc\xcd\xe4\xb3\x59\xca\x19\x3b\x2f\x60\xeb\xde\x3f\x15\x82\x85\x83\xfa\x1c\x81\x19\xca\x99\xf9\x88\x58\x6e\xd7\xcc\x10\xd3\xe0\x75\xb0\x40\x36\xf1\x33\x42\x14\x68\x7d\xa9\x60\x46\xff\xd9\x2f\xd2\xcf\x42\x7d\xda\xdf\x50\xaf\x62\x2e\x54\xa0\x45\xae\x30\xd8\x2a\x5f\x6f\xd6\x34\x4c\x49\x25\x24\x28\x43\x41\xb7\xdc\x14\xf3\xc8\x45\xf2\x41\x22\xdc\x26\x45\x73\x95\x8b\xb7\xe1\xb0\xb5\x38\x4b\x1d\x1d\x93\x49\xe8\x5d\x74\xd3\x1a\x5d\xd5\x46\x56\x8d\x22\x71\xc7\xd7\x90\xc0\x32\x5e\x72\x30\xcd\x22\x96\x68\xfd\x49\xb1\x12\x5a\xcc\xcc\xf0\x1d\x18\x8b\x73\xb2\xa4\xca\xe4\x88\xad\x3f\x75\x73\x23\x13\x18\x99\xb5\x84\xae\x4b\x34\x7f\x57\x22\x97\xfd\xa3\x61\x39\x79\xd3\xdc\x85\x05\x27\x74\xb3\xed\x5e\x6b\xfd\xe8\x9e\x85\xd1\x51\x6b\x1b\x92\xb4\xa2\xd8\x51\x9a\x4d\xe2\x74\x1c\xa7\x99\x97\x26\x5f\xfd\x08\x5c\x63\xc1\x31\x32\xfd\xaa\xd3\x8a\xa8\xa3\xa3\x41\x10\xc5\xca\x78\xbc\x77\x63\xa4\x44\x6e\xe0\x0a\x4d\x19\x3c\x13\x3e\x4f\x49\x74\x47\xb6\x6e\x74\x53\xc7\xdf\x23\xd2\x36\x14\x85\x9d\xfb\xa4\xbb\x39\x4f\x49\x2d\x6f\x4a\xfa\xd1\x56\xf4\xa2\x41\xb0\x6b\x79\xbc\x41\xb9\x3a\xa1\xb9\x4d\x9d\xe7\x8c\xb5\x55\xe2\x49\x44\xbb\x06\xfa\x1b\x27\x52\x50\x6e\xba\x95\xe9\x4f\xb2\x6a\xa3\x4e\x8f\x5f\x05\x37\x88\x72\x50\xef\x61\x4e\xb5\x51\x77\x7e\x2d\xaf\x5a\xa3\x37\x9e\x28\xa5\xa2\x4b\x64\xe0\x0f\xca\x3f\xad\x3b\xfe\x1a\xbf\x4b\xc1\x28\x76\xd5\x0e\xcf\xa9\xb6\x38\x92\x70\xa7\x66\x50\x4a\x21\x2a\xbb\x42\x12\x0d\x82\xad\xc0\x3b\x76\x3c\x4a\x1b\x8d\xfe\x91\xe8\x7c\xca\xc1\xb4\x74\xd2\xc9\xf8\xd6\x69\x23\x81\x13\xfd\x17\xf7\x56\xe9\x21\xaa\x35\x82\x89\x06\xc1\x26\xf7\x0e\x96\xbf\x14\x77\x7b\x5b\xea\xbd\x6b\xd3\x78\xf1\x66\xe0\x39\x68\x3b\x60\xfa\x1f\x6a\xe8\x85\x24\xe0\x30\xf9\x66\x24\x50\x06\x53\x93\xc2\x6e\x4c\x2e\x9b\xc1\xeb\xb1\xb9\xb3\x6a\x36\xfa\xe4\x3e\xd7\xb3\xdc\xdc\x0a\x45\xbf\x14\x96\x6d\x45\xe7\x39\x25\xfd\xd2\xf5\xc5\xe3\x5b\x83\x97\x13\xed\x8b\x77\x81\xcb\x31\x99\x8c\xa7\x93\x93\x69\x9c\x91\xe3\x59\x7c\x3c\x3e\x19\xc7\x68\x34\xc9\x62\xfc\xcb\xf8\xe4\xa7\x63\x32\xca\x46\x30\xf6\x1c\x5f\x8f\xe0\x8f\x54\x62\x49\x09\x28\x9d\x28\xc1\xe0\x4c\x6b\x3a\xe7\xf6\xa9\xd3\x62\xd4\x43\xea\xd7\x58\x38\x9f\xd7\x4f\xc0\xa6\xe3\x72\xc0\xe0\x1c\x66\x94\x17\xb7\xbc\x0b\x77\x0d\xd0\xf9\x54\x63\x45\xa5\x1d\x7a\xef\x75\x58\x2f\x5c\xdd\x48\xe1\x76\x37\x70\xbd\x21\xc9\xda\x03\xa9\xde\xe8\xb6\x54\x74\xbb\xad\xab\x75\xd1\x9a\x3b\xb7\x1f\xc0\xd5\xeb\xaf\xbd\xa6\x3c\xd4\x08\x4e\xe2\x74\x62\x1f\xb6\x52\xc1\x92\xc2\xe7\x7d\x1b\xc2\x5e\xc5\xad\x4b\xfb\xed\x88\xd4\xf7\x30\x3e\xe8\xb3\xb4\xfc\xc6\xf4\xe9\x2f\xe6\x41\x9a\x95\xdf\x3e\xd2\xdc\xf5\xbe\xfa\x74\xe1\xee\x79\x4f\xfe\x6f\xcf\xdd\x4a\xd0\xaf\xaf\xe9\x67\x7c\x15\xbf\x35\xb5\x7f\xcf\xa7\xf1\x33\x3d\x28\xbf\xfd\x66\xf0\x8a\xe7\xfb\xa1\x0f\x1c\x4e\xfd\xb7\xdc\x02\x7a\xee\x7b\xd5\xfb\x37\x00\x00\xff\xff\x53\x34\x37\xf2\x5f\x1b\x00\x00")
+var _clusterPredeployJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xdc\x55\x5d\x6b\xdb\x3c\x14\xbe\xcf\xaf\x30\x7a\x5f\x70\x0a\xfe\x2c\x8c\xd1\xde\x8d\x75\x8c\xc1\xd6\x95\xb5\xf4\xa6\xf4\x42\x95\x4e\x52\xad\xb2\x24\xa4\xe3\x6c\x5d\xc9\x7f\x1f\x8e\xe2\x34\xb6\xe5\x2e\xeb\x1a\x18\x23\x57\xd1\xf9\x7e\xce\xf3\x1c\x3f\x4c\xa2\x28\x8a\xc8\xff\x8e\xdd\x42\x45\xc9\x71\x44\x6e\x11\x8d\x3b\xce\x73\xff\x92\x55\x54\xd1\x39\x54\xa0\x30\xa3\x3f\x6a\x0b\x19\xd3\xd5\xda\xe6\xf2\xc3\xa2\x7c\x95\x16\x65\x5a\x94\x39\x07\x23\xf5\x7d\xe3\x77\x01\x95\x91\x14\x21\xfb\xea\xb4\xfa\x8f\x24\xbe\x02\xd3\x0a\x41\xe1\x25\x58\x27\xb4\x6a\x0a\x95\x59\xd1\xfc\x5a\x07\x43\x2d\xad\x00\xc1\x3a\x72\x1c\xf9\xb6\x7c\xa0\xac\x1d\x82\x3d\xa5\x15\x74\x0c\x2b\x23\xde\x9b\xe6\x95\x38\xb4\x42\xcd\xc9\xc6\xb8\x4c\x06\x09\xce\xc1\x2e\x04\x83\x33\x2b\x14\x13\x86\xca\x0f\xfc\x79\xe9\x66\xe6\xc5\x32\xd5\x52\x9e\xac\x60\x1b\x8f\xbf\xd1\x5a\x92\xa4\x6b\xe3\x30\xa3\xb5\xc4\x4b\x2a\xeb\xc6\x67\x46\xa5\x83\x60\x81\x8a\x36\x83\xbf\xe1\xdc\x82\x73\x67\x16\x66\xe2\xfb\xf3\x3a\xfd\xa6\xed\xdd\xf3\x13\x4d\xb6\xd2\x11\x0b\x4e\xd7\x96\x41\xb3\xe5\xab\x8d\x4f\x2f\x95\xb1\xda\x80\x45\x01\x6e\x50\x66\x65\xa7\xbe\x93\x73\x43\xd9\x90\x14\x7d\x2f\xdf\x6f\xaf\xe0\xc0\xb9\x2c\x3c\x1d\xf3\x23\x12\x74\xba\x1e\xbc\x2e\x3b\x2f\xcb\xde\x92\x94\xe7\x2b\xe1\xb0\x48\x17\x0a\xb0\xbf\xc4\x16\xad\x4f\x82\x59\xed\xf4\x0c\xb3\x53\xc0\x06\xe7\x7c\x21\x2c\xd6\x54\xae\xff\xba\x7e\xa0\xd4\x8c\xe2\x5a\x42\x57\x2d\x9a\xef\xad\xae\xcd\xf4\x20\x6b\x8d\xd7\xfd\x28\xa6\x15\x17\x9b\xb0\x47\xad\x4d\xe3\x47\x16\xc6\x07\x83\x30\x6a\xc4\x96\x62\x0f\x8b\xf2\x28\x2d\x5e\xa7\x45\x19\xa4\xc9\x43\x18\x81\x2b\xa6\x15\xa3\x38\xdd\x2e\xba\x25\xea\xf8\x20\x89\xe2\xd4\x62\xa0\xfa\x38\x46\x56\xd7\x08\x17\xf4\x46\xc2\x0b\xe1\xf3\x27\x83\xee\xc8\xd6\x8d\x6e\xba\xf8\x07\x44\x3a\x84\x62\x95\xe7\x71\xe8\x71\xce\x0b\xde\x99\x5b\xf0\x69\xfc\x24\x7a\x71\x12\xed\xba\x9e\x60\x53\x7e\x4f\x74\xde\x8c\xae\x6a\x29\x87\x2a\x09\x0c\xe2\xfc\x01\x7d\xa7\xb8\xd1\x42\xe1\xb8\x32\xc3\x43\x6e\xe7\xe8\xd2\xe3\xad\x56\x48\x85\x02\xfb\x05\xe6\xc2\xa1\xbd\x0f\x6b\x79\x39\x78\xbd\x0e\x74\x69\xac\x58\x50\x84\x8f\x42\xdd\xad\x2f\xfe\x1a\xbf\x33\x2d\x05\xf3\xdb\x26\x27\xc2\x35\x38\x72\xb2\xd3\x31\x68\xa5\x10\xb7\x57\x21\x8f\x93\xe8\x49\xe0\x3d\x3b\x7e\x4b\x1b\xbd\xfb\x91\xbb\xfa\x46\x01\x0e\x74\x32\xca\xf8\xc1\xd7\xc6\x80\xe2\xee\xb3\x0a\x6e\xe9\x57\x54\xeb\x35\x13\x27\xd1\x66\xf6\x11\x96\xef\x8b\xbb\x93\x27\xf6\xbd\xeb\xd1\xd8\xfb\x31\x08\x7c\x68\x47\x60\xfa\x0b\x35\xb4\x27\x09\x78\x4c\xfe\x19\x09\xb4\xcd\x74\xa4\xb0\x1b\x93\xdb\x63\xf0\xe2\x6c\x9e\xf8\xfd\x2d\x27\x3f\x03\x00\x00\xff\xff\x05\x63\xc4\x18\x0f\x0c\x00\x00")
 
 func clusterPredeployJsonBytes() ([]byte, error) {
 	return bindataRead(
diff --git a/pkg/deploy/generator/templates.go b/pkg/deploy/generator/templates.go
index b8aac8bb8..47cd5f73c 100644
--- a/pkg/deploy/generator/templates.go
+++ b/pkg/deploy/generator/templates.go
@@ -8,7 +8,6 @@ import (
 	"encoding/json"
 
 	"github.com/Azure/ARO-RP/pkg/util/arm"
-	"github.com/Azure/ARO-RP/pkg/util/rbac"
 )
 
 func (g *generator) clusterPredeploy() *arm.Template {
@@ -38,10 +37,6 @@ func (g *generator) clusterPredeploy() *arm.Template {
 		clusterRouteTable(),
 		clusterMasterSubnet(),
 		clusterWorkerSubnet(),
-		rbac.ResourceRoleAssignment(rbac.RoleNetworkContributor, "parameters('clusterServicePrincipalId')", "Microsoft.Network/virtualNetworks", "'dev-vnet'"),
-		rbac.ResourceRoleAssignment(rbac.RoleNetworkContributor, "parameters('fpServicePrincipalId')", "Microsoft.Network/virtualNetworks", "'dev-vnet'"),
-		rbac.ResourceRoleAssignment(rbac.RoleNetworkContributor, "parameters('clusterServicePrincipalId')", "Microsoft.Network/routeTables", "concat(parameters('clusterName'), '-rt')"),
-		rbac.ResourceRoleAssignment(rbac.RoleNetworkContributor, "parameters('fpServicePrincipalId')", "Microsoft.Network/routeTables", "concat(parameters('clusterName'), '-rt')"),
 	)
 
 	return t
diff --git a/pkg/env/dev.go b/pkg/env/dev.go
index 0c6749661..a89c57e44 100644
--- a/pkg/env/dev.go
+++ b/pkg/env/dev.go
@@ -22,6 +22,7 @@ import (
 	"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/clientauthorizer"
+	"github.com/Azure/ARO-RP/pkg/util/rbac"
 	"github.com/Azure/ARO-RP/pkg/util/refreshable"
 	"github.com/Azure/ARO-RP/pkg/util/version"
 )
@@ -128,7 +129,7 @@ func (d *dev) CreateARMResourceGroupRoleAssignment(ctx context.Context, fpAuthor
 
 	_, err = d.roleassignments.Create(ctx, "/subscriptions/"+d.SubscriptionID()+"/resourceGroups/"+resourceGroup, uuid.NewV4().String(), mgmtauthorization.RoleAssignmentCreateParameters{
 		RoleAssignmentProperties: &mgmtauthorization.RoleAssignmentProperties{
-			RoleDefinitionID: to.StringPtr("/subscriptions/" + d.SubscriptionID() + "/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635"),
+			RoleDefinitionID: to.StringPtr("/subscriptions/" + d.SubscriptionID() + "/providers/Microsoft.Authorization/roleDefinitions/" + rbac.RoleOwner),
 			PrincipalID:      res.Value,
 		},
 	})
diff --git a/pkg/util/cluster/cluster.go b/pkg/util/cluster/cluster.go
index 93ed894b5..f72060e49 100644
--- a/pkg/util/cluster/cluster.go
+++ b/pkg/util/cluster/cluster.go
@@ -13,10 +13,12 @@ import (
 	"time"
 
 	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/azure"
 	"github.com/Azure/go-autorest/autorest/azure/auth"
 	"github.com/Azure/go-autorest/autorest/to"
+	uuid "github.com/satori/go.uuid"
 	"github.com/sirupsen/logrus"
 
 	mgmtredhatopenshift "github.com/Azure/ARO-RP/pkg/client/services/redhatopenshift/mgmt/2020-04-30/redhatopenshift"
@@ -30,6 +32,7 @@ import (
 	"github.com/Azure/ARO-RP/pkg/util/azureclient/mgmt/redhatopenshift"
 	"github.com/Azure/ARO-RP/pkg/util/deployment"
 	"github.com/Azure/ARO-RP/pkg/util/instancemetadata"
+	"github.com/Azure/ARO-RP/pkg/util/rbac"
 	"github.com/Azure/ARO-RP/pkg/util/subnet"
 )
 
@@ -178,6 +181,41 @@ func (c *Cluster) Create(ctx context.Context, clusterName string) error {
 		return err
 	}
 
+	c.log.Info("creating role assignments")
+	for _, scope := range []string{
+		"/subscriptions/" + c.SubscriptionID() + "/resourceGroups/" + c.ResourceGroup() + "/providers/Microsoft.Network/virtualNetworks/dev-vnet",
+		"/subscriptions/" + c.SubscriptionID() + "/resourceGroups/" + c.ResourceGroup() + "/providers/Microsoft.Network/routeTables/" + clusterName + "-rt",
+	} {
+		for _, principalID := range []string{spID, fpSPID} {
+			for i := 0; i < 5; i++ {
+				_, err = c.roleassignments.Create(
+					ctx,
+					scope,
+					uuid.NewV4().String(),
+					mgmtauthorization.RoleAssignmentCreateParameters{
+						RoleAssignmentProperties: &mgmtauthorization.RoleAssignmentProperties{
+							RoleDefinitionID: to.StringPtr("/subscriptions/" + c.SubscriptionID() + "/providers/Microsoft.Authorization/roleDefinitions/" + rbac.RoleNetworkContributor),
+							PrincipalID:      &principalID,
+							PrincipalType:    mgmtauthorization.ServicePrincipal,
+						},
+					},
+				)
+				// TODO: tighten this error check
+				if err != nil && i < 4 {
+					// Sometimes we see HashConflictOnDifferentRoleAssignmentIds.
+					// Retry a few times.
+					c.log.Print(err)
+					continue
+				}
+				if err != nil {
+					return err
+				}
+
+				break
+			}
+		}
+	}
+
 	c.log.Info("creating cluster")
 	err = c.createCluster(ctx, clusterName, appID, appSecret)
 	if err != nil {
diff --git a/pkg/util/rbac/rbac.go b/pkg/util/rbac/rbac.go
index f5f31ecd2..25e433de8 100644
--- a/pkg/util/rbac/rbac.go
+++ b/pkg/util/rbac/rbac.go
@@ -17,6 +17,7 @@ const (
 	RoleDocumentDBAccountContributor = "5bd9cd88-fe45-4216-938b-f97437e15450"
 	RoleDNSZoneContributor           = "befefa01-2a29-4197-83a8-272ff33ce314"
 	RoleNetworkContributor           = "4d97b98b-1d4f-4787-a291-c67834d212e7"
+	RoleOwner                        = "8e3af657-a8ff-443c-a75c-2fe8c4bcb635"
 	RoleReader                       = "acdd72a7-3385-48ef-bd42-f606fba81ae7"
 )
 


Test patch:
diff --git a/pkg/api/validate/openshiftcluster_validatedynamic_test.go b/pkg/api/validate/openshiftcluster_validatedynamic_test.go
index ed68f952e..98d2449d3 100644
--- a/pkg/api/validate/openshiftcluster_validatedynamic_test.go
+++ b/pkg/api/validate/openshiftcluster_validatedynamic_test.go
@@ -191,13 +191,6 @@ func TestValidateVnet(t *testing.T) {
 				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) {
@@ -350,7 +343,6 @@ func TestValidateVnet(t *testing.T) {
 	} {
 		t.Run(tt.name, func(t *testing.T) {
 			oc := &api.OpenShiftCluster{
-				Location: "eastus",
 				Properties: api.OpenShiftClusterProperties{
 					ClusterProfile: api.ClusterProfile{
 						ResourceGroupID: resourceGroupID,
@@ -371,8 +363,7 @@ func TestValidateVnet(t *testing.T) {
 			}
 
 			vnet := &mgmtnetwork.VirtualNetwork{
-				Location: to.StringPtr("eastus"),
-				ID:       &vnetID,
+				ID: &vnetID,
 				VirtualNetworkPropertiesFormat: &mgmtnetwork.VirtualNetworkPropertiesFormat{
 					DhcpOptions: &mgmtnetwork.DhcpOptions{
 						DNSServers: &[]string{},