Skip to content

Commit

Permalink
Verify requested providers readiness before admitting ManagedCluster …
Browse files Browse the repository at this point in the history
…creation

Closes Mirantis#236
  • Loading branch information
eromanova committed Sep 10, 2024
1 parent 8c8076e commit 5a0cc4a
Show file tree
Hide file tree
Showing 2 changed files with 234 additions and 80 deletions.
92 changes: 63 additions & 29 deletions internal/webhook/managedcluster_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,18 @@ func (v *ManagedClusterValidator) ValidateCreate(ctx context.Context, obj runtim
if !ok {
return nil, apierrors.NewBadRequest(fmt.Sprintf("expected ManagedCluster but got a %T", obj))
}
template, err := v.getManagedClusterTemplate(ctx, managedCluster.Spec.Template)
template, err := v.getTemplate(ctx, managedCluster.Spec.Template)
if err != nil {
return nil, fmt.Errorf("%s: %v", InvalidManagedClusterErr, err)
}
err = v.isTemplateValid(ctx, template)
err = v.isTemplateValid(template)
if err != nil {
return nil, fmt.Errorf("%s: %v", InvalidManagedClusterErr, err)
}
err = v.checkComponentsHealth(ctx, template)
if err != nil {
return nil, fmt.Errorf("%s: components verification failed: %v", InvalidManagedClusterErr, err)
}
return nil, nil
}

