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 }