Skip to content

Commit

Permalink
FAB-13264 consensus migration: kafka2raft green path #1
Browse files Browse the repository at this point in the history
This is the first of four (1/4) sub-tasks that focus on the
"green" path of consensus-type migration from Kafka to Raft. 

By "green" we mean that there are no failures or aborts along
the way. The flow of the green path and the changes made in
these 4 tasks are described below. The 4 sub-tasks are staged
in a way that minimizes dependencies between them.

In this sub-task we introduce changes to the 
orderer/common/bootstrap package (see details below).
In essence, Just before the last config block of the system
channel (COMMIT) is written to the ledger, the bootstrap file
(a.k.a "genesis.block", do not confuse with the first block of
the ledger) is swapped with the last block of the system
channel. This sub-task extends package orderer/common/bootstrap
to support this functionality.

See respective JIRA item for further details.

The "green" path for migration is the following:

1. Start with a Kafka-based ordering service
2. Send a config update tx (START-TX) on the system channel that:
 - Has ConsensusType.MigrationState=START
 - This will disable the creation of new channels
 - This will disable the processing of normal (standard channel) transactions
3. Wait until the START-TX is committed and get the block height H of that tx
4. Send a config update tx (CONTEXT-TX) on each of the standard channels that:
 - Has ConsensusType.MigrationState=CONTEXT
 - Has ConsensusType.MigrationContext=H
 - Has ConsensusType.Type="etcdraft"
 - Has ConsensusType.Metadata=<a marshaled etcdraft metadata: Consenters,
   Options, etc>
5. Send a config update tx (COMMIT-TX) on the system channel that:
 - Has ConsensusType.MigrationState=COMMIT
 - Has ConsensusType.MigrationContext=H
 - Has ConsensusType.Type="etcdraft"
 - Has ConsensusType.Metadata=<a marshaled etcdraft metadata: Consenters,
   Options, etc>
 - The metadata should be the same as for the standard channels, with the same
   precautions.
 - If committed successfully, no further configuration will be possible
6. Restart each orderer
 - The orderer will bootstrap into an etcdraft mode
 - Each channel will form a cluster
 - Normal transactions can resume now
7. In order to configure the channels (system or standard), make sure that
   the first
   config update tx (on any given channel) after migration has:
 - Has ConsensusType.MigrationState=NONE
 - Has ConsensusType.MigrationS=NONE
 - In addition to other changes to the channel's config.

Change-Id: Iccd146bb7260bafa4e4d8c4ee457d2ac19f5a642
Signed-off-by: Yoav Tock <tock@il.ibm.com>
  • Loading branch information
tock-ibm authored and yacovm committed Jan 30, 2019
1 parent 8fed497 commit bccbd4d
Show file tree
Hide file tree
Showing 3 changed files with 346 additions and 101 deletions.
40 changes: 25 additions & 15 deletions orderer/common/bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
/*
Copyright IBM Corp. 2016 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.
*/
// Copyright IBM Corp. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package bootstrap

Expand All @@ -26,3 +13,26 @@ type Helper interface {
// the ledger (be it reading from the filesystem, generating it, etc.)
GenesisBlock() *ab.Block
}

// Replacer provides the ability to to replace the current genesis block used
// for bootstrapping with the supplied block. It is used during consensus-type
// migration in order to replace the original genesis block used for
// bootstrapping with the latest config block of the system channel, which
// contains the new consensus-type. This will ensure the instantiation of the
// correct consenter type when the server restarts.
type Replacer interface {
// ReplaceGenesisBlockFile should first copy the current file to a backup
// file: <genesis-file-name> => <genesis-file-name>.bak
// and then overwrite the original file with the content of the given block.
// If something goes wrong during migration, the original file could be
// restored from the backup.
// An error is returned if the operation was not completed successfully.
ReplaceGenesisBlockFile(block *ab.Block) error

// CheckReadWrite checks whether the current file is readable and writable,
// because if it is not, there is no point in attempting to replace. This
// check is performed at the beginning of the consensus-type migration
// process.
// An error is returned if the file is not readable and writable.
CheckReadWrite() error
}
109 changes: 89 additions & 20 deletions orderer/common/bootstrap/file/bootstrap.go
Original file line number Diff line number Diff line change
@@ -1,52 +1,121 @@
/*
Copyright IBM Corp. 2016 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.
*/
// Copyright IBM Corp. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package file

