Skip to content

Commit bc2923b

Browse files
author
Jason Yellick
committed
[FAB-2391] Create common config Proposer
https://jira.hyperledger.org/browse/FAB-2391 Today, each config handler implements the Propose/Commit/Rollback API on its own, which has led to a lot of duplicated code. This also means that each config handler must handle its own proto unmarshaling and error handling which makes inspecting the config from a human perspective cumbersome. This is the first step in mapping the config to/from a human readable form. Change-Id: I1dfd595b1e8be2a63ef82cb0b91616a932d4e83b Signed-off-by: Jason Yellick <[email protected]>
1 parent 9379e85 commit bc2923b

File tree

5 files changed

+305
-3
lines changed

5 files changed

+305
-3
lines changed

common/configvalues/api.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package api
17+
// Note, the directory is still configvalues, but this is stuttery and config
18+
// is a more accurate and better name, TODO, update directory
19+
package config
1820

1921
import (
2022
"time"

common/configvalues/channel/application/organization.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func (oc *ApplicationOrgConfig) AnchorPeers() []*pb.AnchorPeer {
6363
}
6464

6565
// BeginValueProposals is used to start a new config proposal
66-
func (oc *ApplicationOrgConfig) BeginValueProposals(groups []string) ([]api.ValueProposer, error) {
66+
func (oc *ApplicationOrgConfig) BeginValueProposals(groups []string) ([]config.ValueProposer, error) {
6767
logger.Debugf("Beginning a possible new org config")
6868
if len(groups) != 0 {
6969
return nil, fmt.Errorf("ApplicationGroup does not support subgroups")

common/configvalues/channel/common/organization/organization.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func NewOrgConfig(id string, mspConfig *mspconfig.MSPConfigHandler) *OrgConfig {
6262
}
6363

6464
// BeginValueProposals is used to start a new config proposal
65-
func (oc *OrgConfig) BeginValueProposals(groups []string) ([]api.ValueProposer, error) {
65+
func (oc *OrgConfig) BeginValueProposals(groups []string) ([]config.ValueProposer, error) {
6666
logger.Debugf("Beginning a possible new org config")
6767
if len(groups) != 0 {
6868
return nil, fmt.Errorf("Orgs do not support sub-groups")

common/configvalues/root/proposer.go

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package config
18+
19+
import (
20+
"fmt"
21+
22+
api "github.com/hyperledger/fabric/common/configvalues"
23+
cb "github.com/hyperledger/fabric/protos/common"
24+
25+
"github.com/golang/protobuf/proto"
26+
logging "github.com/op/go-logging"
27+
)
28+
29+
var logger = logging.MustGetLogger("common/config")
30+
31+
// Values defines a mechanism to supply messages to unamrshal from config
32+
// and a mechanism to validate the results
33+
type Values interface {
34+
// ProtoMsg behaves like a map lookup for key
35+
ProtoMsg(key string) (proto.Message, bool)
36+
37+
// Validate should ensure that the values set into the proto messages are correct
38+
Validate() error
39+
40+
// Commit should call back into the Value handler to update the config
41+
Commit()
42+
}
43+
44+
// Handler
45+
type Handler interface {
46+
Allocate() Values
47+
NewGroup(name string) (api.ValueProposer, error)
48+
}
49+
50+
type config struct {
51+
allocated Values
52+
groups map[string]api.ValueProposer
53+
}
54+
55+
type Proposer struct {
56+
vh Handler
57+
current *config
58+
pending *config
59+
}
60+
61+
func NewProposer(vh Handler) *Proposer {
62+
return &Proposer{
63+
vh: vh,
64+
}
65+
}
66+
67+
// BeginValueProposals called when a config proposal is begun
68+
func (p *Proposer) BeginValueProposals(groups []string) ([]api.ValueProposer, error) {
69+
if p.pending != nil {
70+
logger.Panicf("Duplicated BeginValueProposals without Rollback or Commit")
71+
}
72+
73+
result := make([]api.ValueProposer, len(groups))
74+
75+
p.pending = &config{
76+
allocated: p.vh.Allocate(),
77+
groups: make(map[string]api.ValueProposer),
78+
}
79+
80+
for i, groupName := range groups {
81+
var group api.ValueProposer
82+
var ok bool
83+
84+
if p.current == nil {
85+
ok = false
86+
} else {
87+
group, ok = p.current.groups[groupName]
88+
}
89+
90+
if !ok {
91+
var err error
92+
group, err = p.vh.NewGroup(groupName)
93+
if err != nil {
94+
p.pending = nil
95+
return nil, fmt.Errorf("Error creating group %s: %s", groupName, err)
96+
}
97+
}
98+
99+
p.pending.groups[groupName] = group
100+
result[i] = group
101+
}
102+
103+
return result, nil
104+
}
105+
106+
// ProposeValue called when config is added to a proposal
107+
func (p *Proposer) ProposeValue(key string, configValue *cb.ConfigValue) error {
108+
msg, ok := p.pending.allocated.ProtoMsg(key)
109+
if !ok {
110+
return fmt.Errorf("Unknown value key: %s", key)
111+
}
112+
113+
if err := proto.Unmarshal(configValue.Value, msg); err != nil {
114+
return fmt.Errorf("Error unmarshaling key to proto message: %s", err)
115+
}
116+
117+
return nil
118+
}
119+
120+
// Validate ensures that the new config values is a valid change
121+
func (p *Proposer) PreCommit() error {
122+
return p.pending.allocated.Validate()
123+
}
124+
125+
// RollbackProposals called when a config proposal is abandoned
126+
func (p *Proposer) RollbackProposals() {
127+
p.pending = nil
128+
}
129+
130+
// CommitProposals called when a config proposal is committed
131+
func (p *Proposer) CommitProposals() {
132+
if p.pending == nil {
133+
logger.Panicf("Attempted to commit with no pending values (indicates no Begin invoked)")
134+
}
135+
p.current = p.pending
136+
p.current.allocated.Commit()
137+
p.pending = nil
138+
}
+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package config
18+
19+
import (
20+
"fmt"
21+
"testing"
22+
23+
api "github.com/hyperledger/fabric/common/configvalues"
24+
cb "github.com/hyperledger/fabric/protos/common"
25+
"github.com/hyperledger/fabric/protos/utils"
26+
27+
"github.com/golang/protobuf/proto"
28+
logging "github.com/op/go-logging"
29+
"github.com/stretchr/testify/assert"
30+
)
31+
32+
func init() {
33+
logging.SetLevel(logging.DEBUG, "")
34+
}
35+
36+
type mockValues struct {
37+
ProtoMsgMap map[string]proto.Message
38+
ValidateReturn error
39+
}
40+
41+
func (v *mockValues) ProtoMsg(key string) (proto.Message, bool) {
42+
msg, ok := v.ProtoMsgMap[key]
43+
return msg, ok
44+
}
45+
46+
func (v *mockValues) Validate() error {
47+
return v.ValidateReturn
48+
}
49+
50+
func (v *mockValues) Commit() {}
51+
52+
func newMockValues() *mockValues {
53+
return &mockValues{
54+
ProtoMsgMap: make(map[string]proto.Message),
55+
}
56+
}
57+
58+
type mockHandler struct {
59+
AllocateReturn *mockValues
60+
NewGroupMap map[string]api.ValueProposer
61+
NewGroupError error
62+
}
63+
64+
func (h *mockHandler) Allocate() Values {
65+
return h.AllocateReturn
66+
}
67+
68+
func (h *mockHandler) NewGroup(name string) (api.ValueProposer, error) {
69+
group, ok := h.NewGroupMap[name]
70+
if !ok {
71+
return nil, fmt.Errorf("Missing group implies error")
72+
}
73+
return group, nil
74+
}
75+
76+
func newMockHandler() *mockHandler {
77+
return &mockHandler{
78+
AllocateReturn: newMockValues(),
79+
NewGroupMap: make(map[string]api.ValueProposer),
80+
}
81+
}
82+
83+
func TestDoubleBegin(t *testing.T) {
84+
p := NewProposer(&mockHandler{AllocateReturn: &mockValues{}})
85+
p.BeginValueProposals(nil)
86+
assert.Panics(t, func() { p.BeginValueProposals(nil) }, "Two begins back to back should have caused a panic")
87+
}
88+
89+
func TestCommitWithoutBegin(t *testing.T) {
90+
p := NewProposer(&mockHandler{AllocateReturn: &mockValues{}})
91+
assert.Panics(t, func() { p.CommitProposals() }, "Commit without begin should have caused a panic")
92+
}
93+
94+
func TestRollback(t *testing.T) {
95+
p := NewProposer(&mockHandler{AllocateReturn: &mockValues{}})
96+
p.pending = &config{}
97+
p.RollbackProposals()
98+
assert.Nil(t, p.pending, "Should have cleared pending config on rollback")
99+
}
100+
101+
func TestGoodKeys(t *testing.T) {
102+
mh := newMockHandler()
103+
mh.AllocateReturn.ProtoMsgMap["Envelope"] = &cb.Envelope{}
104+
mh.AllocateReturn.ProtoMsgMap["Payload"] = &cb.Payload{}
105+
106+
p := NewProposer(mh)
107+
_, err := p.BeginValueProposals(nil)
108+
assert.NoError(t, err)
109+
110+
env := &cb.Envelope{Payload: []byte("SOME DATA")}
111+
pay := &cb.Payload{Data: []byte("SOME OTHER DATA")}
112+
113+
assert.NoError(t, p.ProposeValue("Envelope", &cb.ConfigValue{Value: utils.MarshalOrPanic(env)}))
114+
assert.NoError(t, p.ProposeValue("Payload", &cb.ConfigValue{Value: utils.MarshalOrPanic(pay)}))
115+
116+
assert.Equal(t, mh.AllocateReturn.ProtoMsgMap["Envelope"], env)
117+
assert.Equal(t, mh.AllocateReturn.ProtoMsgMap["Payload"], pay)
118+
}
119+
120+
func TestBadMarshaling(t *testing.T) {
121+
mh := newMockHandler()
122+
mh.AllocateReturn.ProtoMsgMap["Envelope"] = &cb.Envelope{}
123+
124+
p := NewProposer(mh)
125+
_, err := p.BeginValueProposals(nil)
126+
assert.NoError(t, err)
127+
128+
assert.Error(t, p.ProposeValue("Envelope", &cb.ConfigValue{Value: []byte("GARBAGE")}), "Should have errored unmarshaling")
129+
}
130+
131+
func TestBadMissingMessage(t *testing.T) {
132+
mh := newMockHandler()
133+
mh.AllocateReturn.ProtoMsgMap["Payload"] = &cb.Payload{}
134+
135+
p := NewProposer(mh)
136+
_, err := p.BeginValueProposals(nil)
137+
assert.NoError(t, err)
138+
139+
assert.Error(t, p.ProposeValue("Envelope", &cb.ConfigValue{Value: utils.MarshalOrPanic(&cb.Envelope{})}), "Should have errored on unexpected message")
140+
}
141+
142+
func TestGroups(t *testing.T) {
143+
mh := newMockHandler()
144+
mh.NewGroupMap["foo"] = nil
145+
mh.NewGroupMap["bar"] = nil
146+
147+
p := NewProposer(mh)
148+
_, err := p.BeginValueProposals([]string{"foo", "bar"})
149+
assert.NoError(t, err, "Both groups were present")
150+
p.CommitProposals()
151+
152+
mh.NewGroupMap = make(map[string]api.ValueProposer)
153+
_, err = p.BeginValueProposals([]string{"foo", "bar"})
154+
assert.NoError(t, err, "Should not have tried to recreate the groups")
155+
p.CommitProposals()
156+
157+
_, err = p.BeginValueProposals([]string{"foo", "other"})
158+
assert.Error(t, err, "Should not have errored when trying to create 'other'")
159+
160+
_, err = p.BeginValueProposals([]string{"foo"})
161+
assert.NoError(t, err, "Should be able to begin again without rolling back because of error")
162+
}

0 commit comments

Comments
 (0)