github.com/argoproj/argo-events@v1.9.1/test/stress/main.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"regexp"
    11  	"runtime"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/spf13/cobra"
    17  	corev1 "k8s.io/api/core/v1"
    18  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  	"k8s.io/apimachinery/pkg/runtime/schema"
    20  	"k8s.io/client-go/dynamic"
    21  	"k8s.io/client-go/kubernetes"
    22  	_ "k8s.io/client-go/plugin/pkg/client/auth"
    23  	"k8s.io/client-go/rest"
    24  	"k8s.io/client-go/tools/clientcmd"
    25  	"sigs.k8s.io/controller-runtime/pkg/manager/signals"
    26  	"sigs.k8s.io/yaml"
    27  
    28  	"github.com/argoproj/argo-events/pkg/apis/eventbus"
    29  	eventbusv1alpha1 "github.com/argoproj/argo-events/pkg/apis/eventbus/v1alpha1"
    30  	"github.com/argoproj/argo-events/pkg/apis/eventsource"
    31  	eventsourcev1alpha1 "github.com/argoproj/argo-events/pkg/apis/eventsource/v1alpha1"
    32  	"github.com/argoproj/argo-events/pkg/apis/sensor"
    33  	sensorv1alpha1 "github.com/argoproj/argo-events/pkg/apis/sensor/v1alpha1"
    34  	eventbusversiond "github.com/argoproj/argo-events/pkg/client/eventbus/clientset/versioned"
    35  	eventbuspkg "github.com/argoproj/argo-events/pkg/client/eventbus/clientset/versioned/typed/eventbus/v1alpha1"
    36  	eventsourceversiond "github.com/argoproj/argo-events/pkg/client/eventsource/clientset/versioned"
    37  	eventsourcepkg "github.com/argoproj/argo-events/pkg/client/eventsource/clientset/versioned/typed/eventsource/v1alpha1"
    38  	sensorversiond "github.com/argoproj/argo-events/pkg/client/sensor/clientset/versioned"
    39  	sensorpkg "github.com/argoproj/argo-events/pkg/client/sensor/clientset/versioned/typed/sensor/v1alpha1"
    40  	testutil "github.com/argoproj/argo-events/test/util"
    41  )
    42  
    43  const (
    44  	eventBusName   = "stress-testing"
    45  	defaultTimeout = 60 * time.Second
    46  
    47  	Success = "success"
    48  	Failure = "failure"
    49  
    50  	First = "first"
    51  	Last  = "last"
    52  
    53  	EventNameKey   = "eventName"
    54  	TriggerNameKey = "triggerName"
    55  
    56  	StressTestingLabel      = "argo-events-stress"
    57  	StressTestingLabelValue = "true"
    58  
    59  	logEventSourceStarted      = "Eventing server started."
    60  	logSensorStarted           = "Sensor started."
    61  	logTriggerActionSuccessful = "Successfully processed trigger"
    62  	logTriggerActionFailed     = "Failed to execute a trigger"
    63  	logEventSuccessful         = "Succeeded to publish an event"
    64  	logEventFailed             = "Failed to publish an event"
    65  )
    66  
    67  type TestingEventSource string
    68  
    69  // possible values of TestingEventSource
    70  const (
    71  	UnsupportedEventsource TestingEventSource = "unsupported"
    72  	WebhookEventSource     TestingEventSource = "webhook"
    73  	SQSEventSource         TestingEventSource = "sqs"
    74  	SNSEventSource         TestingEventSource = "sns"
    75  	KafkaEventSource       TestingEventSource = "kafka"
    76  	NATSEventSource        TestingEventSource = "nats"
    77  	RedisEventSource       TestingEventSource = "redis"
    78  )
    79  
    80  type EventBusType string
    81  
    82  // possible value of EventBus type
    83  const (
    84  	UnsupportedEventBusType EventBusType = "unsupported"
    85  	STANEventBus            EventBusType = "stan"
    86  	JetstreamEventBus       EventBusType = "jetstream"
    87  )
    88  
    89  type TestingTrigger string
    90  
    91  // possible values of TestingTrigger
    92  const (
    93  	UnsupportedTrigger TestingTrigger = "unsupported"
    94  	WorkflowTrigger    TestingTrigger = "workflow"
    95  	LogTrigger         TestingTrigger = "log"
    96  )
    97  
    98  var (
    99  	background = metav1.DeletePropagationBackground
   100  )
   101  
   102  type options struct {
   103  	namespace          string
   104  	testingEventSource TestingEventSource
   105  	testingTrigger     TestingTrigger
   106  	eventBusType       EventBusType
   107  	esName             string
   108  	sensorName         string
   109  	// Inactive time before exiting
   110  	idleTimeout time.Duration
   111  	hardTimeout *time.Duration
   112  	noCleanUp   bool
   113  
   114  	kubeClient        kubernetes.Interface
   115  	eventBusClient    eventbuspkg.EventBusInterface
   116  	eventSourceClient eventsourcepkg.EventSourceInterface
   117  	sensorClient      sensorpkg.SensorInterface
   118  	restConfig        *rest.Config
   119  }
   120  
   121  func NewOptions(testingEventSource TestingEventSource, testingTrigger TestingTrigger, eventBusType EventBusType, esName, sensorName string, idleTimeout time.Duration, noCleanUp bool) (*options, error) {
   122  	loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
   123  	configOverrides := &clientcmd.ConfigOverrides{}
   124  	kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
   125  	config, err := kubeConfig.ClientConfig()
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  	namespace, _, _ := kubeConfig.Namespace()
   130  	kubeClient, err := kubernetes.NewForConfig(config)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  	eventBusClient := eventbusversiond.NewForConfigOrDie(config).ArgoprojV1alpha1().EventBus(namespace)
   135  	eventSourceClient := eventsourceversiond.NewForConfigOrDie(config).ArgoprojV1alpha1().EventSources(namespace)
   136  	sensorClient := sensorversiond.NewForConfigOrDie(config).ArgoprojV1alpha1().Sensors(namespace)
   137  	return &options{
   138  		namespace:          namespace,
   139  		testingEventSource: testingEventSource,
   140  		testingTrigger:     testingTrigger,
   141  		eventBusType:       eventBusType,
   142  		esName:             esName,
   143  		sensorName:         sensorName,
   144  		kubeClient:         kubeClient,
   145  		eventBusClient:     eventBusClient,
   146  		eventSourceClient:  eventSourceClient,
   147  		restConfig:         config,
   148  		sensorClient:       sensorClient,
   149  		idleTimeout:        idleTimeout,
   150  		noCleanUp:          noCleanUp,
   151  	}, nil
   152  }
   153  
   154  func (o *options) createEventBus(ctx context.Context) (*eventbusv1alpha1.EventBus, error) {
   155  	fmt.Printf("------- Creating %v EventBus -------\n", o.eventBusType)
   156  	eb := &eventbusv1alpha1.EventBus{}
   157  	if err := readResource(fmt.Sprintf("@testdata/eventbus/%v.yaml", o.eventBusType), eb); err != nil {
   158  		return nil, fmt.Errorf("failed to read %v event bus yaml file: %w", o.eventBusType, err)
   159  	}
   160  	l := eb.GetLabels()
   161  	if l == nil {
   162  		l = map[string]string{}
   163  	}
   164  	l[StressTestingLabel] = StressTestingLabelValue
   165  	eb.SetLabels(l)
   166  	eb.Name = eventBusName
   167  	result, err := o.eventBusClient.Create(ctx, eb, metav1.CreateOptions{})
   168  	if err != nil {
   169  		return nil, fmt.Errorf("failed to create event bus: %w", err)
   170  	}
   171  	if err := testutil.WaitForEventBusReady(ctx, o.eventBusClient, eb.Name, defaultTimeout); err != nil {
   172  		return nil, fmt.Errorf("expected to see event bus ready: %w", err)
   173  	}
   174  	if err := testutil.WaitForEventBusStatefulSetReady(ctx, o.kubeClient, o.namespace, eb.Name, defaultTimeout); err != nil {
   175  		return nil, fmt.Errorf("expected to see event bus statefulset ready: %w", err)
   176  	}
   177  	return result, nil
   178  }
   179  
   180  func (o *options) createEventSource(ctx context.Context) (*eventsourcev1alpha1.EventSource, error) {
   181  	fmt.Printf("\n------- Creating %v EventSource -------\n", o.testingEventSource)
   182  	es := &eventsourcev1alpha1.EventSource{}
   183  	file := fmt.Sprintf("@testdata/eventsources/%v.yaml", o.testingEventSource)
   184  	if err := readResource(file, es); err != nil {
   185  		return nil, fmt.Errorf("failed to read %v event source yaml file: %w", o.testingEventSource, err)
   186  	}
   187  	l := es.GetLabels()
   188  	if l == nil {
   189  		l = map[string]string{}
   190  	}
   191  	l[StressTestingLabel] = StressTestingLabelValue
   192  	es.SetLabels(l)
   193  	es.Spec.EventBusName = eventBusName
   194  	result, err := o.eventSourceClient.Create(ctx, es, metav1.CreateOptions{})
   195  	if err != nil {
   196  		return nil, fmt.Errorf("failed to create event source: %w", err)
   197  	}
   198  	if err := testutil.WaitForEventSourceReady(ctx, o.eventSourceClient, es.Name, defaultTimeout); err != nil {
   199  		return nil, fmt.Errorf("expected to see event source ready: %w", err)
   200  	}
   201  	if err := testutil.WaitForEventSourceDeploymentReady(ctx, o.kubeClient, o.namespace, es.Name, defaultTimeout); err != nil {
   202  		return nil, fmt.Errorf("expected to see event source deployment and pod ready: %w", err)
   203  	}
   204  	contains, err := testutil.EventSourcePodLogContains(ctx, o.kubeClient, o.namespace, es.Name, logEventSourceStarted)
   205  	if err != nil {
   206  		return nil, fmt.Errorf("expected to see event source pod contains something: %w", err)
   207  	}
   208  	if !contains {
   209  		return nil, fmt.Errorf("EventSource Pod does look good, it might have started failed")
   210  	}
   211  	return result, nil
   212  }
   213  
   214  func (o *options) createSensor(ctx context.Context) (*sensorv1alpha1.Sensor, error) {
   215  	fmt.Printf("\n------- Creating %v Sensor -------\n", o.testingTrigger)
   216  	sensor := &sensorv1alpha1.Sensor{}
   217  	file := fmt.Sprintf("@testdata/sensors/%v.yaml", o.testingTrigger)
   218  	if err := readResource(file, sensor); err != nil {
   219  		return nil, fmt.Errorf("failed to read %v sensor yaml file: %w", o.testingTrigger, err)
   220  	}
   221  	l := sensor.GetLabels()
   222  	if l == nil {
   223  		l = map[string]string{}
   224  	}
   225  	l[StressTestingLabel] = StressTestingLabelValue
   226  	sensor.SetLabels(l)
   227  	sensor.Spec.EventBusName = eventBusName
   228  	result, err := o.sensorClient.Create(ctx, sensor, metav1.CreateOptions{})
   229  	if err != nil {
   230  		return nil, fmt.Errorf("failed to create sensor: %w", err)
   231  	}
   232  	if err := testutil.WaitForSensorReady(ctx, o.sensorClient, sensor.Name, defaultTimeout); err != nil {
   233  		return nil, fmt.Errorf("expected to see sensor ready: %w", err)
   234  	}
   235  	if err := testutil.WaitForSensorDeploymentReady(ctx, o.kubeClient, o.namespace, sensor.Name, defaultTimeout); err != nil {
   236  		return nil, fmt.Errorf("expected to see sensor deployment and pod ready: %w", err)
   237  	}
   238  	contains, err := testutil.SensorPodLogContains(ctx, o.kubeClient, o.namespace, sensor.Name, logSensorStarted)
   239  	if err != nil {
   240  		return nil, fmt.Errorf("expected to see sensor pod contains something: %w", err)
   241  	}
   242  	if !contains {
   243  		return nil, fmt.Errorf("Sensor Pod does look good, it might have started failed")
   244  	}
   245  	return result, nil
   246  }
   247  
   248  func (o *options) getEventSourcePodNames(ctx context.Context, eventSourceName string) ([]string, error) {
   249  	labelSelector := fmt.Sprintf("controller=eventsource-controller,eventsource-name=%s", eventSourceName)
   250  	esPodList, err := o.kubeClient.CoreV1().Pods(o.namespace).List(ctx, metav1.ListOptions{LabelSelector: labelSelector, FieldSelector: "status.phase=Running"})
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  	results := []string{}
   255  	for _, i := range esPodList.Items {
   256  		results = append(results, i.Name)
   257  	}
   258  	return results, nil
   259  }
   260  
   261  func (o *options) getSensorPodNames(ctx context.Context, sensorName string) ([]string, error) {
   262  	labelSelector := fmt.Sprintf("controller=sensor-controller,sensor-name=%s", sensorName)
   263  	sPodList, err := o.kubeClient.CoreV1().Pods(o.namespace).List(ctx, metav1.ListOptions{LabelSelector: labelSelector, FieldSelector: "status.phase=Running"})
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  	results := []string{}
   268  	for _, i := range sPodList.Items {
   269  		results = append(results, i.Name)
   270  	}
   271  	return results, nil
   272  }
   273  
   274  func (o *options) runTesting(ctx context.Context, eventSourceName, sensorName string) error {
   275  	esPodNames, err := o.getEventSourcePodNames(ctx, eventSourceName)
   276  	if err != nil {
   277  		return fmt.Errorf("failed to get event source pod names: %v", err)
   278  	}
   279  	if len(esPodNames) == 0 {
   280  		return fmt.Errorf("no pod found for event source %s", eventSourceName)
   281  	}
   282  	sensorPodNames, err := o.getSensorPodNames(ctx, sensorName)
   283  	if err != nil {
   284  		return fmt.Errorf("failed to get sensor pod names: %v", err)
   285  	}
   286  	if len(sensorPodNames) == 0 {
   287  		return fmt.Errorf("no pod found for sensor %s", sensorName)
   288  	}
   289  
   290  	successActionReg := regexp.MustCompile(logTriggerActionSuccessful)
   291  	failureActionReg := regexp.MustCompile(logTriggerActionFailed)
   292  	successEventReg := regexp.MustCompile(logEventSuccessful)
   293  	failureEventReg := regexp.MustCompile(logEventFailed)
   294  
   295  	fmt.Printf(`
   296  *********************************************************
   297  The application will automatically exit:
   298    - If there's no active events and actions in %v.
   299  `, o.idleTimeout)
   300  	if o.hardTimeout != nil {
   301  		fmt.Printf("  - In %v after it starts.\n", *o.hardTimeout)
   302  	}
   303  	fmt.Printf(`
   304  Or you can terminate it any time by Ctrl + C.
   305  *********************************************************
   306  
   307  `)
   308  
   309  	esMap := map[string]int64{}
   310  	esTimeMap := map[string]time.Time{}
   311  
   312  	sensorMap := map[string]int64{}
   313  	sensorTimeMap := map[string]time.Time{}
   314  
   315  	var esLock = &sync.RWMutex{}
   316  	var sensorLock = &sync.RWMutex{}
   317  
   318  	startTime := time.Now()
   319  
   320  	wg := &sync.WaitGroup{}
   321  	for _, sensorPodName := range sensorPodNames {
   322  		wg.Add(1)
   323  		go func(podName string) {
   324  			defer wg.Done()
   325  			fmt.Printf("Started watching Sensor Pod %s ...\n", podName)
   326  			stream, err := o.kubeClient.CoreV1().Pods(o.namespace).GetLogs(podName, &corev1.PodLogOptions{Follow: true}).Stream(ctx)
   327  			if err != nil {
   328  				fmt.Printf("failed to acquire sensor pod %s log stream: %v", podName, err)
   329  				return
   330  			}
   331  			defer func() { _ = stream.Close() }()
   332  
   333  			sCh := make(chan string)
   334  			go func(dataCh chan string) {
   335  				s := bufio.NewScanner(stream)
   336  				for {
   337  					if !s.Scan() {
   338  						fmt.Printf("Can not read: %v\n", s.Err())
   339  						close(dataCh)
   340  						return
   341  					}
   342  					data := s.Bytes()
   343  					triggerName := getLogValue(data, startTime, TriggerNameKey)
   344  					if triggerName == "" {
   345  						continue
   346  					}
   347  					if successActionReg.Match(data) {
   348  						dataCh <- triggerName + "/" + Success
   349  					} else if failureActionReg.Match(data) {
   350  						dataCh <- triggerName + "/" + Failure
   351  					}
   352  				}
   353  			}(sCh)
   354  
   355  			for {
   356  				if o.hardTimeout != nil && time.Since(startTime).Seconds() > o.hardTimeout.Seconds() {
   357  					fmt.Printf("Exited Sensor Pod %s due to the hard timeout %v\n", podName, *o.hardTimeout)
   358  					return
   359  				}
   360  				timeout := 5 * 60 * time.Second
   361  				lastActionTime := startTime
   362  				sensorLock.RLock()
   363  				if len(sensorMap) > 0 && len(sensorTimeMap) > 0 {
   364  					timeout = o.idleTimeout
   365  					for _, v := range sensorTimeMap {
   366  						if v.After(lastActionTime) {
   367  							lastActionTime = v
   368  						}
   369  					}
   370  				}
   371  				sensorLock.RUnlock()
   372  
   373  				if time.Since(lastActionTime).Seconds() > timeout.Seconds() {
   374  					fmt.Printf("Exited Sensor Pod %s due to no actions in the last %v\n", podName, o.idleTimeout)
   375  					return
   376  				}
   377  				select {
   378  				case <-ctx.Done():
   379  					return
   380  				case data, ok := <-sCh:
   381  					if !ok {
   382  						return
   383  					}
   384  					// e.g. triggerName/success
   385  					t := strings.Split(data, "/")
   386  					sensorLock.Lock()
   387  					if _, ok := sensorMap[data]; !ok {
   388  						sensorMap[t[0]+"/"+Success] = 0
   389  						sensorMap[t[0]+"/"+Failure] = 0
   390  					}
   391  					if t[1] == Success {
   392  						sensorMap[t[0]+"/"+Success]++
   393  					} else {
   394  						sensorMap[t[0]+"/"+Failure]++
   395  					}
   396  					if sensorMap[t[0]+"/"+Success]+sensorMap[t[0]+"/"+Failure] == 1 {
   397  						sensorTimeMap[t[0]+"/"+First] = time.Now()
   398  					}
   399  					sensorTimeMap[t[0]+"/"+Last] = time.Now()
   400  					sensorLock.Unlock()
   401  				default:
   402  				}
   403  			}
   404  		}(sensorPodName)
   405  	}
   406  
   407  	for _, esPodName := range esPodNames {
   408  		wg.Add(1)
   409  		go func(podName string) {
   410  			defer wg.Done()
   411  			fmt.Printf("Started watching EventSource Pod %s ...\n", podName)
   412  			stream, err := o.kubeClient.CoreV1().Pods(o.namespace).GetLogs(podName, &corev1.PodLogOptions{Follow: true}).Stream(ctx)
   413  			if err != nil {
   414  				fmt.Printf("failed to acquire event source pod %s log stream: %v", podName, err)
   415  				return
   416  			}
   417  			defer func() { _ = stream.Close() }()
   418  
   419  			sCh := make(chan string)
   420  			go func(dataCh chan string) {
   421  				s := bufio.NewScanner(stream)
   422  				for {
   423  					if !s.Scan() {
   424  						fmt.Printf("Can not read: %v\n", s.Err())
   425  						close(dataCh)
   426  						return
   427  					}
   428  					data := s.Bytes()
   429  					eventName := getLogValue(data, startTime, EventNameKey)
   430  					if eventName == "" {
   431  						continue
   432  					}
   433  					if successEventReg.Match(data) {
   434  						dataCh <- eventName + "/" + Success
   435  					} else if failureEventReg.Match(data) {
   436  						dataCh <- eventName + "/" + Failure
   437  					}
   438  				}
   439  			}(sCh)
   440  
   441  			for {
   442  				if o.hardTimeout != nil && time.Since(startTime).Seconds() > o.hardTimeout.Seconds() {
   443  					fmt.Printf("Exited EventSource Pod %s due to the hard timeout %v\n", podName, *o.hardTimeout)
   444  					return
   445  				}
   446  				timeout := 5 * 60 * time.Second
   447  				lastEventTime := startTime
   448  
   449  				esLock.RLock()
   450  				if len(esMap) > 0 && len(esTimeMap) > 0 {
   451  					timeout = o.idleTimeout
   452  					for _, v := range esTimeMap {
   453  						if v.After(lastEventTime) {
   454  							lastEventTime = v
   455  						}
   456  					}
   457  				}
   458  				esLock.RUnlock()
   459  				if time.Since(lastEventTime).Seconds() > timeout.Seconds() {
   460  					fmt.Printf("Exited EventSource Pod %s due to no active events in the last %v\n", podName, o.idleTimeout)
   461  					return
   462  				}
   463  				select {
   464  				case <-ctx.Done():
   465  					return
   466  				case data, ok := <-sCh:
   467  					if !ok {
   468  						return
   469  					}
   470  					// e.g. eventName/success
   471  					t := strings.Split(data, "/")
   472  					esLock.Lock()
   473  					if _, ok := esMap[data]; !ok {
   474  						esMap[t[0]+"/"+Success] = 0
   475  						esMap[t[0]+"/"+Failure] = 0
   476  					}
   477  					if t[1] == Success {
   478  						esMap[t[0]+"/"+Success]++
   479  					} else {
   480  						esMap[t[0]+"/"+Failure]++
   481  					}
   482  					if esMap[t[0]+"/"+Success]+esMap[t[0]+"/"+Failure] == 1 {
   483  						esTimeMap[t[0]+"/"+First] = time.Now()
   484  					}
   485  					esTimeMap[t[0]+"/"+Last] = time.Now()
   486  					esLock.Unlock()
   487  				default:
   488  				}
   489  			}
   490  		}(esPodName)
   491  	}
   492  
   493  	wg.Wait()
   494  
   495  	time.Sleep(3 * time.Second)
   496  	eventNames := []string{}
   497  	for k := range esMap {
   498  		t := strings.Split(k, "/")
   499  		if t[1] == Success {
   500  			eventNames = append(eventNames, t[0])
   501  		}
   502  	}
   503  	triggerNames := []string{}
   504  	for k := range sensorMap {
   505  		t := strings.Split(k, "/")
   506  		if t[1] == Success {
   507  			triggerNames = append(triggerNames, t[0])
   508  		}
   509  	}
   510  	fmt.Printf("\n++++++++++++++++++++++++ Events Summary +++++++++++++++++++++++\n")
   511  	if len(eventNames) == 0 {
   512  		fmt.Println("No events.")
   513  	} else {
   514  		for _, eventName := range eventNames {
   515  			fmt.Printf("Event Name                    : %s\n", eventName)
   516  			fmt.Printf("Total processed events        : %d\n", esMap[eventName+"/"+Success]+esMap[eventName+"/"+Failure])
   517  			fmt.Printf("Events sent successful        : %d\n", esMap[eventName+"/"+Success])
   518  			fmt.Printf("Events sent failed            : %d\n", esMap[eventName+"/"+Failure])
   519  			fmt.Printf("First event sent at           : %v\n", esTimeMap[eventName+"/"+First])
   520  			fmt.Printf("Last event sent at            : %v\n", esTimeMap[eventName+"/"+Last])
   521  			fmt.Printf("Total time taken              : %v\n", esTimeMap[eventName+"/"+Last].Sub(esTimeMap[eventName+"/"+First]))
   522  			fmt.Println("--")
   523  		}
   524  	}
   525  
   526  	fmt.Printf("\n+++++++++++++++++++++++ Actions Summary +++++++++++++++++++++++\n")
   527  	if len(triggerNames) == 0 {
   528  		fmt.Println("No actions.")
   529  	} else {
   530  		for _, triggerName := range triggerNames {
   531  			fmt.Printf("Trigger Name                  : %s\n", triggerName)
   532  			fmt.Printf("Total triggered actions       : %d\n", sensorMap[triggerName+"/"+Success]+sensorMap[triggerName+"/"+Failure])
   533  			fmt.Printf("Action triggered successfully : %d\n", sensorMap[triggerName+"/"+Success])
   534  			fmt.Printf("Action triggered failed       : %d\n", sensorMap[triggerName+"/"+Failure])
   535  			fmt.Printf("First action triggered at     : %v\n", sensorTimeMap[triggerName+"/"+First])
   536  			fmt.Printf("Last action triggered at      : %v\n", sensorTimeMap[triggerName+"/"+Last])
   537  			fmt.Printf("Total time taken              : %v\n", sensorTimeMap[triggerName+"/"+Last].Sub(sensorTimeMap[triggerName+"/"+First]))
   538  			fmt.Println("--")
   539  		}
   540  	}
   541  	return nil
   542  }
   543  
   544  // Check if it a valid log in JSON format, which contains something
   545  // like `"ts":1616093369.2583323`, and if it's later than start,
   546  // return the value of a log key
   547  func getLogValue(log []byte, start time.Time, key string) string {
   548  	t := make(map[string]interface{})
   549  	if err := json.Unmarshal(log, &t); err != nil {
   550  		// invalid json format log
   551  		return ""
   552  	}
   553  	ts, ok := t["ts"]
   554  	if !ok {
   555  		return ""
   556  	}
   557  	s, ok := ts.(float64)
   558  	if !ok {
   559  		return ""
   560  	}
   561  	if float64(start.Unix()) > s {
   562  		// old log
   563  		return ""
   564  	}
   565  	v, ok := t[key]
   566  	if !ok {
   567  		return ""
   568  	}
   569  	return fmt.Sprintf("%v", v)
   570  }
   571  
   572  func (o *options) dynamicFor(r schema.GroupVersionResource) dynamic.ResourceInterface {
   573  	resourceInterface := dynamic.NewForConfigOrDie(o.restConfig).Resource(r)
   574  	return resourceInterface.Namespace(o.namespace)
   575  }
   576  
   577  func (o *options) cleanUpResources(ctx context.Context) error {
   578  	hasTestLabel := metav1.ListOptions{LabelSelector: StressTestingLabel}
   579  	resources := []schema.GroupVersionResource{
   580  		{Group: eventsource.Group, Version: "v1alpha1", Resource: eventsource.Plural},
   581  		{Group: sensor.Group, Version: "v1alpha1", Resource: sensor.Plural},
   582  		{Group: eventbus.Group, Version: "v1alpha1", Resource: eventbus.Plural},
   583  	}
   584  	for _, r := range resources {
   585  		if err := o.dynamicFor(r).DeleteCollection(ctx, metav1.DeleteOptions{PropagationPolicy: &background}, hasTestLabel); err != nil {
   586  			return err
   587  		}
   588  	}
   589  
   590  	for _, r := range resources {
   591  		for {
   592  			list, err := o.dynamicFor(r).List(ctx, hasTestLabel)
   593  			if err != nil {
   594  				return err
   595  			}
   596  			if len(list.Items) == 0 {
   597  				break
   598  			}
   599  			time.Sleep(100 * time.Millisecond)
   600  		}
   601  	}
   602  	return nil
   603  }
   604  
   605  func (o *options) Start(ctx context.Context) error {
   606  	fmt.Println("#################### Preparing ####################")
   607  
   608  	esName := o.esName
   609  	sensorName := o.sensorName
   610  
   611  	// Need to create
   612  	if esName == "" && sensorName == "" {
   613  		// Clean up resources if any
   614  		if err := o.cleanUpResources(ctx); err != nil {
   615  			return err
   616  		}
   617  		time.Sleep(10 * time.Second)
   618  
   619  		// Create EventBus
   620  		eb, err := o.createEventBus(ctx)
   621  		if err != nil {
   622  			return err
   623  		}
   624  
   625  		defer func() {
   626  			if !o.noCleanUp {
   627  				_ = o.eventBusClient.Delete(ctx, eb.Name, metav1.DeleteOptions{})
   628  			}
   629  		}()
   630  
   631  		time.Sleep(5 * time.Second)
   632  
   633  		// Create Sensor
   634  		sensor, err := o.createSensor(ctx)
   635  		if err != nil {
   636  			return err
   637  		}
   638  		defer func() {
   639  			if !o.noCleanUp {
   640  				_ = o.sensorClient.Delete(ctx, sensor.Name, metav1.DeleteOptions{})
   641  			}
   642  		}()
   643  
   644  		// Create Event Source
   645  		es, err := o.createEventSource(ctx)
   646  		if err != nil {
   647  			return err
   648  		}
   649  		defer func() {
   650  			if !o.noCleanUp {
   651  				_ = o.eventSourceClient.Delete(ctx, es.Name, metav1.DeleteOptions{})
   652  			}
   653  		}()
   654  
   655  		esName = es.Name
   656  		sensorName = sensor.Name
   657  	} else {
   658  		fmt.Printf("------- Use existing EventSource and Sensor -------\n")
   659  		fmt.Printf("EventSource name : %s\n", esName)
   660  		fmt.Printf("Sensor name      : %s\n", sensorName)
   661  	}
   662  
   663  	// Run testing
   664  	fmt.Println("")
   665  	fmt.Println("################# Started Testing #################")
   666  
   667  	return o.runTesting(ctx, esName, sensorName)
   668  }
   669  
   670  func readResource(text string, v metav1.Object) error {
   671  	var data []byte
   672  	var err error
   673  	if strings.HasPrefix(text, "@") {
   674  		file := strings.TrimPrefix(text, "@")
   675  		_, fileName, _, _ := runtime.Caller(0)
   676  		data, err = os.ReadFile(filepath.Dir(fileName) + "/" + file)
   677  		if err != nil {
   678  			return fmt.Errorf("failed to read a file: %w", err)
   679  		}
   680  	} else {
   681  		data = []byte(text)
   682  	}
   683  	if err = yaml.Unmarshal(data, v); err != nil {
   684  		return fmt.Errorf("failed to unmarshal the yaml: %w", err)
   685  	}
   686  	return nil
   687  }
   688  
   689  func getTestingEventSource(str string) TestingEventSource {
   690  	switch str {
   691  	case "webhook":
   692  		return WebhookEventSource
   693  	case "sqs":
   694  		return SQSEventSource
   695  	case "sns":
   696  		return SNSEventSource
   697  	case "kafka":
   698  		return KafkaEventSource
   699  	case "redis":
   700  		return RedisEventSource
   701  	case "nats":
   702  		return NATSEventSource
   703  	default:
   704  		return UnsupportedEventsource
   705  	}
   706  }
   707  
   708  func getEventBusType(str string) EventBusType {
   709  	switch str {
   710  	case "jetstream":
   711  		return JetstreamEventBus
   712  	case "stan":
   713  		return STANEventBus
   714  	default:
   715  		return UnsupportedEventBusType
   716  	}
   717  }
   718  
   719  func getTestingTrigger(str string) TestingTrigger {
   720  	switch str {
   721  	case "log":
   722  		return LogTrigger
   723  	case "workflow":
   724  		return WorkflowTrigger
   725  	default:
   726  		return UnsupportedTrigger
   727  	}
   728  }
   729  
   730  func main() {
   731  	var (
   732  		ebTypeStr      string
   733  		esTypeStr      string
   734  		triggerTypeStr string
   735  		esName         string
   736  		sensorName     string
   737  		idleTimeoutStr string
   738  		hardTimeoutStr string
   739  		noCleanUp      bool
   740  	)
   741  	var rootCmd = &cobra.Command{
   742  		Use:   "go run ./test/stress/main.go",
   743  		Short: "Argo Events stress testing.",
   744  		Long:  ``,
   745  		Run: func(cmd *cobra.Command, args []string) {
   746  			esType := getTestingEventSource(esTypeStr)
   747  			triggerType := getTestingTrigger(triggerTypeStr)
   748  			if esName == "" && sensorName == "" {
   749  				if esType == UnsupportedEventsource {
   750  					fmt.Printf("Invalid event source %s\n\n", esTypeStr)
   751  					cmd.HelpFunc()(cmd, args)
   752  					os.Exit(1)
   753  				}
   754  
   755  				if triggerType == UnsupportedTrigger {
   756  					fmt.Printf("Invalid trigger %s\n\n", triggerTypeStr)
   757  					cmd.HelpFunc()(cmd, args)
   758  					os.Exit(1)
   759  				}
   760  			} else if (esName == "" && sensorName != "") || (esName != "" && sensorName == "") {
   761  				fmt.Printf("Both event source name and sensor name need to be specified\n\n")
   762  				cmd.HelpFunc()(cmd, args)
   763  				os.Exit(1)
   764  			}
   765  			eventBusType := getEventBusType(ebTypeStr)
   766  			if eventBusType == UnsupportedEventBusType {
   767  				fmt.Printf("Invalid event bus type %s\n\n", ebTypeStr)
   768  				cmd.HelpFunc()(cmd, args)
   769  				os.Exit(1)
   770  			}
   771  
   772  			idleTimeout, err := time.ParseDuration(idleTimeoutStr)
   773  			if err != nil {
   774  				fmt.Printf("Invalid idle timeout %s: %v\n\n", idleTimeoutStr, err)
   775  				cmd.HelpFunc()(cmd, args)
   776  				os.Exit(1)
   777  			}
   778  			opts, err := NewOptions(esType, triggerType, eventBusType, esName, sensorName, idleTimeout, noCleanUp)
   779  			if err != nil {
   780  				fmt.Printf("Failed: %v\n", err)
   781  				os.Exit(1)
   782  			}
   783  			if hardTimeoutStr != "" {
   784  				hardTimeout, err := time.ParseDuration(hardTimeoutStr)
   785  				if err != nil {
   786  					fmt.Printf("Invalid hard timeout %s: %v\n\n", hardTimeoutStr, err)
   787  					cmd.HelpFunc()(cmd, args)
   788  					os.Exit(1)
   789  				}
   790  				opts.hardTimeout = &hardTimeout
   791  			}
   792  			ctx := signals.SetupSignalHandler()
   793  			if err = opts.Start(ctx); err != nil {
   794  				panic(err)
   795  			}
   796  		},
   797  	}
   798  	rootCmd.Flags().StringVarP(&ebTypeStr, "eb-type", "b", "", "Type of event bus to be tested: stan, jetstream")
   799  	rootCmd.Flags().StringVarP(&esTypeStr, "es-type", "e", "", "Type of event source to be tested, e.g. webhook, sqs, etc.")
   800  	rootCmd.Flags().StringVarP(&triggerTypeStr, "trigger-type", "t", string(LogTrigger), "Type of trigger to be tested, e.g. log, workflow.")
   801  	rootCmd.Flags().StringVar(&esName, "es-name", "", "Name of an existing event source to be tested")
   802  	rootCmd.Flags().StringVar(&sensorName, "sensor-name", "", "Name of an existing sensor to be tested.")
   803  	rootCmd.Flags().StringVar(&idleTimeoutStr, "idle-timeout", "60s", "Exit in a period of time without any active events or actions. e.g. 30s, 2m.")
   804  	rootCmd.Flags().StringVar(&hardTimeoutStr, "hard-timeout", "", "Exit in a period of time after the testing starts. e.g. 120s, 5m. If it's specified, the application will exit at the time either hard-timeout or idle-timeout meets.")
   805  	rootCmd.Flags().BoolVar(&noCleanUp, "no-clean-up", false, "Whether to clean up the created resources.")
   806  
   807  	_ = rootCmd.Execute()
   808  }