Skip to content

Commit

Permalink
FAB-11478 Enable etcd/raft configuration in channel
Browse files Browse the repository at this point in the history
This changeset:

1. Defines consenter metadata for the etcd/raft plugin, creates the
associated metadata message factory, and registers it with the metadata
factory map in protos/orderer for protolator to work.

2. Adds supports for etcd/raft configuration to the channel
configuration encoder.

3. Adds an "etcdraft" section to the configtx sample YAML and the
backing config structure.

4. Adds a "SampleDevModeEtcdRaft" profile preset to the configtx sample
YAML. This will be consumed by integration tests.

Change-Id: I7460c89fdb91e68e4217932aeed5ca41a131d47b
Signed-off-by: Kostas Christidis <kostas@christidis.io>
  • Loading branch information
kchristidis committed Aug 10, 2018
1 parent 96492eb commit 808093b
Show file tree
Hide file tree
Showing 17 changed files with 568 additions and 39 deletions.
11 changes: 10 additions & 1 deletion common/tools/configtxgen/encoder/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/hyperledger/fabric/common/util"
"github.com/hyperledger/fabric/msp"
cb "github.com/hyperledger/fabric/protos/common"
"github.com/hyperledger/fabric/protos/orderer/etcdraft"
pb "github.com/hyperledger/fabric/protos/peer"
"github.com/hyperledger/fabric/protos/utils"

