Skip to content

Commit

Permalink
Verify providers readiness before admitting ManagedCluster creation
Browse files Browse the repository at this point in the history
Closes #236
  • Loading branch information
eromanova committed Sep 27, 2024
1 parent c81b512 commit 1a18768
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 62 deletions.
7 changes: 7 additions & 0 deletions api/v1alpha1/management_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ func (m *ManagementSpec) SetProvidersDefaults() error {
return nil
}

func (m *ManagementSpec) GetCoreTemplates() map[string]bool {
return map[string]bool{
m.Core.HMC.Template: true,
m.Core.CAPI.Template: true,
}
}

// ManagementStatus defines the observed state of Management
type ManagementStatus struct {
// ObservedGeneration is the last observed generation.
Expand Down
98 changes: 70 additions & 28 deletions internal/webhook/managedcluster_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ type ManagedClusterValidator struct {
client.Client
}

var errInvalidManagedCluster = errors.New("the ManagedCluster is invalid")
var (
errInvalidManagedCluster = errors.New("the ManagedCluster is invalid")
errNotReadyComponents = errors.New("one or more required components are not ready")
)

func (v *ManagedClusterValidator) SetupWebhookWithManager(mgr ctrl.Manager) error {
v.Client = mgr.GetClient()
Expand All @@ -63,10 +66,17 @@ func (v *ManagedClusterValidator) ValidateCreate(ctx context.Context, obj runtim
if err != nil {
return nil, fmt.Errorf("%s: %v", errInvalidManagedCluster, err)
}
err = v.isTemplateValid(ctx, template)
err = v.isTemplateValid(template)
if err != nil {
return nil, fmt.Errorf("%s: %v", errInvalidManagedCluster, err)
}
warnings, err := v.checkComponentsHealth(ctx, template)
if err != nil {
return nil, fmt.Errorf("failed to verify components health: %v", err)
}
if len(warnings) > 0 {
return warnings, errNotReadyComponents
}
return nil, nil
}

Expand All @@ -80,7 +90,7 @@ func (v *ManagedClusterValidator) ValidateUpdate(ctx context.Context, _ runtime.
if err != nil {
return nil, fmt.Errorf("%s: %v", errInvalidManagedCluster, err)
}
err = v.isTemplateValid(ctx, template)
err = v.isTemplateValid(template)
if err != nil {
return nil, fmt.Errorf("%s: %v", errInvalidManagedCluster, err)
}
Expand All @@ -107,7 +117,7 @@ func (v *ManagedClusterValidator) Default(ctx context.Context, obj runtime.Objec
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 @@ -128,47 +138,73 @@ func (v *ManagedClusterValidator) getManagedClusterTemplate(ctx context.Context,
return template, nil
}

func (v *ManagedClusterValidator) isTemplateValid(ctx context.Context, template *v1alpha1.ClusterTemplate) error {
func (v *ManagedClusterValidator) getProviderTemplate(ctx context.Context, templateName string) (*v1alpha1.ProviderTemplate, error) {
template := &v1alpha1.ProviderTemplate{}
templateRef := types.NamespacedName{Name: templateName}
if err := v.Get(ctx, templateRef, template); err != nil {
return nil, err
}
return template, nil
}

func (*ManagedClusterValidator) isTemplateValid(template *v1alpha1.ClusterTemplate) error {
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.ClusterTemplate) error {
requiredProviders := template.Status.Providers
func (v *ManagedClusterValidator) checkComponentsHealth(ctx context.Context, clusterTemplate *v1alpha1.ClusterTemplate) (admission.Warnings, error) {
requiredProviders := clusterTemplate.Status.Providers
management := &v1alpha1.Management{}
managementRef := types.NamespacedName{Name: v1alpha1.ManagementName}
if err := v.Get(ctx, managementRef, management); err != nil {
return err
return nil, 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.getProviderTemplate(ctx, component)
if err != nil {
return nil, err
}
if management.Spec.GetCoreTemplates()[component] {
missingComponents["core components"] = append(missingComponents["core components"], component)
failedComponents = append(failedComponents, component)
componentsErrors[component] = status.Error
}
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)

warnings := make([]string, 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)
warnings = append(warnings, fmt.Sprintf("not ready %s: %v", componentType, missing))
}
}
if len(errs) > 0 {
sort.Slice(errs, func(i, j int) bool {
return errs[i].Error() < errs[j].Error()
})
return errors.Join(errs...)
sort.Strings(warnings)

sort.Strings(failedComponents)
for _, failedComponent := range failedComponents {
warnings = append(warnings, fmt.Sprintf("%s installation failed: %s", failedComponent, componentsErrors[failedComponent]))
}
return nil
return warnings, nil
}

func getMissingProviders(exposedProviders []string, requiredProviders []string) []string {
Expand All @@ -179,3 +215,9 @@ func getMissingProviders(exposedProviders []string, requiredProviders []string)
}
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 1a18768

Please sign in to comment.