Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions core/src/main/scala/kafka/network/SocketServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,13 @@ class SocketServer(val config: KafkaConfig, val metrics: Metrics, val time: Time
/**
* Start the socket server
*/
def startup() {
def startup(startupProcessors: Boolean = true) {
this.synchronized {
connectionQuotas = new ConnectionQuotas(maxConnectionsPerIp, maxConnectionsPerIpOverrides)
createAcceptorAndProcessors(config.numNetworkThreads, config.listeners)
if (startupProcessors) {
startProcessors()
}
}

newGauge("NetworkProcessorAvgIdlePercent",
Expand Down Expand Up @@ -110,6 +113,11 @@ class SocketServer(val config: KafkaConfig, val metrics: Metrics, val time: Time
info("Started " + acceptors.size + " acceptor threads")
}

def startProcessors(): Unit = synchronized {
acceptors.values.asScala.foreach { _.startProcessors() }
info("Started processors for " + acceptors.size + " acceptors")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: use string interpolation?

}

private def endpoints = config.listeners.map(l => l.listenerName -> l).toMap

private def createAcceptorAndProcessors(processorsPerListener: Int,
Expand Down Expand Up @@ -196,6 +204,7 @@ class SocketServer(val config: KafkaConfig, val metrics: Metrics, val time: Time
def addListeners(listenersAdded: Seq[EndPoint]): Unit = synchronized {
info(s"Adding listeners for endpoints $listenersAdded")
createAcceptorAndProcessors(config.numNetworkThreads, listenersAdded)
startProcessors()
}

def removeListeners(listenersRemoved: Seq[EndPoint]): Unit = synchronized {
Expand Down Expand Up @@ -307,13 +316,25 @@ private[kafka] class Acceptor(val endPoint: EndPoint,
private val nioSelector = NSelector.open()
val serverChannel = openServerSocket(endPoint.host, endPoint.port)
private val processors = new ArrayBuffer[Processor]()
private val processorsStarted = new AtomicBoolean

private[network] def addProcessors(newProcessors: Buffer[Processor]): Unit = synchronized {
newProcessors.foreach { processor =>
processors ++= newProcessors

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not directly related to this patch. However, it seems that we need some synchronization on processor between the reader and the writer?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@junrao Thanks for the review. We were already synchronizing on all read/write accesses to processors apart from shutdown(). I think that was safe anyway, but I have added synchronize in shutdown as well to be consistent.

if (processorsStarted.get)
startProcessors(newProcessors)
}

private[network] def startProcessors(): Unit = synchronized {
if (!processorsStarted.getAndSet(true)) {
startProcessors(processors)
}
}

private def startProcessors(processors: Seq[Processor]): Unit = synchronized {
processors.foreach { processor =>
KafkaThread.nonDaemon(s"kafka-network-thread-$brokerId-${endPoint.listenerName}-${endPoint.securityProtocol}-${processor.id}",
processor).start()
}
processors ++= newProcessors
}

private[network] def removeProcessors(removeCount: Int, requestChannel: RequestChannel): Unit = synchronized {
Expand All @@ -328,7 +349,9 @@ private[kafka] class Acceptor(val endPoint: EndPoint,

override def shutdown(): Unit = {
super.shutdown()
processors.foreach(_.shutdown())
synchronized {
processors.foreach(_.shutdown())
}
}

/**
Expand Down
7 changes: 5 additions & 2 deletions core/src/main/scala/kafka/server/KafkaServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,11 @@ class KafkaServer(val config: KafkaConfig, time: Time = Time.SYSTEM, threadNameP
tokenCache = new DelegationTokenCache(ScramMechanism.mechanismNames)
credentialProvider = new CredentialProvider(ScramMechanism.mechanismNames, tokenCache)

// Create and start the socket server acceptor threads so that the bound port is known.
// Delay starting processors until the end of the initialization sequence to ensure
// that credentials have been loaded before processing authentications.
socketServer = new SocketServer(config, metrics, time, credentialProvider)
socketServer.startup()
socketServer.startup(startupProcessors = false)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should document the new parameter in the scaladoc for startup and the reason why it's needed (i.e. people shouldn't have to read the comments in a particular usage to understand it). We should also include the actual behaviour impact (e.g. what happens to the requests that arrive while the processors are not started).


/* start replica manager */
replicaManager = createReplicaManager(isShuttingDown)
Expand Down Expand Up @@ -310,7 +313,7 @@ class KafkaServer(val config: KafkaConfig, time: Time = Time.SYSTEM, threadNameP
dynamicConfigManager = new DynamicConfigManager(zkClient, dynamicConfigHandlers)
dynamicConfigManager.startup()


socketServer.startProcessors()
brokerState.newState(RunningAsBroker)
shutdownLatch = new CountDownLatch(1)
startupComplete.set(true)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 kafka.server
import java.util.Collections

import kafka.api.{IntegrationTestHarness, KafkaSasl, SaslSetup}
import kafka.utils._
import kafka.zk.ConfigEntityChangeNotificationZNode
import org.apache.kafka.common.security.auth.SecurityProtocol
import org.junit.Assert._
import org.junit.Test

import scala.collection.JavaConverters._

/**
* Tests that there are no failed authentications during broker startup. This is to verify
* that SCRAM credentials are loaded by brokers before client connections can be made.
* For simplicity of testing, this test verifies authentications of controller connections.
*/
class ScramServerStartupTest extends IntegrationTestHarness with SaslSetup {

override val producerCount = 0
override val consumerCount = 0
override val serverCount = 1

private val kafkaClientSaslMechanism = "SCRAM-SHA-256"
private val kafkaServerSaslMechanisms = Collections.singletonList("SCRAM-SHA-256").asScala

override protected def securityProtocol = SecurityProtocol.SASL_PLAINTEXT

override protected val serverSaslProperties = Some(kafkaServerSaslProperties(kafkaServerSaslMechanisms, kafkaClientSaslMechanism))
override protected val clientSaslProperties = Some(kafkaClientSaslProperties(kafkaClientSaslMechanism))

override def configureSecurityBeforeServersStart() {
super.configureSecurityBeforeServersStart()
zkClient.makeSurePersistentPathExists(ConfigEntityChangeNotificationZNode.path)
// Create credentials before starting brokers
createScramCredentials(zkConnect, JaasTestUtils.KafkaScramAdmin, JaasTestUtils.KafkaScramAdminPassword)

startSasl(jaasSections(kafkaServerSaslMechanisms, Option(kafkaClientSaslMechanism), KafkaSasl))
}

@Test
def testAuthentications(): Unit = {
val successfulAuths = totalAuthentications("successful-authentication-total")
assertTrue("No successful authentications", successfulAuths > 0)
val failedAuths = totalAuthentications("failed-authentication-total")
assertEquals(0, failedAuths)
}

private def totalAuthentications(metricName: String): Int = {
val allMetrics = servers.head.metrics.metrics
val totalAuthCount = allMetrics.values().asScala.filter(_.metricName().name() == metricName)
.foldLeft(0.0)((total, metric) => total + metric.metricValue.asInstanceOf[Double])
totalAuthCount.toInt
}
}