/* * 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 org.apache.kafka.connect.runtime; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.common.config.ConfigDef; import org.apache.kafka.common.config.ConfigException; import org.apache.kafka.common.utils.Time; import org.apache.kafka.connect.connector.Connector; import org.apache.kafka.connect.connector.ConnectorContext; import org.apache.kafka.connect.connector.Task; import org.apache.kafka.connect.data.Schema; import org.apache.kafka.connect.data.SchemaAndValue; import org.apache.kafka.connect.errors.ConnectException; import org.apache.kafka.connect.json.JsonConverter; import org.apache.kafka.connect.runtime.isolation.DelegatingClassLoader; import org.apache.kafka.connect.runtime.isolation.PluginClassLoader; import org.apache.kafka.connect.runtime.isolation.Plugins; import org.apache.kafka.connect.runtime.standalone.StandaloneConfig; import org.apache.kafka.connect.sink.SinkTask; import org.apache.kafka.connect.source.SourceRecord; import org.apache.kafka.connect.source.SourceTask; import org.apache.kafka.connect.storage.Converter; import org.apache.kafka.connect.storage.OffsetBackingStore; import org.apache.kafka.connect.storage.OffsetStorageReader; import org.apache.kafka.connect.storage.OffsetStorageWriter; import org.apache.kafka.connect.util.ConnectorTaskId; import org.apache.kafka.connect.util.MockTime; import org.apache.kafka.connect.util.ThreadedTest; import org.easymock.Capture; import org.easymock.EasyMock; import org.easymock.Mock; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.easymock.PowerMock; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; @RunWith(PowerMockRunner.class) @PrepareForTest({Worker.class, Plugins.class}) @PowerMockIgnore("javax.management.*") public class WorkerTest extends ThreadedTest { private static final String CONNECTOR_ID = "test-connector"; private static final ConnectorTaskId TASK_ID = new ConnectorTaskId("job", 0); private static final String WORKER_ID = "localhost:8083"; private WorkerConfig config; private Worker worker; @Mock private Plugins plugins = PowerMock.createMock(Plugins.class); @Mock private PluginClassLoader pluginLoader = PowerMock.createMock(PluginClassLoader.class); @Mock private DelegatingClassLoader delegatingLoader = PowerMock.createMock(DelegatingClassLoader.class); private OffsetBackingStore offsetBackingStore = PowerMock.createMock(OffsetBackingStore.class); private TaskStatus.Listener taskStatusListener = PowerMock.createStrictMock(TaskStatus.Listener.class); private ConnectorStatus.Listener connectorStatusListener = PowerMock.createStrictMock(ConnectorStatus.Listener.class); @Before public void setup() { super.setup(); Map<String, String> workerProps = new HashMap<>(); workerProps.put("key.converter", "org.apache.kafka.connect.json.JsonConverter"); workerProps.put("value.converter", "org.apache.kafka.connect.json.JsonConverter"); workerProps.put("internal.key.converter", "org.apache.kafka.connect.json.JsonConverter"); workerProps.put("internal.value.converter", "org.apache.kafka.connect.json.JsonConverter"); workerProps.put("internal.key.converter.schemas.enable", "false"); workerProps.put("internal.value.converter.schemas.enable", "false"); workerProps.put("offset.storage.file.filename", "/tmp/connect.offsets"); config = new StandaloneConfig(workerProps); PowerMock.mockStatic(Plugins.class); } @Test public void testStartAndStopConnector() throws Exception { expectConverters(); expectStartStorage(); // Create Connector connector = PowerMock.createMock(Connector.class); ConnectorContext ctx = PowerMock.createMock(ConnectorContext.class); EasyMock.expect(plugins.currentThreadLoader()).andReturn(delegatingLoader).times(2); EasyMock.expect(plugins.newConnector(WorkerTestConnector.class.getName())) .andReturn(connector); EasyMock.expect(connector.version()).andReturn("1.0"); Map<String, String> props = new HashMap<>(); props.put(SinkConnectorConfig.TOPICS_CONFIG, "foo,bar"); props.put(ConnectorConfig.TASKS_MAX_CONFIG, "1"); props.put(ConnectorConfig.NAME_CONFIG, CONNECTOR_ID); props.put(ConnectorConfig.CONNECTOR_CLASS_CONFIG, WorkerTestConnector.class.getName()); EasyMock.expect(plugins.compareAndSwapLoaders(connector)) .andReturn(delegatingLoader) .times(2); connector.initialize(EasyMock.anyObject(ConnectorContext.class)); EasyMock.expectLastCall(); connector.start(props); EasyMock.expectLastCall(); EasyMock.expect(Plugins.compareAndSwapLoaders(delegatingLoader)) .andReturn(pluginLoader).times(2); connectorStatusListener.onStartup(CONNECTOR_ID); EasyMock.expectLastCall(); // Remove connector.stop(); EasyMock.expectLastCall(); connectorStatusListener.onShutdown(CONNECTOR_ID); EasyMock.expectLastCall(); expectStopStorage(); PowerMock.replayAll(); worker = new Worker(WORKER_ID, new MockTime(), plugins, config, offsetBackingStore); worker.start(); assertEquals(Collections.emptySet(), worker.connectorNames()); worker.startConnector(CONNECTOR_ID, props, ctx, connectorStatusListener, TargetState.STARTED); assertEquals(new HashSet<>(Arrays.asList(CONNECTOR_ID)), worker.connectorNames()); try { worker.startConnector(CONNECTOR_ID, props, ctx, connectorStatusListener, TargetState.STARTED); fail("Should have thrown exception when trying to add connector with same name."); } catch (ConnectException e) { // expected } worker.stopConnector(CONNECTOR_ID); assertEquals(Collections.emptySet(), worker.connectorNames()); // Nothing should be left, so this should effectively be a nop worker.stop(); PowerMock.verifyAll(); } @Test public void testStartConnectorFailure() throws Exception { expectConverters(); expectStartStorage(); Map<String, String> props = new HashMap<>(); props.put(SinkConnectorConfig.TOPICS_CONFIG, "foo,bar"); props.put(ConnectorConfig.TASKS_MAX_CONFIG, "1"); props.put(ConnectorConfig.NAME_CONFIG, CONNECTOR_ID); props.put(ConnectorConfig.CONNECTOR_CLASS_CONFIG, "java.util.HashMap"); // Bad connector class name EasyMock.expect(plugins.currentThreadLoader()).andReturn(delegatingLoader); EasyMock.expect(plugins.newConnector(EasyMock.anyString())) .andThrow(new ConnectException("Failed to find Connector")); EasyMock.expect(Plugins.compareAndSwapLoaders(delegatingLoader)) .andReturn(pluginLoader); connectorStatusListener.onFailure( EasyMock.eq(CONNECTOR_ID), EasyMock.<ConnectException>anyObject() ); EasyMock.expectLastCall(); PowerMock.replayAll(); worker = new Worker(WORKER_ID, new MockTime(), plugins, config, offsetBackingStore); worker.start(); assertFalse(worker.startConnector(CONNECTOR_ID, props, PowerMock.createMock(ConnectorContext.class), connectorStatusListener, TargetState.STARTED)); assertEquals(Collections.emptySet(), worker.connectorNames()); assertFalse(worker.stopConnector(CONNECTOR_ID)); } @Test public void testAddConnectorByAlias() throws Exception { expectConverters(); expectStartStorage(); // Create Connector connector = PowerMock.createMock(Connector.class); ConnectorContext ctx = PowerMock.createMock(ConnectorContext.class); EasyMock.expect(plugins.currentThreadLoader()).andReturn(delegatingLoader).times(2); EasyMock.expect(plugins.newConnector("WorkerTestConnector")).andReturn(connector); EasyMock.expect(connector.version()).andReturn("1.0"); Map<String, String> props = new HashMap<>(); props.put(SinkConnectorConfig.TOPICS_CONFIG, "foo,bar"); props.put(ConnectorConfig.TASKS_MAX_CONFIG, "1"); props.put(ConnectorConfig.NAME_CONFIG, CONNECTOR_ID); props.put(ConnectorConfig.CONNECTOR_CLASS_CONFIG, "WorkerTestConnector"); EasyMock.expect(plugins.compareAndSwapLoaders(connector)) .andReturn(delegatingLoader) .times(2); connector.initialize(EasyMock.anyObject(ConnectorContext.class)); EasyMock.expectLastCall(); connector.start(props); EasyMock.expectLastCall(); EasyMock.expect(Plugins.compareAndSwapLoaders(delegatingLoader)) .andReturn(pluginLoader) .times(2); connectorStatusListener.onStartup(CONNECTOR_ID); EasyMock.expectLastCall(); // Remove connector.stop(); EasyMock.expectLastCall(); connectorStatusListener.onShutdown(CONNECTOR_ID); EasyMock.expectLastCall(); expectStopStorage(); PowerMock.replayAll(); worker = new Worker(WORKER_ID, new MockTime(), plugins, config, offsetBackingStore); worker.start(); assertEquals(Collections.emptySet(), worker.connectorNames()); worker.startConnector(CONNECTOR_ID, props, ctx, connectorStatusListener, TargetState.STARTED); assertEquals(new HashSet<>(Arrays.asList(CONNECTOR_ID)), worker.connectorNames()); worker.stopConnector(CONNECTOR_ID); assertEquals(Collections.emptySet(), worker.connectorNames()); // Nothing should be left, so this should effectively be a nop worker.stop(); PowerMock.verifyAll(); } @Test public void testAddConnectorByShortAlias() throws Exception { expectConverters(); expectStartStorage(); // Create Connector connector = PowerMock.createMock(Connector.class); ConnectorContext ctx = PowerMock.createMock(ConnectorContext.class); EasyMock.expect(plugins.currentThreadLoader()).andReturn(delegatingLoader).times(2); EasyMock.expect(plugins.newConnector("WorkerTest")).andReturn(connector); EasyMock.expect(connector.version()).andReturn("1.0"); Map<String, String> props = new HashMap<>(); props.put(SinkConnectorConfig.TOPICS_CONFIG, "foo,bar"); props.put(ConnectorConfig.TASKS_MAX_CONFIG, "1"); props.put(ConnectorConfig.NAME_CONFIG, CONNECTOR_ID); props.put(ConnectorConfig.CONNECTOR_CLASS_CONFIG, "WorkerTest"); EasyMock.expect(plugins.compareAndSwapLoaders(connector)) .andReturn(delegatingLoader) .times(2); connector.initialize(EasyMock.anyObject(ConnectorContext.class)); EasyMock.expectLastCall(); connector.start(props); EasyMock.expectLastCall(); EasyMock.expect(Plugins.compareAndSwapLoaders(delegatingLoader)) .andReturn(pluginLoader) .times(2); connectorStatusListener.onStartup(CONNECTOR_ID); EasyMock.expectLastCall(); // Remove connector.stop(); EasyMock.expectLastCall(); connectorStatusListener.onShutdown(CONNECTOR_ID); EasyMock.expectLastCall(); expectStopStorage(); PowerMock.replayAll(); worker = new Worker(WORKER_ID, new MockTime(), plugins, config, offsetBackingStore); worker.start(); assertEquals(Collections.emptySet(), worker.connectorNames()); worker.startConnector(CONNECTOR_ID, props, ctx, connectorStatusListener, TargetState.STARTED); assertEquals(new HashSet<>(Arrays.asList(CONNECTOR_ID)), worker.connectorNames()); worker.stopConnector(CONNECTOR_ID); assertEquals(Collections.emptySet(), worker.connectorNames()); // Nothing should be left, so this should effectively be a nop worker.stop(); PowerMock.verifyAll(); } @Test public void testStopInvalidConnector() { expectConverters(); expectStartStorage(); PowerMock.replayAll(); worker = new Worker(WORKER_ID, new MockTime(), plugins, config, offsetBackingStore); worker.start(); worker.stopConnector(CONNECTOR_ID); } @Test public void testReconfigureConnectorTasks() throws Exception { expectConverters(); expectStartStorage(); // Create Connector connector = PowerMock.createMock(Connector.class); ConnectorContext ctx = PowerMock.createMock(ConnectorContext.class); EasyMock.expect(plugins.currentThreadLoader()).andReturn(delegatingLoader).times(3); EasyMock.expect(plugins.newConnector(WorkerTestConnector.class.getName())) .andReturn(connector); EasyMock.expect(connector.version()).andReturn("1.0"); Map<String, String> props = new HashMap<>(); props.put(SinkConnectorConfig.TOPICS_CONFIG, "foo,bar"); props.put(ConnectorConfig.TASKS_MAX_CONFIG, "1"); props.put(ConnectorConfig.NAME_CONFIG, CONNECTOR_ID); props.put(ConnectorConfig.CONNECTOR_CLASS_CONFIG, WorkerTestConnector.class.getName()); EasyMock.expect(plugins.compareAndSwapLoaders(connector)) .andReturn(delegatingLoader) .times(3); connector.initialize(EasyMock.anyObject(ConnectorContext.class)); EasyMock.expectLastCall(); connector.start(props); EasyMock.expectLastCall(); EasyMock.expect(Plugins.compareAndSwapLoaders(delegatingLoader)) .andReturn(pluginLoader) .times(3); connectorStatusListener.onStartup(CONNECTOR_ID); EasyMock.expectLastCall(); // Reconfigure EasyMock.<Class<? extends Task>>expect(connector.taskClass()).andReturn(TestSourceTask.class); Map<String, String> taskProps = new HashMap<>(); taskProps.put("foo", "bar"); EasyMock.expect(connector.taskConfigs(2)).andReturn(Arrays.asList(taskProps, taskProps)); // Remove connector.stop(); EasyMock.expectLastCall(); connectorStatusListener.onShutdown(CONNECTOR_ID); EasyMock.expectLastCall(); expectStopStorage(); PowerMock.replayAll(); worker = new Worker(WORKER_ID, new MockTime(), plugins, config, offsetBackingStore); worker.start(); assertEquals(Collections.emptySet(), worker.connectorNames()); worker.startConnector(CONNECTOR_ID, props, ctx, connectorStatusListener, TargetState.STARTED); assertEquals(new HashSet<>(Arrays.asList(CONNECTOR_ID)), worker.connectorNames()); try { worker.startConnector(CONNECTOR_ID, props, ctx, connectorStatusListener, TargetState.STARTED); fail("Should have thrown exception when trying to add connector with same name."); } catch (ConnectException e) { // expected } List<Map<String, String>> taskConfigs = worker.connectorTaskConfigs(CONNECTOR_ID, 2, Arrays.asList("foo", "bar")); Map<String, String> expectedTaskProps = new HashMap<>(); expectedTaskProps.put("foo", "bar"); expectedTaskProps.put(TaskConfig.TASK_CLASS_CONFIG, TestSourceTask.class.getName()); expectedTaskProps.put(SinkTask.TOPICS_CONFIG, "foo,bar"); assertEquals(2, taskConfigs.size()); assertEquals(expectedTaskProps, taskConfigs.get(0)); assertEquals(expectedTaskProps, taskConfigs.get(1)); worker.stopConnector(CONNECTOR_ID); assertEquals(Collections.emptySet(), worker.connectorNames()); // Nothing should be left, so this should effectively be a nop worker.stop(); PowerMock.verifyAll(); } @Test public void testAddRemoveTask() throws Exception { expectConverters(); expectStartStorage(); // Create TestSourceTask task = PowerMock.createMock(TestSourceTask.class); WorkerSourceTask workerTask = PowerMock.createMock(WorkerSourceTask.class); EasyMock.expect(workerTask.id()).andStubReturn(TASK_ID); EasyMock.expect(task.version()).andReturn("1.0"); EasyMock.expect(plugins.currentThreadLoader()).andReturn(delegatingLoader).times(2); PowerMock.expectNew( WorkerSourceTask.class, EasyMock.eq(TASK_ID), EasyMock.eq(task), EasyMock.anyObject(TaskStatus.Listener.class), EasyMock.eq(TargetState.STARTED), EasyMock.anyObject(JsonConverter.class), EasyMock.anyObject(JsonConverter.class), EasyMock.eq(TransformationChain.<SourceRecord>noOp()), EasyMock.anyObject(KafkaProducer.class), EasyMock.anyObject(OffsetStorageReader.class), EasyMock.anyObject(OffsetStorageWriter.class), EasyMock.eq(config), EasyMock.anyObject(ClassLoader.class), EasyMock.anyObject(Time.class)) .andReturn(workerTask); Map<String, String> origProps = new HashMap<>(); origProps.put(TaskConfig.TASK_CLASS_CONFIG, TestSourceTask.class.getName()); EasyMock.expect(plugins.newTask(TestSourceTask.class)).andReturn(task); workerTask.initialize(new TaskConfig(origProps)); EasyMock.expectLastCall(); workerTask.run(); EasyMock.expectLastCall(); EasyMock.expect(plugins.delegatingLoader()).andReturn(delegatingLoader); EasyMock.expect(delegatingLoader.connectorLoader(WorkerTestConnector.class.getName())) .andReturn(pluginLoader); EasyMock.expect(Plugins.compareAndSwapLoaders(pluginLoader)).andReturn(delegatingLoader) .times(2); EasyMock.expect(workerTask.loader()).andReturn(pluginLoader); EasyMock.expect(Plugins.compareAndSwapLoaders(delegatingLoader)).andReturn(pluginLoader) .times(2); // Remove workerTask.stop(); EasyMock.expectLastCall(); EasyMock.expect(workerTask.awaitStop(EasyMock.anyLong())).andStubReturn(true); EasyMock.expectLastCall(); expectStopStorage(); PowerMock.replayAll(); worker = new Worker(WORKER_ID, new MockTime(), plugins, config, offsetBackingStore); worker.start(); assertEquals(Collections.emptySet(), worker.taskIds()); worker.startTask(TASK_ID, anyConnectorConfigMap(), origProps, taskStatusListener, TargetState.STARTED); assertEquals(new HashSet<>(Arrays.asList(TASK_ID)), worker.taskIds()); worker.stopAndAwaitTask(TASK_ID); assertEquals(Collections.emptySet(), worker.taskIds()); // Nothing should be left, so this should effectively be a nop worker.stop(); PowerMock.verifyAll(); } @Test public void testStartTaskFailure() throws Exception { expectConverters(); expectStartStorage(); Map<String, String> origProps = new HashMap<>(); origProps.put(TaskConfig.TASK_CLASS_CONFIG, "missing.From.This.Workers.Classpath"); EasyMock.expect(plugins.currentThreadLoader()).andReturn(delegatingLoader); EasyMock.expect(plugins.delegatingLoader()).andReturn(delegatingLoader); EasyMock.expect(delegatingLoader.connectorLoader(WorkerTestConnector.class.getName())) .andReturn(pluginLoader); EasyMock.expect(pluginLoader.loadClass(origProps.get(TaskConfig.TASK_CLASS_CONFIG))) .andThrow(new ClassNotFoundException()); EasyMock.expect(Plugins.compareAndSwapLoaders(delegatingLoader)) .andReturn(pluginLoader); taskStatusListener.onFailure(EasyMock.eq(TASK_ID), EasyMock.<ConfigException>anyObject()); EasyMock.expectLastCall(); PowerMock.replayAll(); worker = new Worker(WORKER_ID, new MockTime(), plugins, config, offsetBackingStore); worker.start(); assertFalse(worker.startTask(TASK_ID, anyConnectorConfigMap(), origProps, taskStatusListener, TargetState.STARTED)); assertEquals(Collections.emptySet(), worker.taskIds()); } @Test public void testCleanupTasksOnStop() throws Exception { expectConverters(); expectStartStorage(); // Create TestSourceTask task = PowerMock.createMock(TestSourceTask.class); WorkerSourceTask workerTask = PowerMock.createMock(WorkerSourceTask.class); EasyMock.expect(workerTask.id()).andStubReturn(TASK_ID); EasyMock.expect(plugins.newTask(TestSourceTask.class)).andReturn(task); EasyMock.expect(task.version()).andReturn("1.0"); EasyMock.expect(plugins.currentThreadLoader()).andReturn(delegatingLoader).times(2); PowerMock.expectNew( WorkerSourceTask.class, EasyMock.eq(TASK_ID), EasyMock.eq(task), EasyMock.anyObject(TaskStatus.Listener.class), EasyMock.eq(TargetState.STARTED), EasyMock.anyObject(JsonConverter.class), EasyMock.anyObject(JsonConverter.class), EasyMock.eq(TransformationChain.<SourceRecord>noOp()), EasyMock.anyObject(KafkaProducer.class), EasyMock.anyObject(OffsetStorageReader.class), EasyMock.anyObject(OffsetStorageWriter.class), EasyMock.anyObject(WorkerConfig.class), EasyMock.eq(pluginLoader), EasyMock.anyObject(Time.class)) .andReturn(workerTask); Map<String, String> origProps = new HashMap<>(); origProps.put(TaskConfig.TASK_CLASS_CONFIG, TestSourceTask.class.getName()); workerTask.initialize(new TaskConfig(origProps)); EasyMock.expectLastCall(); workerTask.run(); EasyMock.expectLastCall(); EasyMock.expect(plugins.delegatingLoader()).andReturn(delegatingLoader); EasyMock.expect(delegatingLoader.connectorLoader(WorkerTestConnector.class.getName())) .andReturn(pluginLoader); EasyMock.expect(Plugins.compareAndSwapLoaders(pluginLoader)).andReturn(delegatingLoader) .times(2); EasyMock.expect(workerTask.loader()).andReturn(pluginLoader); EasyMock.expect(Plugins.compareAndSwapLoaders(delegatingLoader)).andReturn(pluginLoader) .times(2); // Remove on Worker.stop() workerTask.stop(); EasyMock.expectLastCall(); EasyMock.expect(workerTask.awaitStop(EasyMock.anyLong())).andReturn(true); // Note that in this case we *do not* commit offsets since it's an unclean shutdown EasyMock.expectLastCall(); expectStopStorage(); PowerMock.replayAll(); worker = new Worker(WORKER_ID, new MockTime(), plugins, config, offsetBackingStore); worker.start(); worker.startTask(TASK_ID, anyConnectorConfigMap(), origProps, taskStatusListener, TargetState.STARTED); worker.stop(); PowerMock.verifyAll(); } @Test public void testConverterOverrides() throws Exception { expectConverters(); expectStartStorage(); TestSourceTask task = PowerMock.createMock(TestSourceTask.class); WorkerSourceTask workerTask = PowerMock.createMock(WorkerSourceTask.class); EasyMock.expect(workerTask.id()).andStubReturn(TASK_ID); EasyMock.expect(plugins.newTask(TestSourceTask.class)).andReturn(task); EasyMock.expect(task.version()).andReturn("1.0"); Capture<TestConverter> keyConverter = EasyMock.newCapture(); Capture<TestConverter> valueConverter = EasyMock.newCapture(); EasyMock.expect(plugins.currentThreadLoader()).andReturn(delegatingLoader).times(2); PowerMock.expectNew( WorkerSourceTask.class, EasyMock.eq(TASK_ID), EasyMock.eq(task), EasyMock.anyObject(TaskStatus.Listener.class), EasyMock.eq(TargetState.STARTED), EasyMock.capture(keyConverter), EasyMock.capture(valueConverter), EasyMock.eq(TransformationChain.<SourceRecord>noOp()), EasyMock.anyObject(KafkaProducer.class), EasyMock.anyObject(OffsetStorageReader.class), EasyMock.anyObject(OffsetStorageWriter.class), EasyMock.anyObject(WorkerConfig.class), EasyMock.eq(pluginLoader), EasyMock.anyObject(Time.class)) .andReturn(workerTask); Map<String, String> origProps = new HashMap<>(); origProps.put(TaskConfig.TASK_CLASS_CONFIG, TestSourceTask.class.getName()); workerTask.initialize(new TaskConfig(origProps)); EasyMock.expectLastCall(); workerTask.run(); EasyMock.expectLastCall(); EasyMock.expect(plugins.delegatingLoader()).andReturn(delegatingLoader); EasyMock.expect(delegatingLoader.connectorLoader(WorkerTestConnector.class.getName())) .andReturn(pluginLoader); EasyMock.expect(Plugins.compareAndSwapLoaders(pluginLoader)).andReturn(delegatingLoader) .times(2); EasyMock.expect(workerTask.loader()).andReturn(pluginLoader); EasyMock.expect(Plugins.compareAndSwapLoaders(delegatingLoader)).andReturn(pluginLoader) .times(2); // Remove workerTask.stop(); EasyMock.expectLastCall(); EasyMock.expect(workerTask.awaitStop(EasyMock.anyLong())).andStubReturn(true); EasyMock.expectLastCall(); expectStopStorage(); PowerMock.replayAll(); worker = new Worker(WORKER_ID, new MockTime(), plugins, config, offsetBackingStore); worker.start(); assertEquals(Collections.emptySet(), worker.taskIds()); Map<String, String> connProps = anyConnectorConfigMap(); connProps.put(ConnectorConfig.KEY_CONVERTER_CLASS_CONFIG, TestConverter.class.getName()); connProps.put("key.converter.extra.config", "foo"); connProps.put(ConnectorConfig.VALUE_CONVERTER_CLASS_CONFIG, TestConverter.class.getName()); connProps.put("value.converter.extra.config", "bar"); worker.startTask(TASK_ID, connProps, origProps, taskStatusListener, TargetState.STARTED); assertEquals(new HashSet<>(Arrays.asList(TASK_ID)), worker.taskIds()); worker.stopAndAwaitTask(TASK_ID); assertEquals(Collections.emptySet(), worker.taskIds()); // Nothing should be left, so this should effectively be a nop worker.stop(); // Validate extra configs got passed through to overridden converters assertEquals("foo", keyConverter.getValue().configs.get("extra.config")); assertEquals("bar", valueConverter.getValue().configs.get("extra.config")); PowerMock.verifyAll(); } private void expectStartStorage() { offsetBackingStore.configure(EasyMock.anyObject(WorkerConfig.class)); EasyMock.expectLastCall(); offsetBackingStore.start(); EasyMock.expectLastCall(); } private void expectStopStorage() { offsetBackingStore.stop(); EasyMock.expectLastCall(); } private void expectConverters() { expectConverters(JsonConverter.class); } private void expectConverters(Class<? extends Converter> converterClass) { // connector default Converter keyConverter = PowerMock.createMock(converterClass); Converter valueConverter = PowerMock.createMock(converterClass); //internal Converter internalKeyConverter = PowerMock.createMock(converterClass); Converter internalValueConverter = PowerMock.createMock(converterClass); // Instantiate and configure default EasyMock.expect(plugins.newConverter(JsonConverter.class.getName(), config)) .andReturn(keyConverter); keyConverter.configure( EasyMock.<Map<String, ?>>anyObject(), EasyMock.anyBoolean() ); EasyMock.expectLastCall(); EasyMock.expect(plugins.newConverter(JsonConverter.class.getName(), config)) .andReturn(valueConverter); valueConverter.configure( EasyMock.<Map<String, ?>>anyObject(), EasyMock.anyBoolean() ); EasyMock.expectLastCall(); // Instantiate and configure internal EasyMock.expect(plugins.newConverter(JsonConverter.class.getName(), config)) .andReturn(internalKeyConverter); internalKeyConverter.configure( EasyMock.<Map<String, ?>>anyObject(), EasyMock.anyBoolean() ); EasyMock.expectLastCall(); EasyMock.expect(plugins.newConverter(JsonConverter.class.getName(), config)) .andReturn(internalValueConverter); internalValueConverter.configure( EasyMock.<Map<String, ?>>anyObject(), EasyMock.anyBoolean() ); EasyMock.expectLastCall(); } private Map<String, String> anyConnectorConfigMap() { Map<String, String> props = new HashMap<>(); props.put(ConnectorConfig.NAME_CONFIG, CONNECTOR_ID); props.put(ConnectorConfig.CONNECTOR_CLASS_CONFIG, WorkerTestConnector.class.getName()); props.put(ConnectorConfig.TASKS_MAX_CONFIG, "1"); return props; } /* Name here needs to be unique as we are testing the aliasing mechanism */ public static class WorkerTestConnector extends Connector { private static final ConfigDef CONFIG_DEF = new ConfigDef() .define("configName", ConfigDef.Type.STRING, ConfigDef.Importance.HIGH, "Test configName."); @Override public String version() { return "1.0"; } @Override public void start(Map<String, String> props) { } @Override public Class<? extends Task> taskClass() { return null; } @Override public List<Map<String, String>> taskConfigs(int maxTasks) { return null; } @Override public void stop() { } @Override public ConfigDef config() { return CONFIG_DEF; } } private static class TestSourceTask extends SourceTask { public TestSourceTask() { } @Override public String version() { return "1.0"; } @Override public void start(Map<String, String> props) { } @Override public List<SourceRecord> poll() throws InterruptedException { return null; } @Override public void stop() { } } public static class TestConverter implements Converter { public Map<String, ?> configs; @Override public void configure(Map<String, ?> configs, boolean isKey) { this.configs = configs; } @Override public byte[] fromConnectData(String topic, Schema schema, Object value) { return new byte[0]; } @Override public SchemaAndValue toConnectData(String topic, byte[] value) { return null; } } }