...

Source file src/github.com/chaos-mesh/chaos-mesh/controllers/action/multiplexer.go

Documentation: github.com/chaos-mesh/chaos-mesh/controllers/action

     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 action introduces a multiplexer for actions.
    17  package action
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"reflect"
    23  	"strings"
    24  
    25  	"k8s.io/apimachinery/pkg/runtime"
    26  
    27  	"github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
    28  	impltypes "github.com/chaos-mesh/chaos-mesh/controllers/chaosimpl/types"
    29  )
    30  
    31  // TODO: refactor this as a map[name]impltypes.ChaosImpl style, remove reflect usage.
    32  
    33  var _ impltypes.ChaosImpl = (*Multiplexer)(nil)
    34  
    35  // Multiplexer could combine ChaosImpl implementations into one, and route them by Action in the ChaosSpec.
    36  // Field impl should be a struct which contains several fields with struct tag "action", each field should be an implementation of ChaosImpl.
    37  // For example:
    38  //
    39  //	type tempStruct struct {
    40  //	  Impl1 impltypes.ChaosImpl `action:"action1"`
    41  //	  Impl2 impltypes.ChaosImpl `action:"action2"`
    42  //	}
    43  //
    44  // is valid to be the field in Multiplexer.
    45  //
    46  // Because we use reflect fo iterate fields in tempStruct, so fields in tempStruct should be public/exported.
    47  //
    48  // When some Chaos like:
    49  //
    50  //	type SomeChaos struct {
    51  //	  ***
    52  //	  Spec SomeChaosSpec `json:"spec"`
    53  //	  ***
    54  //	}
    55  //	type SomeChaosSpec struct {
    56  //	  ***
    57  //	  // available actions: action1, action2
    58  //	  Action string `json:"action"`
    59  //	  ***
    60  //	}
    61  //
    62  // is created, the corresponding ChaosImpl(s) for each action will be invoked by struct tag.
    63  type Multiplexer struct {
    64  	impl interface{}
    65  }
    66  
    67  func (i *Multiplexer) callAccordingToAction(action, methodName string, defaultPhase v1alpha1.Phase, args ...interface{}) (v1alpha1.Phase, error) {
    68  	implType := reflect.TypeOf(i.impl).Elem()
    69  	implVal := reflect.ValueOf(i.impl)
    70  
    71  	reflectArgs := []reflect.Value{}
    72  	for _, arg := range args {
    73  		reflectArgs = append(reflectArgs, reflect.ValueOf(arg))
    74  	}
    75  	for i := 0; i < implType.NumField(); i++ {
    76  		field := implType.Field(i)
    77  
    78  		actions := strings.Split(field.Tag.Get("action"), ",")
    79  		for i := range actions {
    80  			if actions[i] == action {
    81  				rets := implVal.Elem().FieldByIndex(field.Index).MethodByName(methodName).Call(reflectArgs)
    82  
    83  				// nil.(error) will panic :(
    84  				err := rets[1].Interface()
    85  				if err == nil {
    86  					return rets[0].Interface().(v1alpha1.Phase), nil
    87  				}
    88  
    89  				return rets[0].Interface().(v1alpha1.Phase), err.(error)
    90  			}
    91  		}
    92  	}
    93  	var gvk = ""
    94  	if obj, ok := args[len(args)-1].(runtime.Object); ok {
    95  		gvk = obj.GetObjectKind().GroupVersionKind().String()
    96  	}
    97  	return defaultPhase, NewErrorUnknownAction(gvk, action)
    98  }
    99  
   100  type ErrorUnknownAction struct {
   101  	GroupVersionKind string
   102  	Action           string
   103  }
   104  
   105  func NewErrorUnknownAction(GVK string, action string) ErrorUnknownAction {
   106  	return ErrorUnknownAction{GroupVersionKind: GVK, Action: action}
   107  }
   108  
   109  func (it ErrorUnknownAction) Error() string {
   110  	return fmt.Sprintf("unknown action: Action: %s, GroupVersionKind: %s", it.Action, it.GroupVersionKind)
   111  }
   112  
   113  // TODO: refactor this by introduce a new interface called ContainsAction
   114  func (i *Multiplexer) getAction(obj v1alpha1.InnerObject) string {
   115  	return reflect.ValueOf(obj).Elem().FieldByName("Spec").FieldByName("Action").String()
   116  }
   117  
   118  func (i *Multiplexer) Apply(ctx context.Context, index int, records []*v1alpha1.Record, obj v1alpha1.InnerObject) (v1alpha1.Phase, error) {
   119  	return i.callAccordingToAction(i.getAction(obj), "Apply", v1alpha1.NotInjected, ctx, index, records, obj)
   120  }
   121  
   122  func (i *Multiplexer) Recover(ctx context.Context, index int, records []*v1alpha1.Record, obj v1alpha1.InnerObject) (v1alpha1.Phase, error) {
   123  	return i.callAccordingToAction(i.getAction(obj), "Recover", v1alpha1.Injected, ctx, index, records, obj)
   124  }
   125  
   126  // NewMultiplexer is a constructor of Multiplexer.
   127  // For the detail of the parameter "impl", see the comment of type Multiplexer.
   128  func NewMultiplexer(impl interface{}) Multiplexer {
   129  	return Multiplexer{
   130  		impl,
   131  	}
   132  }
   133