From dc30c8e49413c5830fc4c1a6e5b6d2b47b72d1eb Mon Sep 17 00:00:00 2001 From: Jason Yellick Date: Wed, 8 Feb 2017 23:38:41 -0500 Subject: [PATCH] [FAB-2142] Make new config comparable https://jira.hyperledger.org/browse/FAB-2142 The new configuration structure needs to be comparable in order to validate which portions of the incoming configuration have changed. This CR adds a set of utilities to do just that. Change-Id: I061288145e7d4ae257568043d152c087f1796957 Signed-off-by: Jason Yellick --- common/configtx/compare.go | 171 ++++++++++++++++++ common/configtx/compare_test.go | 305 ++++++++++++++++++++++++++++++++ 2 files changed, 476 insertions(+) create mode 100644 common/configtx/compare.go create mode 100644 common/configtx/compare_test.go diff --git a/common/configtx/compare.go b/common/configtx/compare.go new file mode 100644 index 00000000000..37619dfc0bd --- /dev/null +++ b/common/configtx/compare.go @@ -0,0 +1,171 @@ +/* +Copyright IBM Corp. 2017 All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package configtx + +import ( + "bytes" + + cb "github.com/hyperledger/fabric/protos/common" +) + +type comparable struct { + *cb.ConfigGroup + *cb.ConfigValue + *cb.ConfigPolicy +} + +func (cg comparable) equals(other comparable) bool { + switch { + case cg.ConfigGroup != nil: + if other.ConfigGroup == nil { + return false + } + return equalConfigGroup(cg.ConfigGroup, other.ConfigGroup) + case cg.ConfigValue != nil: + if other.ConfigValue == nil { + return false + } + return equalConfigValues(cg.ConfigValue, other.ConfigValue) + case cg.ConfigPolicy != nil: + if other.ConfigPolicy == nil { + return false + } + return equalConfigPolicies(cg.ConfigPolicy, other.ConfigPolicy) + } + + // Unreachable + return false +} + +func equalConfigValues(lhs, rhs *cb.ConfigValue) bool { + return lhs.Version == rhs.Version && + lhs.ModPolicy == rhs.ModPolicy && + bytes.Equal(lhs.Value, rhs.Value) +} + +func equalConfigPolicies(lhs, rhs *cb.ConfigPolicy) bool { + if lhs.Version != rhs.Version || + lhs.ModPolicy != rhs.ModPolicy { + return false + } + + if lhs.Policy == nil || rhs.Policy == nil { + return lhs.Policy == rhs.Policy + } + + return lhs.Policy.Type == rhs.Policy.Type && + bytes.Equal(lhs.Policy.Policy, rhs.Policy.Policy) +} + +// The subset functions check if inner is a subset of outer +// TODO, try to consolidate these three methods into one, as the code +// contents are the same, but the function signatures need to be different +func subsetOfGroups(inner, outer map[string]*cb.ConfigGroup) bool { + // The empty set is a subset of all sets + if len(inner) == 0 { + return true + } + + // If inner is not the empty set, then the outer empty set cannot be a superset of inner + if len(outer) == 0 { + return false + } + + // If any element in inner is not in outer, it is not a subset + for key := range inner { + if _, ok := outer[key]; !ok { + return false + } + } + + return true +} + +func subsetOfPolicies(inner, outer map[string]*cb.ConfigPolicy) bool { + // The empty set is a subset of all sets + if len(inner) == 0 { + return true + } + + // If inner is not the empty set, then the outer empty set cannot be a superset of inner + if len(outer) == 0 { + return false + } + + // If any element in inner is not in outer, it is not a subset + for key := range inner { + if _, ok := outer[key]; !ok { + return false + } + } + + return true +} + +func subsetOfValues(inner, outer map[string]*cb.ConfigValue) bool { + // The empty set is a subset of all sets + if len(inner) == 0 { + return true + } + + // If inner is not the empty set, then the outer empty set cannot be a superset of inner + if len(outer) == 0 { + return false + } + + // If any element in inner is not in outer, it is not a subset + for key := range inner { + if _, ok := outer[key]; !ok { + return false + } + } + + return true +} + +func equalConfigGroup(lhs, rhs *cb.ConfigGroup) bool { + if lhs.Version != rhs.Version || + lhs.ModPolicy != rhs.ModPolicy { + return false + } + + if !subsetOfGroups(lhs.Groups, rhs.Groups) { + return false + } + + if !subsetOfGroups(rhs.Groups, lhs.Groups) { + return false + } + + if !subsetOfPolicies(lhs.Policies, rhs.Policies) { + return false + } + + if !subsetOfPolicies(rhs.Policies, lhs.Policies) { + return false + } + + if !subsetOfValues(lhs.Values, rhs.Values) { + return false + } + + if !subsetOfValues(rhs.Values, lhs.Values) { + return false + } + + return true +} diff --git a/common/configtx/compare_test.go b/common/configtx/compare_test.go new file mode 100644 index 00000000000..0e2da0a0222 --- /dev/null +++ b/common/configtx/compare_test.go @@ -0,0 +1,305 @@ +/* +Copyright IBM Corp. 2017 All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package configtx + +import ( + "github.com/stretchr/testify/assert" + "testing" + + cb "github.com/hyperledger/fabric/protos/common" +) + +func TestCompareConfigValue(t *testing.T) { + // Normal equality + assert.True(t, comparable{ + ConfigValue: &cb.ConfigValue{ + Version: 0, + ModPolicy: "foo", + Value: []byte("bar"), + }}.equals(comparable{ + ConfigValue: &cb.ConfigValue{ + Version: 0, + ModPolicy: "foo", + Value: []byte("bar"), + }}), "Should have found identical config values to be identical") + + // Different Mod Policy + assert.False(t, comparable{ + ConfigValue: &cb.ConfigValue{ + Version: 0, + ModPolicy: "foo", + Value: []byte("bar"), + }}.equals(comparable{ + ConfigValue: &cb.ConfigValue{ + Version: 0, + ModPolicy: "bar", + Value: []byte("bar"), + }}), "Should have detected different mod policy") + + // Different Value + assert.False(t, comparable{ + ConfigValue: &cb.ConfigValue{ + Version: 0, + ModPolicy: "foo", + Value: []byte("bar"), + }}.equals(comparable{ + ConfigValue: &cb.ConfigValue{ + Version: 0, + ModPolicy: "foo", + Value: []byte("foo"), + }}), "Should have detected different value") + + // Different Version + assert.False(t, comparable{ + ConfigValue: &cb.ConfigValue{ + Version: 0, + ModPolicy: "foo", + Value: []byte("bar"), + }}.equals(comparable{ + ConfigValue: &cb.ConfigValue{ + Version: 1, + ModPolicy: "foo", + Value: []byte("bar"), + }}), "Should have detected different version") + + // One nil value + assert.False(t, comparable{ + ConfigValue: &cb.ConfigValue{ + Version: 0, + ModPolicy: "foo", + Value: []byte("bar"), + }}.equals(comparable{}), "Should have detected nil other value") + +} + +func TestCompareConfigPolicy(t *testing.T) { + // Normal equality + assert.True(t, comparable{ + ConfigPolicy: &cb.ConfigPolicy{ + Version: 0, + ModPolicy: "foo", + Policy: &cb.Policy{ + Type: 1, + Policy: []byte("foo"), + }, + }}.equals(comparable{ + ConfigPolicy: &cb.ConfigPolicy{ + Version: 0, + ModPolicy: "foo", + Policy: &cb.Policy{ + Type: 1, + Policy: []byte("foo"), + }, + }}), "Should have found identical config policies to be identical") + + // Different mod policy + assert.False(t, comparable{ + ConfigPolicy: &cb.ConfigPolicy{ + Version: 0, + ModPolicy: "foo", + Policy: &cb.Policy{ + Type: 1, + Policy: []byte("foo"), + }, + }}.equals(comparable{ + ConfigPolicy: &cb.ConfigPolicy{ + Version: 0, + ModPolicy: "bar", + Policy: &cb.Policy{ + Type: 1, + Policy: []byte("foo"), + }, + }}), "Should have detected different mod policy") + + // Different version + assert.False(t, comparable{ + ConfigPolicy: &cb.ConfigPolicy{ + Version: 0, + ModPolicy: "foo", + Policy: &cb.Policy{ + Type: 1, + Policy: []byte("foo"), + }, + }}.equals(comparable{ + ConfigPolicy: &cb.ConfigPolicy{ + Version: 1, + ModPolicy: "foo", + Policy: &cb.Policy{ + Type: 1, + Policy: []byte("foo"), + }, + }}), "Should have detected different version") + + // Different policy type + assert.False(t, comparable{ + ConfigPolicy: &cb.ConfigPolicy{ + Version: 0, + ModPolicy: "foo", + Policy: &cb.Policy{ + Type: 1, + Policy: []byte("foo"), + }, + }}.equals(comparable{ + ConfigPolicy: &cb.ConfigPolicy{ + Version: 0, + ModPolicy: "foo", + Policy: &cb.Policy{ + Type: 2, + Policy: []byte("foo"), + }, + }}), "Should have detected different policy type") + + // Different policy value + assert.False(t, comparable{ + ConfigPolicy: &cb.ConfigPolicy{ + Version: 0, + ModPolicy: "foo", + Policy: &cb.Policy{ + Type: 1, + Policy: []byte("foo"), + }, + }}.equals(comparable{ + ConfigPolicy: &cb.ConfigPolicy{ + Version: 0, + ModPolicy: "foo", + Policy: &cb.Policy{ + Type: 1, + Policy: []byte("bar"), + }, + }}), "Should have detected different policy value") + + // One nil value + assert.False(t, comparable{ + ConfigPolicy: &cb.ConfigPolicy{ + Version: 0, + ModPolicy: "foo", + Policy: &cb.Policy{ + Type: 1, + Policy: []byte("foo"), + }, + }}.equals(comparable{}), "Should have detected one nil value") + + // One nil policy + assert.False(t, comparable{ + ConfigPolicy: &cb.ConfigPolicy{ + Version: 0, + ModPolicy: "foo", + Policy: &cb.Policy{ + Type: 1, + Policy: []byte("foo"), + }, + }}.equals(comparable{ + ConfigPolicy: &cb.ConfigPolicy{ + Version: 0, + ModPolicy: "foo", + Policy: &cb.Policy{ + Type: 1, + }, + }}), "Should have detected one nil policy") +} + +func TestCompareConfigGroup(t *testing.T) { + // Normal equality + assert.True(t, comparable{ + ConfigGroup: &cb.ConfigGroup{ + Version: 0, + ModPolicy: "foo", + Groups: map[string]*cb.ConfigGroup{"Foo1": nil, "Bar1": nil}, + Values: map[string]*cb.ConfigValue{"Foo2": nil, "Bar2": nil}, + Policies: map[string]*cb.ConfigPolicy{"Foo3": nil, "Bar3": nil}, + }}.equals(comparable{ + ConfigGroup: &cb.ConfigGroup{ + Version: 0, + ModPolicy: "foo", + Groups: map[string]*cb.ConfigGroup{"Foo1": nil, "Bar1": nil}, + Values: map[string]*cb.ConfigValue{"Foo2": nil, "Bar2": nil}, + Policies: map[string]*cb.ConfigPolicy{"Foo3": nil, "Bar3": nil}, + }}), "Should have found identical config groups to be identical") + + // Different mod policy + assert.False(t, comparable{ + ConfigGroup: &cb.ConfigGroup{ + Version: 0, + ModPolicy: "foo", + }}.equals(comparable{ + ConfigGroup: &cb.ConfigGroup{ + Version: 0, + ModPolicy: "bar", + }}), "Should have detected different mod policy") + + // Different version + assert.False(t, comparable{ + ConfigGroup: &cb.ConfigGroup{ + Version: 0, + ModPolicy: "foo", + }}.equals(comparable{ + ConfigGroup: &cb.ConfigGroup{ + Version: 1, + ModPolicy: "foo", + }}), "Should have detected different version") + + // Different groups + assert.False(t, comparable{ + ConfigGroup: &cb.ConfigGroup{ + Version: 0, + ModPolicy: "foo", + Groups: map[string]*cb.ConfigGroup{"Foo1": nil}, + Values: map[string]*cb.ConfigValue{"Foo2": nil, "Bar2": nil}, + Policies: map[string]*cb.ConfigPolicy{"Foo3": nil, "Bar3": nil}, + }}.equals(comparable{ + ConfigGroup: &cb.ConfigGroup{ + Version: 0, + ModPolicy: "foo", + Groups: map[string]*cb.ConfigGroup{"Foo1": nil, "Bar1": nil}, + Values: map[string]*cb.ConfigValue{"Foo2": nil, "Bar2": nil}, + Policies: map[string]*cb.ConfigPolicy{"Foo3": nil, "Bar3": nil}, + }}), "Should have detected different groups entries") + + // Different values + assert.False(t, comparable{ + ConfigGroup: &cb.ConfigGroup{ + Version: 0, + ModPolicy: "foo", + Groups: map[string]*cb.ConfigGroup{"Foo1": nil, "Bar1": nil}, + Values: map[string]*cb.ConfigValue{"Foo2": nil, "Bar2": nil}, + Policies: map[string]*cb.ConfigPolicy{"Foo3": nil, "Bar3": nil}, + }}.equals(comparable{ + ConfigGroup: &cb.ConfigGroup{ + Version: 0, + ModPolicy: "foo", + Groups: map[string]*cb.ConfigGroup{"Foo1": nil, "Bar1": nil}, + Values: map[string]*cb.ConfigValue{"Foo2": nil}, + Policies: map[string]*cb.ConfigPolicy{"Foo3": nil, "Bar3": nil}, + }}), "Should have detected fifferent values entries") + + // Different policies + assert.False(t, comparable{ + ConfigGroup: &cb.ConfigGroup{ + Version: 0, + ModPolicy: "foo", + Groups: map[string]*cb.ConfigGroup{"Foo1": nil, "Bar1": nil}, + Values: map[string]*cb.ConfigValue{"Foo2": nil, "Bar2": nil}, + Policies: map[string]*cb.ConfigPolicy{"Foo3": nil, "Bar3": nil}, + }}.equals(comparable{ + ConfigGroup: &cb.ConfigGroup{ + Version: 0, + ModPolicy: "foo", + Groups: map[string]*cb.ConfigGroup{"Foo1": nil, "Bar1": nil}, + Values: map[string]*cb.ConfigValue{"Foo2": nil, "Bar2": nil}, + Policies: map[string]*cb.ConfigPolicy{"Foo3": nil, "Bar4": nil}, + }}), "Should have detected fifferent policies entries") +}