All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.kafka.connect.runtime.distributed.IncrementalCooperativeConnectProtocol Maven / Gradle / Ivy

The newest version!
/*
 * 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.distributed;

import org.apache.kafka.common.protocol.types.ArrayOf;
import org.apache.kafka.common.protocol.types.Field;
import org.apache.kafka.common.protocol.types.Schema;
import org.apache.kafka.common.protocol.types.SchemaException;
import org.apache.kafka.common.protocol.types.Struct;
import org.apache.kafka.common.protocol.types.Type;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

import static org.apache.kafka.common.message.JoinGroupRequestData.JoinGroupRequestProtocol;
import static org.apache.kafka.common.message.JoinGroupRequestData.JoinGroupRequestProtocolCollection;
import static org.apache.kafka.common.protocol.types.Type.NULLABLE_BYTES;
import static org.apache.kafka.connect.runtime.distributed.ConnectProtocol.ASSIGNMENT_KEY_NAME;
import static org.apache.kafka.connect.runtime.distributed.ConnectProtocol.CONFIG_OFFSET_KEY_NAME;
import static org.apache.kafka.connect.runtime.distributed.ConnectProtocol.CONFIG_STATE_V0;
import static org.apache.kafka.connect.runtime.distributed.ConnectProtocol.CONNECTOR_ASSIGNMENT_V0;
import static org.apache.kafka.connect.runtime.distributed.ConnectProtocol.CONNECT_PROTOCOL_HEADER_SCHEMA;
import static org.apache.kafka.connect.runtime.distributed.ConnectProtocol.CONNECT_PROTOCOL_V0;
import static org.apache.kafka.connect.runtime.distributed.ConnectProtocol.ERROR_KEY_NAME;
import static org.apache.kafka.connect.runtime.distributed.ConnectProtocol.LEADER_KEY_NAME;
import static org.apache.kafka.connect.runtime.distributed.ConnectProtocol.LEADER_URL_KEY_NAME;
import static org.apache.kafka.connect.runtime.distributed.ConnectProtocol.URL_KEY_NAME;
import static org.apache.kafka.connect.runtime.distributed.ConnectProtocol.VERSION_KEY_NAME;
import static org.apache.kafka.connect.runtime.distributed.ConnectProtocolCompatibility.COMPATIBLE;
import static org.apache.kafka.connect.runtime.distributed.ConnectProtocolCompatibility.EAGER;
import static org.apache.kafka.connect.runtime.distributed.ConnectProtocolCompatibility.SESSIONED;


/**
 * This class implements a group protocol for Kafka Connect workers that support incremental and
 * cooperative rebalancing of connectors and tasks. It includes the format of worker state used when
 * joining the group and distributing assignments, and the format of assignments of connectors
 * and tasks to workers.
 */
public class IncrementalCooperativeConnectProtocol {
    public static final String ALLOCATION_KEY_NAME = "allocation";
    public static final String REVOKED_KEY_NAME = "revoked";
    public static final String SCHEDULED_DELAY_KEY_NAME = "delay";
    public static final short CONNECT_PROTOCOL_V1 = 1;
    public static final short CONNECT_PROTOCOL_V2 = 2;
    public static final boolean TOLERATE_MISSING_FIELDS_WITH_DEFAULTS = true;

