github.com/leonlxy/hyperledger@v1.0.0-alpha.0.20170427033203-34922035d248/orderer/kafka/orderer.go (about)

     1  /*
     2  Copyright IBM Corp. 2016 All Rights Reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8                   http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package kafka
    18  
    19  import (
    20  	"time"
    21  
    22  	"github.com/Shopify/sarama"
    23  	"github.com/golang/protobuf/proto"
    24  	"github.com/hyperledger/fabric/orderer/localconfig"
    25  	"github.com/hyperledger/fabric/orderer/multichain"
    26  	cb "github.com/hyperledger/fabric/protos/common"
    27  	ab "github.com/hyperledger/fabric/protos/orderer"
    28  	"github.com/hyperledger/fabric/protos/utils"
    29  )
    30  
    31  // New creates a Kafka-backed consenter. Called by orderer's main.go.
    32  func New(kv sarama.KafkaVersion, ro config.Retry, tls config.TLS) multichain.Consenter {
    33  	return newConsenter(kv, ro, tls, bfValue, pfValue, cfValue)
    34  }
    35  
    36  // New calls here because we need to pass additional arguments to
    37  // the constructor and New() should only read from the config file.
    38  func newConsenter(kv sarama.KafkaVersion, ro config.Retry, tls config.TLS, bf bfType, pf pfType, cf cfType) multichain.Consenter {
    39  	return &consenterImpl{kv, ro, tls, bf, pf, cf}
    40  }
    41  
    42  // bfType defines the signature of the broker constructor.
    43  type bfType func([]string, ChainPartition) (Broker, error)
    44  
    45  // pfType defines the signature of the producer constructor.
    46  type pfType func([]string, sarama.KafkaVersion, config.Retry, config.TLS) Producer
    47  
    48  // cfType defines the signature of the consumer constructor.
    49  type cfType func([]string, sarama.KafkaVersion, config.TLS, ChainPartition, int64) (Consumer, error)
    50  
    51  // bfValue holds the value for the broker constructor that's used in the non-test case.
    52  var bfValue = func(brokers []string, cp ChainPartition) (Broker, error) {
    53  	return newBroker(brokers, cp)
    54  }
    55  
    56  // pfValue holds the value for the producer constructor that's used in the non-test case.
    57  var pfValue = func(brokers []string, kafkaVersion sarama.KafkaVersion, retryOptions config.Retry, tls config.TLS) Producer {
    58  	return newProducer(brokers, kafkaVersion, retryOptions, tls)
    59  }
    60  
    61  // cfValue holds the value for the consumer constructor that's used in the non-test case.
    62  var cfValue = func(brokers []string, kafkaVersion sarama.KafkaVersion, tls config.TLS, cp ChainPartition, offset int64) (Consumer, error) {
    63  	return newConsumer(brokers, kafkaVersion, tls, cp, offset)
    64  }
    65  
    66  // consenterImpl holds the implementation of type that satisfies the
    67  // multichain.Consenter and testableConsenter interfaces. The former
    68  // is needed because that is what the HandleChain contract requires.
    69  // The latter is needed for testing.
    70  type consenterImpl struct {
    71  	kv  sarama.KafkaVersion
    72  	ro  config.Retry
    73  	tls config.TLS
    74  	bf  bfType
    75  	pf  pfType
    76  	cf  cfType
    77  }
    78  
    79  // HandleChain creates/returns a reference to a Chain for the given set of support resources.
    80  // Implements the multichain.Consenter interface. Called by multichain.newChainSupport(), which
    81  // is itself called by multichain.NewManagerImpl() when ranging over the ledgerFactory's existingChains.
    82  func (co *consenterImpl) HandleChain(cs multichain.ConsenterSupport, metadata *cb.Metadata) (multichain.Chain, error) {
    83  	return newChain(co, cs, getLastOffsetPersisted(metadata, cs.ChainID())), nil
    84  }
    85  
    86  func getLastOffsetPersisted(metadata *cb.Metadata, chainID string) int64 {
    87  	if metadata.Value != nil {
    88  		// Extract orderer-related metadata from the tip of the ledger first
    89  		kafkaMetadata := &ab.KafkaMetadata{}
    90  		if err := proto.Unmarshal(metadata.Value, kafkaMetadata); err != nil {
    91  			logger.Panicf("[channel: %s] Ledger may be corrupted:"+
    92  				"cannot unmarshal orderer metadata in most recent block", chainID)
    93  		}
    94  		return kafkaMetadata.LastOffsetPersisted
    95  	}
    96  	return (sarama.OffsetOldest - 1) // default
    97  }
    98  
    99  // When testing we need to inject our own broker/producer/consumer.
   100  // Therefore we need to (a) hold a reference to an object that stores
   101  // the broker/producer/consumer constructors, and (b) refer to that
   102  // object via its interface type, so that we can use a different
   103  // implementation when testing. This, in turn, calls for (c) —- the
   104  // definition of an interface (see testableConsenter below) that will
   105  // be satisfied by both the actual and the mock object and will allow
   106  // us to retrieve these constructors.
   107  func newChain(consenter testableConsenter, support multichain.ConsenterSupport, lastOffsetPersisted int64) *chainImpl {
   108  	lastCutBlock := support.Height() - 1
   109  	logger.Debugf("[channel: %s] Starting chain with last persisted offset %d and last recorded block %d",
   110  		support.ChainID(), lastOffsetPersisted, lastCutBlock)
   111  	return &chainImpl{
   112  		consenter:           consenter,
   113  		support:             support,
   114  		partition:           newChainPartition(support.ChainID(), rawPartition),
   115  		batchTimeout:        support.SharedConfig().BatchTimeout(),
   116  		lastOffsetPersisted: lastOffsetPersisted,
   117  		lastCutBlock:        lastCutBlock,
   118  		producer:            consenter.prodFunc()(support.SharedConfig().KafkaBrokers(), consenter.kafkaVersion(), consenter.retryOptions(), consenter.tlsConfig()),
   119  		halted:              false, // Redundant as the default value for booleans is false but added for readability
   120  		exitChan:            make(chan struct{}),
   121  		haltedChan:          make(chan struct{}),
   122  		setupChan:           make(chan struct{}),
   123  	}
   124  }
   125  
   126  // Satisfied by both chainImpl consenterImpl and mockConsenterImpl.
   127  // Defined so as to facilitate testing.
   128  type testableConsenter interface {
   129  	kafkaVersion() sarama.KafkaVersion
   130  	retryOptions() config.Retry
   131  	tlsConfig() config.TLS
   132  	brokFunc() bfType
   133  	prodFunc() pfType
   134  	consFunc() cfType
   135  }
   136  
   137  func (co *consenterImpl) kafkaVersion() sarama.KafkaVersion { return co.kv }
   138  func (co *consenterImpl) retryOptions() config.Retry        { return co.ro }
   139  func (co *consenterImpl) tlsConfig() config.TLS             { return co.tls }
   140  func (co *consenterImpl) brokFunc() bfType                  { return co.bf }
   141  func (co *consenterImpl) prodFunc() pfType                  { return co.pf }
   142  func (co *consenterImpl) consFunc() cfType                  { return co.cf }
   143  
   144  type chainImpl struct {
   145  	consenter testableConsenter
   146  	support   multichain.ConsenterSupport
   147  
   148  	partition           ChainPartition
   149  	batchTimeout        time.Duration
   150  	lastOffsetPersisted int64
   151  	lastCutBlock        uint64
   152  
   153  	producer Producer
   154  	consumer Consumer
   155  
   156  	halted   bool          // For the Enqueue() calls
   157  	exitChan chan struct{} // For the Chain's Halt() method
   158  
   159  	// Hooks for testing
   160  	haltedChan chan struct{}
   161  	setupChan  chan struct{}
   162  }
   163  
   164  // Start allocates the necessary resources for staying up to date with this Chain.
   165  // Implements the multichain.Chain interface. Called by multichain.NewManagerImpl()
   166  // which is invoked when the ordering process is launched, before the call to NewServer().
   167  func (ch *chainImpl) Start() {
   168  	// 1. Post the CONNECT message to prevent panicking that occurs
   169  	// when seeking on a partition that hasn't been created yet.
   170  	logger.Debugf("[channel: %s] Posting the CONNECT message...", ch.support.ChainID())
   171  	if err := ch.producer.Send(ch.partition, utils.MarshalOrPanic(newConnectMessage())); err != nil {
   172  		logger.Criticalf("[channel: %s] Cannot post CONNECT message: %s", ch.support.ChainID(), err)
   173  		close(ch.exitChan)
   174  		ch.halted = true
   175  		return
   176  	}
   177  	logger.Debugf("[channel: %s] CONNECT message posted successfully", ch.support.ChainID())
   178  
   179  	// 2. Set up the listener/consumer for this partition.
   180  	consumer, err := ch.consenter.consFunc()(ch.support.SharedConfig().KafkaBrokers(), ch.consenter.kafkaVersion(), ch.consenter.tlsConfig(), ch.partition, ch.lastOffsetPersisted+1)
   181  	if err != nil {
   182  		logger.Criticalf("[channel: %s] Cannot retrieve requested offset from Kafka cluster: %s", ch.support.ChainID(), err)
   183  		close(ch.exitChan)
   184  		ch.halted = true
   185  		return
   186  	}
   187  	ch.consumer = consumer
   188  	close(ch.setupChan)
   189  	go ch.listenForErrors()
   190  
   191  	// 3. Set the loop the keep up to date with the chain.
   192  	go ch.loop()
   193  }
   194  
   195  func (ch *chainImpl) listenForErrors() {
   196  	select {
   197  	case <-ch.exitChan:
   198  		return
   199  	case err := <-ch.consumer.Errors():
   200  		logger.Error(err)
   201  	}
   202  }
   203  
   204  // Halt frees the resources which were allocated for this Chain.
   205  // Implements the multichain.Chain interface.
   206  func (ch *chainImpl) Halt() {
   207  	select {
   208  	case <-ch.exitChan:
   209  		// This construct is useful because it allows Halt() to be
   210  		// called multiple times w/o panicking. Recal that a receive
   211  		// from a closed channel returns (the zero value) immediately.
   212  		logger.Debugf("[channel: %s] Halting of chain requested again", ch.support.ChainID())
   213  	default:
   214  		logger.Debugf("[channel: %s] Halting of chain requested", ch.support.ChainID())
   215  		close(ch.exitChan)
   216  	}
   217  }
   218  
   219  // Enqueue accepts a message and returns true on acceptance, or false on shutdown.
   220  // Implements the multichain.Chain interface. Called by the drainQueue goroutine,
   221  // which is spawned when the broadcast handler's Handle() function is invoked.
   222  func (ch *chainImpl) Enqueue(env *cb.Envelope) bool {
   223  	if ch.halted {
   224  		return false
   225  	}
   226  
   227  	logger.Debugf("[channel: %s] Enqueueing envelope...", ch.support.ChainID())
   228  	if err := ch.producer.Send(ch.partition, utils.MarshalOrPanic(newRegularMessage(utils.MarshalOrPanic(env)))); err != nil {
   229  		logger.Errorf("[channel: %s] cannot enqueue envelope: %s", ch.support.ChainID(), err)
   230  		return false
   231  	}
   232  	logger.Debugf("[channel: %s] Envelope enqueued successfully", ch.support.ChainID())
   233  
   234  	return !ch.halted // If ch.halted has been set to true while sending, we should return false
   235  }
   236  
   237  func (ch *chainImpl) loop() {
   238  	msg := new(ab.KafkaMessage)
   239  	var timer <-chan time.Time
   240  	var ttcNumber uint64
   241  	var encodedLastOffsetPersisted []byte
   242  
   243  	defer close(ch.haltedChan)
   244  	defer ch.producer.Close()
   245  	defer func() { ch.halted = true }()
   246  	defer ch.consumer.Close()
   247  
   248  	for {
   249  		select {
   250  		case in := <-ch.consumer.Recv():
   251  			if err := proto.Unmarshal(in.Value, msg); err != nil {
   252  				// This shouldn't happen, it should be filtered at ingress
   253  				logger.Criticalf("[channel: %s] Unable to unmarshal consumed message:", ch.support.ChainID(), err)
   254  			}
   255  			logger.Debugf("[channel: %s] Successfully unmarshalled consumed message. Inspecting type...", ch.support.ChainID())
   256  			switch msg.Type.(type) {
   257  			case *ab.KafkaMessage_Connect:
   258  				logger.Debugf("[channel: %s] It's a connect message - ignoring", ch.support.ChainID())
   259  				continue
   260  			case *ab.KafkaMessage_TimeToCut:
   261  				ttcNumber = msg.GetTimeToCut().BlockNumber
   262  				logger.Debugf("[channel: %s] It's a time-to-cut message for block %d", ch.support.ChainID(), ttcNumber)
   263  				if ttcNumber == ch.lastCutBlock+1 {
   264  					timer = nil
   265  					logger.Debugf("[channel: %s] Nil'd the timer", ch.support.ChainID())
   266  					batch, committers := ch.support.BlockCutter().Cut()
   267  					if len(batch) == 0 {
   268  						logger.Warningf("[channel: %s] Got right time-to-cut message (for block %d),"+
   269  							" no pending requests though; this might indicate a bug", ch.support.ChainID(), ch.lastCutBlock)
   270  						logger.Infof("[channel: %s] Consenter for channel exiting", ch.support.ChainID())
   271  						return
   272  					}
   273  					block := ch.support.CreateNextBlock(batch)
   274  					encodedLastOffsetPersisted = utils.MarshalOrPanic(&ab.KafkaMetadata{LastOffsetPersisted: in.Offset})
   275  					ch.support.WriteBlock(block, committers, encodedLastOffsetPersisted)
   276  					ch.lastCutBlock++
   277  					logger.Debugf("[channel: %s] Proper time-to-cut received, just cut block %d",
   278  						ch.support.ChainID(), ch.lastCutBlock)
   279  					continue
   280  				} else if ttcNumber > ch.lastCutBlock+1 {
   281  					logger.Warningf("[channel: %s] Got larger time-to-cut message (%d) than allowed (%d)"+
   282  						" - this might indicate a bug", ch.support.ChainID(), ttcNumber, ch.lastCutBlock+1)
   283  					logger.Infof("[channel: %s] Consenter for channel exiting", ch.support.ChainID())
   284  					return
   285  				}
   286  				logger.Debugf("[channel: %s] Ignoring stale time-to-cut-message for block %d", ch.support.ChainID(), ch.lastCutBlock)
   287  			case *ab.KafkaMessage_Regular:
   288  				env := new(cb.Envelope)
   289  				if err := proto.Unmarshal(msg.GetRegular().Payload, env); err != nil {
   290  					// This shouldn't happen, it should be filtered at ingress
   291  					logger.Criticalf("[channel: %s] Unable to unmarshal consumed regular message:", ch.support.ChainID(), err)
   292  					continue
   293  				}
   294  				batches, committers, ok := ch.support.BlockCutter().Ordered(env)
   295  				logger.Debugf("[channel: %s] Ordering results: items in batch = %v, ok = %v", ch.support.ChainID(), batches, ok)
   296  				if ok && len(batches) == 0 && timer == nil {
   297  					timer = time.After(ch.batchTimeout)
   298  					logger.Debugf("[channel: %s] Just began %s batch timer", ch.support.ChainID(), ch.batchTimeout.String())
   299  					continue
   300  				}
   301  				// If !ok, batches == nil, so this will be skipped
   302  				for i, batch := range batches {
   303  					block := ch.support.CreateNextBlock(batch)
   304  					encodedLastOffsetPersisted = utils.MarshalOrPanic(&ab.KafkaMetadata{LastOffsetPersisted: in.Offset})
   305  					ch.support.WriteBlock(block, committers[i], encodedLastOffsetPersisted)
   306  					ch.lastCutBlock++
   307  					logger.Debugf("[channel: %s] Batch filled, just cut block %d", ch.support.ChainID(), ch.lastCutBlock)
   308  				}
   309  				if len(batches) > 0 {
   310  					timer = nil
   311  				}
   312  			}
   313  		case <-timer:
   314  			logger.Debugf("[channel: %s] Time-to-cut block %d timer expired", ch.support.ChainID(), ch.lastCutBlock+1)
   315  			timer = nil
   316  			if err := ch.producer.Send(ch.partition, utils.MarshalOrPanic(newTimeToCutMessage(ch.lastCutBlock+1))); err != nil {
   317  				logger.Errorf("[channel: %s] Cannot post time-to-cut message: %s", ch.support.ChainID(), err)
   318  				// Do not exit
   319  			}
   320  		case <-ch.exitChan: // When Halt() is called
   321  			logger.Infof("[channel: %s] Consenter for channel exiting", ch.support.ChainID())
   322  			return
   323  		}
   324  	}
   325  }
   326  
   327  // Closeable allows the shut down of the calling resource.
   328  type Closeable interface {
   329  	Close() error
   330  }