github.com/kubeshop/testkube@v1.17.23/pkg/triggers/executor.go (about)

     1  package triggers
     2  
     3  import (
     4  	"context"
     5  	"regexp"
     6  	"time"
     7  
     8  	"github.com/pkg/errors"
     9  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    10  
    11  	testsv3 "github.com/kubeshop/testkube-operator/api/tests/v3"
    12  	testsuitesv3 "github.com/kubeshop/testkube-operator/api/testsuite/v3"
    13  	testtriggersv1 "github.com/kubeshop/testkube-operator/api/testtriggers/v1"
    14  	"github.com/kubeshop/testkube/pkg/api/v1/testkube"
    15  	"github.com/kubeshop/testkube/pkg/scheduler"
    16  	"github.com/kubeshop/testkube/pkg/workerpool"
    17  )
    18  
    19  type Execution string
    20  
    21  const (
    22  	ExecutionTest      = "test"
    23  	ExecutionTestSuite = "testsuite"
    24  )
    25  
    26  type ExecutorF func(context.Context, *watcherEvent, *testtriggersv1.TestTrigger) error
    27  
    28  func (s *Service) execute(ctx context.Context, e *watcherEvent, t *testtriggersv1.TestTrigger) error {
    29  	status := s.getStatusForTrigger(t)
    30  
    31  	concurrencyLevel := scheduler.DefaultConcurrencyLevel
    32  	variables := map[string]testkube.Variable{
    33  		"WATCHER_EVENT_RESOURCE": {
    34  			Name:  "WATCHER_EVENT_RESOURCE",
    35  			Value: string(e.resource),
    36  			Type_: testkube.VariableTypeBasic,
    37  		},
    38  		"WATCHER_EVENT_NAME": {
    39  			Name:  "WATCHER_EVENT_NAME",
    40  			Value: e.name,
    41  			Type_: testkube.VariableTypeBasic,
    42  		},
    43  		"WATCHER_EVENT_NAMESPACE": {
    44  			Name:  "WATCHER_EVENT_NAMESPACE",
    45  			Value: e.namespace,
    46  			Type_: testkube.VariableTypeBasic,
    47  		},
    48  		"WATCHER_EVENT_EVENT_TYPE": {
    49  			Name:  "WATCHER_EVENT_EVENT_TYPE",
    50  			Value: string(e.eventType),
    51  			Type_: testkube.VariableTypeBasic,
    52  		},
    53  	}
    54  
    55  	switch t.Spec.Execution {
    56  	case ExecutionTest:
    57  		tests, err := s.getTests(t)
    58  		if err != nil {
    59  			return err
    60  		}
    61  
    62  		request := testkube.ExecutionRequest{
    63  			Variables: variables,
    64  			RunningContext: &testkube.RunningContext{
    65  				Type_:   string(testkube.RunningContextTypeTestTrigger),
    66  				Context: t.Name,
    67  			},
    68  		}
    69  
    70  		wp := workerpool.New[testkube.Test, testkube.ExecutionRequest, testkube.Execution](concurrencyLevel)
    71  		go func() {
    72  			isDelayDefined := t.Spec.Delay != nil
    73  			if isDelayDefined {
    74  				s.logger.Infof(
    75  					"trigger service: executor component: trigger %s/%s has delayed test execution configured for %f seconds",
    76  					t.Namespace, t.Name, t.Spec.Delay.Seconds(),
    77  				)
    78  				time.Sleep(t.Spec.Delay.Duration)
    79  			}
    80  			s.logger.Infof(
    81  				"trigger service: executor component: scheduling test executions for trigger %s/%s",
    82  				t.Namespace, t.Name,
    83  			)
    84  			go wp.SendRequests(s.scheduler.PrepareTestRequests(tests, request))
    85  			go wp.Run(ctx)
    86  		}()
    87  
    88  		for r := range wp.GetResponses() {
    89  			status.addExecutionID(r.Result.Id)
    90  		}
    91  	case ExecutionTestSuite:
    92  		testSuites, err := s.getTestSuites(t)
    93  		if err != nil {
    94  			return err
    95  		}
    96  
    97  		request := testkube.TestSuiteExecutionRequest{
    98  			Variables: variables,
    99  			RunningContext: &testkube.RunningContext{
   100  				Type_:   string(testkube.RunningContextTypeTestTrigger),
   101  				Context: t.Name,
   102  			},
   103  		}
   104  
   105  		wp := workerpool.New[testkube.TestSuite, testkube.TestSuiteExecutionRequest, testkube.TestSuiteExecution](concurrencyLevel)
   106  		go func() {
   107  			isDelayDefined := t.Spec.Delay != nil
   108  			if isDelayDefined {
   109  				s.logger.Infof(
   110  					"trigger service: executor component: trigger %s/%s has delayed testsuite execution configured for %f seconds",
   111  					t.Namespace, t.Name, t.Spec.Delay.Seconds(),
   112  				)
   113  				time.Sleep(t.Spec.Delay.Duration)
   114  			}
   115  			s.logger.Infof(
   116  				"trigger service: executor component: scheduling testsuite executions for trigger %s/%s",
   117  				t.Namespace, t.Name,
   118  			)
   119  			go wp.SendRequests(s.scheduler.PrepareTestSuiteRequests(testSuites, request))
   120  			go wp.Run(ctx)
   121  		}()
   122  
   123  		for r := range wp.GetResponses() {
   124  			status.addTestSuiteExecutionID(r.Result.Id)
   125  		}
   126  	default:
   127  		return errors.Errorf("invalid execution: %s", t.Spec.Execution)
   128  	}
   129  
   130  	status.start()
   131  	s.logger.Debugf("trigger service: executor component: started test execution for trigger %s/%s", t.Namespace, t.Name)
   132  
   133  	return nil
   134  }
   135  
   136  func (s *Service) getTests(t *testtriggersv1.TestTrigger) ([]testsv3.Test, error) {
   137  	var tests []testsv3.Test
   138  	if t.Spec.TestSelector.Name != "" {
   139  		s.logger.Debugf("trigger service: executor component: fetching testsv3.Test with name %s", t.Spec.TestSelector.Name)
   140  		test, err := s.testsClient.Get(t.Spec.TestSelector.Name)
   141  		if err != nil {
   142  			return nil, err
   143  		}
   144  		tests = append(tests, *test)
   145  	}
   146  
   147  	if t.Spec.TestSelector.NameRegex != "" {
   148  		s.logger.Debugf("trigger service: executor component: fetching testsv3.Test with name regex %s", t.Spec.TestSelector.NameRegex)
   149  		testList, err := s.testsClient.List("")
   150  		if err != nil {
   151  			return nil, err
   152  		}
   153  
   154  		re, err := regexp.Compile(t.Spec.TestSelector.NameRegex)
   155  		if err != nil {
   156  			return nil, err
   157  		}
   158  
   159  		for i := range testList.Items {
   160  			if re.MatchString(testList.Items[i].Name) {
   161  				tests = append(tests, testList.Items[i])
   162  			}
   163  		}
   164  	}
   165  
   166  	if t.Spec.TestSelector.LabelSelector != nil {
   167  		selector, err := metav1.LabelSelectorAsSelector(t.Spec.TestSelector.LabelSelector)
   168  		if err != nil {
   169  			return nil, errors.WithMessagef(err, "error creating selector from test resource label selector")
   170  		}
   171  		stringifiedSelector := selector.String()
   172  		s.logger.Debugf("trigger service: executor component: fetching testsv3.Test with labels %s", stringifiedSelector)
   173  		testList, err := s.testsClient.List(stringifiedSelector)
   174  		if err != nil {
   175  			return nil, err
   176  		}
   177  		tests = append(tests, testList.Items...)
   178  	}
   179  	return tests, nil
   180  }
   181  
   182  func (s *Service) getTestSuites(t *testtriggersv1.TestTrigger) ([]testsuitesv3.TestSuite, error) {
   183  	var testSuites []testsuitesv3.TestSuite
   184  	if t.Spec.TestSelector.Name != "" {
   185  		s.logger.Debugf("trigger service: executor component: fetching testsuitesv3.TestSuite with name %s", t.Spec.TestSelector.Name)
   186  		testSuite, err := s.testSuitesClient.Get(t.Spec.TestSelector.Name)
   187  		if err != nil {
   188  			return nil, err
   189  		}
   190  		testSuites = append(testSuites, *testSuite)
   191  	}
   192  
   193  	if t.Spec.TestSelector.NameRegex != "" {
   194  		s.logger.Debugf("trigger service: executor component: fetching testsuitesv3.TestSuite with name regex %s", t.Spec.TestSelector.NameRegex)
   195  		testSuitesList, err := s.testSuitesClient.List("")
   196  		if err != nil {
   197  			return nil, err
   198  		}
   199  
   200  		re, err := regexp.Compile(t.Spec.TestSelector.NameRegex)
   201  		if err != nil {
   202  			return nil, err
   203  		}
   204  
   205  		for i := range testSuitesList.Items {
   206  			if re.MatchString(testSuitesList.Items[i].Name) {
   207  				testSuites = append(testSuites, testSuitesList.Items[i])
   208  			}
   209  		}
   210  	}
   211  
   212  	if t.Spec.TestSelector.LabelSelector != nil {
   213  		selector, err := metav1.LabelSelectorAsSelector(t.Spec.TestSelector.LabelSelector)
   214  		if err != nil {
   215  			return nil, errors.WithMessagef(err, "error creating selector from test resource label selector")
   216  		}
   217  		stringifiedSelector := selector.String()
   218  		s.logger.Debugf("trigger service: executor component: fetching testsuitesv3.TestSuite with label %s", stringifiedSelector)
   219  		testSuitesList, err := s.testSuitesClient.List(stringifiedSelector)
   220  		if err != nil {
   221  			return nil, err
   222  		}
   223  		testSuites = append(testSuites, testSuitesList.Items...)
   224  	}
   225  	return testSuites, nil
   226  }