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

org.sourcelab.kafka.connect.apiclient.KafkaConnectClient Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2018, 2019, 2020, 2021 SourceLab.org https://github.com/SourceLabOrg/kafka-connect-client
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
 * persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package org.sourcelab.kafka.connect.apiclient;

import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sourcelab.kafka.connect.apiclient.exception.ResponseParseException;
import org.sourcelab.kafka.connect.apiclient.request.JacksonFactory;
import org.sourcelab.kafka.connect.apiclient.request.Request;
import org.sourcelab.kafka.connect.apiclient.request.RequestErrorResponse;
import org.sourcelab.kafka.connect.apiclient.request.delete.DeleteConnector;
import org.sourcelab.kafka.connect.apiclient.request.dto.ConnectServerVersion;
import org.sourcelab.kafka.connect.apiclient.request.dto.ConnectorDefinition;
import org.sourcelab.kafka.connect.apiclient.request.dto.ConnectorPlugin;
import org.sourcelab.kafka.connect.apiclient.request.dto.ConnectorPluginConfigDefinition;
import org.sourcelab.kafka.connect.apiclient.request.dto.ConnectorPluginConfigValidationResults;
import org.sourcelab.kafka.connect.apiclient.request.dto.ConnectorStatus;
import org.sourcelab.kafka.connect.apiclient.request.dto.ConnectorTopics;
import org.sourcelab.kafka.connect.apiclient.request.dto.ConnectorsWithExpandedInfo;
import org.sourcelab.kafka.connect.apiclient.request.dto.ConnectorsWithExpandedMetadata;
import org.sourcelab.kafka.connect.apiclient.request.dto.ConnectorsWithExpandedStatus;
import org.sourcelab.kafka.connect.apiclient.request.dto.NewConnectorDefinition;
import org.sourcelab.kafka.connect.apiclient.request.dto.Task;
import org.sourcelab.kafka.connect.apiclient.request.dto.TaskStatus;
import org.sourcelab.kafka.connect.apiclient.request.get.GetConnectServerVersion;
import org.sourcelab.kafka.connect.apiclient.request.get.GetConnector;
import org.sourcelab.kafka.connect.apiclient.request.get.GetConnectorConfig;
import org.sourcelab.kafka.connect.apiclient.request.get.GetConnectorPlugins;
import org.sourcelab.kafka.connect.apiclient.request.get.GetConnectorStatus;
import org.sourcelab.kafka.connect.apiclient.request.get.GetConnectorTaskStatus;
import org.sourcelab.kafka.connect.apiclient.request.get.GetConnectorTasks;
import org.sourcelab.kafka.connect.apiclient.request.get.GetConnectorTopics;
import org.sourcelab.kafka.connect.apiclient.request.get.GetConnectors;
import org.sourcelab.kafka.connect.apiclient.request.get.GetConnectorsExpandAllDetails;
import org.sourcelab.kafka.connect.apiclient.request.get.GetConnectorsExpandInfo;
import org.sourcelab.kafka.connect.apiclient.request.get.GetConnectorsExpandStatus;
import org.sourcelab.kafka.connect.apiclient.request.post.PostConnector;
import org.sourcelab.kafka.connect.apiclient.request.post.PostConnectorRestart;
import org.sourcelab.kafka.connect.apiclient.request.post.PostConnectorTaskRestart;
import org.sourcelab.kafka.connect.apiclient.request.put.PutConnectorConfig;
import org.sourcelab.kafka.connect.apiclient.request.put.PutConnectorPause;
import org.sourcelab.kafka.connect.apiclient.request.put.PutConnectorStop;
import org.sourcelab.kafka.connect.apiclient.request.put.PutConnectorPluginConfigValidate;
import org.sourcelab.kafka.connect.apiclient.request.put.PutConnectorResume;
import org.sourcelab.kafka.connect.apiclient.request.put.PutConnectorTopicsReset;
import org.sourcelab.kafka.connect.apiclient.rest.HttpClientRestClient;
import org.sourcelab.kafka.connect.apiclient.rest.RestClient;
import org.sourcelab.kafka.connect.apiclient.rest.RestResponse;
import org.sourcelab.kafka.connect.apiclient.rest.exceptions.InvalidRequestException;
import org.sourcelab.kafka.connect.apiclient.rest.exceptions.UnauthorizedRequestException;