Expand Down Expand Up @@ -200,7 +201,6 @@ func NewOrdererGroup(conf *genesisconfig.Orderer) (*cb.ConfigGroup, error) {
Policy: policies.ImplicitMetaAnyPolicy(channelconfig.WritersPolicyKey).Value(),
ModPolicy: channelconfig.AdminsPolicyKey,
}
addValue(ordererGroup, channelconfig.ConsensusTypeValue(conf.OrdererType, nil), channelconfig.AdminsPolicyKey)
addValue(ordererGroup, channelconfig.BatchSizeValue(
conf.BatchSize.MaxMessageCount,
conf.BatchSize.AbsoluteMaxBytes,
Expand All @@ -213,14 +213,23 @@ func NewOrdererGroup(conf *genesisconfig.Orderer) (*cb.ConfigGroup, error) {
addValue(ordererGroup, channelconfig.CapabilitiesValue(conf.Capabilities), channelconfig.AdminsPolicyKey)
}

var consensusMetadata []byte
var err error

switch conf.OrdererType {
case ConsensusTypeSolo:
case ConsensusTypeKafka:
addValue(ordererGroup, channelconfig.KafkaBrokersValue(conf.Kafka.Brokers), channelconfig.AdminsPolicyKey)
case etcdraft.TypeKey:
if consensusMetadata, err = etcdraft.Marshal(conf.EtcdRaft); err != nil {
return nil, errors.Errorf("cannot marshal metadata for orderer type %s: %s", etcdraft.TypeKey, err)
}
default:
return nil, errors.Errorf("unknown orderer type: %s", conf.OrdererType)
}

addValue(ordererGroup, channelconfig.ConsensusTypeValue(conf.OrdererType, consensusMetadata), channelconfig.AdminsPolicyKey)

for _, org := range conf.Organizations {
var err error
ordererGroup.Groups[org.Name], err = NewOrdererOrgGroup(org)
Expand Down
21 changes: 21 additions & 0 deletions common/tools/configtxgen/encoder/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ package encoder
import (
"testing"

"github.com/hyperledger/fabric/protos/orderer/etcdraft"

"github.com/hyperledger/fabric/common/channelconfig"
"github.com/hyperledger/fabric/common/configtx"
"github.com/hyperledger/fabric/common/flogging"
Expand All @@ -17,10 +19,12 @@ import (
genesisconfig "github.com/hyperledger/fabric/common/tools/configtxgen/localconfig"
msptesttools "github.com/hyperledger/fabric/msp/mgmt/testtools"
cb "github.com/hyperledger/fabric/protos/common"
ab "github.com/hyperledger/fabric/protos/orderer"
"github.com/hyperledger/fabric/protos/utils"

"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func init() {
Expand Down Expand Up @@ -284,6 +288,23 @@ func TestNewOrdererGroup(t *testing.T) {
assert.Error(t, err)
assert.Nil(t, group)
})

t.Run("etcd/raft-based Orderer", func(t *testing.T) {
config := configtxgentest.Load(genesisconfig.SampleDevModeEtcdRaftProfile)
group, _ := NewOrdererGroup(config.Orderer)
consensusType := group.GetValues()[channelconfig.ConsensusTypeKey]
packedType := consensusType.GetValue()
unpackedType := new(ab.ConsensusType)
err := proto.Unmarshal(packedType, unpackedType)
require.NoError(t, err, "cannot extract %s config value from orderer group", channelconfig.ConsensusTypeKey)
unpackedMetadata := new(etcdraft.Metadata)
err = proto.Unmarshal(unpackedType.GetMetadata(), unpackedMetadata)
require.NoError(t, err, "cannot extract metadata value from %s consenters", etcdraft.TypeKey)
for _, v := range unpackedMetadata.GetConsenters() {
// Checking one field for a non-nil value should be enough.
require.NotNil(t, v.GetClientTlsCert(), "cannot extract PEM-encoded client certificate of consenter")
}
})
}

func TestBootstrapper(t *testing.T) {
Expand Down
85 changes: 52 additions & 33 deletions common/tools/configtxgen/localconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,19 @@ package localconfig

import (
"fmt"

"path/filepath"
"strings"
"time"

"github.com/hyperledger/fabric/common/flogging"
"github.com/hyperledger/fabric/common/policies"
"github.com/hyperledger/fabric/common/viperutil"
logging "github.com/op/go-logging"

"github.com/spf13/viper"

"path/filepath"

cf "github.com/hyperledger/fabric/core/config"
"github.com/hyperledger/fabric/msp"
"github.com/hyperledger/fabric/protos/orderer/etcdraft"

logging "github.com/op/go-logging"
"github.com/spf13/viper"
)

const (
Expand Down Expand Up @@ -70,6 +68,10 @@ const (
// only the sample MSP and uses Kafka for ordering.
SampleSingleMSPKafkaProfile = "SampleSingleMSPKafka"

// SampleDevModeEtcdRaftProfile references the sample profile used for testing
// the etcd/raft-based ordering service.
SampleDevModeEtcdRaftProfile = "SampleDevModeEtcdRaft"

// SampleSingleMSPChannelProfile references the sample profile which
// includes only the sample MSP and is used to create a channel
SampleSingleMSPChannelProfile = "SampleSingleMSPChannel"
Expand Down Expand Up @@ -172,6 +174,7 @@ type Orderer struct {
BatchTimeout time.Duration `yaml:"BatchTimeout"`
BatchSize BatchSize `yaml:"BatchSize"`
Kafka Kafka `yaml:"Kafka"`
EtcdRaft *etcdraft.Metadata `yaml:"EtcdRaft"`
Organizations []*Organization `yaml:"Organizations"`
MaxChannels uint64 `yaml:"MaxChannels"`
Capabilities map[string]bool `yaml:"Capabilities"`
Expand Down Expand Up @@ -302,17 +305,11 @@ func (t *TopLevel) completeInitialization(configDir string) {
}

if t.Orderer != nil {
t.Orderer.completeInitialization()
t.Orderer.completeInitialization(configDir)
}
}

func (p *Profile) completeInitialization(configDir string) {
if p.Orderer != nil {
for _, org := range p.Orderer.Organizations {
org.completeInitialization(configDir)
}
}

if p.Application != nil {
for _, org := range p.Application.Organizations {
org.completeInitialization(configDir)
Expand All @@ -330,9 +327,12 @@ func (p *Profile) completeInitialization(configDir string) {
}
}

// Some profiles will not define orderer parameters
if p.Orderer != nil {
p.Orderer.completeInitialization()
for _, org := range p.Orderer.Organizations {
org.completeInitialization(configDir)
}
// Some profiles will not define orderer parameters
p.Orderer.completeInitialization(configDir)
}
}

Expand All @@ -359,32 +359,51 @@ func (org *Organization) completeInitialization(configDir string) {
translatePaths(configDir, org)
}

func (oc *Orderer) completeInitialization() {
func (ord *Orderer) completeInitialization(configDir string) {
loop:
for {
switch {
case oc.OrdererType == "":
case ord.OrdererType == "":
logger.Infof("Orderer.OrdererType unset, setting to %v", genesisDefaults.Orderer.OrdererType)
oc.OrdererType = genesisDefaults.Orderer.OrdererType
case oc.Addresses == nil:
ord.OrdererType = genesisDefaults.Orderer.OrdererType
case ord.Addresses == nil:
logger.Infof("Orderer.Addresses unset, setting to %s", genesisDefaults.Orderer.Addresses)
oc.Addresses = genesisDefaults.Orderer.Addresses
case oc.BatchTimeout == 0:
ord.Addresses = genesisDefaults.Orderer.Addresses
case ord.BatchTimeout == 0:
logger.Infof("Orderer.BatchTimeout unset, setting to %s", genesisDefaults.Orderer.BatchTimeout)
oc.BatchTimeout = genesisDefaults.Orderer.BatchTimeout
case oc.BatchSize.MaxMessageCount == 0:
ord.BatchTimeout = genesisDefaults.Orderer.BatchTimeout
case ord.BatchSize.MaxMessageCount == 0:
logger.Infof("Orderer.BatchSize.MaxMessageCount unset, setting to %v", genesisDefaults.Orderer.BatchSize.MaxMessageCount)
oc.BatchSize.MaxMessageCount = genesisDefaults.Orderer.BatchSize.MaxMessageCount
case oc.BatchSize.AbsoluteMaxBytes == 0:
ord.BatchSize.MaxMessageCount = genesisDefaults.Orderer.BatchSize.MaxMessageCount
case ord.BatchSize.AbsoluteMaxBytes == 0:
logger.Infof("Orderer.BatchSize.AbsoluteMaxBytes unset, setting to %v", genesisDefaults.Orderer.BatchSize.AbsoluteMaxBytes)
oc.BatchSize.AbsoluteMaxBytes = genesisDefaults.Orderer.BatchSize.AbsoluteMaxBytes
case oc.BatchSize.PreferredMaxBytes == 0:
ord.BatchSize.AbsoluteMaxBytes = genesisDefaults.Orderer.BatchSize.AbsoluteMaxBytes
case ord.BatchSize.PreferredMaxBytes == 0:
logger.Infof("Orderer.BatchSize.PreferredMaxBytes unset, setting to %v", genesisDefaults.Orderer.BatchSize.PreferredMaxBytes)
oc.BatchSize.PreferredMaxBytes = genesisDefaults.Orderer.BatchSize.PreferredMaxBytes
case oc.Kafka.Brokers == nil:
logger.Infof("Orderer.Kafka.Brokers unset, setting to %v", genesisDefaults.Orderer.Kafka.Brokers)
oc.Kafka.Brokers = genesisDefaults.Orderer.Kafka.Brokers
ord.BatchSize.PreferredMaxBytes = genesisDefaults.Orderer.BatchSize.PreferredMaxBytes
default:
return
break loop
}
}

// Additional, consensus type-dependent initialization goes here
switch ord.OrdererType {
case "kafka":
if ord.Kafka.Brokers == nil {
logger.Infof("Orderer.Kafka unset, setting to %v", genesisDefaults.Orderer.Kafka.Brokers)
ord.Kafka.Brokers = genesisDefaults.Orderer.Kafka.Brokers
}
case etcdraft.TypeKey:
if ord.EtcdRaft == nil {
logger.Panicf("%s raft configuration missing", etcdraft.TypeKey)
}
for _, c := range ord.EtcdRaft.GetConsenters() {
clientCertPath := string(c.GetClientTlsCert())
cf.TranslatePathInPlace(configDir, &clientCertPath)
c.ClientTlsCert = []byte(clientCertPath)
serverCertPath := string(c.GetServerTlsCert())
cf.TranslatePathInPlace(configDir, &serverCertPath)
c.ServerTlsCert = []byte(serverCertPath)
}
}
}
Expand Down
33 changes: 28 additions & 5 deletions common/tools/configtxgen/localconfig/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,11 @@ package localconfig
import (
"testing"

"github.com/hyperledger/fabric/common/flogging"
"github.com/hyperledger/fabric/core/config/configtest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func init() {
flogging.SetModuleLevel(pkgLogID, "DEBUG")
}

func TestLoadProfile(t *testing.T) {
cleanup := configtest.SetDevFabricConfigPath(t)
defer cleanup()
Expand Down Expand Up @@ -80,3 +75,31 @@ func TestLoadTopLevelWithPath(t *testing.T) {
assert.NotNil(t, topLevel.Organizations, "organizations should not be nil")
assert.NotNil(t, topLevel.Profiles, "profiles should not be nil")
}

func TestConsensusSpecificInit(t *testing.T) {
cleanup := configtest.SetDevFabricConfigPath(t)
defer cleanup()

devConfigDir, err := configtest.GetDevConfigDir()
require.NoError(t, err)

t.Run("solo", func(t *testing.T) {
profile := &Profile{
Orderer: &Orderer{
OrdererType: "solo",
},
}
profile.completeInitialization(devConfigDir)
assert.Nil(t, profile.Orderer.Kafka.Brokers, "Kafka config settings should not be set")
})

t.Run("kafka", func(t *testing.T) {
profile := &Profile{
Orderer: &Orderer{
OrdererType: "kafka",
},
}
profile.completeInitialization(devConfigDir)
assert.NotNil(t, profile.Orderer.Kafka.Brokers, "Kafka config settings should be set")
})
}
53 changes: 53 additions & 0 deletions protos/orderer/etcdraft/configuration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package etcdraft

import (
fmt "fmt"
"io/ioutil"

"github.com/hyperledger/fabric/protos/orderer"

"github.com/golang/protobuf/proto"
)

// TypeKey is the string with which this consensus implementation is identified across Fabric.
const TypeKey = "etcdraft"

func init() {
orderer.ConsensusTypeMetadataMap[TypeKey] = ConsensusTypeMetadataFactory{}
}

// ConsensusTypeMetadataFactory allows this implementation's proto messages to register
// their type with the orderer's proto messages. This is needed for protolator to work.
type ConsensusTypeMetadataFactory struct{}

// NewMessage implements the Orderer.ConsensusTypeMetadataFactory interface.
func (dogf ConsensusTypeMetadataFactory) NewMessage() proto.Message {
return &Metadata{}
}

// Marshal serializes this implementation's proto messages. It is called by the encoder package
// during the creation of the Orderer ConfigGroup.
func Marshal(md *Metadata) ([]byte, error) {
for _, c := range md.Consenters {
// Expect the user to set the config value for client/server certs to the
// path where they are persisted locally, then load these files to memory.
clientCert, err := ioutil.ReadFile(string(c.GetClientTlsCert()))
if err != nil {
return nil, fmt.Errorf("cannot load client cert for consenter %s:%d: %s", c.GetHost(), c.GetPort(), err)
}
c.ClientTlsCert = clientCert

serverCert, err := ioutil.ReadFile(string(c.GetServerTlsCert()))
if err != nil {
return nil, fmt.Errorf("cannot load server cert for consenter %s:%d: %s", c.GetHost(), c.GetPort(), err)
}
c.ServerTlsCert = serverCert
}
return proto.Marshal(md)
}
Loading

0 comments on commit 808093b

Please sign in to comment.