diff --git a/pgraph/pgraph_test.go b/pgraph/pgraph_test.go index 14b5af82..4bbe369c 100644 --- a/pgraph/pgraph_test.go +++ b/pgraph/pgraph_test.go @@ -764,6 +764,18 @@ Loop: } } + // check meta parameters + for v1 := range g1.Adjacency { // for each vertex in g1 + for v2 := range g2.Adjacency { // does it match in g2 ? + s1, s2 := v1.Meta().Sema, v2.Meta().Sema + sort.Strings(s1) + sort.Strings(s2) + if !reflect.DeepEqual(s1, s2) { + return fmt.Errorf("vertex %s and vertex %s have different semaphores", v1.GetName(), v2.GetName()) + } + } + } + return nil // success! } @@ -805,7 +817,11 @@ func (ag *testGrouper) edgeMerge(e1, e2 *Edge) *Edge { func (g *Graph) fullPrint() (str string) { str += "\n" for v := range g.Adjacency { - str += fmt.Sprintf("* v: %v\n", v.GetName()) + if semas := v.Meta().Sema; len(semas) > 0 { + str += fmt.Sprintf("* v: %v; sema: %v\n", v.GetName(), semas) + } else { + str += fmt.Sprintf("* v: %v\n", v.GetName()) + } // TODO: add explicit grouping data? } for v1 := range g.Adjacency { diff --git a/pgraph/semaphore_test.go b/pgraph/semaphore_test.go new file mode 100644 index 00000000..5212ad9b --- /dev/null +++ b/pgraph/semaphore_test.go @@ -0,0 +1,93 @@ +// Mgmt +// Copyright (C) 2013-2017+ James Shubin and the project contributors +// Written by James Shubin and the project contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package pgraph + +import ( + "testing" + + "github.com/purpleidea/mgmt/resources" +) + +func NewNoopResTestSema(name string, semas []string) *NoopResTest { + obj := &NoopResTest{ + NoopRes: resources.NoopRes{ + BaseRes: resources.BaseRes{ + Name: name, + MetaParams: resources.MetaParams{ + AutoGroup: true, // always autogroup + Sema: semas, + }, + }, + }, + } + return obj +} + +func TestPgraphSemaphoreGrouping1(t *testing.T) { + g1 := NewGraph("g1") // original graph + { + a1 := NewVertex(NewNoopResTestSema("a1", []string{"s:1"})) + a2 := NewVertex(NewNoopResTestSema("a2", []string{"s:2"})) + a3 := NewVertex(NewNoopResTestSema("a3", []string{"s:3"})) + g1.AddVertex(a1) + g1.AddVertex(a2) + g1.AddVertex(a3) + } + g2 := NewGraph("g2") // expected result + { + a123 := NewVertex(NewNoopResTestSema("a1,a2,a3", []string{"s:1", "s:2", "s:3"})) + g2.AddVertex(a123) + } + runGraphCmp(t, g1, g2) +} + +func TestPgraphSemaphoreGrouping2(t *testing.T) { + g1 := NewGraph("g1") // original graph + { + a1 := NewVertex(NewNoopResTestSema("a1", []string{"s:10", "s:11"})) + a2 := NewVertex(NewNoopResTestSema("a2", []string{"s:2"})) + a3 := NewVertex(NewNoopResTestSema("a3", []string{"s:3"})) + g1.AddVertex(a1) + g1.AddVertex(a2) + g1.AddVertex(a3) + } + g2 := NewGraph("g2") // expected result + { + a123 := NewVertex(NewNoopResTestSema("a1,a2,a3", []string{"s:10", "s:11", "s:2", "s:3"})) + g2.AddVertex(a123) + } + runGraphCmp(t, g1, g2) +} + +func TestPgraphSemaphoreGrouping3(t *testing.T) { + g1 := NewGraph("g1") // original graph + { + a1 := NewVertex(NewNoopResTestSema("a1", []string{"s:1", "s:2"})) + a2 := NewVertex(NewNoopResTestSema("a2", []string{"s:2"})) + a3 := NewVertex(NewNoopResTestSema("a3", []string{"s:3"})) + g1.AddVertex(a1) + g1.AddVertex(a2) + g1.AddVertex(a3) + } + g2 := NewGraph("g2") // expected result + { + a123 := NewVertex(NewNoopResTestSema("a1,a2,a3", []string{"s:1", "s:2", "s:3"})) + g2.AddVertex(a123) + } + runGraphCmp(t, g1, g2) +} diff --git a/resources/resources.go b/resources/resources.go index aaf5e460..cd0aceee 100644 --- a/resources/resources.go +++ b/resources/resources.go @@ -35,6 +35,7 @@ import ( "github.com/purpleidea/mgmt/converger" "github.com/purpleidea/mgmt/event" "github.com/purpleidea/mgmt/prometheus" + "github.com/purpleidea/mgmt/util" errwrap "github.com/pkg/errors" "golang.org/x/time/rate" @@ -496,6 +497,12 @@ func (obj *BaseRes) GroupRes(res Res) error { return fmt.Errorf("the %v resource is already grouped", res) } + // merging two resources into one should yield the sum of their semas + if semas := res.Meta().Sema; len(semas) > 0 { + obj.Meta().Sema = append(obj.Meta().Sema, semas...) + obj.Meta().Sema = util.StrRemoveDuplicatesInList(obj.Meta().Sema) + } + obj.grouped = append(obj.grouped, res) res.SetGrouped(true) // i am contained _in_ a group return nil diff --git a/util/util.go b/util/util.go index d49cafcf..298edb36 100644 --- a/util/util.go +++ b/util/util.go @@ -57,7 +57,7 @@ func Uint64KeyFromStrInMap(needle string, haystack map[uint64]string) (uint64, b } // StrRemoveDuplicatesInList removes any duplicate values in the list. -// This is a possibly sub-optimal, O(n^2)? implementation. +// This implementation is possibly sub-optimal (O(n^2)?) but preserves ordering. func StrRemoveDuplicatesInList(list []string) []string { unique := []string{} for _, x := range list {