From c2eb805f0d6520bb17f69af9922a479ffc771376 Mon Sep 17 00:00:00 2001 From: Luis Sanchez Date: Wed, 3 May 2017 10:39:59 -0400 Subject: [PATCH] [FAB-3658] improve h/f/orderer coverage improve hyperledger/fabric/orderer coverage to 86.3% Change-Id: If2c79c1b7b4eec8a1d969a09590a91176b775ef4 Signed-off-by: Luis Sanchez --- orderer/main.go | 124 ++++++++++-------- orderer/main_test.go | 290 +++++++++++++++++++++++++++++++++++++++++++ orderer/util.go | 4 +- orderer/util_test.go | 69 ++++++++++ 4 files changed, 431 insertions(+), 56 deletions(-) create mode 100644 orderer/main_test.go diff --git a/orderer/main.go b/orderer/main.go index 5806d156ff0..5f0498bdd58 100644 --- a/orderer/main.go +++ b/orderer/main.go @@ -27,10 +27,12 @@ import ( genesisconfig "github.com/hyperledger/fabric/common/configtx/tool/localconfig" "github.com/hyperledger/fabric/common/configtx/tool/provisional" + "github.com/hyperledger/fabric/common/crypto" "github.com/hyperledger/fabric/common/flogging" "github.com/hyperledger/fabric/core/comm" "github.com/hyperledger/fabric/orderer/common/bootstrap/file" "github.com/hyperledger/fabric/orderer/kafka" + "github.com/hyperledger/fabric/orderer/ledger" "github.com/hyperledger/fabric/orderer/localconfig" "github.com/hyperledger/fabric/orderer/multichain" "github.com/hyperledger/fabric/orderer/sbft" @@ -49,28 +51,38 @@ var logger = logging.MustGetLogger("orderer/main") func main() { conf := config.Load() + initializeLoggingLevel(conf) + initializeProfilingService(conf) + grpcServer := initializeGrpcServer(conf) + initializeLocalMsp(conf) + signer := localmsp.NewSigner() + manager := initializeMultiChainManager(conf, signer) + server := NewServer(manager, signer) + ab.RegisterAtomicBroadcastServer(grpcServer.Server(), server) + logger.Info("Beginning to serve requests") + grpcServer.Start() +} - // Set the logging level +// Set the logging level +func initializeLoggingLevel(conf *config.TopLevel) { flogging.InitFromSpec(conf.General.LogLevel) if conf.Kafka.Verbose { sarama.Logger = log.New(os.Stdout, "[sarama] ", log.Lshortfile) } +} - // Start the profiling service if enabled. - // The ListenAndServe() call does not return unless an error occurs. +// Start the profiling service if enabled. +func initializeProfilingService(conf *config.TopLevel) { if conf.General.Profile.Enabled { go func() { logger.Info("Starting Go pprof profiling service on:", conf.General.Profile.Address) + // The ListenAndServe() call does not return unless an error occurs. logger.Panic("Go pprof service failed:", http.ListenAndServe(conf.General.Profile.Address, nil)) }() } +} - lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", conf.General.ListenAddress, conf.General.ListenPort)) - if err != nil { - logger.Error("Failed to listen:", err) - return - } - +func initializeSecureServerConfig(conf *config.TopLevel) comm.SecureServerConfig { // secure server config secureConfig := comm.SecureServerConfig{ UseTLS: conf.General.TLS.Enabled, @@ -114,52 +126,67 @@ func main() { secureConfig.ServerRootCAs = serverRootCAs secureConfig.ClientRootCAs = clientRootCAs } + return secureConfig +} + +func initializeBootstrapChannel(conf *config.TopLevel, lf ledger.Factory) { + var genesisBlock *cb.Block + + // Select the bootstrapping mechanism + switch conf.General.GenesisMethod { + case "provisional": + genesisBlock = provisional.New(genesisconfig.Load(conf.General.GenesisProfile)).GenesisBlock() + case "file": + genesisBlock = file.New(conf.General.GenesisFile).GenesisBlock() + default: + logger.Panic("Unknown genesis method:", conf.General.GenesisMethod) + } + + chainID, err := utils.GetChainIDFromBlock(genesisBlock) + if err != nil { + logger.Fatal("Failed to parse chain ID from genesis block:", err) + } + gl, err := lf.GetOrCreate(chainID) + if err != nil { + logger.Fatal("Failed to create the system chain:", err) + } + + err = gl.Append(genesisBlock) + if err != nil { + logger.Fatal("Could not write genesis block to ledger:", err) + } +} + +func initializeGrpcServer(conf *config.TopLevel) comm.GRPCServer { + secureConfig := initializeSecureServerConfig(conf) + + lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", conf.General.ListenAddress, conf.General.ListenPort)) + if err != nil { + logger.Fatal("Failed to listen:", err) + } // Create GRPC server - return if an error occurs grpcServer, err := comm.NewGRPCServerFromListener(lis, secureConfig) if err != nil { - logger.Error("Failed to return new GRPC server:", err) - return + logger.Fatal("Failed to return new GRPC server:", err) } + return grpcServer +} + +func initializeLocalMsp(conf *config.TopLevel) { // Load local MSP - err = mspmgmt.LoadLocalMsp(conf.General.LocalMSPDir, conf.General.BCCSP, conf.General.LocalMSPID) + err := mspmgmt.LoadLocalMsp(conf.General.LocalMSPDir, conf.General.BCCSP, conf.General.LocalMSPID) if err != nil { // Handle errors reading the config file - logger.Panic("Failed to initialize local MSP:", err) + logger.Fatal("Failed to initialize local MSP:", err) } +} +func initializeMultiChainManager(conf *config.TopLevel, signer crypto.LocalSigner) multichain.Manager { lf, _ := createLedgerFactory(conf) - // Are we bootstrapping? if len(lf.ChainIDs()) == 0 { - var genesisBlock *cb.Block - - // Select the bootstrapping mechanism - switch conf.General.GenesisMethod { - case "provisional": - genesisBlock = provisional.New(genesisconfig.Load(conf.General.GenesisProfile)).GenesisBlock() - case "file": - genesisBlock = file.New(conf.General.GenesisFile).GenesisBlock() - default: - logger.Panic("Unknown genesis method:", conf.General.GenesisMethod) - } - - chainID, err := utils.GetChainIDFromBlock(genesisBlock) - if err != nil { - logger.Error("Failed to parse chain ID from genesis block:", err) - return - } - gl, err := lf.GetOrCreate(chainID) - if err != nil { - logger.Error("Failed to create the system chain:", err) - return - } - - err = gl.Append(genesisBlock) - if err != nil { - logger.Error("Could not write genesis block to ledger:", err) - return - } + initializeBootstrapChannel(conf, lf) } else { logger.Info("Not bootstrapping because of existing chains") } @@ -169,16 +196,5 @@ func main() { consenters["kafka"] = kafka.New(conf.Kafka.Version, conf.Kafka.Retry, conf.Kafka.TLS) consenters["sbft"] = sbft.New(makeSbftConsensusConfig(conf), makeSbftStackConfig(conf)) - signer := localmsp.NewSigner() - - manager := multichain.NewManagerImpl(lf, consenters, signer) - - server := NewServer( - manager, - signer, - ) - - ab.RegisterAtomicBroadcastServer(grpcServer.Server(), server) - logger.Info("Beginning to serve requests") - grpcServer.Start() + return multichain.NewManagerImpl(lf, consenters, signer) } diff --git a/orderer/main_test.go b/orderer/main_test.go new file mode 100644 index 00000000000..35aa90c127f --- /dev/null +++ b/orderer/main_test.go @@ -0,0 +1,290 @@ +/* +Copyright IBM Corp. 2017 All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "io/ioutil" + "log" + "net" + "net/http" + "os" + "strconv" + "strings" + "testing" + "time" + + "github.com/Shopify/sarama" + "github.com/hyperledger/fabric/bccsp/factory" + "github.com/hyperledger/fabric/common/flogging" + "github.com/hyperledger/fabric/common/localmsp" + coreconfig "github.com/hyperledger/fabric/core/config" + config "github.com/hyperledger/fabric/orderer/localconfig" + logging "github.com/op/go-logging" + // logging "github.com/op/go-logging" + "github.com/stretchr/testify/assert" +) + +func TestInitializeLoggingLevel(t *testing.T) { + initializeLoggingLevel( + &config.TopLevel{ + General: config.General{LogLevel: "debug"}, + Kafka: config.Kafka{Verbose: true}, + }, + ) + assert.Equal(t, flogging.GetModuleLevel("orderer/main"), "DEBUG") + assert.NotNil(t, sarama.Logger) +} + +func TestInitializeProfilingService(t *testing.T) { + // get a free random port + listenAddr := func() string { + l, _ := net.Listen("tcp", "localhost:0") + l.Close() + return l.Addr().String() + }() + initializeProfilingService( + &config.TopLevel{ + General: config.General{ + LogLevel: "debug", + Profile: config.Profile{ + Enabled: true, + Address: listenAddr, + }}, + Kafka: config.Kafka{Verbose: true}, + }, + ) + time.Sleep(500 * time.Millisecond) + if _, err := http.Get("http://" + listenAddr + "/" + "/debug/"); err != nil { + t.Logf("Expected pprof to be up (will retry again in 3 seconds): %s", err) + time.Sleep(3 * time.Second) + if _, err := http.Get("http://" + listenAddr + "/" + "/debug/"); err != nil { + t.Fatalf("Expected pprof to be up: %s", err) + } + } +} + +func TestInitializeSecureServerConfig(t *testing.T) { + initializeSecureServerConfig( + &config.TopLevel{ + General: config.General{ + TLS: config.TLS{ + Enabled: true, + ClientAuthEnabled: true, + Certificate: "main.go", + PrivateKey: "main.go", + RootCAs: []string{"main.go"}, + ClientRootCAs: []string{"main.go"}, + }, + }, + }) + + goodFile := "main.go" + badFile := "does_not_exist" + + logger.SetBackend(logging.AddModuleLevel(newPanicOnCriticalBackend())) + defer func() { + logger = logging.MustGetLogger("orderer/main") + }() + + testCases := []struct { + name string + certificate string + privateKey string + rootCA string + clientCertificate string + }{ + {"BadCertificate", badFile, goodFile, goodFile, goodFile}, + {"BadPrivateKey", goodFile, badFile, goodFile, goodFile}, + {"BadRootCA", goodFile, goodFile, badFile, goodFile}, + {"BadClientCertificate", goodFile, goodFile, goodFile, badFile}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Panics(t, func() { + initializeSecureServerConfig( + &config.TopLevel{ + General: config.General{ + TLS: config.TLS{ + Enabled: true, + ClientAuthEnabled: true, + Certificate: tc.certificate, + PrivateKey: tc.privateKey, + RootCAs: []string{tc.rootCA}, + ClientRootCAs: []string{tc.clientCertificate}, + }, + }, + }) + }, + ) + }) + } +} + +func TestInitializeBootstrapChannel(t *testing.T) { + testCases := []struct { + genesisMethod string + ledgerType string + panics bool + }{ + {"provisional", "ram", false}, + {"provisional", "file", false}, + {"provisional", "json", false}, + {"invalid", "ram", true}, + {"file", "ram", true}, + } + + for _, tc := range testCases { + + t.Run(tc.genesisMethod+"/"+tc.ledgerType, func(t *testing.T) { + + fileLedgerLocation, _ := ioutil.TempDir("", "test-ledger") + ledgerFactory, _ := createLedgerFactory( + &config.TopLevel{ + General: config.General{LedgerType: tc.ledgerType}, + FileLedger: config.FileLedger{ + Location: fileLedgerLocation, + }, + }, + ) + + bootstrapConfig := &config.TopLevel{ + General: config.General{ + GenesisMethod: tc.genesisMethod, + GenesisProfile: "SampleSingleMSPSolo", + GenesisFile: "genesisblock", + }, + } + + if tc.panics { + assert.Panics(t, func() { + initializeBootstrapChannel(bootstrapConfig, ledgerFactory) + }) + } else { + initializeBootstrapChannel(bootstrapConfig, ledgerFactory) + } + + }) + } +} + +func TestInitializeLocalMsp(t *testing.T) { + t.Run("Happy", func(t *testing.T) { + assert.NotPanics(t, func() { + localMSPDir, _ := coreconfig.GetDevMspDir() + initializeLocalMsp( + &config.TopLevel{ + General: config.General{ + LocalMSPDir: localMSPDir, + LocalMSPID: "DEFAULT", + BCCSP: &factory.FactoryOpts{ + ProviderName: "SW", + SwOpts: &factory.SwOpts{ + HashFamily: "SHA2", + SecLevel: 256, + Ephemeral: true, + }, + }, + }, + }) + }) + }) + t.Run("Error", func(t *testing.T) { + logger.SetBackend(logging.AddModuleLevel(newPanicOnCriticalBackend())) + defer func() { + logger = logging.MustGetLogger("orderer/main") + }() + assert.Panics(t, func() { + initializeLocalMsp( + &config.TopLevel{ + General: config.General{ + LocalMSPDir: "", + LocalMSPID: "", + }, + }) + }) + }) +} + +func TestInitializeMultiChainManager(t *testing.T) { + localMSPDir, _ := coreconfig.GetDevMspDir() + conf := &config.TopLevel{ + General: config.General{ + LedgerType: "ram", + GenesisMethod: "provisional", + GenesisProfile: "SampleSingleMSPSolo", + LocalMSPDir: localMSPDir, + LocalMSPID: "DEFAULT", + BCCSP: &factory.FactoryOpts{ + ProviderName: "SW", + SwOpts: &factory.SwOpts{ + HashFamily: "SHA2", + SecLevel: 256, + Ephemeral: true, + }, + }, + }, + } + assert.NotPanics(t, func() { + initializeLocalMsp(conf) + initializeMultiChainManager(conf, localmsp.NewSigner()) + }) +} + +func TestInitializeGrpcServer(t *testing.T) { + // get a free random port + listenAddr := func() string { + l, _ := net.Listen("tcp", "localhost:0") + l.Close() + return l.Addr().String() + }() + host := strings.Split(listenAddr, ":")[0] + port, _ := strconv.ParseUint(strings.Split(listenAddr, ":")[1], 10, 16) + assert.NotPanics(t, func() { + grpcServer := initializeGrpcServer( + &config.TopLevel{ + General: config.General{ + ListenAddress: host, + ListenPort: uint16(port), + TLS: config.TLS{ + Enabled: false, + ClientAuthEnabled: false, + }, + }, + }) + grpcServer.Listener().Close() + }) +} + +// var originalLogger *Logger + +func newPanicOnCriticalBackend() *panicOnCriticalBackend { + return &panicOnCriticalBackend{ + backend: logging.AddModuleLevel(logging.NewLogBackend(os.Stderr, "", log.LstdFlags)), + } +} + +type panicOnCriticalBackend struct { + backend logging.Backend +} + +func (b *panicOnCriticalBackend) Log(level logging.Level, calldepth int, record *logging.Record) error { + err := b.backend.Log(level, calldepth, record) + if level == logging.CRITICAL { + panic(record.Formatted(calldepth)) + } + return err +} diff --git a/orderer/util.go b/orderer/util.go index 99231886b8f..1cb41335013 100644 --- a/orderer/util.go +++ b/orderer/util.go @@ -81,9 +81,9 @@ func createSubDir(parentDirPath string, subDir string) (string, bool) { logger.Panic("Error creating sub dir:", err) } created = true - } else { - logger.Debugf("Found %s sub-dir and using it", fsblkstorage.ChainsDir) } + } else { + logger.Debugf("Found %s sub-dir and using it", fsblkstorage.ChainsDir) } return subDirPath, created } diff --git a/orderer/util_test.go b/orderer/util_test.go index 54aa06c7a0b..7c3ed5b954d 100644 --- a/orderer/util_test.go +++ b/orderer/util_test.go @@ -19,8 +19,12 @@ package main import ( "os" "testing" + "time" config "github.com/hyperledger/fabric/orderer/localconfig" + "github.com/hyperledger/fabric/orderer/sbft" + "github.com/hyperledger/fabric/orderer/sbft/backend" + "github.com/stretchr/testify/assert" ) func TestCreateLedgerFactory(t *testing.T) { @@ -103,4 +107,69 @@ func TestCreateSubDir(t *testing.T) { } }) } + t.Run("ParentDirNotExists", func(t *testing.T) { + assert.Panics(t, func() { createSubDir(os.TempDir(), "foo/name") }) + }) +} + +func TestCreateTempDir(t *testing.T) { + t.Run("Good", func(t *testing.T) { + tempDir := createTempDir("foo") + if _, err := os.Stat(tempDir); err != nil { + t.Fatal(err) + } + }) + + t.Run("Bad", func(t *testing.T) { + assert.Panics(t, func() { + createTempDir("foo/bar") + }) + }) + +} + +func TestMakeSbftStackConfig(t *testing.T) { + stackConfig := makeSbftStackConfig( + &config.TopLevel{ + SbftLocal: config.SbftLocal{ + PeerCommAddr: "0.0.0.0:0", + CertFile: "certfile", + KeyFile: "keyfile", + DataDir: "datadir", + }, + }, + ) + assert.NotNil(t, stackConfig) + assert.IsType(t, &backend.StackConfig{}, stackConfig) + assert.Equal(t, "0.0.0.0:0", stackConfig.ListenAddr) + assert.Equal(t, "certfile", stackConfig.CertFile) + assert.Equal(t, "keyfile", stackConfig.KeyFile) + assert.Equal(t, "datadir", stackConfig.DataDir) +} + +func TestMakeSbftConsensusConfig(t *testing.T) { + consensusConfig := makeSbftConsensusConfig( + &config.TopLevel{ + Genesis: config.Genesis{ + DeprecatedBatchSize: 1, + DeprecatedBatchTimeout: 2 * time.Second, + SbftShared: config.SbftShared{ + F: 3, + N: 4, + Peers: map[string]string{ + "peer": "PEM", + }, + RequestTimeoutNsec: 5, + }, + }, + }, + ) + assert.NotNil(t, consensusConfig) + assert.IsType(t, &sbft.ConsensusConfig{}, consensusConfig) + assert.EqualValues(t, 1, consensusConfig.GetConsensus().BatchSizeBytes) + assert.EqualValues(t, 2*time.Second, consensusConfig.GetConsensus().BatchDurationNsec) + assert.EqualValues(t, 3, consensusConfig.GetConsensus().F) + assert.EqualValues(t, 4, consensusConfig.GetConsensus().N) + assert.EqualValues(t, 5, consensusConfig.GetConsensus().RequestTimeoutNsec) + assert.Len(t, consensusConfig.Peers, 1) }