Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add static preflight validation #2996

Merged
merged 3 commits into from
Jul 17, 2023
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 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{}
carvalhe marked this conversation as resolved.
Show resolved Hide resolved
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 {
carvalhe marked this conversation as resolved.
Show resolved Hide resolved
// 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
Loading