...

Source file src/github.com/chaos-mesh/chaos-mesh/controllers/twophase/state_machine_test.go

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

     1  // Copyright 2020 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  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package twophase
    15  
    16  import (
    17  	"context"
    18  	"errors"
    19  	"testing"
    20  	"time"
    21  
    22  	. "github.com/onsi/ginkgo"
    23  	. "github.com/onsi/gomega"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/types"
    26  	"k8s.io/client-go/kubernetes/scheme"
    27  	ctrl "sigs.k8s.io/controller-runtime"
    28  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    29  	"sigs.k8s.io/controller-runtime/pkg/envtest"
    30  
    31  	"github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
    32  	"github.com/chaos-mesh/chaos-mesh/pkg/mock"
    33  	ctx "github.com/chaos-mesh/chaos-mesh/pkg/router/context"
    34  )
    35  
    36  func TestStateMachine(t *testing.T) {
    37  	RegisterFailHandler(Fail)
    38  
    39  	RunSpecsWithDefaultAndCustomReporters(t,
    40  		"Twophase StateMachine Suite",
    41  		[]Reporter{envtest.NewlineReporter{}})
    42  }
    43  
    44  var _ = Describe("TwoPhase StateMachine", func() {
    45  	Context("TwoPhase", func() {
    46  
    47  		statuses := []v1alpha1.ExperimentPhase{
    48  			v1alpha1.ExperimentPhaseFailed,
    49  			v1alpha1.ExperimentPhaseFinished,
    50  			v1alpha1.ExperimentPhasePaused,
    51  			v1alpha1.ExperimentPhaseRunning,
    52  			v1alpha1.ExperimentPhaseWaiting,
    53  		}
    54  
    55  		It("StateMachine Target Finish", func() {
    56  			defer mock.With("MockApplyError", errors.New("ApplyError"))()
    57  			defer mock.With("MockRecoverError", errors.New("RecoverError"))()
    58  
    59  			for _, status := range statuses {
    60  				now := time.Now()
    61  				sm := setupStateMachineWithStatus(status)
    62  
    63  				updated, err := sm.run(context.TODO(), v1alpha1.ExperimentPhaseFinished, now)
    64  
    65  				if status == v1alpha1.ExperimentPhaseRunning {
    66  					Expect(updated).To(Equal(true))
    67  					Expect(err).To(HaveOccurred())
    68  					Expect(err.Error()).To(ContainSubstring("RecoverError"))
    69  					Expect(sm.Chaos.GetStatus().Experiment.Phase).To(Equal(v1alpha1.ExperimentPhaseRunning))
    70  					Expect(sm.Chaos.GetStatus().FailedMessage).To(ContainSubstring("RecoverError"))
    71  
    72  					return
    73  				} else if status == v1alpha1.ExperimentPhaseFinished {
    74  					Expect(updated).To(Equal(false))
    75  				} else if status == v1alpha1.ExperimentPhaseFailed {
    76  					Expect(updated).To(Equal(true))
    77  					Expect(err).To(HaveOccurred())
    78  					Expect(err.Error()).To(ContainSubstring("RecoverError"))
    79  					Expect(sm.Chaos.GetStatus().FailedMessage).To(ContainSubstring("RecoverError"))
    80  
    81  					return
    82  				} else {
    83  					Expect(updated).To(Equal(true))
    84  				}
    85  
    86  				Expect(err).ToNot(HaveOccurred())
    87  			}
    88  		})
    89  
    90  		It("StateMachine Target Paused", func() {
    91  			defer mock.With("MockApplyError", errors.New("ApplyError"))()
    92  			defer mock.With("MockRecoverError", errors.New("RecoverError"))()
    93  
    94  			for _, status := range statuses {
    95  				now := time.Now()
    96  				sm := setupStateMachineWithStatus(status)
    97  
    98  				updated, err := sm.run(context.TODO(), v1alpha1.ExperimentPhasePaused, now)
    99  
   100  				if status == v1alpha1.ExperimentPhaseRunning {
   101  					Expect(updated).To(Equal(true))
   102  					Expect(err).To(HaveOccurred())
   103  					Expect(err.Error()).To(ContainSubstring("RecoverError"))
   104  					Expect(sm.Chaos.GetStatus().Experiment.Phase).To(Equal(v1alpha1.ExperimentPhaseRunning))
   105  					Expect(sm.Chaos.GetStatus().FailedMessage).To(ContainSubstring("RecoverError"))
   106  
   107  					return
   108  				} else if status == v1alpha1.ExperimentPhaseFinished {
   109  					Expect(err).To(HaveOccurred())
   110  					Expect(err.Error()).To(ContainSubstring("turn from"))
   111  
   112  					return
   113  				} else if status != v1alpha1.ExperimentPhasePaused {
   114  					Expect(updated).To(Equal(true))
   115  				} else {
   116  					Expect(updated).To(Equal(false))
   117  				}
   118  
   119  				Expect(err).ToNot(HaveOccurred())
   120  			}
   121  		})
   122  
   123  		It("StateMachine Target Running", func() {
   124  			defer mock.With("MockApplyError", errors.New("ApplyError"))()
   125  			defer mock.With("MockRecoverError", errors.New("RecoverError"))()
   126  
   127  			for _, status := range statuses {
   128  				now := time.Now()
   129  				sm := setupStateMachineWithStatus(status)
   130  
   131  				updated, err := sm.run(context.TODO(), v1alpha1.ExperimentPhaseRunning, now)
   132  
   133  				if status == v1alpha1.ExperimentPhaseFinished {
   134  					Expect(err).To(HaveOccurred())
   135  					Expect(err.Error()).To(ContainSubstring("turn from"))
   136  				} else if status == v1alpha1.ExperimentPhaseRunning {
   137  					Expect(updated).To(Equal(false))
   138  					Expect(err).ToNot(HaveOccurred())
   139  				} else if status != v1alpha1.ExperimentPhasePaused {
   140  					Expect(err).To(HaveOccurred())
   141  					Expect(err.Error()).To(ContainSubstring("ApplyError"))
   142  				}
   143  			}
   144  		})
   145  
   146  		It("Pause", func() {
   147  			// duration 15min, scheduler @every 20m
   148  			// Then it should be running in 13:10-13:25, 13:30-13:45, 13:50-14:05, 14:10-14:25
   149  			// Pause will only erase part of it
   150  			now, err := time.Parse(time.RFC3339, "2020-12-07T13:10:00+00:00")
   151  			Expect(err).ToNot(HaveOccurred())
   152  			sm := setupStateMachineWithStatus(v1alpha1.ExperimentPhaseUninitialized)
   153  
   154  			updated, err := sm.run(context.TODO(), v1alpha1.ExperimentPhaseRunning, now)
   155  			Expect(err).ToNot(HaveOccurred())
   156  			Expect(updated).To(Equal(true))
   157  			Expect(sm.Chaos.GetStatus().Experiment.Phase).To(Equal(v1alpha1.ExperimentPhaseRunning))
   158  
   159  			now = now.Add(time.Minute) // 13:11
   160  			updated, err = sm.run(context.TODO(), v1alpha1.ExperimentPhasePaused, now)
   161  			Expect(err).ToNot(HaveOccurred())
   162  			Expect(updated).To(Equal(true))
   163  			Expect(sm.Chaos.GetStatus().Experiment.Phase).To(Equal(v1alpha1.ExperimentPhasePaused))
   164  
   165  			// should apply
   166  			now, err = time.Parse(time.RFC3339, "2020-12-07T13:55:00+00:00")
   167  			Expect(err).ToNot(HaveOccurred())
   168  			updated, err = sm.run(context.TODO(), v1alpha1.ExperimentPhaseRunning, now)
   169  			Expect(err).ToNot(HaveOccurred())
   170  			Expect(updated).To(Equal(true))
   171  			Expect(sm.Chaos.GetStatus().Experiment.Phase).To(Equal(v1alpha1.ExperimentPhaseRunning))
   172  
   173  			now = now.Add(time.Minute) // 13:56
   174  			updated, err = sm.run(context.TODO(), v1alpha1.ExperimentPhasePaused, now)
   175  			Expect(err).ToNot(HaveOccurred())
   176  			Expect(updated).To(Equal(true))
   177  			Expect(sm.Chaos.GetStatus().Experiment.Phase).To(Equal(v1alpha1.ExperimentPhasePaused))
   178  
   179  			now, err = time.Parse(time.RFC3339, "2020-12-07T14:06:00+00:00")
   180  			Expect(err).ToNot(HaveOccurred())
   181  			updated, err = sm.run(context.TODO(), v1alpha1.ExperimentPhaseRunning, now)
   182  			Expect(err).ToNot(HaveOccurred())
   183  			Expect(updated).To(Equal(true))
   184  			Expect(sm.Chaos.GetStatus().Experiment.Phase).To(Equal(v1alpha1.ExperimentPhaseWaiting))
   185  
   186  			now, err = time.Parse(time.RFC3339, "2020-12-07T14:11:00+00:00")
   187  			Expect(err).ToNot(HaveOccurred())
   188  			updated, err = sm.run(context.TODO(), v1alpha1.ExperimentPhaseRunning, now)
   189  			Expect(err).ToNot(HaveOccurred())
   190  			Expect(updated).To(Equal(true))
   191  			Expect(sm.Chaos.GetStatus().Experiment.Phase).To(Equal(v1alpha1.ExperimentPhaseRunning))
   192  		})
   193  
   194  		It("StateMachine Unexpected State", func() {
   195  			var unexpectedState v1alpha1.ExperimentPhase = "wrong-state"
   196  			now := time.Now()
   197  
   198  			for _, status := range statuses {
   199  				sm := setupStateMachineWithStatus(status)
   200  				updated, err := sm.run(context.TODO(), unexpectedState, now)
   201  				Expect(updated).To(Equal(false))
   202  				Expect(err).To(HaveOccurred())
   203  				Expect(err.Error()).To(ContainSubstring("unexpected target phase"))
   204  			}
   205  
   206  			sm := setupStateMachineWithStatus(unexpectedState)
   207  			for _, status := range statuses {
   208  				updated, err := sm.run(context.TODO(), status, now)
   209  				Expect(updated).To(Equal(false))
   210  				Expect(err).To(HaveOccurred())
   211  				Expect(err.Error()).To(ContainSubstring("unexpected current phase"))
   212  			}
   213  		})
   214  	})
   215  
   216  })
   217  
   218  func setupStateMachineWithStatus(status v1alpha1.ExperimentPhase) *chaosStateMachine {
   219  	req := ctrl.Request{
   220  		NamespacedName: types.NamespacedName{
   221  			Name:      "fakechaos-name",
   222  			Namespace: metav1.NamespaceDefault,
   223  		},
   224  	}
   225  	typeMeta := metav1.TypeMeta{
   226  		Kind:       "PodChaos",
   227  		APIVersion: "v1",
   228  	}
   229  	objectMeta := metav1.ObjectMeta{
   230  		Namespace: metav1.NamespaceDefault,
   231  		Name:      "fakechaos-name",
   232  	}
   233  
   234  	chaos := fakeTwoPhaseChaos{
   235  		TypeMeta:   typeMeta,
   236  		ObjectMeta: objectMeta,
   237  	}
   238  
   239  	chaos.Status.Experiment.Phase = status
   240  	duration := "15m"
   241  	chaos.Duration = &duration
   242  	chaos.Scheduler = &v1alpha1.SchedulerSpec{
   243  		Cron: "@every 20m",
   244  	}
   245  
   246  	c := fake.NewFakeClientWithScheme(scheme.Scheme, &chaos)
   247  
   248  	r := Reconciler{
   249  		Endpoint: fakeEndpoint{},
   250  		Context: ctx.Context{
   251  			Client: c,
   252  			Log:    ctrl.Log.WithName("controllers").WithName("TwoPhase"),
   253  		},
   254  	}
   255  
   256  	sm := chaosStateMachine{
   257  		Chaos:      &chaos,
   258  		Req:        req,
   259  		Reconciler: &r,
   260  	}
   261  
   262  	return &sm
   263  }
   264