Skip to content

Commit cf29ef3

Browse files
author
Jason Yellick
committed
[FAB-2554] configtx.Manager track deserialized val
https://jira.hyperledger.org/browse/FAB-2554 The configtx Manager currently calls into the config deserialization but does not retain the unmarshaled config values (as they are retained and accessible through the config). In order for the configtx manager to also support debugging and inspection of configuration, it is necessary that these deserialized versions be tracked by the manager. This CR adds this functionality. Change-Id: I37165e8d5877dd06dc29fad95831069cf7f41d30 Signed-off-by: Jason Yellick <[email protected]>
1 parent a552e22 commit cf29ef3

10 files changed

+118
-93
lines changed

common/config/api.go

+1-5
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ package config
2121
import (
2222
"time"
2323

24-
cb "github.com/hyperledger/fabric/protos/common"
2524
ab "github.com/hyperledger/fabric/protos/orderer"
2625
pb "github.com/hyperledger/fabric/protos/peer"
2726
)
@@ -86,10 +85,7 @@ type Orderer interface {
8685

8786
type ValueProposer interface {
8887
// BeginValueProposals called when a config proposal is begun
89-
BeginValueProposals(tx interface{}, groups []string) ([]ValueProposer, error)
90-
91-
// ProposeValue called when config is added to a proposal
92-
ProposeValue(tx interface{}, key string, configValue *cb.ConfigValue) error
88+
BeginValueProposals(tx interface{}, groups []string) (ValueDeserializer, []ValueProposer, error)
9389

9490
// RollbackProposals called when a config proposal is abandoned
9591
RollbackProposals(tx interface{})

common/config/proposer.go

+13-31
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,27 @@ import (
2020
"fmt"
2121
"sync"
2222

23-
cb "github.com/hyperledger/fabric/protos/common"
24-
2523
"github.com/golang/protobuf/proto"
2624
logging "github.com/op/go-logging"
2725
)
2826

2927
var logger = logging.MustGetLogger("common/config")
3028

31-
// ValuesDeserializer provides a mechanism to retrieve proto messages to deserialize config values into
32-
type ValuesDeserializer interface {
33-
// ProtoMsg behaves like a map lookup for key
34-
ProtoMsg(key string) (proto.Message, bool)
29+
// ValueDeserializer provides a mechanism to retrieve proto messages to deserialize config values into
30+
type ValueDeserializer interface {
31+
// Deserialize takes a Value key as a string, and a marshaled Value value as bytes
32+
// and returns the deserialized version of that value. Note, this function operates
33+
// with side effects intended. Using a ValueDeserializer to deserialize a message will
34+
// generally set the value in the Values interface that the ValueDeserializer derived from
35+
// Therefore, the proto.Message may be safely discarded, but may be retained for
36+
// inspection and or debugging purposes.
37+
Deserialize(key string, value []byte) (proto.Message, error)
3538
}
3639

3740
// Values defines a mechanism to supply messages to unamrshal from config
3841
// and a mechanism to validate the results
3942
type Values interface {
40-
ValuesDeserializer
43+
ValueDeserializer
4144

4245
// Validate should ensure that the values set into the proto messages are correct
4346
// and that the new group values are allowed. It also includes a tx ID in case cross
@@ -75,7 +78,7 @@ func NewProposer(vh Handler) *Proposer {
7578
}
7679

7780
// BeginValueProposals called when a config proposal is begun
78-
func (p *Proposer) BeginValueProposals(tx interface{}, groups []string) ([]ValueProposer, error) {
81+
func (p *Proposer) BeginValueProposals(tx interface{}, groups []string) (ValueDeserializer, []ValueProposer, error) {
7982
p.pendingLock.Lock()
8083
defer p.pendingLock.Unlock()
8184
if _, ok := p.pending[tx]; ok {
@@ -104,7 +107,7 @@ func (p *Proposer) BeginValueProposals(tx interface{}, groups []string) ([]Value
104107
group, err = p.vh.NewGroup(groupName)
105108
if err != nil {
106109
pending = nil
107-
return nil, fmt.Errorf("Error creating group %s: %s", groupName, err)
110+
return nil, nil, fmt.Errorf("Error creating group %s: %s", groupName, err)
108111
}
109112
}
110113

@@ -114,28 +117,7 @@ func (p *Proposer) BeginValueProposals(tx interface{}, groups []string) ([]Value
114117

115118
p.pending[tx] = pending
116119

117-
return result, nil
118-
}
119-
120-
// ProposeValue called when config is added to a proposal
121-
func (p *Proposer) ProposeValue(tx interface{}, key string, configValue *cb.ConfigValue) error {
122-
p.pendingLock.RLock()
123-
pending, ok := p.pending[tx]
124-
p.pendingLock.RUnlock()
125-
if !ok {
126-
logger.Panicf("Serious Programming Error: attempted to propose value for tx which had not been begun")
127-
}
128-
129-
msg, ok := pending.allocated.ProtoMsg(key)
130-
if !ok {
131-
return fmt.Errorf("Unknown value key %s for %T", key, p.vh)
132-
}
133-
134-
if err := proto.Unmarshal(configValue.Value, msg); err != nil {
135-
return fmt.Errorf("Error unmarshaling key to proto message: %s", err)
136-
}
137-
138-
return nil
120+
return pending.allocated, result, nil
139121
}
140122

141123
// Validate ensures that the new config values is a valid change

common/config/proposer_test.go

+27-15
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,17 @@ type mockValues struct {
3737
ValidateReturn error
3838
}
3939

40-
func (v *mockValues) ProtoMsg(key string) (proto.Message, bool) {
40+
func (v *mockValues) Deserialize(key string, value []byte) (proto.Message, error) {
4141
msg, ok := v.ProtoMsgMap[key]
42-
return msg, ok
42+
if !ok {
43+
return nil, fmt.Errorf("Missing message key: %s", key)
44+
}
45+
err := proto.Unmarshal(value, msg)
46+
if err != nil {
47+
return nil, err
48+
}
49+
50+
return msg, nil
4351
}
4452

4553
func (v *mockValues) Validate(interface{}, map[string]ValueProposer) error {
@@ -110,39 +118,43 @@ func TestGoodKeys(t *testing.T) {
110118
mh.AllocateReturn.ProtoMsgMap["Payload"] = &cb.Payload{}
111119

112120
p := NewProposer(mh)
113-
_, err := p.BeginValueProposals(t, nil)
121+
vd, _, err := p.BeginValueProposals(t, nil)
114122
assert.NoError(t, err)
115123

116124
env := &cb.Envelope{Payload: []byte("SOME DATA")}
117125
pay := &cb.Payload{Data: []byte("SOME OTHER DATA")}
118126

119-
assert.NoError(t, p.ProposeValue(t, "Envelope", &cb.ConfigValue{Value: utils.MarshalOrPanic(env)}))
120-
assert.NoError(t, p.ProposeValue(t, "Payload", &cb.ConfigValue{Value: utils.MarshalOrPanic(pay)}))
127+
msg, err := vd.Deserialize("Envelope", utils.MarshalOrPanic(env))
128+
assert.NoError(t, err)
129+
assert.Equal(t, msg, env)
121130

122-
assert.Equal(t, mh.AllocateReturn.ProtoMsgMap["Envelope"], env)
123-
assert.Equal(t, mh.AllocateReturn.ProtoMsgMap["Payload"], pay)
131+
msg, err = vd.Deserialize("Payload", utils.MarshalOrPanic(pay))
132+
assert.NoError(t, err)
133+
assert.Equal(t, msg, pay)
124134
}
125135

126136
func TestBadMarshaling(t *testing.T) {
127137
mh := newMockHandler()
128138
mh.AllocateReturn.ProtoMsgMap["Envelope"] = &cb.Envelope{}
129139

130140
p := NewProposer(mh)
131-
_, err := p.BeginValueProposals(t, nil)
141+
vd, _, err := p.BeginValueProposals(t, nil)
132142
assert.NoError(t, err)
133143

134-
assert.Error(t, p.ProposeValue(t, "Envelope", &cb.ConfigValue{Value: []byte("GARBAGE")}), "Should have errored unmarshaling")
144+
_, err = vd.Deserialize("Envelope", []byte("GARBAGE"))
145+
assert.Error(t, err, "Should have errored unmarshaling")
135146
}
136147

137148
func TestBadMissingMessage(t *testing.T) {
138149
mh := newMockHandler()
139150
mh.AllocateReturn.ProtoMsgMap["Payload"] = &cb.Payload{}
140151

141152
p := NewProposer(mh)
142-
_, err := p.BeginValueProposals(t, nil)
153+
vd, _, err := p.BeginValueProposals(t, nil)
143154
assert.NoError(t, err)
144155

145-
assert.Error(t, p.ProposeValue(t, "Envelope", &cb.ConfigValue{Value: utils.MarshalOrPanic(&cb.Envelope{})}), "Should have errored on unexpected message")
156+
_, err = vd.Deserialize("Envelope", utils.MarshalOrPanic(&cb.Envelope{}))
157+
assert.Error(t, err, "Should have errored on unexpected message")
146158
}
147159

148160
func TestGroups(t *testing.T) {
@@ -151,18 +163,18 @@ func TestGroups(t *testing.T) {
151163
mh.NewGroupMap["bar"] = nil
152164

153165
p := NewProposer(mh)
154-
_, err := p.BeginValueProposals(t, []string{"foo", "bar"})
166+
_, _, err := p.BeginValueProposals(t, []string{"foo", "bar"})
155167
assert.NoError(t, err, "Both groups were present")
156168
p.CommitProposals(t)
157169

158170
mh.NewGroupMap = make(map[string]ValueProposer)
159-
_, err = p.BeginValueProposals(t, []string{"foo", "bar"})
171+
_, _, err = p.BeginValueProposals(t, []string{"foo", "bar"})
160172
assert.NoError(t, err, "Should not have tried to recreate the groups")
161173
p.CommitProposals(t)
162174

163-
_, err = p.BeginValueProposals(t, []string{"foo", "other"})
175+
_, _, err = p.BeginValueProposals(t, []string{"foo", "other"})
164176
assert.Error(t, err, "Should not have errored when trying to create 'other'")
165177

166-
_, err = p.BeginValueProposals(t, []string{"foo"})
178+
_, _, err = p.BeginValueProposals(t, []string{"foo"})
167179
assert.NoError(t, err, "Should be able to begin again without rolling back because of error")
168180
}

common/config/root.go

+12-10
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import (
2020
"fmt"
2121

2222
"github.com/hyperledger/fabric/common/config/msp"
23-
cb "github.com/hyperledger/fabric/protos/common"
23+
24+
"github.com/golang/protobuf/proto"
2425
)
2526

2627
// Root acts as the object which anchors the rest of the config
@@ -39,16 +40,22 @@ func NewRoot(mspConfigHandler *msp.MSPConfigHandler) *Root {
3940
}
4041
}
4142

43+
type failDeserializer struct{}
44+
45+
func (fd failDeserializer) Deserialize(key string, value []byte) (proto.Message, error) {
46+
return nil, fmt.Errorf("Programming error, this should never be invoked")
47+
}
48+
4249
// BeginValueProposals is used to start a new config proposal
43-
func (r *Root) BeginValueProposals(tx interface{}, groups []string) ([]ValueProposer, error) {
50+
func (r *Root) BeginValueProposals(tx interface{}, groups []string) (ValueDeserializer, []ValueProposer, error) {
4451
if len(groups) != 1 {
45-
return nil, fmt.Errorf("Root config only supports having one base group")
52+
return nil, nil, fmt.Errorf("Root config only supports having one base group")
4653
}
4754
if groups[0] != ChannelGroupKey {
48-
return nil, fmt.Errorf("Root group must have channel")
55+
return nil, nil, fmt.Errorf("Root group must have channel")
4956
}
5057
r.mspConfigHandler.BeginConfig(tx)
51-
return []ValueProposer{r.channel}, nil
58+
return failDeserializer{}, []ValueProposer{r.channel}, nil
5259
}
5360

5461
// RollbackConfig is used to abandon a new config proposal
@@ -66,11 +73,6 @@ func (r *Root) CommitProposals(tx interface{}) {
6673
r.mspConfigHandler.CommitProposals(tx)
6774
}
6875

69-
// ProposeValue should not be invoked on this object
70-
func (r *Root) ProposeValue(tx interface{}, key string, value *cb.ConfigValue) error {
71-
return fmt.Errorf("Programming error, this should never be invoked")
72-
}
73-
7476
// Channel returns the associated Channel level config
7577
func (r *Root) Channel() *ChannelGroup {
7678
return r.channel

common/config/root_test.go

+7-4
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,19 @@ func init() {
3232
func TestBeginBadRoot(t *testing.T) {
3333
r := NewRoot(&msp.MSPConfigHandler{})
3434

35-
_, err := r.BeginValueProposals(t, []string{ChannelGroupKey, ChannelGroupKey})
35+
_, _, err := r.BeginValueProposals(t, []string{ChannelGroupKey, ChannelGroupKey})
3636
assert.Error(t, err, "Only one root element allowed")
3737

38-
_, err = r.BeginValueProposals(t, []string{"foo"})
38+
_, _, err = r.BeginValueProposals(t, []string{"foo"})
3939
assert.Error(t, err, "Non %s group not allowed", ChannelGroupKey)
4040
}
4141

4242
func TestProposeValue(t *testing.T) {
43-
r := NewRoot(&msp.MSPConfigHandler{})
43+
r := NewRoot(msp.NewMSPConfigHandler())
44+
45+
vd, _, err := r.BeginValueProposals(t, []string{ChannelGroupKey})
46+
assert.NoError(t, err)
4447

45-
err := r.ProposeValue(t, "foo", nil)
48+
_, err = vd.Deserialize("foo", nil)
4649
assert.Error(t, err, "ProposeValue should return error")
4750
}

common/config/standardvalues.go

+14-2
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,21 @@ func NewStandardValues(protosStructs ...interface{}) (*standardValues, error) {
4747
return sv, nil
4848
}
4949

50-
func (sv *standardValues) ProtoMsg(key string) (proto.Message, bool) {
50+
// Deserialize looks up the backing Values proto of the given name, unmarshals the given bytes
51+
// to populate the backing message structure, and retuns a referenced to the retained deserialized
52+
// message (or an error, either because the key did not exist, or there was an an error unmarshaling
53+
func (sv *standardValues) Deserialize(key string, value []byte) (proto.Message, error) {
5154
msg, ok := sv.lookup[key]
52-
return msg, ok
55+
if !ok {
56+
return nil, fmt.Errorf("Not found")
57+
}
58+
59+
err := proto.Unmarshal(value, msg)
60+
if err != nil {
61+
return nil, err
62+
}
63+
64+
return msg, nil
5365
}
5466

5567
func (sv *standardValues) initializeProtosStruct(objValue reflect.Value) error {

common/config/standardvalues_test.go

+12-11
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"testing"
2121

2222
cb "github.com/hyperledger/fabric/protos/common"
23+
"github.com/hyperledger/fabric/protos/utils"
2324

2425
"github.com/stretchr/testify/assert"
2526
)
@@ -53,12 +54,12 @@ func TestSingle(t *testing.T) {
5354
assert.NotNil(t, fooVal.Msg1, "Should have initialized Msg1")
5455
assert.NotNil(t, fooVal.Msg2, "Should have initialized Msg2")
5556

56-
msg1, ok := sv.ProtoMsg("Msg1")
57-
assert.True(t, ok, "Should have found map entry")
57+
msg1, err := sv.Deserialize("Msg1", utils.MarshalOrPanic(&cb.Envelope{}))
58+
assert.NoError(t, err, "Should have found map entry")
5859
assert.Equal(t, msg1, fooVal.Msg1, "Should be same entry")
5960

60-
msg2, ok := sv.ProtoMsg("Msg2")
61-
assert.True(t, ok, "Should have found map entry")
61+
msg2, err := sv.Deserialize("Msg2", utils.MarshalOrPanic(&cb.Payload{}))
62+
assert.NoError(t, err, "Should have found map entry")
6263
assert.Equal(t, msg2, fooVal.Msg2, "Should be same entry")
6364
}
6465

@@ -71,20 +72,20 @@ func TestPair(t *testing.T) {
7172
assert.NotNil(t, fooVal.Msg2, "Should have initialized Msg2")
7273
assert.NotNil(t, barVal.Msg3, "Should have initialized Msg3")
7374

74-
msg1, ok := sv.ProtoMsg("Msg1")
75-
assert.True(t, ok, "Should have found map entry")
75+
msg1, err := sv.Deserialize("Msg1", utils.MarshalOrPanic(&cb.Envelope{}))
76+
assert.NoError(t, err, "Should have found map entry")
7677
assert.Equal(t, msg1, fooVal.Msg1, "Should be same entry")
7778

78-
msg2, ok := sv.ProtoMsg("Msg2")
79-
assert.True(t, ok, "Should have found map entry")
79+
msg2, err := sv.Deserialize("Msg2", utils.MarshalOrPanic(&cb.Payload{}))
80+
assert.NoError(t, err, "Should have found map entry")
8081
assert.Equal(t, msg2, fooVal.Msg2, "Should be same entry")
8182

82-
msg3, ok := sv.ProtoMsg("Msg3")
83-
assert.True(t, ok, "Should have found map entry")
83+
msg3, err := sv.Deserialize("Msg3", utils.MarshalOrPanic(&cb.Header{}))
84+
assert.NoError(t, err, "Should have found map entry")
8485
assert.Equal(t, msg3, barVal.Msg3, "Should be same entry")
8586
}
8687

87-
func TestNestingConflict(t *testing.T) {
88+
func TestPairConflict(t *testing.T) {
8889
_, err := NewStandardValues(&foo{}, &conflict{})
8990
assert.Error(t, err, "Conflicting keys provided")
9091
}

0 commit comments

Comments
 (0)