Expand All @@ -78,11 +82,11 @@ func (v *ManagedClusterValidator) ValidateUpdate(ctx context.Context, _ runtime.
if !ok {
return nil, apierrors.NewBadRequest(fmt.Sprintf("expected ManagedCluster but got a %T", newObj))
}
template, err := v.getManagedClusterTemplate(ctx, newManagedCluster.Spec.Template)
template, err := v.getTemplate(ctx, newManagedCluster.Spec.Template)
if err != nil {
return nil, fmt.Errorf("%s: %v", InvalidManagedClusterErr, err)
}
err = v.isTemplateValid(ctx, template)
err = v.isTemplateValid(template)
if err != nil {
return nil, fmt.Errorf("%s: %v", InvalidManagedClusterErr, err)
}
Expand All @@ -105,11 +109,11 @@ func (v *ManagedClusterValidator) Default(ctx context.Context, obj runtime.Objec
if managedCluster.Spec.Config != nil {
return nil
}
template, err := v.getManagedClusterTemplate(ctx, managedCluster.Spec.Template)
template, err := v.getTemplate(ctx, managedCluster.Spec.Template)
if err != nil {
return fmt.Errorf("could not get template for the managedcluster: %s", err)
}
err = v.isTemplateValid(ctx, template)
err = v.isTemplateValid(template)
if err != nil {
return fmt.Errorf("template is invalid: %s", err)
}
Expand All @@ -121,7 +125,7 @@ func (v *ManagedClusterValidator) Default(ctx context.Context, obj runtime.Objec
return nil
}

func (v *ManagedClusterValidator) getManagedClusterTemplate(ctx context.Context, templateName string) (*v1alpha1.Template, error) {
func (v *ManagedClusterValidator) getTemplate(ctx context.Context, templateName string) (*v1alpha1.Template, error) {
template := &v1alpha1.Template{}
templateRef := types.NamespacedName{Name: templateName, Namespace: v.SystemNamespace}
if err := v.Get(ctx, templateRef, template); err != nil {
Expand All @@ -130,57 +134,87 @@ func (v *ManagedClusterValidator) getManagedClusterTemplate(ctx context.Context,
return template, nil
}

func (v *ManagedClusterValidator) isTemplateValid(ctx context.Context, template *v1alpha1.Template) error {
func (v *ManagedClusterValidator) isTemplateValid(template *v1alpha1.Template) error {
if template.Status.Type != v1alpha1.TemplateTypeDeployment {
return fmt.Errorf("the template should be of the deployment type. Current: %s", template.Status.Type)
}
if !template.Status.Valid {
return fmt.Errorf("the template is not valid: %s", template.Status.ValidationError)
}
err := v.verifyProviders(ctx, template)
if err != nil {
return fmt.Errorf("providers verification failed: %v", err)
}
return nil
}

func (v *ManagedClusterValidator) verifyProviders(ctx context.Context, template *v1alpha1.Template) error {
requiredProviders := template.Status.Providers
func (v *ManagedClusterValidator) checkComponentsHealth(ctx context.Context, managedClusterTemplate *v1alpha1.Template) error {
requiredProviders := managedClusterTemplate.Status.Providers
management := &v1alpha1.Management{}
managementRef := types.NamespacedName{Name: v1alpha1.ManagementName}
if err := v.Get(ctx, managementRef, management); err != nil {
return err
}

exposedProviders := management.Status.AvailableProviders
missingProviders := make(map[string][]string)
missingProviders["bootstrap"] = getMissingProviders(exposedProviders.BootstrapProviders, requiredProviders.BootstrapProviders)
missingProviders["control plane"] = getMissingProviders(exposedProviders.ControlPlaneProviders, requiredProviders.ControlPlaneProviders)
missingProviders["infrastructure"] = getMissingProviders(exposedProviders.InfrastructureProviders, requiredProviders.InfrastructureProviders)
missingComponents := make(map[string][]string)

var failedComponents []string
componentsErrors := make(map[string]string)
for component, status := range management.Status.Components {
if !status.Success {
template, err := v.getTemplate(ctx, component)
if err != nil {
return err
}
if template.Status.Type == v1alpha1.TemplateTypeCore {
missingComponents["core components"] = append(missingComponents["core components"], component)
failedComponents = append(failedComponents, component)
componentsErrors[component] = status.Error
}
if template.Status.Type == v1alpha1.TemplateTypeProvider {
if oneOrMoreProviderFailed(template.Status.Providers.BootstrapProviders, requiredProviders.BootstrapProviders) ||
oneOrMoreProviderFailed(template.Status.Providers.ControlPlaneProviders, requiredProviders.ControlPlaneProviders) ||
oneOrMoreProviderFailed(template.Status.Providers.InfrastructureProviders, requiredProviders.InfrastructureProviders) {
failedComponents = append(failedComponents, component)
componentsErrors[component] = status.Error
}
}
}
}

var errs []error
for providerType, missing := range missingProviders {
missingComponents["bootstrap providers"] = getMissingProviders(exposedProviders.BootstrapProviders, requiredProviders.BootstrapProviders)
missingComponents["control plane providers"] = getMissingProviders(exposedProviders.ControlPlaneProviders, requiredProviders.ControlPlaneProviders)
missingComponents["infrastructure providers"] = getMissingProviders(exposedProviders.InfrastructureProviders, requiredProviders.InfrastructureProviders)

errs := make([]error, 0, len(missingComponents)+len(failedComponents))
for componentType, missing := range missingComponents {
if len(missing) > 0 {
sort.Slice(missing, func(i, j int) bool {
return missing[i] < missing[j]
})
errs = append(errs, fmt.Errorf("one or more required %s providers are not deployed yet: %v", providerType, missing))
sort.Strings(missing)
errs = append(errs, fmt.Errorf("one or more required %s are not deployed yet: %v", componentType, missing))
}
}
sort.Slice(errs, func(i, j int) bool {
return errs[i].Error() < errs[j].Error()
})

sort.Strings(failedComponents)
for _, failedComponent := range failedComponents {
errs = append(errs, fmt.Errorf("%s installation failed: %s", failedComponent, componentsErrors[failedComponent]))
}
if len(errs) > 0 {
sort.Slice(errs, func(i, j int) bool {
return errs[i].Error() < errs[j].Error()
})
return errors.Join(errs...)
}
return nil
}

func getMissingProviders(exposedProviders []string, requiredProviders []string) []string {
exposedBootstrapProviders := utils.SliceToMapKeys[[]string, map[string]struct{}](exposedProviders)
diff, isSubset := utils.DiffSliceSubset[[]string, map[string]struct{}](requiredProviders, exposedBootstrapProviders)
exposedProvidersMap := utils.SliceToMapKeys[[]string, map[string]struct{}](exposedProviders)
diff, isSubset := utils.DiffSliceSubset[[]string, map[string]struct{}](requiredProviders, exposedProvidersMap)
if !isSubset {
return diff
}
return []string{}
}

func oneOrMoreProviderFailed(failedProviders []string, requiredProviders []string) bool {
failedProvidersMap := utils.SliceToMapKeys[[]string, map[string]struct{}](failedProviders)
_, isSubset := utils.DiffSliceSubset[[]string, map[string]struct{}](requiredProviders, failedProvidersMap)
return isSubset
}
Loading

0 comments on commit 5a0cc4a

Please sign in to comment.