diff --git a/.travis.yml b/.travis.yml index d967b3a..f648970 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: go go: - - 1.9.2 - - 1.10.3 + - 1.11.5 - tip matrix: diff --git a/Makefile b/Makefile index 07ebba8..9c13559 100644 --- a/Makefile +++ b/Makefile @@ -21,3 +21,7 @@ test: .PHONY: vet vet: go vet ${GOPACKAGES} + +.PHONY: dofmt +dofmt: + gofmt -l -s -w ${GOFILES} \ No newline at end of file diff --git a/README.md b/README.md index 3272137..b071850 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,13 @@ of keys based on the gin attribute syntax and the comparison to literals or othe keys. These rules can be nested inside of AND, OR and NOT rules to enable the expression of complex relationships of values in etcd and the actions to be triggered when a set of conditions has been met. The engine watches etcd for updates and crawls the data tree -at configurable intervals so that changes that occurred beyond the watch time scope are picked -up and actions triggered by watches that initially failed can be retried without being lost. -This library makes use of the IBM-Cloud/go-etcd-lock library to enable concurrent monitoring -by multiple application instances without collisions--the first client to obtain the lock -processes the change while the others quickly fail to acquire the lock and move on. A trigger -callback function should update the model if the action is successful so it is not retriggered. -Recurring actions, such as continuous polling, can be implemented with rules that reference -nodes with TTLs such that the expiration of a node triggers a rule and the callback adds back -a node with the same key and TTL. +at configurable intervals. This library makes use of the IBM-Cloud/go-etcd-lock library +to enable concurrent monitoring by multiple application instances without collisions--the +first client to obtain the lock processes the change while the others quickly fail to acquire +the lock and move on. A trigger callback function should update the model if the action +is successful so it is not retriggered. Recurring actions, such as continuous polling, +can be implemented with rules that reference nodes with TTLs such that the expiration of +a node triggers a rule and the callback adds back a node with the same key and TTL. Import ------ diff --git a/rules/dynamic_rule.go b/rules/dynamic_rule.go index fe4d1b8..e7946f9 100644 --- a/rules/dynamic_rule.go +++ b/rules/dynamic_rule.go @@ -102,7 +102,7 @@ func processBooleanMap(keys []string, parent map[string]bool, value bool, proc f } func (krp *dynamicRule) getLeafRepresentationPatternMap() map[string][]string { - out := map[string][]string{krp.rep: []string{}} + out := map[string][]string{krp.rep: {}} for _, pattern := range krp.patterns { out[krp.rep] = append(out[krp.rep], pattern) } diff --git a/rules/dynamic_rule_test.go b/rules/dynamic_rule_test.go index 5fff794..f9153fa 100644 --- a/rules/dynamic_rule_test.go +++ b/rules/dynamic_rule_test.go @@ -246,7 +246,7 @@ func newExpandedEqualsLiteralRule(key string, value *string) DynamicRule { if err != nil { return nil } - rules, _ := rule.Expand(map[string][]string{"region": []string{"us"}}) + rules, _ := rule.Expand(map[string][]string{"region": {"us"}}) return rules[0] } @@ -255,7 +255,7 @@ func newExpandedEqualsRule(keys []string) DynamicRule { if err != nil { return nil } - rules, _ := rule.Expand(map[string][]string{"region": []string{"us"}}) + rules, _ := rule.Expand(map[string][]string{"region": {"us"}}) return rules[0] } @@ -358,7 +358,7 @@ func TestRuleSatisfied(t *testing.T) { true, map[string]string{ "/emea/branch/parent/fef460923d2248bf99da87f8d4b1c363/child/child-home-fef460923d2248bf99da87f8d4b1c363-c1/attributes/location/value": "home", - "/updater/emea/child/location/enabled": "true", + "/updater/emea/child/location/enabled": "true", }, }, { @@ -370,7 +370,7 @@ func TestRuleSatisfied(t *testing.T) { false, map[string]string{ "/emea/branch/parent/fef460923d2248bf99da87f8d4b1c363/child/child-home-fef460923d2248bf99da87f8d4b1c363-c1/attributes/location/value": "home", - "/updater/emea/child/location/enabled": "true", + "/updater/emea/child/location/enabled": "true", }, }, } @@ -465,53 +465,53 @@ func TestGetLeafRepresentationPatternMap(t *testing.T) { nil, func() (DynamicRule, error) { return NewEqualsLiteralRule("/:region/test", nil) }, map[string][]string{ - "/:region/test = ": []string{"/:region/test"}, + "/:region/test = ": {"/:region/test"}, }, }, { nil, func() (DynamicRule, error) { return NewEqualsLiteralRule("/:region/test2", sTP("value")) }, map[string][]string{ - "/:region/test2 = \"value\"": []string{"/:region/test2"}, + "/:region/test2 = \"value\"": {"/:region/test2"}, }, }, { func() DynamicRule { return NewAndRule(rules[0], rules[1]) }, nil, map[string][]string{ - "/:region/test = ": []string{"/:region/test"}, - "/:region/test2 = \"value\"": []string{"/:region/test2"}, + "/:region/test = ": {"/:region/test"}, + "/:region/test2 = \"value\"": {"/:region/test2"}, }, }, { func() DynamicRule { return NewOrRule(rules[0], rules[1]) }, nil, map[string][]string{ - "/:region/test = ": []string{"/:region/test"}, - "/:region/test2 = \"value\"": []string{"/:region/test2"}, + "/:region/test = ": {"/:region/test"}, + "/:region/test2 = \"value\"": {"/:region/test2"}, }, }, { func() DynamicRule { return NewOrRule(rules[2], rules[3]) }, nil, map[string][]string{ - "/:region/test = ": []string{"/:region/test"}, - "/:region/test2 = \"value\"": []string{"/:region/test2"}, + "/:region/test = ": {"/:region/test"}, + "/:region/test2 = \"value\"": {"/:region/test2"}, }, }, { func() DynamicRule { return NewNotRule(rules[4]) }, nil, map[string][]string{ - "/:region/test = ": []string{"/:region/test"}, - "/:region/test2 = \"value\"": []string{"/:region/test2"}, + "/:region/test = ": {"/:region/test"}, + "/:region/test2 = \"value\"": {"/:region/test2"}, }, }, { nil, func() (DynamicRule, error) { return NewEqualsRule([]string{"/:region/test", "/:region/test2"}) }, map[string][]string{ - "/:region/test = /:region/test2": []string{"/:region/test", "/:region/test2"}, + "/:region/test = /:region/test2": {"/:region/test", "/:region/test2"}, }, }, } diff --git a/rules/engine.go b/rules/engine.go index 97b456d..f926e7f 100644 --- a/rules/engine.go +++ b/rules/engine.go @@ -10,7 +10,7 @@ import ( "golang.org/x/net/context" ) -type stopable interface { +type stoppable interface { stop() isStopped() bool } @@ -35,9 +35,9 @@ type baseEngine struct { ruleLockTTLs map[int]int ruleMgr ruleManager stopped uint32 - crawlers []stopable - watchers []stopable - workers []stopable + crawlers []stoppable + watchers []stoppable + workers []stoppable } type channelCloser func() @@ -141,9 +141,9 @@ func (e *baseEngine) Shutdown(ctx context.Context) error { func (e *baseEngine) stop() { e.logger.Debug("Stopping crawlers") - stopStopables(e.crawlers) + stopstoppables(e.crawlers) e.logger.Debug("Stopping watchers") - stopStopables(e.watchers) + stopstoppables(e.watchers) e.logger.Debug("Stopping workers") for _, worker := range e.workers { worker.stop() @@ -153,19 +153,19 @@ func (e *baseEngine) stop() { // Ensure workers are stopped; the "stop" method is called again, but // that is idempotent. The workers' "stop" method must be called before // the channel is closed in order to avoid nil pointer dereference panics. - stopStopables(e.workers) + stopstoppables(e.workers) atomicSet(&e.stopped, true) e.logger.Info("Engine stopped") } -func stopStopables(stopables []stopable) { - for _, s := range stopables { +func stopstoppables(stoppables []stoppable) { + for _, s := range stoppables { s.stop() } allStopped := false for !allStopped { allStopped = true - for _, s := range stopables { + for _, s := range stoppables { allStopped = allStopped && s.isStopped() } } diff --git a/rules/matcher_test.go b/rules/matcher_test.go index afc2a1d..3998f74 100644 --- a/rules/matcher_test.go +++ b/rules/matcher_test.go @@ -32,7 +32,7 @@ func TestBasic(t *testing.T) { t.Fail() } prefixes := km.getPrefixesWithConstraints(map[string]constraint{ - "name": constraint{ + "name": { prefix: "xy", chars: [][]rune{{'a', 'b'}, {'a', 'b'}}, }, diff --git a/rules/options_test.go b/rules/options_test.go index bceaef5..2f171e6 100644 --- a/rules/options_test.go +++ b/rules/options_test.go @@ -24,10 +24,10 @@ func TestEngineOptions(t *testing.T) { assert.Equal(t, 1, opts.syncDelay) opts = makeEngineOptions(EngineConcurrency(10)) assert.Equal(t, 10, opts.concurrency) - keyExp1 := KeyExpansion(map[string][]string{"key1": []string{"val1"}, "key2": []string{"val2"}}) - keyExp2 := KeyExpansion(map[string][]string{"key2": []string{"val3"}, "key3": []string{"val4"}}) + keyExp1 := KeyExpansion(map[string][]string{"key1": {"val1"}, "key2": {"val2"}}) + keyExp2 := KeyExpansion(map[string][]string{"key2": {"val3"}, "key3": {"val4"}}) opts = makeEngineOptions(keyExp1, keyExp2) - assert.Equal(t, map[string][]string{"key1": []string{"val1"}, "key2": []string{"val3"}, "key3": []string{"val4"}}, opts.keyExpansion) + assert.Equal(t, map[string][]string{"key1": {"val1"}, "key2": {"val3"}, "key3": {"val4"}}, opts.keyExpansion) opts = makeEngineOptions(EngineSyncDelay(10)) assert.Equal(t, 10, opts.syncDelay) opts = makeEngineOptions(EngineWatchTimeout(3)) diff --git a/rules/rule_manager_test.go b/rules/rule_manager_test.go index d6c6ccc..c1526d0 100644 --- a/rules/rule_manager_test.go +++ b/rules/rule_manager_test.go @@ -51,7 +51,7 @@ func TestCombineRuleData(t *testing.T) { expectedData []string }{ { - [][]string{[]string{"/a/b/c", "/x/y/z"}, []string{"/a/b/c"}}, + [][]string{{"/a/b/c", "/x/y/z"}, {"/a/b/c"}}, []string{"/a/b/c", "/x/y/z"}, }, } diff --git a/rules/static_rule_test.go b/rules/static_rule_test.go index fd1240c..4bfdf6f 100644 --- a/rules/static_rule_test.go +++ b/rules/static_rule_test.go @@ -614,7 +614,7 @@ func TestCompoundQSatisfiable(t *testing.T) { key1 := "key1" rules := map[string]staticRule{} testCases := []*srtc{ - &srtc{ + { name: "dummyTrue", rule: func() staticRule { return &dummyRule{ @@ -626,7 +626,7 @@ func TestCompoundQSatisfiable(t *testing.T) { value: &value, qState: qTrue, }, - &srtc{ + { name: "dummyFalse", rule: func() staticRule { return &dummyRule{ @@ -638,7 +638,7 @@ func TestCompoundQSatisfiable(t *testing.T) { value: &value, qState: qFalse, }, - &srtc{ + { name: "dummyMaybe", rule: func() staticRule { return &dummyRule{ @@ -650,7 +650,7 @@ func TestCompoundQSatisfiable(t *testing.T) { qState: qMaybe, }, - &srtc{ + { name: "dummyNone", rule: func() staticRule { return &dummyRule{ @@ -661,158 +661,158 @@ func TestCompoundQSatisfiable(t *testing.T) { }, qState: qNone, }, - &srtc{ + { name: "TrueAndTrue", rule: func() staticRule { return asrfn(rules["dummyTrue"], rules["dummyTrue"]) }, qState: qTrue, }, - &srtc{ + { name: "TrueAndFalse", rule: func() staticRule { return asrfn(rules["dummyTrue"], rules["dummyFalse"]) }, qState: qFalse, }, - &srtc{ + { name: "TrueAndMaybe", rule: func() staticRule { return asrfn(rules["dummyTrue"], rules["dummyMaybe"]) }, qState: qTrue, }, - &srtc{ + { name: "TrueAndNone", rule: func() staticRule { return asrfn(rules["dummyTrue"], rules["dummyMaybe"]) }, qState: qTrue, }, - &srtc{ + { name: "FalseAndFalse", rule: func() staticRule { return asrfn(rules["dummyFalse"], rules["dummyFalse"]) }, qState: qFalse, }, - &srtc{ + { name: "FalseAndMaybe", rule: func() staticRule { return asrfn(rules["dummyFalse"], rules["dummyMaybe"]) }, qState: qFalse, }, - &srtc{ + { name: "FalseAndNone", rule: func() staticRule { return asrfn(rules["dummyFalse"], rules["dummyNone"]) }, qState: qFalse, }, - &srtc{ + { name: "MaybeAndMaybe", rule: func() staticRule { return asrfn(rules["dummyMaybe"], rules["dummyMaybe"]) }, qState: qMaybe, }, - &srtc{ + { name: "MaybeAndNone", rule: func() staticRule { return asrfn(rules["dummyMaybe"], rules["dummyNone"]) }, qState: qMaybe, }, - &srtc{ + { name: "TrueOrTrue", rule: func() staticRule { return osrfn(rules["dummyTrue"], rules["dummyTrue"]) }, qState: qTrue, }, - &srtc{ + { name: "TrueOrFalse", rule: func() staticRule { return osrfn(rules["dummyTrue"], rules["dummyFalse"]) }, qState: qTrue, }, - &srtc{ + { name: "TrueOrMaybe", rule: func() staticRule { return osrfn(rules["dummyTrue"], rules["dummyMaybe"]) }, qState: qTrue, }, - &srtc{ + { name: "TrueOrNone", rule: func() staticRule { return osrfn(rules["dummyTrue"], rules["dummyMaybe"]) }, qState: qTrue, }, - &srtc{ + { name: "FalseOrFalse", rule: func() staticRule { return osrfn(rules["dummyFalse"], rules["dummyFalse"]) }, qState: qFalse, }, - &srtc{ + { name: "FalseOrMaybe", rule: func() staticRule { return osrfn(rules["dummyFalse"], rules["dummyMaybe"]) }, qState: qMaybe, }, - &srtc{ + { name: "FalseOrNone", rule: func() staticRule { return osrfn(rules["dummyFalse"], rules["dummyNone"]) }, qState: qFalse, }, - &srtc{ + { name: "MaybeOrMaybe", rule: func() staticRule { return osrfn(rules["dummyMaybe"], rules["dummyMaybe"]) }, qState: qMaybe, }, - &srtc{ + { name: "MaybeOrNone", rule: func() staticRule { return osrfn(rules["dummyMaybe"], rules["dummyNone"]) }, qState: qMaybe, }, - &srtc{ + { name: "NotTrue", rule: func() staticRule { return ¬StaticRule{nested: rules["dummyTrue"]} }, qState: qFalse, }, - &srtc{ + { name: "NotFalse", rule: func() staticRule { return ¬StaticRule{nested: rules["dummyFalse"]} }, qState: qTrue, }, - &srtc{ + { name: "NotMaybe", rule: func() staticRule { return ¬StaticRule{nested: rules["dummyMaybe"]} }, qState: qMaybe, }, - &srtc{ + { name: "NotNone", rule: func() staticRule { return ¬StaticRule{nested: rules["dummyNone"]} }, qState: qNone, }, - &srtc{ + { name: "Not(TrueOrFalse) <=> NotTrueAndNotFalse", rule: func() staticRule { return ¬StaticRule{nested: rules["TrueOrFalse"]} }, qState: qFalse, }, - &srtc{ + { name: "NotTrueAndNotFalse <=> Not(TrueOrFalse)", rule: func() staticRule { return asrfn(¬StaticRule{nested: rules["dummyTrue"]}, ¬StaticRule{nested: rules["dummyFalse"]}) }, qState: qFalse, }, - &srtc{ + { name: "Not(FalseOrFalse) <=> NotFalseAndNotFalse", rule: func() staticRule { return ¬StaticRule{nested: rules["FalseOrFalse"]} }, qState: qTrue, }, - &srtc{ + { name: "NotFalseAndNotFalse <=> Not(FalseOrFalse)", rule: func() staticRule { return asrfn(¬StaticRule{nested: rules["dummyFalse"]}, ¬StaticRule{nested: rules["dummyFalse"]}) }, qState: qTrue, }, - &srtc{ + { name: "Not(TrueAndFalse) <=> NotTrueOrNotFalse", rule: func() staticRule { return ¬StaticRule{nested: rules["TrueAndFalse"]} }, qState: qTrue, }, - &srtc{ + { name: "NotTruOrNotFalse <=> Not(TrueAndFalse)", rule: func() staticRule { return osrfn(¬StaticRule{nested: rules["dummyTrue"]}, ¬StaticRule{nested: rules["dummyFalse"]}) }, qState: qTrue, }, - &srtc{ + { name: "Not(TrueAndTrue) <=> NotTrueOrNotTrue", rule: func() staticRule { return ¬StaticRule{nested: rules["TrueAndTrue"]} }, qState: qFalse, }, - &srtc{ + { name: "NotTrueOrNotTrue <=> Not(TrueOrTrue)", rule: func() staticRule { return asrfn(¬StaticRule{nested: rules["dummyTrue"]}, ¬StaticRule{nested: rules["dummyTrue"]})