github.com/argoproj/argo-events@v1.9.1/eventbus/jetstream/sensor/sensor_jetstream.go (about)

     1  package sensor
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  	"time"
     8  
     9  	eventbuscommon "github.com/argoproj/argo-events/eventbus/common"
    10  	eventbusjetstreambase "github.com/argoproj/argo-events/eventbus/jetstream/base"
    11  	"github.com/argoproj/argo-events/pkg/apis/sensor/v1alpha1"
    12  	nats "github.com/nats-io/nats.go"
    13  	"go.uber.org/zap"
    14  
    15  	"encoding/json"
    16  
    17  	"github.com/argoproj/argo-events/common"
    18  	cloudevents "github.com/cloudevents/sdk-go/v2"
    19  	hashstructure "github.com/mitchellh/hashstructure/v2"
    20  )
    21  
    22  const (
    23  	SensorNilError = "sensorSpec == nil??"
    24  )
    25  
    26  type SensorJetstream struct {
    27  	*eventbusjetstreambase.Jetstream
    28  
    29  	sensorName    string
    30  	sensorSpec    *v1alpha1.Sensor
    31  	keyValueStore nats.KeyValue
    32  }
    33  
    34  func NewSensorJetstream(url string, sensorSpec *v1alpha1.Sensor, streamConfig string, auth *eventbuscommon.Auth, logger *zap.SugaredLogger) (*SensorJetstream, error) {
    35  	if sensorSpec == nil {
    36  		errStr := SensorNilError
    37  		logger.Errorf(errStr)
    38  		return nil, fmt.Errorf(errStr)
    39  	}
    40  
    41  	baseJetstream, err := eventbusjetstreambase.NewJetstream(url, streamConfig, auth, logger)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  	return &SensorJetstream{
    46  		baseJetstream,
    47  		sensorSpec.Name,
    48  		sensorSpec,
    49  		nil}, nil
    50  }
    51  
    52  func (stream *SensorJetstream) Initialize() error {
    53  	err := stream.Init() // member of jetstreambase.Jetstream
    54  	if err != nil {
    55  		return err
    56  	}
    57  
    58  	// see if there's an existing one
    59  	stream.keyValueStore, _ = stream.MgmtConnection.JSContext.KeyValue(stream.sensorName)
    60  	if stream.keyValueStore == nil {
    61  		// create Key/Value store for this Sensor (seems to be okay to call this if it already exists)
    62  		stream.keyValueStore, err = stream.MgmtConnection.JSContext.CreateKeyValue(&nats.KeyValueConfig{Bucket: stream.sensorName})
    63  		if err != nil {
    64  			errStr := fmt.Sprintf("failed to Create Key/Value Store for sensor %s, err: %v", stream.sensorName, err)
    65  			stream.Logger.Error(errStr)
    66  			return err
    67  		}
    68  	} else {
    69  		stream.Logger.Infof("found existing K/V store for sensor %s, using that", stream.sensorName)
    70  	}
    71  	stream.Logger.Infof("successfully created/located K/V store for sensor %s", stream.sensorName)
    72  
    73  	// Here we can take the sensor specification and clean up the K/V store so as to remove any old
    74  	// Triggers for this Sensor that no longer exist and any old Dependencies (and also Drain any corresponding Connections)
    75  	err = stream.setStateToSpec(stream.sensorSpec)
    76  	return err
    77  }
    78  
    79  func (stream *SensorJetstream) Connect(ctx context.Context, triggerName string, dependencyExpression string, deps []eventbuscommon.Dependency, atLeastOnce bool) (eventbuscommon.TriggerConnection, error) {
    80  	conn, err := stream.MakeConnection()
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  
    85  	return NewJetstreamTriggerConn(conn, stream.sensorName, triggerName, dependencyExpression, deps)
    86  }
    87  
    88  // Update the K/V store to reflect the current Spec:
    89  //  1. save the current spec, including list of triggers, list of dependencies and how they're defined, and trigger expressions
    90  //  2. selectively purge dependencies from the K/V store if either the Trigger no longer exists,
    91  //     the dependency definition has changed, or the trigger expression has changed
    92  //  3. for each dependency purged, delete the associated consumer so no new data is sent there
    93  func (stream *SensorJetstream) setStateToSpec(sensorSpec *v1alpha1.Sensor) error {
    94  	log := stream.Logger
    95  	if sensorSpec == nil {
    96  		errStr := SensorNilError
    97  		log.Error(errStr)
    98  		return fmt.Errorf(errStr)
    99  	}
   100  
   101  	log.Infof("Comparing previous Spec stored in k/v store for sensor %s to new Spec", sensorSpec.Name)
   102  
   103  	changedDeps, removedDeps, validDeps, err := stream.getChangedDeps(sensorSpec)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	log.Infof("Comparison of previous dependencies definitions to current: changed=%v, removed=%v, still valid=%v", changedDeps, removedDeps, validDeps)
   108  
   109  	changedTriggers, removedTriggers, validTriggers, err := stream.getChangedTriggers(sensorSpec) // this looks at the list of triggers as well as the dependency expression for each trigger
   110  	if err != nil {
   111  		return err
   112  	}
   113  	log.Infof("Comparison of previous trigger list to current: changed=%v, removed=%v, still valid=%v", changedTriggers, removedTriggers, validTriggers)
   114  
   115  	// for all valid triggers, determine if changedDeps or removedDeps requires them to be deleted
   116  	changedPlusRemovedDeps := make([]string, 0, len(changedDeps)+len(removedDeps))
   117  	changedPlusRemovedDeps = append(changedPlusRemovedDeps, changedDeps...)
   118  	changedPlusRemovedDeps = append(changedPlusRemovedDeps, removedDeps...)
   119  	for _, triggerName := range validTriggers {
   120  		_ = stream.purgeSelectedDepsForTrigger(triggerName, changedPlusRemovedDeps)
   121  	}
   122  
   123  	// for all changedTriggers (which includes modified and deleted), purge their dependencies
   124  	changedPlusRemovedTriggers := make([]string, 0, len(changedTriggers)+len(removedTriggers))
   125  	changedPlusRemovedTriggers = append(changedPlusRemovedTriggers, changedTriggers...)
   126  	changedPlusRemovedTriggers = append(changedPlusRemovedTriggers, removedTriggers...)
   127  	for _, triggerName := range changedPlusRemovedTriggers {
   128  		_ = stream.purgeAllDepsForTrigger(triggerName)
   129  	}
   130  
   131  	// save new spec
   132  	err = stream.saveSpec(sensorSpec, removedTriggers)
   133  	if err != nil {
   134  		return err
   135  	}
   136  
   137  	return nil
   138  }
   139  
   140  // purging dependency means both purging it from the K/V store as well as deleting the associated Consumer so no more messages are sent to it
   141  func (stream *SensorJetstream) purgeDependency(triggerName string, depName string) error {
   142  	// purge from Key/Value store first
   143  	key := getDependencyKey(triggerName, depName)
   144  	durableName := getDurableName(stream.sensorName, triggerName, depName)
   145  	stream.Logger.Debugf("purging dependency, including 1) key %s from the K/V store, and 2) durable consumer %s", key, durableName)
   146  	err := stream.keyValueStore.Delete(key)
   147  	if err != nil && err != nats.ErrKeyNotFound { // sometimes we call this on a trigger/dependency combination not sure if it actually exists or not, so
   148  		// don't need to worry about case of it not existing
   149  		stream.Logger.Error(err)
   150  		return err
   151  	}
   152  	// then delete consumer
   153  	stream.Logger.Debugf("durable name for sensor='%s', trigger='%s', dep='%s': '%s'", stream.sensorName, triggerName, depName, durableName)
   154  
   155  	_ = stream.MgmtConnection.JSContext.DeleteConsumer("default", durableName) // sometimes we call this on a trigger/dependency combination not sure if it actually exists or not, so
   156  	// don't need to worry about case of it not existing
   157  
   158  	return nil
   159  }
   160  
   161  func (stream *SensorJetstream) saveSpec(sensorSpec *v1alpha1.Sensor, removedTriggers []string) error {
   162  	// remove the old triggers from the K/V store
   163  	for _, trigger := range removedTriggers {
   164  		key := getTriggerExpressionKey(trigger)
   165  		err := stream.keyValueStore.Delete(key)
   166  		if err != nil {
   167  			errStr := fmt.Sprintf("error deleting key %s: %v", key, err)
   168  			stream.Logger.Error(errStr)
   169  			return fmt.Errorf(errStr)
   170  		}
   171  		stream.Logger.Debugf("successfully removed Trigger expression at key %s", key)
   172  	}
   173  
   174  	// save the dependency definitions
   175  	depMap := make(DependencyDefinitionValue)
   176  	for _, dep := range sensorSpec.Spec.Dependencies {
   177  		hash, err := hashstructure.Hash(dep, hashstructure.FormatV2, nil)
   178  		if err != nil {
   179  			errStr := fmt.Sprintf("failed to hash dependency %+v", dep)
   180  			stream.Logger.Errorf(errStr)
   181  			err = fmt.Errorf(errStr)
   182  			return err
   183  		}
   184  		depMap[dep.Name] = hash
   185  	}
   186  	err := stream.storeDependencyDefinitions(depMap)
   187  	if err != nil {
   188  		return err
   189  	}
   190  
   191  	// save the list of Triggers
   192  	triggerList := make(TriggerValue, len(sensorSpec.Spec.Triggers))
   193  	for i, trigger := range sensorSpec.Spec.Triggers {
   194  		triggerList[i] = trigger.Template.Name
   195  	}
   196  	err = stream.storeTriggerList(triggerList)
   197  	if err != nil {
   198  		return err
   199  	}
   200  
   201  	// for each trigger, save its expression
   202  	for _, trigger := range sensorSpec.Spec.Triggers {
   203  		err := stream.storeTriggerExpression(trigger.Template.Name, trigger.Template.Conditions)
   204  		if err != nil {
   205  			return err
   206  		}
   207  	}
   208  
   209  	return nil
   210  }
   211  
   212  func (stream *SensorJetstream) getChangedTriggers(sensorSpec *v1alpha1.Sensor) (changedTriggers []string, removedTriggers []string, validTriggers []string, err error) {
   213  	if sensorSpec == nil {
   214  		errStr := SensorNilError
   215  		stream.Logger.Errorf(errStr)
   216  		err = fmt.Errorf(errStr)
   217  		return nil, nil, nil, err
   218  	}
   219  
   220  	mappedSpecTriggers := make(map[string]v1alpha1.Trigger, len(sensorSpec.Spec.Triggers))
   221  	for _, trigger := range sensorSpec.Spec.Triggers {
   222  		mappedSpecTriggers[trigger.Template.Name] = trigger
   223  	}
   224  	storedTriggers, err := stream.getTriggerList()
   225  	if err != nil {
   226  		return nil, nil, nil, err
   227  	}
   228  	for _, triggerName := range storedTriggers {
   229  		currTrigger, found := mappedSpecTriggers[triggerName]
   230  		if !found {
   231  			removedTriggers = append(removedTriggers, triggerName)
   232  		} else {
   233  			// is the trigger expression the same or different?
   234  			storedExpression, err := stream.getTriggerExpression(triggerName)
   235  			if err != nil {
   236  				return nil, nil, nil, err
   237  			}
   238  			if storedExpression == currTrigger.Template.Conditions {
   239  				validTriggers = append(validTriggers, triggerName)
   240  			} else {
   241  				changedTriggers = append(changedTriggers, triggerName)
   242  			}
   243  		}
   244  	}
   245  	return changedTriggers, removedTriggers, validTriggers, nil
   246  }
   247  
   248  func (stream *SensorJetstream) getChangedDeps(sensorSpec *v1alpha1.Sensor) (changedDeps []string, removedDeps []string, validDeps []string, err error) {
   249  	if sensorSpec == nil {
   250  		errStr := SensorNilError
   251  		stream.Logger.Errorf(errStr)
   252  		err = fmt.Errorf(errStr)
   253  		return nil, nil, nil, err
   254  	}
   255  
   256  	specDependencies := sensorSpec.Spec.Dependencies
   257  	mappedSpecDependencies := make(map[string]v1alpha1.EventDependency, len(specDependencies))
   258  	for _, dep := range specDependencies {
   259  		mappedSpecDependencies[dep.Name] = dep
   260  	}
   261  	storedDependencies, err := stream.getDependencyDefinitions()
   262  	if err != nil {
   263  		return nil, nil, nil, err
   264  	}
   265  	for depName, hashedDep := range storedDependencies {
   266  		currDep, found := mappedSpecDependencies[depName]
   267  		if !found {
   268  			removedDeps = append(removedDeps, depName)
   269  		} else {
   270  			// is the dependency definition the same or different?
   271  			hash, err := hashstructure.Hash(currDep, hashstructure.FormatV2, nil)
   272  			if err != nil {
   273  				errStr := fmt.Sprintf("failed to hash dependency %+v", currDep)
   274  				stream.Logger.Errorf(errStr)
   275  				err = fmt.Errorf(errStr)
   276  				return nil, nil, nil, err
   277  			}
   278  			if hash == hashedDep {
   279  				validDeps = append(validDeps, depName)
   280  			} else {
   281  				changedDeps = append(changedDeps, depName)
   282  			}
   283  		}
   284  	}
   285  
   286  	return changedDeps, removedDeps, validDeps, nil
   287  }
   288  
   289  func (stream *SensorJetstream) getDependencyDefinitions() (DependencyDefinitionValue, error) {
   290  	depDefs, err := stream.keyValueStore.Get(DependencyDefsKey)
   291  	if err != nil {
   292  		if err == nats.ErrKeyNotFound {
   293  			return make(DependencyDefinitionValue), nil
   294  		}
   295  		errStr := fmt.Sprintf("error getting key %s: %v", DependencyDefsKey, err)
   296  		stream.Logger.Error(errStr)
   297  		return nil, fmt.Errorf(errStr)
   298  	}
   299  	stream.Logger.Debugf("Value of key %s: %s", DependencyDefsKey, string(depDefs.Value()))
   300  
   301  	depDefMap := DependencyDefinitionValue{}
   302  	err = json.Unmarshal(depDefs.Value(), &depDefMap)
   303  	if err != nil {
   304  		errStr := fmt.Sprintf("error unmarshalling value %s of key %s: %v", string(depDefs.Value()), DependencyDefsKey, err)
   305  		stream.Logger.Error(errStr)
   306  		return nil, fmt.Errorf(errStr)
   307  	}
   308  
   309  	return depDefMap, nil
   310  }
   311  
   312  func (stream *SensorJetstream) storeDependencyDefinitions(depDef DependencyDefinitionValue) error {
   313  	bytes, err := json.Marshal(depDef)
   314  	if err != nil {
   315  		errStr := fmt.Sprintf("error marshalling %+v: %v", depDef, err)
   316  		stream.Logger.Error(errStr)
   317  		return fmt.Errorf(errStr)
   318  	}
   319  	_, err = stream.keyValueStore.Put(DependencyDefsKey, bytes)
   320  	if err != nil {
   321  		errStr := fmt.Sprintf("error storing %s under key %s: %v", string(bytes), DependencyDefsKey, err)
   322  		stream.Logger.Error(errStr)
   323  		return fmt.Errorf(errStr)
   324  	}
   325  	stream.Logger.Debugf("successfully stored dependency definition under key %s: %s", DependencyDefsKey, string(bytes))
   326  	return nil
   327  }
   328  
   329  func (stream *SensorJetstream) getTriggerList() (TriggerValue, error) {
   330  	triggerListJson, err := stream.keyValueStore.Get(TriggersKey)
   331  	if err != nil {
   332  		if err == nats.ErrKeyNotFound {
   333  			return make(TriggerValue, 0), nil
   334  		}
   335  		errStr := fmt.Sprintf("error getting key %s: %v", TriggersKey, err)
   336  		stream.Logger.Error(errStr)
   337  		return nil, fmt.Errorf(errStr)
   338  	}
   339  	stream.Logger.Debugf("Value of key %s: %s", TriggersKey, string(triggerListJson.Value()))
   340  
   341  	triggerList := TriggerValue{}
   342  	err = json.Unmarshal(triggerListJson.Value(), &triggerList)
   343  	if err != nil {
   344  		errStr := fmt.Sprintf("error unmarshalling value %s of key %s: %v", string(triggerListJson.Value()), TriggersKey, err)
   345  		stream.Logger.Error(errStr)
   346  		return nil, fmt.Errorf(errStr)
   347  	}
   348  
   349  	return triggerList, nil
   350  }
   351  
   352  func (stream *SensorJetstream) storeTriggerList(triggerList TriggerValue) error {
   353  	bytes, err := json.Marshal(triggerList)
   354  	if err != nil {
   355  		errStr := fmt.Sprintf("error marshalling %+v: %v", triggerList, err)
   356  		stream.Logger.Error(errStr)
   357  		return fmt.Errorf(errStr)
   358  	}
   359  	_, err = stream.keyValueStore.Put(TriggersKey, bytes)
   360  	if err != nil {
   361  		errStr := fmt.Sprintf("error storing %s under key %s: %v", string(bytes), TriggersKey, err)
   362  		stream.Logger.Error(errStr)
   363  		return fmt.Errorf(errStr)
   364  	}
   365  	stream.Logger.Debugf("successfully stored trigger list under key %s: %s", TriggersKey, string(bytes))
   366  	return nil
   367  }
   368  
   369  func (stream *SensorJetstream) getTriggerExpression(triggerName string) (string, error) {
   370  	key := getTriggerExpressionKey(triggerName)
   371  	expr, err := stream.keyValueStore.Get(key)
   372  	if err != nil {
   373  		if err == nats.ErrKeyNotFound {
   374  			return "", nil
   375  		}
   376  		errStr := fmt.Sprintf("error getting key %s: %v", key, err)
   377  		stream.Logger.Error(errStr)
   378  		return "", fmt.Errorf(errStr)
   379  	}
   380  	stream.Logger.Debugf("Value of key %s: %s", key, string(expr.Value()))
   381  
   382  	return string(expr.Value()), nil
   383  }
   384  
   385  func (stream *SensorJetstream) storeTriggerExpression(triggerName string, conditionExpression string) error {
   386  	key := getTriggerExpressionKey(triggerName)
   387  	_, err := stream.keyValueStore.PutString(key, conditionExpression)
   388  	if err != nil {
   389  		errStr := fmt.Sprintf("error storing %s under key %s: %v", conditionExpression, key, err)
   390  		stream.Logger.Error(errStr)
   391  		return fmt.Errorf(errStr)
   392  	}
   393  	stream.Logger.Debugf("successfully stored trigger expression under key %s: %s", key, conditionExpression)
   394  	return nil
   395  }
   396  
   397  func (stream *SensorJetstream) purgeSelectedDepsForTrigger(triggerName string, deps []string) error {
   398  	stream.Logger.Debugf("purging selected dependencies %v for trigger %s", deps, triggerName)
   399  	for _, dep := range deps {
   400  		err := stream.purgeDependency(triggerName, dep) // this will attempt a delete even if no such key exists for a particular trigger, but that's okay
   401  		if err != nil {
   402  			return err
   403  		}
   404  	}
   405  	return nil
   406  }
   407  
   408  func (stream *SensorJetstream) purgeAllDepsForTrigger(triggerName string) error {
   409  	stream.Logger.Debugf("purging all dependencies for trigger %s", triggerName)
   410  	// use the stored trigger expression to determine which dependencies need to be purged
   411  	storedExpression, err := stream.getTriggerExpression(triggerName)
   412  	if err != nil {
   413  		return err
   414  	}
   415  
   416  	// get the individual dependencies by removing the special characters
   417  	modExpr := strings.ReplaceAll(storedExpression, "&&", " ")
   418  	modExpr = strings.ReplaceAll(modExpr, "||", " ")
   419  	modExpr = strings.ReplaceAll(modExpr, "(", " ")
   420  	modExpr = strings.ReplaceAll(modExpr, ")", " ")
   421  	deps := strings.FieldsFunc(modExpr, func(r rune) bool { return r == ' ' })
   422  
   423  	for _, dep := range deps {
   424  		err := stream.purgeDependency(triggerName, dep)
   425  		if err != nil {
   426  			return err
   427  		}
   428  	}
   429  	return nil
   430  }
   431  
   432  // ////////////////////////////////////////////////////////////////////////////////////////////////////
   433  // These are the Keys and methods to derive Keys for our K/V store
   434  var (
   435  	TriggersKey       = "Triggers"
   436  	DependencyDefsKey = "Deps"
   437  )
   438  
   439  func getDependencyKey(triggerName string, depName string) string {
   440  	return fmt.Sprintf("%s/%s", triggerName, depName)
   441  }
   442  
   443  func getTriggerExpressionKey(triggerName string) string {
   444  	return fmt.Sprintf("%s/Expression", triggerName)
   445  }
   446  
   447  // ////////////////////////////////////////////////////////////////////////////////////////////////////
   448  // These are the structs representing Values in our K/V store
   449  type DependencyDefinitionValue map[string]uint64 // value for DependencyDefsKey
   450  type TriggerValue []string                       // value for TriggersKey
   451  
   452  // value for getDependencyKey()
   453  type MsgInfo struct {
   454  	StreamSeq   uint64
   455  	ConsumerSeq uint64
   456  	Timestamp   time.Time
   457  	Event       *cloudevents.Event
   458  }
   459  
   460  func getDurableName(sensorName string, triggerName string, depName string) string {
   461  	hashKey := fmt.Sprintf("%s-%s-%s", sensorName, triggerName, depName)
   462  	hashVal := common.Hasher(hashKey)
   463  	return fmt.Sprintf("group-%s", hashVal)
   464  }