...

Source file src/github.com/chaos-mesh/chaos-mesh/api/v1alpha1/workflow_webhook.go

Documentation: github.com/chaos-mesh/chaos-mesh/api/v1alpha1

     1  // Copyright 2021 Chaos Mesh Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  // http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  
    16  package v1alpha1
    17  
    18  import (
    19  	"fmt"
    20  	"reflect"
    21  	"sort"
    22  
    23  	"github.com/pkg/errors"
    24  	"k8s.io/apimachinery/pkg/runtime"
    25  	"k8s.io/apimachinery/pkg/util/validation"
    26  	"k8s.io/apimachinery/pkg/util/validation/field"
    27  	logf "sigs.k8s.io/controller-runtime/pkg/log"
    28  	"sigs.k8s.io/controller-runtime/pkg/webhook"
    29  	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
    30  
    31  	gw "github.com/chaos-mesh/chaos-mesh/api/genericwebhook"
    32  )
    33  
    34  // log is for logging in this package.
    35  var workflowlog = logf.Log.WithName("workflow-resource")
    36  
    37  var _ webhook.Validator = &Workflow{}
    38  
    39  func (in *Workflow) ValidateCreate() (admission.Warnings, error) {
    40  	var allErrs field.ErrorList
    41  	specPath := field.NewPath("spec")
    42  	allErrs = append(allErrs, entryMustExists(specPath.Child("entry"), in.Spec.Entry, in.Spec.Templates)...)
    43  	allErrs = append(allErrs, validateTemplates(specPath.Child("templates"), in.Spec.Templates)...)
    44  	if len(allErrs) > 0 {
    45  		return nil, errors.New(allErrs.ToAggregate().Error())
    46  	}
    47  	return nil, nil
    48  }
    49  
    50  func (in *Workflow) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
    51  	return in.ValidateCreate()
    52  }
    53  
    54  func (in *Workflow) ValidateDelete() (admission.Warnings, error) {
    55  	return nil, nil
    56  }
    57  
    58  func entryMustExists(path *field.Path, entry string, templates []Template) field.ErrorList {
    59  	var result field.ErrorList
    60  	// name is required
    61  	if len(entry) == 0 {
    62  		result = append(result, field.Required(path, "the entry of workflow is required"))
    63  	}
    64  	founded := false
    65  	for _, item := range templates {
    66  		if item.Name == entry {
    67  			founded = true
    68  			break
    69  		}
    70  	}
    71  	if !founded {
    72  		result = append(result, field.Invalid(path, entry, fmt.Sprintf("can not find a template with name %s", entry)))
    73  	}
    74  	return result
    75  }
    76  
    77  func validateTemplates(path *field.Path, templates []Template) field.ErrorList {
    78  	var result field.ErrorList
    79  	if len(templates) == 0 {
    80  		result = append(result, field.Invalid(path, templates, "templates in workflow could not be empty"))
    81  		return result
    82  	}
    83  	var allNames []string
    84  	for _, template := range templates {
    85  		allNames = append(allNames, template.Name)
    86  	}
    87  	result = append(result, namesCouldNotBeDuplicated(path, allNames)...)
    88  
    89  	for i, item := range templates {
    90  		itemPath := path.Index(i)
    91  		result = append(result, validateTemplate(itemPath, item, templates)...)
    92  	}
    93  	return result
    94  }
    95  
    96  func validateTemplate(path *field.Path, template Template, allTemplates []Template) field.ErrorList {
    97  	var result field.ErrorList
    98  	// name is required
    99  	if len(template.Name) == 0 {
   100  		result = append(result, field.Required(path.Child("name"), "name of template is required"))
   101  	}
   102  
   103  	// name must be restricted with DNS-1123
   104  	errs := validation.IsDNS1123Subdomain(template.Name)
   105  	if len(errs) > 0 {
   106  		result = append(result, field.Invalid(path.Child("name"), template.Name, fmt.Sprintf("field name must be DNS-1123 subdomain, %s", errs)))
   107  	}
   108  
   109  	// template name could not be duplicated
   110  
   111  	switch templateType := template.Type; {
   112  	case templateType == TypeSuspend:
   113  		if template.Deadline == nil || len(*template.Deadline) == 0 {
   114  			result = append(result, field.Invalid(path.Child("deadline"), template.Deadline, "deadline in template with type Suspend could not be empty"))
   115  		}
   116  		result = append(result, shouldBeNoTask(path, template)...)
   117  		result = append(result, shouldBeNoChildren(path, template)...)
   118  		result = append(result, shouldBeNoConditionalBranches(path, template)...)
   119  		result = append(result, shouldBeNoEmbedChaos(path, template)...)
   120  		result = append(result, shouldBeNoSchedule(path, template)...)
   121  	case templateType == TypeSerial, templateType == TypeParallel:
   122  		for i, item := range template.Children {
   123  			result = append(result, templateMustExists(item, path.Child("children").Index(i), allTemplates)...)
   124  		}
   125  		result = append(result, shouldBeNoTask(path, template)...)
   126  		result = append(result, shouldBeNoConditionalBranches(path, template)...)
   127  		result = append(result, shouldBeNoEmbedChaos(path, template)...)
   128  		result = append(result, shouldBeNoSchedule(path, template)...)
   129  	case templateType == TypeSchedule:
   130  		result = append(result, shouldBeNoTask(path, template)...)
   131  		result = append(result, shouldBeNoChildren(path, template)...)
   132  		result = append(result, shouldBeNoConditionalBranches(path, template)...)
   133  		result = append(result, shouldBeNoEmbedChaos(path, template)...)
   134  	case templateType == TypeTask:
   135  		result = append(result, shouldBeNoChildren(path, template)...)
   136  		result = append(result, shouldBeNoEmbedChaos(path, template)...)
   137  		result = append(result, shouldBeNoSchedule(path, template)...)
   138  	case IsChaosTemplateType(templateType):
   139  		result = append(result, shouldNotSetupDurationInTheChaos(path, template)...)
   140  
   141  		result = append(result, shouldBeNoTask(path, template)...)
   142  		result = append(result, shouldBeNoChildren(path, template)...)
   143  		result = append(result, shouldBeNoConditionalBranches(path, template)...)
   144  		result = append(result, shouldBeNoSchedule(path, template)...)
   145  
   146  		result = append(result, template.EmbedChaos.Validate(path, string(templateType))...)
   147  	case templateType == TypeStatusCheck:
   148  		result = append(result, shouldBeNoTask(path, template)...)
   149  		result = append(result, shouldBeNoChildren(path, template)...)
   150  		result = append(result, shouldBeNoConditionalBranches(path, template)...)
   151  		result = append(result, shouldBeNoEmbedChaos(path, template)...)
   152  		result = append(result, shouldBeNoSchedule(path, template)...)
   153  	default:
   154  		result = append(result, field.Invalid(path.Child("templateType"), template.Type, fmt.Sprintf("unrecognized template type: %s", template.Type)))
   155  	}
   156  
   157  	return result
   158  }
   159  
   160  func namesCouldNotBeDuplicated(templatesPath *field.Path, names []string) field.ErrorList {
   161  	nameCounter := make(map[string]int)
   162  	for _, name := range names {
   163  		if count, ok := nameCounter[name]; ok {
   164  			nameCounter[name] = count + 1
   165  		} else {
   166  			nameCounter[name] = 1
   167  		}
   168  	}
   169  	var duplicatedNames []string
   170  	for name, count := range nameCounter {
   171  		if count > 1 {
   172  			duplicatedNames = append(duplicatedNames, name)
   173  		}
   174  	}
   175  	sort.Strings(duplicatedNames)
   176  	if len(duplicatedNames) > 0 {
   177  		return field.ErrorList{
   178  			field.Invalid(templatesPath, "", fmt.Sprintf("template name must be unique, duplicated names: %s", duplicatedNames)),
   179  		}
   180  	}
   181  	return nil
   182  }
   183  
   184  func templateMustExists(templateName string, path *field.Path, template []Template) field.ErrorList {
   185  	var result field.ErrorList
   186  
   187  	founded := false
   188  	for _, item := range template {
   189  		if item.Name == templateName {
   190  			founded = true
   191  			break
   192  		}
   193  	}
   194  
   195  	if !founded {
   196  		err := field.Invalid(path, templateName, fmt.Sprintf("can not find a template with name %s", templateName))
   197  		result = append(result, err)
   198  	}
   199  	return result
   200  }
   201  
   202  func shouldNotSetupDurationInTheChaos(path *field.Path, template Template) field.ErrorList {
   203  	var result field.ErrorList
   204  
   205  	if template.EmbedChaos == nil {
   206  		result = append(result, field.Invalid(path.Child(string(template.Type)), nil, fmt.Sprintf("the value of chaos %s is required", template.Type)))
   207  		return result
   208  	}
   209  
   210  	spec := reflect.ValueOf(template.EmbedChaos).Elem().FieldByName(string(template.Type))
   211  	if !spec.IsValid() || spec.IsNil() {
   212  		result = append(result, field.Invalid(path.Child(string(template.Type)),
   213  			nil,
   214  			fmt.Sprintf("parse workflow field error: missing chaos spec %s", template.Type)))
   215  		return result
   216  	}
   217  	if commonSpec, ok := spec.Interface().(ContainsDuration); !ok {
   218  		result = append(result, field.Invalid(path, "", fmt.Sprintf("Chaos: %s does not implement CommonSpec", template.Type)))
   219  	} else {
   220  		duration, err := commonSpec.GetDuration()
   221  		if err != nil {
   222  			result = append(result, field.Invalid(path, "", err.Error()))
   223  			return result
   224  		}
   225  		if duration != nil {
   226  			result = append(result, field.Invalid(path, duration, "should not define duration in chaos when using Workflow, use Template#Deadline instead."))
   227  		}
   228  	}
   229  	return result
   230  }
   231  
   232  func shouldBeNoTask(path *field.Path, template Template) field.ErrorList {
   233  	if template.Task != nil {
   234  		return field.ErrorList{
   235  			field.Invalid(path, template.Task, "this template should not contain Task"),
   236  		}
   237  	}
   238  	return nil
   239  }
   240  
   241  func shouldBeNoChildren(path *field.Path, template Template) field.ErrorList {
   242  	if len(template.Children) > 0 {
   243  		return field.ErrorList{
   244  			field.Invalid(path, template.Children, "this template should not contain Children"),
   245  		}
   246  	}
   247  	return nil
   248  }
   249  
   250  func shouldBeNoConditionalBranches(path *field.Path, template Template) field.ErrorList {
   251  	if len(template.ConditionalBranches) > 0 {
   252  		return field.ErrorList{
   253  			field.Invalid(path, template.ConditionalBranches, "this template should not contain ConditionalBranches"),
   254  		}
   255  	}
   256  	return nil
   257  }
   258  
   259  func shouldBeNoEmbedChaos(path *field.Path, template Template) field.ErrorList {
   260  	// TODO: we could improve that with code generation in the future
   261  	if template.EmbedChaos != nil {
   262  		return field.ErrorList{
   263  			field.Invalid(path, template.EmbedChaos, "this template should not contain any Chaos"),
   264  		}
   265  	}
   266  	return nil
   267  }
   268  
   269  func shouldBeNoSchedule(path *field.Path, template Template) field.ErrorList {
   270  	if template.Schedule != nil {
   271  		return field.ErrorList{
   272  			field.Invalid(path, template.Schedule, "this template should not contain Schedule"),
   273  		}
   274  	}
   275  	return nil
   276  }
   277  
   278  func (in *Workflow) Default() {
   279  	gw.Default(in)
   280  }
   281