    /**
     * Connect Protocol Header V1:
     * 
     *   Version            => Int16
     * 
*/ private static final Struct CONNECT_PROTOCOL_HEADER_V1 = new Struct(CONNECT_PROTOCOL_HEADER_SCHEMA) .set(VERSION_KEY_NAME, CONNECT_PROTOCOL_V1); /** * Connect Protocol Header V2: *
     *   Version            => Int16
     * 
* The V2 protocol is schematically identical to V1, but is used to signify that internal request * verification and distribution of session keys is enabled (for more information, see * KIP-507) */ private static final Struct CONNECT_PROTOCOL_HEADER_V2 = new Struct(CONNECT_PROTOCOL_HEADER_SCHEMA) .set(VERSION_KEY_NAME, CONNECT_PROTOCOL_V2); /** * Config State V1: *
     *   Url                => [String]
     *   ConfigOffset       => Int64
     * 
*/ public static final Schema CONFIG_STATE_V1 = CONFIG_STATE_V0; /** * Allocation V1 *
     *   Current Assignment => [Byte]
     * 
*/ public static final Schema ALLOCATION_V1 = new Schema( TOLERATE_MISSING_FIELDS_WITH_DEFAULTS, new Field(ALLOCATION_KEY_NAME, NULLABLE_BYTES, null, true, null)); /** * * Connector Assignment V1: *
     *   Connector          => [String]
     *   Tasks              => [Int32]
     * 
* *

Assignments for each worker are a set of connectors and tasks. These are categorized by * connector ID. A sentinel task ID (CONNECTOR_TASK) is used to indicate the connector itself * (i.e. that the assignment includes responsibility for running the Connector instance in * addition to any tasks it generates).

*/ public static final Schema CONNECTOR_ASSIGNMENT_V1 = CONNECTOR_ASSIGNMENT_V0; /** * Raw (non versioned) assignment V1: *
     *   Error              => Int16
     *   Leader             => [String]
     *   LeaderUrl          => [String]
     *   ConfigOffset       => Int64
     *   Assignment         => [Connector Assignment]
     *   Revoked            => [Connector Assignment]
     *   ScheduledDelay     => Int32
     * 
*/ public static final Schema ASSIGNMENT_V1 = new Schema( TOLERATE_MISSING_FIELDS_WITH_DEFAULTS, new Field(ERROR_KEY_NAME, Type.INT16), new Field(LEADER_KEY_NAME, Type.STRING), new Field(LEADER_URL_KEY_NAME, Type.STRING), new Field(CONFIG_OFFSET_KEY_NAME, Type.INT64), new Field(ASSIGNMENT_KEY_NAME, ArrayOf.nullable(CONNECTOR_ASSIGNMENT_V1), null, true, null), new Field(REVOKED_KEY_NAME, ArrayOf.nullable(CONNECTOR_ASSIGNMENT_V1), null, true, null), new Field(SCHEDULED_DELAY_KEY_NAME, Type.INT32, null, 0)); /** * The fields are serialized in sequence as follows: * Subscription V1: *
     *   Version            => Int16
     *   Url                => [String]
     *   ConfigOffset       => Int64
     *   Current Assignment => [Byte]
     * 
*/ public static ByteBuffer serializeMetadata(ExtendedWorkerState workerState, boolean sessioned) { Struct configState = new Struct(CONFIG_STATE_V1) .set(URL_KEY_NAME, workerState.url()) .set(CONFIG_OFFSET_KEY_NAME, workerState.offset()); // Not a big issue if we embed the protocol version with the assignment in the metadata Struct allocation = new Struct(ALLOCATION_V1) .set(ALLOCATION_KEY_NAME, serializeAssignment(workerState.assignment(), sessioned)); Struct connectProtocolHeader = sessioned ? CONNECT_PROTOCOL_HEADER_V2 : CONNECT_PROTOCOL_HEADER_V1; ByteBuffer buffer = ByteBuffer.allocate(connectProtocolHeader.sizeOf() + CONFIG_STATE_V1.sizeOf(configState) + ALLOCATION_V1.sizeOf(allocation)); connectProtocolHeader.writeTo(buffer); CONFIG_STATE_V1.write(buffer, configState); ALLOCATION_V1.write(buffer, allocation); buffer.flip(); return buffer; } /** * Returns the collection of Connect protocols that are supported by this version along * with their serialized metadata. The protocols are ordered by preference. * * @param workerState the current state of the worker metadata * @param sessioned whether the {@link ConnectProtocolCompatibility#SESSIONED} protocol should * be included in the collection of supported protocols * @return the collection of Connect protocol metadata */ public static JoinGroupRequestProtocolCollection metadataRequest(ExtendedWorkerState workerState, boolean sessioned) { // Order matters in terms of protocol preference List joinGroupRequestProtocols = new ArrayList<>(); if (sessioned) { joinGroupRequestProtocols.add(new JoinGroupRequestProtocol() .setName(SESSIONED.protocol()) .setMetadata(IncrementalCooperativeConnectProtocol.serializeMetadata(workerState, true).array()) ); } joinGroupRequestProtocols.add(new JoinGroupRequestProtocol() .setName(COMPATIBLE.protocol()) .setMetadata(IncrementalCooperativeConnectProtocol.serializeMetadata(workerState, false).array()) ); joinGroupRequestProtocols.add(new JoinGroupRequestProtocol() .setName(EAGER.protocol()) .setMetadata(ConnectProtocol.serializeMetadata(workerState).array()) ); return new JoinGroupRequestProtocolCollection(joinGroupRequestProtocols.iterator()); } /** * Given a byte buffer that contains protocol metadata return the deserialized form of the * metadata. * * @param buffer A buffer containing the protocols metadata * @return the deserialized metadata * @throws SchemaException on incompatible Connect protocol version */ public static ExtendedWorkerState deserializeMetadata(ByteBuffer buffer) { Struct header = CONNECT_PROTOCOL_HEADER_SCHEMA.read(buffer); Short version = header.getShort(VERSION_KEY_NAME); checkVersionCompatibility(version); Struct configState = CONFIG_STATE_V1.read(buffer); long configOffset = configState.getLong(CONFIG_OFFSET_KEY_NAME); String url = configState.getString(URL_KEY_NAME); Struct allocation = ALLOCATION_V1.read(buffer); // Protocol version is embedded with the assignment in the metadata ExtendedAssignment assignment = deserializeAssignment(allocation.getBytes(ALLOCATION_KEY_NAME)); return new ExtendedWorkerState(url, configOffset, assignment); } /** * The fields are serialized in sequence as follows: * Complete Assignment V1: *
     *   Version            => Int16
     *   Error              => Int16
     *   Leader             => [String]
     *   LeaderUrl          => [String]
     *   ConfigOffset       => Int64
     *   Assignment         => [Connector Assignment]
     *   Revoked            => [Connector Assignment]
     *   ScheduledDelay     => Int32
     * 
*/ public static ByteBuffer serializeAssignment(ExtendedAssignment assignment, boolean sessioned) { // comparison depends on reference equality for now if (assignment == null || ExtendedAssignment.empty().equals(assignment)) { return null; } Struct struct = assignment.toStruct(); Struct protocolHeader = sessioned ? CONNECT_PROTOCOL_HEADER_V2 : CONNECT_PROTOCOL_HEADER_V1; ByteBuffer buffer = ByteBuffer.allocate(protocolHeader.sizeOf() + ASSIGNMENT_V1.sizeOf(struct)); protocolHeader.writeTo(buffer); ASSIGNMENT_V1.write(buffer, struct); buffer.flip(); return buffer; } /** * Given a byte buffer that contains an assignment as defined by this protocol, return the * deserialized form of the assignment. * * @param buffer the buffer containing a serialized assignment * @return the deserialized assignment * @throws SchemaException on incompatible Connect protocol version */ public static ExtendedAssignment deserializeAssignment(ByteBuffer buffer) { if (buffer == null) { return null; } Struct header = CONNECT_PROTOCOL_HEADER_SCHEMA.read(buffer); Short version = header.getShort(VERSION_KEY_NAME); checkVersionCompatibility(version); Struct struct = ASSIGNMENT_V1.read(buffer); return ExtendedAssignment.fromStruct(version, struct); } private static void checkVersionCompatibility(short version) { // check for invalid versions if (version < CONNECT_PROTOCOL_V0) throw new SchemaException("Unsupported subscription version: " + version); // otherwise, assume versions can be parsed } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy