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

com.instaclustr.cassandra.sidecar.coordination.SidecarBackupOperationCoordinator Maven / Gradle / Ivy

There is a newer version: 2.0.0-alpha8
Show newest version
package com.instaclustr.cassandra.sidecar.coordination;

import static com.instaclustr.cassandra.sidecar.coordination.CoordinationUtils.constructSidecars;
import static java.lang.String.format;
import static java.util.concurrent.CompletableFuture.allOf;
import static java.util.concurrent.CompletableFuture.supplyAsync;

import java.net.InetAddress;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.Inject;
import com.instaclustr.cassandra.backup.guice.BackuperFactory;
import com.instaclustr.cassandra.backup.guice.BucketServiceFactory;
import com.instaclustr.cassandra.backup.impl.StorageLocation;
import com.instaclustr.cassandra.backup.impl.backup.BackupOperation;
import com.instaclustr.cassandra.backup.impl.backup.BackupOperationRequest;
import com.instaclustr.cassandra.backup.impl.backup.BackupPhaseResultGatherer;
import com.instaclustr.cassandra.backup.impl.backup.Backuper;
import com.instaclustr.cassandra.backup.impl.backup.UploadTracker;
import com.instaclustr.cassandra.backup.impl.backup.coordination.BaseBackupOperationCoordinator;
import com.instaclustr.cassandra.sidecar.rest.SidecarClient;
import com.instaclustr.cassandra.sidecar.rest.SidecarClient.OperationResult;
import com.instaclustr.cassandra.topology.CassandraClusterTopology;
import com.instaclustr.cassandra.topology.CassandraClusterTopology.ClusterTopology;
import com.instaclustr.operations.GlobalOperationProgressTracker;
import com.instaclustr.operations.Operation;
import com.instaclustr.operations.OperationsService;
import com.instaclustr.operations.ResultGatherer;
import com.instaclustr.sidecar.picocli.SidecarSpec;
import com.instaclustr.threading.Executors.ExecutorServiceSupplier;
import jmx.org.apache.cassandra.service.CassandraJMXService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SidecarBackupOperationCoordinator extends BaseBackupOperationCoordinator {

    private static final int MAX_NUMBER_OF_CONCURRENT_OPERATIONS = Integer.parseInt(System.getProperty("instaclustr.sidecar.operations.executor.size", "100"));

    private static final Logger logger = LoggerFactory.getLogger(SidecarBackupOperationCoordinator.class);

    private final SidecarSpec sidecarSpec;
    private final ExecutorServiceSupplier executorServiceSupplier;

    @Inject
    public SidecarBackupOperationCoordinator(final CassandraJMXService cassandraJMXService,
                                             final Map backuperFactoryMap,
                                             final Map bucketServiceFactoryMap,
                                             final OperationsService operationsService,
                                             final SidecarSpec sidecarSpec,
                                             final ExecutorServiceSupplier executorServiceSupplier,
                                             final ObjectMapper objectMapper,
                                             final UploadTracker uploadTracker) {
        super(cassandraJMXService, backuperFactoryMap, bucketServiceFactoryMap, objectMapper, uploadTracker);
        this.sidecarSpec = sidecarSpec;
        this.executorServiceSupplier = executorServiceSupplier;
    }

    @Override
    public ResultGatherer coordinate(final Operation operation) {

        if (!operation.request.globalRequest) {
            return super.coordinate(operation);
        }

        ClusterTopology topology;

        try {
            topology = new CassandraClusterTopology(cassandraJMXService, operation.request.dc).act();
        } catch (final Exception ex) {
            return new BackupPhaseResultGatherer().gather(operation, new OperationCoordinatorException("Unable to get cluster topology.", ex));
        }

        final Map endpoints = topology.endpoints;

        logger.info("Datacenter to be backed up: {}", operation.request.dc == null ? "all of them" : operation.request.dc);

        logger.info("Resolved endpoints: {}", endpoints.toString());

        final Map endpointDCs = topology.endpointDcs;

        logger.info("Resolved endpoints and their dc: {}", endpointDCs.toString());

        final String clusterName = topology.clusterName;

        logger.info("Resolved cluster name: {}", clusterName);

        final Map sidecarClientMap = constructSidecars(clusterName, endpoints, endpointDCs, sidecarSpec, objectMapper);

        logger.info("Executing backup requests against {}", sidecarClientMap.toString());

        operation.request.schemaVersion = topology.schemaVersion;
        operation.request.snapshotTag = resolveSnapshotTag(operation.request, topology.timestamp);

        logger.info("Resolved schema version: {}, ", operation.request.schemaVersion);
        logger.info("Resolved snapshot name : {}, ", operation.request.snapshotTag);

        final BackupRequestPreparation backupRequestPreparation = (client, globalRequest, clusterTopology) -> {

            try {
                if (!client.getHostId().isPresent()) {
                    throw new OperationCoordinatorException(format("There is not any hostId for client %s", client.getHost()));
                }

                final BackupOperationRequest clonedRequest = (BackupOperationRequest) globalRequest.clone();
                final BackupOperation backupOperation = new BackupOperation(clonedRequest);
                backupOperation.request.globalRequest = false;

                backupOperation.request.storageLocation = StorageLocation.update(backupOperation.request.storageLocation,
                                                                                 client.getClusterName(),
                                                                                 client.getDc(),
                                                                                 client.getHostId().get().toString());

                backupOperation.request.storageLocation.globalRequest = false;

                return backupOperation;
            } catch (final Exception ex) {
                throw new OperationCoordinatorException(format("Unable to prepare backup operation for client %s.", client.getHost()), ex);
            }
        };


        BackupPhaseResultGatherer backupPhaseResultGatherer = executeDistributedBackup(operation,
                                                                                       sidecarClientMap,
                                                                                       backupRequestPreparation,
                                                                                       topology);

        if (backupPhaseResultGatherer.hasErrors()) {
            return backupPhaseResultGatherer;
        }

        try {
            final String clusterTopologyString = ClusterTopology.writeToString(objectMapper, topology);

            final String clusterId = sidecarClientMap.entrySet().iterator().next().getValue().getClusterName();

            final Path topologyPath = Paths.get(format("topology/%s-%s-topology.json", clusterId, operation.request.snapshotTag));

            logger.info("Uploading cluster topology under {}", topologyPath);
            logger.info("\n" + clusterTopologyString);

            try (Backuper backuper = backuperFactoryMap.get(operation.request.storageLocation.storageProvider).createBackuper(operation.request)) {
                backuper.uploadText(clusterTopologyString, backuper.objectKeyToRemoteReference(topologyPath));
            }
        } catch (final Exception ex) {
            backupPhaseResultGatherer.gather(operation, new OperationCoordinatorException("Unable to upload topology file", ex));
        }

        return backupPhaseResultGatherer;
    }

    private interface BackupRequestPreparation {

        Operation prepare(final SidecarClient client, final BackupOperationRequest globalRequest, final ClusterTopology topology) throws OperationCoordinatorException;
    }

    private BackupPhaseResultGatherer executeDistributedBackup(final Operation globalOperation,
                                                               final Map sidecarClientMap,
                                                               final BackupRequestPreparation requestPreparation,
                                                               final ClusterTopology topology) {
        final ExecutorService executorService = executorServiceSupplier.get(MAX_NUMBER_OF_CONCURRENT_OPERATIONS);

        final BackupPhaseResultGatherer resultGatherer = new BackupPhaseResultGatherer();

        try {
            final List callables = new ArrayList<>();
            final GlobalOperationProgressTracker progressTracker = new GlobalOperationProgressTracker(globalOperation, sidecarClientMap.entrySet().size());

            // create

            for (final Map.Entry entry : sidecarClientMap.entrySet()) {
                try {
                    callables.add(new BackupOperationCallable(requestPreparation.prepare(entry.getValue(), globalOperation.request, topology),
                                                              entry.getValue(),
                                                              progressTracker));
                } catch (final OperationCoordinatorException e) {
                    resultGatherer.gather(globalOperation, e);
                    return resultGatherer;
                }
            }

            // submit & gather results

            allOf(callables.stream().map(c -> supplyAsync(c, executorService).whenComplete((result, throwable) -> {
                if (throwable != null) {
                    logger.warn(format("Backup against %s has failed.", result.request.storageLocation));
                    resultGatherer.gather(result, throwable);
                }

                try {
                    result.close();
                } catch (final Exception ex) {
                    logger.error(format("Unable to close a sidecar client for operation %s upon gathering results from other sidecars: %s", result.id, ex.getMessage()));
                }
            })).toArray(CompletableFuture[]::new)).get();
        } catch (ExecutionException | InterruptedException ex) {
            ex.printStackTrace();
            resultGatherer.gather(globalOperation, new OperationCoordinatorException("Unable to coordinate backup! " + ex.getMessage(), ex));
        } finally {
            executorService.shutdownNow();
        }

        return resultGatherer;
    }

    private static class BackupOperationCallable extends OperationCallable {

        public BackupOperationCallable(final Operation operation,
                                       final SidecarClient sidecarClient,
                                       final GlobalOperationProgressTracker progressTracker) {
            super(operation, operation.request.timeout, sidecarClient, progressTracker, "backup");
        }

        public OperationResult sendOperation() {
            return super.sidecarClient.backup(super.operation.request);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy