Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/monitor/armmonitor v0.11.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi v1.3.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6 v6.2.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armfeatures v1.2.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates v1.4.0
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi v1.3.0 h1:L7G3d
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi v1.3.0/go.mod h1:Ms6gYEy0+A2knfKrwdatsggTXYA2+ICKug8w7STorFw=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6 v6.2.0 h1:HYGD75g0bQ3VO/Omedm54v4LrD3B1cGImuRF3AJ5wLo=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6 v6.2.0/go.mod h1:ulHyBFJOI0ONiRL4vcJTmS7rx18jQQlEPmAgo80cRdM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armfeatures v1.2.0 h1:wIDqH4WA5uJ6irRqjzodeSw6Pmp0tu3oIbwzBZEdMfQ=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armfeatures v1.2.0/go.mod h1:g8mnARUMaYRsg80mxm3PxjF7+oUotB/lneDbwYbGNxg=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 h1:/Zt+cDPnpC3OVDm/JKLOs7M2DKmLRIIp3XIx9pHHiig=
Expand Down
77 changes: 77 additions & 0 deletions pkg/frontend/features_validation.go
Comment thread
sravyanimmala marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package frontend

// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.

import (
"context"
"fmt"
"net/http"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"

"github.com/Azure/ARO-RP/pkg/api"
"github.com/Azure/ARO-RP/pkg/env"
"github.com/Azure/ARO-RP/pkg/util/azureclient/azuresdk/armfeatures"
)

type FeaturesValidator interface {
ValidateSubscriptionFeatures(ctx context.Context, environment env.Interface, subscriptionID string, fpCred azcore.TokenCredential, oc *api.OpenShiftCluster) error
}

type featuresValidator struct{}

func (f featuresValidator) ValidateSubscriptionFeatures(ctx context.Context, environment env.Interface, subscriptionID string, fpCred azcore.TokenCredential, oc *api.OpenShiftCluster) error {
var fieldPath string
if oc.Properties.MasterProfile.EncryptionAtHost == api.EncryptionAtHostEnabled {
fieldPath = "properties.masterProfile.encryptionAtHost"
} else if oc.Properties.WorkerProfiles[0].EncryptionAtHost == api.EncryptionAtHostEnabled {
fieldPath = "properties.workerProfiles[0].encryptionAtHost"
}

if fieldPath != "" {
featuresClient, err := armfeatures.NewFeaturesClient(subscriptionID, fpCred, environment.Environment().ArmClientOptions())
if err != nil {
return err
}

return validateEncryptionAtHostFeature(ctx, featuresClient, subscriptionID, fieldPath)
}
return nil
}

func validateEncryptionAtHostFeature(
ctx context.Context,
featuresClient armfeatures.FeaturesClient,
subscriptionID, fieldPath string,
) error {
resp, err := featuresClient.Get(ctx, "Microsoft.Compute", "EncryptionAtHost", nil)
if err != nil {
return err
}

if resp.Properties == nil || resp.Properties.State == nil {
return api.NewCloudError(
http.StatusInternalServerError,
api.CloudErrorCodeInternalServerError,
"",
fmt.Sprintf(
"Microsoft.Compute/EncryptionAtHost"+
" feature has no state for"+
" subscription %s.",
subscriptionID))
}
Comment thread
kimorris27 marked this conversation as resolved.

if *resp.Properties.State != "Registered" {
return api.NewCloudError(
http.StatusBadRequest,
api.CloudErrorCodeInvalidParameter,
fieldPath,
fmt.Sprintf(
"Microsoft.Compute/EncryptionAtHost feature is not registered on subscription %s. "+
"Please register the feature on your subscription before creating the cluster.",
subscriptionID))
}

return nil
}
104 changes: 104 additions & 0 deletions pkg/frontend/features_validation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package frontend

// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.

import (
"context"
"errors"
"testing"

"go.uber.org/mock/gomock"

"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armfeatures"

mock_armfeatures "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/azuresdk/armfeatures"
"github.com/Azure/ARO-RP/pkg/util/pointerutils"
)