import java.io.IOException;
import java.util.Collection;
import java.util.Map;

/**
 * API Client for interacting with the Kafka-Connect Rest Endpoint.
 * Official Rest Endpoint documentation can be found here:
 *   https://docs.confluent.io/current/connect/references/restapi.html
 */
public class KafkaConnectClient {
    private static final Logger logger = LoggerFactory.getLogger(KafkaConnectClient.class);

    /**
     * Our API Configuration.
     */
    private final Configuration configuration;

    /**
     * Underlying RestClient to use.
     */
    private final RestClient restClient;

    /**
     * Internal State flag.
     */
    private boolean isInitialized = false;


    /**
     * Default Constructor.
     * @param configuration Api Client Configuration.
     */
    public KafkaConnectClient(final Configuration configuration) {
        this.configuration = configuration;
        this.restClient = new HttpClientRestClient();
    }

    /**
     * Constructor for injecting a RestClient implementation.
     * Typically only used in testing.
     * @param configuration Api Client Configuration.
     * @param restClient RestClient implementation to use.
     */
    public KafkaConnectClient(final Configuration configuration, final RestClient restClient) {
        this.configuration = configuration;
        this.restClient = restClient;
    }

    /**
     * Retrieve details about the Kafka-Connect service itself.
     * https://docs.confluent.io/current/connect/references/restapi.html#get--
     * @return ConnectServerVersion
     */
    public ConnectServerVersion getConnectServerVersion() {
        return submitRequest(new GetConnectServerVersion());
    }

    /**
     * Get a list of deployed connectors.
     * https://docs.confluent.io/current/connect/references/restapi.html#get--connectors
     *
     * @return Collection of connector names currently deployed.
     */
    public Collection getConnectors() {
        return submitRequest(new GetConnectors());
    }

    /**
     * Get a list of deployed connectors, including the status for each connector.
     * https://docs.confluent.io/current/connect/references/restapi.html#get--connectors
     *
     * Requires Kafka-Connect 2.3.0+
     *
     * @return All deployed connectors, and their respective statuses.
     */
    public ConnectorsWithExpandedStatus getConnectorsWithExpandedStatus() {
        return submitRequest(new GetConnectorsExpandStatus());
    }

    /**
     * Get a list of deployed connectors, including the definition for each connector.
     * https://docs.confluent.io/current/connect/references/restapi.html#get--connectors
     *
     * Requires Kafka-Connect 2.3.0+
     *
     * @return All deployed connectors, and their respective definition.
     */
    public ConnectorsWithExpandedInfo getConnectorsWithExpandedInfo() {
        return submitRequest(new GetConnectorsExpandInfo());
    }

    /**
     * Get a list of deployed connectors, including all metadata available.
     * Currently includes both 'info' and 'status'
     * metadata.
     * https://docs.confluent.io/current/connect/references/restapi.html#get--connectors
     *
     * Requires Kafka-Connect 2.3.0+
     *
     * @return All deployed connectors, and their respective metadata.
     */
    public ConnectorsWithExpandedMetadata getConnectorsWithAllExpandedMetadata() {
        return submitRequest(new GetConnectorsExpandAllDetails());
    }

    /**
     * Get information about the connector.
     * https://docs.confluent.io/current/connect/references/restapi.html#get--connectors-(string-name)
     * @param connectorName Name of connector.
     * @return Connector details.
     */
    public ConnectorDefinition getConnector(final String connectorName) {
        return submitRequest(new GetConnector(connectorName));
    }

    /**
     * Get the configuration for the connector.
     * https://docs.confluent.io/current/connect/references/restapi.html#get--connectors-(string-name)-config
     * @param connectorName Name of connector.
     * @return Configuration for connector.
     */
    public Map getConnectorConfig(final String connectorName) {
        return submitRequest(new GetConnectorConfig(connectorName));
    }

    /**
     * Get the status of specified connector by name.
     * https://docs.confluent.io/current/connect/references/restapi.html#get--connectors-(string-name)-config
     *
     * @param connectorName Name of connector.
     * @return Status details of the connector.
     */
    public ConnectorStatus getConnectorStatus(final String connectorName) {
        return submitRequest(new GetConnectorStatus(connectorName));
    }

    /**
     * Get the set of topics that a specific connector is using since the connector was created or since a request
     * to reset its set of active topics was issued.
     * https://docs.confluent.io/current/connect/references/restapi.html#get--connectors-(string-name)-topics
     *
     * Requires Kafka-Connect 2.5.0+
     *
     * @param connectorName Name of connector.
     * @return Connector Topics response.
     */
    public ConnectorTopics getConnectorTopics(final String connectorName) {
        return submitRequest(new GetConnectorTopics(connectorName));
    }

    /**
     * Send a request to empty the set of active topics of a connector.
     * https://docs.confluent.io/current/connect/references/restapi.html#put--connectors-(string-name)-topics-reset
     * Requires Kafka-Connect 2.5.0+
     *
     * @param connectorName Name of connector.
     * @return true on success.
     */
    public boolean resetConnectorTopics(final String connectorName) {
        return submitRequest(new PutConnectorTopicsReset(connectorName));
    }

    /**
     * Create a new connector, returning the current connector info if successful.
     * https://docs.confluent.io/current/connect/references/restapi.html#post--connectors
     *
     * @param connectorDefinition Defines the new connector to deploy
     * @return connector info.
     */
    public ConnectorDefinition addConnector(final NewConnectorDefinition connectorDefinition) {
        return submitRequest(new PostConnector(connectorDefinition));
    }

    /**
     * Update a connector's configuration.
     * https://docs.confluent.io/current/connect/references/restapi.html#put--connectors-(string-name)-config
     *
     * @param connectorName Name of connector to update.
     * @param config Configuration values to set.
     * @return ConnectorDefinition describing the connectors configuration.
     */
    public ConnectorDefinition updateConnectorConfig(final String connectorName, final Map config) {
        return submitRequest(new PutConnectorConfig(connectorName, config));
    }

    /**
     * Restart a connector.
     * https://docs.confluent.io/current/connect/references/restapi.html#post--connectors-(string-name)-restart
     *
     * @param connectorName Name of connector to restart.
     * @return Boolean true if success.
     */
    public Boolean restartConnector(final String connectorName) {
        return restartConnector(new PostConnectorRestart(connectorName));
    }

    /**
     * Restart a connector.
     * https://docs.confluent.io/current/connect/references/restapi.html#post--connectors-(string-name)-restart
     *
     * @param connectorRestartRequest Defines the connector restart request.
     * @return Boolean true if success.
     */
    public Boolean restartConnector(final PostConnectorRestart connectorRestartRequest)
    {
        return submitRequest(connectorRestartRequest);
    }

    /**
     * Pause a connector.
     * https://docs.confluent.io/current/connect/references/restapi.html#put--connectors-(string-name)-pause
     *
     * @param connectorName Name of connector to pause.
     * @return Boolean true if success.
     */
    public Boolean pauseConnector(final String connectorName) {
        return submitRequest(new PutConnectorPause(connectorName));
    }

    /**
     * Stop a connector.
     * https://docs.confluent.io/current/connect/references/restapi.html#put--connectors-(string-name)-stop
     *
     * @param connectorName Name of connector to stop.
     * @return Boolean true if success.
     */
    public Boolean stopConnector(final String connectorName) {
        return submitRequest(new PutConnectorStop(connectorName));
    }

    /**
     * Resume a connector.
     * https://docs.confluent.io/current/connect/references/restapi.html#put--connectors-(string-name)-resume
     *
     * @param connectorName Name of connector to resume.
     * @return Boolean true if success.
     */
    public Boolean resumeConnector(final String connectorName) {
        return submitRequest(new PutConnectorResume(connectorName));
    }

    /**
     * Delete a connector.
     * https://docs.confluent.io/platform/current/connect/references/restapi.html#delete--connectors-(string-name)-
     *
     * @param connectorName Name of connector to resume.
     * @return Boolean true if success.
     */
    public Boolean deleteConnector(final String connectorName) {
        return submitRequest(new DeleteConnector(connectorName));
    }

    /**
     * Get a list of tasks currently running for the connector.
     * https://docs.confluent.io/current/connect/references/restapi.html#get--connectors-(string-name)-tasks
     *
     * @param connectorName Name of connector to retrieve tasks for.
     * @return Collection of details about each task.
     */
    public Collection getConnectorTasks(final String connectorName) {
        return submitRequest(new GetConnectorTasks(connectorName));
    }

