Skip to content

Commit

Permalink
[FAB-9238] PeerChannelConfig loading logic refactoring
Browse files Browse the repository at this point in the history
- To set default = 'true' for PeerChannelConfig items,
 Instead of manual lookup of keys in endpointconfig,
a unmarshal hook function is used. All manual key build
and lookup logic in endpoint config has been removed.
- revoke integration test to use config backends

Change-Id: I465bdc37116d2505c7cecc976a3c5745d8289741
Signed-off-by: Sudesh Shetty <sudesh.shetty@securekey.com>
  • Loading branch information
sudeshrshetty committed Apr 16, 2018
1 parent 041e8e1 commit d38bc9c
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 507 deletions.
36 changes: 34 additions & 2 deletions pkg/core/config/lookup/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@ func New(coreBackend core.ConfigBackend) *ConfigLookup {
return &ConfigLookup{backend: coreBackend}
}

//unmarshalOpts opts for unmarshal key function
type unmarshalOpts struct {
hookFunc mapstructure.DecodeHookFunc
}

// UnmarshalOption describes a functional parameter unmarshaling
type UnmarshalOption func(o *unmarshalOpts)

// WithUnmarshalHookFunction provides an option to pass Custom Decode Hook Func
// for unmarshaling
func WithUnmarshalHookFunction(hookFunction mapstructure.DecodeHookFunc) UnmarshalOption {
return func(o *unmarshalOpts) {
o.hookFunc = hookFunction
}
}

//ConfigLookup is wrapper for core.ConfigBackend which performs key lookup and unmarshalling
type ConfigLookup struct {
backend core.ConfigBackend
Expand Down Expand Up @@ -66,19 +82,35 @@ func (c *ConfigLookup) GetDuration(key string) time.Duration {
}

//UnmarshalKey unmarshals value for given key to rawval type
func (c *ConfigLookup) UnmarshalKey(key string, rawVal interface{}) error {
func (c *ConfigLookup) UnmarshalKey(key string, rawVal interface{}, opts ...UnmarshalOption) error {
value, ok := c.backend.Lookup(key)
if !ok {
return nil
}

//mandatory hook func
hookFn := mapstructure.StringToTimeDurationHookFunc()

//check for opts
unmarshalOpts := unmarshalOpts{}
for _, param := range opts {
param(&unmarshalOpts)
}

//compose multiple hook funcs to one if found in opts
if unmarshalOpts.hookFunc != nil {
hookFn = mapstructure.ComposeDecodeHookFunc(hookFn, unmarshalOpts.hookFunc)
}

//build decoder
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
DecodeHook: hookFn,
Result: rawVal,
})
if err != nil {
return err
}

//decode
return decoder.Decode(value)
}
78 changes: 78 additions & 0 deletions pkg/core/config/lookup/lookup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ import (

"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab"
"github.com/hyperledger/fabric-sdk-go/pkg/core/mocks"
"github.com/mitchellh/mapstructure"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
)

var sampleConfigFile = "../testdata/config_test.yaml"

const orgChannelID = "orgchannel"

var backend *mocks.MockConfigBackend

func TestMain(m *testing.M) {
Expand Down Expand Up @@ -244,6 +247,39 @@ func setupCustomBackend() {
backend = &mocks.MockConfigBackend{KeyValueMap: backendMap}
}

func TestUnmarshalWithHookFunc(t *testing.T) {
testLookup := New(backend)
tamperPeerChannelConfig(backend)
//output struct
networkConfig := fab.NetworkConfig{}
testLookup.UnmarshalKey("channels", &networkConfig.Channels, WithUnmarshalHookFunction(setTrueDefaultForPeerChannelConfig()))

//Test if mandatory hook func is working as expected
assert.True(t, len(networkConfig.Channels) == 3)
assert.True(t, len(networkConfig.Channels["mychannel"].Peers) == 1)
assert.True(t, networkConfig.Channels["mychannel"].Policies.QueryChannelConfig.MinResponses == 1)
assert.True(t, networkConfig.Channels["mychannel"].Policies.QueryChannelConfig.MaxTargets == 1)
assert.True(t, networkConfig.Channels["mychannel"].Policies.QueryChannelConfig.RetryOpts.MaxBackoff.String() == (5*time.Second).String())
assert.True(t, networkConfig.Channels["mychannel"].Policies.QueryChannelConfig.RetryOpts.InitialBackoff.String() == (500*time.Millisecond).String())
assert.True(t, networkConfig.Channels["mychannel"].Policies.QueryChannelConfig.RetryOpts.BackoffFactor == 2.0)

//Test if custom hook func is working
assert.True(t, len(networkConfig.Channels[orgChannelID].Peers) == 2)
//test orgchannel peer1 (EndorsingPeer should be true as set, remaining should be default = true)
orgChannelPeer1 := networkConfig.Channels[orgChannelID].Peers["peer0.org1.example.com"]
assert.True(t, orgChannelPeer1.EndorsingPeer)
assert.True(t, orgChannelPeer1.LedgerQuery)
assert.True(t, orgChannelPeer1.EventSource)
assert.True(t, orgChannelPeer1.ChaincodeQuery)

//test orgchannel peer1 (EndorsingPeer should be false as set, remaining should be default = true)
orgChannelPeer2 := networkConfig.Channels[orgChannelID].Peers["peer0.org2.example.com"]
assert.False(t, orgChannelPeer2.EndorsingPeer)
assert.True(t, orgChannelPeer2.LedgerQuery)
assert.True(t, orgChannelPeer2.EventSource)
assert.True(t, orgChannelPeer2.ChaincodeQuery)
}

func newViper() *viper.Viper {
myViper := viper.New()
replacer := strings.NewReplacer(".", "_")
Expand All @@ -255,3 +291,45 @@ func newViper() *viper.Viper {
}
return myViper
}

func tamperPeerChannelConfig(backend *mocks.MockConfigBackend) {
channelsMap := backend.KeyValueMap["channels"]
orgChannel := map[string]interface{}{
"orderers": []string{"orderer.example.com"},
"peers": map[string]interface{}{
"peer0.org1.example.com": map[string]interface{}{"endorsingpeer": true},
"peer0.org2.example.com": map[string]interface{}{"endorsingpeer": false},
},
}
(channelsMap.(map[string]interface{}))[orgChannelID] = orgChannel
}

func setTrueDefaultForPeerChannelConfig() mapstructure.DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {

//If target is of type 'fab.PeerChannelConfig', then only hook should work
if t == reflect.TypeOf(fab.PeerChannelConfig{}) {
dataMap, ok := data.(map[string]interface{})
if ok {
setDefault(dataMap, "endorsingpeer", true)
setDefault(dataMap, "chaincodequery", true)
setDefault(dataMap, "ledgerquery", true)
setDefault(dataMap, "eventsource", true)

return dataMap, nil
}
}
return data, nil
}
}

//setDefault sets default value provided to map if given key not found
func setDefault(dataMap map[string]interface{}, key string, defaultVal bool) {
_, ok := dataMap[key]
if !ok {
dataMap[key] = true
}
}
87 changes: 37 additions & 50 deletions pkg/fab/endpointconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"reflect"
"regexp"
"sort"
"strconv"
Expand All @@ -27,6 +28,7 @@ import (
"github.com/hyperledger/fabric-sdk-go/pkg/core/config/lookup"
"github.com/hyperledger/fabric-sdk-go/pkg/core/cryptosuite"
"github.com/hyperledger/fabric-sdk-go/pkg/util/pathvar"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -401,13 +403,12 @@ func (c *EndpointConfig) ChannelPeers(name string) ([]fab.ChannelPeer, error) {
// viper lowercases all key maps
chConfig, ok := netConfig.Channels[strings.ToLower(name)]
if !ok {
matchingChannel, mappedChannel, matchErr := c.tryMatchingChannelConfig(name)
matchingChannel, _, matchErr := c.tryMatchingChannelConfig(name)
if matchErr != nil {
return peers, nil
}

// reset 'name' with the mappedChannel as it's referenced further below
name = mappedChannel
chConfig = *matchingChannel
}

Expand All @@ -433,21 +434,6 @@ func (c *EndpointConfig) ChannelPeers(name string) ([]fab.ChannelPeer, error) {
p.TLSCACerts.Path = pathvar.Subst(p.TLSCACerts.Path)
}

// Assemble channel peer key
chPeerKey := "channels." + name + ".peers." + peerName

// Default value for endorsing peer key is true
setEndorsingPeer(chPeerKey, c, &chPeerConfig)

// Default value for chaincode query key is true
setChaincodeQuery(chPeerKey, c, &chPeerConfig)

// Default value for ledger query key is true
setLedgerQuery(chPeerKey, c, &chPeerConfig)

// Default value for event source key is true
setEventSource(chPeerKey, c, &chPeerConfig)

mspID, err := c.PeerMSPID(peerName)
if err != nil {
return nil, errors.Errorf("failed to retrieve msp id for peer %s", peerName)
Expand All @@ -464,38 +450,6 @@ func (c *EndpointConfig) ChannelPeers(name string) ([]fab.ChannelPeer, error) {

}

func setEndorsingPeer(chPeerKey string, c *EndpointConfig, chPeerConfig *fab.PeerChannelConfig) {
endorsingPeerKey := strings.ToLower(chPeerKey + ".endorsingPeer")
_, ok := c.backend.Lookup(endorsingPeerKey)
if !ok {
chPeerConfig.EndorsingPeer = true
}
}

func setEventSource(chPeerKey string, c *EndpointConfig, chPeerConfig *fab.PeerChannelConfig) {
eventSourceKey := strings.ToLower(chPeerKey + ".eventSource")
_, ok := c.backend.Lookup(eventSourceKey)
if !ok {
chPeerConfig.EventSource = true
}
}

func setLedgerQuery(chPeerKey string, c *EndpointConfig, chPeerConfig *fab.PeerChannelConfig) {
ledgerQueryKey := strings.ToLower(chPeerKey + ".ledgerQuery")
_, ok := c.backend.Lookup(ledgerQueryKey)
if !ok {
chPeerConfig.LedgerQuery = true
}
}

func setChaincodeQuery(chPeerKey string, c *EndpointConfig, chPeerConfig *fab.PeerChannelConfig) {
ccQueryKey := strings.ToLower(chPeerKey + ".chaincodeQuery")
_, ok := c.backend.Lookup(ccQueryKey)
if !ok {
chPeerConfig.ChaincodeQuery = true
}
}

// ChannelOrderers returns a list of channel orderers
func (c *EndpointConfig) ChannelOrderers(name string) ([]fab.OrdererConfig, error) {
orderers := []fab.OrdererConfig{}
Expand Down Expand Up @@ -717,7 +671,7 @@ func (c *EndpointConfig) cacheNetworkConfiguration() error {
return errors.WithMessage(err, "failed to parse 'client' config item to networkConfig.Client type")
}

err = c.backend.UnmarshalKey("channels", &networkConfig.Channels)
err = c.backend.UnmarshalKey("channels", &networkConfig.Channels, lookup.WithUnmarshalHookFunction(peerChannelConfigHookFunc()))
logger.Debugf("channels are: %+v", networkConfig.Channels)
if err != nil {
return errors.WithMessage(err, "failed to parse 'channels' config item to networkConfig.Channels type")
Expand Down Expand Up @@ -1216,3 +1170,36 @@ func loadByteKeyOrCertFromFile(c *msp.ClientConfig, isKey bool) ([]byte, error)
}
return bts, nil
}

//peerChannelConfigHookFunc returns hook function for unmarshalling 'fab.PeerChannelConfig'
// Rule : default set to 'true' if not provided in config
func peerChannelConfigHookFunc() mapstructure.DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {

//If target is of type 'fab.PeerChannelConfig', then only hook should work
if t == reflect.TypeOf(fab.PeerChannelConfig{}) {
dataMap, ok := data.(map[string]interface{})
if ok {
setDefault(dataMap, "endorsingpeer", true)
setDefault(dataMap, "chaincodequery", true)
setDefault(dataMap, "ledgerquery", true)
setDefault(dataMap, "eventsource", true)

return dataMap, nil
}
}

return data, nil
}
}

//setDefault sets default value provided to map if given key not found
func setDefault(dataMap map[string]interface{}, key string, defaultVal bool) {
_, ok := dataMap[key]
if !ok {
dataMap[key] = true
}
}
57 changes: 57 additions & 0 deletions pkg/fab/endpointconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const (
configPemTestFilePath = "../core/config/testdata/config_test_pem.yaml"
configEmbeddedUsersTestFilePath = "../core/config/testdata/config_test_embedded_pems.yaml"
configType = "yaml"
orgChannelID = "orgchannel"
)

var configBackend core.ConfigBackend
Expand Down Expand Up @@ -1256,3 +1257,59 @@ func TestTLSClientCertsNoCerts(t *testing.T) {
t.Fatalf("Actual cert is not equal to empty cert")
}
}

func TestPeerChannelConfig(t *testing.T) {
//get custom backend and tamper orgchannel values for test
backend := getCustomBackend()
tamperPeerChannelConfig(backend)

//get endpoint config
config, err := ConfigFromBackend(backend)
if err != nil {
t.Fatal(err)
}

//get network config
networkConfig, err := config.NetworkConfig()
if err != nil {
t.Fatal(err)
}

//Test if channels config are working as expected, with time values parsed properly
assert.True(t, len(networkConfig.Channels) == 3)
assert.True(t, len(networkConfig.Channels["mychannel"].Peers) == 1)
assert.True(t, networkConfig.Channels["mychannel"].Policies.QueryChannelConfig.MinResponses == 1)
assert.True(t, networkConfig.Channels["mychannel"].Policies.QueryChannelConfig.MaxTargets == 1)
assert.True(t, networkConfig.Channels["mychannel"].Policies.QueryChannelConfig.RetryOpts.MaxBackoff.String() == (5*time.Second).String())
assert.True(t, networkConfig.Channels["mychannel"].Policies.QueryChannelConfig.RetryOpts.InitialBackoff.String() == (500*time.Millisecond).String())
assert.True(t, networkConfig.Channels["mychannel"].Policies.QueryChannelConfig.RetryOpts.BackoffFactor == 2.0)

//Test if custom hook for (default=true) func is working
assert.True(t, len(networkConfig.Channels[orgChannelID].Peers) == 2)
//test orgchannel peer1 (EndorsingPeer should be true as set, remaining should be default = true)
orgChannelPeer1 := networkConfig.Channels[orgChannelID].Peers["peer0.org1.example.com"]
assert.True(t, orgChannelPeer1.EndorsingPeer)
assert.True(t, orgChannelPeer1.LedgerQuery)
assert.True(t, orgChannelPeer1.EventSource)
assert.True(t, orgChannelPeer1.ChaincodeQuery)

//test orgchannel peer1 (EndorsingPeer should be false as set, remaining should be default = true)
orgChannelPeer2 := networkConfig.Channels[orgChannelID].Peers["peer0.org2.example.com"]
assert.False(t, orgChannelPeer2.EndorsingPeer)
assert.True(t, orgChannelPeer2.LedgerQuery)
assert.True(t, orgChannelPeer2.EventSource)
assert.True(t, orgChannelPeer2.ChaincodeQuery)

}

func tamperPeerChannelConfig(backend *mocks.MockConfigBackend) {
channelsMap := backend.KeyValueMap["channels"]
orgChannel := map[string]interface{}{
"orderers": []string{"orderer.example.com"},
"peers": map[string]interface{}{
"peer0.org1.example.com": map[string]interface{}{"endorsingpeer": true},
"peer0.org2.example.com": map[string]interface{}{"endorsingpeer": false},
},
}
(channelsMap.(map[string]interface{}))[orgChannelID] = orgChannel
}
Loading

0 comments on commit d38bc9c

Please sign in to comment.