org.apache.geode.management.internal.cli.commands.ExportImportClusterConfigurationCommands.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.geode.management.internal.cli.commands.ExportImportClusterConfigurationCommands.java

Source

/*
 * 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.geode.management.internal.cli.commands;

import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;

import org.apache.commons.io.FileUtils;
import org.apache.geode.cache.execute.ResultCollector;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.distributed.internal.ClusterConfigurationService;
import org.apache.geode.distributed.internal.InternalLocator;
import org.apache.geode.internal.cache.GemFireCacheImpl;
import org.apache.geode.internal.lang.StringUtils;
import org.apache.geode.internal.logging.LogService;
import org.apache.geode.management.cli.CliMetaData;
import org.apache.geode.management.cli.Result;
import org.apache.geode.management.internal.cli.AbstractCliAroundInterceptor;
import org.apache.geode.management.internal.cli.CliUtil;
import org.apache.geode.management.internal.cli.GfshParseResult;
import org.apache.geode.management.internal.cli.functions.CliFunctionResult;
import org.apache.geode.management.internal.cli.i18n.CliStrings;
import org.apache.geode.management.internal.cli.remote.CommandExecutionContext;
import org.apache.geode.management.internal.cli.result.ErrorResultData;
import org.apache.geode.management.internal.cli.result.FileResult;
import org.apache.geode.management.internal.cli.result.InfoResultData;
import org.apache.geode.management.internal.cli.result.ResultBuilder;
import org.apache.geode.management.internal.configuration.domain.Configuration;
import org.apache.geode.management.internal.configuration.functions.RecreateCacheFunction;
import org.apache.geode.management.internal.configuration.functions.RegionsWithDataOnServerFunction;
import org.apache.geode.management.internal.configuration.utils.ZipUtils;
import org.apache.geode.management.internal.security.ResourceOperation;
import org.apache.geode.security.ResourcePermission.Operation;
import org.apache.geode.security.ResourcePermission.Resource;
import org.apache.logging.log4j.Logger;
import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

/****
 * Commands for the cluster configuration
 *
 */
@SuppressWarnings("unused")
public class ExportImportClusterConfigurationCommands extends AbstractCommandsSupport {
    @CliCommand(value = { CliStrings.EXPORT_SHARED_CONFIG }, help = CliStrings.EXPORT_SHARED_CONFIG__HELP)
    @CliMetaData(interceptor = "org.apache.geode.management.internal.cli.commands.ExportImportClusterConfigurationCommands$ExportInterceptor", relatedTopic = {
            CliStrings.TOPIC_GEODE_CONFIG })
    @ResourceOperation(resource = Resource.CLUSTER, operation = Operation.READ)
    public Result exportSharedConfig(@CliOption(key = {
            CliStrings.EXPORT_SHARED_CONFIG__FILE }, mandatory = true, help = CliStrings.EXPORT_SHARED_CONFIG__FILE__HELP) String zipFileName,

            @CliOption(key = {
                    CliStrings.EXPORT_SHARED_CONFIG__DIR }, help = CliStrings.EXPORT_SHARED_CONFIG__DIR__HELP) String dir) {

        InternalLocator locator = InternalLocator.getLocator();
        if (!locator.isSharedConfigurationRunning()) {
            return ResultBuilder.createGemFireErrorResult(CliStrings.SHARED_CONFIGURATION_NOT_STARTED);
        }

        ClusterConfigurationService sc = locator.getSharedConfiguration();
        File zipFile = new File(zipFileName);
        zipFile.getParentFile().mkdirs();

        Result result;
        try {
            for (Configuration config : sc.getEntireConfiguration().values()) {
                sc.writeConfigToFile(config);
            }
            ZipUtils.zipDirectory(sc.getSharedConfigurationDirPath(), zipFile.getCanonicalPath());

            InfoResultData infoData = ResultBuilder.createInfoResultData();
            byte[] byteData = FileUtils.readFileToByteArray(zipFile);
            infoData.addAsFile(zipFileName, byteData, InfoResultData.FILE_TYPE_BINARY,
                    CliStrings.EXPORT_SHARED_CONFIG__DOWNLOAD__MSG, false);
            result = ResultBuilder.buildResult(infoData);
        } catch (Exception e) {
            ErrorResultData errorData = ResultBuilder.createErrorResultData();
            errorData.addLine("Export failed");
            logSevere(e);
            result = ResultBuilder.buildResult(errorData);
        } finally {
            zipFile.delete();
        }

        return result;
    }

    @CliCommand(value = { CliStrings.IMPORT_SHARED_CONFIG }, help = CliStrings.IMPORT_SHARED_CONFIG__HELP)
    @CliMetaData(interceptor = "org.apache.geode.management.internal.cli.commands.ExportImportClusterConfigurationCommands$ImportInterceptor", relatedTopic = {
            CliStrings.TOPIC_GEODE_CONFIG })
    @ResourceOperation(resource = Resource.CLUSTER, operation = Operation.MANAGE)
    @SuppressWarnings("unchecked")
    public Result importSharedConfig(@CliOption(key = {
            CliStrings.IMPORT_SHARED_CONFIG__ZIP }, mandatory = true, help = CliStrings.IMPORT_SHARED_CONFIG__ZIP__HELP) String zip) {

        InternalLocator locator = InternalLocator.getLocator();

        if (!locator.isSharedConfigurationRunning()) {
            ErrorResultData errorData = ResultBuilder.createErrorResultData();
            errorData.addLine(CliStrings.SHARED_CONFIGURATION_NOT_STARTED);
            return ResultBuilder.buildResult(errorData);
        }

        GemFireCacheImpl cache = GemFireCacheImpl.getInstance();

        Set<DistributedMember> servers = CliUtil.getAllNormalMembers(cache);

        Set<String> regionsWithData = servers.stream().map(this::getNonEmptyRegionsOnServer)
                .flatMap(Collection::stream).collect(toSet());

        if (!regionsWithData.isEmpty()) {
            return ResultBuilder
                    .createGemFireErrorResult("Cannot import cluster configuration with existing data in regions: "
                            + regionsWithData.stream().collect(joining(",")));
        }

        byte[][] shellBytesData = CommandExecutionContext.getBytesFromShell();
        String zipFileName = CliUtil.bytesToNames(shellBytesData)[0];
        byte[] zipBytes = CliUtil.bytesToData(shellBytesData)[0];

        Result result;
        InfoResultData infoData = ResultBuilder.createInfoResultData();
        File zipFile = new File(zipFileName);
        try {
            ClusterConfigurationService sc = locator.getSharedConfiguration();

            // backup the old config
            for (Configuration config : sc.getEntireConfiguration().values()) {
                sc.writeConfigToFile(config);
            }
            sc.renameExistingSharedConfigDirectory();

            FileUtils.writeByteArrayToFile(zipFile, zipBytes);
            ZipUtils.unzip(zipFileName, sc.getSharedConfigurationDirPath());

            // load it from the disk
            sc.loadSharedConfigurationFromDisk();
            infoData.addLine(CliStrings.IMPORT_SHARED_CONFIG__SUCCESS__MSG);

        } catch (Exception e) {
            ErrorResultData errorData = ResultBuilder.createErrorResultData();
            errorData.addLine("Import failed");
            logSevere(e);
            result = ResultBuilder.buildResult(errorData);
            // if import is unsuccessful, don't need to bounce the server.
            return result;
        } finally {
            FileUtils.deleteQuietly(zipFile);
        }

        // Bounce the cache of each member
        Set<CliFunctionResult> functionResults = servers.stream().map(this::reCreateCache).collect(toSet());

        for (CliFunctionResult functionResult : functionResults) {
            if (functionResult.isSuccessful()) {
                infoData.addLine("Successfully applied the imported cluster configuration on "
                        + functionResult.getMemberIdOrName());
            } else {
                infoData.addLine("Failed to apply the imported cluster configuration on "
                        + functionResult.getMemberIdOrName() + " due to " + functionResult.getMessage());
            }
        }

        result = ResultBuilder.buildResult(infoData);
        return result;
    }

    private Set<String> getNonEmptyRegionsOnServer(DistributedMember server) {
        ResultCollector rc = CliUtil.executeFunction(new RegionsWithDataOnServerFunction(), null, server);
        List<Set<String>> results = (List<Set<String>>) rc.getResult();

        return results.get(0);
    }

    private CliFunctionResult reCreateCache(DistributedMember server) {
        ResultCollector rc = CliUtil.executeFunction(new RecreateCacheFunction(), null, server);
        List<CliFunctionResult> results = (List<CliFunctionResult>) rc.getResult();

        return results.get(0);
    }

    @CliAvailabilityIndicator({ CliStrings.EXPORT_SHARED_CONFIG, CliStrings.IMPORT_SHARED_CONFIG })
    public boolean sharedConfigCommandsAvailable() {
        boolean isAvailable = true; // always available on server
        if (CliUtil.isGfshVM()) { // in gfsh check if connected
            isAvailable = getGfsh() != null && getGfsh().isConnectedAndReady();
        }
        return isAvailable;
    }

    /**
     * Interceptor used by gfsh to intercept execution of export shared config command at "shell".
     */
    public static class ExportInterceptor extends AbstractCliAroundInterceptor {
        private String saveDirString;
        private static final Logger logger = LogService.getLogger();

        @Override
        public Result preExecution(GfshParseResult parseResult) {
            Map<String, String> paramValueMap = parseResult.getParamValueStrings();
            String zip = paramValueMap.get(CliStrings.EXPORT_SHARED_CONFIG__FILE);

            if (!zip.endsWith(".zip")) {
                return ResultBuilder
                        .createUserErrorResult(CliStrings.format(CliStrings.INVALID_FILE_EXTENSION, ".zip"));
            }
            return ResultBuilder.createInfoResult("OK");
        }

        @Override
        public Result postExecution(GfshParseResult parseResult, Result commandResult) {
            if (commandResult.hasIncomingFiles()) {
                try {
                    Map<String, String> paramValueMap = parseResult.getParamValueStrings();
                    String dir = paramValueMap.get(CliStrings.EXPORT_SHARED_CONFIG__DIR);
                    dir = (dir == null) ? null : dir.trim();

                    File saveDirFile = new File(".");

                    if (dir != null && !dir.isEmpty()) {
                        saveDirFile = new File(dir);
                        if (saveDirFile.exists()) {
                            if (!saveDirFile.isDirectory()) {
                                return ResultBuilder.createGemFireErrorResult(CliStrings
                                        .format(CliStrings.EXPORT_SHARED_CONFIG__MSG__NOT_A_DIRECTORY, dir));
                            }
                        } else if (!saveDirFile.mkdirs()) {
                            return ResultBuilder.createGemFireErrorResult(CliStrings
                                    .format(CliStrings.EXPORT_SHARED_CONFIG__MSG__CANNOT_CREATE_DIR, dir));
                        }
                    }
                    if (!saveDirFile.canWrite()) {
                        return ResultBuilder.createGemFireErrorResult(
                                CliStrings.format(CliStrings.EXPORT_SHARED_CONFIG__MSG__NOT_WRITEABLE,
                                        saveDirFile.getCanonicalPath()));
                    }
                    saveDirString = saveDirFile.getAbsolutePath();
                    commandResult.saveIncomingFiles(saveDirString);
                    return commandResult;
                } catch (IOException ioex) {
                    logger.error(ioex);
                    return ResultBuilder.createShellClientErrorResult(
                            CliStrings.EXPORT_SHARED_CONFIG__UNABLE__TO__EXPORT__CONFIG + ": " + ioex.getMessage());
                }
            }
            return null;
        }
    }

    public static class ImportInterceptor extends AbstractCliAroundInterceptor {

        public Result preExecution(GfshParseResult parseResult) {
            Map<String, String> paramValueMap = parseResult.getParamValueStrings();

            String zip = paramValueMap.get(CliStrings.IMPORT_SHARED_CONFIG__ZIP);

            zip = StringUtils.trim(zip);

            if (zip == null) {
                return ResultBuilder.createUserErrorResult(CliStrings.format(
                        CliStrings.IMPORT_SHARED_CONFIG__PROVIDE__ZIP, CliStrings.IMPORT_SHARED_CONFIG__ZIP));
            }
            if (!zip.endsWith(CliStrings.ZIP_FILE_EXTENSION)) {
                return ResultBuilder.createUserErrorResult(
                        CliStrings.format(CliStrings.INVALID_FILE_EXTENSION, CliStrings.ZIP_FILE_EXTENSION));
            }

            FileResult fileResult;

            try {
                fileResult = new FileResult(new String[] { zip });
            } catch (FileNotFoundException fnfex) {
                return ResultBuilder.createUserErrorResult("'" + zip + "' not found.");
            } catch (IOException ioex) {
                return ResultBuilder.createGemFireErrorResult(ioex.getClass().getName() + ": " + ioex.getMessage());
            }

            return fileResult;
        }

        @Override
        public Result postExecution(GfshParseResult parseResult, Result commandResult) {
            return null;
        }
    }

}