Skip to content

Commit

Permalink
[FAB-9556] Peer CLI multi-endorse via connection prof
Browse files Browse the repository at this point in the history
This CR extends peer CLI's support for obtaining
multiple endorsements for an "invoke" call. The peers
and the paths to the TLS root cert files for the peers
can now be supplied by the --connectionprofile pflag,
which if set will be used instead of the --peerAddresses
and --tlsRootCertFiles pflags.

Change-Id: I94d3511332cd7c0bb06de20d99ce5d355bc1829c
Signed-off-by: Will Lahti <wtlahti@us.ibm.com>
  • Loading branch information
wlahti committed Apr 19, 2018
1 parent 32d20ad commit b219274
Show file tree
Hide file tree
Showing 14 changed files with 735 additions and 0 deletions.
3 changes: 3 additions & 0 deletions peer/chaincode/chaincode.go
Expand Up @@ -65,6 +65,7 @@ var (
collectionConfigBytes []byte
peerAddresses []string
tlsRootCertFiles []string
connectionProfile string
)

var chaincodeCmd = &cobra.Command{
Expand Down Expand Up @@ -116,6 +117,8 @@ func resetFlags() {
fmt.Sprint("The addresses of the peers to connect to"))
flags.StringArrayVarP(&tlsRootCertFiles, "tlsRootCertFiles", "", []string{common.UndefinedParamValue},
fmt.Sprint("If TLS is enabled, the paths to the TLS root cert files of the peers to connect to. The order and number of certs specified should match the --peerAddresses flag"))
flags.StringVarP(&connectionProfile, "connectionProfile", "", common.UndefinedParamValue,
fmt.Sprint("Connection profile that provides the necessary connection information for the network. Note: currently only supported for providing peer connection information"))
}

func attachFlags(cmd *cobra.Command, names []string) {
Expand Down
21 changes: 21 additions & 0 deletions peer/chaincode/common.go
Expand Up @@ -292,6 +292,27 @@ func checkChaincodeCmdParams(cmd *cobra.Command) error {
}

func validatePeerConnectionParameters(cmdName string) error {
if connectionProfile != common.UndefinedParamValue {
networkConfig, err := common.GetConfig(connectionProfile)
if err != nil {
return err
}
if len(networkConfig.Channels[channelID].Peers) != 0 {
peerAddresses = []string{}
tlsRootCertFiles = []string{}
for peer, peerChannelConfig := range networkConfig.Channels[channelID].Peers {
if peerChannelConfig.EndorsingPeer {
peerConfig, ok := networkConfig.Peers[peer]
if !ok {
return errors.Errorf("peer '%s' is defined in the channel config but doesn't have associated peer config", peer)
}
peerAddresses = append(peerAddresses, peerConfig.URL)
tlsRootCertFiles = append(tlsRootCertFiles, peerConfig.TLSCACerts.Path)
}
}
}
}

// currently only support multiple peer addresses for invoke
if cmdName != "invoke" && len(peerAddresses) > 1 {
return errors.Errorf("'%s' command can only be executed against one peer. received %d", cmdName, len(peerAddresses))
Expand Down
23 changes: 23 additions & 0 deletions peer/chaincode/common_test.go
Expand Up @@ -274,6 +274,29 @@ func TestValidatePeerConnectionParams(t *testing.T) {
err = validatePeerConnectionParameters("invoke")
assert.NoError(err)

// failure - connection profile doesn't exist
resetFlags()
connectionProfile = "blah"
err = validatePeerConnectionParameters("invoke")
assert.Error(err)
assert.Contains(err.Error(), "error reading connection profile")

// failure - connection profile has peer defined in channel config but
// not in peer config
resetFlags()
channelID = "mychannel"
connectionProfile = "../common/testdata/connectionprofile-uneven.yaml"
err = validatePeerConnectionParameters("invoke")
assert.Error(err)
assert.Contains(err.Error(), "defined in the channel config but doesn't have associated peer config")

// success - connection profile exists
resetFlags()
channelID = "mychannel"
connectionProfile = "../common/testdata/connectionprofile.yaml"
err = validatePeerConnectionParameters("invoke")
assert.NoError(err)

// cleanup pflags and viper
resetFlags()
viper.Reset()
Expand Down
1 change: 1 addition & 0 deletions peer/chaincode/install.go
Expand Up @@ -52,6 +52,7 @@ func installCmd(cf *ChaincodeCmdFactory) *cobra.Command {
"version",
"peerAddresses",
"tlsRootCertFiles",
"connectionProfile",
}
attachFlags(chaincodeInstallCmd, flagList)

Expand Down
1 change: 1 addition & 0 deletions peer/chaincode/instantiate.go
Expand Up @@ -46,6 +46,7 @@ func instantiateCmd(cf *ChaincodeCmdFactory) *cobra.Command {
"collections-config",
"peerAddresses",
"tlsRootCertFiles",
"connectionProfile",
}
attachFlags(chaincodeInstantiateCmd, flagList)

Expand Down
1 change: 1 addition & 0 deletions peer/chaincode/invoke.go
Expand Up @@ -32,6 +32,7 @@ func invokeCmd(cf *ChaincodeCmdFactory) *cobra.Command {
"channelID",
"peerAddresses",
"tlsRootCertFiles",
"connectionProfile",
}
attachFlags(chaincodeInvokeCmd, flagList)

Expand Down
1 change: 1 addition & 0 deletions peer/chaincode/list.go
Expand Up @@ -43,6 +43,7 @@ func listCmd(cf *ChaincodeCmdFactory) *cobra.Command {
"instantiated",
"peerAddresses",
"tlsRootCertFiles",
"connectionProfile",
}
attachFlags(chaincodeListCmd, flagList)

Expand Down
1 change: 1 addition & 0 deletions peer/chaincode/query.go
Expand Up @@ -33,6 +33,7 @@ func queryCmd(cf *ChaincodeCmdFactory) *cobra.Command {
"channelID",
"peerAddresses",
"tlsRootCertFiles",
"connectionProfile",
}
attachFlags(chaincodeQueryCmd, flagList)

Expand Down
1 change: 1 addition & 0 deletions peer/chaincode/upgrade.go
Expand Up @@ -44,6 +44,7 @@ func upgradeCmd(cf *ChaincodeCmdFactory) *cobra.Command {
"vscc",
"peerAddresses",
"tlsRootCertFiles",
"connectionProfile",
}
attachFlags(chaincodeUpgradeCmd, flagList)

Expand Down
176 changes: 176 additions & 0 deletions peer/common/networkconfig.go
@@ -0,0 +1,176 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package common

import (
"io/ioutil"

"github.com/pkg/errors"
yaml "gopkg.in/yaml.v2"
)

// NetworkConfig provides a static definition of a Hyperledger Fabric network
type NetworkConfig struct {
Name string `yaml:"name"`
Xtype string `yaml:"x-type"`
Description string `yaml:"description"`
Version string `yaml:"version"`
Channels map[string]ChannelNetworkConfig `yaml:"channels"`
Organizations map[string]OrganizationConfig `yaml:"organizations"`
Peers map[string]PeerConfig `yaml:"peers"`
Client ClientConfig `yaml:"client"`
Orderers map[string]OrdererConfig `yaml:"orderers"`
CertificateAuthorities map[string]CAConfig `yaml:"certificateAuthorities"`
}

// ClientConfig - not currently used by CLI
type ClientConfig struct {
Organization string `yaml:"organization"`
Logging LoggingType `yaml:"logging"`
CryptoConfig CCType `yaml:"cryptoconfig"`
TLS TLSType `yaml:"tls"`
CredentialStore CredentialStoreType `yaml:"credentialStore"`
}

// LoggingType not currently used by CLI
type LoggingType struct {
Level string `yaml:"level"`
}

// CCType - not currently used by CLI
type CCType struct {
Path string `yaml:"path"`
}

// TLSType - not currently used by CLI
type TLSType struct {
Enabled bool `yaml:"enabled"`
}

// CredentialStoreType - not currently used by CLI
type CredentialStoreType struct {
Path string `yaml:"path"`
CryptoStore struct {
Path string `yaml:"path"`
}
Wallet string `yaml:"wallet"`
}

// ChannelNetworkConfig provides the definition of channels for the network
type ChannelNetworkConfig struct {
// Orderers list of ordering service nodes
Orderers []string `yaml:"orderers"`
// Peers a list of peer-channels that are part of this organization
// to get the real Peer config object, use the Name field and fetch NetworkConfig.Peers[Name]
Peers map[string]PeerChannelConfig `yaml:"peers"`
// Chaincodes list of services
Chaincodes []string `yaml:"chaincodes"`
}

// PeerChannelConfig defines the peer capabilities
type PeerChannelConfig struct {
EndorsingPeer bool `yaml:"endorsingPeer"`
ChaincodeQuery bool `yaml:"chaincodeQuery"`
LedgerQuery bool `yaml:"ledgerQuery"`
EventSource bool `yaml:"eventSource"`
}

// OrganizationConfig provides the definition of an organization in the network
// not currently used by CLI
type OrganizationConfig struct {
MspID string `yaml:"mspid"`
Peers []string `yaml:"peers"`
CryptoPath string `yaml:"cryptoPath"`
CertificateAuthorities []string `yaml:"certificateAuthorities"`
AdminPrivateKey TLSConfig `yaml:"adminPrivateKey"`
SignedCert TLSConfig `yaml:"signedCert"`
}

// OrdererConfig defines an orderer configuration
// not currently used by CLI
type OrdererConfig struct {
URL string `yaml:"url"`
GrpcOptions map[string]interface{} `yaml:"grpcOptions"`
TLSCACerts TLSConfig `yaml:"tlsCACerts"`
}

// PeerConfig defines a peer configuration
type PeerConfig struct {
URL string `yaml:"url"`
EventURL string `yaml:"eventUrl"`
GRPCOptions map[string]interface{} `yaml:"grpcOptions"`
TLSCACerts TLSConfig `yaml:"tlsCACerts"`
}

// CAConfig defines a CA configuration
// not currently used by CLI
type CAConfig struct {
URL string `yaml:"url"`
HTTPOptions map[string]interface{} `yaml:"httpOptions"`
TLSCACerts MutualTLSConfig `yaml:"tlsCACerts"`
Registrar EnrollCredentials `yaml:"registrar"`
CaName string `yaml:"caName"`
}

// EnrollCredentials holds credentials used for enrollment
// not currently used by CLI
type EnrollCredentials struct {
EnrollID string `yaml:"enrollId"`
EnrollSecret string `yaml:"enrollSecret"`
}

// TLSConfig TLS configurations
type TLSConfig struct {
// the following two fields are interchangeable.
// If Path is available, then it will be used to load the cert
// if Pem is available, then it has the raw data of the cert it will be used as-is
// Certificate root certificate path
Path string `yaml:"path"`
// Certificate actual content
Pem string `yaml:"pem"`
}

// MutualTLSConfig Mutual TLS configurations
// not currently used by CLI
type MutualTLSConfig struct {
Pem []string `yaml:"pem"`

// Certfiles root certificates for TLS validation (Comma separated path list)
Path string `yaml:"path"`

//Client TLS information
Client TLSKeyPair `yaml:"client"`
}

// TLSKeyPair contains the private key and certificate for TLS encryption
// not currently used by CLI
type TLSKeyPair struct {
Key TLSConfig `yaml:"key"`
Cert TLSConfig `yaml:"cert"`
}

// GetConfig unmarshals the provided connection profile into a network
// configuration struct
func GetConfig(fileName string) (*NetworkConfig, error) {
if fileName == "" {
return nil, errors.New("filename cannot be empty")
}

data, err := ioutil.ReadFile(fileName)
if err != nil {
return nil, errors.Wrap(err, "error reading connection profile")
}

configData := string(data)
config := &NetworkConfig{}
err = yaml.Unmarshal([]byte(configData), &config)
if err != nil {
return nil, errors.Wrap(err, "error unmarshaling YAML")
}

return config, nil
}
51 changes: 51 additions & 0 deletions peer/common/networkconfig_test.go
@@ -0,0 +1,51 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package common_test

import (
"testing"

"github.com/hyperledger/fabric/peer/common"
"github.com/stretchr/testify/assert"
)

func TestGetConfig(t *testing.T) {
assert := assert.New(t)

// failure - empty file name
networkConfig, err := common.GetConfig("")
assert.Error(err)
assert.Nil(networkConfig)

// failure - file doesn't exist
networkConfig, err = common.GetConfig("fakefile.yaml")
assert.Error(err)
assert.Nil(networkConfig)

// failure - unexpected values for a few bools in the connection profile
networkConfig, err = common.GetConfig("testdata/connectionprofile-bad.yaml")
assert.Error(err, "error should have been nil")
assert.Nil(networkConfig, "network config should be set")

// success
networkConfig, err = common.GetConfig("testdata/connectionprofile.yaml")
assert.NoError(err, "error should have been nil")
assert.NotNil(networkConfig, "network config should be set")
assert.Equal(networkConfig.Name, "connection-profile")

channelPeers := networkConfig.Channels["mychannel"].Peers
assert.Equal(len(channelPeers), 2)
for _, peer := range channelPeers {
assert.True(peer.EndorsingPeer)
}

peers := networkConfig.Peers
assert.Equal(len(peers), 2)
for _, peer := range peers {
assert.NotEmpty(peer.TLSCACerts.Path)
}
}

0 comments on commit b219274

Please sign in to comment.