Skip to content

Commit

Permalink
Add static preflight validation (#2996)
Browse files Browse the repository at this point in the history
* Add static preflight validation

* ResourceId for path

---------

Co-authored-by: BCarvalheira <[email protected]>
  • Loading branch information
carvalhe and BCarvalheira authored Jul 17, 2023
1 parent a4403f5 commit a37c544
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 37 deletions.
1 change: 1 addition & 0 deletions pkg/api/preflight.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type ManagementErrorWithDetails struct {

// ResourceTypeMeta is the Typemeta inside request body of preflight
type ResourceTypeMeta struct {
Id string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Location string `json:"location"`
Expand Down
71 changes: 41 additions & 30 deletions pkg/frontend/openshiftcluster_preflightvalidation.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/sirupsen/logrus"

"github.com/Azure/ARO-RP/pkg/api"
"github.com/Azure/ARO-RP/pkg/env"
"github.com/Azure/ARO-RP/pkg/frontend/middleware"
)

Expand All @@ -32,29 +33,7 @@ func (f *frontend) preflightValidation(w http.ResponseWriter, r *http.Request) {
body := r.Context().Value(middleware.ContextKeyBody).([]byte)

resources, err := unmarshalRequest(body)
if err == nil {
for _, raw := range resources.Resources {
// get typeMeta from the raw data
typeMeta := api.ResourceTypeMeta{}
if err := json.Unmarshal(raw, &typeMeta); err != nil {
// failing to parse the preflight body is not considered a validation failure. continue
log.Warningf("bad request. Failed to unmarshal ResourceTypeMeta: %s", err)
continue
}
if strings.EqualFold(typeMeta.Type, "Microsoft.RedHatOpenShift/openShiftClusters") {
res := f._preflightValidation(ctx, log, raw, typeMeta.APIVersion)
if res.Status == api.ValidationStatusFailed {
log.Warningf("preflight validation failed")
b = marshalValidationResult(res)
reply(log, w, header, b, statusCodeError(http.StatusOK))
return
}
}
}
log.Info("preflight validation succeeded")
b = marshalValidationResult(validationSuccess)
reply(log, w, header, b, statusCodeError(http.StatusOK))
} else {
if err != nil {
b = marshalValidationResult(api.ValidationResult{
Status: api.ValidationStatusFailed,
Error: &api.ManagementErrorWithDetails{
Expand All @@ -63,19 +42,43 @@ func (f *frontend) preflightValidation(w http.ResponseWriter, r *http.Request) {
})
reply(log, w, header, b, statusCodeError(http.StatusOK))
}

for _, raw := range resources.Resources {
// get typeMeta from the raw data
typeMeta := api.ResourceTypeMeta{}
if err := json.Unmarshal(raw, &typeMeta); err != nil {
// failing to parse the preflight body is not considered a validation failure. continue
log.Warningf("bad request. Failed to unmarshal ResourceTypeMeta: %s", err)
continue
}
if strings.EqualFold(typeMeta.Type, "Microsoft.RedHatOpenShift/openShiftClusters") {
res := f._preflightValidation(ctx, log, raw, typeMeta.APIVersion, typeMeta.Id)
if res.Status == api.ValidationStatusFailed {
log.Warningf("preflight validation failed")
b = marshalValidationResult(res)
reply(log, w, header, b, statusCodeError(http.StatusOK))
return
}
}
}

log.Info("preflight validation succeeded")
b = marshalValidationResult(validationSuccess)
reply(log, w, header, b, statusCodeError(http.StatusOK))
}

func (f *frontend) _preflightValidation(ctx context.Context, log *logrus.Entry, raw json.RawMessage, apiVersion string) api.ValidationResult {
func (f *frontend) _preflightValidation(ctx context.Context, log *logrus.Entry, raw json.RawMessage, apiVersion string, resourceID string) api.ValidationResult {
// unmarshal raw to OpenShiftCluster type
doc := &api.OpenShiftCluster{}
doc.Properties.ProvisioningState = api.ProvisioningStateSucceeded
oc := &api.OpenShiftCluster{}
oc.Properties.ProvisioningState = api.ProvisioningStateSucceeded

if !f.env.IsLocalDevelopmentMode() /* not local dev or CI */ {
doc.Properties.FeatureProfile.GatewayEnabled = true
oc.Properties.FeatureProfile.GatewayEnabled = true
}

converter := f.apis[apiVersion].OpenShiftClusterConverter
ext := converter.ToExternal(doc)
staticValidator := f.apis[apiVersion].OpenShiftClusterStaticValidator
ext := converter.ToExternal(oc)
if err := json.Unmarshal(raw, &ext); err != nil {
return api.ValidationResult{
Status: api.ValidationStatusFailed,
Expand All @@ -85,9 +88,17 @@ func (f *frontend) _preflightValidation(ctx context.Context, log *logrus.Entry,
}
}

converter.ToInternal(ext, doc)
converter.ToInternal(ext, oc)
if err := staticValidator.Static(ext, nil, f.env.Location(), f.env.Domain(), f.env.FeatureIsSet(env.FeatureRequireD2sV3Workers), resourceID); err != nil {
return api.ValidationResult{
Status: api.ValidationStatusFailed,
Error: &api.ManagementErrorWithDetails{
Message: to.StringPtr(err.Error()),
},
}
}

if err := f.validateInstallVersion(ctx, doc); err != nil {
if err := f.validateInstallVersion(ctx, oc); err != nil {
return api.ValidationResult{
Status: api.ValidationStatusFailed,
Error: &api.ManagementErrorWithDetails{
Expand Down
48 changes: 47 additions & 1 deletion pkg/frontend/openshiftcluster_preflightvalidation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"net/http"
"testing"

"github.com/Azure/go-autorest/autorest/to"

"github.com/Azure/ARO-RP/pkg/api"
"github.com/Azure/ARO-RP/pkg/metrics/noop"
"github.com/Azure/ARO-RP/pkg/util/version"
Expand Down Expand Up @@ -47,13 +49,14 @@ func TestPreflightValidation(t *testing.T) {
[]byte(`
{
"apiVersion": "2022-04-01",
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourcename/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName",
"name": "resourceName",
"type": "microsoft.redhatopenshift/openshiftclusters",
"location": "eastus",
"properties": {
"clusterProfile": {
"domain": "example.aroapp.io",
"resourceGroupId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourcename",
"resourceGroupId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourcenameTest",
"fipsValidatedModules": "Enabled"
},
"consoleProfile": {},
Expand Down Expand Up @@ -102,6 +105,49 @@ func TestPreflightValidation(t *testing.T) {
Status: api.ValidationStatusSucceeded,
},
},
{
name: "Failed Preflight Static",
fixture: func(f *testdatabase.Fixture) {
f.AddSubscriptionDocuments(&api.SubscriptionDocument{
ID: mockSubID,
Subscription: &api.Subscription{
State: api.SubscriptionStateRegistered,
Properties: &api.SubscriptionProperties{
TenantID: "11111111-1111-1111-1111-111111111111",
},
},
})
},
preflightRequest: func() *api.PreflightRequest {
return &api.PreflightRequest{
Resources: []json.RawMessage{
[]byte(`
{
"apiVersion": "2022-04-01",
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourcename/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName",
"name": "resourceName",
"type": "microsoft.redhatopenshift/openshiftclusters",
"location": "eastus",
"properties": {
"clusterProfile": {
"domain": "example.aroapp.io",
"resourceGroupId": "/subscriptions/00000000-0000-0000-0000-000000000001/resourceGroups/resourcenameTest",
"fipsValidatedModules": "Enabled"
}
}
}
`),
},
}
},
wantStatusCode: http.StatusOK,
wantResponse: &api.ValidationResult{
Status: api.ValidationStatusFailed,
Error: &api.ManagementErrorWithDetails{
Message: to.StringPtr("400: InvalidParameter: properties.clusterProfile.resourceGroupId: The provided resource group '/subscriptions/00000000-0000-0000-0000-000000000001/resourceGroups/resourcenameTest' is invalid: must be in same subscription as cluster."),
},
},
},
} {
t.Run(tt.name, func(t *testing.T) {
ti := newTestInfra(t).
Expand Down
12 changes: 6 additions & 6 deletions pkg/frontend/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,20 +202,20 @@ func validateAdminMasterVMSize(vmSize string) error {

// validateInstallVersion validates the install version set in the clusterprofile.version
// TODO convert this into static validation instead of this receiver function in the validation for frontend.
func (f *frontend) validateInstallVersion(ctx context.Context, doc *api.OpenShiftCluster) error {
func (f *frontend) validateInstallVersion(ctx context.Context, oc *api.OpenShiftCluster) error {
// If this request is from an older API or the user never specified
// the version to install we default to the DefaultInstallStream.Version
// TODO: We should set default version in cosmosdb instead of hardcoding it in golang code
if doc.Properties.ClusterProfile.Version == "" {
doc.Properties.ClusterProfile.Version = version.DefaultInstallStream.Version.String()
if oc.Properties.ClusterProfile.Version == "" {
oc.Properties.ClusterProfile.Version = version.DefaultInstallStream.Version.String()
}

f.mu.RLock()
_, ok := f.enabledOcpVersions[doc.Properties.ClusterProfile.Version]
_, ok := f.enabledOcpVersions[oc.Properties.ClusterProfile.Version]
f.mu.RUnlock()

if !ok || !validate.RxInstallVersion.MatchString(doc.Properties.ClusterProfile.Version) {
return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "properties.clusterProfile.version", "The requested OpenShift version '%s' is invalid.", doc.Properties.ClusterProfile.Version)
if !ok || !validate.RxInstallVersion.MatchString(oc.Properties.ClusterProfile.Version) {
return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "properties.clusterProfile.version", "The requested OpenShift version '%s' is invalid.", oc.Properties.ClusterProfile.Version)
}

return nil
Expand Down

0 comments on commit a37c544

Please sign in to comment.