func TestValidateEncryptionAtHostFeature(t *testing.T) {
ctx := context.Background()

for _, tt := range []struct {
name string
fieldPath string
mockFeatureState *string
mockPropertiesNil bool
mockStateNil bool
mockGetErr error
wantErr string
}{
{
name: "feature is registered - should return nil",
fieldPath: "properties.masterProfile.encryptionAtHost",
mockFeatureState: pointerutils.ToPtr("Registered"),
wantErr: "",
},
{
name: "feature is not registered - should return 400 BadRequest",
fieldPath: "properties.masterProfile.encryptionAtHost",
mockFeatureState: pointerutils.ToPtr("NotRegistered"),
wantErr: "400: InvalidParameter: properties.masterProfile.encryptionAtHost: Microsoft.Compute/EncryptionAtHost feature is not registered on subscription test-subscription. Please register the feature on your subscription before creating the cluster.",
},
{
name: "resp.Properties is nil - should return internal server error",
fieldPath: "properties.workerProfiles[0].encryptionAtHost",
mockPropertiesNil: true,
wantErr: "500: InternalServerError: : Microsoft.Compute/EncryptionAtHost feature has no state for subscription test-subscription.",
},
{
name: "resp.Properties.State is nil - should return internal server error",
fieldPath: "properties.masterProfile.encryptionAtHost",
mockStateNil: true,
wantErr: "500: InternalServerError: : Microsoft.Compute/EncryptionAtHost feature has no state for subscription test-subscription.",
},
{
name: "Get returns error",
fieldPath: "properties.masterProfile.encryptionAtHost",
mockGetErr: errors.New("random error"),
wantErr: "random error",
},
} {
t.Run(tt.name, func(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()

featuresClient := mock_armfeatures.NewMockFeaturesClient(controller)

var mockResponse armfeatures.ClientGetResponse
if tt.mockPropertiesNil {
mockResponse = armfeatures.ClientGetResponse{
FeatureResult: armfeatures.FeatureResult{
Properties: nil,
},
}
} else if tt.mockStateNil {
mockResponse = armfeatures.ClientGetResponse{
FeatureResult: armfeatures.FeatureResult{
Properties: &armfeatures.FeatureProperties{
State: nil,
},
},
}
} else if tt.mockFeatureState != nil {
mockResponse = armfeatures.ClientGetResponse{
FeatureResult: armfeatures.FeatureResult{
Properties: &armfeatures.FeatureProperties{
State: tt.mockFeatureState,
},
},
}
}

featuresClient.EXPECT().
Get(ctx, "Microsoft.Compute", "EncryptionAtHost", nil).
Return(mockResponse, tt.mockGetErr)

err := validateEncryptionAtHostFeature(ctx, featuresClient, "test-subscription", tt.fieldPath)
if err != nil && err.Error() != tt.wantErr ||
err == nil && tt.wantErr != "" {
t.Errorf("expected error %q, got %q", tt.wantErr, err)
}
})
}
}
2 changes: 2 additions & 0 deletions pkg/frontend/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ type frontend struct {
skuValidator SkuValidator
quotaValidator QuotaValidator
providersValidator ProvidersValidator
featuresValidator FeaturesValidator

clusterEnricher clusterdata.BestEffortEnricher

Expand Down Expand Up @@ -188,6 +189,7 @@ func NewFrontend(ctx context.Context,
quotaValidator: quotaValidator{},
skuValidator: skuValidator{},
providersValidator: providersValidator{},
featuresValidator: featuresValidator{},

clusterEnricher: enricher,

Expand Down
1 change: 1 addition & 0 deletions pkg/frontend/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ package frontend
//go:generate mockgen -source quota_validation.go -destination=../util/mocks/$GOPACKAGE/quota_validation.go github.com/Azure/ARO-RP/pkg/frontend QuotaValidator
//go:generate mockgen -source providers_validation.go -destination=../util/mocks/$GOPACKAGE/providers_validation.go github.com/Azure/ARO-RP/pkg/frontend ProvidersValidator
//go:generate mockgen -source sku_validation.go -destination=../util/mocks/$GOPACKAGE/sku_validation.go github.com/Azure/ARO-RP/pkg/frontend SkuValidator
//go:generate mockgen -source features_validation.go -destination=../util/mocks/$GOPACKAGE/features_validation.go github.com/Azure/ARO-RP/pkg/frontend FeaturesValidator
//go:generate mockgen -source adminreplies.go -destination=../util/mocks/$GOPACKAGE/adminreplies.go github.com/Azure/ARO-RP/pkg/frontend StreamResponder
14 changes: 12 additions & 2 deletions pkg/frontend/openshiftcluster_putorpatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -450,12 +450,22 @@ func (f *frontend) ValidateNewCluster(ctx context.Context, subscription *api.Sub
return err
}

err = f.skuValidator.ValidateVMSku(ctx, f.env.Environment(), f.env, subscription.ID, subscription.Subscription.Properties.TenantID, cluster)
fpCred, err := f.env.FPNewClientCertificateCredential(subscription.Subscription.Properties.TenantID, nil)
if err != nil {
return err
}

err = f.quotaValidator.ValidateQuota(ctx, f.env.Environment(), f.env, subscription.ID, subscription.Subscription.Properties.TenantID, cluster)
err = f.skuValidator.ValidateVMSku(ctx, f.env, subscription.ID, fpCred, cluster)
if err != nil {
return err
}

err = f.featuresValidator.ValidateSubscriptionFeatures(ctx, f.env, subscription.ID, fpCred, cluster)
if err != nil {
return err
}

err = f.quotaValidator.ValidateQuota(ctx, f.env.Environment(), f.env, subscription.ID, fpCred, cluster)
if err != nil {
return err
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/frontend/openshiftcluster_putorpatch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,7 @@ func TestPutorPatchOpenShiftClusterCreate(t *testing.T) {
mockQuotaValidator.EXPECT().ValidateQuota(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(tt.quotaValidatorError).AnyTimes()

mockSkuValidator := mock_frontend.NewMockSkuValidator(controller)
mockSkuValidator.EXPECT().ValidateVMSku(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(tt.skuValidatorError).AnyTimes()
mockSkuValidator.EXPECT().ValidateVMSku(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(tt.skuValidatorError).AnyTimes()

mockProvidersValidator := mock_frontend.NewMockProvidersValidator(controller)
mockProvidersValidator.EXPECT().ValidateProviders(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(tt.providersValidatorError).AnyTimes()
Expand Down Expand Up @@ -1145,7 +1145,7 @@ func TestPutorPatchOpenShiftClusterUpdatePut(t *testing.T) {
mockQuotaValidator.EXPECT().ValidateQuota(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(tt.quotaValidatorError).AnyTimes()

mockSkuValidator := mock_frontend.NewMockSkuValidator(controller)
mockSkuValidator.EXPECT().ValidateVMSku(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(tt.skuValidatorError).AnyTimes()
mockSkuValidator.EXPECT().ValidateVMSku(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(tt.skuValidatorError).AnyTimes()

mockProvidersValidator := mock_frontend.NewMockProvidersValidator(controller)
mockProvidersValidator.EXPECT().ValidateProviders(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(tt.providersValidatorError).AnyTimes()
Expand Down Expand Up @@ -1644,7 +1644,7 @@ func TestPutorPatchOpenShiftClusterUpdatePatch(t *testing.T) {
mockQuotaValidator.EXPECT().ValidateQuota(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(tt.quotaValidatorError).AnyTimes()

mockSkuValidator := mock_frontend.NewMockSkuValidator(controller)
mockSkuValidator.EXPECT().ValidateVMSku(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(tt.skuValidatorError).AnyTimes()
mockSkuValidator.EXPECT().ValidateVMSku(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(tt.skuValidatorError).AnyTimes()

mockProvidersValidator := mock_frontend.NewMockProvidersValidator(controller)
mockProvidersValidator.EXPECT().ValidateProviders(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(tt.providersValidatorError).AnyTimes()
Expand Down
19 changes: 8 additions & 11 deletions pkg/frontend/quota_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import (
"fmt"
"net/http"

"github.com/jongio/azidext/go/azidext"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"

"github.com/Azure/ARO-RP/pkg/api"
"github.com/Azure/ARO-RP/pkg/api/validate"
"github.com/Azure/ARO-RP/pkg/env"
Expand All @@ -17,7 +21,7 @@ import (
)

type QuotaValidator interface {
ValidateQuota(ctx context.Context, azEnv *azureclient.AROEnvironment, environment env.Interface, subscriptionID, tenantID string, oc *api.OpenShiftCluster) error
ValidateQuota(ctx context.Context, azEnv *azureclient.AROEnvironment, environment env.Interface, subscriptionID string, fpCred azcore.TokenCredential, oc *api.OpenShiftCluster) error
}

type quotaValidator struct{}
Expand All @@ -39,20 +43,13 @@ func addRequiredResources(requiredResources map[string]int, vmSize api.VMSize, c
// ValidateQuota checks usage quotas vs. resources required by cluster before cluster
// creation
// It is a method on struct so we can make use of interfaces.
func (q quotaValidator) ValidateQuota(ctx context.Context, azEnv *azureclient.AROEnvironment, environment env.Interface, subscriptionID, tenantID string, oc *api.OpenShiftCluster) error {
fpAuthorizer, err := environment.FPAuthorizer(tenantID, nil, environment.Environment().ResourceManagerScope)
if err != nil {
return err
}
func (q quotaValidator) ValidateQuota(ctx context.Context, azEnv *azureclient.AROEnvironment, environment env.Interface, subscriptionID string, fpCred azcore.TokenCredential, oc *api.OpenShiftCluster) error {
fpAuthorizer := azidext.NewTokenCredentialAdapter(fpCred, []string{environment.Environment().ResourceManagerScope})

credential, err := environment.FPNewClientCertificateCredential(tenantID, []string{})
if err != nil {
return err
}
options := environment.Environment().ArmClientOptions()

spComputeUsage := compute.NewUsageClient(azEnv, subscriptionID, fpAuthorizer)
spNetworkUsage, err := armnetwork.NewUsagesClient(subscriptionID, credential, options)
spNetworkUsage, err := armnetwork.NewUsagesClient(subscriptionID, fpCred, options)
if err != nil {
return err
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/frontend/shared_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/sirupsen/logrus"
"go.uber.org/mock/gomock"

"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets"

"github.com/Azure/ARO-RP/pkg/api"
Expand Down Expand Up @@ -132,6 +133,7 @@ func newTestInfraWithFeatures(t *testing.T, features map[env.Feature]bool) *test
_env.EXPECT().Domain().AnyTimes().Return("aro.example")
_env.EXPECT().Listen().AnyTimes().Return(l, nil)
_env.EXPECT().NewMSITokenCredential().AnyTimes().Return(nil, fmt.Errorf("MSI not available in test"))
_env.EXPECT().FPNewClientCertificateCredential(gomock.Any(), gomock.Any()).AnyTimes().Return(&azidentity.ClientCertificateCredential{}, nil)
for f, val := range features {
_env.EXPECT().FeatureIsSet(f).AnyTimes().Return(val)
}
Expand Down
13 changes: 4 additions & 9 deletions pkg/frontend/sku_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,23 @@ import (
"fmt"
"net/http"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
sdkcompute "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v7"

"github.com/Azure/ARO-RP/pkg/api"
"github.com/Azure/ARO-RP/pkg/env"
"github.com/Azure/ARO-RP/pkg/util/azureclient"
"github.com/Azure/ARO-RP/pkg/util/azureclient/azuresdk/armcompute"
"github.com/Azure/ARO-RP/pkg/util/computeskus"
)

type SkuValidator interface {
ValidateVMSku(ctx context.Context, azEnv *azureclient.AROEnvironment, environment env.Interface, subscriptionID, tenantID string, oc *api.OpenShiftCluster) error
ValidateVMSku(ctx context.Context, environment env.Interface, subscriptionID string, fpCred azcore.TokenCredential, oc *api.OpenShiftCluster) error
}

type skuValidator struct{}

func (s skuValidator) ValidateVMSku(ctx context.Context, azEnv *azureclient.AROEnvironment, environment env.Interface, subscriptionID, tenantID string, oc *api.OpenShiftCluster) error {
fpCredClusterTenant, err := environment.FPNewClientCertificateCredential(tenantID, nil)
if err != nil {
return err
}

armResourceSKUsClient, err := armcompute.NewResourceSKUsClient(subscriptionID, fpCredClusterTenant, environment.Environment().ArmClientOptions())
func (s skuValidator) ValidateVMSku(ctx context.Context, environment env.Interface, subscriptionID string, fpCred azcore.TokenCredential, oc *api.OpenShiftCluster) error {
armResourceSKUsClient, err := armcompute.NewResourceSKUsClient(subscriptionID, fpCred, environment.Environment().ArmClientOptions())
if err != nil {
return err
}
Expand Down
Loading
Loading