    /**
     * Get a task’s status.
     * https://docs.confluent.io/current/connect/references/restapi.html#get--connectors-(string-name)-tasks-(int-taskid)-status
     *
     * @param connectorName Name of connector to retrieve tasks for.
     * @param taskId Id of task to get status for.
     * @return Details about task.
     */
    public TaskStatus getConnectorTaskStatus(final String connectorName, final int taskId) {
        return submitRequest(new GetConnectorTaskStatus(connectorName, taskId));
    }

    /**
     * Restart an individual task.
     * https://docs.confluent.io/current/connect/references/restapi.html#post--connectors-(string-name)-tasks-(int-taskid)-restart
     *
     * @param connectorName Name of connector to restart tasks for.
     * @param taskId Id of task to restart
     * @return True if a success.
     */
    public Boolean restartConnectorTask(final String connectorName, final int taskId) {
        return submitRequest(new PostConnectorTaskRestart(connectorName, taskId));
    }

    /**
     * Return a list of connector plugins installed in the Kafka Connect cluster.
     * https://docs.confluent.io/current/connect/references/restapi.html#get--connector-plugins-
     *
     * @return Collection of available connector plugins.
     */
    public Collection getConnectorPlugins() {
        return submitRequest(new GetConnectorPlugins());
    }

    /**
     * Validate the provided configuration values against the configuration definition. This API performs per config
     * validation, returns suggested values and error messages during validation.
     * https://docs.confluent.io/current/connect/references/restapi.html#put--connector-plugins-(string-name)-config-validate
     *
     * @param configDefinition Defines the configuration to validate.
     * @return Results of the validation.
     */
    public ConnectorPluginConfigValidationResults validateConnectorPluginConfig(final ConnectorPluginConfigDefinition configDefinition) {
        return submitRequest(
            new PutConnectorPluginConfigValidate(configDefinition.getName(), configDefinition.getConfig())
        );
    }

    private  T submitRequest(final Request request) {
        // Submit request
        final RestResponse restResponse = getRestClient().submitRequest(request);
        final int responseCode = restResponse.getHttpCode();
        String responseStr = restResponse.getResponseStr();

        // If we have a valid response
        logger.debug("Response: {}", restResponse);

        // Check for invalid http status codes
        if (responseCode >= 200 && responseCode < 300) {
            // These response codes have no values
            if ((responseCode == 204 || responseCode == 205) && responseStr == null) {
                // Avoid NPE
                responseStr = "";
            }

            try {
                return request.parseResponse(responseStr);
            } catch (final MismatchedInputException exception) {
                throw new ResponseParseException(exception.getMessage(), exception);
            } catch (final IOException exception) {
                throw new RuntimeException(exception.getMessage(), exception);
            }
        }

        // Server reject's client's authentication.
        if (responseCode == HttpStatus.SC_UNAUTHORIZED) {
            // Throw contextual error msg based on if credentials are configured or not.
            String errorMsg;
            if (configuration.getBasicAuthUsername() == null) {
                errorMsg = "Server required authentication credentials but none were provided in client configuration.";
            } else {
                errorMsg = "Client authentication credentials (username=" + configuration.getBasicAuthUsername() + ") was rejected by server.";
            }
            errorMsg = errorMsg + " Server responded with: \"" + responseStr + "\"";
            throw new UnauthorizedRequestException(errorMsg, responseCode);
        }

        // Attempt to parse error response
        try {
            final RequestErrorResponse errorResponse = JacksonFactory.newInstance().readValue(responseStr, RequestErrorResponse.class);
            throw InvalidRequestException.factory(errorResponse);
        } catch (final IOException e) {
            // swallow
        }
        throw new InvalidRequestException("Invalid response from server: " + responseStr, restResponse.getHttpCode());
    }

    private RestClient getRestClient() {
        // If we haven't initialized.
        if (!isInitialized) {
            // Call Init.
            restClient.init(getConfiguration());

            // Flip state flag
            isInitialized = true;
        }

        // return our rest client.
        return restClient;
    }

    private Configuration getConfiguration() {
        return configuration;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy