content: /*
   * Copyright 1999-2018 Alibaba Group Holding Ltd.
   *
   * Licensed 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 com.alibaba.csp.sentinel.dashboard.service;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Set;
  import java.util.concurrent.CompletableFuture;
  import java.util.stream.Collectors;
  
  import com.alibaba.csp.sentinel.cluster.ClusterStateManager;
  import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
  import com.alibaba.csp.sentinel.dashboard.discovery.AppInfo;
  import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement;
  import com.alibaba.csp.sentinel.dashboard.domain.cluster.request.ClusterServerModifyRequest;
  import com.alibaba.csp.sentinel.dashboard.util.AsyncUtils;
  import com.alibaba.csp.sentinel.dashboard.util.ClusterEntityUtils;
  import com.alibaba.csp.sentinel.util.StringUtil;
  
  import com.alibaba.csp.sentinel.dashboard.domain.cluster.ClusterGroupEntity;
  import com.alibaba.csp.sentinel.dashboard.domain.cluster.request.ClusterClientModifyRequest;
  import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterClientStateVO;
  import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterUniversalStatePairVO;
  import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterUniversalStateVO;
  import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ClusterClientConfig;
  import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ServerFlowConfig;
  import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ServerTransportConfig;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.stereotype.Service;
  
  /**
   * @author Eric Zhao
   * @since 1.4.0
   */
  @Service
  public class ClusterConfigService {
  
      @Autowired
      private SentinelApiClient sentinelApiClient;
      @Autowired
      private AppManagement appManagement;
  
      public CompletableFuture<Void> modifyClusterClientConfig(ClusterClientModifyRequest request) {
          if (notClientRequestValid(request)) {
              throw new IllegalArgumentException("Invalid request");
          }
          String app = request.getApp();
          String ip = request.getIp();
          int port = request.getPort();
          return sentinelApiClient.modifyClusterClientConfig(app, ip, port, request.getClientConfig())
              .thenCompose(v -> sentinelApiClient.modifyClusterMode(ip, port, ClusterStateManager.CLUSTER_CLIENT));
      }
  
      private boolean notClientRequestValid(/*@NonNull */ ClusterClientModifyRequest request) {
          ClusterClientConfig config = request.getClientConfig();
          return config == null || StringUtil.isEmpty(config.getServerHost())
              || config.getServerPort() == null || config.getServerPort() <= 0
              || config.getRequestTimeout() == null || config.getRequestTimeout() <= 0;
      }
  
      public CompletableFuture<Void> modifyClusterServerConfig(ClusterServerModifyRequest request) {
          ServerTransportConfig transportConfig = request.getTransportConfig();
          ServerFlowConfig flowConfig = request.getFlowConfig();
          Set<String> namespaceSet = request.getNamespaceSet();
          if (invalidTransportConfig(transportConfig)) {
              throw new IllegalArgumentException("Invalid transport config in request");
          }
          if (invalidFlowConfig(flowConfig)) {
              throw new IllegalArgumentException("Invalid flow config in request");
          }
          if (namespaceSet == null) {
              throw new IllegalArgumentException("namespace set cannot be null");
          }
          String app = request.getApp();
          String ip = request.getIp();
          int port = request.getPort();
          return sentinelApiClient.modifyClusterServerNamespaceSet(app, ip, port, namespaceSet)
              .thenCompose(v -> sentinelApiClient.modifyClusterServerTransportConfig(app, ip, port, transportConfig))
              .thenCompose(v -> sentinelApiClient.modifyClusterServerFlowConfig(app, ip, port, flowConfig))
              .thenCompose(v -> sentinelApiClient.modifyClusterMode(ip, port, ClusterStateManager.CLUSTER_SERVER));
      }
  
      /**
       * Get cluster state list of all available machines of provided application.
       *
       * @param app application name
       * @return cluster state list of all available machines of the application
       * @since 1.4.1
       */
      public CompletableFuture<List<ClusterUniversalStatePairVO>> getClusterUniversalState(String app) {
          if (StringUtil.isBlank(app)) {
              return AsyncUtils.newFailedFuture(new IllegalArgumentException("app cannot be empty"));
          }
          AppInfo appInfo = appManagement.getDetailApp(app);
          if (appInfo == null || appInfo.getMachines() == null) {
              return CompletableFuture.completedFuture(new ArrayList<>());
          }
  
          List<CompletableFuture<ClusterUniversalStatePairVO>> futures = appInfo.getMachines().stream()
              .filter(e -> e.isHealthy())
              .map(machine -> getClusterUniversalState(app, machine.getIp(), machine.getPort())
                  .thenApply(e -> new ClusterUniversalStatePairVO(machine.getIp(), machine.getPort(), e)))
              .collect(Collectors.toList());
  
          return AsyncUtils.sequenceSuccessFuture(futures);
      }
  
      public CompletableFuture<ClusterGroupEntity> getClusterUniversalStateForAppMachine(String app, String machineId) {
          if (StringUtil.isBlank(app)) {
              return AsyncUtils.newFailedFuture(new IllegalArgumentException("app cannot be empty"));
          }
          AppInfo appInfo = appManagement.getDetailApp(app);
          if (appInfo == null || appInfo.getMachines() == null) {
              return AsyncUtils.newFailedFuture(new IllegalArgumentException("app does not have machines"));
          }
  
          boolean machineOk = appInfo.getMachines().stream()
              .filter(e -> e.isHealthy())
              .map(e -> e.getIp() + '@' + e.getPort())
              .anyMatch(e -> e.equals(machineId));
          if (!machineOk) {
              return AsyncUtils.newFailedFuture(new IllegalStateException("machine does not exist or disconnected"));
          }
  
          return getClusterUniversalState(app)
              .thenApply(ClusterEntityUtils::wrapToClusterGroup)
              .thenCompose(e -> e.stream()
                  .filter(e1 -> e1.getMachineId().equals(machineId))
                  .findAny()
                  .map(CompletableFuture::completedFuture)
                  .orElse(AsyncUtils.newFailedFuture(new IllegalStateException("not a server: " + machineId)))
              );
      }
  
      public CompletableFuture<ClusterUniversalStateVO> getClusterUniversalState(String app, String ip, int port) {
          return sentinelApiClient.fetchClusterMode(ip, port)
              .thenApply(e -> new ClusterUniversalStateVO().setStateInfo(e))
              .thenCompose(vo -> {
                  if (vo.getStateInfo().getClientAvailable()) {
                      return sentinelApiClient.fetchClusterClientInfoAndConfig(ip, port)
                          .thenApply(cc -> vo.setClient(new ClusterClientStateVO().setClientConfig(cc)));
                  } else {
                      return CompletableFuture.completedFuture(vo);
                  }
              }).thenCompose(vo -> {
                  if (vo.getStateInfo().getServerAvailable()) {
                      return sentinelApiClient.fetchClusterServerBasicInfo(ip, port)
                          .thenApply(vo::setServer);
                  } else {
                      return CompletableFuture.completedFuture(vo);
                  }
              });
      }
  
      private boolean invalidTransportConfig(ServerTransportConfig transportConfig) {
          return transportConfig == null || transportConfig.getPort() == null || transportConfig.getPort() <= 0
              || transportConfig.getIdleSeconds() == null || transportConfig.getIdleSeconds() <= 0;
      }
  
      private boolean invalidFlowConfig(ServerFlowConfig flowConfig) {
          return flowConfig == null || flowConfig.getSampleCount() == null || flowConfig.getSampleCount() <= 0
              || flowConfig.getIntervalMs() == null || flowConfig.getIntervalMs() <= 0
              || flowConfig.getIntervalMs() % flowConfig.getSampleCount() != 0
              || flowConfig.getMaxAllowedQps() == null || flowConfig.getMaxAllowedQps() < 0;
      }
  }
  