import (
"fmt"
"io"
"io/ioutil"
"os"

"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric/orderer/common/bootstrap"
cb "github.com/hyperledger/fabric/protos/common"
"github.com/pkg/errors"
)

type fileBootstrapper struct {
GenesisBlockFile string
}

// New returns a new static bootstrap helper
// New returns a new static bootstrap helper.
func New(fileName string) bootstrap.Helper {
return &fileBootstrapper{
GenesisBlockFile: fileName,
}
}

// GenesisBlock returns the genesis block to be used for bootstrapping
// NewReplacer returns a new bootstrap replacer.
func NewReplacer(fileName string) bootstrap.Replacer {
return &fileBootstrapper{
GenesisBlockFile: fileName,
}
}

// GenesisBlock returns the genesis block to be used for bootstrapping.
func (b *fileBootstrapper) GenesisBlock() *cb.Block {
bootstrapFile, fileErr := ioutil.ReadFile(b.GenesisBlockFile)
if fileErr != nil {
panic(fmt.Errorf("Unable to bootstrap orderer. Error reading genesis block file: %v", fileErr))
panic(errors.Errorf("unable to bootstrap orderer. Error reading genesis block file: %v", fileErr))
}
genesisBlock := &cb.Block{}
unmarshallErr := proto.Unmarshal(bootstrapFile, genesisBlock)
if unmarshallErr != nil {
panic(fmt.Errorf("Unable to bootstrap orderer. Error unmarshalling genesis block: %v", unmarshallErr))
panic(errors.Errorf("unable to bootstrap orderer. Error unmarshalling genesis block: %v", unmarshallErr))

}
return genesisBlock
} // GenesisBlock

// ReplaceGenesisBlockFile creates a backup of the genesis block file, and then replaces
// it with the content of the given block.
// This is used during consensus-type migration in order to generate a bootstrap file that
// specifies the new consensus-type.
func (b *fileBootstrapper) ReplaceGenesisBlockFile(block *cb.Block) error {
buff, marshalErr := proto.Marshal(block)
if marshalErr != nil {
return errors.Wrap(marshalErr, "could not marshal block into a []byte")
}

genFileStat, statErr := os.Stat(b.GenesisBlockFile)
if statErr != nil {
return errors.Wrapf(statErr, "could not get the os.Stat of the genesis block file: %s", b.GenesisBlockFile)
}

if !genFileStat.Mode().IsRegular() {
return errors.Errorf("genesis block file: %s, is not a regular file", b.GenesisBlockFile)
}

backupFile := b.GenesisBlockFile + ".bak"
if err := backupGenesisFile(b.GenesisBlockFile, backupFile); err != nil {
return errors.Wrapf(err, "could not copy genesis block file (%s) into backup file: %s",
b.GenesisBlockFile, backupFile)
}

if err := ioutil.WriteFile(b.GenesisBlockFile, buff, genFileStat.Mode()); err != nil {
return errors.Wrapf(err, "could not write new genesis block into file: %s; use backup if necessary: %s",
b.GenesisBlockFile, backupFile)
}

return nil
}

func backupGenesisFile(src, dst string) error {
source, err := os.Open(src)
if err != nil {
return err
}
defer source.Close()

destination, err := os.Create(dst)
if err != nil {
return err
}
defer destination.Close()

_, err = io.Copy(destination, source)
return err
}

func (b *fileBootstrapper) CheckReadWrite() error {
genFileStat, statErr := os.Stat(b.GenesisBlockFile)
if statErr != nil {
return errors.Wrapf(statErr, "could not get the os.Stat of the genesis block file: %s", b.GenesisBlockFile)
}

if !genFileStat.Mode().IsRegular() {
return errors.Errorf("genesis block file: %s, is not a regular file", b.GenesisBlockFile)
}

genFile, openErr := os.OpenFile(b.GenesisBlockFile, os.O_RDWR, genFileStat.Mode().Perm())
if openErr != nil {
if os.IsPermission(openErr) {
return errors.Wrapf(openErr, "genesis block file: %s, cannot be opened for read-write, check permissions", b.GenesisBlockFile)
} else {
return errors.Wrapf(openErr, "genesis block file: %s, cannot be opened for read-write", b.GenesisBlockFile)
}
}
genFile.Close()

return nil
}
Loading

0 comments on commit bccbd4d

Please sign in to comment.