From 2a0491b560f3a0dfbdbd6f7b4427c341dd089017 Mon Sep 17 00:00:00 2001 From: utu Date: Wed, 20 Nov 2019 18:50:48 +0800 Subject: [PATCH 001/190] add authentication to grpc unicall. --- .../fate/serving/core/utils/EncryptUtils.java | 30 ++++ .../serving/federatedml/model/BaseModel.java | 27 +++- proto/proxy.proto | 9 ++ .../service/DataTransferPipedServerImpl.java | 14 ++ .../fate/networking/proxy/util/AuthUtils.java | 151 ++++++++++++++++++ router/src/main/resources/auth_config.json | 12 ++ router/src/main/resources/proxy.properties | 3 +- 7 files changed, 238 insertions(+), 8 deletions(-) create mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java create mode 100644 router/src/main/resources/auth_config.json diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/EncryptUtils.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/EncryptUtils.java index ff6c3b4b..33aeeb2c 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/EncryptUtils.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/EncryptUtils.java @@ -18,9 +18,17 @@ import com.webank.ai.fate.serving.core.bean.EncryptMethod; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; import java.security.MessageDigest; +import java.util.Base64; public class EncryptUtils { + + public static final String UTF8 = "UTF-8"; + private static final String HMACSHA1 = "HmacSHA1"; + public static String encrypt(String originString, EncryptMethod encryptMethod) { try { MessageDigest m = MessageDigest.getInstance(getEncryptMethodString(encryptMethod)); @@ -50,4 +58,26 @@ private static String getEncryptMethodString(EncryptMethod encryptMethod) { } return methodString; } + + public static byte[] HmacSHA1Encrypt(String encryptText, String encryptKey) throws Exception { + byte[] data = encryptKey.getBytes(UTF8); + SecretKey secretKey = new SecretKeySpec(data, HMACSHA1); + Mac mac = Mac.getInstance(HMACSHA1); + mac.init(secretKey); + + byte[] text = encryptText.getBytes(UTF8); + return mac.doFinal(text); + } + + public static String generateSignature(String applyId, String timestamp, String nonce, + String appKey, String appSecret, String uri, String body) { + try { + String encryptText = applyId + "\n" + timestamp + "\n" + nonce + "\n" + appKey + "\n" + uri + "\n" + body; + encryptText = new String(encryptText.getBytes(), EncryptUtil.UTF8); + return Base64.getEncoder().encodeToString(EncryptUtil.HmacSHA1Encrypt(encryptText, appSecret)); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } } diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java index 007244a7..bef4278f 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java @@ -27,19 +27,27 @@ import com.webank.ai.fate.register.url.URL; import com.webank.ai.fate.serving.core.bean.*; import com.webank.ai.fate.serving.core.utils.ProtobufUtils; +import com.webank.ai.fate.networking.proxy.util.AuthUtils; import io.grpc.ManagedChannel; import io.grpc.netty.shaded.io.grpc.netty.NegotiationType; import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.commons.lang3.StringUtils; -import java.util.List; -import java.util.Map; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.*; import java.util.concurrent.TimeUnit; public abstract class BaseModel implements Predictor>, FederatedParams, Map> { private static final Logger LOGGER = LogManager.getLogger(); + + @Autowired + private AuthUtils authUtils; + public static RouterService routerService; protected String componentName; @@ -51,6 +59,7 @@ public void setComponentName(String componentName) { this.componentName = componentName; } + public abstract int initModel(byte[] protoMeta, byte[] protoParam); protected T parseModel(com.google.protobuf.Parser protoParser, byte[] protoString) throws com.google.protobuf.InvalidProtocolBufferException { @@ -77,7 +86,6 @@ public Map predict(Context context, List> in } - ; @Override public void preprocess(Context context, List> inputData, FederatedParams predictParams) { @@ -166,10 +174,10 @@ protected ReturnResult getFederatedPredictFromRemote(Context context, FederatedP // .setValue(ByteString.copyFrom(ObjectTransform.bean2Json(requestData).getBytes())) // .build()); - - packetBuilder.setBody(Proxy.Data.newBuilder() + Proxy.Data body = Proxy.Data.newBuilder() .setValue(ByteString.copyFrom(JSON.toJSONBytes(hostFederatedParams))) - .build()); + .build(); + packetBuilder.setBody(body); Proxy.Metadata.Builder metaDataBuilder = Proxy.Metadata.newBuilder(); Proxy.Topic.Builder topicBuilder = Proxy.Topic.newBuilder(); @@ -188,7 +196,12 @@ protected ReturnResult getFederatedPredictFromRemote(Context context, FederatedP metaDataBuilder.setConf(Proxy.Conf.newBuilder().setOverallTimeout(60 * 1000)); String version = Configuration.getProperty(Dict.VERSION,""); metaDataBuilder.setOperator(Configuration.getProperty(Dict.VERSION,"")); - packetBuilder.setHeader(metaDataBuilder.build()); + Proxy.Metadata header = bodymetaDataBuilder.build(); + packetBuilder.setHeader(header); + + // to add authentication info + packetBuilder = authUtils.addAuthInfo(context, header, body, packetBuilder); + GrpcConnectionPool grpcConnectionPool = GrpcConnectionPool.getPool(); String routerByZkString = Configuration.getProperty(Dict.USE_ZK_ROUTER, Dict.FALSE); boolean routerByzk = Boolean.valueOf(routerByZkString); diff --git a/proto/proxy.proto b/proto/proxy.proto index 3fa8e85e..d852f9b0 100644 --- a/proto/proxy.proto +++ b/proto/proxy.proto @@ -69,10 +69,19 @@ message Data { bytes value = 2; // actual value } +// authentication info +message AuthInfo { + int64 timestamp = 1; // timestamp of request, millisecond + string signature = 2; // signature + string applyId = 3; + string nonce = 4; +} + // data streaming packet message Packet { Metadata header = 1; // packet header Data body = 2; // packet body + AuthInfo auth = 3; // authentication info, optional } // returned by service heartbeat to decide next operation diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/DataTransferPipedServerImpl.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/DataTransferPipedServerImpl.java index 45104ac5..6ba38955 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/DataTransferPipedServerImpl.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/DataTransferPipedServerImpl.java @@ -18,6 +18,9 @@ import com.webank.ai.fate.api.networking.proxy.DataTransferServiceGrpc; import com.webank.ai.fate.api.networking.proxy.Proxy; + +import com.webank.ai.fate.networking.proxy.util.AuthUtils; + import com.webank.ai.fate.networking.proxy.event.model.PipeHandleNotificationEvent; import com.webank.ai.fate.networking.proxy.factory.EventFactory; import com.webank.ai.fate.networking.proxy.factory.GrpcStreamObserverFactory; @@ -57,6 +60,8 @@ public class DataTransferPipedServerImpl extends DataTransferServiceGrpc.DataTra private ErrorUtils errorUtils; private Pipe defaultPipe; private PipeFactory pipeFactory; + @Autowired + private AuthUtils authUtils; @RegisterService(serviceName = "push") @Override @@ -184,6 +189,15 @@ public void pull(Proxy.Metadata inputMetadata, StreamObserver resp @Override @RegisterService(serviceName = "unaryCall") public void unaryCall(Proxy.Packet request, StreamObserver responseObserver) { + // check authentication + if(false == authUtils.checkAuthentication(request)){ + String msg = "authentication not pass!"; + LOGGER.error(msg); + RuntimeException e = new RuntimeException(msg); + responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); + return; + } + Proxy.Metadata inputMetadata = request.getHeader(); String oneLineStringInputMetadata = toStringUtils.toOneLineString(inputMetadata); LOGGER.info("[UNARYCALL][SERVER] server unary request received. src: {}, dst: {}", diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java new file mode 100644 index 00000000..4b4c92b1 --- /dev/null +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java @@ -0,0 +1,151 @@ +/* + * Copyright 2019 The FATE Authors. All Rights Reserved. + * + * 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.webank.ai.fate.networking.proxy.util; + +import com.webank.ai.fate.serving.core.bean.*; +import com.webank.ai.fate.serving.core.utils.EncryptUtils; +import com.webank.ai.fate.api.networking.proxy.Proxy; + +import org.springframework.stereotype.Component; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; + +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.*; + +@Component +public class AuthUtils implements InitializingBean{ + private static final Logger LOGGER = LogManager.getLogger(); + @Value("${auth.key.path}") + private String keysFilePath; + private static Map ACCESS_KEYS_MAP = new HashMap<>(); + private static int validRequestTimeoutSecond = 10; + private static String applyId = ""; + private static boolean ifUseAuth = false; + @Autowired + private ToStringUtils toStringUtils; + + @Scheduled(fixedRate = 10000) + public void loadConfig(){ + JsonParser jsonParser = new JsonParser(); + JsonReader jsonReader = null; + JsonObject jsonObject = null; + try { + jsonReader = new JsonReader(new FileReader(keysFilePath)); + jsonObject = jsonParser.parse(jsonReader).getAsJsonObject(); + } catch (FileNotFoundException e) { + logger.error("File not found: {}", keysFilePath); + throw new RuntimeException(e); + } finally { + if (jsonReader != null) { + try { + jsonReader.close(); + } catch (IOException ignore) { + } + } + } + + ifUseAuth = jsonObject.get("if_use_auth").getAsBoolean(); + validRequestTimeoutSecond = jsonObject.get("request_expire_seconds").getAsInt(); + applyId = jsonObject.get("apply_id").getAsString(); + + JsonArray jsonArray = jsonObject.getAsJsonArray("access_keys"); + List allowKeys = gson.fromJson(jsonArray, ArrayList.class); + ACCESS_KEYS_MAP.clear(); + for (Map allowKey : allowKeys) { + ACCESS_KEYS_MAP.put(allowKey.get("appKey").toString(), allowKey.get("appSecret").toString()); + } + } + + private String getSecret(String appKey) { + return ACCESS_KEYS_MAP.get(appKey); + } + + private String calSignature(Proxy.Metadata header, Proxy.Data body) { + String signature = ""; + String appSecret = getSecret(header.getDst().getPartyId()); + if (StringUtils.isEmpty(appSecret)) { + logger.error("appSecret not found"); + return signature; + } + String encryptText = String.valueOf(timestamp) + "\n" + + toStringUtils.toOneLineString(header) + "\n" + + toStringUtils.toOneLineString(body); + encryptText = new String(encryptText.getBytes(), EncryptUtils.UTF8); + signature = Base64.getEncoder().encodeToString(EncryptUtils.HmacSHA1Encrypt(encryptText, appSecret)); + return signature; + } + + public Proxy.Packet.Builder addAuthInfo(Context context, Proxy.Metadata header, Proxy.Data body, Proxy.Packet.Builder packetBuilder) { + + Proxy.AuthInfo.Builder authBuilder = Proxy.AuthInfo.newBuilder(); + long timestamp = System.currentTimeMillis(); + authBuilder.setTimestamp(timestamp); + authBuilder.setNonce(context.getCaseId()); + authBuilder.setApplyId(applyId); + if(ifUseAuth){ + String signature = calSignature(header, body); + authBuilder.setSignature(signature); + } + packetBuilder.setAuth(authBuilder.build()); + + return packetBuilder; + } + + public boolean checkAuthentication(Proxy.Packet packet) { + if(ifUseAuth) { + // check timestamp + long currentTimeMillis = System.currentTimeMillis(); + long requestTimeMillis = packet.getAuth().getTimestamp(); + if (currentTimeMillis >= (requestTimeMillis + validRequestTimeoutSecond * 1000)) { + logger.error("receive an expired request, currentTimeMillis:{}, requestTimeMillis{}.", currentTimeMillis, requestTimeMillis); + return false; + } + // check signature + String reqSignature = packet.getAuth().getSignature(); + String validSignature = calSignature(packet.getHeader(), packet.getBody()); + if (!StringUtils.equals(signature, validSignature)) { + logger.error("invalid signature, request:{}, valid:{}", reqSignature, validSignature); + return false; + } + } + return true; + } + + @Override + public void afterPropertiesSet() throws Exception { + + try { + loadConfig(); + } catch (Throwable e) { + LOGGER.error("load authencation keys error", e); + } + + } +} diff --git a/router/src/main/resources/auth_config.json b/router/src/main/resources/auth_config.json new file mode 100644 index 00000000..0f412707 --- /dev/null +++ b/router/src/main/resources/auth_config.json @@ -0,0 +1,12 @@ +{ + "if_use_auth": false, + "request_expire_seconds": 20, + "apply_id": "20191119163236256", + "access_keys": [{ + "appKey": "111", + "appSecret": "11111" + }, { + "appKey": "222", + "appSecret": "22222" + }] +} diff --git a/router/src/main/resources/proxy.properties b/router/src/main/resources/proxy.properties index 05dfa452..2e36a4d3 100644 --- a/router/src/main/resources/proxy.properties +++ b/router/src/main/resources/proxy.properties @@ -24,4 +24,5 @@ route.table=/networking/proxy/src/main/resources/route_tables/route_table.json #server.key=/Users/max-webank/Projects/fdn/fdn-proxy/src/main/resources/certs/server-private.pem root.crt= useJMX=false -#jmx.server.name=JMXServer \ No newline at end of file +#jmx.server.name=JMXServer +auth.key.path=/networking/proxy/src/main/resources/auth_config.json From 7a02c699da56129e25c6a841cc12002d9f29fbbe Mon Sep 17 00:00:00 2001 From: utu Date: Wed, 20 Nov 2019 21:37:44 +0800 Subject: [PATCH 002/190] add authentication to grpc unicall. --- .../webank/ai/fate/serving/federatedml/model/BaseModel.java | 4 ++-- proto/proxy.proto | 1 + .../com/webank/ai/fate/networking/proxy/util/AuthUtils.java | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java index bef4278f..d1c2f623 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java @@ -195,12 +195,12 @@ protected ReturnResult getFederatedPredictFromRemote(Context context, FederatedP metaDataBuilder.setCommand(Proxy.Command.newBuilder().setName(remoteMethodName).build()); metaDataBuilder.setConf(Proxy.Conf.newBuilder().setOverallTimeout(60 * 1000)); String version = Configuration.getProperty(Dict.VERSION,""); - metaDataBuilder.setOperator(Configuration.getProperty(Dict.VERSION,"")); + Proxy.Metadata header = bodymetaDataBuilder.build(); packetBuilder.setHeader(header); // to add authentication info - packetBuilder = authUtils.addAuthInfo(context, header, body, packetBuilder); + packetBuilder = authUtils.addAuthInfo(context, header, body, packetBuilder, version); GrpcConnectionPool grpcConnectionPool = GrpcConnectionPool.getPool(); String routerByZkString = Configuration.getProperty(Dict.USE_ZK_ROUTER, Dict.FALSE); diff --git a/proto/proxy.proto b/proto/proxy.proto index d852f9b0..761f9fb3 100644 --- a/proto/proxy.proto +++ b/proto/proxy.proto @@ -75,6 +75,7 @@ message AuthInfo { string signature = 2; // signature string applyId = 3; string nonce = 4; + string version = 5; } // data streaming packet diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java index 4b4c92b1..5321d12b 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java @@ -102,13 +102,14 @@ private String calSignature(Proxy.Metadata header, Proxy.Data body) { return signature; } - public Proxy.Packet.Builder addAuthInfo(Context context, Proxy.Metadata header, Proxy.Data body, Proxy.Packet.Builder packetBuilder) { + public Proxy.Packet.Builder addAuthInfo(Context context, Proxy.Metadata header, Proxy.Data body, Proxy.Packet.Builder packetBuilder, String version) { Proxy.AuthInfo.Builder authBuilder = Proxy.AuthInfo.newBuilder(); long timestamp = System.currentTimeMillis(); authBuilder.setTimestamp(timestamp); authBuilder.setNonce(context.getCaseId()); authBuilder.setApplyId(applyId); + authBuilder.setVersion(version); if(ifUseAuth){ String signature = calSignature(header, body); authBuilder.setSignature(signature); From ff14679145edd19b6980a5e09072bf644dd81769 Mon Sep 17 00:00:00 2001 From: zengjice Date: Wed, 20 Nov 2019 21:42:07 +0800 Subject: [PATCH 003/190] modify remoteModelInferenceResultCacheDBIndex default value to 0 because of not support db index greater than 0 on redis cluster --- serving-server/conf/serving-server.properties | 2 +- serving-server/src/main/resources/serving-server.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/serving-server/conf/serving-server.properties b/serving-server/conf/serving-server.properties index 0fc2a0fb..8dab0cd3 100644 --- a/serving-server/conf/serving-server.properties +++ b/serving-server/conf/serving-server.properties @@ -39,7 +39,7 @@ redis.timeout=10 redis.maxTotal=100 redis.maxIdle=100 external.remoteModelInferenceResultCacheTTL=86400 -external.remoteModelInferenceResultCacheDBIndex=1,10 +external.remoteModelInferenceResultCacheDBIndex=0 external.inferenceResultCacheTTL=300 external.inferenceResultCacheDBIndex=0 canCacheRetcode=0,102 diff --git a/serving-server/src/main/resources/serving-server.properties b/serving-server/src/main/resources/serving-server.properties index 50ace8d1..040e3164 100644 --- a/serving-server/src/main/resources/serving-server.properties +++ b/serving-server/src/main/resources/serving-server.properties @@ -39,7 +39,7 @@ redis.timeout=10 redis.maxTotal=100 redis.maxIdle=100 external.remoteModelInferenceResultCacheTTL=86400 -external.remoteModelInferenceResultCacheDBIndex=1,10 +external.remoteModelInferenceResultCacheDBIndex=0 external.inferenceResultCacheTTL=300 external.inferenceResultCacheDBIndex=0 canCacheRetcode=0,102 From fa4b4b05bcc8a96a88f07dbc3c82f097f5aee1c3 Mon Sep 17 00:00:00 2001 From: zengjice Date: Thu, 21 Nov 2019 10:38:15 +0800 Subject: [PATCH 004/190] Remove support hash shard storage on redis --- .../core/manager/DefaultCacheManager.java | 18 +++++++++--------- serving-server/conf/serving-server.properties | 2 +- .../main/resources/serving-server.properties | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java index 1ae5e23d..00ddbe72 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java @@ -45,9 +45,9 @@ public class DefaultCacheManager implements CacheManager, InitializingBean { private Cache inferenceResultCache; private Cache remoteModelInferenceResultCache; private Cache processDataCache; - private int[] remoteModelInferenceResultCacheDBIndex; - private int[] inferenceResultCacheDBIndex; - private int[] processCacheDBIndex; + private int remoteModelInferenceResultCacheDBIndex; + private int inferenceResultCacheDBIndex; + private int processCacheDBIndex; private int externalRemoteModelInferenceResultCacheTTL; @@ -82,10 +82,10 @@ public class DefaultCacheManager implements CacheManager, InitializingBean { Configuration.getProperty("redis.password")); - inferenceResultCacheDBIndex = initializeCacheDBIndex(Configuration.getProperty("external.inferenceResultCacheDBIndex")); + inferenceResultCacheDBIndex = Configuration.getPropertyInt("external.inferenceResultCacheDBIndex"); externalInferenceResultCacheTTL = Configuration.getPropertyInt("external.inferenceResultCacheTTL"); - remoteModelInferenceResultCacheDBIndex = initializeCacheDBIndex(Configuration.getProperty("external.remoteModelInferenceResultCacheDBIndex")); - processCacheDBIndex = initializeCacheDBIndex(Configuration.getProperty("external.processCacheDBIndex")); + remoteModelInferenceResultCacheDBIndex = Configuration.getPropertyInt("external.remoteModelInferenceResultCacheDBIndex"); + processCacheDBIndex = Configuration.getPropertyInt("external.processCacheDBIndex"); externalRemoteModelInferenceResultCacheTTL = Configuration.getPropertyInt("external.remoteModelInferenceResultCacheTTL"); canCacheRetcode = initializeCanCacheRetcode(); } @@ -247,15 +247,15 @@ private CacheValueConfig getCacheValueConfig(String cacheKey, CacheType cacheTyp int ttl; switch (cacheType) { case INFERENCE_RESULT: - dbIndex = getCacheDBIndex(cacheKey, inferenceResultCacheDBIndex); + dbIndex = inferenceResultCacheDBIndex; ttl = externalInferenceResultCacheTTL + new Random().nextInt(10); return new CacheValueConfig<>(dbIndex, ttl, inferenceResultCache); case REMOTE_MODEL_INFERENCE_RESULT: - dbIndex = getCacheDBIndex(cacheKey, remoteModelInferenceResultCacheDBIndex); + dbIndex = remoteModelInferenceResultCacheDBIndex; ttl = externalRemoteModelInferenceResultCacheTTL + new Random().nextInt(100); return new CacheValueConfig<>(dbIndex, ttl, remoteModelInferenceResultCache); case PROCESS_DATA: - dbIndex = getCacheDBIndex(cacheKey, processCacheDBIndex); + dbIndex = processCacheDBIndex; ttl = 60; return new CacheValueConfig<>(dbIndex, ttl, processDataCache); default: diff --git a/serving-server/conf/serving-server.properties b/serving-server/conf/serving-server.properties index 8dab0cd3..0fbde69c 100644 --- a/serving-server/conf/serving-server.properties +++ b/serving-server/conf/serving-server.properties @@ -43,7 +43,7 @@ external.remoteModelInferenceResultCacheDBIndex=0 external.inferenceResultCacheTTL=300 external.inferenceResultCacheDBIndex=0 canCacheRetcode=0,102 -external.processCacheDBIndex=3 +external.processCacheDBIndex=0 # federation party.id=9999 # adapter diff --git a/serving-server/src/main/resources/serving-server.properties b/serving-server/src/main/resources/serving-server.properties index 040e3164..d0ad9884 100644 --- a/serving-server/src/main/resources/serving-server.properties +++ b/serving-server/src/main/resources/serving-server.properties @@ -43,7 +43,7 @@ external.remoteModelInferenceResultCacheDBIndex=0 external.inferenceResultCacheTTL=300 external.inferenceResultCacheDBIndex=0 canCacheRetcode=0,102 -external.processCacheDBIndex=3 +external.processCacheDBIndex=0 # federation party.id=9999 # adapter From 0d3f3236be8eb72c39104990805312598fa396f7 Mon Sep 17 00:00:00 2001 From: utu Date: Thu, 21 Nov 2019 18:02:56 +0800 Subject: [PATCH 005/190] 1 --- .../fate/serving/core/utils/EncryptUtils.java | 4 +-- .../serving/federatedml/model/BaseModel.java | 34 +++++++------------ .../fate/networking/proxy/util/AuthUtils.java | 9 +++-- 3 files changed, 19 insertions(+), 28 deletions(-) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/EncryptUtils.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/EncryptUtils.java index 33aeeb2c..064eac15 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/EncryptUtils.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/EncryptUtils.java @@ -73,8 +73,8 @@ public static String generateSignature(String applyId, String timestamp, String String appKey, String appSecret, String uri, String body) { try { String encryptText = applyId + "\n" + timestamp + "\n" + nonce + "\n" + appKey + "\n" + uri + "\n" + body; - encryptText = new String(encryptText.getBytes(), EncryptUtil.UTF8); - return Base64.getEncoder().encodeToString(EncryptUtil.HmacSHA1Encrypt(encryptText, appSecret)); + encryptText = new String(encryptText.getBytes(), UTF8); + return Base64.getEncoder().encodeToString(HmacSHA1Encrypt(encryptText, appSecret)); } catch (Exception e) { e.printStackTrace(); } diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java index d1c2f623..15f971c8 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java @@ -27,27 +27,19 @@ import com.webank.ai.fate.register.url.URL; import com.webank.ai.fate.serving.core.bean.*; import com.webank.ai.fate.serving.core.utils.ProtobufUtils; -import com.webank.ai.fate.networking.proxy.util.AuthUtils; import io.grpc.ManagedChannel; import io.grpc.netty.shaded.io.grpc.netty.NegotiationType; import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.commons.lang3.StringUtils; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.util.*; +import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; public abstract class BaseModel implements Predictor>, FederatedParams, Map> { private static final Logger LOGGER = LogManager.getLogger(); - - @Autowired - private AuthUtils authUtils; - public static RouterService routerService; protected String componentName; @@ -59,7 +51,6 @@ public void setComponentName(String componentName) { this.componentName = componentName; } - public abstract int initModel(byte[] protoMeta, byte[] protoParam); protected T parseModel(com.google.protobuf.Parser protoParser, byte[] protoString) throws com.google.protobuf.InvalidProtocolBufferException { @@ -174,10 +165,10 @@ protected ReturnResult getFederatedPredictFromRemote(Context context, FederatedP // .setValue(ByteString.copyFrom(ObjectTransform.bean2Json(requestData).getBytes())) // .build()); - Proxy.Data body = Proxy.Data.newBuilder() + + packetBuilder.setBody(Proxy.Data.newBuilder() .setValue(ByteString.copyFrom(JSON.toJSONBytes(hostFederatedParams))) - .build(); - packetBuilder.setBody(body); + .build()); Proxy.Metadata.Builder metaDataBuilder = Proxy.Metadata.newBuilder(); Proxy.Topic.Builder topicBuilder = Proxy.Topic.newBuilder(); @@ -195,13 +186,14 @@ protected ReturnResult getFederatedPredictFromRemote(Context context, FederatedP metaDataBuilder.setCommand(Proxy.Command.newBuilder().setName(remoteMethodName).build()); metaDataBuilder.setConf(Proxy.Conf.newBuilder().setOverallTimeout(60 * 1000)); String version = Configuration.getProperty(Dict.VERSION,""); - - Proxy.Metadata header = bodymetaDataBuilder.build(); - packetBuilder.setHeader(header); - - // to add authentication info - packetBuilder = authUtils.addAuthInfo(context, header, body, packetBuilder, version); - + metaDataBuilder.setOperator(Configuration.getProperty(Dict.VERSION,"")); + packetBuilder.setHeader(metaDataBuilder.build()); + + Proxy.AuthInfo.Builder authBuilder = Proxy.AuthInfo.newBuilder(); + authBuilder.setNonce(context.getCaseId()); + authBuilder.setVersion(version); + packetBuilder.setAuth(authBuilder.build()); + GrpcConnectionPool grpcConnectionPool = GrpcConnectionPool.getPool(); String routerByZkString = Configuration.getProperty(Dict.USE_ZK_ROUTER, Dict.FALSE); boolean routerByzk = Boolean.valueOf(routerByZkString); diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java index 5321d12b..7fd346b3 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java @@ -37,6 +37,7 @@ import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.util.*; @Component @@ -87,7 +88,7 @@ private String getSecret(String appKey) { return ACCESS_KEYS_MAP.get(appKey); } - private String calSignature(Proxy.Metadata header, Proxy.Data body) { + private String calSignature(Proxy.Metadata header, Proxy.Data body) throws Exception { String signature = ""; String appSecret = getSecret(header.getDst().getPartyId()); if (StringUtils.isEmpty(appSecret)) { @@ -102,14 +103,12 @@ private String calSignature(Proxy.Metadata header, Proxy.Data body) { return signature; } - public Proxy.Packet.Builder addAuthInfo(Context context, Proxy.Metadata header, Proxy.Data body, Proxy.Packet.Builder packetBuilder, String version) { + public Proxy.Packet.Builder addAuthInfo(Proxy.Metadata header, Proxy.Data body, Proxy.Packet.Builder packetBuilder) throws Exception { Proxy.AuthInfo.Builder authBuilder = Proxy.AuthInfo.newBuilder(); long timestamp = System.currentTimeMillis(); authBuilder.setTimestamp(timestamp); - authBuilder.setNonce(context.getCaseId()); authBuilder.setApplyId(applyId); - authBuilder.setVersion(version); if(ifUseAuth){ String signature = calSignature(header, body); authBuilder.setSignature(signature); @@ -119,7 +118,7 @@ public Proxy.Packet.Builder addAuthInfo(Context context, Proxy.Metadata header, return packetBuilder; } - public boolean checkAuthentication(Proxy.Packet packet) { + public boolean checkAuthentication(Proxy.Packet packet) throws Exception { if(ifUseAuth) { // check timestamp long currentTimeMillis = System.currentTimeMillis(); From d0d66d5670d80d5a2c18a0fbd8623d45313743c7 Mon Sep 17 00:00:00 2001 From: utu Date: Thu, 21 Nov 2019 21:17:55 +0800 Subject: [PATCH 006/190] 1add authentication to grpc unaryCall. --- .../fate/serving/core/utils/EncryptUtils.java | 25 -------- .../grpc/client/DataTransferPipedClient.java | 44 ++++++++----- .../service/DataTransferPipedServerImpl.java | 10 ++- .../fate/networking/proxy/util/AuthUtils.java | 62 ++++++++++--------- .../networking/proxy/util/EncryptUtils.java | 38 ++++++++++++ router/src/main/resources/auth_config.json | 3 +- router/src/main/resources/proxy.properties | 2 +- 7 files changed, 112 insertions(+), 72 deletions(-) create mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/util/EncryptUtils.java diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/EncryptUtils.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/EncryptUtils.java index 064eac15..d08adb45 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/EncryptUtils.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/EncryptUtils.java @@ -18,11 +18,7 @@ import com.webank.ai.fate.serving.core.bean.EncryptMethod; -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; import java.security.MessageDigest; -import java.util.Base64; public class EncryptUtils { @@ -59,25 +55,4 @@ private static String getEncryptMethodString(EncryptMethod encryptMethod) { return methodString; } - public static byte[] HmacSHA1Encrypt(String encryptText, String encryptKey) throws Exception { - byte[] data = encryptKey.getBytes(UTF8); - SecretKey secretKey = new SecretKeySpec(data, HMACSHA1); - Mac mac = Mac.getInstance(HMACSHA1); - mac.init(secretKey); - - byte[] text = encryptText.getBytes(UTF8); - return mac.doFinal(text); - } - - public static String generateSignature(String applyId, String timestamp, String nonce, - String appKey, String appSecret, String uri, String body) { - try { - String encryptText = applyId + "\n" + timestamp + "\n" + nonce + "\n" + appKey + "\n" + uri + "\n" + body; - encryptText = new String(encryptText.getBytes(), UTF8); - return Base64.getEncoder().encodeToString(HmacSHA1Encrypt(encryptText, appSecret)); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } } diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java index c9a4c828..828fa796 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java @@ -31,6 +31,7 @@ import com.webank.ai.fate.networking.proxy.service.FdnRouter; import com.webank.ai.fate.networking.proxy.util.ErrorUtils; import com.webank.ai.fate.networking.proxy.util.ToStringUtils; +import com.webank.ai.fate.networking.proxy.util.AuthUtils; import com.webank.ai.fate.register.common.Constants; import com.webank.ai.fate.register.router.RouterService; import com.webank.ai.fate.register.url.CollectionUtils; @@ -65,6 +66,8 @@ public class DataTransferPipedClient { private ToStringUtils toStringUtils; @Autowired private ErrorUtils errorUtils; + @Autowired + private AuthUtils authUtils; public static RouterService routerService; @@ -187,27 +190,38 @@ public void pull(Proxy.Metadata metadata, Pipe pipe) { public void unaryCall(Proxy.Packet packet, Pipe pipe) { Preconditions.checkNotNull(packet); - Proxy.Metadata header = packet.getHeader(); - String onelineStringMetadata = toStringUtils.toOneLineString(header); - LOGGER.info("[UNARYCALL][CLIENT] client send unary call to server: {}", onelineStringMetadata); - //LOGGER.info("[UNARYCALL][CLIENT] packet: {}", toStringUtils.toOneLineString(packet)); - - DataTransferServiceGrpc.DataTransferServiceStub stub = getStub( - packet.getHeader().getSrc(), packet.getHeader().getDst(), packet); + Proxy.Metadata header = packet.getHeader(); final CountDownLatch finishLatch = new CountDownLatch(1); StreamObserver responseObserver = grpcStreamObserverFactory - .createClientUnaryCallResponseStreamObserver(pipe, finishLatch, packet.getHeader()); - stub.unaryCall(packet, responseObserver); - - LOGGER.info("[UNARYCALL][CLIENT] unary call stub: {}, metadata: {}", - stub.getChannel(), onelineStringMetadata); + .createClientUnaryCallResponseStreamObserver(pipe, finishLatch, header); try { - finishLatch.await(MAX_AWAIT_HOURS, TimeUnit.HOURS); - } catch (InterruptedException e) { - LOGGER.error("[UNARYCALL][CLIENT] client unary call: finishLatch.await() interrupted"); + String onelineStringMetadata = toStringUtils.toOneLineString(header); + LOGGER.info("[UNARYCALL][CLIENT] client send unary call to server: {}", onelineStringMetadata); + + packet = authUtils.addAuthInfo(packet); + + DataTransferServiceGrpc.DataTransferServiceStub stub = getStub( + packet.getHeader().getSrc(), packet.getHeader().getDst(), packet); + + stub.unaryCall(packet, responseObserver); + + LOGGER.info("[UNARYCALL][CLIENT] unary call stub: {}, metadata: {}", + stub.getChannel(), onelineStringMetadata); + + try { + finishLatch.await(MAX_AWAIT_HOURS, TimeUnit.HOURS); + } catch (InterruptedException e) { + LOGGER.error("[UNARYCALL][CLIENT] client unary call: finishLatch.await() interrupted"); + responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); + pipe.onError(e); + Thread.currentThread().interrupt(); + return; + } + } catch (Exception e) { + LOGGER.error("[UNARYCALL][CLIENT] client unary call: exception: ", e); responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); pipe.onError(e); Thread.currentThread().interrupt(); diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/DataTransferPipedServerImpl.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/DataTransferPipedServerImpl.java index 6ba38955..f682aadf 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/DataTransferPipedServerImpl.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/DataTransferPipedServerImpl.java @@ -190,7 +190,15 @@ public void pull(Proxy.Metadata inputMetadata, StreamObserver resp @RegisterService(serviceName = "unaryCall") public void unaryCall(Proxy.Packet request, StreamObserver responseObserver) { // check authentication - if(false == authUtils.checkAuthentication(request)){ + boolean isAuthPass = false; + try { + isAuthPass = authUtils.checkAuthentication(request); + } catch (Exception e) { + LOGGER.error("checkAuthentication throw an exception: ", e); + responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); + return; + } + if(false == isAuthPass){ String msg = "authentication not pass!"; LOGGER.error(msg); RuntimeException e = new RuntimeException(msg); diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java index 7fd346b3..f2222e4e 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java @@ -16,39 +16,37 @@ package com.webank.ai.fate.networking.proxy.util; -import com.webank.ai.fate.serving.core.bean.*; -import com.webank.ai.fate.serving.core.utils.EncryptUtils; -import com.webank.ai.fate.api.networking.proxy.Proxy; - -import org.springframework.stereotype.Component; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Value; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - import com.google.gson.Gson; import com.google.gson.JsonArray; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.stream.JsonReader; +import com.webank.ai.fate.api.networking.proxy.Proxy; +import com.webank.ai.fate.networking.proxy.util.EncryptUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.util.*; @Component public class AuthUtils implements InitializingBean{ private static final Logger LOGGER = LogManager.getLogger(); - @Value("${auth.key.path}") + @Value("${auth.config.path}") private String keysFilePath; private static Map ACCESS_KEYS_MAP = new HashMap<>(); private static int validRequestTimeoutSecond = 10; private static String applyId = ""; private static boolean ifUseAuth = false; + private static String selfPartyId = ""; @Autowired private ToStringUtils toStringUtils; @@ -61,7 +59,7 @@ public void loadConfig(){ jsonReader = new JsonReader(new FileReader(keysFilePath)); jsonObject = jsonParser.parse(jsonReader).getAsJsonObject(); } catch (FileNotFoundException e) { - logger.error("File not found: {}", keysFilePath); + LOGGER.error("File not found: {}", keysFilePath); throw new RuntimeException(e); } finally { if (jsonReader != null) { @@ -71,12 +69,13 @@ public void loadConfig(){ } } } - + selfPartyId = jsonObject.get("self_party_id").getAsString(); ifUseAuth = jsonObject.get("if_use_auth").getAsBoolean(); validRequestTimeoutSecond = jsonObject.get("request_expire_seconds").getAsInt(); applyId = jsonObject.get("apply_id").getAsString(); JsonArray jsonArray = jsonObject.getAsJsonArray("access_keys"); + Gson gson = new Gson(); List allowKeys = gson.fromJson(jsonArray, ArrayList.class); ACCESS_KEYS_MAP.clear(); for (Map allowKey : allowKeys) { @@ -88,11 +87,11 @@ private String getSecret(String appKey) { return ACCESS_KEYS_MAP.get(appKey); } - private String calSignature(Proxy.Metadata header, Proxy.Data body) throws Exception { + private String calSignature(Proxy.Metadata header, Proxy.Data body, long timestamp) throws Exception { String signature = ""; String appSecret = getSecret(header.getDst().getPartyId()); if (StringUtils.isEmpty(appSecret)) { - logger.error("appSecret not found"); + LOGGER.error("appSecret not found"); return signature; } String encryptText = String.valueOf(timestamp) + "\n" @@ -103,35 +102,40 @@ private String calSignature(Proxy.Metadata header, Proxy.Data body) throws Excep return signature; } - public Proxy.Packet.Builder addAuthInfo(Proxy.Metadata header, Proxy.Data body, Proxy.Packet.Builder packetBuilder) throws Exception { + public Proxy.Packet addAuthInfo(Proxy.Packet packet) throws Exception { + + Proxy.Packet.Builder packetBuilder = packet.toBuilder(); + Proxy.AuthInfo.Builder authBuilder = packetBuilder.getAuthBuilder(); - Proxy.AuthInfo.Builder authBuilder = Proxy.AuthInfo.newBuilder(); long timestamp = System.currentTimeMillis(); + authBuilder.setTimestamp(timestamp); authBuilder.setApplyId(applyId); - if(ifUseAuth){ - String signature = calSignature(header, body); + + if(ifUseAuth + && !StringUtils.equals(selfPartyId, packet.getHeader().getDst().getPartyId())) { + String signature = calSignature(packet.getHeader(), packet.getBody(), timestamp); authBuilder.setSignature(signature); } packetBuilder.setAuth(authBuilder.build()); - - return packetBuilder; + return packetBuilder.build(); } public boolean checkAuthentication(Proxy.Packet packet) throws Exception { - if(ifUseAuth) { + if(ifUseAuth + && StringUtils.equals(selfPartyId, packet.getHeader().getDst().getPartyId())) { // check timestamp long currentTimeMillis = System.currentTimeMillis(); long requestTimeMillis = packet.getAuth().getTimestamp(); if (currentTimeMillis >= (requestTimeMillis + validRequestTimeoutSecond * 1000)) { - logger.error("receive an expired request, currentTimeMillis:{}, requestTimeMillis{}.", currentTimeMillis, requestTimeMillis); + LOGGER.error("receive an expired request, currentTimeMillis:{}, requestTimeMillis{}.", currentTimeMillis, requestTimeMillis); return false; } // check signature String reqSignature = packet.getAuth().getSignature(); - String validSignature = calSignature(packet.getHeader(), packet.getBody()); - if (!StringUtils.equals(signature, validSignature)) { - logger.error("invalid signature, request:{}, valid:{}", reqSignature, validSignature); + String validSignature = calSignature(packet.getHeader(), packet.getBody(), requestTimeMillis); + if (!StringUtils.equals(reqSignature, validSignature)) { + LOGGER.error("invalid signature, request:{}, valid:{}", reqSignature, validSignature); return false; } } diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/EncryptUtils.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/EncryptUtils.java new file mode 100644 index 00000000..c903a039 --- /dev/null +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/EncryptUtils.java @@ -0,0 +1,38 @@ +/* + * Copyright 2019 The FATE Authors. All Rights Reserved. + * + * 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.webank.ai.fate.networking.proxy.util; + +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +public class EncryptUtils { + + public static final String UTF8 = "UTF-8"; + private static final String HMACSHA1 = "HmacSHA1"; + + public static byte[] HmacSHA1Encrypt(String encryptText, String encryptKey) throws Exception { + byte[] data = encryptKey.getBytes(UTF8); + SecretKey secretKey = new SecretKeySpec(data, HMACSHA1); + Mac mac = Mac.getInstance(HMACSHA1); + mac.init(secretKey); + + byte[] text = encryptText.getBytes(UTF8); + return mac.doFinal(text); + } + +} diff --git a/router/src/main/resources/auth_config.json b/router/src/main/resources/auth_config.json index 0f412707..4745b315 100644 --- a/router/src/main/resources/auth_config.json +++ b/router/src/main/resources/auth_config.json @@ -1,6 +1,7 @@ { + "self_party_id": "9999", "if_use_auth": false, - "request_expire_seconds": 20, + "request_expire_seconds": 8, "apply_id": "20191119163236256", "access_keys": [{ "appKey": "111", diff --git a/router/src/main/resources/proxy.properties b/router/src/main/resources/proxy.properties index 2e36a4d3..c568d065 100644 --- a/router/src/main/resources/proxy.properties +++ b/router/src/main/resources/proxy.properties @@ -25,4 +25,4 @@ route.table=/networking/proxy/src/main/resources/route_tables/route_table.json root.crt= useJMX=false #jmx.server.name=JMXServer -auth.key.path=/networking/proxy/src/main/resources/auth_config.json +auth.config.path=/networking/proxy/src/main/resources/auth_config.json From 8f1c57648f39ccce527017df593548da6f3f93e3 Mon Sep 17 00:00:00 2001 From: utu Date: Fri, 22 Nov 2019 17:06:42 +0800 Subject: [PATCH 007/190] 1add authentication to grpc unaryCall. --- .../networking/proxy/factory/GrpcServerFactory.java | 7 +++++++ .../ai/fate/networking/proxy/model/ServerConf.java | 10 ++++++++++ .../ai/fate/networking/proxy/util/AuthUtils.java | 9 +++++---- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcServerFactory.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcServerFactory.java index 4700b41a..8db3c3a9 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcServerFactory.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcServerFactory.java @@ -277,6 +277,13 @@ public Server createServer(String confPath) throws IOException { } else { serverConf.setDebugEnabled(false); } + + String authConfigPath = properties.getProperty("auth.config.path", null); + if (authConfigPath == null) { + throw new IllegalArgumentException("auth config cannot be null"); + } else { + serverConf.setauthConfigPath(authConfigPath); + } } catch (Exception e) { LOGGER.error(e); throw e; diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/model/ServerConf.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/model/ServerConf.java index dac3535b..42d9af75 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/model/ServerConf.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/model/ServerConf.java @@ -41,6 +41,7 @@ public class ServerConf { private boolean isAuditEnabled; private boolean isNeighbourInsecureChannelEnabled; private boolean isDebugEnabled; + private String authConfigPath; public Properties getProperties() { return properties; @@ -76,6 +77,7 @@ public String toString() { ", isAuditEnabled=" + isAuditEnabled + ", isNeighbourInsecureChannelEnabled=" + isNeighbourInsecureChannelEnabled + ", isDebugEnabled=" + isDebugEnabled + + ", authConfigPath=" + authConfigPath + '}'; } @@ -198,4 +200,12 @@ public boolean isDebugEnabled() { public void setDebugEnabled(boolean debugEnabled) { isDebugEnabled = debugEnabled; } + + public String getauthConfigPath() { + return authConfigPath; + } + + public void setauthConfigPath(String authConfigPath) { + this.authConfigPath = authConfigPath; + } } diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java index f2222e4e..56a2168e 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java @@ -22,6 +22,7 @@ import com.google.gson.JsonParser; import com.google.gson.stream.JsonReader; import com.webank.ai.fate.api.networking.proxy.Proxy; +import com.webank.ai.fate.networking.proxy.model.ServerConf; import com.webank.ai.fate.networking.proxy.util.EncryptUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; @@ -40,8 +41,8 @@ @Component public class AuthUtils implements InitializingBean{ private static final Logger LOGGER = LogManager.getLogger(); - @Value("${auth.config.path}") - private String keysFilePath; + @Autowired + private ServerConf serverConf; private static Map ACCESS_KEYS_MAP = new HashMap<>(); private static int validRequestTimeoutSecond = 10; private static String applyId = ""; @@ -56,10 +57,10 @@ public void loadConfig(){ JsonReader jsonReader = null; JsonObject jsonObject = null; try { - jsonReader = new JsonReader(new FileReader(keysFilePath)); + jsonReader = new JsonReader(new FileReader(serverConf.getauthConfigPath())); jsonObject = jsonParser.parse(jsonReader).getAsJsonObject(); } catch (FileNotFoundException e) { - LOGGER.error("File not found: {}", keysFilePath); + LOGGER.error("File not found: {}", serverConf.getauthConfigPath()); throw new RuntimeException(e); } finally { if (jsonReader != null) { From a6c1e5e872c95832f51fe07d57cdbaeeedd3ff74 Mon Sep 17 00:00:00 2001 From: utu Date: Mon, 25 Nov 2019 17:38:59 +0800 Subject: [PATCH 008/190] 1add authentication to grpc unaryCall. --- .../networking/proxy/factory/GrpcServerFactory.java | 7 ------- .../ai/fate/networking/proxy/model/ServerConf.java | 9 --------- .../ai/fate/networking/proxy/util/AuthUtils.java | 12 +++++------- 3 files changed, 5 insertions(+), 23 deletions(-) diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcServerFactory.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcServerFactory.java index 8db3c3a9..4700b41a 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcServerFactory.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcServerFactory.java @@ -277,13 +277,6 @@ public Server createServer(String confPath) throws IOException { } else { serverConf.setDebugEnabled(false); } - - String authConfigPath = properties.getProperty("auth.config.path", null); - if (authConfigPath == null) { - throw new IllegalArgumentException("auth config cannot be null"); - } else { - serverConf.setauthConfigPath(authConfigPath); - } } catch (Exception e) { LOGGER.error(e); throw e; diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/model/ServerConf.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/model/ServerConf.java index 42d9af75..60c25f73 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/model/ServerConf.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/model/ServerConf.java @@ -41,7 +41,6 @@ public class ServerConf { private boolean isAuditEnabled; private boolean isNeighbourInsecureChannelEnabled; private boolean isDebugEnabled; - private String authConfigPath; public Properties getProperties() { return properties; @@ -77,7 +76,6 @@ public String toString() { ", isAuditEnabled=" + isAuditEnabled + ", isNeighbourInsecureChannelEnabled=" + isNeighbourInsecureChannelEnabled + ", isDebugEnabled=" + isDebugEnabled + - ", authConfigPath=" + authConfigPath + '}'; } @@ -201,11 +199,4 @@ public void setDebugEnabled(boolean debugEnabled) { isDebugEnabled = debugEnabled; } - public String getauthConfigPath() { - return authConfigPath; - } - - public void setauthConfigPath(String authConfigPath) { - this.authConfigPath = authConfigPath; - } } diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java index 56a2168e..48096417 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java @@ -22,17 +22,15 @@ import com.google.gson.JsonParser; import com.google.gson.stream.JsonReader; import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.networking.proxy.model.ServerConf; -import com.webank.ai.fate.networking.proxy.util.EncryptUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; +import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; @@ -41,8 +39,7 @@ @Component public class AuthUtils implements InitializingBean{ private static final Logger LOGGER = LogManager.getLogger(); - @Autowired - private ServerConf serverConf; + private static final String confFilePath = System.getProperty("user.dir") + File.separator + "conf" + File.separator + "auth_config.json"; private static Map ACCESS_KEYS_MAP = new HashMap<>(); private static int validRequestTimeoutSecond = 10; private static String applyId = ""; @@ -57,10 +54,10 @@ public void loadConfig(){ JsonReader jsonReader = null; JsonObject jsonObject = null; try { - jsonReader = new JsonReader(new FileReader(serverConf.getauthConfigPath())); + jsonReader = new JsonReader(new FileReader(confFilePath)); jsonObject = jsonParser.parse(jsonReader).getAsJsonObject(); } catch (FileNotFoundException e) { - LOGGER.error("File not found: {}", serverConf.getauthConfigPath()); + LOGGER.error("File not found: {}", confFilePath); throw new RuntimeException(e); } finally { if (jsonReader != null) { @@ -82,6 +79,7 @@ public void loadConfig(){ for (Map allowKey : allowKeys) { ACCESS_KEYS_MAP.put(allowKey.get("appKey").toString(), allowKey.get("appSecret").toString()); } + LOGGER.debug("refreshed auth cfg using file {}.", confFilePath); } private String getSecret(String appKey) { From fe998e7605b811c79e087e1496194d36880989e7 Mon Sep 17 00:00:00 2001 From: utu Date: Tue, 26 Nov 2019 15:23:11 +0800 Subject: [PATCH 009/190] add authentication to grpc unaryCall. --- .../com/webank/ai/fate/networking/proxy/util/AuthUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java index 48096417..56183424 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java @@ -88,7 +88,7 @@ private String getSecret(String appKey) { private String calSignature(Proxy.Metadata header, Proxy.Data body, long timestamp) throws Exception { String signature = ""; - String appSecret = getSecret(header.getDst().getPartyId()); + String appSecret = getSecret(header.getSrc().getPartyId()); if (StringUtils.isEmpty(appSecret)) { LOGGER.error("appSecret not found"); return signature; From 5232a5cce309f7ab83affdebfdaa47c72d2fd170 Mon Sep 17 00:00:00 2001 From: kaideng Date: Tue, 26 Nov 2019 16:26:03 +0800 Subject: [PATCH 010/190] change register info Signed-off-by: kaideng --- .../fate/serving/core/bean/BaseContext.java | 4 + .../ai/fate/serving/core/bean/Dict.java | 1 + proto/model_service.proto | 1 + router/pom.xml | 5 + .../grpc/client/DataTransferPipedClient.java | 22 +- .../webank/ai/fate/serving/ServingServer.java | 2 - .../fate/serving/bean/InferenceRequest.java | 10 + .../guest/DefaultGuestInferenceProvider.java | 11 +- .../fate/serving/interfaces/ModelManager.java | 9 +- .../serving/manger/DefaultModelManager.java | 308 ++---------------- .../serving/service/InferenceService.java | 5 - .../ai/fate/serving/service/ModelService.java | 14 +- 12 files changed, 93 insertions(+), 299 deletions(-) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java index efa41912..705084a4 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java @@ -33,6 +33,10 @@ public class BaseContext implements Context role = 2; map model = 3; + string serviceId = 4; } message PublishResponse{ diff --git a/router/pom.xml b/router/pom.xml index 22ecea73..807cbf26 100644 --- a/router/pom.xml +++ b/router/pom.xml @@ -62,6 +62,11 @@ fate-jmx ${fate.version} + + com.webank.ai.fate + fate-serving-core + 1.1 + diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java index 828fa796..d2eb8e21 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java @@ -16,6 +16,7 @@ package com.webank.ai.fate.networking.proxy.grpc.client; +import com.alibaba.fastjson.JSON; import com.google.common.base.Preconditions; import com.webank.ai.fate.api.core.BasicMeta; import com.webank.ai.fate.api.networking.proxy.DataTransferServiceGrpc; @@ -36,6 +37,10 @@ import com.webank.ai.fate.register.router.RouterService; import com.webank.ai.fate.register.url.CollectionUtils; import com.webank.ai.fate.register.url.URL; +import com.webank.ai.fate.serving.core.bean.EncryptMethod; +import com.webank.ai.fate.serving.core.bean.HostFederatedParams; +import com.webank.ai.fate.serving.core.bean.ModelInfo; +import com.webank.ai.fate.serving.core.utils.EncryptUtils; import io.grpc.stub.StreamObserver; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; @@ -45,6 +50,7 @@ import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; @@ -306,6 +312,12 @@ private DataTransferServiceGrpc.DataTransferServiceStub getStub(Proxy.Topic from return stub; } + private static final String modelKeySeparator = "&"; + + public static String genModelKey(String name, String namespace) { + return StringUtils.join(Arrays.asList(name, namespace), modelKeySeparator); + } + private DataTransferServiceGrpc.DataTransferServiceStub routerByServiceRegister(Proxy.Topic from, Proxy.Topic to, Proxy.Packet pack) { DataTransferServiceGrpc.DataTransferServiceStub stub = null; @@ -314,8 +326,14 @@ private DataTransferServiceGrpc.DataTransferServiceStub routerByServiceRegister( String name = to.getName(); String serviceName = pack.getHeader().getCommand().getName(); String version = pack.getHeader().getOperator(); - - URL paramUrl = URL.valueOf("serving/" + partId + "/unaryCall"); + String data = pack.getBody().getValue().toStringUtf8(); + HostFederatedParams requestData = JSON.parseObject(data, HostFederatedParams.class); + ModelInfo partnerModelInfo = requestData.getPartnerModelInfo(); + // (partnerModelInfo.getName(), partnerModelInfo.getNamespace() + String key =genModelKey(partnerModelInfo.getName(), partnerModelInfo.getNamespace()); + String md5Key = EncryptUtils.encrypt(key, EncryptMethod.MD5); + + URL paramUrl = URL.valueOf("serving/" + md5Key + "/unaryCall"); if(StringUtils.isNotEmpty(version)) { paramUrl= paramUrl.addParameter(Constants.VERSION_KEY,version ); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index c2533899..fc65414c 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -66,7 +66,6 @@ public ServingServer(String confPath) { System.setProperty("configpath", confPath); new Configuration(confPath).load(); - new com.webank.ai.eggroll.core.utils.Configuration(confPath).load(); } @@ -114,7 +113,6 @@ private void start(String[] args) throws IOException { LOGGER.info("Server started listening on port: {}, use configuration: {}", port, this.confPath); server.start(); - String userRegisterString = Configuration.getProperty(Dict.USE_REGISTER); useRegister = Boolean.valueOf(userRegisterString); LOGGER.info("serving useRegister {}", useRegister); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java b/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java index 60c0b7c2..025a9d58 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java @@ -31,6 +31,16 @@ public class InferenceRequest implements Request { private String modelId; private String seqno; private String caseid; + + public String getServiceId() { + return serviceId; + } + + public void setServiceId(String serviceId) { + this.serviceId = serviceId; + } + + private String serviceId; private Map featureData; InferenceRequest() { diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java index c387ffdf..a0b839da 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java @@ -72,8 +72,15 @@ public ReturnResult runInference(Context context, InferenceRequest inferenceRequ inferenceResult.setCaseid(inferenceRequest.getCaseid()); String modelName = inferenceRequest.getModelVersion(); String modelNamespace = inferenceRequest.getModelId(); - if (StringUtils.isEmpty(modelNamespace) && inferenceRequest.haveAppId()) { - modelNamespace = modelManager.getModelNamespaceByPartyId(inferenceRequest.getAppid()); + String serviceId = inferenceRequest.getServiceId(); + + + if (StringUtils.isEmpty(modelNamespace) ) { + if(StringUtils.isNotEmpty(inferenceRequest.getServiceId())){ + modelNamespace = modelManager.getModelNamespaceByPartyId(inferenceRequest.getServiceId()); + }else if(inferenceRequest.haveAppId()) { + modelNamespace = modelManager.getModelNamespaceByPartyId(inferenceRequest.getAppid()); + } } if (StringUtils.isEmpty(modelNamespace)) { inferenceResult.setRetcode(InferenceRetCode.LOAD_MODEL_FAILED + 1000); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/interfaces/ModelManager.java b/serving-server/src/main/java/com/webank/ai/fate/serving/interfaces/ModelManager.java index 7f9c9457..a58f0dfc 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/interfaces/ModelManager.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/interfaces/ModelManager.java @@ -18,19 +18,16 @@ import com.webank.ai.fate.serving.bean.ModelNamespaceData; -import com.webank.ai.fate.serving.core.bean.FederatedParty; -import com.webank.ai.fate.serving.core.bean.FederatedRoles; -import com.webank.ai.fate.serving.core.bean.ModelInfo; -import com.webank.ai.fate.serving.core.bean.ReturnResult; +import com.webank.ai.fate.serving.core.bean.*; import com.webank.ai.fate.serving.federatedml.PipelineTask; import java.util.Map; public interface ModelManager { - public ReturnResult publishLoadModel(FederatedParty federatedParty, FederatedRoles federatedRoles, Map> federatedRolesModel); + public ReturnResult publishLoadModel(Context context,FederatedParty federatedParty, FederatedRoles federatedRoles, Map> federatedRolesModel); - public ReturnResult publishOnlineModel(FederatedParty federatedParty, FederatedRoles federatedRoles, Map> federatedRolesModel); + public ReturnResult publishOnlineModel(Context context,FederatedParty federatedParty, FederatedRoles federatedRoles, Map> federatedRolesModel); public PipelineTask getModel(String name, String namespace); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java index 5c908044..97faed47 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java @@ -24,6 +24,7 @@ import com.webank.ai.fate.serving.bean.ModelNamespaceData; import com.webank.ai.fate.serving.core.bean.*; import com.webank.ai.fate.serving.core.constant.InferenceRetCode; +import com.webank.ai.fate.serving.core.utils.EncryptUtils; import com.webank.ai.fate.serving.federatedml.PipelineTask; import com.webank.ai.fate.serving.interfaces.ModelCache; import com.webank.ai.fate.serving.interfaces.ModelManager; @@ -33,6 +34,7 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import sun.security.provider.MD5; import java.io.File; import java.util.HashMap; @@ -57,6 +59,7 @@ public class DefaultModelManager implements ModelManager, InitializingBean { private ConcurrentHashMap partnerModelData; private File modelFile; + public DefaultModelManager() { appNamespaceMap = new HashMap<>(); @@ -90,9 +93,14 @@ public static void main(String[] args) { } @Override - public ReturnResult publishLoadModel(FederatedParty federatedParty, FederatedRoles federatedRoles, Map> federatedRolesModel) { + public ReturnResult publishLoadModel(Context context,FederatedParty federatedParty, FederatedRoles federatedRoles, Map> federatedRolesModel) { String role = federatedParty.getRole(); String partyId = federatedParty.getPartyId(); + String serviceId = null; + if(context.getData(Dict.SERVICE_ID)!=null) { + serviceId = context.getData(Dict.SERVICE_ID).toString(); + } + ReturnResult returnResult = new ReturnResult(); returnResult.setRetcode(InferenceRetCode.OK); try { @@ -123,7 +131,12 @@ public ReturnResult publishLoadModel(FederatedParty federatedParty, FederatedRol if(Dict.HOST.equals(role)){ if (zookeeperRegistry != null) { - zookeeperRegistry.addDynamicEnvironment(partyId); + if(serviceId!=null){ + zookeeperRegistry.addDynamicEnvironment(serviceId); + } + String key = ModelUtils.genModelKey(modelInfo.getName(), modelInfo.getNamespace()); + String keyMd5 = EncryptUtils.encrypt(key,EncryptMethod.MD5); + zookeeperRegistry.addDynamicEnvironment(keyMd5); zookeeperRegistry.register(FateServer.serviceSets); } @@ -139,9 +152,13 @@ public ReturnResult publishLoadModel(FederatedParty federatedParty, FederatedRol } @Override - public ReturnResult publishOnlineModel(FederatedParty federatedParty, FederatedRoles federatedRoles, Map> federatedRolesModel) { + public ReturnResult publishOnlineModel(Context context,FederatedParty federatedParty, FederatedRoles federatedRoles, Map> federatedRolesModel) { String role = federatedParty.getRole(); String partyId = federatedParty.getPartyId(); + String serviceId = null; + if(context.getData(Dict.SERVICE_ID)!=null) { + serviceId = context.getData(Dict.SERVICE_ID).toString(); + } ReturnResult returnResult = new ReturnResult(); ModelInfo modelInfo = federatedRolesModel.get(role).get(partyId); if (modelInfo == null) { @@ -167,10 +184,18 @@ public ReturnResult publishOnlineModel(FederatedParty federatedParty, FederatedR String modelName = modelInfo.getName(); modelNamespaceDataMapPool.put(modelNamespace, new ModelNamespaceData(modelNamespace, federatedParty, federatedRoles, modelName, model)); appNamespaceMapPool.put(partyId, modelNamespace); + if(serviceId!=null){ + appNamespaceMapPool.put(serviceId, modelNamespace); + } + logger.info("Enable model {} for namespace {} success", modelName, modelNamespace); logger.info("Get model namespace {} for app {}", modelNamespace, partyId); returnResult.setRetcode(InferenceRetCode.OK); if (zookeeperRegistry != null) { + if(serviceId!=null){ + zookeeperRegistry.addDynamicEnvironment(serviceId); + } + zookeeperRegistry.addDynamicEnvironment(partyId); zookeeperRegistry.register(FateServer.serviceSets); } @@ -204,259 +229,7 @@ public ModelInfo getModelInfoByPartner(String partnerModelName, String partnerMo } -// -// @Override -// public void store(){ -// -// Map storeData = Maps.newHashMap(); -// Set keys =modelCache.getKeys(); -// -// if(keys.size()>0){ -// -// storeData.put(Dict.MODEL_KEYS,keys); -// -// // storeData.put(Dict.MODEL_NANESPACE_DATA,modelNamespaceDataMapPool.getDataMap()); -// -// -//// modelFederatedParty.put(modelKey,federatedParty); -//// -//// modelFederatedRoles.put(modelKey,federatedRoles); -// storeData.put(Dict.MODEL_FEDERATED_ROLES,this.modelFederatedRoles); -// -// storeData.put(Dict.MODEL_FEDERATED_PARTY,this.modelFederatedParty); -// -// storeData.put(Dict.APPID_NANESPACE_DATA,appNamespaceMapPool.getDataMap()); -// -// storeData.put(Dict.PARTNER_MODEL_DATA,partnerModelData); -// -// String content = JSON.toJSONString(storeData); -// -// long version = lastCacheChanged.incrementAndGet(); -// -// if (version < lastCacheChanged.get()) { -// return; -// } -// if (modelFile == null) { -// return; -// } -// // Save -// try { -// File lockfile = new File(modelFile.getAbsolutePath() + ".lock"); -// if (!lockfile.exists()) { -// lockfile.createNewFile(); -// } -// try (RandomAccessFile raf = new RandomAccessFile(lockfile, "rw"); -// FileChannel channel = raf.getChannel()) { -// FileLock lock = channel.tryLock(); -// if (lock == null) { -// throw new IOException("Can not lock the cache file " + modelFile.getAbsolutePath() ); -// } -// try { -// if (!modelFile.exists()) { -// modelFile.createNewFile(); -// } -// try (FileOutputStream outputFile = new FileOutputStream(modelFile)) { -// if(StringUtils.isNotEmpty(content)) { -// outputFile.write(content.getBytes()); -// } -// -// } -// } finally { -// lock.release(); -// } -// } -// } catch (Throwable e) { -// -// logger.error("Failed to save modelFile cache file, cause: " + e.getMessage(), e); -// } -// -// -// -// } -// -// } -// -// @Override -// public void restore() { -// -// try { -// File lockfile = new File(modelFile.getAbsolutePath() + ".lock"); -// if (!lockfile.exists()) { -// lockfile.createNewFile(); -// } -// try (RandomAccessFile raf = new RandomAccessFile(lockfile, "rw"); -// FileChannel channel = raf.getChannel()) { -// FileLock lock = channel.tryLock(); -// if (lock == null) { -// throw new IOException("Can not lock the cache file " + modelFile.getAbsolutePath() ); -// } -// long length = modelFile.length(); -// if(length==0){ -// return ; -// } -// -// byte[] content = new byte[(int)length]; -// -// try { -// -// try (FileInputStream inputputFile = new FileInputStream(modelFile)) { -// -// inputputFile.read(content); -// -// } -// Map contentMap = JSON.parseObject(content,Map.class); -// -// Map appNamespaceData = (Map)contentMap.get(Dict.APPID_NANESPACE_DATA); -// -// List modelKeys = (List)contentMap.get(Dict.MODEL_KEYS); -// -// -// -// if(CollectionUtils.isEmpty(modelKeys)){ -// -// return ; -// -// } -// -// for(String modelKey:modelKeys){ -// -// String[] modelKeyElements = ModelUtils.splitModelKey(modelKey); -// -// String name = modelKeyElements[0]; -// -// String namespace = modelKeyElements[1]; -// -// logger.info("restore model name {} namespace {}",name,namespace); -// -// pushModelIntoPool(name,namespace); -// -// } -// -// -// this.appNamespaceMapPool.putAll(appNamespaceData); -// -// Map partModelData = (Map) contentMap.get(Dict.PARTNER_MODEL_DATA); -// -// if(partModelData!=null){ -// -// partModelData.forEach((k,v)->{ -// -// try{ -// partnerModelData.put(k.toString(),parseModelInfo((Map)v)); -// -// } -// catch(Throwable e){ -// logger.error("set partnerModelData error" ,e); -// } -// -// }); -// -// } -// -// -// -// Map modelParty= (Map)contentMap.get(Dict.MODEL_FEDERATED_PARTY); -// -// if(modelParty!=null){ -// -// modelParty.forEach((k,v)->{ -// -// try{ -// modelFederatedParty.put(k.toString(),parseFederatedParty((Map)v)); -// -// } -// catch(Throwable e){ -// logger.error("set partnerModelData error" ,e); -// } -// -// }); -// -// } -// -// -// Map modelRoles= (Map)contentMap.get(Dict.MODEL_FEDERATED_ROLES); -// -// if(modelRoles!=null){ -// -// modelRoles.forEach((k,v)->{ -// -// try{ -// modelFederatedRoles.put(k.toString(),parseFederatedRoles((Map)v)); -// -// } -// catch(Throwable e){ -// logger.error("set partnerModelData error" ,e); -// } -// -// }); -// -// } -// -// -// -// // modelNamespaceDataMapPool.put(modelNamespace, new ModelNamespaceData(modelNamespace, federatedParty, federatedRoles, modelName, model)); -// -// -// if(partnerModelData.size()>0){ -// -// partnerModelData.forEach((k,v)->{ -// -// String namespace =v.getNamespace(); -// -// FederatedParty federatedParty = modelFederatedParty.get(k); -// -// FederatedRoles federatedRoles = modelFederatedRoles.get(k); -// -// PipelineTask model = modelCache.get(k); -// if(federatedParty==null||federatedRoles==null||model==null){ -// return; -// } -// -// ModelNamespaceData modelNamespaceData = new ModelNamespaceData(v.getNamespace(),federatedParty,federatedRoles,v.getName(),model); -// -// modelNamespaceDataMapPool.put(namespace,modelNamespaceData); -// -// }); -// -// } -// -// -// -// -// -// -// -//// Map modeNamespaceDataMap = (Map) contentMap.get(Dict.MODEL_NANESPACE_DATA); -//// -//// if(modeNamespaceDataMap!=null){ -//// -//// -//// modeNamespaceDataMap.forEach((k,v)->{ -//// -//// try { -//// modelNamespaceDataMapPool.put(k.toString(), parseModelNamespaceData((Map) v)); -//// }catch (Throwable e){ -//// -//// logger.error("set modelNamespaceDataMapPool error" ,e); -//// } -//// -//// -//// }); -//// -//// } -// -// -// -// } finally { -// lock.release(); -// } -// } -// } catch (Throwable e) { -// -// logger.error("Failed to save modelFile cache file, cause: " + e.getMessage(), e); -// } -// -// } + @Override public PipelineTask pushModelIntoPool(String name, String namespace) { @@ -487,28 +260,7 @@ private ModelInfo parseModelInfo(Map data) { return null; } -// private void test(){ -// -// storeData.put(Dict.MODEL_KEYS,keys); -// -// // storeData.put(Dict.MODEL_NANESPACE_DATA,modelNamespaceDataMapPool.getDataMap()); -// -// -//// modelFederatedParty.put(modelKey,federatedParty); -//// -//// modelFederatedRoles.put(modelKey,federatedRoles); -// storeData.put(Dict.MODEL_FEDERATED_ROLES,this.modelFederatedRoles); -// -// storeData.put(Dict.MODEL_FEDERATED_PARTY,this.modelFederatedParty); -// -// storeData.put(Dict.APPID_NANESPACE_DATA,appNamespaceMapPool.getDataMap()); -// -// storeData.put(Dict.PARTNER_MODEL_DATA,partnerModelData); -// -// -// -// -// } + private ModelNamespaceData parseModelNamespaceData(Map data) { diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java index 4ac03691..91eb0aaa 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java @@ -64,7 +64,6 @@ private void inferenceServiceAction(InferenceMessage req, StreamObserver Date: Tue, 26 Nov 2019 17:14:45 +0800 Subject: [PATCH 011/190] add authentication to grpc unaryCall. --- router/bin/service.sh | 2 +- .../com/webank/ai/fate/networking/proxy/util/AuthUtils.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/router/bin/service.sh b/router/bin/service.sh index f9eb181a..6c15c540 100644 --- a/router/bin/service.sh +++ b/router/bin/service.sh @@ -56,7 +56,7 @@ start() { getpid if [[ $? -eq 0 ]]; then mklogsdir - java -Drouter_file=${configpath}/route_table.json -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/proxy.properties >> logs/console.log 2>>logs/error.log & + java -DauthFile=${configpath}/auth_config.json -Drouter_file=${configpath}/route_table.json -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/proxy.properties >> logs/console.log 2>>logs/error.log & if [[ $? -eq 0 ]]; then getpid echo "service start sucessfully. pid: ${pid}" diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java index 56183424..4b00d55b 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java @@ -39,7 +39,7 @@ @Component public class AuthUtils implements InitializingBean{ private static final Logger LOGGER = LogManager.getLogger(); - private static final String confFilePath = System.getProperty("user.dir") + File.separator + "conf" + File.separator + "auth_config.json"; + private static final String confFilePath = System.getProperty("authFile"); private static Map ACCESS_KEYS_MAP = new HashMap<>(); private static int validRequestTimeoutSecond = 10; private static String applyId = ""; From 4649dbd730a014434241a230fedb48392aa9aced Mon Sep 17 00:00:00 2001 From: kaideng Date: Wed, 27 Nov 2019 17:12:20 +0800 Subject: [PATCH 012/190] change some feature Signed-off-by: kaideng --- .../register/router/DefaultRouterService.java | 16 +++++----------- .../ai/fate/register/task/AbstractRetryTask.java | 2 +- .../ai/fate/register/url/CollectionUtils.java | 2 +- .../com/webank/ai/fate/register/url/URL.java | 4 ++-- .../ai/fate/serving/bean/InferenceRequest.java | 11 +++++++++++ .../guest/DefaultGuestInferenceProvider.java | 5 ++++- .../ai/fate/serving/service/ModelService.java | 1 - 7 files changed, 24 insertions(+), 17 deletions(-) diff --git a/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java b/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java index cd5be491..cf206880 100644 --- a/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java +++ b/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java @@ -20,7 +20,7 @@ import com.webank.ai.fate.register.loadbalance.LoadBalanceModel; import com.webank.ai.fate.register.url.CollectionUtils; import com.webank.ai.fate.register.url.URL; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.List; @@ -32,22 +32,16 @@ public List doRouter(URL url, LoadBalanceModel loadBalanceModel) { List urls = registry.getCacheUrls(url); + if (CollectionUtils.isEmpty(urls)) { + return null; + } + urls = filterEmpty(urls); String version = url.getParameter(Constants.VERSION_KEY); if (CollectionUtils.isNotEmpty(urls) && StringUtils.isNotBlank(version)) { urls = filterVersion(urls, version); } -// else{ -// AtomicReference> resultUrls = new AtomicReference<>(); -// registry.subscribe(url , resultUrls::set); -// urls =resultUrls.get(); -// urls= filterVersion(urls,version); -// } - - if (CollectionUtils.isEmpty(urls)) { - return null; - } List resultUrls = this.loadBalancer.select(urls); diff --git a/register/src/main/java/com/webank/ai/fate/register/task/AbstractRetryTask.java b/register/src/main/java/com/webank/ai/fate/register/task/AbstractRetryTask.java index 16eaa99b..024024e0 100644 --- a/register/src/main/java/com/webank/ai/fate/register/task/AbstractRetryTask.java +++ b/register/src/main/java/com/webank/ai/fate/register/task/AbstractRetryTask.java @@ -23,7 +23,7 @@ import com.webank.ai.fate.register.interfaces.Timeout; import com.webank.ai.fate.register.interfaces.Timer; import com.webank.ai.fate.register.url.URL; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/register/src/main/java/com/webank/ai/fate/register/url/CollectionUtils.java b/register/src/main/java/com/webank/ai/fate/register/url/CollectionUtils.java index 4d945500..5a7cb93e 100644 --- a/register/src/main/java/com/webank/ai/fate/register/url/CollectionUtils.java +++ b/register/src/main/java/com/webank/ai/fate/register/url/CollectionUtils.java @@ -16,7 +16,7 @@ package com.webank.ai.fate.register.url; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import java.util.*; diff --git a/register/src/main/java/com/webank/ai/fate/register/url/URL.java b/register/src/main/java/com/webank/ai/fate/register/url/URL.java index fe7fc39b..b73673b7 100644 --- a/register/src/main/java/com/webank/ai/fate/register/url/URL.java +++ b/register/src/main/java/com/webank/ai/fate/register/url/URL.java @@ -17,8 +17,8 @@ package com.webank.ai.fate.register.url; -import org.apache.commons.lang.ArrayUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; import java.io.Serializable; import java.io.UnsupportedEncodingException; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java b/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java index 025a9d58..8a85ffdc 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java @@ -43,10 +43,21 @@ public void setServiceId(String serviceId) { private String serviceId; private Map featureData; + public Map getSendToRemoteFeatureData() { + return sendToRemoteFeatureData; + } + + public void setSendToRemoteFeatureData(Map sendToRemoteFeatureData) { + this.sendToRemoteFeatureData = sendToRemoteFeatureData; + } + + private Map sendToRemoteFeatureData; + InferenceRequest() { seqno = InferenceUtils.generateSeqno(); caseid = InferenceUtils.generateCaseid(); featureData = new HashMap<>(); + sendToRemoteFeatureData = new HashMap<>(); } public void setCaseId(String caseId) { diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java index a0b839da..e084bdcd 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java @@ -130,12 +130,15 @@ public ReturnResult runInference(Context context, InferenceRequest inferenceRequ Map modelFeatureData = Maps.newHashMap(featureData); FederatedParams federatedParams = new FederatedParams(); + federatedParams.setFeatureIdMap(inferenceRequest.getSendToRemoteFeatureData()); federatedParams.setCaseId(inferenceRequest.getCaseid()); federatedParams.setSeqNo(inferenceRequest.getSeqno()); federatedParams.setLocal(modelNamespaceData.getLocal()); federatedParams.setModelInfo(new ModelInfo(modelName, modelNamespace)); federatedParams.setRole(modelNamespaceData.getRole()); - federatedParams.setFeatureIdMap(featureIds); + if(featureIds.size()>0) { + federatedParams.setFeatureIdMap(featureIds); + } Map modelResult = model.predict(context, modelFeatureData, federatedParams); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java index 0a7962aa..cc8b6bd8 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java @@ -51,7 +51,6 @@ public class ModelService extends ModelServiceGrpc.ModelServiceImplBase implemen @Autowired ModelManager modelManager; - LinkedHashMap publishLoadReqMap = new LinkedHashMap(); LinkedHashMap publicOnlineReqMap = new LinkedHashMap(); ExecutorService executorService = Executors.newSingleThreadExecutor(); From 31e7dbac67f2bbe2f6a3b0e98ff2727513d8fded Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Wed, 27 Nov 2019 17:34:14 +0800 Subject: [PATCH 013/190] feature-1 2-http-read-model Signed-off-by: v_dylanxu <136539068@qq.com> --- .../ai/fate/serving/core/bean/Dict.java | 2 + .../com/webank/ai/fate/register/url/URL.java | 2 +- .../webank/ai/fate/serving/ServingServer.java | 9 +- .../ai/fate/serving/manger/ModelUtils.java | 82 +++++++++++++++++-- .../ai/fate/serving/utils/HttpClientPool.java | 14 ++++ serving-server/src/main/resources/log4j2.xml | 2 +- 6 files changed, 97 insertions(+), 14 deletions(-) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java index 4a2d0d6b..17000ceb 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java @@ -68,6 +68,7 @@ public class Dict { public static final String PROPERTY_PROXY_ADDRESS = "proxy"; public static final String ONLINE_ENVIROMMENT = "online"; public static final String PROPERTY_ROLL_ADDRESS = "roll"; + public static final String PROPERTY_FLOW_ADDRESS = "flow"; public static final String PROPERTY_USE_ZOOKEEPER = "useZookeeper"; public static final String PROPERTY_SERVER_PORT = "port"; public static final String PROPERTY_USER_DIR = "user.dir"; @@ -128,6 +129,7 @@ public class Dict { public static final String USE_JMX = "useJMX"; public static final String JMX_SERVER_NAME = "jmx.server.name"; public static final String JMX_PORT = "jmx.port"; + public static final String MODEL_TRANSFER_URL = "model.transfer.url"; public static final String GUEST_APP_ID = "guestAppId"; diff --git a/register/src/main/java/com/webank/ai/fate/register/url/URL.java b/register/src/main/java/com/webank/ai/fate/register/url/URL.java index fe7fc39b..b28ae302 100644 --- a/register/src/main/java/com/webank/ai/fate/register/url/URL.java +++ b/register/src/main/java/com/webank/ai/fate/register/url/URL.java @@ -1339,7 +1339,7 @@ private String buildString(boolean appendUser, boolean appendParameter, boolean if (StringUtils.isNotEmpty(project)) { - + buf.append("/"); buf.append(project); } if (StringUtils.isNotEmpty(environment)) { diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index c2533899..9a80d794 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -115,14 +115,13 @@ private void start(String[] args) throws IOException { server.start(); - String userRegisterString = Configuration.getProperty(Dict.USE_REGISTER); - useRegister = Boolean.valueOf(userRegisterString); + useRegister = Boolean.valueOf(Configuration.getProperty(Dict.USE_REGISTER)); LOGGER.info("serving useRegister {}", useRegister); if (useRegister) { - ZookeeperRegistry zookeeperRegistry = applicationContext.getBean(ZookeeperRegistry.class); zookeeperRegistry.subProject(Dict.PROPERTY_PROXY_ADDRESS); + zookeeperRegistry.subProject(Dict.PROPERTY_FLOW_ADDRESS); BaseModel.routerService = applicationContext.getBean(RouterService.class); FateServer.serviceSets.forEach(servie -> { @@ -141,10 +140,8 @@ private void start(String[] args) throws IOException { } }); - zookeeperRegistry.register(FateServer.serviceSets); - - } + boolean useJMX = Boolean.valueOf(Configuration.getProperty(Dict.USE_JMX)); if (useJMX) { String jmxServerName = Configuration.getProperty(Dict.JMX_SERVER_NAME, "serving"); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java index eb524d08..20fb610d 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java @@ -16,29 +16,99 @@ package com.webank.ai.fate.serving.manger; +import com.alibaba.fastjson.JSONObject; import com.webank.ai.eggroll.core.storage.dtable.DTable; import com.webank.ai.eggroll.core.storage.dtable.DTableFactory; import com.webank.ai.fate.api.mlmodel.manager.ModelServiceProto; - +import com.webank.ai.fate.register.router.RouterService; +import com.webank.ai.fate.register.url.URL; +import com.webank.ai.fate.serving.core.bean.Configuration; +import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.core.bean.FederatedRoles; import com.webank.ai.fate.serving.core.bean.ModelInfo; import com.webank.ai.fate.serving.federatedml.PipelineTask; +import com.webank.ai.fate.serving.utils.HttpClientPool; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; +import javax.annotation.PostConstruct; +import java.util.*; +@Component public class ModelUtils { private static final Logger LOGGER = LogManager.getLogger(); private static final String modelKeySeparator = "&"; + private static ModelUtils modelUtils; + + @Autowired + private RouterService routerService; + + @PostConstruct + private void init() { + modelUtils = this; + } + public static Map readModel(String name, String namespace) { LOGGER.info("read model, name: {} namespace: {}", name, namespace); - DTable dataTable = DTableFactory.getDTable(name, namespace, 1); - return dataTable.collect(); + + String requestUrl = ""; + boolean useRegister = Boolean.valueOf(Configuration.getProperty(Dict.USE_REGISTER)); + if (useRegister) { + URL url = URL.valueOf("flow/online/transfer"); + List urls = modelUtils.routerService.router(url); + if (urls == null || urls.isEmpty()) { + LOGGER.info("url not found, {}", url); + return null; + } + + url = urls.get(0); + requestUrl = url.toFullString(); + } else { + requestUrl = Configuration.getProperty(Dict.MODEL_TRANSFER_URL); + } + + if (StringUtils.isBlank(requestUrl)) { + LOGGER.info("roll address not found"); + return null; + } + + Map requestData = new HashMap<>(); + requestData.put("name", name); + requestData.put("namespace", namespace); + + long start = System.currentTimeMillis(); + String responseBody = HttpClientPool.transferPost(requestUrl, requestData); + long end = System.currentTimeMillis(); + + LOGGER.info("{}|{}|{}|{}", requestUrl, start, end, (end - start) + " ms"); + + if (StringUtils.isEmpty(responseBody)) { + LOGGER.info("read model fail, {}, {}", name, namespace); + return null; + } + + JSONObject responseData = JSONObject.parseObject(responseBody); + if (responseData.getInteger("retcode") != 0) { + LOGGER.info("read model fail, {}, {}, {}", name, namespace, responseData.getString("retmsg")); + return null; + } + + Map resultMap = new HashMap<>(); + Map dataMap = responseData.getJSONObject("data"); + if (dataMap == null || dataMap.isEmpty()) { + LOGGER.info("read model fail, {}, {}, {}", name, namespace, dataMap); + return null; + } + + for (Map.Entry entry : dataMap.entrySet()) { + resultMap.put(entry.getKey(), Base64.getDecoder().decode(String.valueOf(entry.getValue()))); + } + + return resultMap; } public static PipelineTask loadModel(String name, String namespace) { diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/utils/HttpClientPool.java b/serving-server/src/main/java/com/webank/ai/fate/serving/utils/HttpClientPool.java index 6a0dbc68..b5e006a8 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/utils/HttpClientPool.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/utils/HttpClientPool.java @@ -156,4 +156,18 @@ private static String getResponse(HttpRequestBase request) { } } } + + public static String transferPost(String url, Map requestData) { + HttpPost httpPost = new HttpPost(url); + RequestConfig requestConfig = RequestConfig.custom() + .setConnectionRequestTimeout(60000) + .setConnectTimeout(60000) + .setSocketTimeout(60000).build(); + httpPost.addHeader(Dict.CONTENT_TYPE, Dict.CONTENT_TYPE_JSON_UTF8); + httpPost.setConfig(requestConfig); + StringEntity stringEntity = new StringEntity(ObjectTransform.bean2Json(requestData), Dict.CHARSET_UTF8); + stringEntity.setContentEncoding(Dict.CHARSET_UTF8); + httpPost.setEntity(stringEntity); + return getResponse(httpPost); + } } diff --git a/serving-server/src/main/resources/log4j2.xml b/serving-server/src/main/resources/log4j2.xml index 1a35ee95..ae3da2d8 100644 --- a/serving-server/src/main/resources/log4j2.xml +++ b/serving-server/src/main/resources/log4j2.xml @@ -145,7 +145,7 @@ - + From c0c1d81cb2a6259be82ea3dba12b005734ea7013 Mon Sep 17 00:00:00 2001 From: utu Date: Fri, 29 Nov 2019 16:41:24 +0800 Subject: [PATCH 014/190] add authentication to grpc unaryCall. --- proto/proxy.proto | 9 ++++--- .../fate/networking/proxy/util/AuthUtils.java | 25 ++++++++++++------- router/src/main/resources/auth_config.json | 10 +++++--- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/proto/proxy.proto b/proto/proxy.proto index 761f9fb3..ee9cafa9 100644 --- a/proto/proxy.proto +++ b/proto/proxy.proto @@ -72,10 +72,11 @@ message Data { // authentication info message AuthInfo { int64 timestamp = 1; // timestamp of request, millisecond - string signature = 2; // signature - string applyId = 3; - string nonce = 4; - string version = 5; + string appKey = 2; + string signature = 3; // signature + string applyId = 4; + string nonce = 5; + string version = 6; } // data streaming packet diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java index 4b00d55b..300b170f 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java @@ -30,7 +30,6 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; @@ -40,7 +39,8 @@ public class AuthUtils implements InitializingBean{ private static final Logger LOGGER = LogManager.getLogger(); private static final String confFilePath = System.getProperty("authFile"); - private static Map ACCESS_KEYS_MAP = new HashMap<>(); + private static Map KEY_SECRET_MAP = new HashMap<>(); + private static Map PARTYID_KEY_MAP = new HashMap<>(); private static int validRequestTimeoutSecond = 10; private static String applyId = ""; private static boolean ifUseAuth = false; @@ -75,20 +75,25 @@ public void loadConfig(){ JsonArray jsonArray = jsonObject.getAsJsonArray("access_keys"); Gson gson = new Gson(); List allowKeys = gson.fromJson(jsonArray, ArrayList.class); - ACCESS_KEYS_MAP.clear(); + KEY_SECRET_MAP.clear(); for (Map allowKey : allowKeys) { - ACCESS_KEYS_MAP.put(allowKey.get("appKey").toString(), allowKey.get("appSecret").toString()); + KEY_SECRET_MAP.put(allowKey.get("app_key").toString(), allowKey.get("app_secret").toString()); + PARTYID_KEY_MAP.put(allowKey.get("party_id").toString(), allowKey.get("app_key").toString()); } LOGGER.debug("refreshed auth cfg using file {}.", confFilePath); } private String getSecret(String appKey) { - return ACCESS_KEYS_MAP.get(appKey); + return KEY_SECRET_MAP.get(appKey); } - private String calSignature(Proxy.Metadata header, Proxy.Data body, long timestamp) throws Exception { + private String getAppKey(String partyId) { + return PARTYID_KEY_MAP.get(partyId); + } + + private String calSignature(Proxy.Metadata header, Proxy.Data body, long timestamp, String appKey) throws Exception { String signature = ""; - String appSecret = getSecret(header.getSrc().getPartyId()); + String appSecret = getSecret(appKey); if (StringUtils.isEmpty(appSecret)) { LOGGER.error("appSecret not found"); return signature; @@ -110,10 +115,12 @@ public Proxy.Packet addAuthInfo(Proxy.Packet packet) throws Exception { authBuilder.setTimestamp(timestamp); authBuilder.setApplyId(applyId); + String appKey = getAppKey(packet.getHeader().getSrc().getPartyId()); + authBuilder.setAppKey(appKey); if(ifUseAuth && !StringUtils.equals(selfPartyId, packet.getHeader().getDst().getPartyId())) { - String signature = calSignature(packet.getHeader(), packet.getBody(), timestamp); + String signature = calSignature(packet.getHeader(), packet.getBody(), timestamp, appKey); authBuilder.setSignature(signature); } packetBuilder.setAuth(authBuilder.build()); @@ -132,7 +139,7 @@ public boolean checkAuthentication(Proxy.Packet packet) throws Exception { } // check signature String reqSignature = packet.getAuth().getSignature(); - String validSignature = calSignature(packet.getHeader(), packet.getBody(), requestTimeMillis); + String validSignature = calSignature(packet.getHeader(), packet.getBody(), requestTimeMillis, packet.getAuth().getAppKey()); if (!StringUtils.equals(reqSignature, validSignature)) { LOGGER.error("invalid signature, request:{}, valid:{}", reqSignature, validSignature); return false; diff --git a/router/src/main/resources/auth_config.json b/router/src/main/resources/auth_config.json index 4745b315..27a1426d 100644 --- a/router/src/main/resources/auth_config.json +++ b/router/src/main/resources/auth_config.json @@ -4,10 +4,12 @@ "request_expire_seconds": 8, "apply_id": "20191119163236256", "access_keys": [{ - "appKey": "111", - "appSecret": "11111" + "party_id": "9999", + "app_key": "11", + "app_secret": "11111" }, { - "appKey": "222", - "appSecret": "22222" + "party_id": "10000", + "app_key": "22", + "app_secret": "22222" }] } From a96cdb8e5e4ee70c7bc170daf8d16f4d4b2aef09 Mon Sep 17 00:00:00 2001 From: utu Date: Fri, 29 Nov 2019 18:00:55 +0800 Subject: [PATCH 015/190] add authentication to grpc unaryCall. --- .../fate/networking/proxy/util/AuthUtils.java | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java index 300b170f..51e4c4de 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java @@ -107,24 +107,25 @@ private String calSignature(Proxy.Metadata header, Proxy.Data body, long timesta } public Proxy.Packet addAuthInfo(Proxy.Packet packet) throws Exception { + if(!StringUtils.equals(selfPartyId, packet.getHeader().getDst().getPartyId())) { + Proxy.Packet.Builder packetBuilder = packet.toBuilder(); + Proxy.AuthInfo.Builder authBuilder = packetBuilder.getAuthBuilder(); + + long timestamp = System.currentTimeMillis(); + authBuilder.setTimestamp(timestamp); + authBuilder.setApplyId(applyId); + + if(ifUseAuth) { + String appKey = getAppKey(packet.getHeader().getSrc().getPartyId()); + authBuilder.setAppKey(appKey); + String signature = calSignature(packet.getHeader(), packet.getBody(), timestamp, appKey); + authBuilder.setSignature(signature); + } - Proxy.Packet.Builder packetBuilder = packet.toBuilder(); - Proxy.AuthInfo.Builder authBuilder = packetBuilder.getAuthBuilder(); - - long timestamp = System.currentTimeMillis(); - - authBuilder.setTimestamp(timestamp); - authBuilder.setApplyId(applyId); - String appKey = getAppKey(packet.getHeader().getSrc().getPartyId()); - authBuilder.setAppKey(appKey); - - if(ifUseAuth - && !StringUtils.equals(selfPartyId, packet.getHeader().getDst().getPartyId())) { - String signature = calSignature(packet.getHeader(), packet.getBody(), timestamp, appKey); - authBuilder.setSignature(signature); + packetBuilder.setAuth(authBuilder.build()); + return packetBuilder.build(); } - packetBuilder.setAuth(authBuilder.build()); - return packetBuilder.build(); + return packet; } public boolean checkAuthentication(Proxy.Packet packet) throws Exception { From 073b53a20a46900f015c06d6303beab145588b9a Mon Sep 17 00:00:00 2001 From: kaideng Date: Mon, 2 Dec 2019 10:08:58 +0800 Subject: [PATCH 016/190] change for fdn Signed-off-by: kaideng --- .../fate/serving/core/bean/ReturnResult.java | 13 +++-- .../serving/core/processor/Processor.java | 6 +++ .../serving/federatedml/model/BaseModel.java | 28 +---------- .../federatedml/model/HeteroLRGuest.java | 22 ++++----- .../serving/federatedml/model/Imputer.java | 22 +++++---- .../serving/federatedml/model/Outlier.java | 23 +++++---- proto/proxy.proto | 1 + .../processing/CommonPostProcessing.java | 49 +++++++++++++++++++ .../processing/CommonPreProcessing.java | 31 ++++++++++++ .../guest/DefaultGuestInferenceProvider.java | 10 ++-- .../host/DefaultHostInferenceProvider.java | 8 --- .../serving/service/InferenceService.java | 1 + 12 files changed, 140 insertions(+), 74 deletions(-) create mode 100644 fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/processor/Processor.java create mode 100644 serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/CommonPostProcessing.java create mode 100644 serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/CommonPreProcessing.java diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/ReturnResult.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/ReturnResult.java index ead595dc..95c81c1d 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/ReturnResult.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/ReturnResult.java @@ -34,6 +34,16 @@ public class ReturnResult { private Map log; private Map warn; + public int getFlag() { + return flag; + } + + public void setFlag(int flag) { + this.flag = flag; + } + + private int flag; + public ReturnResult(){ this.data = new HashMap<>(); this.log = new HashMap<>(); @@ -92,9 +102,6 @@ public String getCaseid() { public String toString(){ String result= ""; - - - return result; diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/processor/Processor.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/processor/Processor.java new file mode 100644 index 00000000..36f7d642 --- /dev/null +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/processor/Processor.java @@ -0,0 +1,6 @@ +package com.webank.ai.fate.serving.core.processor; + +public interface Processor { + + +} \ No newline at end of file diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java index 15f971c8..9fdafb07 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java @@ -101,25 +101,18 @@ protected ReturnResult getFederatedPredict(Context context, FederatedParams gues FederatedParty srcParty = guestFederatedParams.getLocal(); FederatedRoles federatedRoles = guestFederatedParams.getRole(); Map featureIds = (Map) guestFederatedParams.getFeatureIdMap(); - - //TODO: foreach FederatedParty dstParty = new FederatedParty(Dict.HOST, federatedRoles.getRole(Dict.HOST).get(0)); if (useCache) { ReturnResult remoteResultFromCache = CacheManager.getInstance().getRemoteModelInferenceResult(dstParty, federatedRoles, featureIds); if (remoteResultFromCache != null) { LOGGER.info("caseid {} get remote party model inference result from cache.", context.getCaseId()); - //federatedParams.put("getRemotePartyResult", false); context.putData(Dict.GET_REMOTE_PARTY_RESULT, false); context.hitCache(true); remoteResult = remoteResultFromCache; return remoteResult; } } - - HostFederatedParams hostFederatedParams = new HostFederatedParams(); - - hostFederatedParams.setCaseId(guestFederatedParams.getCaseId()); hostFederatedParams.setSeqNo(guestFederatedParams.getSeqNo()); hostFederatedParams.setFeatureIdMap(guestFederatedParams.getFeatureIdMap()); @@ -128,21 +121,9 @@ protected ReturnResult getFederatedPredict(Context context, FederatedParams gues hostFederatedParams.setRole(federatedRoles); hostFederatedParams.setPartnerModelInfo(guestFederatedParams.getModelInfo()); hostFederatedParams.setData(guestFederatedParams.getData()); - - -// Map requestData = new HashMap<>(); -// Arrays.asList("caseid", "seqno").forEach((field -> { -// requestData.put(field, federatedParams.get(field)); -// })); -// requestData.put("partner_local", ObjectTransform.bean2Json(srcParty)); -// requestData.put("partner_model_info", ObjectTransform.bean2Json(federatedParams.get("model_info"))); -// requestData.put("feature_id", ObjectTransform.bean2Json(federatedParams.get("feature_id"))); -// requestData.put("local", ObjectTransform.bean2Json(dstParty)); -// requestData.put("role", ObjectTransform.bean2Json(federatedParams.get("role"))); -// federatedParams.put("getRemotePartyResult", true); context.putData(Dict.GET_REMOTE_PARTY_RESULT, true); remoteResult = getFederatedPredictFromRemote(context, srcParty, dstParty, hostFederatedParams, remoteMethodName); - if (useCache) { + if (useCache&& remoteResult!=null&&remoteResult.getRetcode()==0) { CacheManager.getInstance().putRemoteModelInferenceResult(dstParty, federatedRoles, featureIds, remoteResult); LOGGER.info("caseid {} get remote party model inference result from federated request.", context.getCaseId()); } @@ -160,12 +141,6 @@ protected ReturnResult getFederatedPredictFromRemote(Context context, FederatedP try { Proxy.Packet.Builder packetBuilder = Proxy.Packet.newBuilder(); - -// packetBuilder.setBody(Proxy.Data.newBuilder() -// .setValue(ByteString.copyFrom(ObjectTransform.bean2Json(requestData).getBytes())) -// .build()); - - packetBuilder.setBody(Proxy.Data.newBuilder() .setValue(ByteString.copyFrom(JSON.toJSONBytes(hostFederatedParams))) .build()); @@ -217,6 +192,7 @@ protected ReturnResult getFederatedPredictFromRemote(Context context, FederatedP DataTransferServiceGrpc.DataTransferServiceBlockingStub stub1 = DataTransferServiceGrpc.newBlockingStub(channel1); Proxy.Packet packet = stub1.unaryCall(packetBuilder.build()); + LOGGER.info("PPPPPPPPPPPPPPPPPPPPPPPPPP {}",packet.getBody().getValue().toStringUtf8()); remoteResult = (ReturnResult) ObjectTransform.json2Bean(packet.getBody().getValue().toStringUtf8(), ReturnResult.class); } finally { grpcConnectionPool.returnPool(channel1, address); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java index 2d9e0174..e65eefe9 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java @@ -43,20 +43,21 @@ public Map handlePredict(Context context, List forwardRet = forward(inputData); double score = forwardRet.get(Dict.SCORE); - LOGGER.info("guest score:{}", score); + LOGGER.info("caseid {} guest score:{}", context.getCaseId(),score); try { ReturnResult hostPredictResponse = this.getFederatedPredict(context, predictParams, Dict.FEDERATED_INFERENCE, true); - //predictParams.put("federatedResult", hostPredictResponse); - //context.setFederatedResult(hostPredictResponse); - LOGGER.info("host response is {}", hostPredictResponse.getData()); - if(hostPredictResponse.getData()!=null&&hostPredictResponse.getData().get(Dict.SCORE)!=null) { - double hostScore =( (Number) hostPredictResponse.getData().get(Dict.SCORE)).doubleValue(); - LOGGER.info("host score:{}", hostScore); - score += hostScore; + if(hostPredictResponse !=null) { + result.put(Dict.RET_CODE,hostPredictResponse.getRetcode()); + LOGGER.info("caseid {} host response is {}",context.getCaseId(),hostPredictResponse.getData()); + if (hostPredictResponse.getData() != null && hostPredictResponse.getData().get(Dict.SCORE) != null) { + double hostScore = ((Number) hostPredictResponse.getData().get(Dict.SCORE)).doubleValue(); + LOGGER.info("caseid {} host score:{}",context.getCaseId(), hostScore); + score += hostScore; + } + }else{ + LOGGER.info("caseid {} host response is null",context.getCaseId()); } - - } catch (Exception ex) { LOGGER.error("get host predict failed:", ex); } @@ -65,7 +66,6 @@ public Map handlePredict(Context context, List missingValues, Map missingReplaceVal } public Map transform(Map inputData) { - LOGGER.info("start imputer transform task"); - for (String key : inputData.keySet()) { - String value = inputData.get(key).toString(); - if (this.missingValueSet.contains(value.toLowerCase())) { - try { - inputData.put(key, this.missingReplaceValues.get(key)); - } catch (Exception ex) { - ex.printStackTrace(); - inputData.put(key, 0.); + if(inputData!=null) { + LOGGER.info("start imputer transform task"); + for (String key : inputData.keySet()) { + if(inputData.get(key)!=null) { + String value = inputData.get(key).toString(); + if (this.missingValueSet.contains(value.toLowerCase())) { + try { + inputData.put(key, this.missingReplaceValues.get(key)); + } catch (Exception ex) { + ex.printStackTrace(); + inputData.put(key, 0.); + } + } } } } diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Outlier.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Outlier.java index 453fcb2a..208c2956 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Outlier.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Outlier.java @@ -35,16 +35,19 @@ public Outlier(List outlierValues, Map outlierReplaceVal public Map transform(Map inputData) { LOGGER.info("start outlier transform task"); - - for (String key : inputData.keySet()) { - String value = inputData.get(key).toString(); - if (this.outlierValueSet.contains(value.toLowerCase())) { - try { - LOGGER.info("value:{}", value); - inputData.put(key, outlierReplaceValues.get(key)); - } catch (Exception ex) { - ex.printStackTrace(); - inputData.put(key, 0.); + if(inputData!=null) { + for (String key : inputData.keySet()) { + if(inputData.get(key)!=null) { + String value = inputData.get(key).toString(); + if (this.outlierValueSet.contains(value.toLowerCase())) { + try { + LOGGER.info("value:{}", value); + inputData.put(key, outlierReplaceValues.get(key)); + } catch (Exception ex) { + ex.printStackTrace(); + inputData.put(key, 0.); + } + } } } } diff --git a/proto/proxy.proto b/proto/proxy.proto index 761f9fb3..fd0b5d2d 100644 --- a/proto/proxy.proto +++ b/proto/proxy.proto @@ -76,6 +76,7 @@ message AuthInfo { string applyId = 3; string nonce = 4; string version = 5; + int32 flag = 6; } // data streaming packet diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/CommonPostProcessing.java b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/CommonPostProcessing.java new file mode 100644 index 00000000..353592d6 --- /dev/null +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/CommonPostProcessing.java @@ -0,0 +1,49 @@ +package com.webank.ai.fate.serving.adapter.processing; + + +import com.webank.ai.fate.serving.bean.PostProcessingResult; +import com.webank.ai.fate.serving.core.bean.Context; +import com.webank.ai.fate.serving.core.bean.Dict; +import com.webank.ai.fate.serving.core.bean.ReturnResult; +import com.webank.ai.fate.serving.core.constant.InferenceRetCode; +import com.webank.ai.fate.serving.host.DefaultHostInferenceProvider; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + + +import java.util.*; + +public class CommonPostProcessing implements PostProcessing { + + + private static final Logger LOGGER = LogManager.getLogger(CommonPostProcessing.class); + + @Override + public PostProcessingResult getResult(Context context, Map featureData, Map modelResult) { + PostProcessingResult postProcessingResult = new PostProcessingResult(); + Integer rcode=0; + Map data = new HashMap<>(); + if(modelResult!=null) { + if (modelResult.get(Dict.RET_CODE) != null) { + rcode = (Integer) modelResult.get(Dict.RET_CODE); + } + else { + rcode= InferenceRetCode.NO_RESULT; + } + + Double prob = Double.parseDouble(modelResult.get("prob").toString()); + data.put(Dict.SCORE, prob); + }else{ + rcode= InferenceRetCode.NO_RESULT; + } + ReturnResult result = new ReturnResult(); + result.setData(data); + result.setRetcode(rcode); + postProcessingResult.setProcessingResult(result); + return postProcessingResult; + } + +} + + + diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/CommonPreProcessing.java b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/CommonPreProcessing.java new file mode 100644 index 00000000..0286d5e7 --- /dev/null +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/CommonPreProcessing.java @@ -0,0 +1,31 @@ +package com.webank.ai.fate.serving.adapter.processing; + + +import com.webank.ai.fate.serving.bean.PreProcessingResult; +import com.webank.ai.fate.serving.core.bean.Context; +import jdk.nashorn.internal.runtime.ParserException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +public class CommonPreProcessing implements PreProcessing { + @Override + public PreProcessingResult getResult(Context context , String paras) { + PreProcessingResult preProcessingResult = new PreProcessingResult(); + preProcessingResult.setProcessingResult(preProcessing(paras)); + Map featureIds = new HashMap<>(); + JSONObject para_obj = new JSONObject(paras); + preProcessingResult.setFeatureIds(featureIds); + return preProcessingResult; + } + + private Map preProcessing(String paras) throws ClassCastException, ParserException { + Map feature = new HashMap<>(); + + return feature; + } + + public static void main(String[] args){ + } +} diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java index e084bdcd..3d41d56f 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java @@ -136,11 +136,9 @@ public ReturnResult runInference(Context context, InferenceRequest inferenceRequ federatedParams.setLocal(modelNamespaceData.getLocal()); federatedParams.setModelInfo(new ModelInfo(modelName, modelNamespace)); federatedParams.setRole(modelNamespaceData.getRole()); - if(featureIds.size()>0) { + if(featureIds!=null&&featureIds.size()>0) { federatedParams.setFeatureIdMap(featureIds); } - - Map modelResult = model.predict(context, modelFeatureData, federatedParams); PostProcessingResult postProcessingResult; try { @@ -170,17 +168,15 @@ private ReturnResult handleResult(Context context, InferenceRequest inferenceReq if (!getRemotePartyResult) { billing = false; } else if (federatedResult != null) { - if (federatedResult.getRetcode() == InferenceRetCode.GET_FEATURE_FAILED || federatedResult.getRetcode() == InferenceRetCode.INVALID_FEATURE || federatedResult.getRetcode() == InferenceRetCode.NO_FEATURE) { billing = false; } - - if (federatedResult.getRetcode() != 0) { partyInferenceRetcode += 2; inferenceResult.setRetcode(federatedResult.getRetcode()); } - + }else{ + partyInferenceRetcode += 2; } if (inferenceResult.getRetcode() != 0) { partyInferenceRetcode += 1; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/host/DefaultHostInferenceProvider.java b/serving-server/src/main/java/com/webank/ai/fate/serving/host/DefaultHostInferenceProvider.java index ff27c14d..e417116b 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/host/DefaultHostInferenceProvider.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/host/DefaultHostInferenceProvider.java @@ -39,8 +39,6 @@ public class DefaultHostInferenceProvider implements HostInferenceProvider { private static final Logger LOGGER = LogManager.getLogger(DefaultHostInferenceProvider.class); - - //Logger logger = LoggerFactory.getLogger(DefaultHostInferenceProvider.class); @Autowired ModelManager modelManager; @@ -67,11 +65,6 @@ public ReturnResult federatedInference(Context context, HostFederatedParams fede long startTime = System.currentTimeMillis(); ReturnResult returnResult = new ReturnResult(); - //TODO: Very ugly, need to be optimized -// FederatedParty party = (FederatedParty) ObjectTransform.json2Bean(federatedParams.get("local").toString(), FederatedParty.class); -// FederatedRoles federatedRoles = (FederatedRoles) ObjectTransform.json2Bean(federatedParams.get("role").toString(), FederatedRoles.class); -// ModelInfo partnerModelInfo = (ModelInfo) ObjectTransform.json2Bean(federatedParams.get("partner_model_info").toString(), ModelInfo.class); -// Map featureIds = (Map) ObjectTransform.json2Bean(federatedParams.get("feature_id").toString(), HashMap.class); boolean billing = false; FederatedParty party = federatedParams.getLocal(); FederatedRoles federatedRoles = federatedParams.getRole(); @@ -89,7 +82,6 @@ public ReturnResult federatedInference(Context context, HostFederatedParams fede if (model == null) { returnResult.setRetcode(InferenceRetCode.LOAD_MODEL_FAILED); returnResult.setRetmsg("Can not found model."); - // InferenceUtils.logInference(context ,federatedParams, party, federatedRoles, returnResult, 0, false, false); return returnResult; } LOGGER.info("use model to inference on {} {}, id: {}, version: {}", party.getRole(), party.getPartyId(), modelInfo.getNamespace(), modelInfo.getName()); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java index 91eb0aaa..44d047c4 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java @@ -16,6 +16,7 @@ package com.webank.ai.fate.serving.service; +import com.alibaba.fastjson.JSON; import com.google.protobuf.ByteString; import com.webank.ai.eggroll.core.utils.ObjectTransform; import com.webank.ai.fate.api.serving.InferenceServiceGrpc; From f37be5c3f410816116bb8a299339123991f1ead0 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Tue, 3 Dec 2019 10:54:23 +0800 Subject: [PATCH 017/190] change publish online Signed-off-by: v_dylanxu <136539068@qq.com> --- proto/model_service.proto | 2 +- .../ai/fate/serving/service/ModelService.java | 35 ++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/proto/model_service.proto b/proto/model_service.proto index 49dce8b0..2023acda 100644 --- a/proto/model_service.proto +++ b/proto/model_service.proto @@ -38,5 +38,5 @@ message PublishResponse{ service ModelService{ rpc publishLoad(PublishRequest) returns (PublishResponse); - rpc publishOnline(PublishRequest) returns (PublishResponse); + rpc publishBind(PublishRequest) returns (PublishResponse); } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java index 0a7962aa..d130e350 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java @@ -121,7 +121,7 @@ public synchronized void publishLoad(PublishRequest req, StreamObserver responseStreamObserver) { Context context = new BaseContext(new BaseLoggerPrinter()); @@ -154,6 +154,39 @@ public synchronized void publishOnline(PublishRequest req, StreamObserver responseStreamObserver) { + Context context = new BaseContext(new BaseLoggerPrinter()); + context.setActionType(ModelActionType.MODEL_PUBLISH_ONLINE.name()); + context.preProcess(); + ReturnResult returnResult = null; + try { + PublishResponse.Builder builder = PublishResponse.newBuilder(); + context.putData(Dict.SERVICE_ID,req.getServiceId()); + returnResult = modelManager.publishOnlineModel(context, + new FederatedParty(req.getLocal().getRole(), req.getLocal().getPartyId()), + ModelUtils.getFederatedRoles(req.getRoleMap()), + ModelUtils.getFederatedRolesModel(req.getModelMap()) + ); + builder.setStatusCode(returnResult.getRetcode()) + .setMessage(returnResult.getRetmsg()) + .setData(ByteString.copyFrom(ObjectTransform.bean2Json(returnResult.getData()).getBytes())); + if (returnResult.getRetcode() == 0) { + + String content = new String(Base64.encode(req.toByteArray())); + String key = Md5Crypt.md5Crypt(content.getBytes()); + + publicOnlineReqMap.put(key, content); + fireStoreEvent(); + } + responseStreamObserver.onNext(builder.build()); + responseStreamObserver.onCompleted(); + } finally { + context.postProcess(req, returnResult); + } + } + public void fireStoreEvent() { executorService.submit(() -> { From d0465a8ce11fde3e30b5759db9e1210ce3748752 Mon Sep 17 00:00:00 2001 From: utu Date: Tue, 3 Dec 2019 14:45:16 +0800 Subject: [PATCH 018/190] use selfPartyId instead of srcPartyId. --- .../com/webank/ai/fate/networking/proxy/util/AuthUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java index 51e4c4de..8c09f91d 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java @@ -116,7 +116,7 @@ public Proxy.Packet addAuthInfo(Proxy.Packet packet) throws Exception { authBuilder.setApplyId(applyId); if(ifUseAuth) { - String appKey = getAppKey(packet.getHeader().getSrc().getPartyId()); + String appKey = getAppKey(selfPartyId); authBuilder.setAppKey(appKey); String signature = calSignature(packet.getHeader(), packet.getBody(), timestamp, appKey); authBuilder.setSignature(signature); From 8acdb8d39a58a8ec3b21d4e01278fa750ca54426 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Wed, 4 Dec 2019 15:03:21 +0800 Subject: [PATCH 019/190] update package.xml Signed-off-by: v_dylanxu <136539068@qq.com> --- router/package.xml | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/router/package.xml b/router/package.xml index 336cb0c3..ca7a6cce 100644 --- a/router/package.xml +++ b/router/package.xml @@ -44,22 +44,9 @@ src/main/resources proxy.properties - - - - - /conf - src/main/resources - log4j2.properties - - - - - /conf - src/main/resources - applicationContext-proxy.xml + auth_config.json From 52ae2155360bf7b6ef3ec3a414dc8a6a0addfb35 Mon Sep 17 00:00:00 2001 From: kaideng Date: Wed, 4 Dec 2019 17:44:05 +0800 Subject: [PATCH 020/190] change some thing for fdn Signed-off-by: kaideng --- .../fate/serving/federatedml/model/BaseModel.java | 3 +-- .../fate/register/router/DefaultRouterService.java | 14 +++++++++----- .../guest/DefaultGuestInferenceProvider.java | 2 +- .../fate/serving/manger/DefaultModelManager.java | 1 + .../ai/fate/serving/service/InferenceService.java | 2 +- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java index 9fdafb07..75cc73a3 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java @@ -180,7 +180,7 @@ protected ReturnResult getFederatedPredictFromRemote(Context context, FederatedP URL paramUrl = URL.valueOf(Dict.PROPERTY_PROXY_ADDRESS + "/" + Dict.ONLINE_ENVIROMMENT + "/" + Dict.UNARYCALL); URL newUrl =paramUrl.addParameter(Constants.VERSION_KEY,version); List urls = routerService.router(newUrl); - if (urls.size() > 0) { + if (urls!=null&&urls.size() > 0) { URL url = urls.get(0); String ip = url.getHost(); int port = url.getPort(); @@ -192,7 +192,6 @@ protected ReturnResult getFederatedPredictFromRemote(Context context, FederatedP DataTransferServiceGrpc.DataTransferServiceBlockingStub stub1 = DataTransferServiceGrpc.newBlockingStub(channel1); Proxy.Packet packet = stub1.unaryCall(packetBuilder.build()); - LOGGER.info("PPPPPPPPPPPPPPPPPPPPPPPPPP {}",packet.getBody().getValue().toStringUtf8()); remoteResult = (ReturnResult) ObjectTransform.json2Bean(packet.getBody().getValue().toStringUtf8(), ReturnResult.class); } finally { grpcConnectionPool.returnPool(channel1, address); diff --git a/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java b/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java index cf206880..9d8ad49d 100644 --- a/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java +++ b/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java @@ -53,13 +53,17 @@ public List doRouter(URL url, LoadBalanceModel loadBalanceModel) { } private List filterEmpty(List urls) { + + List resultList = new ArrayList<>(); + if(urls!=null) { - urls.forEach(url -> { - if (!url.getProtocol().equalsIgnoreCase(Constants.EMPTY_PROTOCOL)) { - resultList.add(url); - } - }); + urls.forEach(url -> { + if (!url.getProtocol().equalsIgnoreCase(Constants.EMPTY_PROTOCOL)) { + resultList.add(url); + } + }); + } return resultList; } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java index 3d41d56f..9922a247 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java @@ -178,7 +178,7 @@ private ReturnResult handleResult(Context context, InferenceRequest inferenceReq }else{ partyInferenceRetcode += 2; } - if (inferenceResult.getRetcode() != 0) { + if (inferenceResult.getRetcode() != 0&&partyInferenceRetcode==0) { partyInferenceRetcode += 1; } inferenceResult.setRetcode(inferenceResult.getRetcode() + partyInferenceRetcode * 1000); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java index 97faed47..2a04619d 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java @@ -185,6 +185,7 @@ public ReturnResult publishOnlineModel(Context context,FederatedParty federatedP modelNamespaceDataMapPool.put(modelNamespace, new ModelNamespaceData(modelNamespace, federatedParty, federatedRoles, modelName, model)); appNamespaceMapPool.put(partyId, modelNamespace); if(serviceId!=null){ + logger.info("put serviceId {} input pool",serviceId); appNamespaceMapPool.put(serviceId, modelNamespace); } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java index 44d047c4..cfde9b64 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java @@ -72,7 +72,7 @@ private void inferenceServiceAction(InferenceMessage req, StreamObserver Date: Wed, 4 Dec 2019 18:59:48 +0800 Subject: [PATCH 021/190] add new interface Signed-off-by: kaideng --- .../com/webank/ai/fate/serving/core/bean/BaseContext.java | 2 +- .../webank/ai/fate/serving/manger/DefaultModelManager.java | 7 +++++-- .../com/webank/ai/fate/serving/service/ModelService.java | 6 ++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java index 705084a4..83600619 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java @@ -68,7 +68,7 @@ public void preProcess() { @Override public Object getData(Object key) { - return null; + return dataMap.get(key); } @Override diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java index 2a04619d..e8507b18 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java @@ -156,8 +156,11 @@ public ReturnResult publishOnlineModel(Context context,FederatedParty federatedP String role = federatedParty.getRole(); String partyId = federatedParty.getPartyId(); String serviceId = null; + if(context.getData(Dict.SERVICE_ID)!=null) { serviceId = context.getData(Dict.SERVICE_ID).toString(); + }else{ + logger.info("service id is null"); } ReturnResult returnResult = new ReturnResult(); ModelInfo modelInfo = federatedRolesModel.get(role).get(partyId); @@ -184,7 +187,7 @@ public ReturnResult publishOnlineModel(Context context,FederatedParty federatedP String modelName = modelInfo.getName(); modelNamespaceDataMapPool.put(modelNamespace, new ModelNamespaceData(modelNamespace, federatedParty, federatedRoles, modelName, model)); appNamespaceMapPool.put(partyId, modelNamespace); - if(serviceId!=null){ + if(StringUtils.isNotEmpty(serviceId)){ logger.info("put serviceId {} input pool",serviceId); appNamespaceMapPool.put(serviceId, modelNamespace); } @@ -193,7 +196,7 @@ public ReturnResult publishOnlineModel(Context context,FederatedParty federatedP logger.info("Get model namespace {} for app {}", modelNamespace, partyId); returnResult.setRetcode(InferenceRetCode.OK); if (zookeeperRegistry != null) { - if(serviceId!=null){ + if(StringUtils.isNotEmpty(serviceId)){ zookeeperRegistry.addDynamicEnvironment(serviceId); } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java index cf531897..66a3e454 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java @@ -130,6 +130,7 @@ public synchronized void publishOnline(PublishRequest req, StreamObserver Date: Wed, 4 Dec 2019 19:32:20 +0800 Subject: [PATCH 022/190] add some change Signed-off-by: kaideng --- .../com/webank/ai/fate/serving/manger/DefaultModelManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java index e8507b18..9998931b 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java @@ -131,7 +131,7 @@ public ReturnResult publishLoadModel(Context context,FederatedParty federatedPar if(Dict.HOST.equals(role)){ if (zookeeperRegistry != null) { - if(serviceId!=null){ + if(StringUtils.isNotEmpty(serviceId)){ zookeeperRegistry.addDynamicEnvironment(serviceId); } String key = ModelUtils.genModelKey(modelInfo.getName(), modelInfo.getNamespace()); From 6b88a4e772201e6b2c225f68beeb3db994eba489 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Thu, 5 Dec 2019 11:23:22 +0800 Subject: [PATCH 023/190] update Signed-off-by: v_dylanxu <136539068@qq.com> --- router/bin/service.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/bin/service.sh b/router/bin/service.sh index 6c15c540..3901d3a7 100644 --- a/router/bin/service.sh +++ b/router/bin/service.sh @@ -25,7 +25,7 @@ module=serving-router main_class=com.webank.ai.fate.networking.Proxy getpid() { - pid=`ps aux | grep ${main_class} | grep -v grep | awk '{print $2}'` + pid=`ps aux | grep ${module} | grep -v grep | awk '{print $2}'` if [[ -n ${pid} ]]; then return 1 From 698bc9e0b0f61533dfcc4aaf4df154ea3cd583f7 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Thu, 5 Dec 2019 22:07:13 +0800 Subject: [PATCH 024/190] add zookeeper acl auth Signed-off-by: v_dylanxu <136539068@qq.com> --- .../ai/fate/serving/core/bean/Dict.java | 3 ++ .../common/CuratorZookeeperClient.java | 51 +++++++++++++++++-- .../com/webank/ai/fate/networking/Proxy.java | 6 +++ router/src/main/resources/proxy.properties | 3 ++ .../webank/ai/fate/serving/ServingServer.java | 2 + .../main/resources/serving-server.properties | 2 + 6 files changed, 62 insertions(+), 5 deletions(-) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java index b23eefe0..b3bfe858 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java @@ -134,5 +134,8 @@ public class Dict { public static final String HOST_APP_ID = "hostAppId"; public static final String SERVICE_ID = "serviceId"; + public static final String ACL_USERNAME = "acl.username"; + public static final String ACL_PASSWORD = "acl.password"; + } diff --git a/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java b/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java index cf8f6b66..bf0e9446 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java @@ -34,8 +34,14 @@ import org.apache.zookeeper.KeeperException.NoNodeException; import org.apache.zookeeper.KeeperException.NodeExistsException; import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.data.ACL; +import org.apache.zookeeper.data.Id; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.server.auth.DigestAuthenticationProvider; import java.nio.charset.Charset; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -54,6 +60,12 @@ public class CuratorZookeeperClient extends AbstractZookeeperClient treeCacheMap = new ConcurrentHashMap<>(); + private static final List acls = new ArrayList<>(); + + private static final String scheme = "digest"; + private static final String aclUserName = System.getProperty("acl.username"); + private static final String aclPassword = System.getProperty("acl.password"); + public CuratorZookeeperClient(URL url) { super(url); @@ -64,6 +76,14 @@ public CuratorZookeeperClient(URL url) { .retryPolicy(new RetryNTimes(1, 1000)) .connectionTimeoutMs(timeout); + if (StringUtils.isNotEmpty(aclUserName) && StringUtils.isNotEmpty(aclPassword) ) { + builder.authorization(scheme, (aclUserName + ":" + aclPassword).getBytes()); + + Id allow = new Id(scheme, DigestAuthenticationProvider.generateDigest(aclUserName + ":" + aclPassword)); + // add more + acls.add(new ACL(ZooDefs.Perms.ALL, allow)); + } + client = builder.build(); client.getConnectionStateListenable().addListener(new ConnectionStateListener() { @Override @@ -91,7 +111,11 @@ public void createPersistent(String path) { try { logger.info("createPersistent {}", path); - client.create().forPath(path); + if (acls.size() > 0) { + client.create().withACL(acls).forPath(path); + } else { + client.create().forPath(path); + } } catch (NodeExistsException e) { } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); @@ -102,7 +126,11 @@ public void createPersistent(String path) { public void createEphemeral(String path) { try { logger.info("createEphemeral {}", path); - client.create().withMode(CreateMode.EPHEMERAL).forPath(path); + if (acls.size() > 0) { + client.create().withMode(CreateMode.EPHEMERAL).withACL(acls).forPath(path); + } else { + client.create().withMode(CreateMode.EPHEMERAL).forPath(path); + } } catch (NodeExistsException e) { } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); @@ -114,7 +142,11 @@ protected void createPersistent(String path, String data) { byte[] dataBytes = data.getBytes(CHARSET); try { logger.info("createPersistent {} data {}", path, data); - client.create().forPath(path, dataBytes); + if (acls.size() > 0) { + client.create().withACL(acls).forPath(path, dataBytes); + } else { + client.create().forPath(path, dataBytes); + } } catch (NodeExistsException e) { try { client.setData().forPath(path, dataBytes); @@ -131,7 +163,11 @@ protected void createEphemeral(String path, String data) { byte[] dataBytes = data.getBytes(CHARSET); try { logger.info("createEphemeral {} data {}", path, data); - client.create().withMode(CreateMode.EPHEMERAL).forPath(path, dataBytes); + if (acls.size() > 0) { + client.create().withMode(CreateMode.EPHEMERAL).withACL(acls).forPath(path, dataBytes); + } else { + client.create().withMode(CreateMode.EPHEMERAL).forPath(path, dataBytes); + } } catch (NodeExistsException e) { try { client.setData().forPath(path, dataBytes); @@ -146,7 +182,12 @@ protected void createEphemeral(String path, String data) { @Override public void delete(String path) { try { - client.delete().forPath(path); + if (acls.size() > 0) { + Stat stat = client.checkExists().forPath(path); + client.delete().withVersion(stat.getAversion()).forPath(path); + } else { + client.delete().forPath(path); + } } catch (NoNodeException e) { } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); diff --git a/router/src/main/java/com/webank/ai/fate/networking/Proxy.java b/router/src/main/java/com/webank/ai/fate/networking/Proxy.java index 3e90c5fa..10843698 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/Proxy.java +++ b/router/src/main/java/com/webank/ai/fate/networking/Proxy.java @@ -29,6 +29,8 @@ import com.webank.ai.fate.register.router.DefaultRouterService; import com.webank.ai.fate.register.url.URL; import com.webank.ai.fate.register.zookeeper.ZookeeperRegistry; +import com.webank.ai.fate.serving.core.bean.Configuration; +import com.webank.ai.fate.serving.core.bean.Dict; import io.grpc.Server; import org.apache.commons.cli.*; import org.apache.logging.log4j.LogManager; @@ -77,6 +79,10 @@ public static void main(String[] args) throws Exception { LOGGER.info("server conf: {}", serverConf); server.start(); Properties properties = serverConf.getProperties(); + + System.setProperty(Dict.ACL_USERNAME, properties.getProperty(Dict.ACL_USERNAME)); + System.setProperty(Dict.ACL_PASSWORD, properties.getProperty(Dict.ACL_PASSWORD)); + useRegister = Boolean.valueOf(properties.getProperty("useRegister", "false")); useZkRouter = Boolean.valueOf(properties.getProperty("useZkRouter", "false")); if (useRegister) { diff --git a/router/src/main/resources/proxy.properties b/router/src/main/resources/proxy.properties index c568d065..8703a5da 100644 --- a/router/src/main/resources/proxy.properties +++ b/router/src/main/resources/proxy.properties @@ -26,3 +26,6 @@ root.crt= useJMX=false #jmx.server.name=JMXServer auth.config.path=/networking/proxy/src/main/resources/auth_config.json + +acl.username=fate +acl.password=fate \ No newline at end of file diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index fc65414c..39f76002 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -68,6 +68,8 @@ public ServingServer(String confPath) { new Configuration(confPath).load(); new com.webank.ai.eggroll.core.utils.Configuration(confPath).load(); + System.setProperty(Dict.ACL_USERNAME, Configuration.getProperty(Dict.ACL_USERNAME)); + System.setProperty(Dict.ACL_PASSWORD, Configuration.getProperty(Dict.ACL_PASSWORD)); } public static void main(String[] args) { diff --git a/serving-server/src/main/resources/serving-server.properties b/serving-server/src/main/resources/serving-server.properties index d0ad9884..481db333 100644 --- a/serving-server/src/main/resources/serving-server.properties +++ b/serving-server/src/main/resources/serving-server.properties @@ -59,3 +59,5 @@ useRegister=true useZkRouter=true useJMX=false #jmx.server.name=JMXServer +acl.username=fate +acl.password=fate \ No newline at end of file From acb31a1272a0979ee35fc7d4e18ed29fb0a274d1 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Thu, 5 Dec 2019 22:08:11 +0800 Subject: [PATCH 025/190] update Signed-off-by: v_dylanxu <136539068@qq.com> --- .../register/common/CuratorZookeeperClient.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java b/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java index bf0e9446..d4c32fef 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java @@ -149,7 +149,12 @@ protected void createPersistent(String path, String data) { } } catch (NodeExistsException e) { try { - client.setData().forPath(path, dataBytes); + if (acls.size() > 0) { + Stat stat = client.checkExists().forPath(path); + client.setData().withVersion(stat.getAversion()).forPath(path, dataBytes); + } else { + client.setData().forPath(path, dataBytes); + } } catch (Exception e1) { throw new IllegalStateException(e.getMessage(), e1); } @@ -170,7 +175,12 @@ protected void createEphemeral(String path, String data) { } } catch (NodeExistsException e) { try { - client.setData().forPath(path, dataBytes); + if (acls.size() > 0) { + Stat stat = client.checkExists().forPath(path); + client.setData().withVersion(stat.getAversion()).forPath(path, dataBytes); + } else { + client.setData().forPath(path, dataBytes); + } } catch (Exception e1) { throw new IllegalStateException(e.getMessage(), e1); } From 4defc65eb0fe62b4b751dc3787e6631bfbbfbdb9 Mon Sep 17 00:00:00 2001 From: kaideng Date: Thu, 5 Dec 2019 22:20:13 +0800 Subject: [PATCH 026/190] change modelload Signed-off-by: kaideng --- .../Maven__com_alibaba_fastjson_1_2_25.xml | 13 +++++++ ...ackson_core_jackson_annotations_2_10_0.xml | 13 +++++++ ...erxml_jackson_core_jackson_core_2_10_0.xml | 13 +++++++ ...l_jackson_core_jackson_databind_2_10_0.xml | 13 +++++++ .../serving/manger/DefaultModelManager.java | 25 ++++++++------ .../ai/fate/serving/service/ModelService.java | 34 +++++++++++++------ 6 files changed, 89 insertions(+), 22 deletions(-) create mode 100644 .idea/libraries/Maven__com_alibaba_fastjson_1_2_25.xml create mode 100644 .idea/libraries/Maven__com_fasterxml_jackson_core_jackson_annotations_2_10_0.xml create mode 100644 .idea/libraries/Maven__com_fasterxml_jackson_core_jackson_core_2_10_0.xml create mode 100644 .idea/libraries/Maven__com_fasterxml_jackson_core_jackson_databind_2_10_0.xml diff --git a/.idea/libraries/Maven__com_alibaba_fastjson_1_2_25.xml b/.idea/libraries/Maven__com_alibaba_fastjson_1_2_25.xml new file mode 100644 index 00000000..88d06a73 --- /dev/null +++ b/.idea/libraries/Maven__com_alibaba_fastjson_1_2_25.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_annotations_2_10_0.xml b/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_annotations_2_10_0.xml new file mode 100644 index 00000000..c7085107 --- /dev/null +++ b/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_annotations_2_10_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_core_2_10_0.xml b/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_core_2_10_0.xml new file mode 100644 index 00000000..ba1879ab --- /dev/null +++ b/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_core_2_10_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_databind_2_10_0.xml b/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_databind_2_10_0.xml new file mode 100644 index 00000000..12966549 --- /dev/null +++ b/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_databind_2_10_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java index 9998931b..c88277cd 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java @@ -85,12 +85,7 @@ public DefaultModelManager() { } - public static void main(String[] args) { - URL serviceUrl = URL.valueOf("grpc://" + "127.0.0.1" + ":" + 1235 + Constants.PATH_SEPARATOR + "kkkkk"); - - - } @Override public ReturnResult publishLoadModel(Context context,FederatedParty federatedParty, FederatedRoles federatedRoles, Map> federatedRolesModel) { @@ -98,7 +93,7 @@ public ReturnResult publishLoadModel(Context context,FederatedParty federatedPar String partyId = federatedParty.getPartyId(); String serviceId = null; if(context.getData(Dict.SERVICE_ID)!=null) { - serviceId = context.getData(Dict.SERVICE_ID).toString(); + serviceId = context.getData(Dict.SERVICE_ID).toString(); } ReturnResult returnResult = new ReturnResult(); @@ -134,10 +129,18 @@ public ReturnResult publishLoadModel(Context context,FederatedParty federatedPar if(StringUtils.isNotEmpty(serviceId)){ zookeeperRegistry.addDynamicEnvironment(serviceId); } - String key = ModelUtils.genModelKey(modelInfo.getName(), modelInfo.getNamespace()); - String keyMd5 = EncryptUtils.encrypt(key,EncryptMethod.MD5); - zookeeperRegistry.addDynamicEnvironment(keyMd5); - zookeeperRegistry.register(FateServer.serviceSets); + partnerModelData.forEach((key,v)->{ + + + String keyMd5 = EncryptUtils.encrypt(key,EncryptMethod.MD5); + logger.info("transform key {} to md5key {}",key,keyMd5); + zookeeperRegistry.addDynamicEnvironment(keyMd5); + zookeeperRegistry.register(FateServer.serviceSets); + + }); + + + } } @@ -157,7 +160,7 @@ public ReturnResult publishOnlineModel(Context context,FederatedParty federatedP String partyId = federatedParty.getPartyId(); String serviceId = null; - if(context.getData(Dict.SERVICE_ID)!=null) { + if(context.getData(Dict.SERVICE_ID)!=null&&StringUtils.isNotEmpty(context.getData(Dict.SERVICE_ID).toString().trim())) { serviceId = context.getData(Dict.SERVICE_ID).toString(); }else{ logger.info("service id is null"); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java index 66a3e454..6a3b82df 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java @@ -16,8 +16,9 @@ package com.webank.ai.fate.serving.service; -import ch.ethz.ssh2.crypto.Base64; + import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; import com.webank.ai.eggroll.core.utils.ObjectTransform; import com.webank.ai.fate.api.mlmodel.manager.ModelServiceGrpc; import com.webank.ai.fate.api.mlmodel.manager.ModelServiceProto.PublishRequest; @@ -35,7 +36,7 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; - +import java.util.Base64; import java.io.*; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; @@ -50,6 +51,8 @@ public class ModelService extends ModelServiceGrpc.ModelServiceImplBase implemen private static final Logger logger = LogManager.getLogger(); @Autowired ModelManager modelManager; + Base64.Encoder encoder = Base64.getEncoder(); + Base64.Decoder decoder = Base64.getDecoder(); LinkedHashMap publishLoadReqMap = new LinkedHashMap(); LinkedHashMap publicOnlineReqMap = new LinkedHashMap(); @@ -109,7 +112,7 @@ public synchronized void publishLoad(PublishRequest req, StreamObserver { + properties.forEach((k, v) -> { try { String content = k + "=" + v; - //logger.info("write content {}", content); + bufferedWriter.write(content); bufferedWriter.newLine(); } catch (IOException e) { @@ -320,13 +330,15 @@ private void loadProperties(File file, Map properties) { @Override public void afterPropertiesSet() throws Exception { + + loadProperties(publishLoadStoreFile, publishLoadReqMap); loadProperties(publishOnlineStoreFile, publicOnlineReqMap); publishLoadReqMap.forEach((k, v) -> { try { - byte[] data = Base64.decode(v.toString().toCharArray()); + byte[] data = decoder.decode(v.getBytes()); PublishRequest req = PublishRequest.parseFrom(data); - logger.info("resotre publishLoadModel req {}", req); + logger.info("restore publishLoadModel req {}", req); Context context = new BaseContext(); context.putData(Dict.SERVICE_ID,req.getServiceId()); modelManager.publishLoadModel(context, @@ -340,10 +352,10 @@ public void afterPropertiesSet() throws Exception { }); publicOnlineReqMap.forEach((k, v) -> { try { - byte[] data = Base64.decode(v.toString().toCharArray()); + byte[] data = decoder.decode(v.getBytes()); PublishRequest req = PublishRequest.parseFrom(data); - logger.info("resotre publishOnlineModel req {}", req); + logger.info("restore publishOnlineModel req {} base64 {}", req,v); Context context = new BaseContext(); context.putData(Dict.SERVICE_ID,req.getServiceId()); modelManager.publishOnlineModel(context, From bd2662a47b154b0680ecbc32aff030df4394f5e0 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Mon, 9 Dec 2019 18:24:22 +0800 Subject: [PATCH 027/190] change auth file config Signed-off-by: v_dylanxu <136539068@qq.com> --- .../webank/ai/fate/serving/core/bean/Dict.java | 1 + .../fate/networking/proxy/util/AuthUtils.java | 18 ++++++++++++++---- router/src/main/resources/proxy.properties | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java index b3bfe858..41b30780 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java @@ -136,6 +136,7 @@ public class Dict { public static final String ACL_USERNAME = "acl.username"; public static final String ACL_PASSWORD = "acl.password"; + public static final String AUTH_FILE = "authFile"; } diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java index 8c09f91d..04dacbe1 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java @@ -22,6 +22,8 @@ import com.google.gson.JsonParser; import com.google.gson.stream.JsonReader; import com.webank.ai.fate.api.networking.proxy.Proxy; +import com.webank.ai.fate.networking.proxy.manager.ServerConfManager; +import com.webank.ai.fate.serving.core.bean.Dict; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -36,9 +38,9 @@ import java.util.*; @Component -public class AuthUtils implements InitializingBean{ +public class AuthUtils { private static final Logger LOGGER = LogManager.getLogger(); - private static final String confFilePath = System.getProperty("authFile"); + private static String confFilePath = System.getProperty(Dict.AUTH_FILE); private static Map KEY_SECRET_MAP = new HashMap<>(); private static Map PARTYID_KEY_MAP = new HashMap<>(); private static int validRequestTimeoutSecond = 10; @@ -48,11 +50,19 @@ public class AuthUtils implements InitializingBean{ @Autowired private ToStringUtils toStringUtils; + @Autowired + private ServerConfManager serverConfManager; + @Scheduled(fixedRate = 10000) public void loadConfig(){ JsonParser jsonParser = new JsonParser(); JsonReader jsonReader = null; JsonObject jsonObject = null; + + if (StringUtils.isEmpty(confFilePath)) { + confFilePath = serverConfManager.getServerConf().getData(Dict.AUTH_FILE, ""); + } + try { jsonReader = new JsonReader(new FileReader(confFilePath)); jsonObject = jsonParser.parse(jsonReader).getAsJsonObject(); @@ -149,7 +159,7 @@ public boolean checkAuthentication(Proxy.Packet packet) throws Exception { return true; } - @Override + /*@Override public void afterPropertiesSet() throws Exception { try { @@ -158,5 +168,5 @@ public void afterPropertiesSet() throws Exception { LOGGER.error("load authencation keys error", e); } - } + }*/ } diff --git a/router/src/main/resources/proxy.properties b/router/src/main/resources/proxy.properties index 8703a5da..c077899b 100644 --- a/router/src/main/resources/proxy.properties +++ b/router/src/main/resources/proxy.properties @@ -25,7 +25,7 @@ route.table=/networking/proxy/src/main/resources/route_tables/route_table.json root.crt= useJMX=false #jmx.server.name=JMXServer -auth.config.path=/networking/proxy/src/main/resources/auth_config.json +authFile=/data/projects/fate/serving-router/conf/auth_config.json acl.username=fate acl.password=fate \ No newline at end of file From 6299bbbea3a416a8749eb9bc698348e8f93f3d80 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Tue, 10 Dec 2019 15:47:24 +0800 Subject: [PATCH 028/190] fix load model duplicate key Signed-off-by: v_dylanxu <136539068@qq.com> --- .../ai/fate/serving/core/bean/Dict.java | 2 ++ .../ai/fate/serving/service/ModelService.java | 19 ++++++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java index 41b30780..55425e4b 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java @@ -138,5 +138,7 @@ public class Dict { public static final String ACL_PASSWORD = "acl.password"; public static final String AUTH_FILE = "authFile"; + public static final String MD5_SALT = "$1$ML"; + } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java index 6a3b82df..11ddc30d 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java @@ -36,6 +36,8 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; + +import java.util.Arrays; import java.util.Base64; import java.io.*; import java.nio.channels.FileChannel; @@ -110,9 +112,7 @@ public synchronized void publishLoad(PublishRequest req, StreamObserver { From 950188e0bc58ac8e86feb5035a14fac57daa5774 Mon Sep 17 00:00:00 2001 From: kaideng Date: Wed, 11 Dec 2019 10:43:54 +0800 Subject: [PATCH 029/190] change register router interface Signed-off-by: kaideng --- .../common/AbstractRegistryFactory.java | 2 +- .../router/AbstractRouterService.java | 35 ++++++++++--------- .../register/router/DefaultRouterService.java | 17 ++------- .../fate/register/router/RouterService.java | 3 ++ 4 files changed, 25 insertions(+), 32 deletions(-) diff --git a/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistryFactory.java b/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistryFactory.java index 39384f54..cfaffc7f 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistryFactory.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistryFactory.java @@ -53,7 +53,7 @@ public static Collection getRegistries() { /** * Close all created registries */ - // TODO: 2017/8/30 to move somewhere else better + public static void destroyAll() { if (LOGGER.isInfoEnabled()) { LOGGER.info("Close all registries " + getRegistries()); diff --git a/register/src/main/java/com/webank/ai/fate/register/router/AbstractRouterService.java b/register/src/main/java/com/webank/ai/fate/register/router/AbstractRouterService.java index b718e468..6120de05 100644 --- a/register/src/main/java/com/webank/ai/fate/register/router/AbstractRouterService.java +++ b/register/src/main/java/com/webank/ai/fate/register/router/AbstractRouterService.java @@ -16,6 +16,7 @@ package com.webank.ai.fate.register.router; +import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.webank.ai.fate.register.common.AbstractRegistry; import com.webank.ai.fate.register.common.Constants; @@ -36,10 +37,9 @@ public abstract class AbstractRouterService implements RouterService { - protected LoadBalancerFactory loadBalancerFactory = new DefaultLoadBalancerFactory(); protected AbstractRegistry registry; - protected LoadBalancer loadBalancer; + Logger logger = LogManager.getLogger(); public Registry getRegistry() { @@ -50,23 +50,28 @@ public void setRegistry(AbstractRegistry registry) { this.registry = registry; } - public LoadBalancer getLoadBalancer() { - return loadBalancer; - } - - public void setLoadBalancer(LoadBalancer loadBalancer) { - this.loadBalancer = loadBalancer; - } @Override public List router(URL url, LoadBalanceModel loadBalanceModel) { - this.loadBalancer = loadBalancerFactory.getLoaderBalancer(loadBalanceModel); - - return doRouter(url, loadBalanceModel); + LoadBalancer loadBalancer = loadBalancerFactory.getLoaderBalancer(loadBalanceModel); + return doRouter(url, loadBalancer); } + @Override + public List router(String project,String environment ,String serviceName){ + + Preconditions.checkArgument(StringUtils.isNotEmpty(project)); + Preconditions.checkArgument(StringUtils.isNotEmpty(environment)); + Preconditions.checkArgument(StringUtils.isNotEmpty(serviceName)); + LoadBalancer loadBalancer = loadBalancerFactory.getLoaderBalancer(LoadBalanceModel.random_with_weight); + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(project).append("/").append(environment).append("/").append(serviceName); + URL paramUrl = URL.valueOf(stringBuilder.toString()); + return doRouter(paramUrl, loadBalancer); + } + - public abstract List doRouter(URL url, LoadBalanceModel loadBalanceModel); + public abstract List doRouter(URL url, LoadBalancer loadBalancer); @Override @@ -75,13 +80,9 @@ public List router(URL url) { } protected List filterVersion(List urls, String version) { - if(StringUtils.isEmpty(version)){ - return urls; - } - final List resultUrls = Lists.newArrayList(); if (CollectionUtils.isNotEmpty(urls)) { diff --git a/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java b/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java index 9d8ad49d..f19c81e1 100644 --- a/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java +++ b/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java @@ -18,6 +18,7 @@ import com.webank.ai.fate.register.common.Constants; import com.webank.ai.fate.register.loadbalance.LoadBalanceModel; +import com.webank.ai.fate.register.loadbalance.LoadBalancer; import com.webank.ai.fate.register.url.CollectionUtils; import com.webank.ai.fate.register.url.URL; import org.apache.commons.lang3.StringUtils; @@ -28,36 +29,24 @@ public class DefaultRouterService extends AbstractRouterService { @Override - public List doRouter(URL url, LoadBalanceModel loadBalanceModel) { - + public List doRouter(URL url ,LoadBalancer loadBalancer) { List urls = registry.getCacheUrls(url); - if (CollectionUtils.isEmpty(urls)) { return null; } - urls = filterEmpty(urls); - String version = url.getParameter(Constants.VERSION_KEY); if (CollectionUtils.isNotEmpty(urls) && StringUtils.isNotBlank(version)) { urls = filterVersion(urls, version); } - - List resultUrls = this.loadBalancer.select(urls); - + List resultUrls = loadBalancer.select(urls); logger.info("router service return urls {}", resultUrls); - return resultUrls; - - } private List filterEmpty(List urls) { - - List resultList = new ArrayList<>(); if(urls!=null) { - urls.forEach(url -> { if (!url.getProtocol().equalsIgnoreCase(Constants.EMPTY_PROTOCOL)) { resultList.add(url); diff --git a/register/src/main/java/com/webank/ai/fate/register/router/RouterService.java b/register/src/main/java/com/webank/ai/fate/register/router/RouterService.java index ab06109c..91f7b9fb 100644 --- a/register/src/main/java/com/webank/ai/fate/register/router/RouterService.java +++ b/register/src/main/java/com/webank/ai/fate/register/router/RouterService.java @@ -28,5 +28,8 @@ public interface RouterService { List router(URL url); + List router(String project,String environment ,String serviceName); + + } From ba9d6eff6fd3a2df7df1f489b6f4e95c9fefa211 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Wed, 11 Dec 2019 17:19:06 +0800 Subject: [PATCH 030/190] update Signed-off-by: v_dylanxu <136539068@qq.com> --- .../src/main/java/com/webank/ai/fate/serving/ServingServer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index 9a80d794..dded3c51 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -140,6 +140,7 @@ private void start(String[] args) throws IOException { } }); + zookeeperRegistry.register(FateServer.serviceSets); } boolean useJMX = Boolean.valueOf(Configuration.getProperty(Dict.USE_JMX)); From d1d458e3ed967e55e89e93bffc2af22e86e94eb3 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Wed, 11 Dec 2019 18:36:48 +0800 Subject: [PATCH 031/190] add root acl Signed-off-by: v_dylanxu <136539068@qq.com> --- .../webank/ai/fate/register/common/CuratorZookeeperClient.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java b/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java index d4c32fef..3549bf2e 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java @@ -101,6 +101,8 @@ public void stateChanged(CuratorFramework client, ConnectionState state) { } }); client.start(); + + client.setACL().withACL(acls).forPath("/"); } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); } From b694b2766b243758cf598c0772951d75564d7fee Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Wed, 11 Dec 2019 18:57:59 +0800 Subject: [PATCH 032/190] ok Signed-off-by: v_dylanxu <136539068@qq.com> --- router/bin/service.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/bin/service.sh b/router/bin/service.sh index f9eb181a..7e5a16df 100644 --- a/router/bin/service.sh +++ b/router/bin/service.sh @@ -25,7 +25,7 @@ module=serving-router main_class=com.webank.ai.fate.networking.Proxy getpid() { - pid=`ps aux | grep ${main_class} | grep -v grep | awk '{print $2}'` + pid=`ps aux | grep ${module} | grep -v grep | awk '{print $2}'` if [[ -n ${pid} ]]; then return 1 From 457755f110602db92dd4d7409f7673aa8caf7151 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Thu, 12 Dec 2019 09:11:45 +0800 Subject: [PATCH 033/190] ok Signed-off-by: v_dylanxu <136539068@qq.com> --- serving-server/src/main/resources/serving-server.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/serving-server/src/main/resources/serving-server.properties b/serving-server/src/main/resources/serving-server.properties index 50ace8d1..3c2fdbcc 100644 --- a/serving-server/src/main/resources/serving-server.properties +++ b/serving-server/src/main/resources/serving-server.properties @@ -53,6 +53,7 @@ InferencePreProcessingAdapter=PassPreProcessing # external subsystem proxy=127.0.0.1:9370 roll=127.0.0.1:8011 +model.transfer.url= #zk.url=zookeeper://localhost:2181?backup=localhost:2182,localhost:2183 zk.url=zookeeper://localhost:2181 useRegister=true From f6e470fd35b4b0668ba1427ffb6d6efd95a66234 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Thu, 12 Dec 2019 18:24:47 +0800 Subject: [PATCH 034/190] ok Signed-off-by: v_dylanxu <136539068@qq.com> --- .../register/router/DefaultRouterService.java | 38 ++++++------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java b/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java index cd5be491..f7a4c5dd 100644 --- a/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java +++ b/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java @@ -20,7 +20,7 @@ import com.webank.ai.fate.register.loadbalance.LoadBalanceModel; import com.webank.ai.fate.register.url.CollectionUtils; import com.webank.ai.fate.register.url.URL; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.List; @@ -29,43 +29,29 @@ public class DefaultRouterService extends AbstractRouterService { @Override public List doRouter(URL url, LoadBalanceModel loadBalanceModel) { - List urls = registry.getCacheUrls(url); - + if (CollectionUtils.isEmpty(urls)) { + return null; + } urls = filterEmpty(urls); - String version = url.getParameter(Constants.VERSION_KEY); if (CollectionUtils.isNotEmpty(urls) && StringUtils.isNotBlank(version)) { urls = filterVersion(urls, version); } -// else{ -// AtomicReference> resultUrls = new AtomicReference<>(); -// registry.subscribe(url , resultUrls::set); -// urls =resultUrls.get(); -// urls= filterVersion(urls,version); -// } - - if (CollectionUtils.isEmpty(urls)) { - return null; - } - - List resultUrls = this.loadBalancer.select(urls); - + List resultUrls = loadBalancer.select(urls); logger.info("router service return urls {}", resultUrls); - return resultUrls; - - } private List filterEmpty(List urls) { List resultList = new ArrayList<>(); - - urls.forEach(url -> { - if (!url.getProtocol().equalsIgnoreCase(Constants.EMPTY_PROTOCOL)) { - resultList.add(url); - } - }); + if(urls!=null) { + urls.forEach(url -> { + if (!url.getProtocol().equalsIgnoreCase(Constants.EMPTY_PROTOCOL)) { + resultList.add(url); + } + }); + } return resultList; } From b1fc6ac9d43003a1bf5b3e0bbc2eec19b0015435 Mon Sep 17 00:00:00 2001 From: kaideng Date: Fri, 13 Dec 2019 17:44:05 +0800 Subject: [PATCH 035/190] fix bug Signed-off-by: kaideng --- .../fate/serving/federatedml/model/HeteroLRGuest.java | 11 +++++++---- .../fate/register/router/AbstractRouterService.java | 1 - .../java/com/webank/ai/fate/networking/Proxy.java | 5 ----- .../java/com/webank/ai/fate/serving/SpringConfig.java | 10 ++-------- 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java index e65eefe9..15cd96d5 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java @@ -21,6 +21,7 @@ import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.core.bean.FederatedParams; import com.webank.ai.fate.serving.core.bean.ReturnResult; +import com.webank.ai.fate.serving.core.constant.InferenceRetCode; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -42,9 +43,7 @@ public Map handlePredict(Context context, List result = new HashMap<>(); Map forwardRet = forward(inputData); double score = forwardRet.get(Dict.SCORE); - LOGGER.info("caseid {} guest score:{}", context.getCaseId(),score); - try { ReturnResult hostPredictResponse = this.getFederatedPredict(context, predictParams, Dict.FEDERATED_INFERENCE, true); if(hostPredictResponse !=null) { @@ -58,10 +57,14 @@ public Map handlePredict(Context context, List Date: Mon, 16 Dec 2019 16:52:53 +0800 Subject: [PATCH 036/190] change feature Data Signed-off-by: kaideng --- .../fate/serving/guest/DefaultGuestInferenceProvider.java | 1 + .../webank/ai/fate/serving/service/InferenceService.java | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java index 9922a247..d68a3874 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java @@ -98,6 +98,7 @@ public ReturnResult runInference(Context context, InferenceRequest inferenceRequ inferenceResult.setRetcode(InferenceRetCode.LOAD_MODEL_FAILED + 1000); return inferenceResult; } + LOGGER.info("use model to inference for {}, id: {}, version: {}", inferenceRequest.getAppid(), modelNamespace, modelName); Map rawFeatureData = inferenceRequest.getFeatureData(); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java index cfde9b64..cd3b9f1b 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java @@ -34,6 +34,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.Map; + @Service public class InferenceService extends InferenceServiceGrpc.InferenceServiceImplBase { private static final Logger LOGGER = LogManager.getLogger(); @@ -78,6 +80,10 @@ private void inferenceServiceAction(InferenceMessage req, StreamObserver sendToRemoteFeatureData = inferenceRequest.getSendToRemoteFeatureData(); + if(sendToRemoteFeatureData!=null) { + inferenceRequest.getFeatureData().putAll(sendToRemoteFeatureData); + } context.setCaseId(inferenceRequest.getCaseid()); context.setActionType(actionType.name()); From 41e8d17642bfe99e68ceb536f9220144511ed81e Mon Sep 17 00:00:00 2001 From: kaideng Date: Tue, 17 Dec 2019 13:25:02 +0800 Subject: [PATCH 037/190] change featureids Signed-off-by: kaideng --- .../java/com/webank/ai/fate/serving/core/bean/Dict.java | 1 + .../webank/ai/fate/serving/core/bean/FederatedParams.java | 8 ++++---- .../fate/serving/guest/DefaultGuestInferenceProvider.java | 7 ++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java index 55425e4b..42c51901 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java @@ -137,6 +137,7 @@ public class Dict { public static final String ACL_USERNAME = "acl.username"; public static final String ACL_PASSWORD = "acl.password"; public static final String AUTH_FILE = "authFile"; + public static final String ENCRYPT_TYPE = "encrypt_type"; public static final String MD5_SALT = "$1$ML"; diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/FederatedParams.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/FederatedParams.java index 8c4db46d..1e1dbc7d 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/FederatedParams.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/FederatedParams.java @@ -40,7 +40,7 @@ public class FederatedParams { FederatedParty local; ModelInfo modelInfo; FederatedRoles role; - Map featureIdMap; + Map featureIdMap = Maps.newHashMap(); Map data = Maps.newHashMap(); public ModelInfo getModelInfo() { @@ -87,9 +87,9 @@ public Map getFeatureIdMap() { return featureIdMap; } - public void setFeatureIdMap(Map featureIdMap) { - this.featureIdMap = featureIdMap; - } +// public void setFeatureIdda(Map featureIdMap) { +// this.featureIdMap = featureIdMap; +// } public Map getData() { return data; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java index d68a3874..944796f7 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java @@ -130,15 +130,16 @@ public ReturnResult runInference(Context context, InferenceRequest inferenceRequ Map predictParams = new HashMap<>(); Map modelFeatureData = Maps.newHashMap(featureData); FederatedParams federatedParams = new FederatedParams(); - - federatedParams.setFeatureIdMap(inferenceRequest.getSendToRemoteFeatureData()); + if(inferenceRequest.getSendToRemoteFeatureData()!=null) { + federatedParams.getFeatureIdMap().putAll(inferenceRequest.getSendToRemoteFeatureData()); + } federatedParams.setCaseId(inferenceRequest.getCaseid()); federatedParams.setSeqNo(inferenceRequest.getSeqno()); federatedParams.setLocal(modelNamespaceData.getLocal()); federatedParams.setModelInfo(new ModelInfo(modelName, modelNamespace)); federatedParams.setRole(modelNamespaceData.getRole()); if(featureIds!=null&&featureIds.size()>0) { - federatedParams.setFeatureIdMap(featureIds); + federatedParams.getFeatureIdMap().putAll(featureIds); } Map modelResult = model.predict(context, modelFeatureData, federatedParams); PostProcessingResult postProcessingResult; From 7be56ffaa13e16944cf9151c060d0d576fafe47c Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Wed, 18 Dec 2019 11:31:43 +0800 Subject: [PATCH 038/190] add auto add ln Signed-off-by: v_dylanxu <136539068@qq.com> --- .../webank/ai/fate/serving/federatedml/model/BaseModel.java | 3 ++- router/bin/service.sh | 4 ++++ serving-server/bin/service.sh | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java index 75cc73a3..dccbc089 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java @@ -115,7 +115,8 @@ protected ReturnResult getFederatedPredict(Context context, FederatedParams gues HostFederatedParams hostFederatedParams = new HostFederatedParams(); hostFederatedParams.setCaseId(guestFederatedParams.getCaseId()); hostFederatedParams.setSeqNo(guestFederatedParams.getSeqNo()); - hostFederatedParams.setFeatureIdMap(guestFederatedParams.getFeatureIdMap()); +// hostFederatedParams.setFeatureIdMap(guestFederatedParams.getFeatureIdMap()); + hostFederatedParams.getFeatureIdMap().putAll(guestFederatedParams.getFeatureIdMap()); hostFederatedParams.setLocal(dstParty); hostFederatedParams.setPartnerLocal(srcParty); hostFederatedParams.setRole(federatedRoles); diff --git a/router/bin/service.sh b/router/bin/service.sh index 3901d3a7..26ee158b 100644 --- a/router/bin/service.sh +++ b/router/bin/service.sh @@ -23,6 +23,7 @@ configpath=$(cd $basepath/conf;pwd) module=serving-router main_class=com.webank.ai.fate.networking.Proxy +module_version=1.1 getpid() { pid=`ps aux | grep ${module} | grep -v grep | awk '{print $2}'` @@ -56,6 +57,9 @@ start() { getpid if [[ $? -eq 0 ]]; then mklogsdir + if [[ ! -e "fate-${module}.jar" ]]; then + ln -s fate-${module}-${module_version}.jar fate-${module}.jar + fi java -DauthFile=${configpath}/auth_config.json -Drouter_file=${configpath}/route_table.json -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/proxy.properties >> logs/console.log 2>>logs/error.log & if [[ $? -eq 0 ]]; then getpid diff --git a/serving-server/bin/service.sh b/serving-server/bin/service.sh index e0b063cb..90c922de 100644 --- a/serving-server/bin/service.sh +++ b/serving-server/bin/service.sh @@ -21,6 +21,7 @@ module=serving-server main_class=com.webank.ai.fate.serving.ServingServer +module_version=1.1 getpid() { pid=`ps aux | grep ${main_class} | grep -v grep | awk '{print $2}'` @@ -54,6 +55,9 @@ start() { getpid if [[ $? -eq 0 ]]; then mklogsdir + if [[ ! -e "fate-${module}.jar" ]]; then + ln -s fate-${module}-${module_version}.jar fate-${module}.jar + fi java -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/${module}.properties >> logs/console.log 2>>logs/error.log & if [[ $? -eq 0 ]]; then getpid From 41565edda178b77baf6ee65fdf65e8acc34ef05b Mon Sep 17 00:00:00 2001 From: utu Date: Wed, 18 Dec 2019 18:15:57 +0800 Subject: [PATCH 039/190] add new proxy. --- .gitignore | 33 ++ pom.xml | 1 + serving-proxy/bin/service.sh | 108 +++++ serving-proxy/package.xml | 62 +++ serving-proxy/pom.xml | 336 ++++++++++++++++ .../serving/proxy/bootstrap/Bootstrap.java | 60 +++ .../ai/fate/serving/proxy/common/Dict.java | 37 ++ .../proxy/common/ErrorMessageUtil.java | 97 +++++ .../serving/proxy/common/GetSystemInfo.java | 144 +++++++ .../common/GlobalResponseController.java | 52 +++ .../serving/proxy/common/ResponseResult.java | 65 +++ .../proxy/config/CharacterEncodingFilter.java | 34 ++ .../proxy/config/GrpcConfigration.java | 51 +++ .../serving/proxy/config/WebConfigration.java | 87 ++++ .../proxy/controller/ProxyController.java | 100 +++++ .../proxy/exceptions/BaseException.java | 16 + .../proxy/exceptions/CustomException.java | 26 ++ .../serving/proxy/exceptions/ErrorCode.java | 22 ++ .../proxy/exceptions/ErrorResponseEntity.java | 22 ++ .../exceptions/GlobalExceptionHandler.java | 35 ++ .../exceptions/InvalidRoleInfoException.java | 13 + .../proxy/exceptions/NoResultException.java | 8 + .../exceptions/NoRouteInfoException.java | 8 + .../proxy/exceptions/OverLoadException.java | 8 + .../exceptions/ShowDownRejectException.java | 8 + .../proxy/exceptions/SqlAttactException.java | 7 + .../proxy/exceptions/SysException.java | 24 ++ .../rpc/core/AbstractServiceAdaptor.java | 228 +++++++++++ .../fate/serving/proxy/rpc/core/Context.java | 139 +++++++ .../rpc/core/DefaultInterceptorChain.java | 59 +++ .../serving/proxy/rpc/core/FlowLogger.java | 144 +++++++ .../proxy/rpc/core/InboundPackage.java | 77 ++++ .../serving/proxy/rpc/core/Interceptor.java | 9 + .../proxy/rpc/core/InterceptorChain.java | 12 + .../proxy/rpc/core/OutboundPackage.java | 30 ++ .../proxy/rpc/core/OverLoadEndPoint.java | 36 ++ .../proxy/rpc/core/OverLoadLogger.java | 125 ++++++ .../serving/proxy/rpc/core/ProxyService.java | 21 + .../proxy/rpc/core/ProxyServiceRegister.java | 88 +++++ .../proxy/rpc/core/ServiceAdaptor.java | 11 + .../proxy/rpc/core/ServiceRegister.java | 12 + .../proxy/rpc/grpc/GrpcConnectionPool.java | 179 +++++++++ .../fate/serving/proxy/rpc/grpc/GrpcType.java | 11 + .../proxy/rpc/grpc/InterGrpcServer.java | 45 +++ .../proxy/rpc/grpc/InterRequestHandler.java | 28 ++ .../proxy/rpc/grpc/IntraGrpcServer.java | 46 +++ .../proxy/rpc/grpc/IntraRequestHandler.java | 30 ++ .../proxy/rpc/grpc/ProxyRequestHandler.java | 59 +++ .../rpc/grpc/ServiceExceptionHandler.java | 28 ++ .../proxy/rpc/router/BaseServingRouter.java | 67 ++++ .../router/ConfigFileBasedServingRouter.java | 372 ++++++++++++++++++ .../rpc/router/DefaultServingRouter.java | 44 +++ .../serving/proxy/rpc/router/RouteType.java | 11 + .../proxy/rpc/router/RouteTypeConvertor.java | 35 ++ .../serving/proxy/rpc/router/RouterInfo.java | 34 ++ .../proxy/rpc/router/RouterInterface.java | 10 + .../proxy/rpc/router/ZkServingRouter.java | 122 ++++++ .../proxy/rpc/services/InferenceService.java | 109 +++++ .../proxy/rpc/services/NotFoundService.java | 31 ++ .../proxy/rpc/services/UnaryCallService.java | 127 ++++++ .../serving/proxy/security/AuthUtils.java | 160 ++++++++ .../proxy/security/DefaultAuthentication.java | 44 +++ .../security/FederationParamValidator.java | 30 ++ .../security/InferenceParamValidator.java | 29 ++ .../proxy/security/OverloadMonitor.java | 42 ++ .../fate/serving/proxy/utils/EncryptUtil.java | 37 ++ .../proxy/utils/FederatedModelUtils.java | 32 ++ .../fate/serving/proxy/utils/FileUtils.java | 35 ++ .../serving/proxy/utils/ToStringUtils.java | 63 +++ .../ai/fate/serving/proxy/utils/WebUtil.java | 68 ++++ .../src/main/resources/application.properties | 32 ++ .../src/main/resources/auth_config.json | 15 + 72 files changed, 4430 insertions(+) create mode 100644 .gitignore create mode 100644 serving-proxy/bin/service.sh create mode 100644 serving-proxy/package.xml create mode 100644 serving-proxy/pom.xml create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/Dict.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ErrorMessageUtil.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/GetSystemInfo.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/GlobalResponseController.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ResponseResult.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/CharacterEncodingFilter.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/GrpcConfigration.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/BaseException.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/CustomException.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorCode.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorResponseEntity.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/GlobalExceptionHandler.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/InvalidRoleInfoException.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/NoResultException.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/NoRouteInfoException.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/OverLoadException.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ShowDownRejectException.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/SqlAttactException.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/SysException.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/Context.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/DefaultInterceptorChain.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/FlowLogger.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/InboundPackage.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/Interceptor.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/InterceptorChain.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OutboundPackage.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OverLoadEndPoint.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OverLoadLogger.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyService.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyServiceRegister.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ServiceAdaptor.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ServiceRegister.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcType.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterGrpcServer.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterRequestHandler.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraRequestHandler.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ServiceExceptionHandler.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/DefaultServingRouter.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteType.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteTypeConvertor.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouterInfo.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouterInterface.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/NotFoundService.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/DefaultAuthentication.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/FederationParamValidator.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/OverloadMonitor.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/EncryptUtil.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/FederatedModelUtils.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/FileUtils.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/ToStringUtils.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/WebUtil.java create mode 100644 serving-proxy/src/main/resources/application.properties create mode 100644 serving-proxy/src/main/resources/auth_config.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..0241e4ad --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# common file patterns +.DS_STORE +.idea +*.iml +*.pyc +__pycache__ +*.jar +*.class +.project +*.prefs + +# excluded paths +/arch/core/target/ +/arch/driver/federation/target/ +/arch/eggroll/egg/target/ +/arch/eggroll/meta-service/target/ +/arch/eggroll/roll/target/ +/arch/eggroll/storage-service/target/ +/arch/networking/proxy/target/ +/arch/driver/target/ +/arch/eggroll/target/ +/fate-serving/federatedml/target/ +/fate-serving/serving-server/target/ +/fate-serving/target/ +/fate-serving/fate-serving-core/target/ +/arch/networking/target/ +/arch/target/ +/fateboard/target/ +/data/ +/logs/ +/jobs/ +/audit/ +.vscode/* diff --git a/pom.xml b/pom.xml index aa0c1d75..98aa8dbe 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,7 @@ register router fate-jmx + serving-proxy diff --git a/serving-proxy/bin/service.sh b/serving-proxy/bin/service.sh new file mode 100644 index 00000000..3901d3a7 --- /dev/null +++ b/serving-proxy/bin/service.sh @@ -0,0 +1,108 @@ +#!/bin/bash + +# +# Copyright 2019 The FATE Authors. All Rights Reserved. +# +# 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. +# +basepath=$(cd `dirname $0`;pwd) +#export JAVA_HOME=/data/projects/common/jdk/jdk1.8.0_192 +#export PATH=$PATH:$JAVA_HOME/bin +configpath=$(cd $basepath/conf;pwd) + + +module=serving-router +main_class=com.webank.ai.fate.networking.Proxy + +getpid() { + pid=`ps aux | grep ${module} | grep -v grep | awk '{print $2}'` + + if [[ -n ${pid} ]]; then + return 1 + else + return 0 + fi +} + +mklogsdir() { + if [[ ! -d "logs" ]]; then + mkdir logs + fi +} + +status() { + getpid + if [[ -n ${pid} ]]; then + echo "status: + `ps aux | grep ${pid} | grep -v grep`" + exit 1 + else + echo "service not running" + exit 0 + fi +} + +start() { + getpid + if [[ $? -eq 0 ]]; then + mklogsdir + java -DauthFile=${configpath}/auth_config.json -Drouter_file=${configpath}/route_table.json -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/proxy.properties >> logs/console.log 2>>logs/error.log & + if [[ $? -eq 0 ]]; then + getpid + echo "service start sucessfully. pid: ${pid}" + else + echo "service start failed" + fi + else + echo "service already started. pid: ${pid}" + fi +} + +stop() { + getpid + if [[ -n ${pid} ]]; then + echo "killing: + `ps aux | grep ${pid} | grep -v grep`" + kill -9 ${pid} + if [[ $? -eq 0 ]]; then + echo "killed" + else + echo "kill error" + fi + else + echo "service not running" + fi +} + +case "$1" in + start) + start + status + ;; + + stop) + stop + ;; + status) + status + ;; + + restart) + stop + start + status + ;; + *) + echo "usage: $0 {start|stop|status|restart}" + exit -1 +esac \ No newline at end of file diff --git a/serving-proxy/package.xml b/serving-proxy/package.xml new file mode 100644 index 00000000..ca7a6cce --- /dev/null +++ b/serving-proxy/package.xml @@ -0,0 +1,62 @@ + + release + + + + zip + + + + + false + + + + / + target + + *.jar + + + + + + + /lib + target/lib + + *.jar + + + + + / + bin + + * + + + + + + /conf + src/main/resources + + proxy.properties + log4j2.properties + applicationContext-proxy.xml + auth_config.json + + + + + /conf + src/main/resources/route_tables + + route_table.json + + + + + \ No newline at end of file diff --git a/serving-proxy/pom.xml b/serving-proxy/pom.xml new file mode 100644 index 00000000..7d770c1a --- /dev/null +++ b/serving-proxy/pom.xml @@ -0,0 +1,336 @@ + + + + fate-serving-proxy + + + fate-serving + com.webank.ai.fate + ${fate.version} + + + ${fate.version} + jar + 4.0.0 + + fate-serving-proxy + + + 2.2.0.RELEASE + UTF-8 + UTF-8 + 1.7 + 1.4.0 + + 3.1.1 + 3.1.0 + 2.0.1.RELEASE + 1.6.1 + yyMMddHHmm + + + + + com.webank.ai.fate + fate-register + ${fate.version} + + + + org.apache.zookeeper + zookeeper + 3.5.5 + + + slf4j-log4j12 + org.slf4j + + + + + + com.googlecode.json-simple + json-simple + 1.1 + + + + org.springframework.boot + spring-boot-starter-webflux + ${spring.boot.version} + + + + org.jolokia + jolokia-core + 1.6.1 + + + org.apache.commons + commons-lang3 + 3.9 + + + + + + commons-codec + commons-codec + 1.12 + + + + + io.grpc + grpc-core + 1.25.0 + + + io.grpc + grpc-netty-shaded + 1.25.0 + + + + + com.alibaba + fastjson + 1.2.58 + + + + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} + + + + + org.springframework.boot + spring-boot-starter-actuator + ${spring.boot.version} + + + + mysql + mysql-connector-java + runtime + 8.0.16 + + + + org.springframework.boot + spring-boot-starter-test + test + ${spring.boot.version} + + + + com.google.guava + guava + 27.1-jre + + + + + org.aspectj + aspectjrt + 1.9.1 + + + + org.aspectj + aspectjweaver + 1.9.1 + + + cglib + cglib + 2.2.2 + + + + io.grpc + grpc-stub + 1.25.0 + + + + + org.apache.curator + curator-framework + 2.13.0 + + + + org.apache.curator + curator-recipes + 2.13.0 + + + + + + + org.apache.httpcomponents + httpclient + 4.5.1 + + + + org.apache.commons + commons-pool2 + 2.6.2 + + + org.apache.logging.log4j + log4j-slf4j-impl + + + + + + com.alibaba.csp + sentinel-core + 1.6.3 + + + + com.alibaba.csp + sentinel-datasource-extension + 1.6.3 + + + + com.alibaba.csp + sentinel-transport-simple-http + 1.6.3 + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + 2.1.0.RELEASE + + + com.webank.ai.fate + fate-serving-core + 1.1 + compile + + + + + + + + + ${project.basedir}/lib + BOOT-INF/lib/ + + **/*.jar + + + + src/main/resources + BOOT-INF/classes/ + + + + + + kr.motd.maven + os-maven-plugin + 1.5.0.Final + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + ${protobuf-maven-plugin.version} + + com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} + + grpc-java + io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} + + ${basedir}/../../proto + + + + + compile + compile-custom + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + true + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.10 + + + copy-dependencies + package + + copy-dependencies + + + ${project.build.directory}/lib + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.2.1 + + + make-assembly + package + + single + + + + package.xml + + + + + + + + + + \ No newline at end of file diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java new file mode 100644 index 00000000..fbcebe5d --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java @@ -0,0 +1,60 @@ +package com.webank.ai.fate.serving.proxy.bootstrap; + +import com.webank.ai.fate.serving.proxy.rpc.core.AbstractServiceAdaptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ImportResource; +import org.springframework.context.annotation.PropertySource; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * @Description TODO + * @Author + **/ +@SpringBootApplication +@ComponentScan(basePackages = {"com.webank.ai.fate.serving.proxy.*"}) +@PropertySource("classpath:application.properties") +@EnableScheduling +public class Bootstrap { + + + static Logger logger = LoggerFactory.getLogger(Bootstrap.class); + + public static void main(String[] args) { + + SpringApplication.run(Bootstrap.class, args); + + + Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { + + + + @Override + public void run() { + + logger.info("try to shutdown server ==============!!!!!!!!!!!!!!!!!!!!!"); + AbstractServiceAdaptor.isOpen=false; + int tryNum= 0; + /** + * 3秒 + */ + while(AbstractServiceAdaptor.requestInHandle.get()>0&&tryNum<30){ + + logger.info("try to shundown,try count {}, remain {}",tryNum,AbstractServiceAdaptor.requestInHandle.get()); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + }) + + + ); + } + +} \ No newline at end of file diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/Dict.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/Dict.java new file mode 100644 index 00000000..6444317c --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/Dict.java @@ -0,0 +1,37 @@ +package com.webank.ai.fate.serving.proxy.common; + +/** + * @Description TODO + * @Author + **/ +public class Dict { + public static final String APPID = "APP_ID"; + public static final String TIMESTAMP = "TIMESTAMP"; + public static final String SIGNATURE = "SIGNATURE"; + public static final String SERVICE_ID = "serviceId"; + public static final String PARTY_ID ="PARTY_ID"; + public static final String TRACE_ID ="TRACE_ID"; + public static final String NONCE ="NONCE"; + public static final String APP_KEY ="APP_KEY"; + public static final String HTTP_URI="HTTP_URI"; + public static final String CASE_ID="caseid"; + public static final String APP_ID="app_id"; + public static final String CONTENT_TYPE = "Content-Type"; + public static final String CONTENT_TYPE_JSON_UTF8 = "application/json;charset=UTF-8"; + public static final String CHARSET_UTF8 = "UTF-8"; + public static final String HTTP = "http"; + public static final String HTTPS = "https"; + public static final String ACCESS_TOKEN = "ACCESS_TOKEN"; + public static final String CODE ="code"; + public static final String DATA ="data"; + public static final String MESSAGE ="message"; + public static final String MODEL_ID = "modelid"; + public static final String MODEL_VERSION = "modelversion"; + public static final String SEND_TO_REMOTE_FEATURE_DATA="sendToRemoteFeatureData"; + public static final String FEATURE_DATA = "featureData"; + public static final String PARTNER_PARTY_NAME = "partnerPartyName"; + public static final String PARTY_NAME = "partyName"; + public static final String DEFAULT_VERSION = "1.0"; + public static final String SELF_PROJECT_NAME = "proxy"; + public static final String SELF_ENVIRONMENT = "online"; +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ErrorMessageUtil.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ErrorMessageUtil.java new file mode 100644 index 00000000..4d167435 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ErrorMessageUtil.java @@ -0,0 +1,97 @@ +package com.webank.ai.fate.serving.proxy.common; + +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.webank.ai.fate.serving.proxy.exceptions.*; +import com.webank.ai.fate.serving.proxy.exceptions.ErrorCode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +import static com.webank.ai.fate.serving.proxy.common.Dict.CODE; +import static com.webank.ai.fate.serving.proxy.common.Dict.MESSAGE; + +/** + * @Description TODO + * @Author + **/ +public class ErrorMessageUtil { + + static Logger logger = LoggerFactory.getLogger(ErrorMessageUtil.class); + + public static Map handleException(Map result,Throwable e){ + + if (e instanceof IllegalArgumentException) { + /** + * 参数错误 + * + */ + result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.PARAM_ERROR); + result.put(MESSAGE,"PARAM_ERROR"); + }else if ( e instanceof SqlAttactException) + { + result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.PARAM_ERROR); + result.put(MESSAGE,"SqlAttactException"); + } + + else if(e instanceof NoRouteInfoException){ + + /** + * 无路由信息 + */ + + result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.ROUTER_ERROR); + result.put(MESSAGE, "ROUTER_ERROR"); + + + } else if (e instanceof SysException) { + /** + * 系统错误 + */ + result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.SYSTEM_ERROR); + result.put(MESSAGE, "SYSTEM_ERROR"); + + + } else if (e instanceof BlockException) { + /** + * 系统限流 + */ + result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.LIMIT_ERROR); + + result.put(MESSAGE, "OVERLOAD"); + + } else if (e instanceof InvalidRoleInfoException) { + /** + * 鉴权错误 + */ + result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.ROLE_ERROR); + result.put(MESSAGE, "ROLE_ERROR"); + } else if (e instanceof ShowDownRejectException){ + + /** + * 关闭拒绝 + */ + result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.SHUTDOWN_ERROR); + result.put(MESSAGE, "SHUTDOWN_ERROR"); + + } + else if (e instanceof NoResultException) { + logger.error("NET_ERROR ",e); + /** + * 网络异常 + */ + result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.NET_ERROR); + result.put(MESSAGE, "NET_ERROR"); + } else { + /** + * 系统异常 + */ + logger.error("SYSTEM_ERROR ",e); + result.put(CODE, ErrorCode.SYSTEM_ERROR); + result.put(MESSAGE, "SYSTEM_ERROR"); + } + + return result; + + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/GetSystemInfo.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/GetSystemInfo.java new file mode 100644 index 00000000..5cb52262 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/GetSystemInfo.java @@ -0,0 +1,144 @@ +/* + * Copyright 2019 The FATE Authors. All Rights Reserved. + * + * 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.webank.ai.fate.serving.proxy.common; + + +import com.sun.management.OperatingSystemMXBean; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.lang.management.ManagementFactory; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; + +public class GetSystemInfo { + + private static final Logger LOGGER = LogManager.getLogger(); + + + public static String localIp; + + static { + localIp = getLocalIp(); + } + + public static String getLocalIp() { + + String sysType = System.getProperties().getProperty("os.name"); + String ip; + + try { + if (sysType.toLowerCase().startsWith("win")) { + String localIP = null; + + localIP = InetAddress.getLocalHost().getHostAddress(); + + if (localIP != null) { + return localIP; + } + } else { + ip = getIpByEthNum("eth0"); + if (ip != null) { + return ip; + + + } + } + } catch (Throwable e) { + LOGGER.error(e.getMessage(), e); + } + return ""; + } + + private static String getIpByEthNum(String ethNum) { + try { + Enumeration allNetInterfaces = NetworkInterface.getNetworkInterfaces(); + InetAddress ip; + while (allNetInterfaces.hasMoreElements()) { + NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement(); + if (ethNum.equals(netInterface.getName())) { + Enumeration addresses = netInterface.getInetAddresses(); + while (addresses.hasMoreElements()) { + ip = (InetAddress) addresses.nextElement(); + if (ip != null && ip instanceof Inet4Address) { + return ip.getHostAddress(); + } + } + } + } + } catch (SocketException e) { + LOGGER.error(e.getMessage(), e); + } + return ""; + } + + + public static String getOsName() { + + String osName = System.getProperty("os.name"); + return osName; + } + + + public static double getSystemCpuLoad() { + OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory + .getOperatingSystemMXBean(); + double SystemCpuLoad = osmxb.getSystemCpuLoad(); + return SystemCpuLoad; + } + + + public static double getProcessCpuLoad() { + OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory + .getOperatingSystemMXBean(); + double ProcessCpuLoad = osmxb.getProcessCpuLoad(); + return ProcessCpuLoad; + } + + + public static long getTotalMemorySize() { + int kb = 1024; + OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory + .getOperatingSystemMXBean(); + long totalMemorySize = osmxb.getTotalPhysicalMemorySize() / kb; + return totalMemorySize; + } + + + public static long getFreePhysicalMemorySize() { + int kb = 1024; + OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory + .getOperatingSystemMXBean(); + long freePhysicalMemorySize = osmxb.getFreePhysicalMemorySize() / kb; + return freePhysicalMemorySize; + } + + + public static long getUsedMemory() { + int kb = 1024; + OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory + .getOperatingSystemMXBean(); + long usedMemory = (osmxb.getTotalPhysicalMemorySize() - osmxb.getFreePhysicalMemorySize()) / kb; + return usedMemory; + } + + +} + diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/GlobalResponseController.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/GlobalResponseController.java new file mode 100644 index 00000000..624621e5 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/GlobalResponseController.java @@ -0,0 +1,52 @@ +package com.webank.ai.fate.serving.proxy.common; + + +import org.springframework.core.MethodParameter; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.lang.Nullable; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + + +@RestControllerAdvice +/** + * 全局的http结果返回处理器 + */ +public class GlobalResponseController implements ResponseBodyAdvice { + + @Override + public boolean supports(MethodParameter methodParameter, Class converterType) { + + Boolean isRest = AnnotationUtils.isAnnotationDeclaredLocally( + RestController.class, methodParameter.getContainingClass()); + ResponseBody responseBody = AnnotationUtils.findAnnotation( + methodParameter.getMethod(), ResponseBody.class); //得到方法上的注解 + + if (responseBody != null || isRest) { + return true; + } else { + return false; + } + + } + + @Nullable + @Override + public Object beforeBodyWrite(@Nullable Object body, + MethodParameter methodParameter, + MediaType mediaType, + Class> aClass, + ServerHttpRequest serverHttpRequest, + ServerHttpResponse serverHttpResponse) { + + + return body; + + } +} \ No newline at end of file diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ResponseResult.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ResponseResult.java new file mode 100644 index 00000000..e7cfb787 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ResponseResult.java @@ -0,0 +1,65 @@ +package com.webank.ai.fate.serving.proxy.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ResponseResult { + + @JsonProperty(value = "code") + private String code = "0"; + + @JsonProperty(value = "message") + private String msg = "success"; + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + + @JsonProperty(value = "data") + private T data; + + public ResponseResult(T data) { + this.data = data; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + + public ResponseResult() { + + } + + public ResponseResult(String code, String msg) { + this.code = code; + this.msg = msg; + } + + public ResponseResult(String code, T data) { + this.code = code; + this.data = data; + } + + + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + + +} \ No newline at end of file diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/CharacterEncodingFilter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/CharacterEncodingFilter.java new file mode 100644 index 00000000..6734b9b4 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/CharacterEncodingFilter.java @@ -0,0 +1,34 @@ +package com.webank.ai.fate.serving.proxy.config; + +import javax.servlet.*; +import javax.servlet.annotation.WebFilter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + + +public class CharacterEncodingFilter implements Filter { + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + + HttpServletRequest request = (HttpServletRequest) servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); + filterChain.doFilter(request , response); + + } + + + + @Override + public void destroy() { + } +} + diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/GrpcConfigration.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/GrpcConfigration.java new file mode 100644 index 00000000..1e2b8377 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/GrpcConfigration.java @@ -0,0 +1,51 @@ +package com.webank.ai.fate.serving.proxy.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * @Description TODO + * @Author + **/ +@Configuration +public class GrpcConfigration { + + + private static final Logger logger = LoggerFactory.getLogger(GrpcConfigration.class); + + //@Value("${proxy.async.timeout:5000}") + @Value("${proxy.grpc.threadpool.coresize:50}") + private int coreSize; + @Value("${proxy.grpc.threadpool.maxsize:100}") + private int maxPoolSize; + @Value("${proxy.grpc.threadpool.queuesize:10}") + private int queueSize; + + @Bean(name="grpcExecutorPool") + public Executor asyncServiceExecutor() { + + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + //配置核心线程数 + executor.setCorePoolSize(coreSize); + //配置最大线程数 + executor.setMaxPoolSize(maxPoolSize); + //配置队列大小 + executor.setQueueCapacity(queueSize); + //配置线程池中的线程的名称前缀 + executor.setThreadNamePrefix("grpc-service"); + //拒绝 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); + //执行初始化 + executor.initialize(); + return executor; + } + + +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java new file mode 100644 index 00000000..e3923569 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java @@ -0,0 +1,87 @@ +package com.webank.ai.fate.serving.proxy.config; + + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.web.context.request.async.TimeoutCallableProcessingInterceptor; +import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import javax.servlet.ServletContext; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.concurrent.ThreadPoolExecutor; + +@Configuration + +public class WebConfigration implements WebMvcConfigurer { + + + private final Logger logger = LoggerFactory.getLogger(WebConfigration.class); + + @Autowired + ServletContext servletContext; + @Value("${proxy.async.timeout:5000}") + long timeout; + @Value("${proxy.async.coresize:10}") + int coreSize; + + @Value("${proxy.async.maxsize:100}") + int maxSize; + + /** + * 设置异步线程池大小 + * @param configurer + */ + @Override + public void configureAsyncSupport(AsyncSupportConfigurer configurer) { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(coreSize); + executor.setMaxPoolSize(maxSize); + executor.setThreadNamePrefix("GatewayAsync"); + /** + * 直接拒绝 + */ + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); + executor.initialize(); + configurer.setTaskExecutor(executor); + configurer.setDefaultTimeout(timeout); + configurer.registerCallableInterceptors(new TimeoutCallableProcessingInterceptor()); + } + + @Bean + public TimeoutCallableProcessingInterceptor timeoutCallableProcessingInterceptor() { + return new TimeoutCallableProcessingInterceptor(); + } + + + @Bean + + public FilterRegistrationBean initUserServletFilterRegistration() { + + + + FilterRegistrationBean registration = new FilterRegistrationBean(); + CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(); + registration.setFilter(characterEncodingFilter); + + registration.addUrlPatterns("/*");//设置过滤路径,/*所有路径 + // registration.addInitParameter("name", "alue");//添加默认参数 + registration.setName("CharacterEncodingFilter");//设置优先级 + registration.setOrder(Integer.MAX_VALUE);//设置优先级 + + return registration; + } + +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java new file mode 100644 index 00000000..3a08259c --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java @@ -0,0 +1,100 @@ +package com.webank.ai.fate.serving.proxy.controller; + +import com.alibaba.fastjson.JSON; +import com.google.common.collect.Maps; +import com.webank.ai.fate.serving.proxy.common.Dict; +import com.webank.ai.fate.serving.proxy.rpc.core.*; +import com.webank.ai.fate.serving.proxy.utils.WebUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Callable; + +/** + * @Description TODO + * @Author + **/ +@Controller +public class ProxyController { + + @Autowired + ProxyServiceRegister proxyServiceRegister; + + Logger logger = LoggerFactory.getLogger(ProxyController.class); + + String binaryReader(HttpServletRequest request) throws IOException { + int len = request.getContentLength(); + ServletInputStream iii = request.getInputStream(); + byte[] buffer = new byte[len]; + iii.read(buffer, 0, len); + return new String(buffer); + } + + + @RequestMapping(value = "/federation/{version}/inference", method = {RequestMethod.POST, RequestMethod.GET}) + @ResponseBody + public Callable federation(@PathVariable String version, + @RequestBody String data, + HttpServletRequest httpServletRequest, + @RequestHeader HttpHeaders headers + ) throws Exception { + + return new Callable() { + @Override + public String call() throws Exception { + logger.info("receive : {} headers {}", data, headers.toSingleValueMap()); + + final ServiceAdaptor serviceAdaptor = proxyServiceRegister.getServiceAdaptor("inference"); + + Context context = new Context(); + context.setVersion(version); + + InboundPackage inboundPackage = buildInboundPackageFederation(context, headers, data, httpServletRequest); + + OutboundPackage result = serviceAdaptor.service(context,inboundPackage ); + if(result!=null&&result.getData()!=null) { + result.getData().remove("log"); + result.getData().remove("warn"); + result.getData().remove("caseid"); + } + return JSON.toJSONString(result.getData()); + + } + }; + + } + + + private InboundPackage buildInboundPackageFederation(Context context ,HttpHeaders headers, + String data,HttpServletRequest httpServletRequest) { + String sourceIp = WebUtil.getIpAddr(httpServletRequest); + context.setSourceIp(sourceIp); + context.setCaseId(UUID.randomUUID().toString()); + + Map head = Maps.newHashMap(); + // SERVICE_ID == fun(MODEL_ID, MODEL_VERSION) + head.put(Dict.SERVICE_ID, headers.getFirst(Dict.SERVICE_ID)!=null?headers.getFirst(Dict.SERVICE_ID).trim():""); + head.put(Dict.MODEL_ID, headers.getFirst(Dict.MODEL_ID)!=null?headers.getFirst(Dict.MODEL_ID).trim():""); + head.put(Dict.MODEL_VERSION, headers.getFirst(Dict.MODEL_VERSION)!=null?headers.getFirst(Dict.MODEL_VERSION).trim():""); + + Map body = JSON.parseObject(data, Map.class); + + InboundPackage inboundPackage = new InboundPackage(); + inboundPackage.setBody(body); + inboundPackage.setHead(head); + inboundPackage.setHttpServletRequest(httpServletRequest); + return inboundPackage; + } + + + +} \ No newline at end of file diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/BaseException.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/BaseException.java new file mode 100644 index 00000000..4e02e85e --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/BaseException.java @@ -0,0 +1,16 @@ +package com.webank.ai.fate.serving.proxy.exceptions; + + +public class BaseException extends Exception { + + private int retCode; + + public BaseException(int retCode, String message) { + super(message); + this.retCode = retCode; + } + + public int getRetCode() { + return retCode; + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/CustomException.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/CustomException.java new file mode 100644 index 00000000..00624f95 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/CustomException.java @@ -0,0 +1,26 @@ +package com.webank.ai.fate.serving.proxy.exceptions; + + +public class CustomException extends RuntimeException { + + private static final long serialVersionUID = 4564124491192825748L; + + private int retcode; + + public CustomException() { + super(); + } + + public CustomException(int code, String message) { + super(message); + this.setCode(code); + } + + public int getCode() { + return retcode; + } + + public void setCode(int code) { + this.retcode = code; + } +} \ No newline at end of file diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorCode.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorCode.java new file mode 100644 index 00000000..b1d14fb5 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorCode.java @@ -0,0 +1,22 @@ +package com.webank.ai.fate.serving.proxy.exceptions; + +import com.alibaba.csp.sentinel.slots.block.BlockException; + +public class ErrorCode { + + public static String PARAM_ERROR ="100"; //参数错误 + public static String ROLE_ERROR ="101"; //鉴权错误 + public static String SERVICE_NOT_FOUND= "102"; //服务不存在 + public static String SYSTEM_ERROR = "103";//系统错误 + public static String LIMIT_ERROR="104";// 系统限流 + public static String QUOTA_ERROR="105";// 配额耗尽 + public static String ORDER_ERROR="106";// 订单信息异常 + public static String NET_ERROR="107";// 网络异常 + public static String SHUTDOWN_ERROR="108";// 服务器关闭异常 + public static String ROUTER_ERROR="109";// 路由信息异常 + + + + + +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorResponseEntity.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorResponseEntity.java new file mode 100644 index 00000000..17098704 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorResponseEntity.java @@ -0,0 +1,22 @@ +package com.webank.ai.fate.serving.proxy.exceptions; + +public class ErrorResponseEntity { + private int code; + private String msg; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/GlobalExceptionHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/GlobalExceptionHandler.java new file mode 100644 index 00000000..27f6c788 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/GlobalExceptionHandler.java @@ -0,0 +1,35 @@ +package com.webank.ai.fate.serving.proxy.exceptions; + + +import com.webank.ai.fate.serving.proxy.common.ErrorMessageUtil; +import com.webank.ai.fate.serving.proxy.common.ResponseResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.sql.SQLIntegrityConstraintViolationException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.RejectedExecutionException; + +/** + * 统一异常样处理类 + * + * @Author + */ +@RestController +@ControllerAdvice +public class GlobalExceptionHandler { + Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); + @ExceptionHandler(Throwable.class) + @ResponseBody + public Map defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { + Map resultMap = ErrorMessageUtil.handleException(new HashMap(),e); + return resultMap; + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/InvalidRoleInfoException.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/InvalidRoleInfoException.java new file mode 100644 index 00000000..f1014b9e --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/InvalidRoleInfoException.java @@ -0,0 +1,13 @@ +package com.webank.ai.fate.serving.proxy.exceptions; + +/** + * @Description TODO + * @Author + **/ +public class InvalidRoleInfoException extends RuntimeException { + + public InvalidRoleInfoException(){ + + super("InvalidRole"); + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/NoResultException.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/NoResultException.java new file mode 100644 index 00000000..063ab29b --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/NoResultException.java @@ -0,0 +1,8 @@ +package com.webank.ai.fate.serving.proxy.exceptions; + +/** + * @Description TODO + * @Author + **/ +public class NoResultException extends RuntimeException{ +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/NoRouteInfoException.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/NoRouteInfoException.java new file mode 100644 index 00000000..4f381e9c --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/NoRouteInfoException.java @@ -0,0 +1,8 @@ +package com.webank.ai.fate.serving.proxy.exceptions; + +/** + * @Description TODO + * @Author + **/ +public class NoRouteInfoException extends RuntimeException{ +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/OverLoadException.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/OverLoadException.java new file mode 100644 index 00000000..430db3bb --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/OverLoadException.java @@ -0,0 +1,8 @@ +package com.webank.ai.fate.serving.proxy.exceptions; + +/** + * @Description TODO + * @Author + **/ +public class OverLoadException extends RuntimeException { +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ShowDownRejectException.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ShowDownRejectException.java new file mode 100644 index 00000000..c6199974 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ShowDownRejectException.java @@ -0,0 +1,8 @@ +package com.webank.ai.fate.serving.proxy.exceptions; + +/** + * @Description 停机时拒绝请求异常 + * @Author + **/ +public class ShowDownRejectException extends RuntimeException { +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/SqlAttactException.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/SqlAttactException.java new file mode 100644 index 00000000..463b8700 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/SqlAttactException.java @@ -0,0 +1,7 @@ +package com.webank.ai.fate.serving.proxy.exceptions; + +/** + * sql 注入 + */ +public class SqlAttactException extends RuntimeException{ +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/SysException.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/SysException.java new file mode 100644 index 00000000..9b35ec87 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/SysException.java @@ -0,0 +1,24 @@ +package com.webank.ai.fate.serving.proxy.exceptions; + +import org.springframework.core.NestedRuntimeException; + +public class SysException extends NestedRuntimeException { + /** + * 构造函数。 + * + * @param msg 异常描述 + */ + public SysException(String msg) { + super(msg); + } + + /** + * 构造函数。 + * + * @param msg 异常描述 + * @param ex 异常 + */ + public SysException(String msg, Throwable ex) { + super(msg, ex); + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java new file mode 100644 index 00000000..9e03e8e7 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java @@ -0,0 +1,228 @@ +package com.webank.ai.fate.serving.proxy.rpc.core; + +import com.alibaba.csp.sentinel.Entry; +import com.alibaba.csp.sentinel.SphU; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.google.common.collect.Lists; +import com.webank.ai.fate.serving.proxy.common.ErrorMessageUtil; +import com.webank.ai.fate.serving.proxy.exceptions.*; +import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcConnectionPool; +import com.webank.ai.fate.serving.proxy.rpc.router.RouterInterface; +import io.grpc.stub.AbstractStub; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.webank.ai.fate.serving.proxy.common.Dict.CODE; +import static com.webank.ai.fate.serving.proxy.common.Dict.MESSAGE; + +/** + * @Description 默认的服务适配器 + * @Author + **/ + +public abstract class AbstractServiceAdaptor implements ServiceAdaptor { + + Logger logger; + + public AbstractServiceAdaptor(){ + /** + * + */ + logger = LoggerFactory.getLogger( this.getClass().getName()); + } + + + public void addPreProcessor(Interceptor interceptor){ + + preChain.addInterceptor(interceptor); + }; + + public void addPostProcessor(Interceptor interceptor){ + postChain.addInterceptor(interceptor); + }; + /** + * 处理中的request,用于优雅停机 + */ + static public AtomicInteger requestInHandle = new AtomicInteger(0); + + public static boolean isOpen=true; + + + public GrpcConnectionPool getGrpcConnectionPool() { + return grpcConnectionPool; + } + + public void setGrpcConnectionPool(GrpcConnectionPool grpcConnectionPool) { + this.grpcConnectionPool = grpcConnectionPool; + } + + public ServiceAdaptor getServiceAdaptor() { + return serviceAdaptor; + } + + public void setServiceAdaptor(ServiceAdaptor serviceAdaptor) { + this.serviceAdaptor = serviceAdaptor; + } + + GrpcConnectionPool grpcConnectionPool; + + ServiceAdaptor serviceAdaptor; + + /** + * 处理逻辑前调用链 + */ + InterceptorChain preChain = new DefaultInterceptorChain(); + + /** + * 处理逻辑后调用链 + */ + InterceptorChain postChain = new DefaultInterceptorChain(); + + public AbstractStub getServiceStub() { + return serviceStub; + } + + public void setServiceStub(AbstractStub serviceStub) { + this.serviceStub = serviceStub; + } + + private AbstractStub serviceStub; + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + String serviceName; + + + + public abstract resp doService(Context context,InboundPackage data,OutboundPackage outboundPackage) ; + + /** + * @param context + * @param data + * @return + * @throws Exception + */ + @Override + public OutboundPackage service( Context context ,InboundPackage data) throws Exception { + + OutboundPackage outboundPackage= new OutboundPackage(); + long begin = System.currentTimeMillis(); + + List exceptions = Lists.newArrayList(); + context.setReturnCode("0"); + if(!isOpen){ + /** + * 系统关闭中 ,直接返回拒绝,关闭线程将等待所有处理完成 + */ + return this.serviceFailInner(context,data,new ShowDownRejectException()); + } + + try { + requestInHandle.addAndGet(1); + + + resp result=null; + context.setServiceName(this.serviceName); + /** + * preChain 不要放在try中,因为多数是校验逻辑, 抛错就直接中断流程 + */ + preChain.doPreProcess(context,data,outboundPackage); + + try { + result = doService(context, data, outboundPackage); + if(logger.isDebugEnabled()) { + logger.debug("do service, router info: {}, service name: {}, result: {}", JSON.toJSONString(data.getRouterInfo()), serviceName, result); + } + }catch(Throwable e){ + /** + * 这里catch的原因是,就算发生异常也要走完后处理 + */ + e.printStackTrace(); + exceptions.add(e); + logger.error("do service fail, cause by: {}", e.getMessage()); + } + outboundPackage.setData(result); + postChain.doPostProcess(context,data,outboundPackage); + + } + catch (Throwable e) { + exceptions.add(e); + logger.info(e.getMessage()); + } finally { + + requestInHandle.decrementAndGet(); + long end = System.currentTimeMillis(); + long cost = end - begin; + if(exceptions.size()!=0){ + try { + outboundPackage = this.serviceFail(context, data, exceptions); + }catch(Throwable e){ + logger.error("handle serviceFail error",e); + } + } + } + return outboundPackage; + + } + + private OutboundPackage serviceFailInner(Context context, InboundPackage data, Throwable e) throws Exception{ + + + + Map result = new HashMap(); + OutboundPackage outboundPackage = new OutboundPackage(); + result.put(MESSAGE, e.getMessage()); + ErrorMessageUtil.handleException(result,e); + context.setReturnCode(result.get(CODE)!=null?result.get(CODE).toString():ErrorCode.SYSTEM_ERROR.toString()); + Entry entry=null; + try { + StringBuilder sb = new StringBuilder("ERROR_"); + sb.append(serviceName); + String errResourceName= sb.append("_").append(result.get(CODE).toString()).toString(); + entry = SphU.entry(errResourceName); + } + finally { + if(entry!=null){ + entry.exit(); + } + } + resp rsp = transformErrorMap(context ,result); + outboundPackage.setData(rsp); + return outboundPackage; + + + } + + + @Override + public OutboundPackage serviceFail(Context context, InboundPackage data, List errors) throws Exception { + + logger.error("serviceFail {}", errors); + Throwable e = errors.get(0); + return serviceFailInner(context,data,e); + + } + + abstract protected resp transformErrorMap(Context context,Map data); + + private String objectToJson(Object obj) { + return JSONObject.toJSONString(obj, SerializerFeature.WriteEnumUsingToString); + } + + +} \ No newline at end of file diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/Context.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/Context.java new file mode 100644 index 00000000..c9b1b96a --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/Context.java @@ -0,0 +1,139 @@ +package com.webank.ai.fate.serving.proxy.rpc.core; + +import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcType; +import com.webank.ai.fate.serving.proxy.rpc.router.RouterInfo; + +/** + * @Description TODO + * @Author + **/ +public class Context { + + public GrpcType getGrpcType() { + return grpcType; + } + + public void setGrpcType(GrpcType grpcType) { + this.grpcType = grpcType; + } + + private GrpcType grpcType; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + private String version; + + private String guestAppId; + + private String hostAppid; + + + public String getGuestAppId() { + return guestAppId; + } + + public void setGuestAppId(String guestAppId) { + this.guestAppId = guestAppId; + } + + public String getHostAppid() { + return hostAppid; + } + + public void setHostAppid(String hostAppid) { + this.hostAppid = hostAppid; + } + + public RouterInfo getRouterInfo() { + return routerInfo; + } + + public void setRouterInfo(RouterInfo routerInfo) { + this.routerInfo = routerInfo; + } + + private RouterInfo routerInfo; + + public String getCaseId() { + return caseId; + } + + public void setCaseId(String caseId) { + this.caseId = caseId; + } + + private String caseId; + + public Object getResultData() { + return resultData; + } + + public void setResultData(Object resultData) { + this.resultData = resultData; + } + + private Object resultData; + + + public String getReturnCode() { + return returnCode; + } + + public void setReturnCode(String returnCode) { + this.returnCode = returnCode; + } + + + private String returnCode; + + public long getDownstreamCost() { + return downstreamCost; + } + + public void setDownstreamCost(long downstreamCost) { + this.downstreamCost = downstreamCost; + } + + private long downstreamCost; + + public long getDownstreamBegin() { + return downstreamBegin; + } + + public void setDownstreamBegin(long downstreamBegin) { + this.downstreamBegin = downstreamBegin; + } + + private long downstreamBegin; + + public long getRouteBasis() { + return routeBasis; + } + public void setRouteBasis(long routeBasis) { + this.routeBasis = routeBasis; + } + private long routeBasis; + + public String getSourceIp() { + return sourceIp; + } + public void setSourceIp(String sourceIp) { + this.sourceIp = sourceIp; + } + private String sourceIp; + + public String getServiceName() { + return serviceName; + } + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + String serviceName; + +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/DefaultInterceptorChain.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/DefaultInterceptorChain.java new file mode 100644 index 00000000..1747235f --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/DefaultInterceptorChain.java @@ -0,0 +1,59 @@ +package com.webank.ai.fate.serving.proxy.rpc.core; + +import com.google.common.collect.Lists; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * @Description TODO + * @Author + **/ +public class DefaultInterceptorChain implements InterceptorChain { + + Logger logger = LoggerFactory.getLogger(DefaultInterceptorChain.class); + + List> chain = Lists.newArrayList(); + + @Override + public void addInterceptor(Interceptor interceptor) { + + chain.add(interceptor); + } + + /** + * 前处理因为多数是校验逻辑 , 在这里抛出异常,将中断流程 + * @param context + * @param inboundPackage + * @param outboundPackage + * @throws Exception + */ + @Override + public void doPreProcess(Context context, InboundPackage inboundPackage ,OutboundPackage outboundPackage) throws Exception { + + for(Interceptor interceptor:chain){ + interceptor.doPreProcess(context,inboundPackage,outboundPackage); + } + + } + + /** + * 后处理即使抛出异常,也将执行完所有 + * @param context + * @param inboundPackage + * @param outboundPackage + * @throws Exception + */ + @Override + public void doPostProcess(Context context, InboundPackage inboundPackage,OutboundPackage outboundPackage) throws Exception { + + for(Interceptor interceptor:chain){ + try { + interceptor.doPostProcess(context, inboundPackage, outboundPackage); + }catch(Throwable e){ + logger.error("doPostProcess error",e); + } + } + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/FlowLogger.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/FlowLogger.java new file mode 100644 index 00000000..8fc8cd2a --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/FlowLogger.java @@ -0,0 +1,144 @@ +package com.webank.ai.fate.serving.proxy.rpc.core; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.encoder.PatternLayoutEncoder; +import ch.qos.logback.classic.filter.LevelFilter; +import ch.qos.logback.core.rolling.RollingFileAppender; +import ch.qos.logback.core.rolling.TimeBasedRollingPolicy; +import ch.qos.logback.core.spi.FilterReply; +import ch.qos.logback.core.util.OptionHelper; +import org.slf4j.LoggerFactory; + +import java.text.DateFormat; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * @Description TODO + * @Author + * 坑爹玩意,需要根据业务字段打到不同文件 + **/ +public class FlowLogger { + + + private static final Map container = new HashMap<>(); + public static Logger getLogger(String name) { + Logger logger = container.get(name); + if(logger != null) { + return logger; + } + synchronized (FlowLogger.class) { + logger = container.get(name); + if(logger != null) { + return logger; + } + logger = build(name); + container.put(name,logger); + } + return logger; + } + + + + + private static Logger build(String name) { + + RollingFileAppender infoAppender =getAppender(name,Level.INFO); + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + Logger logger = context.getLogger("FILE-" + name); + //设置不向上级打印信息 + logger.setAdditive(false); + + logger.addAppender(infoAppender); + + + return logger; + } + + + + + public static RollingFileAppender getAppender(String name, Level level){ + DateFormat format = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.SIMPLIFIED_CHINESE); + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + //这里是可以用来设置appender的,在xml配置文件里面,是这种形式: + // + RollingFileAppender appender = new RollingFileAppender(); +// ConsoleAppender consoleAppender = new ConsoleAppender(); + +// //这里设置级别过滤器 + LevelFilter levelFilter = new LevelFilter(); + levelFilter.setLevel(level); + levelFilter.setOnMatch(FilterReply.ACCEPT); + levelFilter.setOnMismatch(FilterReply.DENY); + levelFilter.start(); + appender.addFilter(levelFilter); + + + //设置上下文,每个logger都关联到logger上下文,默认上下文名称为default。 + // 但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改。 + appender.setContext(context); + //appender的name属性 + appender.setName("FILE-" + name); + //设置文件名 + appender.setFile(OptionHelper.substVars( "logs/flow-"+name+".log",context)); + + appender.setAppend(true); + + appender.setPrudent(false); + + TimeBasedRollingPolicy rollingPolicy = new TimeBasedRollingPolicy(); + + +//      +//        logFile.%d{yyyy-MM-dd}.log + + + + rollingPolicy.setFileNamePattern("logs/flow-"+name+".%d{yyyy-MM-dd}.log"); + rollingPolicy.setMaxHistory(30); + rollingPolicy.setContext(context); + rollingPolicy.setParent(appender); + rollingPolicy.start(); + appender.setRollingPolicy(rollingPolicy); + + + +// //设置文件创建时间及大小的类 +// SizeAndTimeBasedRollingPolicy policy = new SizeAndTimeBasedRollingPolicy(); +// //文件名格式 +// String fp = OptionHelper.substVars("E:/eppLog/"+ name +"/" + format.format(new Date())+"/"+ level.levelStr + "/.%d{yyyy-MM-dd}.%i.log",context); +// //最大日志文件大小 +// policy.setMaxFileSize(FileSize.valueOf("128MB")); +// //设置文件名模式 +// policy.setFileNamePattern(fp); +// //设置最大历史记录为15条 +// policy.setMaxHistory(15); +// //总大小限制 +// policy.setTotalSizeCap(FileSize.valueOf("32GB")); +// //设置父节点是appender +// policy.setParent(appender); +// //设置上下文,每个logger都关联到logger上下文,默认上下文名称为default。 +// // 但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改。 +// policy.setContext(context); +// policy.start(); +// + PatternLayoutEncoder encoder = new PatternLayoutEncoder(); + //设置上下文,每个logger都关联到logger上下文,默认上下文名称为default。 + // 但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改。 + encoder.setContext(context); + //设置格式 + encoder.setPattern("%d %p (%file:%line\\)- %m%n"); + encoder.start(); + + //加入下面两个节点 +// appender.setRollingPolicy(policy); + appender.setEncoder(encoder); + appender.start(); + return appender; + } + +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/InboundPackage.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/InboundPackage.java new file mode 100644 index 00000000..2156e16e --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/InboundPackage.java @@ -0,0 +1,77 @@ +package com.webank.ai.fate.serving.proxy.rpc.core; + +import com.webank.ai.fate.serving.proxy.rpc.router.RouterInfo; +import io.grpc.ManagedChannel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +/** + * @Description TODO + * @Author + **/ +public class InboundPackage { + + + static Logger logger = LoggerFactory.getLogger(InboundPackage.class); + + public ManagedChannel getManagedChannel() { + return managedChannel; + } + + public void setManagedChannel(ManagedChannel managedChannel) { + this.managedChannel = managedChannel; + } + + ManagedChannel managedChannel; + + RouterInfo routerInfo; + + public HttpServletRequest getHttpServletRequest() { + return httpServletRequest; + } + + public void setHttpServletRequest(HttpServletRequest httpServletRequest) { + this.httpServletRequest = httpServletRequest; + } + + HttpServletRequest httpServletRequest; + + public RouterInfo getRouterInfo() { + return routerInfo; + } + + public void setRouterInfo(RouterInfo routerInfo) { + this.routerInfo = routerInfo; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public Map getHead() { + return head; + } + + public void setHead(Map head) { + this.head = head; + } + + public T getBody() { + return body; + } + + public void setBody(T body) { + this.body = body; + } + + String source; + Map head; + T body; +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/Interceptor.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/Interceptor.java new file mode 100644 index 00000000..fd556a00 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/Interceptor.java @@ -0,0 +1,9 @@ +package com.webank.ai.fate.serving.proxy.rpc.core; + +public interface Interceptor { + + public void doPreProcess(Context context,InboundPackage inboundPackage,OutboundPackage outboundPackage) throws Exception; + + public void doPostProcess(Context context,InboundPackage inboundPackage,OutboundPackage outboundPackage) throws Exception; + +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/InterceptorChain.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/InterceptorChain.java new file mode 100644 index 00000000..b1f82e0b --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/InterceptorChain.java @@ -0,0 +1,12 @@ +package com.webank.ai.fate.serving.proxy.rpc.core; + +/** + * @Description 拦截器链 + * @Author + **/ +public interface InterceptorChain extends Interceptor { + + public void addInterceptor(Interceptor interceptor); + + +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OutboundPackage.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OutboundPackage.java new file mode 100644 index 00000000..07f64208 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OutboundPackage.java @@ -0,0 +1,30 @@ +package com.webank.ai.fate.serving.proxy.rpc.core; + +/** + * @Description TODO + * @Author + **/ +public class OutboundPackage { + + + public boolean isHitCache() { + return hitCache; + } + + public void setHitCache(boolean hitCache) { + this.hitCache = hitCache; + } + + public boolean hitCache=false; + + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + + T data ; +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OverLoadEndPoint.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OverLoadEndPoint.java new file mode 100644 index 00000000..73b71572 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OverLoadEndPoint.java @@ -0,0 +1,36 @@ +package com.webank.ai.fate.serving.proxy.rpc.core; + +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +/** + * @Description 服务过载监控 + * @Author + **/ +@Endpoint(id = "overload") +@Component +public class OverLoadEndPoint { + + + Map overLoadInfo = new HashMap(); + + public void addServiceOverLoad(String serviceName) { + + } + + @ReadOperation + public Map recordOverLoad() { + Map map = new HashMap(); + map.put("name", "ppppp"); + System.err.println("00000ppppppppppppppppp"); + return map; + + } + + +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OverLoadLogger.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OverLoadLogger.java new file mode 100644 index 00000000..8e65ae11 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OverLoadLogger.java @@ -0,0 +1,125 @@ +package com.webank.ai.fate.serving.proxy.rpc.core; + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.DateFormat; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * @Description TODO + * @Author + **/ +public class OverLoadLogger { + + static Logger logger = LoggerFactory.getLogger(OverLoadLogger.class); + + + +// private static final Map container = new HashMap<>(); +// public Logger getLogger(String name) { +// Logger logger = container.get(name); +// if(logger != null) { +// return logger; +// } +// synchronized (LoggerBuilder.class) { +// logger = container.get(name); +// if(logger != null) { +// return logger; +// } +// logger = build(name); +// container.put(name,logger); +// } +// return logger; +// } +// +// +// +// +// private static Logger build(String name) { +// RollingFileAppender errorAppender =new AppenderTest().getAppender(name,Level.ERROR); +// RollingFileAppender infoAppender =new AppenderTest().getAppender(name,Level.INFO); +// RollingFileAppender warnAppender =new AppenderTest().getAppender(name,Level.WARN); +// RollingFileAppender debugAppender =new AppenderTest().getAppender(name,Level.DEBUG); +// LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); +// Logger logger = context.getLogger("FILE-" + name); +// //设置不向上级打印信息 +// logger.setAdditive(false); +// logger.addAppender(errorAppender); +// logger.addAppender(infoAppender); +// logger.addAppender(warnAppender); +// logger.addAppender(debugAppender); +// +// return logger; +// } + + + + +// public RollingFileAppender getAppender(String name, Level level){ +// DateFormat format = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.SIMPLIFIED_CHINESE); +// LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); +// //这里是可以用来设置appender的,在xml配置文件里面,是这种形式: +// // +// RollingFileAppender appender = new RollingFileAppender(); +//// ConsoleAppender consoleAppender = new ConsoleAppender(); +// +//// //这里设置级别过滤器 +//// LevelController levelController = new LevelController(); +//// LevelFilter levelFilter = levelController.getLevelFilter(level); +//// levelFilter.start(); +//// appender.addFilter(levelFilter); +// +// +// //设置上下文,每个logger都关联到logger上下文,默认上下文名称为default。 +// // 但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改。 +// appender.setContext(context); +// //appender的name属性 +// appender.setName("FILE-" + name); +// //设置文件名 +// appender.setFile(OptionHelper.substVars("E:/eppLog/"+ name+"/" + format.format(new Date())+"/"+ level.levelStr + ".log",context)); +// +// appender.setAppend(true); +// +// appender.setPrudent(false); +// +// //设置文件创建时间及大小的类 +// SizeAndTimeBasedRollingPolicy policy = new SizeAndTimeBasedRollingPolicy(); +// //文件名格式 +// String fp = OptionHelper.substVars("E:/eppLog/"+ name +"/" + format.format(new Date())+"/"+ level.levelStr + "/.%d{yyyy-MM-dd}.%i.log",context); +// //最大日志文件大小 +// policy.setMaxFileSize(FileSize.valueOf("128MB")); +// //设置文件名模式 +// policy.setFileNamePattern(fp); +// //设置最大历史记录为15条 +// policy.setMaxHistory(15); +// //总大小限制 +// policy.setTotalSizeCap(FileSize.valueOf("32GB")); +// //设置父节点是appender +// policy.setParent(appender); +// //设置上下文,每个logger都关联到logger上下文,默认上下文名称为default。 +// // 但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改。 +// policy.setContext(context); +// policy.start(); +// +// PatternLayoutEncoder encoder = new PatternLayoutEncoder(); +// //设置上下文,每个logger都关联到logger上下文,默认上下文名称为default。 +// // 但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改。 +// encoder.setContext(context); +// //设置格式 +// encoder.setPattern("%d %p (%file:%line\\)- %m%n"); +// encoder.start(); +// +// //加入下面两个节点 +// appender.setRollingPolicy(policy); +// appender.setEncoder(encoder); +// appender.start(); +// return appender; +// } + + + +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyService.java new file mode 100644 index 00000000..069ea4c7 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyService.java @@ -0,0 +1,21 @@ +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by Fernflower decompiler) +// + +package com.webank.ai.fate.serving.proxy.rpc.core; + + + +import java.lang.annotation.*; +import java.util.List; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface ProxyService { + String name(); + String[] preChain() default {}; + String[] postChain() default {}; + +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyServiceRegister.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyServiceRegister.java new file mode 100644 index 00000000..105975e6 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyServiceRegister.java @@ -0,0 +1,88 @@ +package com.webank.ai.fate.serving.proxy.rpc.core; +import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcConnectionPool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + + +/** + * @Description TODO + * @Author + **/ +@Component +public class ProxyServiceRegister implements ServiceRegister, ApplicationContextAware, ApplicationListener { + + Logger logger = LoggerFactory.getLogger(ProxyServiceRegister.class); + + @Override + public ServiceAdaptor getServiceAdaptor(String name) { + if( serviceAdaptorMap.get(name)!=null){ + return serviceAdaptorMap.get(name); + }else + return serviceAdaptorMap.get("NotFound"); + + } + + Map serviceAdaptorMap = new HashMap(); + + ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + + this.applicationContext = context; + + } + + + @Autowired + GrpcConnectionPool grpcConnectionPool; + + + @Override + public void onApplicationEvent(ApplicationEvent applicationEvent) { + + if (applicationEvent instanceof ContextRefreshedEvent) { + String[] beans = applicationContext.getBeanNamesForType(AbstractServiceAdaptor.class); + for (String beanName : beans) { + AbstractServiceAdaptor serviceAdaptor = applicationContext.getBean(beanName,AbstractServiceAdaptor.class); + + ProxyService proxyService = (ProxyService) serviceAdaptor.getClass().getAnnotation(ProxyService.class); + + if (proxyService != null) { + + serviceAdaptor.setServiceName(proxyService.name()); + // TODO utu: may load from cfg file is a better choice? + String [] postChain = proxyService.postChain(); + String [] preChain = proxyService.preChain(); + for(String post:postChain){ + Interceptor postInterceptor = applicationContext.getBean(post,Interceptor.class); + serviceAdaptor.addPostProcessor(postInterceptor); + } + for(String pre:preChain){ + Interceptor preInterceptor = applicationContext.getBean(pre,Interceptor.class); + serviceAdaptor.addPreProcessor(preInterceptor); + } + + this.serviceAdaptorMap.put(proxyService.name(), serviceAdaptor); + } + + + } + logger.info("service register info {}",this.serviceAdaptorMap); + } + + + + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ServiceAdaptor.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ServiceAdaptor.java new file mode 100644 index 00000000..0eb7b986 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ServiceAdaptor.java @@ -0,0 +1,11 @@ +package com.webank.ai.fate.serving.proxy.rpc.core; + +import java.util.List; + +public interface ServiceAdaptor { + public OutboundPackage service(Context context,InboundPackage inboundPackage) throws Exception; + + public OutboundPackage serviceFail( Context context,InboundPackage data,List e) throws Exception; + + +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ServiceRegister.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ServiceRegister.java new file mode 100644 index 00000000..34ed838e --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ServiceRegister.java @@ -0,0 +1,12 @@ +package com.webank.ai.fate.serving.proxy.rpc.core; + +/** + * @Description TODO + * @Author + **/ + +public interface ServiceRegister { + + public ServiceAdaptor getServiceAdaptor(String name); + +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java new file mode 100644 index 00000000..da1d1185 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java @@ -0,0 +1,179 @@ +package com.webank.ai.fate.serving.proxy.rpc.grpc; + +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; + +import org.apache.commons.pool2.BasePooledObjectFactory; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.impl.DefaultEvictionPolicy; +import org.apache.commons.pool2.impl.DefaultPooledObject; +import org.apache.commons.pool2.impl.GenericObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + + +/** + * @Description grpc 连接池 + * @Author + **/ +@Service +public class GrpcConnectionPool { + + Logger logger = LoggerFactory.getLogger(GrpcConnectionPool.class); + + ConcurrentHashMap> poolMap = new ConcurrentHashMap>(); + + public void returnPool(ManagedChannel channel, String host, int port) { + try { + logger.info("return grpc pool {}:{}",host,port); + String key = host + ":" + port; + poolMap.get(key).returnObject(channel); + // logger.info("pool active size {}",poolMap.get(key).()); + } catch (Exception e) { + logger.error("return to pool error", e); + } + + } + + @Value("${proxy.grpc.pool.maxTotal:64}") + private Integer maxTotal; + @Value("${proxy.grpc.pool.maxIdle:1}") + private Integer maxIdle; + + public ManagedChannel getManagedChannel(String host, int port) throws Exception { + String key = host + ":" + port; + logger.info("try to get grpc channel {}",key); + GenericObjectPool pool = poolMap.get(key); + if (pool == null) { + + GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); + + poolConfig.setMaxTotal(maxTotal); + + poolConfig.setMinIdle(0); + + poolConfig.setMaxIdle(maxIdle); + + poolConfig.setMaxWaitMillis(-1); + + poolConfig.setLifo(true); + + poolConfig.setTestOnBorrow(true); + + poolConfig.setTestWhileIdle(true); + + poolConfig.setNumTestsPerEvictionRun(1); + + poolConfig.setTimeBetweenEvictionRunsMillis(1000); + + poolConfig.setEvictionPolicy(new DefaultEvictionPolicy()); + + poolConfig.setMinEvictableIdleTimeMillis(3000); + + poolConfig.setSoftMinEvictableIdleTimeMillis(3000); + + poolConfig.setBlockWhenExhausted(true); + + poolMap.putIfAbsent(key, new GenericObjectPool + (new ManagedChannelFactory(host, port), poolConfig)); + + + } + GenericObjectPool objectPool =poolMap.get(key); + + + logger.info("grpc pool host {} active num {} idle num {}",key,objectPool.getNumActive(),objectPool.getNumIdle()); + + return objectPool.borrowObject(); + } + + ; + + + private class ManagedChannelFactory extends BasePooledObjectFactory { + + private String host; + private int port; + + public ManagedChannelFactory(String host, int port) { + this.host = host; + this.port = port; + } + + @Override + public ManagedChannel create() throws Exception { + + + ManagedChannelBuilder builder = ManagedChannelBuilder + .forAddress(host,port) + .keepAliveTime(6, TimeUnit.SECONDS) + .keepAliveTimeout(6, TimeUnit.SECONDS) + .keepAliveWithoutCalls(true) + .idleTimeout(6, TimeUnit.SECONDS) + .perRpcBufferLimit(128 << 20) + // .flowControlWindow(32 << 20) + .maxInboundMessageSize(32 << 20) + + + .retryBufferSize(16 << 20); + + + + builder + .usePlaintext(); + + ManagedChannel managedChannel = builder + .build(); + + return ManagedChannelBuilder.forAddress(host, port). + usePlaintext(true).build(); + } + + @Override + public PooledObject wrap(ManagedChannel managedChannel) { + return new DefaultPooledObject<>(managedChannel); + } + + @Override + public void destroyObject(PooledObject p) throws Exception { + //System.err.println("destroyObject ================"); + try { + p.getObject().shutdownNow(); + + super.destroyObject(p); + }catch(Exception e){ + + } + } + @Override + public boolean validateObject(PooledObject p) { + + + ManagedChannel managedChannel = p.getObject(); + + + boolean isOk = !managedChannel.isShutdown()&&!managedChannel.isTerminated(); + System.err.println("validateObject ================"+isOk); + + + return isOk; + } + + } + + + public static void main(String[] args){ + + + + + } + + +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcType.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcType.java new file mode 100644 index 00000000..c10a1a10 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcType.java @@ -0,0 +1,11 @@ +package com.webank.ai.fate.serving.proxy.rpc.grpc; + +/** + * @Description TODO + * @Author + **/ +public enum GrpcType { + INTRA_GRPC, + INTER_GRPC +} + diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterGrpcServer.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterGrpcServer.java new file mode 100644 index 00000000..a2c5edfb --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterGrpcServer.java @@ -0,0 +1,45 @@ +package com.webank.ai.fate.serving.proxy.rpc.grpc; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.ServerInterceptors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.concurrent.Executor; + +/** + * @Description TODO + * @Author + **/ +@Service +public class InterGrpcServer implements InitializingBean { + + @Value("${proxy.grpc.inter.port:8867}") + private Integer port; + + Logger logger = LoggerFactory.getLogger(InterGrpcServer.class); + + Server server ; + + @Autowired + InterRequestHandler interRequestHandler; + + @Resource(name="grpcExecutorPool") + Executor executor; + + @Override + public void afterPropertiesSet() throws Exception { + ServerBuilder serverBuilder = ServerBuilder.forPort(port); + serverBuilder.executor(executor); + serverBuilder.addService(ServerInterceptors.intercept(interRequestHandler, new ServiceExceptionHandler())); + serverBuilder.addService(interRequestHandler); + server = serverBuilder.build(); + server.start(); + + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterRequestHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterRequestHandler.java new file mode 100644 index 00000000..3785c40f --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterRequestHandler.java @@ -0,0 +1,28 @@ + + +package com.webank.ai.fate.serving.proxy.rpc.grpc; + +import com.webank.ai.fate.api.networking.proxy.Proxy; +import com.webank.ai.fate.serving.proxy.rpc.core.*; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class InterRequestHandler extends ProxyRequestHandler { + private static final Logger logger = LogManager.getLogger(); + + @Autowired + ProxyServiceRegister proxyServiceRegister; + + public ProxyServiceRegister getProxyServiceRegister(){ + return proxyServiceRegister; + } + + public void setExtraInfo(Context context, InboundPackage inboundPackage, Proxy.Packet req){ + context.setGrpcType(GrpcType.INTER_GRPC); + } + + +} \ No newline at end of file diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java new file mode 100644 index 00000000..7c5bc121 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java @@ -0,0 +1,46 @@ +package com.webank.ai.fate.serving.proxy.rpc.grpc; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.ServerInterceptors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.concurrent.Executor; + +/** + * @Description TODO + * @Author + **/ +@Service +public class IntraGrpcServer implements InitializingBean { + + @Value("${proxy.grpc.inter.port:8867}") + private Integer port; + + Logger logger = LoggerFactory.getLogger(InterGrpcServer.class); + + Server server ; + + @Autowired + IntraRequestHandler intraRequestHandler; + + @Resource(name="grpcExecutorPool") + Executor executor; + + @Override + public void afterPropertiesSet() throws Exception { + ServerBuilder serverBuilder = ServerBuilder.forPort(port); + serverBuilder.executor(executor); + serverBuilder.addService(ServerInterceptors.intercept(intraRequestHandler, new ServiceExceptionHandler())); + serverBuilder.addService(intraRequestHandler); + server = serverBuilder.build(); + server.start(); + + } +} + diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraRequestHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraRequestHandler.java new file mode 100644 index 00000000..3788ae65 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraRequestHandler.java @@ -0,0 +1,30 @@ + + +package com.webank.ai.fate.serving.proxy.rpc.grpc; + +import com.webank.ai.fate.api.networking.proxy.Proxy; +import com.webank.ai.fate.serving.proxy.rpc.core.Context; +import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.proxy.rpc.core.ProxyServiceRegister; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class IntraRequestHandler extends ProxyRequestHandler { + private static final Logger logger = LogManager.getLogger(); + + @Autowired + ProxyServiceRegister proxyServiceRegister; + + public ProxyServiceRegister getProxyServiceRegister(){ + return proxyServiceRegister; + } + + public void setExtraInfo(Context context, InboundPackage inboundPackage, Proxy.Packet req){ + context.setGrpcType(GrpcType.INTRA_GRPC); + } + + +} \ No newline at end of file diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java new file mode 100644 index 00000000..c60dbe35 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java @@ -0,0 +1,59 @@ + + +package com.webank.ai.fate.serving.proxy.rpc.grpc; + +import com.google.common.collect.Maps; +import com.webank.ai.fate.api.networking.proxy.DataTransferServiceGrpc; +import com.webank.ai.fate.api.networking.proxy.Proxy; +import com.webank.ai.fate.serving.proxy.common.Dict; +import com.webank.ai.fate.serving.proxy.rpc.core.*; +import io.grpc.stub.StreamObserver; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Map; + +public abstract class ProxyRequestHandler extends DataTransferServiceGrpc.DataTransferServiceImplBase { + private static final Logger logger = LogManager.getLogger(); + + public abstract ProxyServiceRegister getProxyServiceRegister(); + + public abstract void setExtraInfo(Context context, InboundPackage inboundPackage, Proxy.Packet req); + + @Override + public void unaryCall(Proxy.Packet req, StreamObserver responseObserver) { + + logger.info("unaryCall req {}",req); + ServiceAdaptor unaryCallService = getProxyServiceRegister().getServiceAdaptor("unaryCall"); + Context context = new Context(); + InboundPackage inboundPackage = buildInboundPackage(context, req); + setExtraInfo(context, inboundPackage, req); + + OutboundPackage outboundPackage = null; + try { + outboundPackage = unaryCallService.service(context,inboundPackage); + } catch (Exception e) { + e.printStackTrace(); + + } + Proxy.Packet result = (Proxy.Packet)outboundPackage.getData(); + responseObserver.onNext(result); + responseObserver.onCompleted(); + } + + public InboundPackage buildInboundPackage(Context context, Proxy.Packet req){ + context.setCaseId(Long.toString(System.currentTimeMillis())); + context.setVersion(req.getAuth().getVersion()); + if(StringUtils.isEmpty(context.getVersion())){ + context.setVersion(Dict.DEFAULT_VERSION); + } + context.setGuestAppId(req.getHeader().getSrc().getPartyId()); + context.setHostAppid(req.getHeader().getDst().getPartyId()); + + InboundPackage inboundPackage = new InboundPackage(); + inboundPackage.setBody(req); + + return inboundPackage; + } +} \ No newline at end of file diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ServiceExceptionHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ServiceExceptionHandler.java new file mode 100644 index 00000000..a109d518 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ServiceExceptionHandler.java @@ -0,0 +1,28 @@ +package com.webank.ai.fate.serving.proxy.rpc.grpc; + +import io.grpc.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ServiceExceptionHandler implements ServerInterceptor { + private static final Logger LOGGER = LoggerFactory.getLogger(ServiceExceptionHandler.class); + + @Override + public ServerCall.Listener interceptCall(ServerCall call, + Metadata requestHeaders, ServerCallHandler next) { + ServerCall.Listener delegate = next.startCall(call, requestHeaders); + return new ForwardingServerCallListener.SimpleForwardingServerCallListener(delegate) { + @Override + public void onHalfClose() { + try { + super.onHalfClose(); + } catch (Exception e) { + LOGGER.info("ServiceException:", e); + call.close(Status.INTERNAL + .withCause(e) + .withDescription(e.getMessage()), new Metadata()); + } + } + }; + } +} \ No newline at end of file diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java new file mode 100644 index 00000000..313db8df --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java @@ -0,0 +1,67 @@ +package com.webank.ai.fate.serving.proxy.rpc.router; + +import com.google.common.hash.Hashing; +import com.webank.ai.fate.serving.proxy.exceptions.NoRouteInfoException; +import com.webank.ai.fate.serving.proxy.rpc.core.Context; +import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.proxy.rpc.core.OutboundPackage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + + +public abstract class BaseServingRouter implements RouterInterface{ + private static final Logger logger = LoggerFactory.getLogger(BaseServingRouter.class); + + public abstract List getRouterInfoList(Context context, InboundPackage inboundPackage); + + public abstract RouteType getRouteType(); + + @Override + public RouterInfo route(Context context, InboundPackage inboundPackage) { + List routeList = getRouterInfoList(context,inboundPackage); + + if(routeList==null + || 0 == routeList.size()){ + return null; + } + int idx = 0; + RouteType routeType = getRouteType(); + switch (routeType){ + case RANDOM_ROUTE:{ + idx = ThreadLocalRandom.current().nextInt(routeList.size()); + break; + } + case CONSISTENT_HASH_ROUTE:{ + idx = Hashing.consistentHash(context.getRouteBasis(), routeList.size()); + break; + } + default:{ + // to use the first one. + break; + } + } + RouterInfo routerInfo = routeList.get(idx); + + context.setRouterInfo(routerInfo); + + logger.info("host appid {} get route info {}:{}", context.getHostAppid(),routerInfo.getHost(),routerInfo.getPort()); + return routerInfo; + } + + @Override + public void doPreProcess(Context context, InboundPackage inboundPackage,OutboundPackage outboundPackage) throws Exception { + RouterInfo routerInfo =this.route(context,inboundPackage); + if(routerInfo ==null){ + throw new NoRouteInfoException(); + } + inboundPackage.setRouterInfo(routerInfo); + } + + @Override + public void doPostProcess(Context context, InboundPackage inboundPackage,OutboundPackage outboundPackage) throws Exception { + + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java new file mode 100644 index 00000000..6a70ba2b --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java @@ -0,0 +1,372 @@ +package com.webank.ai.fate.serving.proxy.rpc.router; + +import com.google.common.base.Preconditions; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; +import com.webank.ai.fate.api.core.BasicMeta; +import com.webank.ai.fate.api.networking.proxy.Proxy; +import com.webank.ai.fate.serving.proxy.common.Dict; +import com.webank.ai.fate.serving.proxy.rpc.core.Context; +import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.proxy.utils.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + + +@Service +public class ConfigFileBasedServingRouter extends BaseServingRouter implements InitializingBean{ + @Value("${routeType}") + private String routeTypeString; + + private RouteType routeType; + + @Value("${route.table}") + private String routeTableFile; + + @Value("${coordinator}") + private String selfCoordinator; + + @Value("${inference.service.name:serving}") + private String inferenceServiceName; + + private String lastFileMd5; + + Logger logger = LoggerFactory.getLogger(ConfigFileBasedServingRouter.class); + + private Map> allow; + private Map> deny; + private boolean defaultAllow; + private Map>> routeTable; + private Map> topicEndpointMapping; + private BasicMeta.Endpoint.Builder endpointBuilder; + + private static final String IP = "ip"; + private static final String PORT = "port"; + private static final String HOSTNAME = "hostname"; + private static final String DEFAULT = "default"; + + @Override + public RouteType getRouteType(){ + return routeType; + } + + @Override + public List getRouterInfoList(Context context, InboundPackage inboundPackage){ + Proxy.Topic dstTopic; + Proxy.Topic srcTopic; + String service_name = context.getServiceName(); + if("inference".equals(service_name)) { + Proxy.Topic.Builder topicBuilder = Proxy.Topic.newBuilder(); + dstTopic = topicBuilder.setPartyId(selfCoordinator). + setRole(inferenceServiceName) + .setName(Dict.PARTNER_PARTY_NAME) + .build(); + srcTopic = dstTopic; + } else { // default unaryCall + Proxy.Packet sourcePacket = (Proxy.Packet) inboundPackage.getBody(); + dstTopic = sourcePacket.getHeader().getDst(); + srcTopic = sourcePacket.getHeader().getSrc(); + } + + Preconditions.checkNotNull(dstTopic, "dstTopic cannot be null"); + + if(!isAllowed(srcTopic, dstTopic)){ + logger.warn("from {} to {} is not allowed!", srcTopic, dstTopic); + return null; + } + + List routeList = topicEndpointMapping.getOrDefault(dstTopic, null); + if (routeList != null) { + return routeList; + } + + // to get route list from routeTable + String topicName = dstTopic.getName(); + String coordinator = dstTopic.getPartyId(); + String serviceName = dstTopic.getRole(); + if (StringUtils.isAnyBlank(topicName, coordinator, serviceName)) { + throw new IllegalArgumentException("one of dstTopic name, coordinator, role is null. dstTopic: " + dstTopic); + } + Map> serviceTable = + routeTable.getOrDefault(coordinator, routeTable.getOrDefault(DEFAULT, null)); + if (serviceTable == null) { + throw new IllegalStateException("No available endpoint for the coordinator: " + coordinator + + ". Considering adding a default endpoint?"); + } + List endpoints = + serviceTable.getOrDefault(serviceName, serviceTable.getOrDefault(DEFAULT, null)); + if (endpoints == null || endpoints.isEmpty()) { + throw new IllegalStateException("No available endpoint for this service: " + serviceName + + ". Considering adding a default endpoint, or check if the list is empty?"); + } + + routeList = new ArrayList<>(); + for(BasicMeta.Endpoint epoint: endpoints){ + RouterInfo router = new RouterInfo(); + // ip is first priority + if(!epoint.getIp().isEmpty()) { + router.setHost(epoint.getIp()); + }else{ + router.setHost(epoint.getHostname()); + } + router.setPort(epoint.getPort()); + routeList.add(router); + } + + topicEndpointMapping.put(dstTopic, routeList); + + return routeList; + } + + private boolean isAllowed(Proxy.Topic from, Proxy.Topic to) { + if (hasRule(deny, from, to)) { + return false; + } else if (hasRule(allow, from, to)) { + return true; + } else { + return defaultAllow; + } + } + + // TODO utu: sucks here, need to be optimized on efficiency + private boolean hasRule(Map> target, Proxy.Topic from, Proxy.Topic to) { + boolean result = false; + + if (target == null || target.isEmpty()) { + return result; + } + + Proxy.Topic.Builder fromBuilder = Proxy.Topic.newBuilder(); + Proxy.Topic.Builder toBuilder = Proxy.Topic.newBuilder(); + + Proxy.Topic fromValidator = fromBuilder.setPartyId(from.getPartyId()).setRole(from.getRole()).build(); + Proxy.Topic toValidator = toBuilder.setPartyId(to.getPartyId()).setRole(to.getRole()).build(); + + Set rules = null; + if (target.containsKey(fromValidator)) { + rules = target.get(fromValidator); + } + + int stage = 0; + while (stage < 3 && rules == null) { + switch (stage) { + case 0: + break; + case 1: + fromValidator = fromBuilder.setRole("*").build(); + break; + case 2: + fromValidator = fromBuilder.setPartyId("*").build(); + break; + default: + throw new IllegalStateException("Illegal state when checking from rule"); + } + + if (target.containsKey(fromValidator)) { + rules = target.get(fromValidator); + } + + ++stage; + } + + if (rules == null) { + return result; // false + } + + + stage = 0; + while (stage < 3 && !result) { + switch (stage) { + case 0: + break; + case 1: + toValidator = toBuilder.setRole("*").build(); + break; + case 2: + toValidator = toBuilder.setPartyId("*").build(); + break; + default: + throw new IllegalStateException("Illegal state when checking to rule"); + } + + if (rules.contains(toValidator)) { + result = true; + } + + ++stage; + } + + return result; + } + + + @Scheduled(fixedRate = 10000) + public void loadRouteTable() { + String fileMd5 = FileUtils.fileMd5(routeTableFile); + if(null != fileMd5 && fileMd5.equals(lastFileMd5)){ + return; + } + lastFileMd5 = fileMd5; + + JsonParser jsonParser = new JsonParser(); + JsonReader jsonReader = null; + JsonObject confJson = null; + try { + jsonReader = new JsonReader(new FileReader(routeTableFile)); + confJson = jsonParser.parse(jsonReader).getAsJsonObject(); + } catch (FileNotFoundException e) { + logger.error("File not found: {}", routeTableFile); + throw new RuntimeException(e); + } finally { + if (jsonReader != null) { + try { + jsonReader.close(); + } catch (IOException ignore) { + + } + } + } + initRouteTable(confJson.getAsJsonObject("route_table")); + initPermission(confJson.getAsJsonObject("permission")); + + logger.info("refreshed route table at: {}", routeTableFile); + } + + + @Override + public void afterPropertiesSet() throws Exception { + routeType = RouteTypeConvertor.string2RouteType(routeTypeString); + routeTable = new ConcurrentHashMap<>(); + topicEndpointMapping = new WeakHashMap<>(); + endpointBuilder = BasicMeta.Endpoint.newBuilder(); + + allow = new ConcurrentHashMap<>(); + deny = new ConcurrentHashMap<>(); + defaultAllow = false; + + lastFileMd5 = ""; + } + + + private void initRouteTable(JsonObject confJson) { + Map>> newRouteTable = new ConcurrentHashMap<>(); + + // loop through coordinator + for (Map.Entry coordinatorEntry : confJson.entrySet()) { + String coordinatorKey = coordinatorEntry.getKey(); + JsonObject coordinatorValue = coordinatorEntry.getValue().getAsJsonObject(); + + Map> serviceTable = newRouteTable.get(coordinatorKey); + if (serviceTable == null) { + serviceTable = new ConcurrentHashMap<>(4); + newRouteTable.put(coordinatorKey, serviceTable); + } + + // loop through role in coordinator + for (Map.Entry roleEntry : coordinatorValue.entrySet()) { + String roleKey = roleEntry.getKey(); + JsonArray roleValue = roleEntry.getValue().getAsJsonArray(); + + List endpoints = serviceTable.get(roleKey); + if (endpoints == null) { + endpoints = new ArrayList<>(); + serviceTable.put(roleKey, endpoints); + } + + // loop through endpoints + for (JsonElement endpointElement : roleValue) { + endpointBuilder.clear(); + JsonObject endpointJson = endpointElement.getAsJsonObject(); + + if (endpointJson.has(IP)) { + String targetIp = endpointJson.get(IP).getAsString(); + endpointBuilder.setIp(targetIp); + } + + if (endpointJson.has(PORT)) { + int targetPort = endpointJson.get(PORT).getAsInt(); + endpointBuilder.setPort(targetPort); + } + + if (endpointJson.has(HOSTNAME)) { + String targetHostname = endpointJson.get(HOSTNAME).getAsString(); + endpointBuilder.setHostname(targetHostname); + } + + BasicMeta.Endpoint endpoint = endpointBuilder.build(); + endpoints.add(endpoint); + } + } + } + + routeTable = newRouteTable; + topicEndpointMapping.clear(); + } + + private void initPermission(JsonObject confJson) { + boolean newDefaultAllow = false; + Map> newAllow = new ConcurrentHashMap<>(); + Map> newDeny = new ConcurrentHashMap<>(); + + if (confJson.has("default_allow")) { + newDefaultAllow = confJson.getAsJsonPrimitive("default_allow").getAsBoolean(); + } + + if (confJson.has("allow")) { + initPermissionType(newAllow, confJson.getAsJsonArray("allow")); + } + + if (confJson.has("deny")) { + initPermissionType(newDeny, confJson.getAsJsonArray("deny")); + } + + defaultAllow = newDefaultAllow; + allow = newAllow; + deny = newDeny; + } + + private void initPermissionType(Map> target, JsonArray conf) { + for (JsonElement pairElement : conf) { + JsonObject pair = pairElement.getAsJsonObject(); + + JsonObject from = pair.getAsJsonObject("from"); + Proxy.Topic fromTopic = createTopicFromJson(from); + + JsonObject to = pair.getAsJsonObject("to"); + Proxy.Topic toTopic = createTopicFromJson(to); + + if (!target.containsKey(fromTopic)) { + target.put(fromTopic, new HashSet<>()); + } + Set toTopics = target.get(fromTopic); + toTopics.add(toTopic); + } + } + + private Proxy.Topic createTopicFromJson(JsonObject json) { + Proxy.Topic.Builder topicBuilder = Proxy.Topic.newBuilder(); + if (json.has("coordinator")) { + topicBuilder.setPartyId(json.get("coordinator").getAsString()); + } + + if (json.has("role")) { + topicBuilder.setRole(json.get("role").getAsString()); + } + + return topicBuilder.build(); + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/DefaultServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/DefaultServingRouter.java new file mode 100644 index 00000000..7154ce78 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/DefaultServingRouter.java @@ -0,0 +1,44 @@ +package com.webank.ai.fate.serving.proxy.rpc.router; + +import com.webank.ai.fate.serving.proxy.exceptions.NoRouteInfoException; +import com.webank.ai.fate.serving.proxy.rpc.core.Context; +import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.proxy.rpc.core.Interceptor; +import com.webank.ai.fate.serving.proxy.rpc.core.OutboundPackage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; + +/** + * @Description TODO + * @Author + **/ +@Service +public class DefaultServingRouter implements Interceptor{ + Logger logger = LoggerFactory.getLogger(DefaultServingRouter.class); + + @Autowired + private ZkServingRouter zkServingRouter; + + @Autowired + private ConfigFileBasedServingRouter configFileBasedServingRouter; + + @Override + public void doPreProcess(Context context, InboundPackage inboundPackage,OutboundPackage outboundPackage) throws Exception { + RouterInfo routerInfo =zkServingRouter.route(context, inboundPackage); + if(null == routerInfo){ + routerInfo = configFileBasedServingRouter.route(context, inboundPackage); + } + if(null == routerInfo){ + throw new NoRouteInfoException(); + } + inboundPackage.setRouterInfo(routerInfo); + } + + @Override + public void doPostProcess(Context context, InboundPackage inboundPackage,OutboundPackage outboundPackage) throws Exception { + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteType.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteType.java new file mode 100644 index 00000000..2b983384 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteType.java @@ -0,0 +1,11 @@ +package com.webank.ai.fate.serving.proxy.rpc.router; + +/** + * @Description TODO + * @Author + **/ +public enum RouteType { + RANDOM_ROUTE, + CONSISTENT_HASH_ROUTE +} + diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteTypeConvertor.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteTypeConvertor.java new file mode 100644 index 00000000..ac32e17b --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteTypeConvertor.java @@ -0,0 +1,35 @@ +package com.webank.ai.fate.serving.proxy.rpc.router; + +import com.webank.ai.fate.serving.proxy.common.Dict; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @Description TODO + * @Author + **/ + +public class RouteTypeConvertor { + private static final String ROUTE_TYPE_RANDOM ="random"; + private static final String ROUTE_TYPE_CONSISTENT_HASH ="consistent"; + + private static final Logger logger = LoggerFactory.getLogger(RouteTypeConvertor.class); + + public static RouteType string2RouteType(String routeTypeString) { + RouteType routeType = RouteType.RANDOM_ROUTE; + if(StringUtils.isNotEmpty(routeTypeString)){ + if(routeTypeString.equalsIgnoreCase(ROUTE_TYPE_RANDOM)){ + routeType = RouteType.RANDOM_ROUTE; + } + else if(routeTypeString.equalsIgnoreCase(ROUTE_TYPE_CONSISTENT_HASH)){ + routeType = RouteType.CONSISTENT_HASH_ROUTE; + } + else{ + routeType = RouteType.RANDOM_ROUTE; + logger.error("unknown routeType{}, will use {} instead.", routeTypeString, ROUTE_TYPE_RANDOM); + } + } + return routeType; + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouterInfo.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouterInfo.java new file mode 100644 index 00000000..85cde2ea --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouterInfo.java @@ -0,0 +1,34 @@ +package com.webank.ai.fate.serving.proxy.rpc.router; + +public class RouterInfo { + private String host; + + private Integer port; + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host == null ? null : host.trim(); + } + + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + + + @Override + public String toString(){ + + StringBuilder sb = new StringBuilder(); + sb.append(host).append(":").append(port); + return sb.toString(); + + } + +} \ No newline at end of file diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouterInterface.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouterInterface.java new file mode 100644 index 00000000..3066c381 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouterInterface.java @@ -0,0 +1,10 @@ +package com.webank.ai.fate.serving.proxy.rpc.router; + + +import com.webank.ai.fate.serving.proxy.rpc.core.Context; +import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.proxy.rpc.core.Interceptor; + +public interface RouterInterface extends Interceptor{ + RouterInfo route(Context context, InboundPackage inboundPackage); +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java new file mode 100644 index 00000000..f8c1e920 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java @@ -0,0 +1,122 @@ +package com.webank.ai.fate.serving.proxy.rpc.router; + +import com.webank.ai.fate.api.networking.proxy.Proxy; +import com.webank.ai.fate.register.router.DefaultRouterService; +import com.webank.ai.fate.register.url.URL; +import com.webank.ai.fate.register.zookeeper.ZookeeperRegistry; +import com.webank.ai.fate.serving.proxy.common.Dict; +import com.webank.ai.fate.serving.proxy.rpc.core.Context; +import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcType; +import com.webank.ai.fate.serving.proxy.utils.FederatedModelUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + + +@Service +public class ZkServingRouter extends BaseServingRouter implements InitializingBean{ + @Value("${zk.url}") + private String zkUrl ; + + @Value("${useZkRouter:false}") + private String useZkRouter; + + @Value("${acl.username}") + private String aclUsername; + + @Value("${acl.password}") + private String aclPassword; + + @Value("${port}") + private String port; + + @Value("${routeType}") + private String routeTypeString; + + private RouteType routeType; + + @Value("${coordinator}") + private String selfCoordinator; + + ZookeeperRegistry zookeeperRegistry; + + com.webank.ai.fate.register.router.RouterService zkRouterService; + + + Logger logger = LoggerFactory.getLogger(ZkServingRouter.class); + + @Override + public RouteType getRouteType(){ + return routeType; + } + + @Override + public List getRouterInfoList(Context context, InboundPackage inboundPackage){ + String environment = getEnvironment(context, inboundPackage); + List list =this.zkRouterService.router("serving", environment, context.getServiceName()); + logger.info("try to find zk ,{}:{}:{}, result {}", "serving", environment, context.getServiceName(), list); + List routeList = new ArrayList<>(); + for(URL url: list){ + String urlip = url.getHost(); + int port = url.getPort(); + RouterInfo router = new RouterInfo(); + router.setHost(urlip); + router.setPort(port); + routeList.add(router); + } + return routeList; + } + + // TODO utu: sucks! have to reconstruct the entire protocol of online serving + private String getEnvironment(Context context, InboundPackage inboundPackage){ + if("inference".equals(context.getServiceName())){ + // guest, proxy -> serving + return (String)inboundPackage.getHead().get(Dict.SERVICE_ID); + } + // default unaryCall + if(GrpcType.INTRA_GRPC == context.getGrpcType()){ + // guest, serving -> proxy + return Dict.SELF_ENVIRONMENT; + } else { + Proxy.Packet sourcePacket = (Proxy.Packet) inboundPackage.getBody(); + if(selfCoordinator.equals(sourcePacket.getHeader().getDst().getPartyId())){ + // host, proxy -> serving + return FederatedModelUtils.getModelRouteKey(sourcePacket); + } else { + // exchange, proxy -> proxy + return Dict.SELF_ENVIRONMENT; + } + } + } + + + @Override + public void afterPropertiesSet() throws Exception { + + if("true".equals(useZkRouter)&&StringUtils.isNotEmpty(zkUrl)) { + + System.setProperty("acl.username", aclUsername); + System.setProperty("acl.password", aclPassword); + + zookeeperRegistry = ZookeeperRegistry.getRegistery(zkUrl, Dict.SELF_PROJECT_NAME, Dict.SELF_ENVIRONMENT, Integer.valueOf(port)); + + zookeeperRegistry.subProject("serving"); + + DefaultRouterService defaultRouterService = new DefaultRouterService(); + + defaultRouterService.setRegistry(zookeeperRegistry); + + zkRouterService = defaultRouterService; + } + + routeType = RouteTypeConvertor.string2RouteType(routeTypeString); + + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java new file mode 100644 index 00000000..a0c46324 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java @@ -0,0 +1,109 @@ +package com.webank.ai.fate.serving.proxy.rpc.services; + +import com.alibaba.fastjson.JSON; +import com.google.common.collect.Maps; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.protobuf.ByteString; +import com.webank.ai.fate.api.serving.InferenceServiceGrpc; +import com.webank.ai.fate.api.serving.InferenceServiceProto; +import com.webank.ai.fate.serving.proxy.common.Dict; +import com.webank.ai.fate.serving.proxy.exceptions.NoResultException; +import com.webank.ai.fate.serving.proxy.rpc.core.*; +import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcConnectionPool; +import com.webank.ai.fate.serving.proxy.rpc.router.RouterInfo; +import io.grpc.ManagedChannel; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * @Description TODO + * @Author + **/ +@Service + +// TODO utu: may load from cfg file is a better choice compare to using annotation? +@ProxyService(name = "inference",preChain = { + "overloadMonitor", + "inferenceParamValidator", + "defaultServingRouter"}) + +public class InferenceService extends AbstractServiceAdaptor { + + Logger logger = LoggerFactory.getLogger(InferenceService.class); + @Autowired + GrpcConnectionPool grpcConnectionPool; + + public InferenceService() {} + + @Value("${proxy.grpc.inference.timeout:3000}") + private int timeout; + + @Override + public Map doService(Context context, InboundPackage data, OutboundPackage outboundPackage) { + + Map resultMap = Maps.newHashMap(); + RouterInfo routerInfo = data.getRouterInfo(); + + ManagedChannel managedChannel = null; + + String resultString=null; + try { + try { + logger.info("try to get grpc connection"); + managedChannel = this.grpcConnectionPool.getManagedChannel(routerInfo.getHost(), routerInfo.getPort()); + + } catch (Exception e) { + logger.error("get grpc channel error", e); + throw new NoResultException(); + } + try { + Map reqBodyMap = data.getBody(); + Map reqHeadMap = data.getHead(); + + Map inferenceReqMap = Maps.newHashMap(); + inferenceReqMap.put(Dict.CASE_ID, context.getCaseId()); + inferenceReqMap.put(Dict.SERVICE_ID, reqHeadMap.get(Dict.SERVICE_ID)); + inferenceReqMap.put(Dict.MODEL_ID, reqHeadMap.get(Dict.MODEL_ID)); + inferenceReqMap.put(Dict.MODEL_VERSION, reqHeadMap.get(Dict.MODEL_VERSION)); + inferenceReqMap.put(Dict.FEATURE_DATA,Maps.newHashMap(reqBodyMap)); + + InferenceServiceGrpc.InferenceServiceFutureStub futureStub = InferenceServiceGrpc.newFutureStub(managedChannel); + InferenceServiceProto.InferenceMessage.Builder reqBuilder = InferenceServiceProto.InferenceMessage.newBuilder(); + + logger.info("============================= {}",JSON.toJSONString(inferenceReqMap)); + reqBuilder.setBody(ByteString.copyFrom(JSON.toJSONString(inferenceReqMap).getBytes())); + ListenableFuture resultFuture = futureStub.inference(reqBuilder.build()); + InferenceServiceProto.InferenceMessage result = resultFuture.get(timeout,TimeUnit.MILLISECONDS); + logger.info("routerinfo {} send {} result {}",routerInfo,inferenceReqMap,result); + resultString = new String(result.getBody().toByteArray()); + } catch (Exception e) { + logger.error("get grpc result error", e); + throw new NoResultException(); + } + } + finally { + if(managedChannel!=null) { + grpcConnectionPool.returnPool(managedChannel, routerInfo.getHost(), routerInfo.getPort()); + } + } + if(StringUtils.isNotEmpty(resultString)){ + resultMap = JSON.parseObject(resultString,Map.class); + } + return resultMap; + } + + @Override + protected Map transformErrorMap(Context context,Map data) { + return data; + } +} + + + diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/NotFoundService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/NotFoundService.java new file mode 100644 index 00000000..93c24ab2 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/NotFoundService.java @@ -0,0 +1,31 @@ +package com.webank.ai.fate.serving.proxy.rpc.services; + +import com.alibaba.fastjson.JSON; +import com.google.common.collect.Maps; +import com.webank.ai.fate.serving.proxy.common.Dict; +import com.webank.ai.fate.serving.proxy.exceptions.ErrorCode; +import com.webank.ai.fate.serving.proxy.rpc.core.*; + +import java.util.List; +import java.util.Map; + +@ProxyService(name ="NotFound") +public class NotFoundService extends AbstractServiceAdaptor { + @Override + public String doService(Context context, InboundPackage data, OutboundPackage outboundPackage) { + Map result = Maps.newHashMap(); + result.put(Dict.CODE, ErrorCode.SERVICE_NOT_FOUND); + result.put(Dict.MESSAGE,"SERVICE_NOT_FOUND"); + return JSON.toJSONString(result); + } + + @Override + public OutboundPackage serviceFail(Context context, InboundPackage data, List e) throws Exception { + return null; + } + + @Override + protected String transformErrorMap(Context context,Map data) { + return null; + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java new file mode 100644 index 00000000..f154dd30 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java @@ -0,0 +1,127 @@ +package com.webank.ai.fate.serving.proxy.rpc.services; + +import com.alibaba.fastjson.JSON; +import com.google.common.collect.Maps; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.protobuf.ByteString; +import com.webank.ai.fate.api.networking.proxy.DataTransferServiceGrpc; +import com.webank.ai.fate.api.networking.proxy.Proxy; +import com.webank.ai.fate.serving.proxy.common.Dict; +import com.webank.ai.fate.serving.proxy.exceptions.ErrorCode; +import com.webank.ai.fate.serving.proxy.rpc.core.*; +import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcConnectionPool; +import com.webank.ai.fate.serving.proxy.rpc.router.RouterInfo; +import com.webank.ai.fate.serving.proxy.security.AuthUtils; +import io.grpc.ManagedChannel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * @Description TODO + * @Author + **/ + +@Service +// TODO utu: may load from cfg file is a better choice compare to using annotation? +@ProxyService(name = "unaryCall",preChain = { + "overloadMonitor", + "federationParamValidator", + "defaultAuthentication", + "defaultServingRouter"}) + +public class UnaryCallService extends AbstractServiceAdaptor { + @Autowired + GrpcConnectionPool grpcConnectionPool; + + @Autowired + AuthUtils authUtils; + + + @Value("${proxy.grpc.unaryCall.timeout:3000}") + private int timeout; + + Logger logger = LoggerFactory.getLogger(UnaryCallService.class); + + static final String RETURN_CODE= "retcode"; + + @Override + public Proxy.Packet doService(Context context, InboundPackage data, OutboundPackage outboundPackage) { + + RouterInfo routerInfo = data.getRouterInfo(); + ManagedChannel managedChannel = null; + try { + Proxy.Packet sourcePackage = data.getBody(); + sourcePackage = authUtils.addAuthInfo(sourcePackage); + + managedChannel = grpcConnectionPool.getManagedChannel(routerInfo.getHost(), routerInfo.getPort()); + DataTransferServiceGrpc.DataTransferServiceFutureStub stub1 = DataTransferServiceGrpc.newFutureStub(managedChannel); + + stub1.withDeadlineAfter(timeout, TimeUnit.MILLISECONDS); + + context.setDownstreamBegin(System.currentTimeMillis()); + + ListenableFuture future= stub1.unaryCall(sourcePackage); + + Proxy.Packet packet = future.get(timeout,TimeUnit.MILLISECONDS); + + return packet; + + } catch (Exception e) { + e.printStackTrace(); + logger.error("unaryCall error ",e); + }finally { + if(managedChannel!=null){ + grpcConnectionPool.returnPool(managedChannel, routerInfo.getHost(), routerInfo.getPort()); + } + long end = System.currentTimeMillis(); + context.setDownstreamCost(end - context.getDownstreamBegin()); + } + return null; + } + + @Override + protected Proxy.Packet transformErrorMap(Context context,Map data) { + Proxy.Packet.Builder builder = Proxy.Packet.newBuilder(); + Proxy.Data.Builder dataBuilder = Proxy.Data.newBuilder(); + Map fateMap = Maps.newHashMap(); + fateMap.put("retcode",transformErrorCode(data.get(Dict.CODE).toString())); + fateMap.put("retmsg",data.get(Dict.MESSAGE)); + builder.setBody(dataBuilder.setValue(ByteString.copyFromUtf8(JSON.toJSONString(fateMap)))); + return builder.build(); + } + + + static Map fateErrorCodeMap = Maps.newHashMap(); + + static { + fateErrorCodeMap.put(ErrorCode.PARAM_ERROR,"500"); + fateErrorCodeMap.put(ErrorCode.ROLE_ERROR,"501"); + fateErrorCodeMap.put(ErrorCode.SERVICE_NOT_FOUND,"502"); + fateErrorCodeMap.put(ErrorCode.SYSTEM_ERROR,"503"); + fateErrorCodeMap.put(ErrorCode.LIMIT_ERROR,"504"); + fateErrorCodeMap.put(ErrorCode.QUOTA_ERROR,"505"); + fateErrorCodeMap.put(ErrorCode.ORDER_ERROR,"506"); + fateErrorCodeMap.put(ErrorCode.NET_ERROR,"507"); + fateErrorCodeMap.put(ErrorCode.SHUTDOWN_ERROR,"508"); + fateErrorCodeMap.put(ErrorCode.ROUTER_ERROR,"509"); + } + + private String transformErrorCode(String errorCode){ + String result = fateErrorCodeMap.get(errorCode); + if( result!=null){ + + return fateErrorCodeMap.get(errorCode); + + }else { + return ""; + } + + } + +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java new file mode 100644 index 00000000..8fb71a7b --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java @@ -0,0 +1,160 @@ +/* + * Copyright 2019 The FATE Authors. All Rights Reserved. + * + * 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.webank.ai.fate.serving.proxy.security; + +import com.webank.ai.fate.serving.proxy.utils.EncryptUtil; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; +import com.webank.ai.fate.api.networking.proxy.Proxy; +import com.webank.ai.fate.serving.proxy.utils.ToStringUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.*; + +@Component +public class AuthUtils implements InitializingBean{ + private static final Logger LOGGER = LogManager.getLogger(); + private static final String confFilePath = System.getProperty("authFile"); + private static Map KEY_SECRET_MAP = new HashMap<>(); + private static Map PARTYID_KEY_MAP = new HashMap<>(); + private static int validRequestTimeoutSecond = 10; + private static String applyId = ""; + private static boolean ifUseAuth = false; + private static String selfPartyId = ""; + @Autowired + private ToStringUtils toStringUtils; + + @Scheduled(fixedRate = 10000) + public void loadConfig(){ + JsonParser jsonParser = new JsonParser(); + JsonReader jsonReader = null; + JsonObject jsonObject = null; + try { + jsonReader = new JsonReader(new FileReader(confFilePath)); + jsonObject = jsonParser.parse(jsonReader).getAsJsonObject(); + } catch (FileNotFoundException e) { + LOGGER.error("File not found: {}", confFilePath); + throw new RuntimeException(e); + } finally { + if (jsonReader != null) { + try { + jsonReader.close(); + } catch (IOException ignore) { + } + } + } + selfPartyId = jsonObject.get("self_party_id").getAsString(); + ifUseAuth = jsonObject.get("if_use_auth").getAsBoolean(); + validRequestTimeoutSecond = jsonObject.get("request_expire_seconds").getAsInt(); + applyId = jsonObject.get("apply_id").getAsString(); + + JsonArray jsonArray = jsonObject.getAsJsonArray("access_keys"); + Gson gson = new Gson(); + List allowKeys = gson.fromJson(jsonArray, ArrayList.class); + KEY_SECRET_MAP.clear(); + for (Map allowKey : allowKeys) { + KEY_SECRET_MAP.put(allowKey.get("app_key").toString(), allowKey.get("app_secret").toString()); + PARTYID_KEY_MAP.put(allowKey.get("party_id").toString(), allowKey.get("app_key").toString()); + } + LOGGER.debug("refreshed auth cfg using file {}.", confFilePath); + } + + private String getSecret(String appKey) { + return KEY_SECRET_MAP.get(appKey); + } + + private String getAppKey(String partyId) { + return PARTYID_KEY_MAP.get(partyId); + } + + private String calSignature(Proxy.Metadata header, Proxy.Data body, long timestamp, String appKey) throws Exception { + String signature = ""; + String appSecret = getSecret(appKey); + if (StringUtils.isEmpty(appSecret)) { + LOGGER.error("appSecret not found"); + return signature; + } + String encryptText = String.valueOf(timestamp) + "\n" + + toStringUtils.toOneLineString(header) + "\n" + + toStringUtils.toOneLineString(body); + encryptText = new String(encryptText.getBytes(), EncryptUtil.UTF8); + signature = Base64.getEncoder().encodeToString(EncryptUtil.HmacSHA1Encrypt(encryptText, appSecret)); + return signature; + } + + public Proxy.Packet addAuthInfo(Proxy.Packet packet) throws Exception { + if(ifUseAuth && !StringUtils.equals(selfPartyId, packet.getHeader().getDst().getPartyId())) { + Proxy.Packet.Builder packetBuilder = packet.toBuilder(); + + Proxy.AuthInfo.Builder authBuilder = packetBuilder.getAuthBuilder(); + long timestamp = System.currentTimeMillis(); + authBuilder.setTimestamp(timestamp); + String appKey = getAppKey(selfPartyId); + authBuilder.setAppKey(appKey); + String signature = calSignature(packet.getHeader(), packet.getBody(), timestamp, appKey); + authBuilder.setSignature(signature); + + packetBuilder.setAuth(authBuilder.build()); + return packetBuilder.build(); + } + return packet; + } + + public boolean checkAuthentication(Proxy.Packet packet) throws Exception { + if(ifUseAuth) { + // check timestamp + long currentTimeMillis = System.currentTimeMillis(); + long requestTimeMillis = packet.getAuth().getTimestamp(); + if (currentTimeMillis >= (requestTimeMillis + validRequestTimeoutSecond * 1000)) { + LOGGER.error("receive an expired request, currentTimeMillis:{}, requestTimeMillis{}.", currentTimeMillis, requestTimeMillis); + return false; + } + // check signature + String reqSignature = packet.getAuth().getSignature(); + String validSignature = calSignature(packet.getHeader(), packet.getBody(), requestTimeMillis, packet.getAuth().getAppKey()); + if (!StringUtils.equals(reqSignature, validSignature)) { + LOGGER.error("invalid signature, request:{}, valid:{}", reqSignature, validSignature); + return false; + } + } + return true; + } + + @Override + public void afterPropertiesSet() throws Exception { + + try { + loadConfig(); + } catch (Throwable e) { + LOGGER.error("load authencation keys error", e); + } + + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/DefaultAuthentication.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/DefaultAuthentication.java new file mode 100644 index 00000000..1e3d562d --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/DefaultAuthentication.java @@ -0,0 +1,44 @@ +package com.webank.ai.fate.serving.proxy.security; + +import com.webank.ai.fate.api.networking.proxy.Proxy; +import com.webank.ai.fate.serving.proxy.exceptions.InvalidRoleInfoException; +import com.webank.ai.fate.serving.proxy.rpc.core.Context; +import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.proxy.rpc.core.Interceptor; +import com.webank.ai.fate.serving.proxy.rpc.core.OutboundPackage; +import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * 检查用户权限 + */ +@Service +public class DefaultAuthentication implements Interceptor { + + Logger logger = LoggerFactory.getLogger(DefaultAuthentication.class); + + @Autowired + private AuthUtils authUtils; + + @Override + public void doPreProcess(Context context, InboundPackage inboundPackage, OutboundPackage outboundPackage) throws Exception { + if(GrpcType.INTRA_GRPC == context.getGrpcType()){ + return; + } + + Proxy.Packet sourcePackage = (Proxy.Packet) inboundPackage.getBody(); + boolean isAuthPass = authUtils.checkAuthentication(sourcePackage); + if (!isAuthPass) { + logger.error("invalid signature"); + throw new InvalidRoleInfoException(); + } + } + + @Override + public void doPostProcess(Context context, InboundPackage inboundPackage,OutboundPackage outboundPackage) throws Exception { + + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/FederationParamValidator.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/FederationParamValidator.java new file mode 100644 index 00000000..66d6d128 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/FederationParamValidator.java @@ -0,0 +1,30 @@ +package com.webank.ai.fate.serving.proxy.security; + +import com.google.common.base.Preconditions; +import com.webank.ai.fate.serving.proxy.rpc.core.Context; +import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.proxy.rpc.core.Interceptor; +import com.webank.ai.fate.serving.proxy.rpc.core.OutboundPackage; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +/** + * @Description TODO + * @Author + **/ +@Service +public class FederationParamValidator implements Interceptor { + + @Override + public void doPreProcess(Context context, InboundPackage inboundPackage, OutboundPackage outboundPackage) throws Exception { + + Preconditions.checkArgument(StringUtils.isNotEmpty(context.getHostAppid()),"host id is null"); + Preconditions.checkArgument(StringUtils.isNotEmpty(context.getGuestAppId()),"guest id is null"); + Preconditions.checkArgument(StringUtils.isNotEmpty(context.getCaseId()),"case id is null"); + } + + @Override + public void doPostProcess(Context context, InboundPackage inboundPackage,OutboundPackage outboundPackage) throws Exception { + + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java new file mode 100644 index 00000000..29198414 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java @@ -0,0 +1,29 @@ +package com.webank.ai.fate.serving.proxy.security; + +import com.google.common.base.Preconditions; +import com.webank.ai.fate.serving.proxy.common.Dict; +import com.webank.ai.fate.serving.proxy.rpc.core.Context; +import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.proxy.rpc.core.Interceptor; +import com.webank.ai.fate.serving.proxy.rpc.core.OutboundPackage; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +/** + * @Description TODO + * @Author + **/ +@Service +public class InferenceParamValidator implements Interceptor{ + + @Override + public void doPreProcess(Context context, InboundPackage inboundPackage,OutboundPackage outboundPackage) throws Exception { + Preconditions.checkArgument(StringUtils.isNotEmpty(context.getCaseId()),"case id is null"); + Preconditions.checkArgument(StringUtils.isNotEmpty((String) inboundPackage.getHead().get(Dict.SERVICE_ID)), Dict.SERVICE_ID + " is null"); + } + + @Override + public void doPostProcess(Context context, InboundPackage inboundPackage,OutboundPackage outboundPackage) throws Exception { + + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/OverloadMonitor.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/OverloadMonitor.java new file mode 100644 index 00000000..33f94ba2 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/OverloadMonitor.java @@ -0,0 +1,42 @@ +package com.webank.ai.fate.serving.proxy.security; + +import com.alibaba.csp.sentinel.Entry; +import com.alibaba.csp.sentinel.SphU; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.webank.ai.fate.serving.proxy.rpc.core.Context; +import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.proxy.rpc.core.Interceptor; +import com.webank.ai.fate.serving.proxy.rpc.core.OutboundPackage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +/** + * @Description TODO + * @Author + **/ +@Service +public class OverloadMonitor implements Interceptor{ + Logger logger = LoggerFactory.getLogger(OverloadMonitor.class); + + @Override + public void doPreProcess(Context context, InboundPackage inboundPackage,OutboundPackage outboundPackage) throws Exception { + Entry entry = null; + try { + SphU.entry(context.getServiceName()); + } catch (BlockException ex){ + logger.warn("request was block by overload monitor, serviceName:{}.", context.getServiceName()); + throw ex; + } + finally { + if (entry != null) { + entry.exit(); + } + } + } + + @Override + public void doPostProcess(Context context, InboundPackage inboundPackage,OutboundPackage outboundPackage) throws Exception { + + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/EncryptUtil.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/EncryptUtil.java new file mode 100644 index 00000000..1ebcdcb9 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/EncryptUtil.java @@ -0,0 +1,37 @@ +package com.webank.ai.fate.serving.proxy.utils; + +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.util.Base64; + +public class EncryptUtil { + + public static final String UTF8 = "UTF-8"; + private static final String HMACSHA1 = "HmacSHA1"; + + public static byte[] HmacSHA1Encrypt(String encryptText, String encryptKey) throws Exception { + byte[] data = encryptKey.getBytes(UTF8); + SecretKey secretKey = new SecretKeySpec(data, HMACSHA1); + Mac mac = Mac.getInstance(HMACSHA1); + mac.init(secretKey); + + byte[] text = encryptText.getBytes(UTF8); + return mac.doFinal(text); + } + + public static String generateSignature(String applyId, String timestamp, String nonce, + String appKey, String appSecret, String uri, String body) { + try { + String encryptText = applyId + "\n" + timestamp + "\n" + nonce + "\n" + appKey + "\n" + uri + "\n" + body; + encryptText = new String(encryptText.getBytes(), EncryptUtil.UTF8); + return Base64.getEncoder().encodeToString(EncryptUtil.HmacSHA1Encrypt(encryptText, appSecret)); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + + +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/FederatedModelUtils.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/FederatedModelUtils.java new file mode 100644 index 00000000..730139a6 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/FederatedModelUtils.java @@ -0,0 +1,32 @@ +package com.webank.ai.fate.serving.proxy.utils; + +import com.alibaba.fastjson.JSON; +import com.webank.ai.fate.api.networking.proxy.Proxy; +import com.webank.ai.fate.serving.core.bean.EncryptMethod; +import com.webank.ai.fate.serving.core.bean.HostFederatedParams; +import com.webank.ai.fate.serving.core.bean.ModelInfo; +import com.webank.ai.fate.serving.core.utils.EncryptUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; + +public class FederatedModelUtils { + + private static final String modelKeySeparator = "&"; + + public static String genModelKey(String name, String namespace) { + return StringUtils.join(Arrays.asList(name, namespace), modelKeySeparator); + } + + public static String getModelRouteKey(Proxy.Packet packet) { + String data = packet.getBody().getValue().toStringUtf8(); + HostFederatedParams requestData = JSON.parseObject(data, HostFederatedParams.class); + ModelInfo partnerModelInfo = requestData.getPartnerModelInfo(); + String key =genModelKey(partnerModelInfo.getName(), partnerModelInfo.getNamespace()); + String md5Key = EncryptUtils.encrypt(key, EncryptMethod.MD5); + return md5Key; + } + + + +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/FileUtils.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/FileUtils.java new file mode 100644 index 00000000..0157c36e --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/FileUtils.java @@ -0,0 +1,35 @@ +package com.webank.ai.fate.serving.proxy.utils; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.util.DigestUtils; + +public class FileUtils { + private static final Logger LOGGER = LogManager.getLogger(FileUtils.class); + + public static String fileMd5(String filePath) { + InputStream in = null; + try { + in = new FileInputStream(filePath); + return DigestUtils.md5DigestAsHex(in); + } catch (Exception e) { + LOGGER.error(e); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + LOGGER.error(e); + } + } + } + return null; + } + + + +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/ToStringUtils.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/ToStringUtils.java new file mode 100644 index 00000000..b0c7d941 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/ToStringUtils.java @@ -0,0 +1,63 @@ +/* + * Copyright 2019 The FATE Authors. All Rights Reserved. + * + * 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.webank.ai.fate.serving.proxy.utils; + +import com.google.protobuf.Message; +import com.google.protobuf.util.JsonFormat; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Component +@Scope("prototype") +public class ToStringUtils { + private static final String NULL_STRING = "[null]"; + private static final String LEFT_BRACKET = "["; + private static final String RIGHT_BRACKET = "]"; + private static final String LEFT_BRACE = "{"; + private static final String RIGHT_BRACE = "}"; + private static final String LEFT_PARENTHESIS = "("; + private static final String RIGHT_PARENTHESIS = ")"; + private static final String COLON = ":"; + private static final String SEMICOLON = ";"; + private static final String COMMA = ","; + + private static final Logger LOGGER = LogManager.getLogger(ToStringUtils.class); + + private JsonFormat.Printer protoPrinter = JsonFormat.printer() + .preservingProtoFieldNames() + .omittingInsignificantWhitespace(); + + public String toOneLineString(Message target) { + String result = "[null]"; + + if (target == null) { + LOGGER.info("target is null"); + return result; + } + + try { + result = protoPrinter.print(target); + } catch (Exception e) { + LOGGER.info(ExceptionUtils.getStackTrace(e)); + } + + return result; + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/WebUtil.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/WebUtil.java new file mode 100644 index 00000000..12aa0ab7 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/WebUtil.java @@ -0,0 +1,68 @@ +package com.webank.ai.fate.serving.proxy.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletRequest; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * @Description TODO + * @Author + **/ +public class WebUtil { + + static Logger logger = LoggerFactory.getLogger(WebUtil.class); + + /** + * 获取来源ip + * @param request + * @return + */ + public static String getIpAddr(HttpServletRequest request) { + + String ipAddress =""; + try{ + ipAddress = request.getHeader("x-forwarded-for"); + if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { + ipAddress = request.getHeader("Proxy-Client-IP"); + } + if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { + ipAddress = request.getHeader("WL-Proxy-Client-IP"); + } + if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { + ipAddress = request.getRemoteAddr(); + String localIp = "127.0.0.1"; + String localIpv6 = "0:0:0:0:0:0:0:1"; + if (ipAddress.equals(localIp) || ipAddress.equals(localIpv6)) { + // 根据网卡取本机配置的IP + InetAddress inet = null; + try { + inet = InetAddress.getLocalHost(); + ipAddress = inet.getHostAddress(); + } catch (UnknownHostException e) { + e.printStackTrace(); + } + } + } + // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 + String ipSeparate = ","; + int ipLength = 15; + if (ipAddress != null && ipAddress.length() > ipLength) { + if (ipAddress.indexOf(ipSeparate) > 0) { + ipAddress = ipAddress.substring(0, ipAddress.indexOf(ipSeparate)); + } + } + }catch(Exception e){ + logger.error("get remote ip error",e); + } + + return ipAddress; + } + + + + + +} diff --git a/serving-proxy/src/main/resources/application.properties b/serving-proxy/src/main/resources/application.properties new file mode 100644 index 00000000..c7d2ee5f --- /dev/null +++ b/serving-proxy/src/main/resources/application.properties @@ -0,0 +1,32 @@ +server.port=8087 +server.tomcat.max-threads=500 +server.tomcat.max-connections=10000 +spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver +management.endpoints.web.exposure.include=* +spring.boot.admin.client.url=http://localhost:8089 +spring.http.encoding.charset=UTF-8 +spring.http.encoding.enabled=true +server.tomcat.uri-encoding=UTF-8 +openRmb=true +proxy.rmb.testMode=108 +redis.host=localhost +redis.port=6379 +redis.password= +redis.timeout=2000 +sentinel.path= + +logging.level.main.blog.mapper=info +spring.datasource.url=jdbc:mysql://localhost:3306/fdn?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true +spring.datasource.username=root +spring.datasource.password= + + +useZkRouter=true +zk.url=zookeeper://localhost:2181 + +spring.application.name=api-proxy +spring.cloud.sentinel.transport.dashboard=localhost:18080 + +authFile=/data/projects/fate/fate-serving/proxy/config/auth_config.json +acl.username=fate +acl.password=fate \ No newline at end of file diff --git a/serving-proxy/src/main/resources/auth_config.json b/serving-proxy/src/main/resources/auth_config.json new file mode 100644 index 00000000..27a1426d --- /dev/null +++ b/serving-proxy/src/main/resources/auth_config.json @@ -0,0 +1,15 @@ +{ + "self_party_id": "9999", + "if_use_auth": false, + "request_expire_seconds": 8, + "apply_id": "20191119163236256", + "access_keys": [{ + "party_id": "9999", + "app_key": "11", + "app_secret": "11111" + }, { + "party_id": "10000", + "app_key": "22", + "app_secret": "22222" + }] +} From 8079934d9f37cb54b2aa096da6a185561c34d740 Mon Sep 17 00:00:00 2001 From: kaideng Date: Thu, 19 Dec 2019 15:56:50 +0800 Subject: [PATCH 040/190] fdn-feature Signed-off-by: kaideng --- fate-serving-core/pom.xml | 5 ++ .../fate/serving/core/bean/CacheManager.java | 53 +------------------ .../ai/fate/serving/core/bean/Dict.java | 2 + .../core/manager/DefaultCacheManager.java | 51 +++++++++++++++--- .../serving/federatedml/PipelineTask.java | 28 ++++++---- .../serving/federatedml/model/BaseModel.java | 9 ++-- .../register/provider/FateServerBuilder.java | 4 -- .../register/zookeeper/ZookeeperRegistry.java | 34 ------------ .../guest/DefaultGuestInferenceProvider.java | 2 +- .../serving/service/InferenceService.java | 1 - 10 files changed, 80 insertions(+), 109 deletions(-) diff --git a/fate-serving-core/pom.xml b/fate-serving-core/pom.xml index ae143808..14fc13a3 100644 --- a/fate-serving-core/pom.xml +++ b/fate-serving-core/pom.xml @@ -70,6 +70,11 @@ jedis ${jedis.version} + + commons-codec + commons-codec + 1.12 + diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/CacheManager.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/CacheManager.java index 299688c3..eb1c854a 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/CacheManager.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/CacheManager.java @@ -25,66 +25,17 @@ public interface CacheManager { static CacheManager getInstance() { return (CacheManager) ApplicationHolder.applicationContext.getBean(CacheManager.class); } - ; - public void store(Context context, String key, Object object); public T restore(Context context, String key, Class dataType); public void putInferenceResultCache(Context context, String partyId, String caseid, ReturnResult returnResult); -// { -// -// long beginTime =System.currentTimeMillis(); -// try { -// -// String inferenceResultCacheKey = generateInferenceResultCacheKey(partyId, caseid); -// boolean putCacheSuccess = putIntoCache(inferenceResultCacheKey, CacheType.INFERENCE_RESULT, returnResult); -// if (putCacheSuccess) { -// LOGGER.info("Put {} inference result into cache", inferenceResultCacheKey); -// } -// }finally { -// long end =System.currentTimeMillis(); -// LOGGER.info("caseid {} putInferenceResultCache cost {}",context.getCaseId(),end - beginTime); -// -// } -// } public ReturnResult getInferenceResultCache(String partyId, String caseid); -// { -// String inferenceResultCacheKey = generateInferenceResultCacheKey(partyId, caseid); -// ReturnResult returnResult = getFromCache(inferenceResultCacheKey, CacheType.INFERENCE_RESULT); -// if (returnResult != null) { -// LOGGER.info("Get {} inference result from cache.", inferenceResultCacheKey); -// } -// return returnResult; -// } - - public void putRemoteModelInferenceResult(FederatedParty remoteParty, FederatedRoles federatedRoles, Map featureIds, ReturnResult returnResult); -// { -// if (! Boolean.parseBoolean(Configuration.getProperty("remoteModelInferenceResultCacheSwitch"))){ -// return; -// } -// String remoteModelInferenceResultCacheKey = generateRemoteModelInferenceResultCacheKey(remoteParty, federatedRoles, featureIds); -// boolean putCacheSuccess = putIntoCache(remoteModelInferenceResultCacheKey, CacheType.REMOTE_MODEL_INFERENCE_RESULT, returnResult); -// if (putCacheSuccess) { -// LOGGER.info("Put {} remote model inference result into cache.", remoteModelInferenceResultCacheKey); -// } -// } - - public ReturnResult getRemoteModelInferenceResult(FederatedParty remoteParty, FederatedRoles federatedRoles, Map featureIds); -// { -// if (! Boolean.parseBoolean(Configuration.getProperty("remoteModelInferenceResultCacheSwitch"))){ -// return null; -// } -// String remoteModelInferenceResultCacheKey = generateRemoteModelInferenceResultCacheKey(remoteParty, federatedRoles, featureIds); -// ReturnResult returnResult = getFromCache(remoteModelInferenceResultCacheKey, CacheType.REMOTE_MODEL_INFERENCE_RESULT); -// if (returnResult != null) { -// LOGGER.info("Get {} remote model inference result from cache.", remoteModelInferenceResultCacheKey); -// } -// return returnResult; -// } + public ReturnResult getRemoteModelInferenceResult(FederatedParams guestFederatedParams ); + public void putRemoteModelInferenceResult(FederatedParams guestFederatedParams, ReturnResult returnResult); } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java index 42c51901..69efa0e6 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java @@ -24,6 +24,7 @@ public class Dict { public static final String SCORE = "score"; public static final String SEQNO = "seqno"; public static final String NONE = "NONE"; + public static final String PIPLELINE_IN_MODEL = "pipeline.pipeline:Pipeline"; public static final String POST_PROCESSING_CONFIG = "InferencePostProcessingAdapter"; public static final String PRE_PROCESSING_CONFIG = "InferencePreProcessingAdapter"; public static final String GET_REMOTE_PARTY_RESULT = "getRemotePartyResult"; @@ -139,6 +140,7 @@ public class Dict { public static final String AUTH_FILE = "authFile"; public static final String ENCRYPT_TYPE = "encrypt_type"; + public static final String MD5_SALT = "$1$ML"; diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java index 00ddbe72..35843399 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java @@ -17,12 +17,15 @@ package com.webank.ai.fate.serving.core.manager; import com.google.common.base.Charsets; +import com.google.common.base.Preconditions; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import com.google.common.collect.Maps; import com.google.common.hash.Hashing; import com.alibaba.fastjson.JSON; //import com.webank.ai.fate.core.utils.ObjectTransform; import com.webank.ai.fate.serving.core.bean.*; +import org.apache.commons.codec.digest.Md5Crypt; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -144,26 +147,39 @@ public ReturnResult getInferenceResultCache(String partyId, String caseid) { } @Override - public void putRemoteModelInferenceResult(FederatedParty remoteParty, FederatedRoles federatedRoles, Map featureIds, ReturnResult returnResult) { + public void putRemoteModelInferenceResult(FederatedParams guestFederatedParams, ReturnResult returnResult) { if (!Boolean.parseBoolean(Configuration.getProperty("remoteModelInferenceResultCacheSwitch"))) { return; } - String remoteModelInferenceResultCacheKey = generateRemoteModelInferenceResultCacheKey(remoteParty, federatedRoles, featureIds); + String remoteModelInferenceResultCacheKey = generateRemoteModelInferenceResultCacheKey(guestFederatedParams); boolean putCacheSuccess = putIntoCache(remoteModelInferenceResultCacheKey, CacheType.REMOTE_MODEL_INFERENCE_RESULT, returnResult); if (putCacheSuccess) { - LOGGER.info("Put {} remote model inference result into cache.", remoteModelInferenceResultCacheKey); + LOGGER.info("put {} remote model inference result into cache", remoteModelInferenceResultCacheKey); } } +// +// @Override +// public ReturnResult getRemoteModelInferenceResult(FederatedParty remoteParty, FederatedRoles federatedRoles, Map featureIds) { +// if (!Boolean.parseBoolean(Configuration.getProperty("remoteModelInferenceResultCacheSwitch"))) { +// return null; +// } +// String remoteModelInferenceResultCacheKey = generateRemoteModelInferenceResultCacheKey(remoteParty, federatedRoles, featureIds); +// ReturnResult returnResult = getFromCache(remoteModelInferenceResultCacheKey, CacheType.REMOTE_MODEL_INFERENCE_RESULT); +// if (returnResult != null) { +// LOGGER.info("Get {} remote model inference result from cache.", remoteModelInferenceResultCacheKey); +// } +// return returnResult; +// } @Override - public ReturnResult getRemoteModelInferenceResult(FederatedParty remoteParty, FederatedRoles federatedRoles, Map featureIds) { + public ReturnResult getRemoteModelInferenceResult(FederatedParams guestFederatedParams) { if (!Boolean.parseBoolean(Configuration.getProperty("remoteModelInferenceResultCacheSwitch"))) { return null; } - String remoteModelInferenceResultCacheKey = generateRemoteModelInferenceResultCacheKey(remoteParty, federatedRoles, featureIds); + String remoteModelInferenceResultCacheKey = generateRemoteModelInferenceResultCacheKey(guestFederatedParams); ReturnResult returnResult = getFromCache(remoteModelInferenceResultCacheKey, CacheType.REMOTE_MODEL_INFERENCE_RESULT); if (returnResult != null) { - LOGGER.info("Get {} remote model inference result from cache.", remoteModelInferenceResultCacheKey); + LOGGER.info("get {} remote model inference result from cache", remoteModelInferenceResultCacheKey); } return returnResult; } @@ -272,6 +288,29 @@ private String generateInferenceResultCacheKey(String partyId, String caseid) { return StringUtils.join(Arrays.asList(partyId, caseid), "_"); } + + private String generateRemoteModelInferenceResultCacheKey(FederatedParams federatedParams){ + Preconditions.checkNotNull(federatedParams); + Preconditions.checkNotNull(federatedParams.getModelInfo()); + Preconditions.checkNotNull(federatedParams.getFeatureIdMap()); + String namespace = federatedParams.getModelInfo().getNamespace(); + String name = federatedParams.getModelInfo().getName(); + // Set featureIdKey = federatedParams.getFeatureIdMap(); + + Map sortedMap = Maps.newTreeMap(); + federatedParams.getFeatureIdMap().forEach((k,v)->{ + sortedMap.put(k,v); + }); + StringBuffer sb = new StringBuffer(); + sb.append(namespace); + sb.append(name); + sortedMap.forEach((k,v)->{ + sb.append(k).append(v); + }); + String md5key = Md5Crypt.md5Crypt(sb.toString().getBytes(), Dict.MD5_SALT); + return md5key; + } + private String generateRemoteModelInferenceResultCacheKey(FederatedParty remoteParty, FederatedRoles federatedRoles, Map featureIds) { String remotePartyKey = StringUtils.join(Arrays.asList(remoteParty.getRole(), remoteParty.getPartyId(), FederatedUtils.federatedRolesIdentificationString(federatedRoles)), "#"); Object[] featureIdKeys = featureIds.keySet().toArray(); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java index c9195d1a..7843512e 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java @@ -17,6 +17,8 @@ package com.webank.ai.fate.serving.federatedml; +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; import com.webank.ai.fate.core.mlmodel.buffer.PipelineProto; import com.webank.ai.fate.serving.core.bean.*; import com.webank.ai.fate.serving.federatedml.model.BaseModel; @@ -25,23 +27,24 @@ import java.util.*; +import static com.webank.ai.fate.serving.core.bean.Dict.PIPLELINE_IN_MODEL; + public class PipelineTask { private static final Logger LOGGER = LogManager.getLogger(); private List pipeLineNode = new ArrayList<>(); private Map modelMap = new HashMap(); private DSLParser dslParser = new DSLParser(); private String modelPackage = "com.webank.ai.fate.serving.federatedml.model"; - public BaseModel getModelByComponentName(String name) { return this.modelMap.get(name); } - public int initModel(Map modelProtoMap) { - LOGGER.info("start init Pipeline"); + LOGGER.info("start init pipeline,model components {}",modelProtoMap.keySet()); try { Map newModelProtoMap = changeModelProto(modelProtoMap); - String pipelineProtoName = "pipeline.pipeline:Pipeline"; - PipelineProto.Pipeline pipeLineProto = PipelineProto.Pipeline.parseFrom(newModelProtoMap.get(pipelineProtoName)); + LOGGER.info("after parse pipeline {}",newModelProtoMap.keySet()); + Preconditions.checkArgument(newModelProtoMap.get(PIPLELINE_IN_MODEL)!=null); + PipelineProto.Pipeline pipeLineProto = PipelineProto.Pipeline.parseFrom(newModelProtoMap.get(PIPLELINE_IN_MODEL)); String dsl = pipeLineProto.getInferenceDsl().toStringUtf8(); //inference_dsl; dslParser.parseDagFromDSL(dsl); ArrayList components = dslParser.getAllComponent(); @@ -50,7 +53,7 @@ public int initModel(Map modelProtoMap) { for (int i = 0; i < components.size(); ++i) { String componentName = components.get(i); String className = componentModuleMap.get(componentName); - LOGGER.info("Start get className:{}", className); + LOGGER.info("try to get class:{}", className); try { Class modelClass = Class.forName(this.modelPackage + "." + className); BaseModel mlNode = (BaseModel) modelClass.getConstructor().newInstance(); @@ -66,10 +69,10 @@ public int initModel(Map modelProtoMap) { LOGGER.warn("Can not instance {} class", className); } } - } catch (Exception ex) { ex.printStackTrace(); - LOGGER.info("Pipeline init catch error:{}", ex); + LOGGER.info("initModel error:{}", ex); + throw new RuntimeException("initModel error"); } LOGGER.info("Finish init Pipeline"); return StatusCode.OK; @@ -113,7 +116,14 @@ public Map predict(Context context, Map inputDat } LOGGER.info("Finish Pipeline predict"); - return outputData.get(outputData.size() - 1); + + if(outputData.size()>0){ + return outputData.get(outputData.size() - 1); + }else{ + return Maps.newHashMap(); + } + + } private HashMap changeModelProto(Map modelProtoMap) { diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java index dccbc089..6f9e1642 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java @@ -17,6 +17,7 @@ package com.webank.ai.fate.serving.federatedml.model; import com.alibaba.fastjson.JSON; +import com.google.common.base.Preconditions; import com.google.protobuf.ByteString; import com.webank.ai.eggroll.core.utils.ObjectTransform; import com.webank.ai.fate.api.networking.proxy.DataTransferServiceGrpc; @@ -30,6 +31,7 @@ import io.grpc.ManagedChannel; import io.grpc.netty.shaded.io.grpc.netty.NegotiationType; import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -103,9 +105,9 @@ protected ReturnResult getFederatedPredict(Context context, FederatedParams gues Map featureIds = (Map) guestFederatedParams.getFeatureIdMap(); FederatedParty dstParty = new FederatedParty(Dict.HOST, federatedRoles.getRole(Dict.HOST).get(0)); if (useCache) { - ReturnResult remoteResultFromCache = CacheManager.getInstance().getRemoteModelInferenceResult(dstParty, federatedRoles, featureIds); + ReturnResult remoteResultFromCache = CacheManager.getInstance().getRemoteModelInferenceResult(guestFederatedParams); if (remoteResultFromCache != null) { - LOGGER.info("caseid {} get remote party model inference result from cache.", context.getCaseId()); + LOGGER.info("caseid {} get remote party model inference result from cache", context.getCaseId()); context.putData(Dict.GET_REMOTE_PARTY_RESULT, false); context.hitCache(true); remoteResult = remoteResultFromCache; @@ -125,7 +127,7 @@ protected ReturnResult getFederatedPredict(Context context, FederatedParams gues context.putData(Dict.GET_REMOTE_PARTY_RESULT, true); remoteResult = getFederatedPredictFromRemote(context, srcParty, dstParty, hostFederatedParams, remoteMethodName); if (useCache&& remoteResult!=null&&remoteResult.getRetcode()==0) { - CacheManager.getInstance().putRemoteModelInferenceResult(dstParty, federatedRoles, featureIds, remoteResult); + CacheManager.getInstance().putRemoteModelInferenceResult(guestFederatedParams, remoteResult); LOGGER.info("caseid {} get remote party model inference result from federated request.", context.getCaseId()); } return remoteResult; @@ -188,6 +190,7 @@ protected ReturnResult getFederatedPredictFromRemote(Context context, FederatedP address = ip + ":" + port; } } + Preconditions.checkArgument(StringUtils.isNotEmpty(address)); ManagedChannel channel1 = grpcConnectionPool.getManagedChannel(address); try { diff --git a/register/src/main/java/com/webank/ai/fate/register/provider/FateServerBuilder.java b/register/src/main/java/com/webank/ai/fate/register/provider/FateServerBuilder.java index f22078d3..c54888b2 100644 --- a/register/src/main/java/com/webank/ai/fate/register/provider/FateServerBuilder.java +++ b/register/src/main/java/com/webank/ai/fate/register/provider/FateServerBuilder.java @@ -296,17 +296,13 @@ public String getZkRegisterLocation() { public FateServerBuilder setZkRegisterLocation(String zkRegisterLocation) { this.zkRegisterLocation = zkRegisterLocation; - return this; } @Override public Server build() { - Server server = serverBuilder.build(); - FateServer fateServer = new FateServer(server); - fateServer.setEnvironment(environment); fateServer.setProject(project); diff --git a/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java b/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java index b060b49c..87a64100 100644 --- a/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java +++ b/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java @@ -339,40 +339,6 @@ public void doSubscribe(final URL url, final NotifyListener listener) { } - - -// if (ANY_VALUE.equals(url.getServiceInterface())) { -// String root = toRootPath(); -// ConcurrentMap listeners = zkListeners.get(url); -// if (listeners == null) { -// zkListeners.putIfAbsent(url, new ConcurrentHashMap<>()); -// listeners = zkListeners.get(url); -// } -// ChildListener zkListener = listeners.get(listener); -// if (zkListener == null) { -// listeners.putIfAbsent(listener, (parentPath, currentChilds) -> { -// for (String child : currentChilds) { -// child = URL.decode(child); -// if (!anyServices.contains(child)) { -// anyServices.add(child); -// subscribe(url.setPath(child).addParameters(INTERFACE_KEY, child, -// Constants.CHECK_KEY, String.valueOf(false)), listener); -// } -// } -// }); -// zkListener = listeners.get(listener); -// } -// zkClient.create(root, false); -// List services = zkClient.addChildListener(root, zkListener); -// if (CollectionUtils.isNotEmpty(services)) { -// for (String service : services) { -// service = URL.decode(service); -// anyServices.add(service); -// subscribe(url.setPath(service).addParameters(INTERFACE_KEY, service, -// Constants.CHECK_KEY, String.valueOf(false)), listener); -// } -// } -// } else { for (String path : toCategoriesPath(url)) { diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java index 944796f7..b627d495 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java @@ -130,7 +130,7 @@ public ReturnResult runInference(Context context, InferenceRequest inferenceRequ Map predictParams = new HashMap<>(); Map modelFeatureData = Maps.newHashMap(featureData); FederatedParams federatedParams = new FederatedParams(); - if(inferenceRequest.getSendToRemoteFeatureData()!=null) { + if(inferenceRequest.getSendToRemoteFeatureData()!=null&&federatedParams.getFeatureIdMap()!=null) { federatedParams.getFeatureIdMap().putAll(inferenceRequest.getSendToRemoteFeatureData()); } federatedParams.setCaseId(inferenceRequest.getCaseid()); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java index cd3b9f1b..b808d1e1 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java @@ -101,7 +101,6 @@ private void inferenceServiceAction(InferenceMessage req, StreamObserver Date: Thu, 19 Dec 2019 17:39:22 +0800 Subject: [PATCH 041/190] use selfPartyId instead of srcPartyId. --- .gitignore | 17 +-- conf/serving-server.properties | 61 ++++++++ pom.xml | 6 +- serving-proxy/bin/service.sh | 14 +- serving-proxy/package.xml | 15 +- serving-proxy/pom.xml | 22 +-- .../serving/proxy/bootstrap/Bootstrap.java | 3 +- .../proxy/config/GrpcConfigration.java | 16 +- .../serving/proxy/config/WebConfigration.java | 17 +-- .../serving/proxy/rpc/core/FlowLogger.java | 144 ------------------ .../proxy/rpc/grpc/IntraGrpcServer.java | 2 +- .../router/ConfigFileBasedServingRouter.java | 10 +- .../proxy/rpc/router/ZkServingRouter.java | 2 +- .../serving/proxy/security/AuthUtils.java | 10 +- .../src/main/resources/application.properties | 64 ++++---- .../src/main/resources/auth_config.json | 1 - .../src/main/resources/log4j2.properties | 105 +++++++++++++ .../src/main/resources/route_table.json | 31 ++++ 18 files changed, 285 insertions(+), 255 deletions(-) create mode 100644 conf/serving-server.properties delete mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/FlowLogger.java create mode 100644 serving-proxy/src/main/resources/log4j2.properties create mode 100644 serving-proxy/src/main/resources/route_table.json diff --git a/.gitignore b/.gitignore index 0241e4ad..5e14d801 100644 --- a/.gitignore +++ b/.gitignore @@ -10,24 +10,9 @@ __pycache__ *.prefs # excluded paths -/arch/core/target/ -/arch/driver/federation/target/ -/arch/eggroll/egg/target/ -/arch/eggroll/meta-service/target/ -/arch/eggroll/roll/target/ -/arch/eggroll/storage-service/target/ -/arch/networking/proxy/target/ -/arch/driver/target/ -/arch/eggroll/target/ /fate-serving/federatedml/target/ /fate-serving/serving-server/target/ /fate-serving/target/ /fate-serving/fate-serving-core/target/ -/arch/networking/target/ -/arch/target/ -/fateboard/target/ -/data/ -/logs/ -/jobs/ -/audit/ +.idea/* .vscode/* diff --git a/conf/serving-server.properties b/conf/serving-server.properties new file mode 100644 index 00000000..0fbde69c --- /dev/null +++ b/conf/serving-server.properties @@ -0,0 +1,61 @@ +# +# Copyright 2019 The FATE Authors. All Rights Reserved. +# +# 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. +# +coordinator=webank +ip=127.0.0.1 +port=8000 +workMode=0 +serviceRoleName=serving +inferenceWorkerThreadNum=10 +#storage +# maybe python/data/ +standaloneStoragePath= +# cache +remoteModelInferenceResultCacheSwitch=true +# in-process cache +modelCacheAccessTTL=12 +modelCacheMaxSize=50 +remoteModelInferenceResultCacheTTL=300 +remoteModelInferenceResultCacheMaxSize=10000 +inferenceResultCacheTTL=30 +inferenceResultCacheCacheMaxSize=1000 +# external cache +redis.ip=127.0.0.1 +redis.port=6379 +redis.password=fate_dev +redis.timeout=10 +redis.maxTotal=100 +redis.maxIdle=100 +external.remoteModelInferenceResultCacheTTL=86400 +external.remoteModelInferenceResultCacheDBIndex=0 +external.inferenceResultCacheTTL=300 +external.inferenceResultCacheDBIndex=0 +canCacheRetcode=0,102 +external.processCacheDBIndex=0 +# federation +party.id=9999 +# adapter +OnlineDataAccessAdapter=TestFile +InferencePostProcessingAdapter=PassPostProcessing +InferencePreProcessingAdapter=PassPreProcessing +# external subsystem +proxy=127.0.0.1:9370 +roll=127.0.0.1:8011 +#zk.url=zookeeper://localhost:2181?backup=localhost:2182,localhost:2183 +zk.url=zookeeper://localhost:2181 +useRegister=false +useZkRouter=false +useJMX=true +jmx.server.name=JMXServer diff --git a/pom.xml b/pom.xml index 98aa8dbe..e048110a 100644 --- a/pom.xml +++ b/pom.xml @@ -26,10 +26,10 @@ serving-server + router federatedml fate-serving-core register - router fate-jmx serving-proxy @@ -46,10 +46,12 @@ 3.6.1 4.12 2.11.1 - 5.1.9.RELEASE + 5.2.0.RELEASE 0.6.1 1.6.1 + + 2.2.0.RELEASE diff --git a/serving-proxy/bin/service.sh b/serving-proxy/bin/service.sh index 3901d3a7..679d8d78 100644 --- a/serving-proxy/bin/service.sh +++ b/serving-proxy/bin/service.sh @@ -16,15 +16,15 @@ # limitations under the License. # basepath=$(cd `dirname $0`;pwd) -#export JAVA_HOME=/data/projects/common/jdk/jdk1.8.0_192 -#export PATH=$PATH:$JAVA_HOME/bin +export JAVA_HOME=/data/projects/common/jdk/jdk1.8.0_192 +export PATH=$PATH:$JAVA_HOME/bin configpath=$(cd $basepath/conf;pwd) - -module=serving-router -main_class=com.webank.ai.fate.networking.Proxy +module=serving-proxy +main_class=com.webank.ai.fate.serving.proxy.bootstrap.Bootstrap getpid() { + sleep 1 pid=`ps aux | grep ${module} | grep -v grep | awk '{print $2}'` if [[ -n ${pid} ]]; then @@ -56,7 +56,7 @@ start() { getpid if [[ $? -eq 0 ]]; then mklogsdir - java -DauthFile=${configpath}/auth_config.json -Drouter_file=${configpath}/route_table.json -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/proxy.properties >> logs/console.log 2>>logs/error.log & + java -Dspring.config.location=${configpath}/application.properties -DconfPath=$configpath -Xmx2048m -Xms2048m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/application.properties >> logs/console.log 2>>logs/error.log & if [[ $? -eq 0 ]]; then getpid echo "service start sucessfully. pid: ${pid}" @@ -105,4 +105,4 @@ case "$1" in *) echo "usage: $0 {start|stop|status|restart}" exit -1 -esac \ No newline at end of file +esac diff --git a/serving-proxy/package.xml b/serving-proxy/package.xml index ca7a6cce..80864c18 100644 --- a/serving-proxy/package.xml +++ b/serving-proxy/package.xml @@ -7,8 +7,6 @@ zip - - false @@ -43,18 +41,7 @@ /conf src/main/resources - proxy.properties - log4j2.properties - applicationContext-proxy.xml - auth_config.json - - - - - /conf - src/main/resources/route_tables - - route_table.json + * diff --git a/serving-proxy/pom.xml b/serving-proxy/pom.xml index 7d770c1a..4b91509c 100644 --- a/serving-proxy/pom.xml +++ b/serving-proxy/pom.xml @@ -4,6 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> fate-serving-proxy + ${fate.version} fate-serving @@ -11,26 +12,11 @@ ${fate.version} - ${fate.version} jar 4.0.0 fate-serving-proxy - - 2.2.0.RELEASE - UTF-8 - UTF-8 - 1.7 - 1.4.0 - - 3.1.1 - 3.1.0 - 2.0.1.RELEASE - 1.6.1 - yyMMddHHmm - - com.webank.ai.fate @@ -60,6 +46,12 @@ org.springframework.boot spring-boot-starter-webflux ${spring.boot.version} + + + org.springframework.boot + spring-boot-starter-logging + + diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java index fbcebe5d..56ddc87e 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java @@ -6,7 +6,6 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.ImportResource; import org.springframework.context.annotation.PropertySource; import org.springframework.scheduling.annotation.EnableScheduling; @@ -16,7 +15,7 @@ **/ @SpringBootApplication @ComponentScan(basePackages = {"com.webank.ai.fate.serving.proxy.*"}) -@PropertySource("classpath:application.properties") +//@PropertySource("classpath:application.properties") @EnableScheduling public class Bootstrap { diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/GrpcConfigration.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/GrpcConfigration.java index 1e2b8377..adf75b04 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/GrpcConfigration.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/GrpcConfigration.java @@ -20,11 +20,12 @@ public class GrpcConfigration { private static final Logger logger = LoggerFactory.getLogger(GrpcConfigration.class); - //@Value("${proxy.async.timeout:5000}") @Value("${proxy.grpc.threadpool.coresize:50}") private int coreSize; + @Value("${proxy.grpc.threadpool.maxsize:100}") private int maxPoolSize; + @Value("${proxy.grpc.threadpool.queuesize:10}") private int queueSize; @@ -32,18 +33,19 @@ public class GrpcConfigration { public Executor asyncServiceExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - //配置核心线程数 + executor.setCorePoolSize(coreSize); - //配置最大线程数 + executor.setMaxPoolSize(maxPoolSize); - //配置队列大小 + executor.setQueueCapacity(queueSize); - //配置线程池中的线程的名称前缀 + executor.setThreadNamePrefix("grpc-service"); - //拒绝 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); - //执行初始化 + executor.initialize(); + return executor; } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java index e3923569..4bf2578c 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java @@ -32,27 +32,23 @@ public class WebConfigration implements WebMvcConfigurer { @Autowired ServletContext servletContext; + @Value("${proxy.async.timeout:5000}") long timeout; + @Value("${proxy.async.coresize:10}") int coreSize; @Value("${proxy.async.maxsize:100}") int maxSize; - /** - * 设置异步线程池大小 - * @param configurer - */ @Override public void configureAsyncSupport(AsyncSupportConfigurer configurer) { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(coreSize); executor.setMaxPoolSize(maxSize); executor.setThreadNamePrefix("GatewayAsync"); - /** - * 直接拒绝 - */ + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); executor.initialize(); configurer.setTaskExecutor(executor); @@ -76,10 +72,9 @@ public FilterRegistrationBean initUserServletFilterRegistration() { CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(); registration.setFilter(characterEncodingFilter); - registration.addUrlPatterns("/*");//设置过滤路径,/*所有路径 - // registration.addInitParameter("name", "alue");//添加默认参数 - registration.setName("CharacterEncodingFilter");//设置优先级 - registration.setOrder(Integer.MAX_VALUE);//设置优先级 + registration.addUrlPatterns("/*"); + registration.setName("CharacterEncodingFilter"); + registration.setOrder(Integer.MAX_VALUE); return registration; } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/FlowLogger.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/FlowLogger.java deleted file mode 100644 index 8fc8cd2a..00000000 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/FlowLogger.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.webank.ai.fate.serving.proxy.rpc.core; - -import ch.qos.logback.classic.Level; -import ch.qos.logback.classic.Logger; -import ch.qos.logback.classic.LoggerContext; -import ch.qos.logback.classic.encoder.PatternLayoutEncoder; -import ch.qos.logback.classic.filter.LevelFilter; -import ch.qos.logback.core.rolling.RollingFileAppender; -import ch.qos.logback.core.rolling.TimeBasedRollingPolicy; -import ch.qos.logback.core.spi.FilterReply; -import ch.qos.logback.core.util.OptionHelper; -import org.slf4j.LoggerFactory; - -import java.text.DateFormat; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -/** - * @Description TODO - * @Author - * 坑爹玩意,需要根据业务字段打到不同文件 - **/ -public class FlowLogger { - - - private static final Map container = new HashMap<>(); - public static Logger getLogger(String name) { - Logger logger = container.get(name); - if(logger != null) { - return logger; - } - synchronized (FlowLogger.class) { - logger = container.get(name); - if(logger != null) { - return logger; - } - logger = build(name); - container.put(name,logger); - } - return logger; - } - - - - - private static Logger build(String name) { - - RollingFileAppender infoAppender =getAppender(name,Level.INFO); - LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); - Logger logger = context.getLogger("FILE-" + name); - //设置不向上级打印信息 - logger.setAdditive(false); - - logger.addAppender(infoAppender); - - - return logger; - } - - - - - public static RollingFileAppender getAppender(String name, Level level){ - DateFormat format = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.SIMPLIFIED_CHINESE); - LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); - //这里是可以用来设置appender的,在xml配置文件里面,是这种形式: - // - RollingFileAppender appender = new RollingFileAppender(); -// ConsoleAppender consoleAppender = new ConsoleAppender(); - -// //这里设置级别过滤器 - LevelFilter levelFilter = new LevelFilter(); - levelFilter.setLevel(level); - levelFilter.setOnMatch(FilterReply.ACCEPT); - levelFilter.setOnMismatch(FilterReply.DENY); - levelFilter.start(); - appender.addFilter(levelFilter); - - - //设置上下文,每个logger都关联到logger上下文,默认上下文名称为default。 - // 但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改。 - appender.setContext(context); - //appender的name属性 - appender.setName("FILE-" + name); - //设置文件名 - appender.setFile(OptionHelper.substVars( "logs/flow-"+name+".log",context)); - - appender.setAppend(true); - - appender.setPrudent(false); - - TimeBasedRollingPolicy rollingPolicy = new TimeBasedRollingPolicy(); - - -//      -//        logFile.%d{yyyy-MM-dd}.log - - - - rollingPolicy.setFileNamePattern("logs/flow-"+name+".%d{yyyy-MM-dd}.log"); - rollingPolicy.setMaxHistory(30); - rollingPolicy.setContext(context); - rollingPolicy.setParent(appender); - rollingPolicy.start(); - appender.setRollingPolicy(rollingPolicy); - - - -// //设置文件创建时间及大小的类 -// SizeAndTimeBasedRollingPolicy policy = new SizeAndTimeBasedRollingPolicy(); -// //文件名格式 -// String fp = OptionHelper.substVars("E:/eppLog/"+ name +"/" + format.format(new Date())+"/"+ level.levelStr + "/.%d{yyyy-MM-dd}.%i.log",context); -// //最大日志文件大小 -// policy.setMaxFileSize(FileSize.valueOf("128MB")); -// //设置文件名模式 -// policy.setFileNamePattern(fp); -// //设置最大历史记录为15条 -// policy.setMaxHistory(15); -// //总大小限制 -// policy.setTotalSizeCap(FileSize.valueOf("32GB")); -// //设置父节点是appender -// policy.setParent(appender); -// //设置上下文,每个logger都关联到logger上下文,默认上下文名称为default。 -// // 但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改。 -// policy.setContext(context); -// policy.start(); -// - PatternLayoutEncoder encoder = new PatternLayoutEncoder(); - //设置上下文,每个logger都关联到logger上下文,默认上下文名称为default。 - // 但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改。 - encoder.setContext(context); - //设置格式 - encoder.setPattern("%d %p (%file:%line\\)- %m%n"); - encoder.start(); - - //加入下面两个节点 -// appender.setRollingPolicy(policy); - appender.setEncoder(encoder); - appender.start(); - return appender; - } - -} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java index 7c5bc121..c9ca8cf8 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java @@ -19,7 +19,7 @@ @Service public class IntraGrpcServer implements InitializingBean { - @Value("${proxy.grpc.inter.port:8867}") + @Value("${proxy.grpc.intra.port:8867}") private Integer port; Logger logger = LoggerFactory.getLogger(InterGrpcServer.class); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java index 6a70ba2b..6346e363 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java @@ -34,7 +34,7 @@ public class ConfigFileBasedServingRouter extends BaseServingRouter implements private RouteType routeType; - @Value("${route.table}") + @Value("${route.table:conf/route_table.json}") private String routeTableFile; @Value("${coordinator}") @@ -216,6 +216,7 @@ private boolean hasRule(Map> target, Proxy.Topic f @Scheduled(fixedRate = 10000) public void loadRouteTable() { + logger.debug("start refreshed route table..."); String fileMd5 = FileUtils.fileMd5(routeTableFile); if(null != fileMd5 && fileMd5.equals(lastFileMd5)){ return; @@ -249,6 +250,7 @@ public void loadRouteTable() { @Override public void afterPropertiesSet() throws Exception { + logger.debug("in ConfigFileBasedServingRouter:afterPropertiesSet"); routeType = RouteTypeConvertor.string2RouteType(routeTypeString); routeTable = new ConcurrentHashMap<>(); topicEndpointMapping = new WeakHashMap<>(); @@ -259,6 +261,12 @@ public void afterPropertiesSet() throws Exception { defaultAllow = false; lastFileMd5 = ""; + + try { + loadRouteTable(); + } catch (Throwable e) { + logger.error("load route table fail. ", e); + } } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java index f8c1e920..44776b1c 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java @@ -34,7 +34,7 @@ public class ZkServingRouter extends BaseServingRouter implements InitializingB @Value("${acl.password}") private String aclPassword; - @Value("${port}") + @Value("${zk.self.port}") private String port; @Value("${routeType}") diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java index 8fb71a7b..14f85647 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java @@ -30,6 +30,7 @@ import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -41,16 +42,18 @@ @Component public class AuthUtils implements InitializingBean{ private static final Logger LOGGER = LogManager.getLogger(); - private static final String confFilePath = System.getProperty("authFile"); private static Map KEY_SECRET_MAP = new HashMap<>(); private static Map PARTYID_KEY_MAP = new HashMap<>(); private static int validRequestTimeoutSecond = 10; - private static String applyId = ""; private static boolean ifUseAuth = false; private static String selfPartyId = ""; + @Autowired private ToStringUtils toStringUtils; + @Value("${auth.file:conf/auth_config.json}") + private String confFilePath; + @Scheduled(fixedRate = 10000) public void loadConfig(){ JsonParser jsonParser = new JsonParser(); @@ -73,7 +76,6 @@ public void loadConfig(){ selfPartyId = jsonObject.get("self_party_id").getAsString(); ifUseAuth = jsonObject.get("if_use_auth").getAsBoolean(); validRequestTimeoutSecond = jsonObject.get("request_expire_seconds").getAsInt(); - applyId = jsonObject.get("apply_id").getAsString(); JsonArray jsonArray = jsonObject.getAsJsonArray("access_keys"); Gson gson = new Gson(); @@ -153,7 +155,7 @@ public void afterPropertiesSet() throws Exception { try { loadConfig(); } catch (Throwable e) { - LOGGER.error("load authencation keys error", e); + LOGGER.error("load authencation keys fail. ", e); } } diff --git a/serving-proxy/src/main/resources/application.properties b/serving-proxy/src/main/resources/application.properties index c7d2ee5f..3c348bae 100644 --- a/serving-proxy/src/main/resources/application.properties +++ b/serving-proxy/src/main/resources/application.properties @@ -1,32 +1,38 @@ -server.port=8087 -server.tomcat.max-threads=500 -server.tomcat.max-connections=10000 -spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver -management.endpoints.web.exposure.include=* -spring.boot.admin.client.url=http://localhost:8089 -spring.http.encoding.charset=UTF-8 -spring.http.encoding.enabled=true -server.tomcat.uri-encoding=UTF-8 -openRmb=true -proxy.rmb.testMode=108 -redis.host=localhost -redis.port=6379 -redis.password= -redis.timeout=2000 -sentinel.path= - -logging.level.main.blog.mapper=info -spring.datasource.url=jdbc:mysql://localhost:3306/fdn?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true -spring.datasource.username=root -spring.datasource.password= - - -useZkRouter=true -zk.url=zookeeper://localhost:2181 +# same as partyid +coordinator=9999 + +server.port=8080 + +#random, consistent +routeType=random -spring.application.name=api-proxy -spring.cloud.sentinel.transport.dashboard=localhost:18080 +route.table=/data/projects/serving-proxy/conf/route_table.json -authFile=/data/projects/fate/fate-serving/proxy/config/auth_config.json +useZkRouter=false +zk.url=zookeeper://localhost:2181 acl.username=fate -acl.password=fate \ No newline at end of file +acl.password=fate +zk.self.port=1111 + +# intra-partyid port +proxy.grpc.intra.port=8867 + +# inter-partyid port +proxy.grpc.inter.port=8869 + +proxy.grpc.inference.timeout=3000 +proxy.grpc.unaryCall.timeout=3000 + +inference.service.name=serving + +proxy.grpc.threadpool.coresize=50 +proxy.grpc.threadpool.maxsize=100 +proxy.grpc.threadpool.queuesize=10 + +proxy.async.timeout=5000 +proxy.async.coresize=10 +proxy.async.maxsize=100 + +proxy.grpc.pool.maxTotal=64 +proxy.grpc.pool.maxIdle=1 + diff --git a/serving-proxy/src/main/resources/auth_config.json b/serving-proxy/src/main/resources/auth_config.json index 27a1426d..1c116398 100644 --- a/serving-proxy/src/main/resources/auth_config.json +++ b/serving-proxy/src/main/resources/auth_config.json @@ -2,7 +2,6 @@ "self_party_id": "9999", "if_use_auth": false, "request_expire_seconds": 8, - "apply_id": "20191119163236256", "access_keys": [{ "party_id": "9999", "app_key": "11", diff --git a/serving-proxy/src/main/resources/log4j2.properties b/serving-proxy/src/main/resources/log4j2.properties new file mode 100644 index 00000000..960a1003 --- /dev/null +++ b/serving-proxy/src/main/resources/log4j2.properties @@ -0,0 +1,105 @@ +# +# Copyright 2019 The FATE Authors. All Rights Reserved. +# +# 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. +# +name=PropertiesConfig +property.auditDir=audit +property.logDir=logs +property.project=fate +property.module=proxy +property.logPattern=[%-5level] %d{yyyy-MM-dd}T%d{HH:mm:ss,SSS} [%t] [%c{1}:%L] - %msg%n +# console +appender.console.type=Console +appender.console.name=STDOUT +appender.console.layout.type=PatternLayout +appender.console.layout.pattern=${logPattern} +# default file +appender.file.type=RollingFile +appender.file.name=LOGFILE +appender.file.fileName=${logDir}/${project}-${module}.log +appender.file.filePattern=${logDir}/%d{yyyy}/%d{MM}/%d{dd}/${project}-${module}.log.%d{yyyy-MM-dd-HH} +appender.file.layout.type=PatternLayout +appender.file.layout.pattern=${logPattern} +appender.file.policies.type=Policies +appender.file.policies.time.type=TimeBasedTriggeringPolicy +appender.file.policies.time.interval=1 +appender.file.policies.time.modulate=true +appender.file.strategy.type=DefaultRolloverStrategy +# debug +appender.debugging.type=RollingFile +appender.debugging.name=LOGDEBUGGING +appender.debugging.fileName=${logDir}/${project}-${module}-debug.log +appender.debugging.filePattern=${logDir}/%d{yyyy}/%d{MM}/%d{dd}/${project}-${module}-debug.log.%d{yyyy-MM-dd-HH-mm} +appender.debugging.layout.type=PatternLayout +appender.debugging.layout.pattern=${logPattern} +appender.debugging.policies.type=Policies +appender.debugging.policies.time.type=TimeBasedTriggeringPolicy +appender.debugging.policies.time.interval=1 +appender.debugging.policies.time.modulate=true +appender.debugging.strategy.type=DefaultRolloverStrategy +# audit +appender.audit.type=RollingFile +appender.audit.name=LOGAUDIT +appender.audit.fileName=${auditDir}/${project}-${module}-audit.log +appender.audit.filePattern=${auditDir}/%d{yyyy}/%d{MM}/%d{dd}/${project}-${module}-audit.log.%d{yyyy-MM-dd-HH} +appender.audit.layout.type=PatternLayout +appender.audit.layout.pattern=[%d{yyyy-MM-dd}T%d{HH:mm:ss,SSS}]%msg%n +appender.audit.policies.type=Policies +appender.audit.policies.time.type=TimeBasedTriggeringPolicy +appender.audit.policies.time.interval=1 +appender.audit.policies.time.modulate=true +appender.audit.strategy.type=DefaultRolloverStrategy +# stat +appender.stat.type=RollingFile +appender.stat.name=LOGSTAT +appender.stat.fileName=${logDir}/${project}-${module}-stat.log +appender.stat.filePattern=${logDir}/%d{yyyy}/%d{MM}/%d{dd}/${project}-${module}-stat.log.%d{yyyy-MM-dd-HH} +appender.stat.layout.type=PatternLayout +appender.stat.layout.pattern=${logPattern} +appender.stat.policies.type=Policies +appender.stat.policies.time.type=TimeBasedTriggeringPolicy +appender.stat.policies.time.interval=1 +appender.stat.policies.time.modulate=true +appender.stat.strategy.type=DefaultRolloverStrategy +# loggers +loggers=file, debugging, audit, stat +# logger - file +logger.file.name=file +logger.file.level=info +logger.file.appenderRefs=file +logger.file.appenderRef.file.ref=LOGFILE +logger.file.additivity=false +# logger - debugging +logger.debugging.name=debugging +logger.debugging.level=info +logger.debugging.appenderRefs=debugging +logger.debugging.appenderRef.debugging.ref=LOGDEBUGGING +logger.debugging.additivity=false +# logger - audit +logger.audit.name=audit +logger.audit.level=info +logger.audit.appenderRefs=audit +logger.audit.appenderRef.file.ref=LOGAUDIT +logger.audit.additivity=false +# logger - stat +logger.stat.name=stat +logger.stat.level=info +logger.stat.appenderRefs=stat +logger.stat.appenderRef.file.ref=LOGSTAT +logger.stat.additivity=false +# logger - root +rootLogger.level=info +rootLogger.appenderRefs=stdout, file +rootLogger.appenderRef.stdout.ref=STDOUT +rootLogger.appenderRef.file.ref=LOGFILE diff --git a/serving-proxy/src/main/resources/route_table.json b/serving-proxy/src/main/resources/route_table.json new file mode 100644 index 00000000..06a6f7b4 --- /dev/null +++ b/serving-proxy/src/main/resources/route_table.json @@ -0,0 +1,31 @@ +{ + "route_table": { + "default": { + "default": [ + { + "ip": "127.0.0.1", + "port": 9999 + } + ] + }, + "10000": { + "default": [ + { + "ip": "127.0.0.1", + "port": 8889 + } + ] + }, + "9999": { + "default": [ + { + "ip": "127.0.0.1", + "port": 8890 + } + ] + } + }, + "permission": { + "default_allow": true + } +} From 29a5e1bcd1d10a5a9a5983a8994d452746a73101 Mon Sep 17 00:00:00 2001 From: utu Date: Thu, 19 Dec 2019 21:42:22 +0800 Subject: [PATCH 042/190] proxy refactor --- .../Maven__com_alibaba_fastjson_1_2_25.xml | 13 -- ...ackson_core_jackson_annotations_2_10_0.xml | 13 -- ...erxml_jackson_core_jackson_core_2_10_0.xml | 13 -- ...l_jackson_core_jackson_databind_2_10_0.xml | 13 -- pom.xml | 6 +- serving-proxy/bin/service.sh | 14 +- serving-proxy/package.xml | 15 +- serving-proxy/pom.xml | 28 ++-- .../serving/proxy/bootstrap/Bootstrap.java | 3 +- .../ai/fate/serving/proxy/common/Dict.java | 18 --- .../proxy/common/ErrorMessageUtil.java | 35 ----- .../proxy/config/GrpcConfigration.java | 16 +- .../serving/proxy/config/WebConfigration.java | 17 +-- .../serving/proxy/rpc/core/FlowLogger.java | 144 ------------------ .../proxy/rpc/grpc/InterGrpcServer.java | 2 +- .../proxy/rpc/grpc/IntraGrpcServer.java | 2 +- .../router/ConfigFileBasedServingRouter.java | 20 ++- .../proxy/rpc/router/ZkServingRouter.java | 21 +-- .../serving/proxy/security/AuthUtils.java | 28 +++- .../src/main/resources/application.properties | 65 ++++---- .../src/main/resources/auth_config.json | 1 - .../src/main/resources/log4j2.properties | 105 +++++++++++++ .../src/main/resources/route_table.json | 31 ++++ 23 files changed, 262 insertions(+), 361 deletions(-) delete mode 100644 .idea/libraries/Maven__com_alibaba_fastjson_1_2_25.xml delete mode 100644 .idea/libraries/Maven__com_fasterxml_jackson_core_jackson_annotations_2_10_0.xml delete mode 100644 .idea/libraries/Maven__com_fasterxml_jackson_core_jackson_core_2_10_0.xml delete mode 100644 .idea/libraries/Maven__com_fasterxml_jackson_core_jackson_databind_2_10_0.xml delete mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/FlowLogger.java create mode 100644 serving-proxy/src/main/resources/log4j2.properties create mode 100644 serving-proxy/src/main/resources/route_table.json diff --git a/.idea/libraries/Maven__com_alibaba_fastjson_1_2_25.xml b/.idea/libraries/Maven__com_alibaba_fastjson_1_2_25.xml deleted file mode 100644 index 88d06a73..00000000 --- a/.idea/libraries/Maven__com_alibaba_fastjson_1_2_25.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_annotations_2_10_0.xml b/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_annotations_2_10_0.xml deleted file mode 100644 index c7085107..00000000 --- a/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_annotations_2_10_0.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_core_2_10_0.xml b/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_core_2_10_0.xml deleted file mode 100644 index ba1879ab..00000000 --- a/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_core_2_10_0.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_databind_2_10_0.xml b/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_databind_2_10_0.xml deleted file mode 100644 index 12966549..00000000 --- a/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_databind_2_10_0.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index 98aa8dbe..e048110a 100644 --- a/pom.xml +++ b/pom.xml @@ -26,10 +26,10 @@ serving-server + router federatedml fate-serving-core register - router fate-jmx serving-proxy @@ -46,10 +46,12 @@ 3.6.1 4.12 2.11.1 - 5.1.9.RELEASE + 5.2.0.RELEASE 0.6.1 1.6.1 + + 2.2.0.RELEASE diff --git a/serving-proxy/bin/service.sh b/serving-proxy/bin/service.sh index 3901d3a7..679d8d78 100644 --- a/serving-proxy/bin/service.sh +++ b/serving-proxy/bin/service.sh @@ -16,15 +16,15 @@ # limitations under the License. # basepath=$(cd `dirname $0`;pwd) -#export JAVA_HOME=/data/projects/common/jdk/jdk1.8.0_192 -#export PATH=$PATH:$JAVA_HOME/bin +export JAVA_HOME=/data/projects/common/jdk/jdk1.8.0_192 +export PATH=$PATH:$JAVA_HOME/bin configpath=$(cd $basepath/conf;pwd) - -module=serving-router -main_class=com.webank.ai.fate.networking.Proxy +module=serving-proxy +main_class=com.webank.ai.fate.serving.proxy.bootstrap.Bootstrap getpid() { + sleep 1 pid=`ps aux | grep ${module} | grep -v grep | awk '{print $2}'` if [[ -n ${pid} ]]; then @@ -56,7 +56,7 @@ start() { getpid if [[ $? -eq 0 ]]; then mklogsdir - java -DauthFile=${configpath}/auth_config.json -Drouter_file=${configpath}/route_table.json -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/proxy.properties >> logs/console.log 2>>logs/error.log & + java -Dspring.config.location=${configpath}/application.properties -DconfPath=$configpath -Xmx2048m -Xms2048m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/application.properties >> logs/console.log 2>>logs/error.log & if [[ $? -eq 0 ]]; then getpid echo "service start sucessfully. pid: ${pid}" @@ -105,4 +105,4 @@ case "$1" in *) echo "usage: $0 {start|stop|status|restart}" exit -1 -esac \ No newline at end of file +esac diff --git a/serving-proxy/package.xml b/serving-proxy/package.xml index ca7a6cce..80864c18 100644 --- a/serving-proxy/package.xml +++ b/serving-proxy/package.xml @@ -7,8 +7,6 @@ zip - - false @@ -43,18 +41,7 @@ /conf src/main/resources - proxy.properties - log4j2.properties - applicationContext-proxy.xml - auth_config.json - - - - - /conf - src/main/resources/route_tables - - route_table.json + * diff --git a/serving-proxy/pom.xml b/serving-proxy/pom.xml index 7d770c1a..632c70b8 100644 --- a/serving-proxy/pom.xml +++ b/serving-proxy/pom.xml @@ -4,6 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> fate-serving-proxy + ${fate.version} fate-serving @@ -11,26 +12,11 @@ ${fate.version} - ${fate.version} jar 4.0.0 fate-serving-proxy - - 2.2.0.RELEASE - UTF-8 - UTF-8 - 1.7 - 1.4.0 - - 3.1.1 - 3.1.0 - 2.0.1.RELEASE - 1.6.1 - yyMMddHHmm - - com.webank.ai.fate @@ -50,6 +36,12 @@ + + org.slf4j + slf4j-simple + 1.7.25 + + com.googlecode.json-simple json-simple @@ -60,6 +52,12 @@ org.springframework.boot spring-boot-starter-webflux ${spring.boot.version} + + + org.springframework.boot + spring-boot-starter-logging + + diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java index fbcebe5d..56ddc87e 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java @@ -6,7 +6,6 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.ImportResource; import org.springframework.context.annotation.PropertySource; import org.springframework.scheduling.annotation.EnableScheduling; @@ -16,7 +15,7 @@ **/ @SpringBootApplication @ComponentScan(basePackages = {"com.webank.ai.fate.serving.proxy.*"}) -@PropertySource("classpath:application.properties") +//@PropertySource("classpath:application.properties") @EnableScheduling public class Bootstrap { diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/Dict.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/Dict.java index 6444317c..cc98a812 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/Dict.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/Dict.java @@ -5,32 +5,14 @@ * @Author **/ public class Dict { - public static final String APPID = "APP_ID"; - public static final String TIMESTAMP = "TIMESTAMP"; - public static final String SIGNATURE = "SIGNATURE"; public static final String SERVICE_ID = "serviceId"; - public static final String PARTY_ID ="PARTY_ID"; - public static final String TRACE_ID ="TRACE_ID"; - public static final String NONCE ="NONCE"; - public static final String APP_KEY ="APP_KEY"; - public static final String HTTP_URI="HTTP_URI"; public static final String CASE_ID="caseid"; - public static final String APP_ID="app_id"; - public static final String CONTENT_TYPE = "Content-Type"; - public static final String CONTENT_TYPE_JSON_UTF8 = "application/json;charset=UTF-8"; - public static final String CHARSET_UTF8 = "UTF-8"; - public static final String HTTP = "http"; - public static final String HTTPS = "https"; - public static final String ACCESS_TOKEN = "ACCESS_TOKEN"; public static final String CODE ="code"; - public static final String DATA ="data"; public static final String MESSAGE ="message"; public static final String MODEL_ID = "modelid"; public static final String MODEL_VERSION = "modelversion"; - public static final String SEND_TO_REMOTE_FEATURE_DATA="sendToRemoteFeatureData"; public static final String FEATURE_DATA = "featureData"; public static final String PARTNER_PARTY_NAME = "partnerPartyName"; - public static final String PARTY_NAME = "partyName"; public static final String DEFAULT_VERSION = "1.0"; public static final String SELF_PROJECT_NAME = "proxy"; public static final String SELF_ENVIRONMENT = "online"; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ErrorMessageUtil.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ErrorMessageUtil.java index 4d167435..257e184d 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ErrorMessageUtil.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ErrorMessageUtil.java @@ -22,70 +22,35 @@ public class ErrorMessageUtil { public static Map handleException(Map result,Throwable e){ if (e instanceof IllegalArgumentException) { - /** - * 参数错误 - * - */ result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.PARAM_ERROR); result.put(MESSAGE,"PARAM_ERROR"); - }else if ( e instanceof SqlAttactException) - { - result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.PARAM_ERROR); - result.put(MESSAGE,"SqlAttactException"); } - else if(e instanceof NoRouteInfoException){ - - /** - * 无路由信息 - */ - result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.ROUTER_ERROR); result.put(MESSAGE, "ROUTER_ERROR"); - - } else if (e instanceof SysException) { - /** - * 系统错误 - */ result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.SYSTEM_ERROR); result.put(MESSAGE, "SYSTEM_ERROR"); } else if (e instanceof BlockException) { - /** - * 系统限流 - */ result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.LIMIT_ERROR); result.put(MESSAGE, "OVERLOAD"); } else if (e instanceof InvalidRoleInfoException) { - /** - * 鉴权错误 - */ result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.ROLE_ERROR); result.put(MESSAGE, "ROLE_ERROR"); } else if (e instanceof ShowDownRejectException){ - - /** - * 关闭拒绝 - */ result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.SHUTDOWN_ERROR); result.put(MESSAGE, "SHUTDOWN_ERROR"); } else if (e instanceof NoResultException) { logger.error("NET_ERROR ",e); - /** - * 网络异常 - */ result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.NET_ERROR); result.put(MESSAGE, "NET_ERROR"); } else { - /** - * 系统异常 - */ logger.error("SYSTEM_ERROR ",e); result.put(CODE, ErrorCode.SYSTEM_ERROR); result.put(MESSAGE, "SYSTEM_ERROR"); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/GrpcConfigration.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/GrpcConfigration.java index 1e2b8377..adf75b04 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/GrpcConfigration.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/GrpcConfigration.java @@ -20,11 +20,12 @@ public class GrpcConfigration { private static final Logger logger = LoggerFactory.getLogger(GrpcConfigration.class); - //@Value("${proxy.async.timeout:5000}") @Value("${proxy.grpc.threadpool.coresize:50}") private int coreSize; + @Value("${proxy.grpc.threadpool.maxsize:100}") private int maxPoolSize; + @Value("${proxy.grpc.threadpool.queuesize:10}") private int queueSize; @@ -32,18 +33,19 @@ public class GrpcConfigration { public Executor asyncServiceExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - //配置核心线程数 + executor.setCorePoolSize(coreSize); - //配置最大线程数 + executor.setMaxPoolSize(maxPoolSize); - //配置队列大小 + executor.setQueueCapacity(queueSize); - //配置线程池中的线程的名称前缀 + executor.setThreadNamePrefix("grpc-service"); - //拒绝 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); - //执行初始化 + executor.initialize(); + return executor; } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java index e3923569..4bf2578c 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java @@ -32,27 +32,23 @@ public class WebConfigration implements WebMvcConfigurer { @Autowired ServletContext servletContext; + @Value("${proxy.async.timeout:5000}") long timeout; + @Value("${proxy.async.coresize:10}") int coreSize; @Value("${proxy.async.maxsize:100}") int maxSize; - /** - * 设置异步线程池大小 - * @param configurer - */ @Override public void configureAsyncSupport(AsyncSupportConfigurer configurer) { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(coreSize); executor.setMaxPoolSize(maxSize); executor.setThreadNamePrefix("GatewayAsync"); - /** - * 直接拒绝 - */ + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); executor.initialize(); configurer.setTaskExecutor(executor); @@ -76,10 +72,9 @@ public FilterRegistrationBean initUserServletFilterRegistration() { CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(); registration.setFilter(characterEncodingFilter); - registration.addUrlPatterns("/*");//设置过滤路径,/*所有路径 - // registration.addInitParameter("name", "alue");//添加默认参数 - registration.setName("CharacterEncodingFilter");//设置优先级 - registration.setOrder(Integer.MAX_VALUE);//设置优先级 + registration.addUrlPatterns("/*"); + registration.setName("CharacterEncodingFilter"); + registration.setOrder(Integer.MAX_VALUE); return registration; } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/FlowLogger.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/FlowLogger.java deleted file mode 100644 index 8fc8cd2a..00000000 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/FlowLogger.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.webank.ai.fate.serving.proxy.rpc.core; - -import ch.qos.logback.classic.Level; -import ch.qos.logback.classic.Logger; -import ch.qos.logback.classic.LoggerContext; -import ch.qos.logback.classic.encoder.PatternLayoutEncoder; -import ch.qos.logback.classic.filter.LevelFilter; -import ch.qos.logback.core.rolling.RollingFileAppender; -import ch.qos.logback.core.rolling.TimeBasedRollingPolicy; -import ch.qos.logback.core.spi.FilterReply; -import ch.qos.logback.core.util.OptionHelper; -import org.slf4j.LoggerFactory; - -import java.text.DateFormat; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -/** - * @Description TODO - * @Author - * 坑爹玩意,需要根据业务字段打到不同文件 - **/ -public class FlowLogger { - - - private static final Map container = new HashMap<>(); - public static Logger getLogger(String name) { - Logger logger = container.get(name); - if(logger != null) { - return logger; - } - synchronized (FlowLogger.class) { - logger = container.get(name); - if(logger != null) { - return logger; - } - logger = build(name); - container.put(name,logger); - } - return logger; - } - - - - - private static Logger build(String name) { - - RollingFileAppender infoAppender =getAppender(name,Level.INFO); - LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); - Logger logger = context.getLogger("FILE-" + name); - //设置不向上级打印信息 - logger.setAdditive(false); - - logger.addAppender(infoAppender); - - - return logger; - } - - - - - public static RollingFileAppender getAppender(String name, Level level){ - DateFormat format = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.SIMPLIFIED_CHINESE); - LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); - //这里是可以用来设置appender的,在xml配置文件里面,是这种形式: - // - RollingFileAppender appender = new RollingFileAppender(); -// ConsoleAppender consoleAppender = new ConsoleAppender(); - -// //这里设置级别过滤器 - LevelFilter levelFilter = new LevelFilter(); - levelFilter.setLevel(level); - levelFilter.setOnMatch(FilterReply.ACCEPT); - levelFilter.setOnMismatch(FilterReply.DENY); - levelFilter.start(); - appender.addFilter(levelFilter); - - - //设置上下文,每个logger都关联到logger上下文,默认上下文名称为default。 - // 但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改。 - appender.setContext(context); - //appender的name属性 - appender.setName("FILE-" + name); - //设置文件名 - appender.setFile(OptionHelper.substVars( "logs/flow-"+name+".log",context)); - - appender.setAppend(true); - - appender.setPrudent(false); - - TimeBasedRollingPolicy rollingPolicy = new TimeBasedRollingPolicy(); - - -//      -//        logFile.%d{yyyy-MM-dd}.log - - - - rollingPolicy.setFileNamePattern("logs/flow-"+name+".%d{yyyy-MM-dd}.log"); - rollingPolicy.setMaxHistory(30); - rollingPolicy.setContext(context); - rollingPolicy.setParent(appender); - rollingPolicy.start(); - appender.setRollingPolicy(rollingPolicy); - - - -// //设置文件创建时间及大小的类 -// SizeAndTimeBasedRollingPolicy policy = new SizeAndTimeBasedRollingPolicy(); -// //文件名格式 -// String fp = OptionHelper.substVars("E:/eppLog/"+ name +"/" + format.format(new Date())+"/"+ level.levelStr + "/.%d{yyyy-MM-dd}.%i.log",context); -// //最大日志文件大小 -// policy.setMaxFileSize(FileSize.valueOf("128MB")); -// //设置文件名模式 -// policy.setFileNamePattern(fp); -// //设置最大历史记录为15条 -// policy.setMaxHistory(15); -// //总大小限制 -// policy.setTotalSizeCap(FileSize.valueOf("32GB")); -// //设置父节点是appender -// policy.setParent(appender); -// //设置上下文,每个logger都关联到logger上下文,默认上下文名称为default。 -// // 但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改。 -// policy.setContext(context); -// policy.start(); -// - PatternLayoutEncoder encoder = new PatternLayoutEncoder(); - //设置上下文,每个logger都关联到logger上下文,默认上下文名称为default。 - // 但可以使用设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改。 - encoder.setContext(context); - //设置格式 - encoder.setPattern("%d %p (%file:%line\\)- %m%n"); - encoder.start(); - - //加入下面两个节点 -// appender.setRollingPolicy(policy); - appender.setEncoder(encoder); - appender.start(); - return appender; - } - -} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterGrpcServer.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterGrpcServer.java index a2c5edfb..4f1b81d7 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterGrpcServer.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterGrpcServer.java @@ -19,7 +19,7 @@ @Service public class InterGrpcServer implements InitializingBean { - @Value("${proxy.grpc.inter.port:8867}") + @Value("${proxy.grpc.inter.port:8869}") private Integer port; Logger logger = LoggerFactory.getLogger(InterGrpcServer.class); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java index 7c5bc121..c9ca8cf8 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java @@ -19,7 +19,7 @@ @Service public class IntraGrpcServer implements InitializingBean { - @Value("${proxy.grpc.inter.port:8867}") + @Value("${proxy.grpc.intra.port:8867}") private Integer port; Logger logger = LoggerFactory.getLogger(InterGrpcServer.class); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java index 6a70ba2b..df720997 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java @@ -13,8 +13,8 @@ import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; import com.webank.ai.fate.serving.proxy.utils.FileUtils; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; @@ -29,15 +29,15 @@ @Service public class ConfigFileBasedServingRouter extends BaseServingRouter implements InitializingBean{ - @Value("${routeType}") + @Value("${routeType:random}") private String routeTypeString; private RouteType routeType; - @Value("${route.table}") + @Value("${route.table:conf/route_table.json}") private String routeTableFile; - @Value("${coordinator}") + @Value("${coordinator:9999}") private String selfCoordinator; @Value("${inference.service.name:serving}") @@ -45,7 +45,7 @@ public class ConfigFileBasedServingRouter extends BaseServingRouter implements private String lastFileMd5; - Logger logger = LoggerFactory.getLogger(ConfigFileBasedServingRouter.class); + private static final Logger logger = LogManager.getLogger(); private Map> allow; private Map> deny; @@ -216,6 +216,7 @@ private boolean hasRule(Map> target, Proxy.Topic f @Scheduled(fixedRate = 10000) public void loadRouteTable() { + logger.debug("start refreshed route table..."); String fileMd5 = FileUtils.fileMd5(routeTableFile); if(null != fileMd5 && fileMd5.equals(lastFileMd5)){ return; @@ -249,6 +250,7 @@ public void loadRouteTable() { @Override public void afterPropertiesSet() throws Exception { + logger.debug("in ConfigFileBasedServingRouter:afterPropertiesSet"); routeType = RouteTypeConvertor.string2RouteType(routeTypeString); routeTable = new ConcurrentHashMap<>(); topicEndpointMapping = new WeakHashMap<>(); @@ -259,6 +261,12 @@ public void afterPropertiesSet() throws Exception { defaultAllow = false; lastFileMd5 = ""; + + try { + loadRouteTable(); + } catch (Throwable e) { + logger.error("load route table fail. ", e); + } } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java index f8c1e920..5cadaf2b 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java @@ -10,8 +10,8 @@ import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcType; import com.webank.ai.fate.serving.proxy.utils.FederatedModelUtils; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -22,27 +22,27 @@ @Service public class ZkServingRouter extends BaseServingRouter implements InitializingBean{ - @Value("${zk.url}") + @Value("${zk.url:zookeeper://localhost:2181}") private String zkUrl ; @Value("${useZkRouter:false}") private String useZkRouter; - @Value("${acl.username}") + @Value("${acl.username:fate}") private String aclUsername; - @Value("${acl.password}") + @Value("${acl.password:fate}") private String aclPassword; - @Value("${port}") + @Value("${zk.self.port:1111}") private String port; - @Value("${routeType}") + @Value("${routeType:random}") private String routeTypeString; private RouteType routeType; - @Value("${coordinator}") + @Value("${coordinator:9999}") private String selfCoordinator; ZookeeperRegistry zookeeperRegistry; @@ -50,7 +50,7 @@ public class ZkServingRouter extends BaseServingRouter implements InitializingB com.webank.ai.fate.register.router.RouterService zkRouterService; - Logger logger = LoggerFactory.getLogger(ZkServingRouter.class); + private static final Logger logger = LogManager.getLogger(); @Override public RouteType getRouteType(){ @@ -59,6 +59,9 @@ public RouteType getRouteType(){ @Override public List getRouterInfoList(Context context, InboundPackage inboundPackage){ + if(!"true".equals(useZkRouter)){ + return null; + } String environment = getEnvironment(context, inboundPackage); List list =this.zkRouterService.router("serving", environment, context.getServiceName()); logger.info("try to find zk ,{}:{}:{}, result {}", "serving", environment, context.getServiceName(), list); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java index 8fb71a7b..676e1a5b 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java @@ -24,12 +24,14 @@ import com.google.gson.JsonParser; import com.google.gson.stream.JsonReader; import com.webank.ai.fate.api.networking.proxy.Proxy; +import com.webank.ai.fate.serving.proxy.utils.FileUtils; import com.webank.ai.fate.serving.proxy.utils.ToStringUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -41,18 +43,31 @@ @Component public class AuthUtils implements InitializingBean{ private static final Logger LOGGER = LogManager.getLogger(); - private static final String confFilePath = System.getProperty("authFile"); private static Map KEY_SECRET_MAP = new HashMap<>(); private static Map PARTYID_KEY_MAP = new HashMap<>(); private static int validRequestTimeoutSecond = 10; - private static String applyId = ""; private static boolean ifUseAuth = false; - private static String selfPartyId = ""; + @Autowired private ToStringUtils toStringUtils; + @Value("${auth.file:conf/auth_config.json}") + private String confFilePath; + + @Value("${coordinator:9999}") + private String selfPartyId; + + private String lastFileMd5; + @Scheduled(fixedRate = 10000) public void loadConfig(){ + LOGGER.debug("start refreshed auth config..."); + String fileMd5 = FileUtils.fileMd5(confFilePath); + if(null != fileMd5 && fileMd5.equals(lastFileMd5)){ + return; + } + lastFileMd5 = fileMd5; + JsonParser jsonParser = new JsonParser(); JsonReader jsonReader = null; JsonObject jsonObject = null; @@ -73,7 +88,6 @@ public void loadConfig(){ selfPartyId = jsonObject.get("self_party_id").getAsString(); ifUseAuth = jsonObject.get("if_use_auth").getAsBoolean(); validRequestTimeoutSecond = jsonObject.get("request_expire_seconds").getAsInt(); - applyId = jsonObject.get("apply_id").getAsString(); JsonArray jsonArray = jsonObject.getAsJsonArray("access_keys"); Gson gson = new Gson(); @@ -83,7 +97,7 @@ public void loadConfig(){ KEY_SECRET_MAP.put(allowKey.get("app_key").toString(), allowKey.get("app_secret").toString()); PARTYID_KEY_MAP.put(allowKey.get("party_id").toString(), allowKey.get("app_key").toString()); } - LOGGER.debug("refreshed auth cfg using file {}.", confFilePath); + LOGGER.info("refreshed auth cfg using file {}.", confFilePath); } private String getSecret(String appKey) { @@ -149,11 +163,11 @@ public boolean checkAuthentication(Proxy.Packet packet) throws Exception { @Override public void afterPropertiesSet() throws Exception { - + lastFileMd5 = ""; try { loadConfig(); } catch (Throwable e) { - LOGGER.error("load authencation keys error", e); + LOGGER.error("load authencation keys fail. ", e); } } diff --git a/serving-proxy/src/main/resources/application.properties b/serving-proxy/src/main/resources/application.properties index c7d2ee5f..249b71ef 100644 --- a/serving-proxy/src/main/resources/application.properties +++ b/serving-proxy/src/main/resources/application.properties @@ -1,32 +1,39 @@ -server.port=8087 -server.tomcat.max-threads=500 -server.tomcat.max-connections=10000 -spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver -management.endpoints.web.exposure.include=* -spring.boot.admin.client.url=http://localhost:8089 -spring.http.encoding.charset=UTF-8 -spring.http.encoding.enabled=true -server.tomcat.uri-encoding=UTF-8 -openRmb=true -proxy.rmb.testMode=108 -redis.host=localhost -redis.port=6379 -redis.password= -redis.timeout=2000 -sentinel.path= - -logging.level.main.blog.mapper=info -spring.datasource.url=jdbc:mysql://localhost:3306/fdn?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true -spring.datasource.username=root -spring.datasource.password= - - -useZkRouter=true -zk.url=zookeeper://localhost:2181 +# same as partyid +coordinator=9999 + +server.port=8080 + +#random, consistent +routeType=random -spring.application.name=api-proxy -spring.cloud.sentinel.transport.dashboard=localhost:18080 +route.table=/data/projects/serving-proxy/conf/route_table.json +auth.file=/data/projects/serving-proxy/conf/auth_config.json -authFile=/data/projects/fate/fate-serving/proxy/config/auth_config.json +useZkRouter=false +zk.url=zookeeper://localhost:2181 acl.username=fate -acl.password=fate \ No newline at end of file +acl.password=fate +zk.self.port=1111 + +# intra-partyid port +proxy.grpc.intra.port=8867 + +# inter-partyid port +proxy.grpc.inter.port=8869 + +proxy.grpc.inference.timeout=3000 +proxy.grpc.unaryCall.timeout=3000 + +inference.service.name=serving + +proxy.grpc.threadpool.coresize=50 +proxy.grpc.threadpool.maxsize=100 +proxy.grpc.threadpool.queuesize=10 + +proxy.async.timeout=5000 +proxy.async.coresize=10 +proxy.async.maxsize=100 + +proxy.grpc.pool.maxTotal=64 +proxy.grpc.pool.maxIdle=1 + diff --git a/serving-proxy/src/main/resources/auth_config.json b/serving-proxy/src/main/resources/auth_config.json index 27a1426d..1c116398 100644 --- a/serving-proxy/src/main/resources/auth_config.json +++ b/serving-proxy/src/main/resources/auth_config.json @@ -2,7 +2,6 @@ "self_party_id": "9999", "if_use_auth": false, "request_expire_seconds": 8, - "apply_id": "20191119163236256", "access_keys": [{ "party_id": "9999", "app_key": "11", diff --git a/serving-proxy/src/main/resources/log4j2.properties b/serving-proxy/src/main/resources/log4j2.properties new file mode 100644 index 00000000..cbf55415 --- /dev/null +++ b/serving-proxy/src/main/resources/log4j2.properties @@ -0,0 +1,105 @@ +# +# Copyright 2019 The FATE Authors. All Rights Reserved. +# +# 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. +# +name=PropertiesConfig +property.auditDir=audit +property.logDir=logs +property.project=fate +property.module=serving-proxyf +property.logPattern=[%-5level] %d{yyyy-MM-dd}T%d{HH:mm:ss,SSS} [%t] [%c{1}:%L] - %msg%n +# console +appender.console.type=Console +appender.console.name=STDOUT +appender.console.layout.type=PatternLayout +appender.console.layout.pattern=${logPattern} +# default file +appender.file.type=RollingFile +appender.file.name=LOGFILE +appender.file.fileName=${logDir}/${project}-${module}.log +appender.file.filePattern=${logDir}/%d{yyyy}/%d{MM}/%d{dd}/${project}-${module}.log.%d{yyyy-MM-dd-HH} +appender.file.layout.type=PatternLayout +appender.file.layout.pattern=${logPattern} +appender.file.policies.type=Policies +appender.file.policies.time.type=TimeBasedTriggeringPolicy +appender.file.policies.time.interval=1 +appender.file.policies.time.modulate=true +appender.file.strategy.type=DefaultRolloverStrategy +# debug +appender.debugging.type=RollingFile +appender.debugging.name=LOGDEBUGGING +appender.debugging.fileName=${logDir}/${project}-${module}-debug.log +appender.debugging.filePattern=${logDir}/%d{yyyy}/%d{MM}/%d{dd}/${project}-${module}-debug.log.%d{yyyy-MM-dd-HH-mm} +appender.debugging.layout.type=PatternLayout +appender.debugging.layout.pattern=${logPattern} +appender.debugging.policies.type=Policies +appender.debugging.policies.time.type=TimeBasedTriggeringPolicy +appender.debugging.policies.time.interval=1 +appender.debugging.policies.time.modulate=true +appender.debugging.strategy.type=DefaultRolloverStrategy +# audit +appender.audit.type=RollingFile +appender.audit.name=LOGAUDIT +appender.audit.fileName=${auditDir}/${project}-${module}-audit.log +appender.audit.filePattern=${auditDir}/%d{yyyy}/%d{MM}/%d{dd}/${project}-${module}-audit.log.%d{yyyy-MM-dd-HH} +appender.audit.layout.type=PatternLayout +appender.audit.layout.pattern=[%d{yyyy-MM-dd}T%d{HH:mm:ss,SSS}]%msg%n +appender.audit.policies.type=Policies +appender.audit.policies.time.type=TimeBasedTriggeringPolicy +appender.audit.policies.time.interval=1 +appender.audit.policies.time.modulate=true +appender.audit.strategy.type=DefaultRolloverStrategy +# stat +appender.stat.type=RollingFile +appender.stat.name=LOGSTAT +appender.stat.fileName=${logDir}/${project}-${module}-stat.log +appender.stat.filePattern=${logDir}/%d{yyyy}/%d{MM}/%d{dd}/${project}-${module}-stat.log.%d{yyyy-MM-dd-HH} +appender.stat.layout.type=PatternLayout +appender.stat.layout.pattern=${logPattern} +appender.stat.policies.type=Policies +appender.stat.policies.time.type=TimeBasedTriggeringPolicy +appender.stat.policies.time.interval=1 +appender.stat.policies.time.modulate=true +appender.stat.strategy.type=DefaultRolloverStrategy +# loggers +loggers=file, debugging, audit, stat +# logger - file +logger.file.name=file +logger.file.level=info +logger.file.appenderRefs=file +logger.file.appenderRef.file.ref=LOGFILE +logger.file.additivity=false +# logger - debugging +logger.debugging.name=debugging +logger.debugging.level=info +logger.debugging.appenderRefs=debugging +logger.debugging.appenderRef.debugging.ref=LOGDEBUGGING +logger.debugging.additivity=false +# logger - audit +logger.audit.name=audit +logger.audit.level=info +logger.audit.appenderRefs=audit +logger.audit.appenderRef.file.ref=LOGAUDIT +logger.audit.additivity=false +# logger - stat +logger.stat.name=stat +logger.stat.level=info +logger.stat.appenderRefs=stat +logger.stat.appenderRef.file.ref=LOGSTAT +logger.stat.additivity=false +# logger - root +rootLogger.level=info +rootLogger.appenderRefs=stdout, file +rootLogger.appenderRef.stdout.ref=STDOUT +rootLogger.appenderRef.file.ref=LOGFILE diff --git a/serving-proxy/src/main/resources/route_table.json b/serving-proxy/src/main/resources/route_table.json new file mode 100644 index 00000000..06a6f7b4 --- /dev/null +++ b/serving-proxy/src/main/resources/route_table.json @@ -0,0 +1,31 @@ +{ + "route_table": { + "default": { + "default": [ + { + "ip": "127.0.0.1", + "port": 9999 + } + ] + }, + "10000": { + "default": [ + { + "ip": "127.0.0.1", + "port": 8889 + } + ] + }, + "9999": { + "default": [ + { + "ip": "127.0.0.1", + "port": 8890 + } + ] + } + }, + "permission": { + "default_allow": true + } +} From dc5cc259092477701ad539736c187a10dc64e8f5 Mon Sep 17 00:00:00 2001 From: utu Date: Fri, 20 Dec 2019 20:10:15 +0800 Subject: [PATCH 043/190] proxy refactor. --- proto/model_service.proto | 1 + .../ai/fate/serving/proxy/common/Dict.java | 8 ++++-- .../serving/proxy/config/WebConfigration.java | 2 +- .../proxy/controller/ProxyController.java | 26 ++++++++++++------- .../proxy/rpc/grpc/GrpcConnectionPool.java | 2 -- .../proxy/rpc/router/BaseServingRouter.java | 8 +++--- .../router/ConfigFileBasedServingRouter.java | 6 +++-- .../proxy/rpc/services/InferenceService.java | 17 ++++++------ .../security/InferenceParamValidator.java | 4 ++- .../ai/fate/serving/service/ModelService.java | 2 +- 10 files changed, 44 insertions(+), 32 deletions(-) diff --git a/proto/model_service.proto b/proto/model_service.proto index 2023acda..db44ef48 100644 --- a/proto/model_service.proto +++ b/proto/model_service.proto @@ -38,5 +38,6 @@ message PublishResponse{ service ModelService{ rpc publishLoad(PublishRequest) returns (PublishResponse); + rpc publishOnline(PublishRequest) returns (PublishResponse); rpc publishBind(PublishRequest) returns (PublishResponse); } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/Dict.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/Dict.java index cc98a812..b48f2fec 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/Dict.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/Dict.java @@ -9,11 +9,15 @@ public class Dict { public static final String CASE_ID="caseid"; public static final String CODE ="code"; public static final String MESSAGE ="message"; - public static final String MODEL_ID = "modelid"; - public static final String MODEL_VERSION = "modelversion"; + public static final String MODEL_ID = "modelId"; + public static final String MODEL_VERSION = "modelVersion"; + public static final String APP_ID = "appid"; + public static final String PARTY_ID = "partyId"; public static final String FEATURE_DATA = "featureData"; public static final String PARTNER_PARTY_NAME = "partnerPartyName"; public static final String DEFAULT_VERSION = "1.0"; public static final String SELF_PROJECT_NAME = "proxy"; public static final String SELF_ENVIRONMENT = "online"; + public static final String HEAD = "head"; + public static final String BODY = "body"; } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java index 4bf2578c..9f3a2a4e 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java @@ -47,7 +47,7 @@ public void configureAsyncSupport(AsyncSupportConfigurer configurer) { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(coreSize); executor.setMaxPoolSize(maxSize); - executor.setThreadNamePrefix("GatewayAsync"); + executor.setThreadNamePrefix("ProxyAsync"); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); executor.initialize(); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java index 3a08259c..776cafd7 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java @@ -1,13 +1,14 @@ package com.webank.ai.fate.serving.proxy.controller; import com.alibaba.fastjson.JSON; -import com.google.common.collect.Maps; +import com.alibaba.fastjson.JSONObject; import com.webank.ai.fate.serving.proxy.common.Dict; import com.webank.ai.fate.serving.proxy.rpc.core.*; import com.webank.ai.fate.serving.proxy.utils.WebUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; @@ -29,6 +30,10 @@ public class ProxyController { @Autowired ProxyServiceRegister proxyServiceRegister; + + @Value("${coordinator:9999}") + private String selfCoordinator; + Logger logger = LoggerFactory.getLogger(ProxyController.class); String binaryReader(HttpServletRequest request) throws IOException { @@ -58,7 +63,7 @@ public String call() throws Exception { Context context = new Context(); context.setVersion(version); - InboundPackage inboundPackage = buildInboundPackageFederation(context, headers, data, httpServletRequest); + InboundPackage inboundPackage = buildInboundPackageFederation(context, data, httpServletRequest); OutboundPackage result = serviceAdaptor.service(context,inboundPackage ); if(result!=null&&result.getData()!=null) { @@ -74,19 +79,20 @@ public String call() throws Exception { } - private InboundPackage buildInboundPackageFederation(Context context ,HttpHeaders headers, - String data,HttpServletRequest httpServletRequest) { + private InboundPackage buildInboundPackageFederation(Context context , String data, + HttpServletRequest httpServletRequest) { String sourceIp = WebUtil.getIpAddr(httpServletRequest); context.setSourceIp(sourceIp); context.setCaseId(UUID.randomUUID().toString()); + context.setGuestAppId(selfCoordinator); - Map head = Maps.newHashMap(); - // SERVICE_ID == fun(MODEL_ID, MODEL_VERSION) - head.put(Dict.SERVICE_ID, headers.getFirst(Dict.SERVICE_ID)!=null?headers.getFirst(Dict.SERVICE_ID).trim():""); - head.put(Dict.MODEL_ID, headers.getFirst(Dict.MODEL_ID)!=null?headers.getFirst(Dict.MODEL_ID).trim():""); - head.put(Dict.MODEL_VERSION, headers.getFirst(Dict.MODEL_VERSION)!=null?headers.getFirst(Dict.MODEL_VERSION).trim():""); + JSONObject jsonObject =JSON.parseObject(data); + Map head = JSON.parseObject(jsonObject.getString(Dict.HEAD), Map.class); + Map body = JSON.parseObject(jsonObject.getString(Dict.BODY), Map.class); - Map body = JSON.parseObject(data, Map.class); + if(null != head){ + context.setHostAppid((String) head.get(Dict.APP_ID)); + } InboundPackage inboundPackage = new InboundPackage(); inboundPackage.setBody(body); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java index da1d1185..51e173f3 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java @@ -93,8 +93,6 @@ public ManagedChannel getManagedChannel(String host, int port) throws Exception return objectPool.borrowObject(); } - ; - private class ManagedChannelFactory extends BasePooledObjectFactory { diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java index 313db8df..19c1cd5d 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java @@ -5,15 +5,15 @@ import com.webank.ai.fate.serving.proxy.rpc.core.Context; import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; import com.webank.ai.fate.serving.proxy.rpc.core.OutboundPackage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.util.List; import java.util.concurrent.ThreadLocalRandom; public abstract class BaseServingRouter implements RouterInterface{ - private static final Logger logger = LoggerFactory.getLogger(BaseServingRouter.class); + private static final Logger logger = LogManager.getLogger(); public abstract List getRouterInfoList(Context context, InboundPackage inboundPackage); @@ -47,7 +47,7 @@ public RouterInfo route(Context context, InboundPackage inboundPackage) { context.setRouterInfo(routerInfo); - logger.info("host appid {} get route info {}:{}", context.getHostAppid(),routerInfo.getHost(),routerInfo.getPort()); + logger.info("caseid {} get route info {}:{}", context.getCaseId(),routerInfo.getHost(),routerInfo.getPort()); return routerInfo; } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java index df720997..83b4963d 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java @@ -75,7 +75,10 @@ public List getRouterInfoList(Context context, InboundPackage inboun setRole(inferenceServiceName) .setName(Dict.PARTNER_PARTY_NAME) .build(); - srcTopic = dstTopic; + srcTopic = topicBuilder.setPartyId(selfCoordinator). + setRole(Dict.SELF_PROJECT_NAME) + .setName(Dict.PARTNER_PARTY_NAME) + .build(); } else { // default unaryCall Proxy.Packet sourcePacket = (Proxy.Packet) inboundPackage.getBody(); dstTopic = sourcePacket.getHeader().getDst(); @@ -187,7 +190,6 @@ private boolean hasRule(Map> target, Proxy.Topic f return result; // false } - stage = 0; while (stage < 3 && !result) { switch (stage) { diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java index a0c46324..a5eab54a 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java @@ -13,8 +13,8 @@ import com.webank.ai.fate.serving.proxy.rpc.router.RouterInfo; import io.grpc.ManagedChannel; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -36,7 +36,7 @@ public class InferenceService extends AbstractServiceAdaptor { - Logger logger = LoggerFactory.getLogger(InferenceService.class); + Logger logger = LogManager.getLogger(); @Autowired GrpcConnectionPool grpcConnectionPool; @@ -69,16 +69,15 @@ public Map doService(Context context, InboundPackage data, OutboundPackage< Map inferenceReqMap = Maps.newHashMap(); inferenceReqMap.put(Dict.CASE_ID, context.getCaseId()); - inferenceReqMap.put(Dict.SERVICE_ID, reqHeadMap.get(Dict.SERVICE_ID)); - inferenceReqMap.put(Dict.MODEL_ID, reqHeadMap.get(Dict.MODEL_ID)); - inferenceReqMap.put(Dict.MODEL_VERSION, reqHeadMap.get(Dict.MODEL_VERSION)); + inferenceReqMap.putAll(reqHeadMap); inferenceReqMap.put(Dict.FEATURE_DATA,Maps.newHashMap(reqBodyMap)); - InferenceServiceGrpc.InferenceServiceFutureStub futureStub = InferenceServiceGrpc.newFutureStub(managedChannel); - InferenceServiceProto.InferenceMessage.Builder reqBuilder = InferenceServiceProto.InferenceMessage.newBuilder(); + logger.info("inference req ============================= {}", JSON.toJSONString(inferenceReqMap)); - logger.info("============================= {}",JSON.toJSONString(inferenceReqMap)); + InferenceServiceProto.InferenceMessage.Builder reqBuilder = InferenceServiceProto.InferenceMessage.newBuilder(); reqBuilder.setBody(ByteString.copyFrom(JSON.toJSONString(inferenceReqMap).getBytes())); + + InferenceServiceGrpc.InferenceServiceFutureStub futureStub = InferenceServiceGrpc.newFutureStub(managedChannel); ListenableFuture resultFuture = futureStub.inference(reqBuilder.build()); InferenceServiceProto.InferenceMessage result = resultFuture.get(timeout,TimeUnit.MILLISECONDS); logger.info("routerinfo {} send {} result {}",routerInfo,inferenceReqMap,result); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java index 29198414..8d402d49 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java @@ -19,7 +19,9 @@ public class InferenceParamValidator implements Interceptor{ @Override public void doPreProcess(Context context, InboundPackage inboundPackage,OutboundPackage outboundPackage) throws Exception { Preconditions.checkArgument(StringUtils.isNotEmpty(context.getCaseId()),"case id is null"); - Preconditions.checkArgument(StringUtils.isNotEmpty((String) inboundPackage.getHead().get(Dict.SERVICE_ID)), Dict.SERVICE_ID + " is null"); + Preconditions.checkArgument(null != inboundPackage.getHead(),Dict.HEAD + " is null"); + Preconditions.checkArgument(null != inboundPackage.getBody(),Dict.BODY + " is null"); + //Preconditions.checkArgument(StringUtils.isNotEmpty((String) inboundPackage.getHead().get(Dict.SERVICE_ID)), Dict.SERVICE_ID + " is null"); } @Override diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java index 11ddc30d..7100b544 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java @@ -123,7 +123,7 @@ public synchronized void publishLoad(PublishRequest req, StreamObserver responseStreamObserver) { Context context = new BaseContext(new BaseLoggerPrinter()); From b4a4a9c505e3b5bdb714d6ccd6b33ab3f7038554 Mon Sep 17 00:00:00 2001 From: kaideng Date: Mon, 23 Dec 2019 09:43:09 +0800 Subject: [PATCH 044/190] add mertirx Signed-off-by: kaideng --- fate-serving-core/pom.xml | 5 + .../fate/serving/core/bean/BaseContext.java | 28 ++- .../serving/core/bean/BaseLoggerPrinter.java | 5 +- .../fate/serving/core/bean/Configuration.java | 8 + .../ai/fate/serving/core/bean/Dict.java | 2 +- .../bean/GuestInferenceLoggerPrinter.java | 7 +- .../core/bean/HostInferenceLoggerPrinter.java | 5 +- .../core/manager/DefaultCacheManager.java | 45 ++--- .../fate/serving/core/monitor/WatchDog.java | 164 +++++++++--------- serving-server/pom.xml | 5 + .../webank/ai/fate/serving/ServingServer.java | 24 +-- .../webank/ai/fate/serving/SpringConfig.java | 44 +++-- .../serving/service/InferenceService.java | 7 +- .../ai/fate/serving/service/ModelService.java | 11 +- .../ai/fate/serving/service/ProxyService.java | 20 +-- .../ServiceOverloadProtectionHandle.java | 7 +- 16 files changed, 206 insertions(+), 181 deletions(-) diff --git a/fate-serving-core/pom.xml b/fate-serving-core/pom.xml index 14fc13a3..6af4bf30 100644 --- a/fate-serving-core/pom.xml +++ b/fate-serving-core/pom.xml @@ -75,6 +75,11 @@ commons-codec 1.12 + + io.dropwizard.metrics + metrics-core + 4.1.0-rc2 + diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java index 83600619..19eb1637 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java @@ -16,6 +16,9 @@ package com.webank.ai.fate.serving.core.bean; +import com.codahale.metrics.Counter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; import com.google.common.collect.Maps; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -32,13 +35,16 @@ public class BaseContext implements Context { @Override public void printLog(Context context, Object req, ReturnResult resp) { - LOGGER.info("{}|{}|{}|{}|{}|{}|{}|{}|{}", GetSystemInfo.getLocalIp(), context.getSeqNo(), Dict.NONE, context.getActionType(), context.getCostTime(), - resp != null ? resp.getRetcode() : Dict.NONE, WatchDog.get(), req, resp); + LOGGER.info("{}|{}|{}|{}|{}|{}|{}|{}", GetSystemInfo.getLocalIp(), context.getSeqNo(), Dict.NONE, context.getActionType(), context.getCostTime(), + resp != null ? resp.getRetcode() : Dict.NONE, req, resp); } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Configuration.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Configuration.java index bd0d7bb3..1a90fb77 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Configuration.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Configuration.java @@ -125,6 +125,14 @@ public static Integer getPropertyInt(String key) { return Integer.parseInt(getProperty(key)); } + public static Integer getPropertyInt(String key,Integer defaultInteger) { + if (getProperty(key) == null) { + return defaultInteger; + } + return Integer.parseInt(getProperty(key)); + } + + public static Integer getPropertyInt(String key, int defaultValue) { if (getProperty(key) == null) { return defaultValue; diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java index 69efa0e6..1f487018 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java @@ -134,7 +134,7 @@ public class Dict { public static final String GUEST_APP_ID = "guestAppId"; public static final String HOST_APP_ID = "hostAppId"; public static final String SERVICE_ID = "serviceId"; - + public static final String CONFIGPATH = "configpath"; public static final String ACL_USERNAME = "acl.username"; public static final String ACL_PASSWORD = "acl.password"; public static final String AUTH_FILE = "authFile"; diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GuestInferenceLoggerPrinter.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GuestInferenceLoggerPrinter.java index abc23008..6cc866c1 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GuestInferenceLoggerPrinter.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GuestInferenceLoggerPrinter.java @@ -16,8 +16,6 @@ package com.webank.ai.fate.serving.core.bean; - -import com.webank.ai.fate.serving.core.monitor.WatchDog; import com.webank.ai.fate.serving.core.utils.GetSystemInfo; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -28,13 +26,12 @@ public class GuestInferenceLoggerPrinter implements LoggerPrinter featureIds) { -// if (!Boolean.parseBoolean(Configuration.getProperty("remoteModelInferenceResultCacheSwitch"))) { -// return null; -// } -// String remoteModelInferenceResultCacheKey = generateRemoteModelInferenceResultCacheKey(remoteParty, federatedRoles, featureIds); -// ReturnResult returnResult = getFromCache(remoteModelInferenceResultCacheKey, CacheType.REMOTE_MODEL_INFERENCE_RESULT); -// if (returnResult != null) { -// LOGGER.info("Get {} remote model inference result from cache.", remoteModelInferenceResultCacheKey); -// } -// return returnResult; -// } + @Override public ReturnResult getRemoteModelInferenceResult(FederatedParams guestFederatedParams) { @@ -212,7 +200,6 @@ private T getFromRedisCache(String cacheKey, CacheValueConfig cacheValueConf try (Jedis jedis = jedisPool.getResource()) { jedis.select(cacheValueConfig.getDbIndex()); String cacheValueString = jedis.get(cacheKey); - //T returnResultFromExternalCache = (T) ObjectTransform.json2Bean(cacheValueString, dataType); T returnResultFromExternalCache =JSON.parseObject(cacheValueString,dataType); return returnResultFromExternalCache; } @@ -223,7 +210,6 @@ private void putIntoRedisCache(String cacheKey, CacheValueConfig cacheValueConfi try (Jedis jedis = jedisPool.getResource()) { Pipeline redisPipeline = jedis.pipelined(); redisPipeline.select(cacheValueConfig.getDbIndex()); - //redisPipeline.set(cacheKey, ObjectTransform.bean2Json(returnResult)); redisPipeline.set(cacheKey, JSON.toJSONString(returnResult)); redisPipeline.expire(cacheKey, cacheValueConfig.getTtl()); redisPipeline.sync(); @@ -279,10 +265,7 @@ private CacheValueConfig getCacheValueConfig(String cacheKey, CacheType cacheTyp } } - private int getCacheDBIndex(String cacheKey, int[] dbIndexs) { - int i = Hashing.murmur3_128().hashString(cacheKey, Charsets.UTF_8).asInt() % dbIndexs.length; - return dbIndexs[i > 0 ? i : -i]; - } + private String generateInferenceResultCacheKey(String partyId, String caseid) { return StringUtils.join(Arrays.asList(partyId, caseid), "_"); diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/monitor/WatchDog.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/monitor/WatchDog.java index ab6daaae..e8a75f71 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/monitor/WatchDog.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/monitor/WatchDog.java @@ -1,80 +1,84 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.serving.core.monitor; - - -import com.webank.ai.fate.serving.core.bean.Context; -import org.apache.commons.lang3.StringUtils; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicLong; - -public class WatchDog { - - private static final int MAX_PROCESS_COUNT = 100000; - private static AtomicLong RPC_IN_PROCESS = new AtomicLong(0); - private static AtomicLong TOTAL_RPC_PROCESS = new AtomicLong(0); - private static AtomicLong COMPLATE_RPC_PROCESS = new AtomicLong(0); - private static ConcurrentHashMap SERVICE_PROCESS_COUNT_MAP = new ConcurrentHashMap(); - - public static void enter(Context context) { - RPC_IN_PROCESS.addAndGet(1); - } - - public static void quit(Context context) { - RPC_IN_PROCESS.decrementAndGet(); - } - - public static long get() { - return RPC_IN_PROCESS.get(); - } - - public static void enter(String serviceName) { - if (StringUtils.isNotEmpty(serviceName)) { - if (SERVICE_PROCESS_COUNT_MAP.get(serviceName) == null) { - SERVICE_PROCESS_COUNT_MAP.putIfAbsent(serviceName, new AtomicLong(1)); - } else { - SERVICE_PROCESS_COUNT_MAP.get(serviceName).incrementAndGet(); - } - // update in process - RPC_IN_PROCESS.incrementAndGet(); - // update total - TOTAL_RPC_PROCESS.incrementAndGet(); - } - } - - public static void quit(String serviceName) { - if (StringUtils.isNotEmpty(serviceName)) { - if (SERVICE_PROCESS_COUNT_MAP.containsKey(serviceName)) { - SERVICE_PROCESS_COUNT_MAP.get(serviceName).decrementAndGet(); - // update in process - RPC_IN_PROCESS.decrementAndGet(); - } - } - } - - public static void complete(String serviceName) { - quit(serviceName); - // update complate process - COMPLATE_RPC_PROCESS.incrementAndGet(); - } - - public static void getCount() { - System.out.println("rpc in process count: " + RPC_IN_PROCESS + ";\n\r" + "complate process count: " - + COMPLATE_RPC_PROCESS + ";\n\r" + "total rpc process count: " + TOTAL_RPC_PROCESS + ";"); - } -} +///* +// * Copyright 2019 The FATE Authors. All Rights Reserved. +// * +// * 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.webank.ai.fate.serving.core.monitor; +// +// +//import com.codahale.metrics.MetricRegistry; +//import com.webank.ai.fate.serving.core.bean.Context; +//import org.apache.commons.lang3.StringUtils; +// +//import java.util.concurrent.ConcurrentHashMap; +//import java.util.concurrent.atomic.AtomicLong; +// +//public class WatchDog { +// +// private static MetricRegistry metricRegistry= new MetricRegistry(); +// private static final int MAX_PROCESS_COUNT = 100000; +// private static AtomicLong RPC_IN_PROCESS = new AtomicLong(0); +// private static AtomicLong TOTAL_RPC_PROCESS = new AtomicLong(0); +// private static AtomicLong COMPLATE_RPC_PROCESS = new AtomicLong(0); +// private static ConcurrentHashMap SERVICE_PROCESS_COUNT_MAP = new ConcurrentHashMap(); +// +// public static void enter(Context context) { +// +// metricRegistry. +// RPC_IN_PROCESS.addAndGet(1); +// } +// +// public static void quit(Context context) { +// RPC_IN_PROCESS.decrementAndGet(); +// } +// +// public static long get() { +// return RPC_IN_PROCESS.get(); +// } +// +// public static void enter(String serviceName) { +// if (StringUtils.isNotEmpty(serviceName)) { +// if (SERVICE_PROCESS_COUNT_MAP.get(serviceName) == null) { +// SERVICE_PROCESS_COUNT_MAP.putIfAbsent(serviceName, new AtomicLong(1)); +// } else { +// SERVICE_PROCESS_COUNT_MAP.get(serviceName).incrementAndGet(); +// } +// // update in process +// RPC_IN_PROCESS.incrementAndGet(); +// // update total +// TOTAL_RPC_PROCESS.incrementAndGet(); +// } +// } +// +// public static void quit(String serviceName) { +// if (StringUtils.isNotEmpty(serviceName)) { +// if (SERVICE_PROCESS_COUNT_MAP.containsKey(serviceName)) { +// SERVICE_PROCESS_COUNT_MAP.get(serviceName).decrementAndGet(); +// // update in process +// RPC_IN_PROCESS.decrementAndGet(); +// } +// } +// } +// +// public static void complete(String serviceName) { +// quit(serviceName); +// // update complate process +// COMPLATE_RPC_PROCESS.incrementAndGet(); +// } +// +// public static void getCount() { +// System.out.println("rpc in process count: " + RPC_IN_PROCESS + ";\n\r" + "complate process count: " +// + COMPLATE_RPC_PROCESS + ";\n\r" + "total rpc process count: " + TOTAL_RPC_PROCESS + ";"); +// } +//} diff --git a/serving-server/pom.xml b/serving-server/pom.xml index e775763f..319bf97a 100644 --- a/serving-server/pom.xml +++ b/serving-server/pom.xml @@ -203,6 +203,11 @@ httpclient 4.5.7 + + io.dropwizard.metrics + metrics-core + 4.1.2 + com.webank.ai.fate fate-register diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index 39f76002..d9b061b2 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -16,6 +16,7 @@ package com.webank.ai.fate.serving; +import com.codahale.metrics.ConsoleReporter; import com.google.common.collect.Sets; import com.webank.ai.fate.jmx.server.FateMBeanServer; @@ -49,6 +50,7 @@ import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; public class ServingServer implements InitializingBean { private static final Logger LOGGER = LogManager.getLogger(); @@ -60,16 +62,17 @@ public class ServingServer implements InitializingBean { public ServingServer() { } - public ServingServer(String confPath) { this.confPath = new File(confPath).getAbsolutePath(); - - System.setProperty("configpath", confPath); + System.setProperty(Dict.CONFIGPATH, confPath); new Configuration(confPath).load(); new com.webank.ai.eggroll.core.utils.Configuration(confPath).load(); - - System.setProperty(Dict.ACL_USERNAME, Configuration.getProperty(Dict.ACL_USERNAME)); - System.setProperty(Dict.ACL_PASSWORD, Configuration.getProperty(Dict.ACL_PASSWORD)); + if(Configuration.getProperty(Dict.ACL_USERNAME)!=null) { + System.setProperty(Dict.ACL_USERNAME, Configuration.getProperty(Dict.ACL_USERNAME)); + } + if(Configuration.getProperty(Dict.ACL_PASSWORD)!=null) { + System.setProperty(Dict.ACL_PASSWORD, Configuration.getProperty(Dict.ACL_PASSWORD)); + } } public static void main(String[] args) { @@ -98,8 +101,6 @@ public static void main(String[] args) { private void start(String[] args) throws IOException { this.initialize(); applicationContext = SpringApplication.run(SpringConfig.class, args); - - ApplicationHolder.applicationContext = applicationContext; int port = Integer.parseInt(Configuration.getProperty(Dict.PROPERTY_SERVER_PORT)); //TODO: Server custom configuration @@ -111,9 +112,7 @@ private void start(String[] args) throws IOException { serverBuilder.addService(ServerInterceptors.intercept(applicationContext.getBean(ModelService.class), new ServiceExceptionHandler(), new ServiceOverloadProtectionHandle()), ModelService.class); serverBuilder.addService(ServerInterceptors.intercept(applicationContext.getBean(ProxyService.class), new ServiceExceptionHandler(), new ServiceOverloadProtectionHandle()), ProxyService.class); server = serverBuilder.build(); - - LOGGER.info("Server started listening on port: {}, use configuration: {}", port, this.confPath); - + LOGGER.info("server started listening on port: {}, use configuration: {}", port, this.confPath); server.start(); String userRegisterString = Configuration.getProperty(Dict.USE_REGISTER); useRegister = Boolean.valueOf(userRegisterString); @@ -158,6 +157,9 @@ private void start(String[] args) throws IOException { } } + ConsoleReporter reporter = applicationContext.getBean(ConsoleReporter.class); + reporter.start(1, TimeUnit.SECONDS); + Runtime.getRuntime().addShutdownHook(new Thread() { @Override diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java b/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java index 16089945..bb3a1a95 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java @@ -1,6 +1,7 @@ package com.webank.ai.fate.serving; +import com.codahale.metrics.*; import com.webank.ai.fate.register.loadbalance.LoadBalancer; import com.webank.ai.fate.register.loadbalance.RandomLoadBalance; import com.webank.ai.fate.register.router.DefaultRouterService; @@ -16,11 +17,12 @@ import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Service; +import java.util.concurrent.TimeUnit; + @Configuration @ComponentScan(basePackages = {"com.webank.ai.fate.serving.*"}) @ConfigurationProperties @PropertySource(value = "file:${user.dir}/conf/serving-server.properties", ignoreResourceNotFound = false) -//@PropertySource(value ={"file:${user.dir}/config/custom.properties","file:${user.dir}/config/custom_prison.properties"}, ignoreResourceNotFound = true) @SpringBootApplication @Service public class SpringConfig { @@ -33,7 +35,7 @@ public class SpringConfig { @Bean ZookeeperRegistry getServiceRegistry() { - // String project, String environment, int port + String useRegisterString = com.webank.ai.fate.serving .core.bean.Configuration.getProperty("useRegister"); if (Boolean.valueOf(useRegisterString)) @@ -44,29 +46,45 @@ ZookeeperRegistry getServiceRegistry() { } - + @Bean + public MetricRegistry metrics() { + return new MetricRegistry(); + } @Bean - RouterService getRouterService() { + public Meter requestMeter(MetricRegistry metrics) { + return metrics.meter("request"); + } - DefaultRouterService routerService = new DefaultRouterService(); +// @Bean +// public Histogram responseSizes(MetricRegistry metrics) { +// return metrics.histogram("response-sizes"); +// } - routerService.setRegistry(zookeeperRegistry); + @Bean + public Counter pendingJobs(MetricRegistry metrics) { + return metrics.counter("requestCount"); + } - // routerService.setLoadBalancer(this.loadBalancer); + @Bean + public ConsoleReporter consoleReporter(MetricRegistry metrics) { + return ConsoleReporter.forRegistry(metrics) + .convertRatesTo(TimeUnit.SECONDS) + .convertDurationsTo(TimeUnit.MILLISECONDS) + .build(); + } + @Bean + RouterService getRouterService() { + DefaultRouterService routerService = new DefaultRouterService(); + routerService.setRegistry(zookeeperRegistry); return routerService; } -// @Override -// public void afterPropertiesSet() throws Exception { -// //List lists = zookeeperRegistry.lookup(URL.valueOf("proxy/online/unaryCall")); -// System.err.println( routerService.router(URL.valueOf("proxy/online/unaryCall"), LoadBalanceModel.random_with_weight)); -// -// } + } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java index b808d1e1..be4b2001 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java @@ -17,6 +17,8 @@ package com.webank.ai.fate.serving.service; import com.alibaba.fastjson.JSON; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; import com.google.protobuf.ByteString; import com.webank.ai.eggroll.core.utils.ObjectTransform; import com.webank.ai.fate.api.serving.InferenceServiceGrpc; @@ -42,7 +44,8 @@ public class InferenceService extends InferenceServiceGrpc.InferenceServiceImplB private static final Logger accessLOGGER = LogManager.getLogger(Dict.ACCESS); @Autowired GuestInferenceProvider guestInferenceProvider; - + @Autowired + MetricRegistry metricRegistry; @Override @RegisterService(useDynamicEnvironment = true, serviceName = "inference") @@ -68,7 +71,7 @@ private void inferenceServiceAction(InferenceMessage req, StreamObserver responseStreamObserver) { - Context context = new BaseContext(new BaseLoggerPrinter()); + Context context = new BaseContext(new BaseLoggerPrinter(),metricRegistry); context.setActionType(ModelActionType.MODEL_LOAD.name()); context.preProcess(); ReturnResult returnResult = null; @@ -126,7 +130,7 @@ public synchronized void publishLoad(PublishRequest req, StreamObserver responseStreamObserver) { - Context context = new BaseContext(new BaseLoggerPrinter()); + Context context = new BaseContext(new BaseLoggerPrinter(),metricRegistry); context.setActionType(ModelActionType.MODEL_PUBLISH_ONLINE.name()); context.preProcess(); ReturnResult returnResult = null; @@ -159,7 +163,7 @@ public synchronized void publishOnline(PublishRequest req, StreamObserver responseStreamObserver) { - Context context = new BaseContext(new BaseLoggerPrinter()); + Context context = new BaseContext(new BaseLoggerPrinter(),metricRegistry); context.setActionType(ModelActionType.MODEL_PUBLISH_ONLINE.name()); context.preProcess(); ReturnResult returnResult = null; @@ -227,7 +231,6 @@ public void store() { public void doSaveProperties(Map properties, File file, long version) { - //logger.info("prepare to save modelinfo {} {}", file, properties); if (file == null) { return; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ProxyService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ProxyService.java index 9e13a6e5..4c641300 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ProxyService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ProxyService.java @@ -17,6 +17,7 @@ package com.webank.ai.fate.serving.service; import com.alibaba.fastjson.JSON; +import com.codahale.metrics.MetricRegistry; import com.google.protobuf.ByteString; import com.webank.ai.eggroll.core.utils.ObjectTransform; import com.webank.ai.fate.api.networking.proxy.DataTransferServiceGrpc; @@ -37,30 +38,23 @@ public class ProxyService extends DataTransferServiceGrpc.DataTransferServiceImp private static final Logger logger = LogManager.getLogger(); @Autowired HostInferenceProvider hostInferenceProvider; - + @Autowired + MetricRegistry metricRegistry; @Override @RegisterService(serviceName = Dict.UNARYCALL, useDynamicEnvironment = true) public void unaryCall(Proxy.Packet req, StreamObserver responseObserver) { ReturnResult responseResult = null; - Context context = new BaseContext(new HostInferenceLoggerPrinter()); + Context context = new BaseContext(new HostInferenceLoggerPrinter(),metricRegistry); context.setActionType(req.getHeader().getCommand().getName()); context.preProcess(); - //Map requestData=null; HostFederatedParams requestData = null; try { - //requestData = (Map) ObjectTransform.json2Bean(req.getBody().getValue().toStringUtf8(), HashMap.class); - //{"caseId":"73aca9d0dec811e9a0af5254005e961b","featureIdMap":{"device_id":"xxxxxxxxxx","phone_num":""},"local":{"partyId":"10000","role":"host"}, - // "partnerLocal":{"partyId":"9999","role":"guest"},"partnerModelInfo":{"name":"201909241953242070093","namespace":"guest#9999#guest-9999#host-10000#model"}, - // "role":{"allRole":{"host":["10000"],"guest":["9999"]}},"seqNo":"1b91bb3621704dc3bf4e63a1ed22e81d"} - String data = req.getBody().getValue().toStringUtf8(); logger.info("unaryCall {} head {}", data,req.getHeader().getCommand().getName()); - requestData = JSON.parseObject(data, HostFederatedParams.class); - context.setCaseId(requestData.getCaseId() != null ? requestData.getCaseId() : Dict.NONE); switch (req.getHeader().getCommand().getName()) { @@ -84,17 +78,11 @@ public void unaryCall(Proxy.Packet req, StreamObserver responseObs Proxy.Metadata.Builder metaDataBuilder = Proxy.Metadata.newBuilder(); Proxy.Topic.Builder topicBuilder = Proxy.Topic.newBuilder(); - FederatedParty partnerParty = requestData.getPartnerLocal(); FederatedParty party = requestData.getLocal(); - context.putData(Dict.GUEST_APP_ID, partnerParty.getPartyId()); context.putData(Dict.HOST_APP_ID, party.getPartyId()); - -// FederatedParty partnerParty = (FederatedParty) ObjectTransform.json2Bean(requestData.get("partner_local").toString(), FederatedParty.class); -// FederatedParty party = (FederatedParty) ObjectTransform.json2Bean(requestData.get("local").toString(), FederatedParty.class); - metaDataBuilder.setSrc( topicBuilder.setPartyId(String.valueOf(party.getPartyId())) .setRole(Dict.HOST) diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ServiceOverloadProtectionHandle.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ServiceOverloadProtectionHandle.java index 246307fb..9b1d1538 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ServiceOverloadProtectionHandle.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ServiceOverloadProtectionHandle.java @@ -17,7 +17,6 @@ package com.webank.ai.fate.serving.service; import com.webank.ai.fate.serving.core.bean.Dict; -import com.webank.ai.fate.serving.core.monitor.WatchDog; import io.grpc.*; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; @@ -40,7 +39,7 @@ public ServerCall.Listener interceptCall(ServerCall Date: Mon, 23 Dec 2019 14:55:27 +0800 Subject: [PATCH 045/190] update to 1.1.2 Signed-off-by: v_dylanxu <136539068@qq.com> --- .../Maven__com_alibaba_fastjson_1_2_25.xml | 13 ------------- ...rxml_jackson_core_jackson_annotations_2_10_0.xml | 13 ------------- ...m_fasterxml_jackson_core_jackson_core_2_10_0.xml | 13 ------------- ...sterxml_jackson_core_jackson_databind_2_10_0.xml | 13 ------------- 4 files changed, 52 deletions(-) delete mode 100644 .idea/libraries/Maven__com_alibaba_fastjson_1_2_25.xml delete mode 100644 .idea/libraries/Maven__com_fasterxml_jackson_core_jackson_annotations_2_10_0.xml delete mode 100644 .idea/libraries/Maven__com_fasterxml_jackson_core_jackson_core_2_10_0.xml delete mode 100644 .idea/libraries/Maven__com_fasterxml_jackson_core_jackson_databind_2_10_0.xml diff --git a/.idea/libraries/Maven__com_alibaba_fastjson_1_2_25.xml b/.idea/libraries/Maven__com_alibaba_fastjson_1_2_25.xml deleted file mode 100644 index 88d06a73..00000000 --- a/.idea/libraries/Maven__com_alibaba_fastjson_1_2_25.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_annotations_2_10_0.xml b/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_annotations_2_10_0.xml deleted file mode 100644 index c7085107..00000000 --- a/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_annotations_2_10_0.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_core_2_10_0.xml b/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_core_2_10_0.xml deleted file mode 100644 index ba1879ab..00000000 --- a/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_core_2_10_0.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_databind_2_10_0.xml b/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_databind_2_10_0.xml deleted file mode 100644 index 12966549..00000000 --- a/.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_databind_2_10_0.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file From e8f819da703c7f4fd9105e9fc0abf684110b45b0 Mon Sep 17 00:00:00 2001 From: qianduoduo <1002502212@qq.com> Date: Mon, 23 Dec 2019 15:22:46 +0800 Subject: [PATCH 046/190] =?UTF-8?q?serving=20=E9=83=A8=E7=BD=B2=E8=84=9A?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../allinone_cluster_configurations.sh | 7 + .../cluster-deploy/scripts/jdk_install.sh | 18 ++ .../cluster-deploy/scripts/package.sh | 165 +++++++++++++++ .../cluster-deploy/scripts/redis/service.sh | 87 ++++++++ .../cluster-deploy/scripts/redis_install.sh | 188 ++++++++++++++++++ .../cluster-deploy/scripts/services.sh | 78 ++++++++ .../scripts/start_env/services.sh | 78 ++++++++ .../cluster-deploy/scripts/zk/service.sh | 87 ++++++++ .../cluster-deploy/scripts/zkui/service.sh | 88 ++++++++ .../cluster-deploy/scripts/zkui_install.sh | 14 ++ .../scripts/zookeeper_install.sh | 41 ++++ ...\347\275\262\346\226\207\346\241\243.docx" | Bin 0 -> 59610 bytes 12 files changed, 851 insertions(+) create mode 100644 serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh create mode 100644 serving-server/cluster-deploy/scripts/jdk_install.sh create mode 100644 serving-server/cluster-deploy/scripts/package.sh create mode 100644 serving-server/cluster-deploy/scripts/redis/service.sh create mode 100644 serving-server/cluster-deploy/scripts/redis_install.sh create mode 100644 serving-server/cluster-deploy/scripts/services.sh create mode 100644 serving-server/cluster-deploy/scripts/start_env/services.sh create mode 100644 serving-server/cluster-deploy/scripts/zk/service.sh create mode 100644 serving-server/cluster-deploy/scripts/zkui/service.sh create mode 100644 serving-server/cluster-deploy/scripts/zkui_install.sh create mode 100644 serving-server/cluster-deploy/scripts/zookeeper_install.sh create mode 100644 "serving-server/doc/serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" diff --git a/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh b/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh new file mode 100644 index 00000000..51b6028a --- /dev/null +++ b/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh @@ -0,0 +1,7 @@ +#!/bin/bash +user=app +host_guest=(172.16.153.9 172.16.153.113) +roll_hostAndguest=(172.16.153.9 172.16.153.113) +deploy_dir=/data/projects +redis_password=fate_dev +apply_zk=true diff --git a/serving-server/cluster-deploy/scripts/jdk_install.sh b/serving-server/cluster-deploy/scripts/jdk_install.sh new file mode 100644 index 00000000..d7913bbc --- /dev/null +++ b/serving-server/cluster-deploy/scripts/jdk_install.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +BASE_SERVER=https://webank-ai-1251170195.cos.ap-guangzhou.myqcloud.com/ +jdk_version=jdk-8u192-linux-x64.tar.gz +jdk=jdk-8u192-linux-x64 +yum install -y wget +wget $BASE_SERVER/${jdk_version} +tar -zxvf ${jdk_version} -C /usr/local +cat >> /etc/profile << EOF +export JAVA_HOME=/usr/local/${jdk} +export PATH=\$PATH:\$JAVA_HOME/bin +EOF +#source /etc/profile + +#cat >> /etc/profile << EOF +#export JAVA_HOME=/usr/local/${jdk} +#export PATH=\$PATH:\$JAVA_HOME/bin +#EOF diff --git a/serving-server/cluster-deploy/scripts/package.sh b/serving-server/cluster-deploy/scripts/package.sh new file mode 100644 index 00000000..d12ceb63 --- /dev/null +++ b/serving-server/cluster-deploy/scripts/package.sh @@ -0,0 +1,165 @@ +#!/bean package -DskipTestsin/bash +# +#Copyright 2019 The serving Authors. All Rights Reserved. +# + +set -e +source ./allinone_cluster_configurations.sh +cwd=$(cd `dirname $0`; pwd) +cd ${cwd} +public_path=$deploy_dir +package_path=$cwd/FATE-Serving +fate_serving_path=$public_path/fate-serving +sering_path=$fate_serving_path/serving +fate_serving=fate-serving +serving=serving +fate_serving_zip=fate-serving-server-*-release.zip +fate_serving_jar=fate-serving-server-*.jar +router_zip=fate-serving-router-*-release.zip +router_jar=fate-serving-router-*.jar +common=$public_path/fate-serving/common +router=router +router_path=$fate_serving_path/router +if [ ! -d $common ]; then + mkdir -p $common +else + echo [INFO] $common dir exist +fi +cp jdk_install.sh $common +cp redis_install.sh $common +cp services.sh $fate_serving_path +cd $common +echo "[INFO] jdk install" +#sudo sh jdk_install.sh +#sudo rm -rf jdk_install.sh +echo "[INFO] redis install" +sudo sh redis_install.sh +sudo rm -rf redis_install.sh +cd redis/conf +sudo sed -i.bak "s/# requirepass foobared/requirepass ${redis_password}/g" ./redis.conf +sudo sed -i.bak "s/databases 16/databases 50/g" ./redis.conf +cd ${cwd}/../../../ +echo "[INFO] mvn clean package start" +mvn clean package -DskipTests +if [ $? -eq 0 ]; then + echo "[INFO] mvn clean package success" +else + echo "[INFO] mvn clan package filed" +fi + +if [ ! -d $fate_serving_path ]; then + cd $public_path + mkdir $fate_serving +else + echo [INFO] $fate_serving_path dir exist +fi + +if [ -d $fate_serving_path ]; then + if [ ! -d $sering_path ]; then + cd $fate_serving_path + mkdir $serving + mkdir $router + else + echo [INFO] $fate_serving_path dir exist + fi + +else + echo "" +fi + +cd $cwd/../../target +cp $fate_serving_zip $sering_path +if [ $? -eq 0 ]; then + echo "[INFO] cp fate-serving-server-*-release.zip success" +else + echo "[INFO] cp fate-serving-server-*-release.zip filed" +fi +cd $sering_path +unzip $fate_serving_zip +ln -s $fate_serving_jar fate-serving-server.jar + +echo "[INFO] cp fate-serving-router-*-release.zip success" +cd $cwd/../../../router/target +cp $router_zip $router_path +if [ $? -eq 0 ]; then + echo "[INFO] cp fate-serving-router-*-release.zip success" +else + echo "[INFO] cp fate-serving-server-*-release.zip filed" +fi +echo "-------------------routerpach" +cd $router_path +unzip $router_zip +ln -s $router_jar fate-serving-router.jar +echo "--------------------ln -s" +cd $cwd +cp start_env/services.sh $public_path/fate-serving/common +echo "------------apply----" ${apply_zk} + +if [ ${apply_zk} = "false" ] +then + cd $router_path/conf + sed -i 's#useRegister=true#'useRegister=false'#' proxy.properties + sed -i 's#useZkRouter=true#'useZkRouter=false'#' proxy.properties + cd $sering_path/conf + sed -i 's#useRegister=true#'useRegister=false'#' serving-server.properties + sed -i 's#useZkRouter=true#'useZkRouter=false'#' serving-server.properties +fi + sh zookeeper_install.sh + cd $cwd + echo "-------zkui_instal start" + sh zkui_install.sh +sudo cp redis/service.sh $common/redis +cp zk/service.sh $common/zookeeper* +cp zkui/service.sh $common/zkui +echo "---------------------sed roll" +if [[ ${host_guest[0]} ]] +then + cd ${public_path}/fate-serving/serving/conf + sed -i 's#127.0.0.1:8011#'${roll_hostAndguest[0]}'#' serving-server.properties +else + echo "no have host--------false no null" + exit +fi + +init_env() { + ssh -tt ${user}@${host_guest[1]} << eeooff + if [ ! -d ${public_path} ] + then + mkdir -p ${public_path} + fi + exit +eeooff + cd ${public_path} + tar -zcvf fate-serving.tar.gz fate-serving/ + + scp ${public_path}/fate-serving.tar.gz @${host_guest[1]}:${public_path} + ssh -tt ${user}@${host_guest[1]} << eeooff + cd ${public_path} + echo "public_path-------" ${public_path} + tar -zxvf fate-serving.tar.gz + rm -rf fate-serving.tar.gz + cd ${public_path}/fate-serving/serving/conf + sed -i 's#${roll_hostAndguest[0]}#'${roll_hostAndguest[1]}'#' serving-server.properties +exit +eeooff + +} + +echo "host_guest1--if-----" ${host_guest[1]} +if [[ ${host_guest[1]} ]] +then + echo "------guest hava host_guest1 -----is not null" + if [[ ${roll_hostAndguest[1]} != "" ]]; then + init_env + else + echo "please input guest from allinone_cluster_configurations" + exit 0 + fi + +else + echo "no have guest--------false no null" +fi + + + + diff --git a/serving-server/cluster-deploy/scripts/redis/service.sh b/serving-server/cluster-deploy/scripts/redis/service.sh new file mode 100644 index 00000000..0e3fa590 --- /dev/null +++ b/serving-server/cluster-deploy/scripts/redis/service.sh @@ -0,0 +1,87 @@ +i#!/bin/bash + +# +# Copyright 2019 The FATE Authors. All Rights Reserved. +# +# 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. +# +set -e +basepath=$(cd `dirname $0`;pwd) +user=`whoami` + +getpid() { + pid=`ps -ef | grep redis-server | grep -v grep | awk '{print $2}'` +} + +status() { + getpid + if [[ -n ${pid} ]]; then + echo "status:`ps aux | grep ${pid} | grep -v grep`" + else + echo "service not running" + fi +} + +start() { + getpid + if [[ ${pid} == "" ]]; then + $basepath/bin/redis-server & + if [[ $? -eq 0 ]]; then + sleep 2 + getpid + echo "service start sucessfully. pid: ${pid}" + else + echo "service start failed" + fi + else + echo "service already started. pid: ${pid}" + fi +} + +stop() { + getpid + if [[ -n ${pid} ]]; then + echo "killing:`ps aux | grep ${pid} | grep -v grep`" + kill -9 ${pid} + if [[ $? -eq 0 ]]; then + echo "killed" + else + echo "kill error" + fi + else + echo "service not running" + fi +} + +case "$1" in + start) + start + status + ;; + + stop) + stop + ;; + status) + status + ;; + + restart) + stop + start + status + ;; + *) + echo "usage: $0 {start|stop|status|restart}" + exit -1 +esac diff --git a/serving-server/cluster-deploy/scripts/redis_install.sh b/serving-server/cluster-deploy/scripts/redis_install.sh new file mode 100644 index 00000000..4dfc654e --- /dev/null +++ b/serving-server/cluster-deploy/scripts/redis_install.sh @@ -0,0 +1,188 @@ +#!/bin/bash +#------------------------------------------------------------------------------------------------------------------# +#| Some people die at the age of 25 and don't bury themselves in the earth until they are 75 |# +#------------------------------------------------------------------------------------------------------------------# +#| $$$$ $$ $$ $$$$$$ $$ $$ $$$$$$ $$ $$$$$$ $$$$$$ |# +#| $$ $$ $$ $$ $$ $$ $$ $$ $$ $$ $$ |# +#| $$ $$$$$$$ $$$$$ $$ $$ $ $$ $$$$$$ $$ $$$$$ $$$$$ |# +#| $$ $$ $$ $$ $$ $ $ $$ $$ $$ $$ $$ |# +#| $$$$ $$ $$ $$$$$$ $$ $$ $$$$$ $$ $$$$$$ $$$$$$ $$$$$$ |# +#------------------------------------------------------------------------------------------------------------------# +cwd=$(cd `dirname $0`; pwd) +onversion="4.0.3" +offversion=`basename redis-*.tar.gz .tar.gz | awk -F '-' '{print$2}'` +installdir=$(cd `dirname $0`; pwd) + +function initize(){ + installdir=$(cd `dirname $0`; pwd) +} + +function checkroot(){ +if [ $UID -ne 0 ] + then + echo "|----------------------------------------------------------------------------------------------------------------|" + echo "|------------------------------------------[权限不足...请切换至root用户]-----------------------------------------|" + echo "|----------------------------------------------------------------------------------------------------------------|" + exit; +fi +} + +function judge(){ + echo + offfile=`ls | grep redis-*.tar.gz` + if [[ "$offfile" != "" ]] + then + echo "|----------------------------------------------------------------------------------------------------------------|" + echo "|-------------------------------------------------[发现离线包]---------------------------------------------------|" + echo "|----------------------------------------------------------------------------------------------------------------|" + /usr/bin/sleep 3 + offinstall + else + echo "|----------------------------------------------------------------------------------------------------------------|" + echo "|-------------------------------------------------[未发现离线包]-------------------------------------------------|" + echo "|--------------------------------------------[开始判断是否连接外网安装]------------------------------------------|" + /usr/bin/sleep 3 + network + fi +} + +function offinstall(){ + echo "|----------------------------------------------------------------------------------------------------------------|" + echo "|------------------------------------------------[离线包安装中]--------------------------------------------------|" + echo "|----------------------------------------------------------------------------------------------------------------|" + tar -zxvf redis-${offversion}.tar.gz >/dev/null 2>&1 + redis="redis-${offversion}" + cd ${redis}/src && make >/dev/null 2>&1 + if [[ $? -ne 0 ]]; then + echo "编译出错" + else + echo "|----------------------------------------------------------------------------------------------------------------|" + echo "|---------------------------------------------------[编译完成]---------------------------------------------------|" + echo "|----------------------------------------------------------------------------------------------------------------|" + /usr/bin/sleep 3 + intend + fi +} + +function network(){ + httpcode=`curl -I -m 10 -o /dev/null -s -w %{http_code}'\n' http://www.baidu.com` + net1=$(echo $httpcode | grep "200") + if [[ "$net1" != "" ]] + then + echo "|----------------------------------------------------------------------------------------------------------------|" + echo "|-----------------------------------------------------[联网]-----------------------------------------------------|" + echo "|-------------------------------------------------[准备联网安装]-------------------------------------------------|" + echo "|----------------------------------------------------------------------------------------------------------------|" + /usr/bin/sleep 3 + online + else + echo "|----------------------------------------------------------------------------------------------------------------|" + echo "|-------------------------------------------[未联网,无离线安装包,准备退出]---------------------------------------|" + echo "|----------------------------------------------------------------------------------------------------------------|" + /usr/bin/sleep 3 + exit; + fi +} +function online(){ + wget_v=`which wget` + wget_vv=$(echo $wget_v | grep wget) + if [[ "$wget_vv" != "" ]] + then + echo "|----------------------------------------------------------------------------------------------------------------|" + echo "|--------------------------------------`wget -V |head -n 1`---------------------------------------|" + echo "|----------------------------------------------------------------------------------------------------------------|" + wget http://download.redis.io/releases/redis-${onversion}.tar.gz + installon + else + echo "|----------------------------------------------------------------------------------------------------------------|" + echo "|----------------------------------------[检测到wget没有安装, 准备安装wget]---------------------------------------|" + echo "|----------------------------------------------------------------------------------------------------------------|" + yum install wget -y + echo "|----------------------------------------------------------------------------------------------------------------|" + echo "|--------------------------------------`wget -V |head -n 1`---------------------------------------|" + echo "|----------------------------------------------------------------------------------------------------------------|" + wget http://download.redis.io/releases/redis-${onversion}.tar.gz + installon + fi +} + +function installon(){ + echo "|----------------------------------------------------------------------------------------------------------------|" + echo "|------------------------------------------------[在线包安装中]--------------------------------------------------|" + echo "|----------------------------------------------------------------------------------------------------------------|" + tar -zxvf redis-${onversion}.tar.gz >/dev/null 2>&1 + redis="redis-${onversion}" + cd ${redis}/src && make >/dev/null 2>&1 + if [[ $? -ne 0 ]]; then + echo "编译出错" + else + echo "|----------------------------------------------------------------------------------------------------------------|" + echo "|--------------------------------------------------[编译完成]----------------------------------------------------|" + echo "|----------------------------------------------------------------------------------------------------------------|" + /usr/bin/sleep 3 + intend + fi +} + +function intend(){ + echo "|----------------------------------------------------------------------------------------------------------------|" + echo "|-------------------------------------------------[开始迁移文件]-------------------------------------------------|" + echo "|----------------------------------------------------------------------------------------------------------------|" + mkdir -p ${installdir}/redis/{logs,nodes,conf,bin} + cp redis-cli redis-server ${installdir}/redis/bin + cp redis-trib.rb ${installdir}/redis + cp ../redis.conf ${installdir}/redis/conf + echo "|----------------------------------------------------------------------------------------------------------------|" + echo "|-------------------------------------------------[数据迁移完成]-------------------------------------------------|" + echo "|----------------------------------------------------------------------------------------------------------------|" + /usr/bin/sleep 2 + echo "|----------------------------------------------------------------------------------------------------------------|" + echo "|-------------------------------------------------[清理多余文件]-------------------------------------------------|" + echo "|----------------------------------------------------------------------------------------------------------------|" + finish + /usr/bin/sleep 2 + echo "|----------------------------------------------------------------------------------------------------------------|" + echo "|-------------------------------------------------[配置快捷启动]-------------------------------------------------|" + echo "|----------------------------------------------------------------------------------------------------------------|" + service + /usr/bin/sleep 2 + echo "|----------------------------------------------------------------------------------------------------------------|" + echo "|-------------------------------------------------[修改配置文件]-------------------------------------------------|" + echo "|----------------------------------------------------------------------------------------------------------------|" + configfile + /usr/bin/sleep 2 + echo "|****************************************************************************************************************|" + echo "| WW WW EEEEEEE LL CCCCC OOOOOO MM MM EEEEEEE |" + echo "| WW WWWW WW EE LL CC OO OO MMMM MMMM EE |" + echo "| WW WW WW WW EEEEE LL CC OO OO MM MM MM MM EEEEE |" + echo "| WW W W WW EE LL CC OO OO MM M M MM EE |" + echo "| WW WW EEEEEEE LLLLLL CCCCC OOOOOO MM MMM MM EEEEEEE |" + echo "|****************************************************************************************************************|" +} +function finish(){ + echo + rm -rf ${installdir}/redis-* +} +function service(){ + cd ${installdir}/redis && echo "./bin/redis-server conf/redis.conf" > start.sh + chmod +x start.sh +} +function configfile(){ + cd ${installdir}/redis/conf + #后台 + sed -i 's/daemonize no/daemonize yes/' redis.conf + #端口 + #日志输出文件 + sed -i.bak "s/bind 127.0.0.1/bind 0.0.0.0/g" ./redis.conf + sed -i 's/logfile ""/logfile "\/usr\/local\/src\/redis\/logs\/redis.logs"/' redis.conf +} +function main(){ +checkroot +judge +} +main + +#cd $cwd/redis/bin +#echo "$cwd--------" $cwd +#echo "[INFO] --------redis start" +#./redis-server & diff --git a/serving-server/cluster-deploy/scripts/services.sh b/serving-server/cluster-deploy/scripts/services.sh new file mode 100644 index 00000000..40e9b281 --- /dev/null +++ b/serving-server/cluster-deploy/scripts/services.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +# +# Copyright 2019 The FATE Authors. All Rights Reserved. +# +# 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. +# + +modules=(router serving) + +cwd=`pwd` + +main() { + module=$1 + action=$2 + echo "--------------" + echo "[INFO] processing: ${module}" + if [[ "${module}" == "apigateway" ]];then + cd ${module}/bin/ + bash service.sh ${action} + else + cd ${module} + bash service.sh ${action} + fi + echo "--------------" + echo +} + +all() { + echo "[INFO] processing all modules" + echo "==================" + echo + for module in "${modules[@]}"; do + cd ${cwd} + main ${module} $2 + done + echo "==================" +} + +usage() { + echo "usage: $0 {all|[module1, ...]} {start|stop|status|restart}" +} + +multiple() { + total=$# + action=${!total} + for (( i=1; iSK>Q1{ME0E50R#ZB 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 K(7naG-v0qQi-BVR literal 0 HcmV?d00001 From af223c5529e2839fff177b5e01fa6a92bc0aeb31 Mon Sep 17 00:00:00 2001 From: qianduoduo <1002502212@qq.com> Date: Mon, 23 Dec 2019 15:24:47 +0800 Subject: [PATCH 047/190] =?UTF-8?q?serving=20=E9=83=A8=E7=BD=B2=E8=84=9A?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cluster-deploy/scripts/allinone_cluster_configurations.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh b/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh index 51b6028a..25d128f5 100644 --- a/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh +++ b/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh @@ -1,7 +1,7 @@ #!/bin/bash user=app -host_guest=(172.16.153.9 172.16.153.113) -roll_hostAndguest=(172.16.153.9 172.16.153.113) +host_guest=(192.168.0.1 192.168.0.2) +roll_hostAndguest=(192.168.0.1 192.168.0.2) deploy_dir=/data/projects redis_password=fate_dev apply_zk=true From 1d967f4d2831cb2d12135387e73ede94e8d7866b Mon Sep 17 00:00:00 2001 From: utu Date: Mon, 23 Dec 2019 20:08:49 +0800 Subject: [PATCH 048/190] =?UTF-8?q?proxy=20refactor=EF=BC=9Adev-test=20pas?= =?UTF-8?q?s.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../serving/proxy/bootstrap/Bootstrap.java | 2 +- .../proxy/rpc/grpc/InterGrpcServer.java | 3 +- .../proxy/rpc/grpc/IntraGrpcServer.java | 39 +++++++++++++++++- .../proxy/rpc/grpc/ProxyRequestHandler.java | 2 + .../proxy/rpc/router/ZkServingRouter.java | 41 ++++--------------- .../src/main/resources/application.properties | 1 - .../fate/serving/bean/InferenceRequest.java | 10 +++++ 7 files changed, 61 insertions(+), 37 deletions(-) diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java index 56ddc87e..f00c5aab 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java @@ -15,7 +15,7 @@ **/ @SpringBootApplication @ComponentScan(basePackages = {"com.webank.ai.fate.serving.proxy.*"}) -//@PropertySource("classpath:application.properties") +@PropertySource("classpath:application.properties") @EnableScheduling public class Bootstrap { diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterGrpcServer.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterGrpcServer.java index 4f1b81d7..9b2d596e 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterGrpcServer.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterGrpcServer.java @@ -1,4 +1,5 @@ package com.webank.ai.fate.serving.proxy.rpc.grpc; +import com.webank.ai.fate.register.provider.FateServerBuilder; import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.ServerInterceptors; @@ -34,7 +35,7 @@ public class InterGrpcServer implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { - ServerBuilder serverBuilder = ServerBuilder.forPort(port); + FateServerBuilder serverBuilder = (FateServerBuilder) ServerBuilder.forPort(port); serverBuilder.executor(executor); serverBuilder.addService(ServerInterceptors.intercept(interRequestHandler, new ServiceExceptionHandler())); serverBuilder.addService(interRequestHandler); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java index c9ca8cf8..e0c3f0a4 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java @@ -1,7 +1,14 @@ package com.webank.ai.fate.serving.proxy.rpc.grpc; +import com.webank.ai.fate.register.provider.FateServer; +import com.webank.ai.fate.register.provider.FateServerBuilder; +import com.webank.ai.fate.register.router.DefaultRouterService; +import com.webank.ai.fate.register.zookeeper.ZookeeperRegistry; +import com.webank.ai.fate.serving.proxy.common.Dict; +import com.webank.ai.fate.serving.proxy.rpc.router.ZkServingRouter; import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.ServerInterceptors; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; @@ -22,6 +29,20 @@ public class IntraGrpcServer implements InitializingBean { @Value("${proxy.grpc.intra.port:8867}") private Integer port; + @Value("${zk.url:zookeeper://localhost:2181}") + private String zkUrl ; + + @Value("${useZkRouter:false}") + private String useZkRouter; + + @Value("${acl.username:fate}") + private String aclUsername; + + @Value("${acl.password:fate}") + private String aclPassword; + + ZookeeperRegistry zookeeperRegistry; + Logger logger = LoggerFactory.getLogger(InterGrpcServer.class); Server server ; @@ -34,13 +55,29 @@ public class IntraGrpcServer implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { - ServerBuilder serverBuilder = ServerBuilder.forPort(port); + FateServerBuilder serverBuilder = (FateServerBuilder)ServerBuilder.forPort(port); serverBuilder.executor(executor); serverBuilder.addService(ServerInterceptors.intercept(intraRequestHandler, new ServiceExceptionHandler())); serverBuilder.addService(intraRequestHandler); server = serverBuilder.build(); server.start(); + if("true".equals(useZkRouter)&& StringUtils.isNotEmpty(zkUrl)) { + + System.setProperty("acl.username", aclUsername); + System.setProperty("acl.password", aclPassword); + + zookeeperRegistry = ZookeeperRegistry.getRegistery(zkUrl, Dict.SELF_PROJECT_NAME, Dict.SELF_ENVIRONMENT, Integer.valueOf(port)); + zookeeperRegistry.register(FateServer.serviceSets); + zookeeperRegistry.subProject("serving"); + + DefaultRouterService defaultRouterService = new DefaultRouterService(); + + defaultRouterService.setRegistry(zookeeperRegistry); + + ZkServingRouter.setZkRouterService(defaultRouterService); + } + } } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java index c60dbe35..ff372fb6 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java @@ -5,6 +5,7 @@ import com.google.common.collect.Maps; import com.webank.ai.fate.api.networking.proxy.DataTransferServiceGrpc; import com.webank.ai.fate.api.networking.proxy.Proxy; +import com.webank.ai.fate.register.annotions.RegisterService; import com.webank.ai.fate.serving.proxy.common.Dict; import com.webank.ai.fate.serving.proxy.rpc.core.*; import io.grpc.stub.StreamObserver; @@ -21,6 +22,7 @@ public abstract class ProxyRequestHandler extends DataTransferServiceGrpc.DataTr public abstract void setExtraInfo(Context context, InboundPackage inboundPackage, Proxy.Packet req); + @RegisterService(serviceName = "unaryCall") @Override public void unaryCall(Proxy.Packet req, StreamObserver responseObserver) { diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java index 5cadaf2b..76304bd7 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java @@ -1,9 +1,8 @@ package com.webank.ai.fate.serving.proxy.rpc.router; import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.register.router.DefaultRouterService; +import com.webank.ai.fate.register.router.RouterService; import com.webank.ai.fate.register.url.URL; -import com.webank.ai.fate.register.zookeeper.ZookeeperRegistry; import com.webank.ai.fate.serving.proxy.common.Dict; import com.webank.ai.fate.serving.proxy.rpc.core.Context; import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; @@ -22,21 +21,10 @@ @Service public class ZkServingRouter extends BaseServingRouter implements InitializingBean{ - @Value("${zk.url:zookeeper://localhost:2181}") - private String zkUrl ; @Value("${useZkRouter:false}") private String useZkRouter; - @Value("${acl.username:fate}") - private String aclUsername; - - @Value("${acl.password:fate}") - private String aclPassword; - - @Value("${zk.self.port:1111}") - private String port; - @Value("${routeType:random}") private String routeTypeString; @@ -45,9 +33,11 @@ public class ZkServingRouter extends BaseServingRouter implements InitializingB @Value("${coordinator:9999}") private String selfCoordinator; - ZookeeperRegistry zookeeperRegistry; + public static void setZkRouterService(RouterService zkRouterService) { + ZkServingRouter.zkRouterService = zkRouterService; + } - com.webank.ai.fate.register.router.RouterService zkRouterService; + private static RouterService zkRouterService; private static final Logger logger = LogManager.getLogger(); @@ -65,6 +55,9 @@ public List getRouterInfoList(Context context, InboundPackage inboun String environment = getEnvironment(context, inboundPackage); List list =this.zkRouterService.router("serving", environment, context.getServiceName()); logger.info("try to find zk ,{}:{}:{}, result {}", "serving", environment, context.getServiceName(), list); + if(null == list || list.isEmpty()){ + return null; + } List routeList = new ArrayList<>(); for(URL url: list){ String urlip = url.getHost(); @@ -102,24 +95,6 @@ private String getEnvironment(Context context, InboundPackage inboundPackage){ @Override public void afterPropertiesSet() throws Exception { - - if("true".equals(useZkRouter)&&StringUtils.isNotEmpty(zkUrl)) { - - System.setProperty("acl.username", aclUsername); - System.setProperty("acl.password", aclPassword); - - zookeeperRegistry = ZookeeperRegistry.getRegistery(zkUrl, Dict.SELF_PROJECT_NAME, Dict.SELF_ENVIRONMENT, Integer.valueOf(port)); - - zookeeperRegistry.subProject("serving"); - - DefaultRouterService defaultRouterService = new DefaultRouterService(); - - defaultRouterService.setRegistry(zookeeperRegistry); - - zkRouterService = defaultRouterService; - } - routeType = RouteTypeConvertor.string2RouteType(routeTypeString); - } } diff --git a/serving-proxy/src/main/resources/application.properties b/serving-proxy/src/main/resources/application.properties index 249b71ef..3497ae9e 100644 --- a/serving-proxy/src/main/resources/application.properties +++ b/serving-proxy/src/main/resources/application.properties @@ -13,7 +13,6 @@ useZkRouter=false zk.url=zookeeper://localhost:2181 acl.username=fate acl.password=fate -zk.self.port=1111 # intra-partyid port proxy.grpc.intra.port=8867 diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java b/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java index 8a85ffdc..8ce19f92 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java @@ -27,7 +27,17 @@ public class InferenceRequest implements Request { private String appid; private String partyId; + + public void setModelVersion(String modelVersion) { + this.modelVersion = modelVersion; + } + private String modelVersion; + + public void setModelId(String modelId) { + this.modelId = modelId; + } + private String modelId; private String seqno; private String caseid; From f1e3f7d73b632d98603a434f136839544aa2ecfd Mon Sep 17 00:00:00 2001 From: kaideng Date: Tue, 24 Dec 2019 09:44:12 +0800 Subject: [PATCH 049/190] change some funcation Signed-off-by: kaideng --- .../fate/serving/core/bean/BaseContext.java | 34 +++++++++---------- .../fate/register/task/AbstractRetryTask.java | 2 +- .../grpc/client/DataTransferPipedClient.java | 2 +- .../fate/serving/bean/InferenceRequest.java | 21 ++++++------ .../serving/service/InferenceService.java | 4 +-- .../ai/fate/serving/service/ModelService.java | 11 +++--- .../ai/fate/serving/service/ProxyService.java | 4 ++- 7 files changed, 39 insertions(+), 39 deletions(-) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java index 19eb1637..f44da360 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java @@ -16,6 +16,7 @@ package com.webank.ai.fate.serving.core.bean; +import com.codahale.metrics.ConsoleReporter; import com.codahale.metrics.Counter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; @@ -25,10 +26,10 @@ import org.springframework.context.ApplicationContext; import java.util.Map; +import java.util.concurrent.TimeUnit; public class BaseContext implements Context { - private static final Logger LOGGER = LogManager.getLogger(LOGGER_NAME); public static ApplicationContext applicationContext; long timestamp; @@ -42,10 +43,11 @@ public BaseContext(){ } - public BaseContext(LoggerPrinter loggerPrinter,MetricRegistry metricRegistry) { + public BaseContext(LoggerPrinter loggerPrinter,String actionType,MetricRegistry metricRegistry) { this.loggerPrinter = loggerPrinter; this.metricRegistry = metricRegistry; timestamp = System.currentTimeMillis(); + this.actionType = actionType; } private BaseContext(LoggerPrinter loggerPrinter, long timestamp, Map dataMap) { @@ -62,16 +64,19 @@ public String getActionType() { @Override public void setActionType(String actionType) { this.actionType = actionType; - } @Override public void preProcess() { - //WatchDog.enter(this); - Timer timer = metricRegistry.timer(actionType); - Counter counter = metricRegistry.counter(actionType); - counter.inc(); - timerContext = timer.time(); + try { + Timer timer = metricRegistry.timer(actionType+"_timer"); + Counter counter = metricRegistry.counter(actionType+"_couter"); + counter.inc(); + timerContext = timer.time(); + }catch(Exception e){ + LOGGER.error("preProcess error" ,e); + + } } @Override @@ -110,23 +115,19 @@ public long getTimeStamp() { @Override public void postProcess(Req req, Resp resp) { - - try { - if(timerContext!=null){ costTime = timerContext.stop(); }else{ costTime = System.currentTimeMillis() - timestamp; } - if (loggerPrinter != null) { loggerPrinter.printLog(this, req, resp); } } catch (Throwable e) { - + LOGGER.error("postProcess error" ,e); } } @@ -152,9 +153,7 @@ public void hitCache(boolean hitCache) { @Override public Context subContext() { - Map newDataMap = Maps.newHashMap(dataMap); - return new BaseContext(this.loggerPrinter, this.timestamp, dataMap); } @@ -165,8 +164,9 @@ public String getSeqNo() { @Override public long getCostTime() { - return costTime; - } + + + } diff --git a/register/src/main/java/com/webank/ai/fate/register/task/AbstractRetryTask.java b/register/src/main/java/com/webank/ai/fate/register/task/AbstractRetryTask.java index 024024e0..493d4ffa 100644 --- a/register/src/main/java/com/webank/ai/fate/register/task/AbstractRetryTask.java +++ b/register/src/main/java/com/webank/ai/fate/register/task/AbstractRetryTask.java @@ -72,7 +72,7 @@ public abstract class AbstractRetryTask implements TimerTask { this.taskName = taskName; cancel = false; this.retryPeriod = url.getParameter(Constants.REGISTRY_FILESAVE_SYNC_KEY, DEFAULT_REGISTRY_RETRY_PERIOD); - this.retryTimes = url.getParameter(REGISTRY_RETRY_TIMES_KEY, 5000); + this.retryTimes = url.getParameter(REGISTRY_RETRY_TIMES_KEY, Integer.MAX_VALUE); } public void cancel() { diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java index d2eb8e21..1dc19cb4 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java @@ -332,7 +332,7 @@ private DataTransferServiceGrpc.DataTransferServiceStub routerByServiceRegister( // (partnerModelInfo.getName(), partnerModelInfo.getNamespace() String key =genModelKey(partnerModelInfo.getName(), partnerModelInfo.getNamespace()); String md5Key = EncryptUtils.encrypt(key, EncryptMethod.MD5); - + URL paramUrl = URL.valueOf("serving/" + md5Key + "/unaryCall"); if(StringUtils.isNotEmpty(version)) { paramUrl= paramUrl.addParameter(Constants.VERSION_KEY,version diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java b/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java index 8a85ffdc..5f49edbd 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java @@ -16,6 +16,7 @@ package com.webank.ai.fate.serving.bean; +import com.alibaba.fastjson.JSON; import com.fasterxml.jackson.databind.ObjectMapper; import com.webank.ai.fate.serving.core.bean.Request; import com.webank.ai.fate.serving.utils.InferenceUtils; @@ -31,17 +32,20 @@ public class InferenceRequest implements Request { private String modelId; private String seqno; private String caseid; - + private String serviceId; + private Map featureData; public String getServiceId() { return serviceId; } - public void setServiceId(String serviceId) { this.serviceId = serviceId; } - - private String serviceId; - private Map featureData; + public void setModelId(String modelId) { + this.modelId = modelId; + } + public void setModelVersion(String modelVersion) { + this.modelVersion = modelVersion; + } public Map getSendToRemoteFeatureData() { return sendToRemoteFeatureData; @@ -117,16 +121,11 @@ public boolean haveAppId() { public String toString() { String result = ""; try { - - ObjectMapper objectMapper = new ObjectMapper(); - - result = objectMapper.writeValueAsString(this); - + result= JSON.toJSONString(this); } catch (Throwable e) { } return result; - } } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java index be4b2001..701c842c 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java @@ -71,7 +71,7 @@ private void inferenceServiceAction(InferenceMessage req, StreamObserver responseStreamObserver) { - Context context = new BaseContext(new BaseLoggerPrinter(),metricRegistry); - context.setActionType(ModelActionType.MODEL_LOAD.name()); + Context context = new BaseContext(new BaseLoggerPrinter(),ModelActionType.MODEL_LOAD.name(),metricRegistry); + context.preProcess(); ReturnResult returnResult = null; @@ -130,8 +130,7 @@ public synchronized void publishLoad(PublishRequest req, StreamObserver responseStreamObserver) { - Context context = new BaseContext(new BaseLoggerPrinter(),metricRegistry); - context.setActionType(ModelActionType.MODEL_PUBLISH_ONLINE.name()); + Context context = new BaseContext(new BaseLoggerPrinter(),ModelActionType.MODEL_PUBLISH_ONLINE.name(),metricRegistry); context.preProcess(); ReturnResult returnResult = null; try { @@ -163,8 +162,8 @@ public synchronized void publishOnline(PublishRequest req, StreamObserver responseStreamObserver) { - Context context = new BaseContext(new BaseLoggerPrinter(),metricRegistry); - context.setActionType(ModelActionType.MODEL_PUBLISH_ONLINE.name()); + Context context = new BaseContext(new BaseLoggerPrinter(),ModelActionType.MODEL_PUBLISH_ONLINE.name(),metricRegistry); + context.preProcess(); ReturnResult returnResult = null; try { diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ProxyService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ProxyService.java index 4c641300..38cd77de 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ProxyService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ProxyService.java @@ -45,7 +45,9 @@ public class ProxyService extends DataTransferServiceGrpc.DataTransferServiceImp @RegisterService(serviceName = Dict.UNARYCALL, useDynamicEnvironment = true) public void unaryCall(Proxy.Packet req, StreamObserver responseObserver) { ReturnResult responseResult = null; - Context context = new BaseContext(new HostInferenceLoggerPrinter(),metricRegistry); + String actionType = req.getHeader().getCommand().getName(); + + Context context = new BaseContext(new HostInferenceLoggerPrinter(),actionType,metricRegistry); context.setActionType(req.getHeader().getCommand().getName()); context.preProcess(); HostFederatedParams requestData = null; From 9d28663dc57dbd8290c394d2e4b1a9cb110fd1b7 Mon Sep 17 00:00:00 2001 From: utu Date: Tue, 24 Dec 2019 15:21:34 +0800 Subject: [PATCH 050/190] =?UTF-8?q?proxy=20refactor=EF=BC=9Adev-test=20pas?= =?UTF-8?q?s.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- serving-proxy/pom.xml | 2 +- .../ai/fate/serving/proxy/rpc/services/UnaryCallService.java | 2 -- serving-proxy/src/main/resources/log4j2.properties | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index e048110a..e6f4733a 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ - 1.1 + 1.2.1 0.3 8 UTF-8 diff --git a/serving-proxy/pom.xml b/serving-proxy/pom.xml index 632c70b8..556dc185 100644 --- a/serving-proxy/pom.xml +++ b/serving-proxy/pom.xml @@ -215,7 +215,7 @@ com.webank.ai.fate fate-serving-core - 1.1 + ${fate.version} compile diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java index f154dd30..d4888336 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java @@ -105,8 +105,6 @@ protected Proxy.Packet transformErrorMap(Context context,Map data) { fateErrorCodeMap.put(ErrorCode.SERVICE_NOT_FOUND,"502"); fateErrorCodeMap.put(ErrorCode.SYSTEM_ERROR,"503"); fateErrorCodeMap.put(ErrorCode.LIMIT_ERROR,"504"); - fateErrorCodeMap.put(ErrorCode.QUOTA_ERROR,"505"); - fateErrorCodeMap.put(ErrorCode.ORDER_ERROR,"506"); fateErrorCodeMap.put(ErrorCode.NET_ERROR,"507"); fateErrorCodeMap.put(ErrorCode.SHUTDOWN_ERROR,"508"); fateErrorCodeMap.put(ErrorCode.ROUTER_ERROR,"509"); diff --git a/serving-proxy/src/main/resources/log4j2.properties b/serving-proxy/src/main/resources/log4j2.properties index cbf55415..0829a0d3 100644 --- a/serving-proxy/src/main/resources/log4j2.properties +++ b/serving-proxy/src/main/resources/log4j2.properties @@ -17,7 +17,7 @@ name=PropertiesConfig property.auditDir=audit property.logDir=logs property.project=fate -property.module=serving-proxyf +property.module=serving-proxy property.logPattern=[%-5level] %d{yyyy-MM-dd}T%d{HH:mm:ss,SSS} [%t] [%c{1}:%L] - %msg%n # console appender.console.type=Console From 52ea4d5effaa616dd5cf6f3b1a4e3a5485f68f6f Mon Sep 17 00:00:00 2001 From: kaideng Date: Tue, 24 Dec 2019 17:20:21 +0800 Subject: [PATCH 051/190] fix model overwrite bug Signed-off-by: kaideng --- .../grpc/client/DataTransferPipedClient.java | 7 +- .../ai/fate/serving/service/ModelService.java | 114 +++++++++++------- 2 files changed, 73 insertions(+), 48 deletions(-) diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java index 1dc19cb4..bf37abf0 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java @@ -332,14 +332,15 @@ private DataTransferServiceGrpc.DataTransferServiceStub routerByServiceRegister( // (partnerModelInfo.getName(), partnerModelInfo.getNamespace() String key =genModelKey(partnerModelInfo.getName(), partnerModelInfo.getNamespace()); String md5Key = EncryptUtils.encrypt(key, EncryptMethod.MD5); - - URL paramUrl = URL.valueOf("serving/" + md5Key + "/unaryCall"); + String urlString = "serving/" + md5Key + "/unaryCall"; + URL paramUrl = URL.valueOf(urlString); if(StringUtils.isNotEmpty(version)) { paramUrl= paramUrl.addParameter(Constants.VERSION_KEY,version ); } - List urls = routerService.router(paramUrl); + List urls = routerService.router(paramUrl); + LOGGER.info("try to find {} returns {}",urlString,urls); if (CollectionUtils.isNotEmpty(urls)) { URL url = urls.get(0); BasicMeta.Endpoint.Builder builder = BasicMeta.Endpoint.newBuilder(); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java index 96e774a4..165c38b6 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java @@ -18,6 +18,8 @@ import com.codahale.metrics.MetricRegistry; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; import com.webank.ai.eggroll.core.utils.ObjectTransform; @@ -38,16 +40,14 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.util.Arrays; -import java.util.Base64; +import java.util.*; import java.io.*; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.charset.Charset; -import java.util.LinkedHashMap; -import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; @Service public class ModelService extends ModelServiceGrpc.ModelServiceImplBase implements InitializingBean { @@ -60,8 +60,25 @@ public class ModelService extends ModelServiceGrpc.ModelServiceImplBase implemen Base64.Encoder encoder = Base64.getEncoder(); Base64.Decoder decoder = Base64.getDecoder(); - LinkedHashMap publishLoadReqMap = new LinkedHashMap(); - LinkedHashMap publicOnlineReqMap = new LinkedHashMap(); + private static class RequestWapper{ + public RequestWapper(String content,long timestamp,String md5){ + + this.content= content; + this.timestamp = timestamp; + this.md5 = md5; + } + @Override + public String toString(){ + return content+":"+timestamp; + } + String content; + long timestamp; + String md5; + } + + + LinkedHashMap publishLoadReqMap = new LinkedHashMap(); + LinkedHashMap publicOnlineReqMap = new LinkedHashMap(); ExecutorService executorService = Executors.newSingleThreadExecutor(); // ConcurrentMap publishLoadReqMap =new ConcurrentHashMap<>(); @@ -115,8 +132,8 @@ public synchronized void publishLoad(PublishRequest req, StreamObserver responseStreamObserver) { Context context = new BaseContext(new BaseLoggerPrinter(),ModelActionType.MODEL_PUBLISH_ONLINE.name(),metricRegistry); - context.preProcess(); ReturnResult returnResult = null; try { @@ -188,8 +203,8 @@ public synchronized void publishBind(PublishRequest req, StreamObserver sortRequest(Map data){ + + List list = Lists.newArrayList(); + data.forEach((k,v)->{ + list.add(v); + }); + Collections.sort(list, new Comparator() { + @Override + public int compare(RequestWapper o1, RequestWapper o2) { + return o1.timestamp - o2.timestamp>0?1:-1; + } + }); + return list; + } + public void fireStoreEvent() { executorService.submit(() -> { @@ -229,7 +259,7 @@ public void store() { } - public void doSaveProperties(Map properties, File file, long version) { + public void doSaveProperties(Map data, File file, long version) { if (file == null) { return; @@ -253,12 +283,10 @@ public void doSaveProperties(Map properties, File file, long version) { try (FileOutputStream outputFile = new FileOutputStream(file)) { try (BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputFile, Charset.forName("UTF-8")))) { bufferedWriter.newLine(); - properties.forEach((k, v) -> { - + List sortedList = sortRequest(data); + sortedList.forEach(( v) -> { try { - - String content = k + "=" + v; - + String content = v.md5 + "=" + v.toString(); bufferedWriter.write(content); bufferedWriter.newLine(); } catch (IOException e) { @@ -278,47 +306,39 @@ public void doSaveProperties(Map properties, File file, long version) { } } } catch (Throwable e) { -// savePropertiesRetryTimes.incrementAndGet(); -// if (savePropertiesRetryTimes.get() >= MAX_RETRY_TIMES_SAVE_PROPERTIES) { -// logger.warn("Failed to save registry cache file after retrying " + MAX_RETRY_TIMES_SAVE_PROPERTIES + " times, cause: " + e.getMessage(), e); -// savePropertiesRetryTimes.set(0); -// return; -// } -// if (version < lastCacheChanged.get()) { -// savePropertiesRetryTimes.set(0); -// return; -// } else { -// registryCacheExecutor.execute(new AbstractRegistry.SaveProperties(lastCacheChanged.incrementAndGet())); -// } logger.error("Failed to save model cache file, will retry, cause: " + e.getMessage(), e); } } - private void loadProperties(File file, Map properties) { - + private List loadProperties(File file, Map properties) { if (file != null && file.exists()) { InputStream in = null; try { in = new FileInputStream(file); - //properties.load(in); try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in))) { - + final AtomicInteger count= new AtomicInteger(0); bufferedReader.lines().forEach(temp -> { + count.addAndGet(1); int index = temp.indexOf("="); if (index > 0) { String key = temp.substring(0, index); String value = temp.substring(index + 1); - properties.put(key, value); + String[] args =value.split(":"); + String content = args[0]; + long timestamp = count.longValue();; + if(args.length>=2){ + timestamp = new Long(args[1]); + } + properties.put(key, new RequestWapper(content,timestamp,key)); } }); } - - if (logger.isInfoEnabled()) { logger.info("Load model cache file " + file + ", data: " + properties); } + return sortRequest(properties); } catch (Throwable e) { logger.error("failed to load cache file {} ", file); } finally { @@ -330,7 +350,9 @@ private void loadProperties(File file, Map properties) { } } } + } + return null; } @@ -339,11 +361,11 @@ public void afterPropertiesSet() throws Exception { - loadProperties(publishLoadStoreFile, publishLoadReqMap); - loadProperties(publishOnlineStoreFile, publicOnlineReqMap); - publishLoadReqMap.forEach((k, v) -> { + List publishLoadList = loadProperties(publishLoadStoreFile, publishLoadReqMap); + List publishOnlineList = loadProperties(publishOnlineStoreFile, publicOnlineReqMap); + publishLoadList.forEach(( v) -> { try { - byte[] data = decoder.decode(v.getBytes()); + byte[] data = decoder.decode(v.content.getBytes()); PublishRequest req = PublishRequest.parseFrom(data); logger.info("restore publishLoadModel req {}", req); Context context = new BaseContext(); @@ -357,9 +379,9 @@ public void afterPropertiesSet() throws Exception { e.printStackTrace(); } }); - publicOnlineReqMap.forEach((k, v) -> { + publishOnlineList.forEach(( v) -> { try { - byte[] data = decoder.decode(v.getBytes()); + byte[] data = decoder.decode(v.content.getBytes()); PublishRequest req = PublishRequest.parseFrom(data); logger.info("restore publishOnlineModel req {} base64 {}", req,v); @@ -378,4 +400,6 @@ public void afterPropertiesSet() throws Exception { } + + } From 4514f3f515b797ba0927cea4c066c3542f68f2e0 Mon Sep 17 00:00:00 2001 From: FanTao <289765648@qq.com> Date: Tue, 24 Dec 2019 17:36:50 +0800 Subject: [PATCH 052/190] release 1.2.0 --- RELEASE.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/RELEASE.md b/RELEASE.md index b0bd8f6c..4d823261 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,3 +1,9 @@ +# Release 1.2.0 +## Major Features and Improvements +* Decouple FATE-Serving and Eggroll, model is read directly from FATE-Flow +* Serving router supports authentication +* Fixed a bug that got the remote inference result cache + # Release 1.1 ## Major Features and Improvements * Add Online OneHotEncoder transform From 8697581b0507e0c74d10db6f6777319ed6bffcde Mon Sep 17 00:00:00 2001 From: kaideng Date: Wed, 25 Dec 2019 09:21:30 +0800 Subject: [PATCH 053/190] change some detail Signed-off-by: kaideng --- .../ai/fate/serving/core/bean/BaseContext.java | 2 +- proto/model_service.proto | 1 + .../com/webank/ai/fate/networking/Proxy.java | 16 +--------------- .../com/webank/ai/fate/serving/SpringConfig.java | 8 ++++---- .../ai/fate/serving/service/ModelService.java | 9 +-------- 5 files changed, 8 insertions(+), 28 deletions(-) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java index f44da360..b55fd2e5 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java @@ -117,7 +117,7 @@ public long getTimeStamp() { public void postProcess(Req req, Resp resp) { try { if(timerContext!=null){ - costTime = timerContext.stop(); + costTime = timerContext.stop()/1000000; }else{ costTime = System.currentTimeMillis() - timestamp; } diff --git a/proto/model_service.proto b/proto/model_service.proto index 2023acda..215ae69c 100644 --- a/proto/model_service.proto +++ b/proto/model_service.proto @@ -39,4 +39,5 @@ message PublishResponse{ service ModelService{ rpc publishLoad(PublishRequest) returns (PublishResponse); rpc publishBind(PublishRequest) returns (PublishResponse); + rpc publishOnline(PublishRequest) returns (PublishResponse); } diff --git a/router/src/main/java/com/webank/ai/fate/networking/Proxy.java b/router/src/main/java/com/webank/ai/fate/networking/Proxy.java index ee7e6e2f..b70d59df 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/Proxy.java +++ b/router/src/main/java/com/webank/ai/fate/networking/Proxy.java @@ -17,7 +17,6 @@ package com.webank.ai.fate.networking; import com.google.common.collect.Sets; -import com.webank.ai.fate.jmx.server.FateMBeanServer; import com.webank.ai.fate.networking.proxy.factory.GrpcServerFactory; import com.webank.ai.fate.networking.proxy.factory.LocalBeanFactory; import com.webank.ai.fate.networking.proxy.grpc.client.DataTransferPipedClient; @@ -43,6 +42,7 @@ import java.util.Set; public class Proxy { + private static final Logger LOGGER = LogManager.getLogger(); public static ZookeeperRegistry zookeeperRegistry; @@ -104,20 +104,6 @@ public static void main(String[] args) throws Exception { } - - - boolean useJMX = Boolean.valueOf(properties.getProperty("useJMX", "false")); - if (useJMX) { - String jmxServerName = properties.getProperty("jmx.server.name", "proxy"); - int jmxPort = Integer.valueOf(System.getProperty("jmx.port", "9999")); - FateMBeanServer fateMBeanServer = new FateMBeanServer(ManagementFactory.getPlatformMBeanServer(), true); - String jmxServerUrl = fateMBeanServer.openJMXServer(jmxServerName, jmxPort); - URL jmxUrl = URL.parseJMXServiceUrl(jmxServerUrl); - if(useRegister) { - zookeeperRegistry.register(jmxUrl); - } - } - Runtime.getRuntime().addShutdownHook(new Thread(() -> { // Use stderr here since the logger may have been reset by its JVM shutdown hook. System.err.println("*** shutting down gRPC server since JVM is shutting down"); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java b/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java index bb3a1a95..962477a9 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java @@ -57,10 +57,7 @@ public Meter requestMeter(MetricRegistry metrics) { return metrics.meter("request"); } -// @Bean -// public Histogram responseSizes(MetricRegistry metrics) { -// return metrics.histogram("response-sizes"); -// } + @Bean public Counter pendingJobs(MetricRegistry metrics) { @@ -69,6 +66,7 @@ public Counter pendingJobs(MetricRegistry metrics) { @Bean public ConsoleReporter consoleReporter(MetricRegistry metrics) { + return ConsoleReporter.forRegistry(metrics) .convertRatesTo(TimeUnit.SECONDS) .convertDurationsTo(TimeUnit.MILLISECONDS) @@ -87,4 +85,6 @@ RouterService getRouterService() { + + } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java index 165c38b6..47ba7b25 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java @@ -81,8 +81,6 @@ public String toString(){ LinkedHashMap publicOnlineReqMap = new LinkedHashMap(); ExecutorService executorService = Executors.newSingleThreadExecutor(); - // ConcurrentMap publishLoadReqMap =new ConcurrentHashMap<>(); -// ConcurrentMap publicOnlineReqMap =new ConcurrentHashMap(); File publishLoadStoreFile; File publishOnlineStoreFile; @@ -144,7 +142,7 @@ public synchronized void publishLoad(PublishRequest req, StreamObserver responseStreamObserver) { Context context = new BaseContext(new BaseLoggerPrinter(),ModelActionType.MODEL_PUBLISH_ONLINE.name(),metricRegistry); @@ -246,11 +244,6 @@ public void fireStoreEvent() { } - public void restore() { - - } - - public void store() { doSaveProperties(publishLoadReqMap, publishLoadStoreFile, 0); From 03392b385d918b725ed36ff92a50ef9d401eb3a5 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Wed, 25 Dec 2019 14:32:53 +0800 Subject: [PATCH 054/190] add metrics-jmx Signed-off-by: v_dylanxu <136539068@qq.com> --- serving-server/pom.xml | 6 ++++++ .../main/java/com/webank/ai/fate/serving/ServingServer.java | 4 ++++ .../main/java/com/webank/ai/fate/serving/SpringConfig.java | 6 ++++++ 3 files changed, 16 insertions(+) diff --git a/serving-server/pom.xml b/serving-server/pom.xml index 319bf97a..d9304913 100644 --- a/serving-server/pom.xml +++ b/serving-server/pom.xml @@ -208,6 +208,12 @@ metrics-core 4.1.2
+ + io.dropwizard.metrics + metrics-jmx + 4.1.2 + + com.webank.ai.fate fate-register diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index d9b061b2..c4d7fa56 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -17,6 +17,7 @@ package com.webank.ai.fate.serving; import com.codahale.metrics.ConsoleReporter; +import com.codahale.metrics.jmx.JmxReporter; import com.google.common.collect.Sets; import com.webank.ai.fate.jmx.server.FateMBeanServer; @@ -160,6 +161,9 @@ private void start(String[] args) throws IOException { ConsoleReporter reporter = applicationContext.getBean(ConsoleReporter.class); reporter.start(1, TimeUnit.SECONDS); + JmxReporter jmxReporter = applicationContext.getBean(JmxReporter.class); + jmxReporter.start(); + Runtime.getRuntime().addShutdownHook(new Thread() { @Override diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java b/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java index bb3a1a95..95d7206f 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java @@ -2,6 +2,7 @@ import com.codahale.metrics.*; +import com.codahale.metrics.jmx.JmxReporter; import com.webank.ai.fate.register.loadbalance.LoadBalancer; import com.webank.ai.fate.register.loadbalance.RandomLoadBalance; import com.webank.ai.fate.register.router.DefaultRouterService; @@ -75,6 +76,11 @@ public ConsoleReporter consoleReporter(MetricRegistry metrics) { .build(); } + @Bean + public JmxReporter jmxReporter(MetricRegistry metrics) { + return JmxReporter.forRegistry(metrics).build(); + } + @Bean RouterService getRouterService() { DefaultRouterService routerService = new DefaultRouterService(); From 3da7c17b6506b4a8fcfe308f7e6429c5733813f9 Mon Sep 17 00:00:00 2001 From: kaideng Date: Wed, 25 Dec 2019 15:18:59 +0800 Subject: [PATCH 055/190] change some code Signed-off-by: kaideng --- fate-jmx/pom.xml | 2 +- fate-serving-core/pom.xml | 2 +- .../fate/serving/core/bean/Configuration.java | 1 + .../ai/fate/serving/core/bean/Dict.java | 2 -- .../serving/core/bean/DistributedDTable.java | 2 +- federatedml/pom.xml | 14 ++++-------- .../fate/serving/federatedml/DSLParser.java | 4 ++-- .../serving/federatedml/PipelineTask.java | 2 +- .../federatedml/model/FeatureSelection.java | 2 +- .../model/HeteroFeatureBinning.java | 4 ++-- .../serving/federatedml/model/HeteroLR.java | 2 +- .../federatedml/model/HeteroLRGuest.java | 2 +- .../federatedml/model/HeteroLRHost.java | 2 +- .../model/HeteroSecureBoostingTreeGuest.java | 6 ++--- .../model/HeteroSecureBoostingTreeHost.java | 6 ++--- .../federatedml/model/OneHotEncoder.java | 9 +++++--- pom.xml | 2 +- register/pom.xml | 5 ++--- .../register/common/AbstractRegistry.java | 4 ++-- .../com/webank/ai/fate/register/url/URL.java | 16 +++++++------- .../register/zookeeper/ZookeeperRegistry.java | 22 +++++++------------ router/pom.xml | 10 ++++----- serving-server/pom.xml | 10 ++++----- .../webank/ai/fate/serving/ServingServer.java | 14 +++++++----- .../serving/adapter/dataaccess/DTest.java | 4 ++-- .../adapter/processing/PassPreProcessing.java | 2 +- .../guest/DefaultGuestInferenceProvider.java | 2 +- .../host/DefaultHostInferenceProvider.java | 2 +- .../manger/InferenceWorkerManager.java | 1 + .../ai/fate/serving/manger/ModelUtils.java | 4 ++-- .../ai/fate/serving/utils/InferenceUtils.java | 2 +- 31 files changed, 77 insertions(+), 85 deletions(-) diff --git a/fate-jmx/pom.xml b/fate-jmx/pom.xml index d1248b93..0de2e7da 100644 --- a/fate-jmx/pom.xml +++ b/fate-jmx/pom.xml @@ -20,7 +20,7 @@ fate-serving com.webank.ai.fate - ${fate.version} + 1.1.2 4.0.0 diff --git a/fate-serving-core/pom.xml b/fate-serving-core/pom.xml index 6af4bf30..13c8e76a 100644 --- a/fate-serving-core/pom.xml +++ b/fate-serving-core/pom.xml @@ -20,7 +20,7 @@ fate-serving com.webank.ai.fate - ${fate.version} + 1.1.2 4.0.0 diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Configuration.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Configuration.java index 1a90fb77..b59bb9dc 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Configuration.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Configuration.java @@ -90,6 +90,7 @@ private void loadOtherConf(String confRootDir, String confDirName, fileInputStream.read(bytes); JSONObject jsonObject = new JSONObject(new String(bytes,"UTF-8")); jsonMapConfigPool.put(confFile.getName(), jsonObject); + break; } } catch (IOException ex) { LOGGER.error(ex); diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java index 1f487018..7b0f09b0 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java @@ -42,8 +42,6 @@ public class Dict { public static final String MODEL_FEDERATED_PARTY = "model_federated_party"; public static final String MODEL_FEDERATED_ROLES = "model_federated_roles"; public static final String VERSION ="version"; - - // configuration property key public static final String PROPERTY_REMOTE_MODEL_INFERENCE_RESULT_CACHE_TTL = "remoteModelInferenceResultCacheTTL"; public static final String PROPERTY_REMOTE_MODEL_INFERENCE_RESULT_CACHE_MAX_SIZE = "remoteModelInferenceResultCacheMaxSize"; public static final String PROPERTY_INFERENCE_RESULT_CACHE_TTL = "inferenceResultCacheTTL"; diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/DistributedDTable.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/DistributedDTable.java index 2e3c1157..48238f59 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/DistributedDTable.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/DistributedDTable.java @@ -115,7 +115,7 @@ public Map collect() { ManagedChannel channel=null; try { channel = getChannel(address); - Map result = new HashMap<>(); + Map result = new HashMap<>(8); Kv.Range.Builder rangeOrBuilder = Kv.Range.newBuilder(); KVServiceGrpc.KVServiceBlockingStub kvServiceBlockingStub = KVServiceGrpc.newBlockingStub(channel); Iterator item = MetadataUtils.attachHeaders(kvServiceBlockingStub, this.genHeader()).iterate(rangeOrBuilder.build()); diff --git a/federatedml/pom.xml b/federatedml/pom.xml index be6f84aa..5cb92cee 100644 --- a/federatedml/pom.xml +++ b/federatedml/pom.xml @@ -20,23 +20,17 @@ fate-serving com.webank.ai.fate - ${fate.version} + 1.1.2 4.0.0 fate-serving-federatedml - - - - - - - + com.webank.ai.fate fate-serving-core - ${fate.version} + ${project.version} org.json @@ -51,7 +45,7 @@ com.webank.ai.fate fate-register - ${fate.version} + ${project.version} diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/DSLParser.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/DSLParser.java index 8149efe8..4875f2b2 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/DSLParser.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/DSLParser.java @@ -93,7 +93,7 @@ public int parseDagFromDSL(String jsonStr) { public void topoSort(JSONObject components, ArrayList topoRankComponent) { Stack stk = new Stack(); - HashMap componentIndexMapping = new HashMap(); + HashMap componentIndexMapping = new HashMap(8); ArrayList componentList = new ArrayList(); int index = 0; Iterator componentNames = components.keys(); @@ -106,7 +106,7 @@ public void topoSort(JSONObject components, ArrayList topoRankComponent) } int inDegree[] = new int[index]; - HashMap> edges = new HashMap>(); + HashMap> edges = new HashMap>(8); for (int i = 0; i < componentList.size(); ++i) { String componentName = componentList.get(i); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java index 7843512e..4742c6d0 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java @@ -127,7 +127,7 @@ public Map predict(Context context, Map inputDat } private HashMap changeModelProto(Map modelProtoMap) { - HashMap newModelProtoMap = new HashMap(); + HashMap newModelProtoMap = new HashMap(8); for (Map.Entry entry : modelProtoMap.entrySet()) { String key = entry.getKey(); if (!"pipeline.pipeline:Pipeline".equals(key)) { diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/FeatureSelection.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/FeatureSelection.java index f2baaceb..1e345cd6 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/FeatureSelection.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/FeatureSelection.java @@ -60,7 +60,7 @@ public int initModel(byte[] protoMeta, byte[] protoParam) { @Override public Map handlePredict(Context context, List> inputData, FederatedParams predictParams) { LOGGER.info("Start Feature Selection predict"); - HashMap outputData = new HashMap<>(); + HashMap outputData = new HashMap<>(8); Map firstData = inputData.get(0); if (!this.needRun) { diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFeatureBinning.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFeatureBinning.java index 6e9eb65b..0f538cbf 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFeatureBinning.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFeatureBinning.java @@ -30,7 +30,7 @@ public class HeteroFeatureBinning extends BaseModel { public int initModel(byte[] protoMeta, byte[] protoParam) { LOGGER.info("start init Feature Binning class"); this.needRun = false; - this.splitPoints = new HashMap<>(); + this.splitPoints = new HashMap<>(8); try { FeatureBinningMeta featureBinningMeta = this.parseModel(FeatureBinningMeta.parser(), protoMeta); @@ -58,7 +58,7 @@ public int initModel(byte[] protoMeta, byte[] protoParam) { @Override public Map handlePredict(Context context, List> inputData, FederatedParams predictParams) { LOGGER.info("Start Feature Binning predict"); - HashMap outputData = new HashMap<>(); + HashMap outputData = new HashMap<>(8); Map firstData = inputData.get(0); if (!this.needRun) { return firstData; diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLR.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLR.java index 2353ba30..680b13f4 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLR.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLR.java @@ -85,7 +85,7 @@ Map forward(List> inputDatas) { LOGGER.info("model weight hit rate:{}", modelWeightHitRate); LOGGER.info("input data features hit rate:{}", inputDataHitRate); - Map ret = new HashMap<>(); + Map ret = new HashMap<>(8); ret.put(Dict.SCORE, score); ret.put(Dict.MODEL_WRIGHT_HIT_RATE, modelWeightHitRate); ret.put(Dict.INPUT_DATA_HIT_RATE, inputDataHitRate); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java index 15cd96d5..a2bcff43 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java @@ -40,7 +40,7 @@ private double sigmod(double x) { @Override public Map handlePredict(Context context, List> inputData, FederatedParams predictParams) { - Map result = new HashMap<>(); + Map result = new HashMap<>(8); Map forwardRet = forward(inputData); double score = forwardRet.get(Dict.SCORE); LOGGER.info("caseid {} guest score:{}", context.getCaseId(),score); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRHost.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRHost.java index 22ea17b4..e678f8b1 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRHost.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRHost.java @@ -35,7 +35,7 @@ public Map handlePredict(Context context, List result = new HashMap<>(); + HashMap result = new HashMap<>(8); Map ret = forward(inputData); result.put(Dict.SCORE, ret.get(Dict.SCORE)); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeGuest.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeGuest.java index 009bc6ee..e49130ff 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeGuest.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeGuest.java @@ -108,7 +108,7 @@ private int traverseTree(int treeId, int treeNodeId, Map input) private Map getFinalPredict(double[] weights) { - Map ret = new HashMap(); + Map ret = new HashMap(8); if (this.numClasses == 2) { double sum = 0; for (int i = 0; i < this.treeNum; ++i) { @@ -142,7 +142,7 @@ public Map handlePredict(Context context, List input = inputData.get(0); - HashMap fidValueMapping = new HashMap(); + HashMap fidValueMapping = new HashMap(8); ReturnResult returnResult = this.getFederatedPredict(context, predictParams, Dict.FEDERATED_INFERENCE, false); @@ -158,7 +158,7 @@ public Map handlePredict(Context context, List treeLocation = new HashMap(); + HashMap treeLocation = new HashMap(8); for (int i = 0; i < this.treeNum; ++i) { if (this.isLocateInLeaf(i, treeNodeIds[i])) { continue; diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeHost.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeHost.java index 1b876fea..48418c1d 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeHost.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeHost.java @@ -77,9 +77,9 @@ public Map handlePredict(Context context, List input = inputData.get(0); String tag = predictParams.getCaseId() + "." + this.componentName + "." + Dict.INPUT_DATA; - Map ret = new HashMap(); + Map ret = new HashMap(8); - HashMap fidValueMapping = new HashMap(); + HashMap fidValueMapping = new HashMap(8); int featureHit = 0; for (String key : input.keySet()) { if (this.featureNameFidMapping.containsKey(key)) { @@ -95,7 +95,7 @@ public Map handlePredict(Context context, List predictSingleRound(Context context, Map interactiveData, FederatedParams predictParams) { String tag = predictParams.getCaseId() + "." + this.componentName + "." + Dict.INPUT_DATA; Map input = this.getData(context, tag); - Map ret = new HashMap(); + Map ret = new HashMap(8); for (String treeIdx : interactiveData.keySet()) { int idx = Integer.valueOf(treeIdx); int nodeId = this.traverseTree(idx, (Integer) interactiveData.get(treeIdx), input); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/OneHotEncoder.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/OneHotEncoder.java index e97f67d2..aa752dd3 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/OneHotEncoder.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/OneHotEncoder.java @@ -17,12 +17,14 @@ package com.webank.ai.fate.serving.federatedml.model; +import com.google.common.base.Preconditions; import com.webank.ai.fate.core.mlmodel.buffer.OneHotMetaProto.OneHotMeta; import com.webank.ai.fate.core.mlmodel.buffer.OneHotParamProto.OneHotParam; import com.webank.ai.fate.core.mlmodel.buffer.OneHotParamProto.ColsMap; import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.bean.FederatedParams; import com.webank.ai.fate.serving.core.bean.StatusCode; +import org.apache.commons.collections4.CollectionUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -57,8 +59,9 @@ public int initModel(byte[] protoMeta, byte[] protoParam) { @Override public Map handlePredict(Context context, List> inputData, FederatedParams predictParams) { - LOGGER.info("Start OneHot Encoder transform"); - HashMap outputData = new HashMap<>(); + LOGGER.info("start onehot encoder transform"); + HashMap outputData = new HashMap<>(8); + Preconditions.checkArgument(CollectionUtils.isNotEmpty(inputData)); Map firstData = inputData.get(0); if (!this.needRun) { return firstData; @@ -72,7 +75,7 @@ public Map handlePredict(Context context, List com.webank.ai.fate fate-serving - ${fate.version} + 1.1.2 pom 4.0.0 diff --git a/register/pom.xml b/register/pom.xml index 240b40f1..c48497a3 100644 --- a/register/pom.xml +++ b/register/pom.xml @@ -20,12 +20,12 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> fate-register - ${fate.version} + 1.1.2 fate-serving com.webank.ai.fate - ${fate.version} + 1.1.2 jar @@ -34,7 +34,6 @@ 2.10.0.pr1 - 8.0.13 1.3.7 0.6.1 diff --git a/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistry.java b/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistry.java index 2295008f..fafc4a63 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistry.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistry.java @@ -398,7 +398,7 @@ protected void notify(URL url, NotifyListener listener, List urls) { logger.info("Notify urls for subscribe url " + url + ", urls: " + urls); } // keep every provider's category. - Map> result = new HashMap<>(); + Map> result = new HashMap<>(8); for (URL u : urls) { if (UrlUtils.isMatch(url, u)) { String category = u.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY); @@ -409,7 +409,7 @@ protected void notify(URL url, NotifyListener listener, List urls) { if (result.size() == 0) { return; } - Map> categoryNotified = notified.computeIfAbsent(url, u -> new ConcurrentHashMap<>()); + Map> categoryNotified = notified.computeIfAbsent(url, u -> new ConcurrentHashMap<>(8)); for (Map.Entry> entry : result.entrySet()) { String category = entry.getKey(); List categoryList = entry.getValue(); diff --git a/register/src/main/java/com/webank/ai/fate/register/url/URL.java b/register/src/main/java/com/webank/ai/fate/register/url/URL.java index b73673b7..3acfdb1d 100644 --- a/register/src/main/java/com/webank/ai/fate/register/url/URL.java +++ b/register/src/main/java/com/webank/ai/fate/register/url/URL.java @@ -141,7 +141,7 @@ public static URL valueOf(String url, String project, String environment) { int i = url.indexOf("?"); // separator between body and parameters if (i >= 0) { String[] parts = url.substring(i + 1).split("&"); - parameters = new HashMap<>(); + parameters = new HashMap<>(8); for (String part : parts) { part = part.trim(); if (part.length() > 0) { @@ -246,7 +246,7 @@ public static URL valueOf(String url) { int i = url.indexOf("?"); // separator between body and parameters if (i >= 0) { String[] parts = url.substring(i + 1).split("&"); - parameters = new HashMap<>(); + parameters = new HashMap<>(8); for (String part : parts) { part = part.trim(); if (part.length() > 0) { @@ -343,7 +343,7 @@ public static URL valueOf(String url, String... reserveParams) { } public static URL valueOf(URL url, String[] reserveParams, String[] reserveParamPrefixs) { - Map newMap = new HashMap<>(); + Map newMap = new HashMap<>(8); Map oldMap = url.getParameters(); if (reserveParamPrefixs != null && reserveParamPrefixs.length != 0) { for (Map.Entry entry : oldMap.entrySet()) { @@ -453,7 +453,7 @@ public static URL parseJMXServiceUrl(String url) { host = url.substring(0, i); } - return new URL(protocol, project, environment, host, port, path, new HashMap<>()); + return new URL(protocol, project, environment, host, port, path, new HashMap<>(8)); } public String getEnvironment() { @@ -620,12 +620,12 @@ public List getParameter(String key, List defaultValue) { private Map getNumbers() { // concurrent initialization is tolerant - return numbers == null ? new ConcurrentHashMap<>() : numbers; + return numbers == null ? new ConcurrentHashMap<>(8) : numbers; } private Map getUrls() { // concurrent initialization is tolerant - return urls == null ? new ConcurrentHashMap<>() : urls; + return urls == null ? new ConcurrentHashMap<>(8) : urls; } public URL getUrlParameter(String key) { @@ -1133,7 +1133,7 @@ public URL addParameters(String... pairs) { if (pairs.length % 2 != 0) { throw new IllegalArgumentException("Map pairs can not be odd number."); } - Map map = new HashMap<>(); + Map map = new HashMap<>(8); int len = pairs.length / 2; for (int i = 0; i < len; i++) { map.put(pairs[2 * i], pairs[2 * i + 1]); @@ -1170,7 +1170,7 @@ public URL removeParameters(String... keys) { } public URL clearParameters() { - return new URL(protocol, project, environment, host, port, path, new HashMap<>()); + return new URL(protocol, project, environment, host, port, path, new HashMap<>(8)); } public String getRawParameter(String key) { diff --git a/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java b/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java index 87a64100..cd79eaf1 100644 --- a/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java +++ b/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java @@ -307,30 +307,24 @@ public void doSubscribe(final URL url, final NotifyListener listener) { zkListener = listeners.get(listener); } + StringBuilder sb = new StringBuilder(root); + sb.append("/").append(url.getProject()); - String xx = root + "/" + url.getProject(); - List children = zkClient.addChildListener(xx, zkListener); - + List children = zkClient.addChildListener(sb.toString(), zkListener); for (String environment : children) { - - //URL childUrl = url.setEnvironment(environment); - //urls.add(childUrl); - - xx = xx + "/" + environment; - List interfaces = zkClient.addChildListener(xx, zkListener); + sb.append("/").append(environment); + List interfaces = zkClient.addChildListener(sb.toString(), zkListener); if (interfaces != null) { for (String inter : interfaces) { - xx = xx + "/" + inter + "/" + Constants.PROVIDERS_CATEGORY; - List services = zkClient.addChildListener(xx, zkListener); + sb.append("/").append(inter).append("/").append(Constants.PROVIDERS_CATEGORY); + List services = zkClient.addChildListener(sb.toString(), zkListener); if (services != null) { - urls.addAll(toUrlsWithEmpty(url, xx, services)); + urls.addAll(toUrlsWithEmpty(url, sb.toString(), services)); } - - } } diff --git a/router/pom.xml b/router/pom.xml index 807cbf26..3075cb86 100644 --- a/router/pom.xml +++ b/router/pom.xml @@ -20,13 +20,13 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> fate-serving-router - ${fate.version} + fate-serving com.webank.ai.fate - ${fate.version} + 1.1.2 jar @@ -40,7 +40,7 @@ com.webank.ai.fate fate-register - ${fate.version} + 1.1.2 @@ -60,12 +60,12 @@ com.webank.ai.fate fate-jmx - ${fate.version} + ${project.version} com.webank.ai.fate fate-serving-core - 1.1 + ${project.version}
diff --git a/serving-server/pom.xml b/serving-server/pom.xml index 319bf97a..b761c04e 100644 --- a/serving-server/pom.xml +++ b/serving-server/pom.xml @@ -20,7 +20,7 @@ fate-serving com.webank.ai.fate - ${fate.version} + 1.1.2 4.0.0 @@ -88,12 +88,12 @@ com.webank.ai.fate fate-serving-federatedml - ${fate.version} + ${project.version} com.webank.ai.fate fate-serving-core - ${fate.version} + ${project.version} io.grpc @@ -211,12 +211,12 @@ com.webank.ai.fate fate-register - ${fate.version} + ${project.version} com.webank.ai.fate fate-jmx - ${fate.version} + ${project.version}
diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index d9b061b2..d43cc14f 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -48,9 +48,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Set; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; public class ServingServer implements InitializingBean { private static final Logger LOGGER = LogManager.getLogger(); @@ -104,7 +102,13 @@ private void start(String[] args) throws IOException { ApplicationHolder.applicationContext = applicationContext; int port = Integer.parseInt(Configuration.getProperty(Dict.PROPERTY_SERVER_PORT)); //TODO: Server custom configuration - Executor executor = Executors.newCachedThreadPool(); + + Integer corePoolSize = Configuration.getPropertyInt("serving.core.pool.size",10); + Integer maxPoolSize = Configuration.getPropertyInt("serving.max.pool.size",100); + Integer aliveTime = Configuration.getPropertyInt("serving.pool.alive.time",1000); + Integer queueSize = Configuration.getPropertyInt("serving.pool.queue.size",10); + Executor executor = new ThreadPoolExecutor(corePoolSize,maxPoolSize,aliveTime.longValue(),TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(queueSize)); + FateServerBuilder serverBuilder = (FateServerBuilder) ServerBuilder.forPort(port); serverBuilder.executor(executor); //new ServiceOverloadProtectionHandle() @@ -117,9 +121,7 @@ private void start(String[] args) throws IOException { String userRegisterString = Configuration.getProperty(Dict.USE_REGISTER); useRegister = Boolean.valueOf(userRegisterString); LOGGER.info("serving useRegister {}", useRegister); - if (useRegister) { - ZookeeperRegistry zookeeperRegistry = applicationContext.getBean(ZookeeperRegistry.class); zookeeperRegistry.subProject(Dict.PROPERTY_PROXY_ADDRESS); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/DTest.java b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/DTest.java index 8ba636f8..0414807a 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/DTest.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/DTest.java @@ -38,7 +38,7 @@ public class DTest implements FeatureData { @Override public ReturnResult getData(Context context, Map featureIds) { ReturnResult returnResult = new ReturnResult(); - Map requestData = new HashMap<>(); + Map requestData = new HashMap<>(8); requestData.putAll(featureIds); String responseBody = HttpClientPool.post("http://127.0.0.1:1234/feature", requestData); if (StringUtils.isEmpty(responseBody)) { @@ -49,7 +49,7 @@ public ReturnResult getData(Context context, Map featureIds) { return null; } String[] features = StringUtils.split(((List) tmp.get(Dict.DATA)).get(0), "\t"); - Map featureData = new HashMap<>(); + Map featureData = new HashMap<>(8); for (int i = 1; i < features.length; i++) { featureData.put(features[i], i); } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/PassPreProcessing.java b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/PassPreProcessing.java index aa9f93cd..6fb7eea8 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/PassPreProcessing.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/PassPreProcessing.java @@ -31,7 +31,7 @@ public class PassPreProcessing implements PreProcessing { public PreProcessingResult getResult(Context context, String paras) { PreProcessingResult preProcessingResult = new PreProcessingResult(); preProcessingResult.setProcessingResult((Map) ObjectTransform.json2Bean(paras, HashMap.class)); - Map featureIds = new HashMap<>(); + Map featureIds = new HashMap<>(8); Arrays.asList(Dict.DEVICE_ID, Dict.PHONE_NUM).forEach((field -> { featureIds.put(field, Optional.ofNullable(preProcessingResult.getProcessingResult().get(field)).orElse("")); })); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java index b627d495..7629a890 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java @@ -127,7 +127,7 @@ public ReturnResult runInference(Context context, InferenceRequest inferenceRequ logInference(context, inferenceRequest, modelNamespaceData, inferenceResult, 0, false, false); return inferenceResult; } - Map predictParams = new HashMap<>(); + Map predictParams = new HashMap<>(8); Map modelFeatureData = Maps.newHashMap(featureData); FederatedParams federatedParams = new FederatedParams(); if(inferenceRequest.getSendToRemoteFeatureData()!=null&&federatedParams.getFeatureIdMap()!=null) { diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/host/DefaultHostInferenceProvider.java b/serving-server/src/main/java/com/webank/ai/fate/serving/host/DefaultHostInferenceProvider.java index e417116b..e1cebad5 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/host/DefaultHostInferenceProvider.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/host/DefaultHostInferenceProvider.java @@ -85,7 +85,7 @@ public ReturnResult federatedInference(Context context, HostFederatedParams fede return returnResult; } LOGGER.info("use model to inference on {} {}, id: {}, version: {}", party.getRole(), party.getPartyId(), modelInfo.getNamespace(), modelInfo.getName()); - Map predictParams = new HashMap<>(); + Map predictParams = new HashMap<>(8); predictParams.put(Dict.FEDERATED_PARAMS, federatedParams); try { diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/InferenceWorkerManager.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/InferenceWorkerManager.java index 0adc5f6f..5501a1e9 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/InferenceWorkerManager.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/InferenceWorkerManager.java @@ -62,6 +62,7 @@ public Thread newThread(Runnable r) { } public static class InferenceWorkerThreadRejectedPolicy implements RejectedExecutionHandler { + @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { LOGGER.info("{} rejected.", r.toString()); } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java index eb524d08..1af4f700 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java @@ -70,9 +70,9 @@ public static FederatedRoles getFederatedRoles(Map> getFederatedRolesModel(Map federatedRolesModelProto) { - Map> federatedRolesModel = new HashMap<>(); + Map> federatedRolesModel = new HashMap<>(8); federatedRolesModelProto.forEach((roleName, roleModelInfo) -> { - federatedRolesModel.put(roleName, new HashMap<>()); + federatedRolesModel.put(roleName, new HashMap<>(8)); roleModelInfo.getRoleModelInfoMap().forEach((partyId, modelInfo) -> { federatedRolesModel.get(roleName).put(partyId, new ModelInfo(modelInfo.getTableName(), modelInfo.getNamespace())); }); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/utils/InferenceUtils.java b/serving-server/src/main/java/com/webank/ai/fate/serving/utils/InferenceUtils.java index 3cdbe1cf..5a1c54ac 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/utils/InferenceUtils.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/utils/InferenceUtils.java @@ -45,7 +45,7 @@ public static String generateSeqno() { public static void logInference(Context context, Enum inferenceType, FederatedParty federatedParty, FederatedRoles federatedRoles, String caseid, String seqno, int retcode, long elapsed, boolean getRemotePartyResult, boolean billing, Map inferenceRequest, ReturnResult inferenceResult) { inferenceAuditLogger.info("{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}", GetSystemInfo.localIp, inferenceType, federatedParty.getRole(), federatedParty.getPartyId(), FederatedUtils.federatedRolesIdentificationString(federatedRoles), caseid, seqno, retcode, elapsed, getRemotePartyResult ? 1 : 0, billing ? 1 : 0, context.isHitCache()); - Map inferenceLog = new HashMap<>(); + Map inferenceLog = new HashMap<>(8); inferenceLog.put(Dict.INFERENCE_REQUEST, inferenceRequest); inferenceLog.put(Dict.INFERENCE_RESULT, ObjectTransform.bean2Json(inferenceResult)); String inferenceLogBase64String = Base64.getEncoder().encodeToString(ObjectTransform.bean2Json(inferenceLog).getBytes()); From e204d5e4f310e784dcb3fb06e2415400f8bea8ae Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Wed, 25 Dec 2019 16:38:39 +0800 Subject: [PATCH 056/190] remove fate-jmx module Signed-off-by: v_dylanxu <136539068@qq.com> --- fate-jmx/pom.xml | 30 ---- .../ai/fate/jmx/client/FateJmxClient.java | 53 ------- .../com/webank/ai/fate/jmx/mbean/Sample.java | 24 --- .../webank/ai/fate/jmx/mbean/SampleMBean.java | 22 --- .../ai/fate/jmx/server/FateMBeanServer.java | 90 ----------- .../fate/jmx/server/TestFateMBeanServer.java | 35 ----- .../ai/fate/jmx/util/GetSystemInfo.java | 144 ------------------ fate-jmx/src/main/resources/log4j2.properties | 105 ------------- .../ai/fate/serving/core/bean/Dict.java | 4 - pom.xml | 1 - router/pom.xml | 5 - router/src/main/resources/proxy.properties | 3 +- serving-server/pom.xml | 5 - .../webank/ai/fate/serving/ServingServer.java | 20 +-- .../main/resources/serving-server.properties | 3 +- 15 files changed, 6 insertions(+), 538 deletions(-) delete mode 100644 fate-jmx/pom.xml delete mode 100644 fate-jmx/src/main/java/com/webank/ai/fate/jmx/client/FateJmxClient.java delete mode 100644 fate-jmx/src/main/java/com/webank/ai/fate/jmx/mbean/Sample.java delete mode 100644 fate-jmx/src/main/java/com/webank/ai/fate/jmx/mbean/SampleMBean.java delete mode 100644 fate-jmx/src/main/java/com/webank/ai/fate/jmx/server/FateMBeanServer.java delete mode 100644 fate-jmx/src/main/java/com/webank/ai/fate/jmx/server/TestFateMBeanServer.java delete mode 100644 fate-jmx/src/main/java/com/webank/ai/fate/jmx/util/GetSystemInfo.java delete mode 100644 fate-jmx/src/main/resources/log4j2.properties diff --git a/fate-jmx/pom.xml b/fate-jmx/pom.xml deleted file mode 100644 index 0de2e7da..00000000 --- a/fate-jmx/pom.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - fate-serving - com.webank.ai.fate - 1.1.2 - - 4.0.0 - - fate-jmx - jar - - \ No newline at end of file diff --git a/fate-jmx/src/main/java/com/webank/ai/fate/jmx/client/FateJmxClient.java b/fate-jmx/src/main/java/com/webank/ai/fate/jmx/client/FateJmxClient.java deleted file mode 100644 index c4800952..00000000 --- a/fate-jmx/src/main/java/com/webank/ai/fate/jmx/client/FateJmxClient.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.jmx.client; - -import com.webank.ai.fate.jmx.mbean.SampleMBean; -import com.webank.ai.fate.jmx.util.GetSystemInfo; - -import javax.management.JMX; -import javax.management.MBeanServerConnection; -import javax.management.MalformedObjectNameException; -import javax.management.ObjectName; -import javax.management.remote.JMXConnector; -import javax.management.remote.JMXConnectorFactory; -import javax.management.remote.JMXServiceURL; -import java.io.IOException; -import java.util.Set; - -public class FateJmxClient { - public static void main(String[] args) throws IOException, MalformedObjectNameException { - JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + GetSystemInfo.getLocalIp() + ":9999/fate"); - JMXConnector connector = JMXConnectorFactory.connect(url, null); - - MBeanServerConnection mbsc = connector.getMBeanServerConnection(); - - for (String domain : mbsc.getDomains()) { - System.out.println("domain = " + domain); - } - - System.out.println("MBeanServer default domain = " + mbsc.getDefaultDomain()); - - Set objectNames = mbsc.queryNames(null, null); - for (ObjectName objectName : objectNames) { - System.out.println("objectName = " + objectName); - } - - SampleMBean proxy = JMX.newMBeanProxy(mbsc, new ObjectName("com.webank.ai.fate.jmx:name=sample,type=standard"), SampleMBean.class, true); - proxy.call(); - } -} diff --git a/fate-jmx/src/main/java/com/webank/ai/fate/jmx/mbean/Sample.java b/fate-jmx/src/main/java/com/webank/ai/fate/jmx/mbean/Sample.java deleted file mode 100644 index 237eaf73..00000000 --- a/fate-jmx/src/main/java/com/webank/ai/fate/jmx/mbean/Sample.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.jmx.mbean; - -public class Sample implements SampleMBean { - @Override - public void call() { - System.out.println("do SamlpeMBean call()"); - } -} diff --git a/fate-jmx/src/main/java/com/webank/ai/fate/jmx/mbean/SampleMBean.java b/fate-jmx/src/main/java/com/webank/ai/fate/jmx/mbean/SampleMBean.java deleted file mode 100644 index c23c4c97..00000000 --- a/fate-jmx/src/main/java/com/webank/ai/fate/jmx/mbean/SampleMBean.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.jmx.mbean; - -public interface SampleMBean { - - void call(); -} diff --git a/fate-jmx/src/main/java/com/webank/ai/fate/jmx/server/FateMBeanServer.java b/fate-jmx/src/main/java/com/webank/ai/fate/jmx/server/FateMBeanServer.java deleted file mode 100644 index 2e31c2fc..00000000 --- a/fate-jmx/src/main/java/com/webank/ai/fate/jmx/server/FateMBeanServer.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.jmx.server; - -import com.webank.ai.fate.jmx.mbean.Sample; -import com.webank.ai.fate.jmx.util.GetSystemInfo; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import javax.management.*; -import javax.management.remote.JMXConnectorServer; -import javax.management.remote.JMXConnectorServerFactory; -import javax.management.remote.JMXServiceURL; -import java.io.IOException; -import java.rmi.registry.LocateRegistry; - -public class FateMBeanServer { - - private static final Logger LOGGER = LogManager.getLogger(FateMBeanServer.class); - - private MBeanServer mBeanServer; - - private JMXConnectorServer jmxConnectorServer; - - /** - * FateMBeanServer Constructor - * - * @param mBeanServer - * @param initMBeans auto register define mbeans - */ - public FateMBeanServer(MBeanServer mBeanServer, boolean initMBeans) { - this.mBeanServer = mBeanServer; - try { - if (initMBeans) { - init(); - } - } catch (Exception e) { - LOGGER.error("Fate MBean server init fail"); - e.printStackTrace(); - } - } - - private void init() throws MalformedObjectNameException, MBeanRegistrationException, InstanceAlreadyExistsException, NotCompliantMBeanException { - LOGGER.info("Init register all MBeans"); - - // register any mbeans - registerMBean(new Sample(), new ObjectName("com.webank.ai.fate.jmx:name=sample")); - // TODO: add mbeans - // ... - } - - public void registerMBean(Object object, ObjectName objectName) - throws NotCompliantMBeanException, InstanceAlreadyExistsException, MBeanRegistrationException { - this.mBeanServer.registerMBean(object, objectName); - - LOGGER.info("Server registerMBean {}", objectName); - } - - public String openJMXServer(String serverName, int port) throws IOException { - LocateRegistry.createRegistry(port); - String url = "service:jmx:rmi:///jndi/rmi://" + GetSystemInfo.getLocalIp() + ":" + port + "/" + serverName; - JMXServiceURL jmxServiceURL = new JMXServiceURL(url); - jmxConnectorServer = JMXConnectorServerFactory.newJMXConnectorServer(jmxServiceURL, null, mBeanServer); - jmxConnectorServer.start(); - LOGGER.info("JMX Server started listening on port: {}, url: {}", port, url); - return url; - } - - public void stopJMXServer() throws IOException { - - if (jmxConnectorServer != null) { - jmxConnectorServer.stop(); - } - } - -} diff --git a/fate-jmx/src/main/java/com/webank/ai/fate/jmx/server/TestFateMBeanServer.java b/fate-jmx/src/main/java/com/webank/ai/fate/jmx/server/TestFateMBeanServer.java deleted file mode 100644 index 7397be0c..00000000 --- a/fate-jmx/src/main/java/com/webank/ai/fate/jmx/server/TestFateMBeanServer.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.jmx.server; - -import com.webank.ai.fate.jmx.mbean.Sample; - -import javax.management.ObjectName; -import java.lang.management.ManagementFactory; - -public class TestFateMBeanServer { - - public static void main(String[] args) { - try { - FateMBeanServer fateMBeanServer = new FateMBeanServer(ManagementFactory.getPlatformMBeanServer(), false); - fateMBeanServer.registerMBean(new Sample(), new ObjectName("com.webank.ai.fate.jmx:name=sample,type=standard")); - fateMBeanServer.openJMXServer("fate", 9999); - } catch (Exception e) { - e.printStackTrace(); - } - } -} diff --git a/fate-jmx/src/main/java/com/webank/ai/fate/jmx/util/GetSystemInfo.java b/fate-jmx/src/main/java/com/webank/ai/fate/jmx/util/GetSystemInfo.java deleted file mode 100644 index aa976612..00000000 --- a/fate-jmx/src/main/java/com/webank/ai/fate/jmx/util/GetSystemInfo.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.jmx.util; - - -import com.sun.management.OperatingSystemMXBean; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.lang.management.ManagementFactory; -import java.net.Inet4Address; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.util.Enumeration; - -public class GetSystemInfo { - - private static final Logger LOGGER = LogManager.getLogger(); - - - public static String localIp; - - static { - localIp = getLocalIp(); - } - - public static String getLocalIp() { - - String sysType = System.getProperties().getProperty("os.name"); - String ip; - - try { - if (sysType.toLowerCase().startsWith("win")) { - String localIP = null; - - localIP = InetAddress.getLocalHost().getHostAddress(); - - if (localIP != null) { - return localIP; - } - } else { - ip = getIpByEthNum("eth0"); - if (ip != null) { - return ip; - - - } - } - } catch (Throwable e) { - LOGGER.error(e.getMessage(), e); - } - return ""; - } - - private static String getIpByEthNum(String ethNum) { - try { - Enumeration allNetInterfaces = NetworkInterface.getNetworkInterfaces(); - InetAddress ip; - while (allNetInterfaces.hasMoreElements()) { - NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement(); - if (ethNum.equals(netInterface.getName())) { - Enumeration addresses = netInterface.getInetAddresses(); - while (addresses.hasMoreElements()) { - ip = (InetAddress) addresses.nextElement(); - if (ip != null && ip instanceof Inet4Address) { - return ip.getHostAddress(); - } - } - } - } - } catch (SocketException e) { - LOGGER.error(e.getMessage(), e); - } - return ""; - } - - - public static String getOsName() { - - String osName = System.getProperty("os.name"); - return osName; - } - - - public static double getSystemCpuLoad() { - OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory - .getOperatingSystemMXBean(); - double SystemCpuLoad = osmxb.getSystemCpuLoad(); - return SystemCpuLoad; - } - - - public static double getProcessCpuLoad() { - OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory - .getOperatingSystemMXBean(); - double ProcessCpuLoad = osmxb.getProcessCpuLoad(); - return ProcessCpuLoad; - } - - - public static long getTotalMemorySize() { - int kb = 1024; - OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory - .getOperatingSystemMXBean(); - long totalMemorySize = osmxb.getTotalPhysicalMemorySize() / kb; - return totalMemorySize; - } - - - public static long getFreePhysicalMemorySize() { - int kb = 1024; - OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory - .getOperatingSystemMXBean(); - long freePhysicalMemorySize = osmxb.getFreePhysicalMemorySize() / kb; - return freePhysicalMemorySize; - } - - - public static long getUsedMemory() { - int kb = 1024; - OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory - .getOperatingSystemMXBean(); - long usedMemory = (osmxb.getTotalPhysicalMemorySize() - osmxb.getFreePhysicalMemorySize()) / kb; - return usedMemory; - } - - -} - diff --git a/fate-jmx/src/main/resources/log4j2.properties b/fate-jmx/src/main/resources/log4j2.properties deleted file mode 100644 index 960a1003..00000000 --- a/fate-jmx/src/main/resources/log4j2.properties +++ /dev/null @@ -1,105 +0,0 @@ -# -# Copyright 2019 The FATE Authors. All Rights Reserved. -# -# 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. -# -name=PropertiesConfig -property.auditDir=audit -property.logDir=logs -property.project=fate -property.module=proxy -property.logPattern=[%-5level] %d{yyyy-MM-dd}T%d{HH:mm:ss,SSS} [%t] [%c{1}:%L] - %msg%n -# console -appender.console.type=Console -appender.console.name=STDOUT -appender.console.layout.type=PatternLayout -appender.console.layout.pattern=${logPattern} -# default file -appender.file.type=RollingFile -appender.file.name=LOGFILE -appender.file.fileName=${logDir}/${project}-${module}.log -appender.file.filePattern=${logDir}/%d{yyyy}/%d{MM}/%d{dd}/${project}-${module}.log.%d{yyyy-MM-dd-HH} -appender.file.layout.type=PatternLayout -appender.file.layout.pattern=${logPattern} -appender.file.policies.type=Policies -appender.file.policies.time.type=TimeBasedTriggeringPolicy -appender.file.policies.time.interval=1 -appender.file.policies.time.modulate=true -appender.file.strategy.type=DefaultRolloverStrategy -# debug -appender.debugging.type=RollingFile -appender.debugging.name=LOGDEBUGGING -appender.debugging.fileName=${logDir}/${project}-${module}-debug.log -appender.debugging.filePattern=${logDir}/%d{yyyy}/%d{MM}/%d{dd}/${project}-${module}-debug.log.%d{yyyy-MM-dd-HH-mm} -appender.debugging.layout.type=PatternLayout -appender.debugging.layout.pattern=${logPattern} -appender.debugging.policies.type=Policies -appender.debugging.policies.time.type=TimeBasedTriggeringPolicy -appender.debugging.policies.time.interval=1 -appender.debugging.policies.time.modulate=true -appender.debugging.strategy.type=DefaultRolloverStrategy -# audit -appender.audit.type=RollingFile -appender.audit.name=LOGAUDIT -appender.audit.fileName=${auditDir}/${project}-${module}-audit.log -appender.audit.filePattern=${auditDir}/%d{yyyy}/%d{MM}/%d{dd}/${project}-${module}-audit.log.%d{yyyy-MM-dd-HH} -appender.audit.layout.type=PatternLayout -appender.audit.layout.pattern=[%d{yyyy-MM-dd}T%d{HH:mm:ss,SSS}]%msg%n -appender.audit.policies.type=Policies -appender.audit.policies.time.type=TimeBasedTriggeringPolicy -appender.audit.policies.time.interval=1 -appender.audit.policies.time.modulate=true -appender.audit.strategy.type=DefaultRolloverStrategy -# stat -appender.stat.type=RollingFile -appender.stat.name=LOGSTAT -appender.stat.fileName=${logDir}/${project}-${module}-stat.log -appender.stat.filePattern=${logDir}/%d{yyyy}/%d{MM}/%d{dd}/${project}-${module}-stat.log.%d{yyyy-MM-dd-HH} -appender.stat.layout.type=PatternLayout -appender.stat.layout.pattern=${logPattern} -appender.stat.policies.type=Policies -appender.stat.policies.time.type=TimeBasedTriggeringPolicy -appender.stat.policies.time.interval=1 -appender.stat.policies.time.modulate=true -appender.stat.strategy.type=DefaultRolloverStrategy -# loggers -loggers=file, debugging, audit, stat -# logger - file -logger.file.name=file -logger.file.level=info -logger.file.appenderRefs=file -logger.file.appenderRef.file.ref=LOGFILE -logger.file.additivity=false -# logger - debugging -logger.debugging.name=debugging -logger.debugging.level=info -logger.debugging.appenderRefs=debugging -logger.debugging.appenderRef.debugging.ref=LOGDEBUGGING -logger.debugging.additivity=false -# logger - audit -logger.audit.name=audit -logger.audit.level=info -logger.audit.appenderRefs=audit -logger.audit.appenderRef.file.ref=LOGAUDIT -logger.audit.additivity=false -# logger - stat -logger.stat.name=stat -logger.stat.level=info -logger.stat.appenderRefs=stat -logger.stat.appenderRef.file.ref=LOGSTAT -logger.stat.additivity=false -# logger - root -rootLogger.level=info -rootLogger.appenderRefs=stdout, file -rootLogger.appenderRef.stdout.ref=STDOUT -rootLogger.appenderRef.file.ref=LOGFILE diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java index 7b0f09b0..3ec076d6 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java @@ -124,10 +124,6 @@ public class Dict { public static final String USE_ZK_ROUTER = "useZkRouter"; public static final String FALSE = "false"; public static final String USE_REGISTER = "useRegister"; - public static final String USE_JMX = "useJMX"; - public static final String JMX_SERVER_NAME = "jmx.server.name"; - public static final String JMX_PORT = "jmx.port"; - public static final String GUEST_APP_ID = "guestAppId"; public static final String HOST_APP_ID = "hostAppId"; diff --git a/pom.xml b/pom.xml index 0927b783..22f8b46e 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,6 @@ fate-serving-core register router - fate-jmx diff --git a/router/pom.xml b/router/pom.xml index 3075cb86..457e3c45 100644 --- a/router/pom.xml +++ b/router/pom.xml @@ -57,11 +57,6 @@ - - com.webank.ai.fate - fate-jmx - ${project.version} - com.webank.ai.fate fate-serving-core diff --git a/router/src/main/resources/proxy.properties b/router/src/main/resources/proxy.properties index c077899b..ed2429a6 100644 --- a/router/src/main/resources/proxy.properties +++ b/router/src/main/resources/proxy.properties @@ -23,8 +23,7 @@ route.table=/networking/proxy/src/main/resources/route_tables/route_table.json #server.crt=/Users/max-webank/Projects/fdn/fdn-proxy/src/main/resources/certs/server.crt #server.key=/Users/max-webank/Projects/fdn/fdn-proxy/src/main/resources/certs/server-private.pem root.crt= -useJMX=false -#jmx.server.name=JMXServer + authFile=/data/projects/fate/serving-router/conf/auth_config.json acl.username=fate diff --git a/serving-server/pom.xml b/serving-server/pom.xml index 0cb7aed0..4130c9c7 100644 --- a/serving-server/pom.xml +++ b/serving-server/pom.xml @@ -219,11 +219,6 @@ fate-register ${project.version} - - com.webank.ai.fate - fate-jmx - ${project.version} - diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index 01196f61..689fc1fc 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -19,8 +19,6 @@ import com.codahale.metrics.ConsoleReporter; import com.codahale.metrics.jmx.JmxReporter; import com.google.common.collect.Sets; - -import com.webank.ai.fate.jmx.server.FateMBeanServer; import com.webank.ai.fate.register.provider.FateServer; import com.webank.ai.fate.register.provider.FateServerBuilder; import com.webank.ai.fate.register.router.RouterService; @@ -45,11 +43,13 @@ import java.io.File; import java.io.IOException; -import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.HashMap; import java.util.Set; -import java.util.concurrent.*; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; public class ServingServer implements InitializingBean { private static final Logger LOGGER = LogManager.getLogger(); @@ -147,18 +147,6 @@ private void start(String[] args) throws IOException { } - boolean useJMX = Boolean.valueOf(Configuration.getProperty(Dict.USE_JMX)); - if (useJMX) { - String jmxServerName = Configuration.getProperty(Dict.JMX_SERVER_NAME, "serving"); - int jmxPort = Integer.valueOf(Configuration.getProperty(Dict.JMX_PORT, "9999")); - FateMBeanServer fateMBeanServer = new FateMBeanServer(ManagementFactory.getPlatformMBeanServer(), true); - String jmxServerUrl = fateMBeanServer.openJMXServer(jmxServerName, jmxPort); - URL jmxUrl = URL.parseJMXServiceUrl(jmxServerUrl); - if(useRegister) { - ZookeeperRegistry zookeeperRegistry = applicationContext.getBean(ZookeeperRegistry.class); - zookeeperRegistry.register(jmxUrl); - } - } ConsoleReporter reporter = applicationContext.getBean(ConsoleReporter.class); reporter.start(1, TimeUnit.SECONDS); diff --git a/serving-server/src/main/resources/serving-server.properties b/serving-server/src/main/resources/serving-server.properties index 481db333..f5e614bf 100644 --- a/serving-server/src/main/resources/serving-server.properties +++ b/serving-server/src/main/resources/serving-server.properties @@ -57,7 +57,6 @@ roll=127.0.0.1:8011 zk.url=zookeeper://localhost:2181 useRegister=true useZkRouter=true -useJMX=false -#jmx.server.name=JMXServer + acl.username=fate acl.password=fate \ No newline at end of file From 0e8cffedfaf7a4eebdff45e8a0104b9d60c40b2e Mon Sep 17 00:00:00 2001 From: qianduoduo <1002502212@qq.com> Date: Wed, 25 Dec 2019 18:30:24 +0800 Subject: [PATCH 057/190] =?UTF-8?q?serving=20=E9=83=A8=E7=BD=B2=E8=84=9A?= =?UTF-8?q?=E6=9C=AC=E6=92=A4=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../allinone_cluster_configurations.sh | 7 - .../cluster-deploy/scripts/jdk_install.sh | 18 -- .../cluster-deploy/scripts/package.sh | 165 --------------- .../cluster-deploy/scripts/redis/service.sh | 87 -------- .../cluster-deploy/scripts/redis_install.sh | 188 ------------------ .../cluster-deploy/scripts/services.sh | 78 -------- .../scripts/start_env/services.sh | 78 -------- .../cluster-deploy/scripts/zk/service.sh | 87 -------- .../cluster-deploy/scripts/zkui/service.sh | 88 -------- .../cluster-deploy/scripts/zkui_install.sh | 14 -- .../scripts/zookeeper_install.sh | 41 ---- 11 files changed, 851 deletions(-) delete mode 100644 serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh delete mode 100644 serving-server/cluster-deploy/scripts/jdk_install.sh delete mode 100644 serving-server/cluster-deploy/scripts/package.sh delete mode 100644 serving-server/cluster-deploy/scripts/redis/service.sh delete mode 100644 serving-server/cluster-deploy/scripts/redis_install.sh delete mode 100644 serving-server/cluster-deploy/scripts/services.sh delete mode 100644 serving-server/cluster-deploy/scripts/start_env/services.sh delete mode 100644 serving-server/cluster-deploy/scripts/zk/service.sh delete mode 100644 serving-server/cluster-deploy/scripts/zkui/service.sh delete mode 100644 serving-server/cluster-deploy/scripts/zkui_install.sh delete mode 100644 serving-server/cluster-deploy/scripts/zookeeper_install.sh diff --git a/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh b/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh deleted file mode 100644 index 25d128f5..00000000 --- a/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -user=app -host_guest=(192.168.0.1 192.168.0.2) -roll_hostAndguest=(192.168.0.1 192.168.0.2) -deploy_dir=/data/projects -redis_password=fate_dev -apply_zk=true diff --git a/serving-server/cluster-deploy/scripts/jdk_install.sh b/serving-server/cluster-deploy/scripts/jdk_install.sh deleted file mode 100644 index d7913bbc..00000000 --- a/serving-server/cluster-deploy/scripts/jdk_install.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -BASE_SERVER=https://webank-ai-1251170195.cos.ap-guangzhou.myqcloud.com/ -jdk_version=jdk-8u192-linux-x64.tar.gz -jdk=jdk-8u192-linux-x64 -yum install -y wget -wget $BASE_SERVER/${jdk_version} -tar -zxvf ${jdk_version} -C /usr/local -cat >> /etc/profile << EOF -export JAVA_HOME=/usr/local/${jdk} -export PATH=\$PATH:\$JAVA_HOME/bin -EOF -#source /etc/profile - -#cat >> /etc/profile << EOF -#export JAVA_HOME=/usr/local/${jdk} -#export PATH=\$PATH:\$JAVA_HOME/bin -#EOF diff --git a/serving-server/cluster-deploy/scripts/package.sh b/serving-server/cluster-deploy/scripts/package.sh deleted file mode 100644 index d12ceb63..00000000 --- a/serving-server/cluster-deploy/scripts/package.sh +++ /dev/null @@ -1,165 +0,0 @@ -#!/bean package -DskipTestsin/bash -# -#Copyright 2019 The serving Authors. All Rights Reserved. -# - -set -e -source ./allinone_cluster_configurations.sh -cwd=$(cd `dirname $0`; pwd) -cd ${cwd} -public_path=$deploy_dir -package_path=$cwd/FATE-Serving -fate_serving_path=$public_path/fate-serving -sering_path=$fate_serving_path/serving -fate_serving=fate-serving -serving=serving -fate_serving_zip=fate-serving-server-*-release.zip -fate_serving_jar=fate-serving-server-*.jar -router_zip=fate-serving-router-*-release.zip -router_jar=fate-serving-router-*.jar -common=$public_path/fate-serving/common -router=router -router_path=$fate_serving_path/router -if [ ! -d $common ]; then - mkdir -p $common -else - echo [INFO] $common dir exist -fi -cp jdk_install.sh $common -cp redis_install.sh $common -cp services.sh $fate_serving_path -cd $common -echo "[INFO] jdk install" -#sudo sh jdk_install.sh -#sudo rm -rf jdk_install.sh -echo "[INFO] redis install" -sudo sh redis_install.sh -sudo rm -rf redis_install.sh -cd redis/conf -sudo sed -i.bak "s/# requirepass foobared/requirepass ${redis_password}/g" ./redis.conf -sudo sed -i.bak "s/databases 16/databases 50/g" ./redis.conf -cd ${cwd}/../../../ -echo "[INFO] mvn clean package start" -mvn clean package -DskipTests -if [ $? -eq 0 ]; then - echo "[INFO] mvn clean package success" -else - echo "[INFO] mvn clan package filed" -fi - -if [ ! -d $fate_serving_path ]; then - cd $public_path - mkdir $fate_serving -else - echo [INFO] $fate_serving_path dir exist -fi - -if [ -d $fate_serving_path ]; then - if [ ! -d $sering_path ]; then - cd $fate_serving_path - mkdir $serving - mkdir $router - else - echo [INFO] $fate_serving_path dir exist - fi - -else - echo "" -fi - -cd $cwd/../../target -cp $fate_serving_zip $sering_path -if [ $? -eq 0 ]; then - echo "[INFO] cp fate-serving-server-*-release.zip success" -else - echo "[INFO] cp fate-serving-server-*-release.zip filed" -fi -cd $sering_path -unzip $fate_serving_zip -ln -s $fate_serving_jar fate-serving-server.jar - -echo "[INFO] cp fate-serving-router-*-release.zip success" -cd $cwd/../../../router/target -cp $router_zip $router_path -if [ $? -eq 0 ]; then - echo "[INFO] cp fate-serving-router-*-release.zip success" -else - echo "[INFO] cp fate-serving-server-*-release.zip filed" -fi -echo "-------------------routerpach" -cd $router_path -unzip $router_zip -ln -s $router_jar fate-serving-router.jar -echo "--------------------ln -s" -cd $cwd -cp start_env/services.sh $public_path/fate-serving/common -echo "------------apply----" ${apply_zk} - -if [ ${apply_zk} = "false" ] -then - cd $router_path/conf - sed -i 's#useRegister=true#'useRegister=false'#' proxy.properties - sed -i 's#useZkRouter=true#'useZkRouter=false'#' proxy.properties - cd $sering_path/conf - sed -i 's#useRegister=true#'useRegister=false'#' serving-server.properties - sed -i 's#useZkRouter=true#'useZkRouter=false'#' serving-server.properties -fi - sh zookeeper_install.sh - cd $cwd - echo "-------zkui_instal start" - sh zkui_install.sh -sudo cp redis/service.sh $common/redis -cp zk/service.sh $common/zookeeper* -cp zkui/service.sh $common/zkui -echo "---------------------sed roll" -if [[ ${host_guest[0]} ]] -then - cd ${public_path}/fate-serving/serving/conf - sed -i 's#127.0.0.1:8011#'${roll_hostAndguest[0]}'#' serving-server.properties -else - echo "no have host--------false no null" - exit -fi - -init_env() { - ssh -tt ${user}@${host_guest[1]} << eeooff - if [ ! -d ${public_path} ] - then - mkdir -p ${public_path} - fi - exit -eeooff - cd ${public_path} - tar -zcvf fate-serving.tar.gz fate-serving/ - - scp ${public_path}/fate-serving.tar.gz @${host_guest[1]}:${public_path} - ssh -tt ${user}@${host_guest[1]} << eeooff - cd ${public_path} - echo "public_path-------" ${public_path} - tar -zxvf fate-serving.tar.gz - rm -rf fate-serving.tar.gz - cd ${public_path}/fate-serving/serving/conf - sed -i 's#${roll_hostAndguest[0]}#'${roll_hostAndguest[1]}'#' serving-server.properties -exit -eeooff - -} - -echo "host_guest1--if-----" ${host_guest[1]} -if [[ ${host_guest[1]} ]] -then - echo "------guest hava host_guest1 -----is not null" - if [[ ${roll_hostAndguest[1]} != "" ]]; then - init_env - else - echo "please input guest from allinone_cluster_configurations" - exit 0 - fi - -else - echo "no have guest--------false no null" -fi - - - - diff --git a/serving-server/cluster-deploy/scripts/redis/service.sh b/serving-server/cluster-deploy/scripts/redis/service.sh deleted file mode 100644 index 0e3fa590..00000000 --- a/serving-server/cluster-deploy/scripts/redis/service.sh +++ /dev/null @@ -1,87 +0,0 @@ -i#!/bin/bash - -# -# Copyright 2019 The FATE Authors. All Rights Reserved. -# -# 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. -# -set -e -basepath=$(cd `dirname $0`;pwd) -user=`whoami` - -getpid() { - pid=`ps -ef | grep redis-server | grep -v grep | awk '{print $2}'` -} - -status() { - getpid - if [[ -n ${pid} ]]; then - echo "status:`ps aux | grep ${pid} | grep -v grep`" - else - echo "service not running" - fi -} - -start() { - getpid - if [[ ${pid} == "" ]]; then - $basepath/bin/redis-server & - if [[ $? -eq 0 ]]; then - sleep 2 - getpid - echo "service start sucessfully. pid: ${pid}" - else - echo "service start failed" - fi - else - echo "service already started. pid: ${pid}" - fi -} - -stop() { - getpid - if [[ -n ${pid} ]]; then - echo "killing:`ps aux | grep ${pid} | grep -v grep`" - kill -9 ${pid} - if [[ $? -eq 0 ]]; then - echo "killed" - else - echo "kill error" - fi - else - echo "service not running" - fi -} - -case "$1" in - start) - start - status - ;; - - stop) - stop - ;; - status) - status - ;; - - restart) - stop - start - status - ;; - *) - echo "usage: $0 {start|stop|status|restart}" - exit -1 -esac diff --git a/serving-server/cluster-deploy/scripts/redis_install.sh b/serving-server/cluster-deploy/scripts/redis_install.sh deleted file mode 100644 index 4dfc654e..00000000 --- a/serving-server/cluster-deploy/scripts/redis_install.sh +++ /dev/null @@ -1,188 +0,0 @@ -#!/bin/bash -#------------------------------------------------------------------------------------------------------------------# -#| Some people die at the age of 25 and don't bury themselves in the earth until they are 75 |# -#------------------------------------------------------------------------------------------------------------------# -#| $$$$ $$ $$ $$$$$$ $$ $$ $$$$$$ $$ $$$$$$ $$$$$$ |# -#| $$ $$ $$ $$ $$ $$ $$ $$ $$ $$ $$ |# -#| $$ $$$$$$$ $$$$$ $$ $$ $ $$ $$$$$$ $$ $$$$$ $$$$$ |# -#| $$ $$ $$ $$ $$ $ $ $$ $$ $$ $$ $$ |# -#| $$$$ $$ $$ $$$$$$ $$ $$ $$$$$ $$ $$$$$$ $$$$$$ $$$$$$ |# -#------------------------------------------------------------------------------------------------------------------# -cwd=$(cd `dirname $0`; pwd) -onversion="4.0.3" -offversion=`basename redis-*.tar.gz .tar.gz | awk -F '-' '{print$2}'` -installdir=$(cd `dirname $0`; pwd) - -function initize(){ - installdir=$(cd `dirname $0`; pwd) -} - -function checkroot(){ -if [ $UID -ne 0 ] - then - echo "|----------------------------------------------------------------------------------------------------------------|" - echo "|------------------------------------------[权限不足...请切换至root用户]-----------------------------------------|" - echo "|----------------------------------------------------------------------------------------------------------------|" - exit; -fi -} - -function judge(){ - echo - offfile=`ls | grep redis-*.tar.gz` - if [[ "$offfile" != "" ]] - then - echo "|----------------------------------------------------------------------------------------------------------------|" - echo "|-------------------------------------------------[发现离线包]---------------------------------------------------|" - echo "|----------------------------------------------------------------------------------------------------------------|" - /usr/bin/sleep 3 - offinstall - else - echo "|----------------------------------------------------------------------------------------------------------------|" - echo "|-------------------------------------------------[未发现离线包]-------------------------------------------------|" - echo "|--------------------------------------------[开始判断是否连接外网安装]------------------------------------------|" - /usr/bin/sleep 3 - network - fi -} - -function offinstall(){ - echo "|----------------------------------------------------------------------------------------------------------------|" - echo "|------------------------------------------------[离线包安装中]--------------------------------------------------|" - echo "|----------------------------------------------------------------------------------------------------------------|" - tar -zxvf redis-${offversion}.tar.gz >/dev/null 2>&1 - redis="redis-${offversion}" - cd ${redis}/src && make >/dev/null 2>&1 - if [[ $? -ne 0 ]]; then - echo "编译出错" - else - echo "|----------------------------------------------------------------------------------------------------------------|" - echo "|---------------------------------------------------[编译完成]---------------------------------------------------|" - echo "|----------------------------------------------------------------------------------------------------------------|" - /usr/bin/sleep 3 - intend - fi -} - -function network(){ - httpcode=`curl -I -m 10 -o /dev/null -s -w %{http_code}'\n' http://www.baidu.com` - net1=$(echo $httpcode | grep "200") - if [[ "$net1" != "" ]] - then - echo "|----------------------------------------------------------------------------------------------------------------|" - echo "|-----------------------------------------------------[联网]-----------------------------------------------------|" - echo "|-------------------------------------------------[准备联网安装]-------------------------------------------------|" - echo "|----------------------------------------------------------------------------------------------------------------|" - /usr/bin/sleep 3 - online - else - echo "|----------------------------------------------------------------------------------------------------------------|" - echo "|-------------------------------------------[未联网,无离线安装包,准备退出]---------------------------------------|" - echo "|----------------------------------------------------------------------------------------------------------------|" - /usr/bin/sleep 3 - exit; - fi -} -function online(){ - wget_v=`which wget` - wget_vv=$(echo $wget_v | grep wget) - if [[ "$wget_vv" != "" ]] - then - echo "|----------------------------------------------------------------------------------------------------------------|" - echo "|--------------------------------------`wget -V |head -n 1`---------------------------------------|" - echo "|----------------------------------------------------------------------------------------------------------------|" - wget http://download.redis.io/releases/redis-${onversion}.tar.gz - installon - else - echo "|----------------------------------------------------------------------------------------------------------------|" - echo "|----------------------------------------[检测到wget没有安装, 准备安装wget]---------------------------------------|" - echo "|----------------------------------------------------------------------------------------------------------------|" - yum install wget -y - echo "|----------------------------------------------------------------------------------------------------------------|" - echo "|--------------------------------------`wget -V |head -n 1`---------------------------------------|" - echo "|----------------------------------------------------------------------------------------------------------------|" - wget http://download.redis.io/releases/redis-${onversion}.tar.gz - installon - fi -} - -function installon(){ - echo "|----------------------------------------------------------------------------------------------------------------|" - echo "|------------------------------------------------[在线包安装中]--------------------------------------------------|" - echo "|----------------------------------------------------------------------------------------------------------------|" - tar -zxvf redis-${onversion}.tar.gz >/dev/null 2>&1 - redis="redis-${onversion}" - cd ${redis}/src && make >/dev/null 2>&1 - if [[ $? -ne 0 ]]; then - echo "编译出错" - else - echo "|----------------------------------------------------------------------------------------------------------------|" - echo "|--------------------------------------------------[编译完成]----------------------------------------------------|" - echo "|----------------------------------------------------------------------------------------------------------------|" - /usr/bin/sleep 3 - intend - fi -} - -function intend(){ - echo "|----------------------------------------------------------------------------------------------------------------|" - echo "|-------------------------------------------------[开始迁移文件]-------------------------------------------------|" - echo "|----------------------------------------------------------------------------------------------------------------|" - mkdir -p ${installdir}/redis/{logs,nodes,conf,bin} - cp redis-cli redis-server ${installdir}/redis/bin - cp redis-trib.rb ${installdir}/redis - cp ../redis.conf ${installdir}/redis/conf - echo "|----------------------------------------------------------------------------------------------------------------|" - echo "|-------------------------------------------------[数据迁移完成]-------------------------------------------------|" - echo "|----------------------------------------------------------------------------------------------------------------|" - /usr/bin/sleep 2 - echo "|----------------------------------------------------------------------------------------------------------------|" - echo "|-------------------------------------------------[清理多余文件]-------------------------------------------------|" - echo "|----------------------------------------------------------------------------------------------------------------|" - finish - /usr/bin/sleep 2 - echo "|----------------------------------------------------------------------------------------------------------------|" - echo "|-------------------------------------------------[配置快捷启动]-------------------------------------------------|" - echo "|----------------------------------------------------------------------------------------------------------------|" - service - /usr/bin/sleep 2 - echo "|----------------------------------------------------------------------------------------------------------------|" - echo "|-------------------------------------------------[修改配置文件]-------------------------------------------------|" - echo "|----------------------------------------------------------------------------------------------------------------|" - configfile - /usr/bin/sleep 2 - echo "|****************************************************************************************************************|" - echo "| WW WW EEEEEEE LL CCCCC OOOOOO MM MM EEEEEEE |" - echo "| WW WWWW WW EE LL CC OO OO MMMM MMMM EE |" - echo "| WW WW WW WW EEEEE LL CC OO OO MM MM MM MM EEEEE |" - echo "| WW W W WW EE LL CC OO OO MM M M MM EE |" - echo "| WW WW EEEEEEE LLLLLL CCCCC OOOOOO MM MMM MM EEEEEEE |" - echo "|****************************************************************************************************************|" -} -function finish(){ - echo - rm -rf ${installdir}/redis-* -} -function service(){ - cd ${installdir}/redis && echo "./bin/redis-server conf/redis.conf" > start.sh - chmod +x start.sh -} -function configfile(){ - cd ${installdir}/redis/conf - #后台 - sed -i 's/daemonize no/daemonize yes/' redis.conf - #端口 - #日志输出文件 - sed -i.bak "s/bind 127.0.0.1/bind 0.0.0.0/g" ./redis.conf - sed -i 's/logfile ""/logfile "\/usr\/local\/src\/redis\/logs\/redis.logs"/' redis.conf -} -function main(){ -checkroot -judge -} -main - -#cd $cwd/redis/bin -#echo "$cwd--------" $cwd -#echo "[INFO] --------redis start" -#./redis-server & diff --git a/serving-server/cluster-deploy/scripts/services.sh b/serving-server/cluster-deploy/scripts/services.sh deleted file mode 100644 index 40e9b281..00000000 --- a/serving-server/cluster-deploy/scripts/services.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/bash - -# -# Copyright 2019 The FATE Authors. All Rights Reserved. -# -# 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. -# - -modules=(router serving) - -cwd=`pwd` - -main() { - module=$1 - action=$2 - echo "--------------" - echo "[INFO] processing: ${module}" - if [[ "${module}" == "apigateway" ]];then - cd ${module}/bin/ - bash service.sh ${action} - else - cd ${module} - bash service.sh ${action} - fi - echo "--------------" - echo -} - -all() { - echo "[INFO] processing all modules" - echo "==================" - echo - for module in "${modules[@]}"; do - cd ${cwd} - main ${module} $2 - done - echo "==================" -} - -usage() { - echo "usage: $0 {all|[module1, ...]} {start|stop|status|restart}" -} - -multiple() { - total=$# - action=${!total} - for (( i=1; i Date: Wed, 25 Dec 2019 18:33:35 +0800 Subject: [PATCH 058/190] update to 1.2.0 Signed-off-by: v_dylanxu <136539068@qq.com> --- fate-serving-core/pom.xml | 2 +- federatedml/pom.xml | 2 +- pom.xml | 3 +- proto/model_service.proto | 1 - register/pom.xml | 4 +- router/pom.xml | 6 +- serving-proxy/pom.xml | 7 +-- serving-server/audit/fate-proxy-audit.log | 0 serving-server/conf/serving-server.properties | 61 ------------------- serving-server/pom.xml | 2 +- .../fate/serving/bean/InferenceRequest.java | 10 --- 11 files changed, 12 insertions(+), 86 deletions(-) delete mode 100644 serving-server/audit/fate-proxy-audit.log delete mode 100644 serving-server/conf/serving-server.properties diff --git a/fate-serving-core/pom.xml b/fate-serving-core/pom.xml index 13c8e76a..94f972c5 100644 --- a/fate-serving-core/pom.xml +++ b/fate-serving-core/pom.xml @@ -20,7 +20,7 @@ fate-serving com.webank.ai.fate - 1.1.2 + 1.2.0 4.0.0 diff --git a/federatedml/pom.xml b/federatedml/pom.xml index 5cb92cee..1cc61eb2 100644 --- a/federatedml/pom.xml +++ b/federatedml/pom.xml @@ -20,7 +20,7 @@ fate-serving com.webank.ai.fate - 1.1.2 + 1.2.0 4.0.0 diff --git a/pom.xml b/pom.xml index ad65d135..2d451c04 100644 --- a/pom.xml +++ b/pom.xml @@ -19,14 +19,13 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> com.webank.ai.fate fate-serving - 1.1.2 + 1.2.0 pom 4.0.0 serving-server - router federatedml fate-serving-core register diff --git a/proto/model_service.proto b/proto/model_service.proto index f3addd7c..db44ef48 100644 --- a/proto/model_service.proto +++ b/proto/model_service.proto @@ -40,5 +40,4 @@ service ModelService{ rpc publishLoad(PublishRequest) returns (PublishResponse); rpc publishOnline(PublishRequest) returns (PublishResponse); rpc publishBind(PublishRequest) returns (PublishResponse); - rpc publishOnline(PublishRequest) returns (PublishResponse); } diff --git a/register/pom.xml b/register/pom.xml index c48497a3..7880e95b 100644 --- a/register/pom.xml +++ b/register/pom.xml @@ -20,12 +20,12 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> fate-register - 1.1.2 + fate-serving com.webank.ai.fate - 1.1.2 + 1.2.0 jar diff --git a/router/pom.xml b/router/pom.xml index 457e3c45..f40ef51d 100644 --- a/router/pom.xml +++ b/router/pom.xml @@ -20,13 +20,13 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> fate-serving-router - + fate-serving com.webank.ai.fate - 1.1.2 + 1.2.0 jar @@ -40,7 +40,7 @@ com.webank.ai.fate fate-register - 1.1.2 + ${project.version} diff --git a/serving-proxy/pom.xml b/serving-proxy/pom.xml index 556dc185..8f8544d2 100644 --- a/serving-proxy/pom.xml +++ b/serving-proxy/pom.xml @@ -4,12 +4,11 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> fate-serving-proxy - ${fate.version} fate-serving com.webank.ai.fate - ${fate.version} + 1.2.0 jar @@ -21,7 +20,7 @@ com.webank.ai.fate fate-register - ${fate.version} + ${project.version} @@ -215,7 +214,7 @@ com.webank.ai.fate fate-serving-core - ${fate.version} + ${project.version} compile diff --git a/serving-server/audit/fate-proxy-audit.log b/serving-server/audit/fate-proxy-audit.log deleted file mode 100644 index e69de29b..00000000 diff --git a/serving-server/conf/serving-server.properties b/serving-server/conf/serving-server.properties deleted file mode 100644 index 0fbde69c..00000000 --- a/serving-server/conf/serving-server.properties +++ /dev/null @@ -1,61 +0,0 @@ -# -# Copyright 2019 The FATE Authors. All Rights Reserved. -# -# 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. -# -coordinator=webank -ip=127.0.0.1 -port=8000 -workMode=0 -serviceRoleName=serving -inferenceWorkerThreadNum=10 -#storage -# maybe python/data/ -standaloneStoragePath= -# cache -remoteModelInferenceResultCacheSwitch=true -# in-process cache -modelCacheAccessTTL=12 -modelCacheMaxSize=50 -remoteModelInferenceResultCacheTTL=300 -remoteModelInferenceResultCacheMaxSize=10000 -inferenceResultCacheTTL=30 -inferenceResultCacheCacheMaxSize=1000 -# external cache -redis.ip=127.0.0.1 -redis.port=6379 -redis.password=fate_dev -redis.timeout=10 -redis.maxTotal=100 -redis.maxIdle=100 -external.remoteModelInferenceResultCacheTTL=86400 -external.remoteModelInferenceResultCacheDBIndex=0 -external.inferenceResultCacheTTL=300 -external.inferenceResultCacheDBIndex=0 -canCacheRetcode=0,102 -external.processCacheDBIndex=0 -# federation -party.id=9999 -# adapter -OnlineDataAccessAdapter=TestFile -InferencePostProcessingAdapter=PassPostProcessing -InferencePreProcessingAdapter=PassPreProcessing -# external subsystem -proxy=127.0.0.1:9370 -roll=127.0.0.1:8011 -#zk.url=zookeeper://localhost:2181?backup=localhost:2182,localhost:2183 -zk.url=zookeeper://localhost:2181 -useRegister=false -useZkRouter=false -useJMX=true -jmx.server.name=JMXServer diff --git a/serving-server/pom.xml b/serving-server/pom.xml index 4130c9c7..313cda2a 100644 --- a/serving-server/pom.xml +++ b/serving-server/pom.xml @@ -20,7 +20,7 @@ fate-serving com.webank.ai.fate - 1.1.2 + 1.2.0 4.0.0 diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java b/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java index 78d9cd8b..5f49edbd 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java @@ -28,17 +28,7 @@ public class InferenceRequest implements Request { private String appid; private String partyId; - - public void setModelVersion(String modelVersion) { - this.modelVersion = modelVersion; - } - private String modelVersion; - - public void setModelId(String modelId) { - this.modelId = modelId; - } - private String modelId; private String seqno; private String caseid; From 2a0579f8bc03a687cffe6476e174ab06bf9c7313 Mon Sep 17 00:00:00 2001 From: qianduoduo <1002502212@qq.com> Date: Wed, 25 Dec 2019 18:40:33 +0800 Subject: [PATCH 059/190] =?UTF-8?q?service.sh=20=E8=84=9A=E6=9C=ACkill?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serving-server/bin/service.sh | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/serving-server/bin/service.sh b/serving-server/bin/service.sh index 90c922de..41a27672 100644 --- a/serving-server/bin/service.sh +++ b/serving-server/bin/service.sh @@ -21,16 +21,21 @@ module=serving-server main_class=com.webank.ai.fate.serving.ServingServer -module_version=1.1 getpid() { - pid=`ps aux | grep ${main_class} | grep -v grep | awk '{print $2}'` + # pid=`ps aux | grep ${main_class} | grep -v grep | awk '{print $2}'` - if [[ -n ${pid} ]]; then - return 1 - else - return 0 - fi + if [ ! -e "./${module}_pid" ];then + touch ./${module}_pid + echo "" >./${module}_pid + fi + module_pid=`cat ./${module}_pid` + pid=`ps aux | grep ${module_pid} | grep -v grep | grep -v $0 | awk '{print $2}'` + if [[ -n ${pid} ]]; then + return 1 + else + return 0 + fi } mklogsdir() { @@ -55,11 +60,10 @@ start() { getpid if [[ $? -eq 0 ]]; then mklogsdir - if [[ ! -e "fate-${module}.jar" ]]; then - ln -s fate-${module}-${module_version}.jar fate-${module}.jar - fi java -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/${module}.properties >> logs/console.log 2>>logs/error.log & if [[ $? -eq 0 ]]; then + sleep 2 + echo $!>./${module}_pid getpid echo "service start sucessfully. pid: ${pid}" else @@ -77,6 +81,7 @@ stop() { `ps aux | grep ${pid} | grep -v grep`" kill -9 ${pid} if [[ $? -eq 0 ]]; then + rm -rf ./${module}_pid echo "killed" else echo "kill error" @@ -107,4 +112,4 @@ case "$1" in *) echo "usage: $0 {start|stop|status|restart}" exit -1 -esac \ No newline at end of file +esac From c91296458ffef364193201f96648e2c7f6559089 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Thu, 26 Dec 2019 10:46:29 +0800 Subject: [PATCH 060/190] update to 1.1.2 Signed-off-by: v_dylanxu <136539068@qq.com> --- router/bin/service.sh | 2 +- serving-server/bin/service.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/router/bin/service.sh b/router/bin/service.sh index 26ee158b..ef3e9bdd 100644 --- a/router/bin/service.sh +++ b/router/bin/service.sh @@ -23,7 +23,7 @@ configpath=$(cd $basepath/conf;pwd) module=serving-router main_class=com.webank.ai.fate.networking.Proxy -module_version=1.1 +module_version=1.1.2 getpid() { pid=`ps aux | grep ${module} | grep -v grep | awk '{print $2}'` diff --git a/serving-server/bin/service.sh b/serving-server/bin/service.sh index 90c922de..10251a75 100644 --- a/serving-server/bin/service.sh +++ b/serving-server/bin/service.sh @@ -21,7 +21,7 @@ module=serving-server main_class=com.webank.ai.fate.serving.ServingServer -module_version=1.1 +module_version=1.1.2 getpid() { pid=`ps aux | grep ${main_class} | grep -v grep | awk '{print $2}'` From 0f6ab8a518b4b6f147e00434d20f709daf15004d Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Thu, 26 Dec 2019 10:51:01 +0800 Subject: [PATCH 061/190] update to 1.2.0 Signed-off-by: v_dylanxu <136539068@qq.com> --- router/bin/service.sh | 2 +- serving-server/bin/service.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/router/bin/service.sh b/router/bin/service.sh index 26ee158b..52319e07 100644 --- a/router/bin/service.sh +++ b/router/bin/service.sh @@ -23,7 +23,7 @@ configpath=$(cd $basepath/conf;pwd) module=serving-router main_class=com.webank.ai.fate.networking.Proxy -module_version=1.1 +module_version=1.2.0 getpid() { pid=`ps aux | grep ${module} | grep -v grep | awk '{print $2}'` diff --git a/serving-server/bin/service.sh b/serving-server/bin/service.sh index 90c922de..b4ff2207 100644 --- a/serving-server/bin/service.sh +++ b/serving-server/bin/service.sh @@ -21,7 +21,7 @@ module=serving-server main_class=com.webank.ai.fate.serving.ServingServer -module_version=1.1 +module_version=1.2.0 getpid() { pid=`ps aux | grep ${main_class} | grep -v grep | awk '{print $2}'` From b0b66e9b2f13e6c3a130dec6415ff7b404a0a857 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Thu, 26 Dec 2019 20:14:59 +0800 Subject: [PATCH 062/190] update to 1.2.0 Signed-off-by: v_dylanxu <136539068@qq.com> --- .../java/com/webank/ai/fate/serving/core/monitor/WatchDog.java | 0 proto/model_service.proto | 1 - 2 files changed, 1 deletion(-) delete mode 100644 fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/monitor/WatchDog.java diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/monitor/WatchDog.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/monitor/WatchDog.java deleted file mode 100644 index e69de29b..00000000 diff --git a/proto/model_service.proto b/proto/model_service.proto index 32ac4a7d..db44ef48 100644 --- a/proto/model_service.proto +++ b/proto/model_service.proto @@ -38,7 +38,6 @@ message PublishResponse{ service ModelService{ rpc publishLoad(PublishRequest) returns (PublishResponse); - rpc publishBind(PublishRequest) returns (PublishResponse); rpc publishOnline(PublishRequest) returns (PublishResponse); rpc publishBind(PublishRequest) returns (PublishResponse); } From 75a352cd10a92f2b9e1fc2d071701c1fca29a4f0 Mon Sep 17 00:00:00 2001 From: qianduoduo <1002502212@qq.com> Date: Thu, 26 Dec 2019 21:34:03 +0800 Subject: [PATCH 063/190] =?UTF-8?q?zk=20=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6bug=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serving-server/cluster-deploy/scripts/zookeeper_install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/serving-server/cluster-deploy/scripts/zookeeper_install.sh b/serving-server/cluster-deploy/scripts/zookeeper_install.sh index 966a62b3..b58baeb0 100644 --- a/serving-server/cluster-deploy/scripts/zookeeper_install.sh +++ b/serving-server/cluster-deploy/scripts/zookeeper_install.sh @@ -31,7 +31,8 @@ cd conf echo "sh zkServer.sh start ------" $sh zkServer.sh start cp zoo_sample.cfg zoo.cfg sed -i 's#/tmp/zookeepe#'$zk_path'/data#' zoo.cfg -sed -i '/^dataDir/a\dataLogDir='$zk_path'/logs\' zoo.cfg +sed -i '/^dataDir/a\dataLogDir='$zk_path'/logs\' zoo.cfg +sed -i '$a\admin.serverPort=9080' zoo.cfg #cd ../bin #sh zkServer.sh start #if [ $? -eq 0 ]; then From ee651238f2e9887cd7ebc00642a94d4a3c3bd282 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Fri, 27 Dec 2019 10:30:08 +0800 Subject: [PATCH 064/190] remote eggroll lib dependencies, change logger to slf4j Signed-off-by: v_dylanxu <136539068@qq.com> --- fate-serving-core/pom.xml | 4 +- .../fate/serving/core/bean/BaseContext.java | 12 +- .../serving/core/bean/BaseLoggerPrinter.java | 10 +- .../fate/serving/core/bean/CacheManager.java | 2 - .../fate/serving/core/bean/Configuration.java | 17 +-- .../ai/fate/serving/core/bean/Context.java | 2 +- .../serving/core/bean/DistributedDTable.java | 142 ------------------ .../serving/core/bean/GrpcConnectionPool.java | 8 +- .../bean/GuestInferenceLoggerPrinter.java | 10 +- .../core/bean/HostInferenceLoggerPrinter.java | 10 +- .../fate/serving/core/bean/ReturnResult.java | 9 +- .../core/manager/DefaultCacheManager.java | 30 ++-- .../serving/core/utils/GetSystemInfo.java | 10 +- .../serving/core/utils/ObjectTransform.java | 51 +++++++ .../serving/core/utils/ProtobufUtils.java | 10 +- .../fate/serving/federatedml/DSLParser.java | 26 ++-- .../serving/federatedml/PipelineTask.java | 28 ++-- .../serving/federatedml/model/BaseModel.java | 33 ++-- .../serving/federatedml/model/DataIO.java | 14 +- .../federatedml/model/FeatureSelection.java | 12 +- .../model/HeteroFeatureBinning.java | 18 +-- .../serving/federatedml/model/HeteroLR.java | 20 +-- .../federatedml/model/HeteroLRGuest.java | 18 +-- .../federatedml/model/HeteroLRHost.java | 10 +- .../federatedml/model/HeteroSecureBoost.java | 11 +- .../model/HeteroSecureBoostingTreeGuest.java | 16 +- .../model/HeteroSecureBoostingTreeHost.java | 4 +- .../serving/federatedml/model/Imputer.java | 8 +- .../federatedml/model/MinMaxScale.java | 12 +- .../federatedml/model/OneHotEncoder.java | 26 ++-- .../serving/federatedml/model/Outlier.java | 10 +- .../fate/serving/federatedml/model/Scale.java | 10 +- .../federatedml/model/StandardScale.java | 10 +- .../register/common/AbstractRegistry.java | 6 +- .../common/AbstractRegistryFactory.java | 12 +- .../common/AbstractZookeeperClient.java | 6 +- .../common/AbstractZookeeperTransporter.java | 6 +- .../common/CuratorZookeeperClient.java | 6 +- .../register/common/FailbackRegistry.java | 6 +- .../register/common/HashedWheelTimer.java | 6 +- .../ai/fate/register/provider/FateServer.java | 6 +- .../register/provider/HelloWorldServer.java | 6 +- .../router/AbstractRouterService.java | 6 +- .../register/router/DefaultRouterService.java | 1 - .../fate/register/task/AbstractRetryTask.java | 6 +- .../ai/fate/register/utils/NetUtils.java | 14 +- .../register/zookeeper/ZookeeperRegistry.java | 6 +- .../com/webank/ai/fate/networking/Proxy.java | 16 +- .../fate/networking/proxy/Main0Insecure.java | 8 +- .../ai/fate/networking/proxy/Main0Stream.java | 8 +- .../ai/fate/networking/proxy/Main1.java | 10 +- .../ai/fate/networking/proxy/Main2.java | 10 +- .../ai/fate/networking/proxy/Main3.java | 10 +- .../PipeHandleNotificationEventListener.java | 10 +- .../proxy/factory/GrpcServerFactory.java | 24 +-- .../factory/GrpcStreamObserverFactory.java | 6 +- .../proxy/factory/GrpcStubFactory.java | 22 +-- .../grpc/client/DataTransferPipedClient.java | 50 +++--- .../ClientPullResponseStreamObserver.java | 20 +-- .../ClientPushResponseStreamObserver.java | 14 +- ...ClientUnaryCallResponseStreamObserver.java | 20 +-- .../ServerPushRequestStreamObserver.java | 46 +++--- .../service/DataTransferPipedServerImpl.java | 52 +++---- .../proxy/grpc/service/RouteServerImpl.java | 10 +- .../InputStreamOutputStreamNoStoragePipe.java | 10 +- ...InputStreamToPacketUnidirectionalPipe.java | 8 +- .../proxy/infra/impl/PacketQueuePipe.java | 12 +- .../impl/PacketQueueSingleResultPipe.java | 6 +- ...acketToOutputStreamUnidirectionalPipe.java | 8 +- .../proxy/manager/ExecutorManager.java | 10 +- .../proxy/manager/StatsManager.java | 16 +- .../proxy/service/ConfFileBasedFdnRouter.java | 16 +- .../fate/networking/proxy/util/AuthUtils.java | 19 ++- .../fate/networking/proxy/util/Timeouts.java | 10 +- .../networking/proxy/util/ToStringUtils.java | 10 +- .../proxy/common/ErrorMessageUtil.java | 1 - .../serving/proxy/common/GetSystemInfo.java | 10 +- .../proxy/config/CharacterEncodingFilter.java | 1 - .../serving/proxy/config/WebConfigration.java | 7 - .../serving/proxy/exceptions/ErrorCode.java | 2 - .../exceptions/GlobalExceptionHandler.java | 4 - .../rpc/core/AbstractServiceAdaptor.java | 6 +- .../proxy/rpc/core/OverLoadLogger.java | 5 - .../serving/proxy/rpc/core/ProxyService.java | 2 - .../proxy/rpc/grpc/GrpcConnectionPool.java | 1 - .../proxy/rpc/grpc/InterRequestHandler.java | 10 +- .../proxy/rpc/grpc/IntraRequestHandler.java | 6 +- .../proxy/rpc/grpc/ProxyRequestHandler.java | 9 +- .../rpc/grpc/ServiceExceptionHandler.java | 4 +- .../proxy/rpc/router/BaseServingRouter.java | 6 +- .../router/ConfigFileBasedServingRouter.java | 6 +- .../rpc/router/DefaultServingRouter.java | 2 - .../proxy/rpc/router/RouteTypeConvertor.java | 1 - .../proxy/rpc/router/ZkServingRouter.java | 7 +- .../proxy/rpc/services/InferenceService.java | 6 +- .../serving/proxy/security/AuthUtils.java | 23 ++- .../fate/serving/proxy/utils/FileUtils.java | 14 +- .../serving/proxy/utils/ToStringUtils.java | 10 +- .../webank/ai/fate/serving/ServingServer.java | 34 ++--- .../webank/ai/fate/serving/SpringConfig.java | 7 +- .../serving/adapter/dataaccess/DTest.java | 9 +- .../serving/adapter/dataaccess/TestFile.java | 8 +- .../processing/CommonPostProcessing.java | 11 +- .../adapter/processing/PassPreProcessing.java | 2 +- .../fate/serving/bean/InferenceRequest.java | 1 - .../guest/DefaultGuestInferenceProvider.java | 31 ++-- .../host/DefaultHostInferenceProvider.java | 16 +- .../serving/manger/DefaultModelCache.java | 8 +- .../serving/manger/DefaultModelManager.java | 11 +- .../manger/InferenceWorkerManager.java | 10 +- .../ai/fate/serving/manger/ModelUtils.java | 24 ++- .../manger/ReentrantReadWriteMapPool.java | 12 +- .../serving/service/InferenceService.java | 16 +- .../ai/fate/serving/service/ModelService.java | 12 +- .../ai/fate/serving/service/ProxyService.java | 9 +- .../service/ServiceExceptionHandler.java | 8 +- .../ServiceOverloadProtectionHandle.java | 9 +- .../ai/fate/serving/utils/DTableUtils.java | 52 ------- .../ai/fate/serving/utils/HttpClientPool.java | 15 +- .../ai/fate/serving/utils/InferenceUtils.java | 21 ++- .../ai/fate/serving/utils/VersionControl.java | 75 --------- 121 files changed, 702 insertions(+), 1009 deletions(-) delete mode 100644 fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/DistributedDTable.java create mode 100644 fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/ObjectTransform.java delete mode 100644 serving-server/src/main/java/com/webank/ai/fate/serving/utils/DTableUtils.java delete mode 100644 serving-server/src/main/java/com/webank/ai/fate/serving/utils/VersionControl.java diff --git a/fate-serving-core/pom.xml b/fate-serving-core/pom.xml index 94f972c5..2794ac00 100644 --- a/fate-serving-core/pom.xml +++ b/fate-serving-core/pom.xml @@ -31,13 +31,13 @@ 2.9.0 - + com.alibaba fastjson diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java index b55fd2e5..468f159d 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java @@ -16,21 +16,19 @@ package com.webank.ai.fate.serving.core.bean; -import com.codahale.metrics.ConsoleReporter; import com.codahale.metrics.Counter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; import com.google.common.collect.Maps; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import java.util.Map; -import java.util.concurrent.TimeUnit; public class BaseContext implements Context { - private static final Logger LOGGER = LogManager.getLogger(LOGGER_NAME); + private static final Logger logger = LoggerFactory.getLogger(logger_NAME); public static ApplicationContext applicationContext; long timestamp; LoggerPrinter loggerPrinter; @@ -74,7 +72,7 @@ public void preProcess() { counter.inc(); timerContext = timer.time(); }catch(Exception e){ - LOGGER.error("preProcess error" ,e); + logger.error("preProcess error" ,e); } } @@ -127,7 +125,7 @@ public void postProcess(Req req, Resp resp) { } catch (Throwable e) { - LOGGER.error("postProcess error" ,e); + logger.error("postProcess error" ,e); } } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseLoggerPrinter.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseLoggerPrinter.java index 934127d8..65916452 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseLoggerPrinter.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseLoggerPrinter.java @@ -18,21 +18,21 @@ import com.webank.ai.fate.serving.core.utils.GetSystemInfo; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class BaseLoggerPrinter implements LoggerPrinter { - static final String LOGGER_NAME = "flow"; + static final String logger_NAME = "flow"; - private static final Logger LOGGER = LogManager.getLogger(LOGGER_NAME); + private static final Logger logger = LoggerFactory.getLogger(logger_NAME); @Override public void printLog(Context context, Object req, ReturnResult resp) { - LOGGER.info("{}|{}|{}|{}|{}|{}|{}|{}", GetSystemInfo.getLocalIp(), context.getSeqNo(), Dict.NONE, context.getActionType(), context.getCostTime(), + logger.info("{}|{}|{}|{}|{}|{}|{}|{}", GetSystemInfo.getLocalIp(), context.getSeqNo(), Dict.NONE, context.getActionType(), context.getCostTime(), resp != null ? resp.getRetcode() : Dict.NONE, req, resp); diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/CacheManager.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/CacheManager.java index eb1c854a..08a20154 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/CacheManager.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/CacheManager.java @@ -17,8 +17,6 @@ package com.webank.ai.fate.serving.core.bean; -import java.util.Map; - public interface CacheManager { diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Configuration.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Configuration.java index b59bb9dc..c1574caa 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Configuration.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Configuration.java @@ -16,6 +16,10 @@ package com.webank.ai.fate.serving.core.bean; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.*; import java.nio.file.Paths; import java.util.HashMap; @@ -23,13 +27,8 @@ import java.util.Optional; import java.util.Properties; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.json.JSONObject; - public class Configuration { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(Configuration.class); private final String confPath; private static String confDirectory; private static HashMap properties; @@ -58,10 +57,10 @@ public int load() { loadAdapterConf(baseConfFile.getParent()); return StatusCode.OK; } catch (FileNotFoundException ex) { - LOGGER.error("Can not found this file: {}", this.confPath); + logger.error("Can not found this file: {}", this.confPath); return StatusCode.NOFILE; } catch (Exception ex) { - LOGGER.error("", ex); + logger.error("", ex); return StatusCode.UNKNOWNERROR; } } @@ -93,7 +92,7 @@ private void loadOtherConf(String confRootDir, String confDirName, break; } } catch (IOException ex) { - LOGGER.error(ex); + logger.error(ex.getMessage()); } } } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Context.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Context.java index ab41e27a..668706e6 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Context.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Context.java @@ -21,7 +21,7 @@ public interface Context { - static final String LOGGER_NAME = "flow"; + static final String logger_NAME = "flow"; public void preProcess(); diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/DistributedDTable.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/DistributedDTable.java deleted file mode 100644 index 48238f59..00000000 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/DistributedDTable.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.serving.core.bean; - -import com.google.protobuf.ByteString; - -import com.webank.ai.eggroll.api.storage.*; - - -import io.grpc.Channel; -import io.grpc.ManagedChannel; -import io.grpc.Metadata; -import io.grpc.stub.MetadataUtils; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -public class DistributedDTable implements DTable { - private static final Logger LOGGER = LogManager.getLogger(); - - private String name; - private String nameSpace; - private int partition; - private String address; - - public DistributedDTable(String name, String nameSpace, int partition) { - this.address =Configuration.getProperty("roll"); - this.name = name; - this.nameSpace = nameSpace; - this.partition = partition; - } - - private ManagedChannel getChannel(String address) { - - try { - - return GrpcConnectionPool.getPool().getManagedChannel(address); - }catch(Exception e){ - e.printStackTrace(); - } - return null; - - - } - - @Override - public byte[] get(String key) { - Kv.Operand.Builder requestOperand = Kv.Operand.newBuilder(); - requestOperand.setKey(ByteString.copyFrom(key.getBytes())); - ManagedChannel channel=null; - try { - channel = getChannel(address); - KVServiceGrpc.KVServiceBlockingStub kvServiceBlockingStub = KVServiceGrpc.newBlockingStub(channel); - Kv.Operand resultOperand = MetadataUtils.attachHeaders(kvServiceBlockingStub, this.genHeader()).get(requestOperand.build()); - if (resultOperand.getValue() != null) { - return resultOperand.getValue().toByteArray(); - } else { - return null; - } - }finally { - if(channel!=null) { - GrpcConnectionPool.getPool().returnPool(channel, address); - } - - } - } - - @Override - public void put(String key, byte[] value) { - StorageBasic.StorageLocator.Builder storageLocator = StorageBasic.StorageLocator.newBuilder(); - storageLocator.setType(StorageBasic.StorageType.LMDB) - .setNamespace(this.nameSpace) - .setName(this.name); - - Kv.CreateTableInfo.Builder tableInfo = Kv.CreateTableInfo.newBuilder(); - tableInfo.setStorageLocator(storageLocator.build()).setFragmentCount(this.partition); - ManagedChannel channel=null; - try { - channel = getChannel(address); - KVServiceGrpc.KVServiceBlockingStub kvServiceBlockingStub = KVServiceGrpc.newBlockingStub(channel); - Kv.CreateTableInfo newTableInfo = kvServiceBlockingStub.createIfAbsent(tableInfo.build()); - - Kv.Operand.Builder requestOperand = Kv.Operand.newBuilder(); - requestOperand.setKey(ByteString.copyFrom(key.getBytes())); - requestOperand.setValue(ByteString.copyFrom(value)); - MetadataUtils.attachHeaders(kvServiceBlockingStub, this.genHeader()).put(requestOperand.build()); - }finally { - if(channel!=null) { - GrpcConnectionPool.getPool().returnPool(channel, address); - } - - } - } - - @Override - public Map collect() { - ManagedChannel channel=null; - try { - channel = getChannel(address); - Map result = new HashMap<>(8); - Kv.Range.Builder rangeOrBuilder = Kv.Range.newBuilder(); - KVServiceGrpc.KVServiceBlockingStub kvServiceBlockingStub = KVServiceGrpc.newBlockingStub(channel); - Iterator item = MetadataUtils.attachHeaders(kvServiceBlockingStub, this.genHeader()).iterate(rangeOrBuilder.build()); - while (item.hasNext()) { - Kv.Operand tmp = item.next(); - result.put(tmp.getKey().toStringUtf8(), tmp.getValue().toByteArray()); - } - return result; - }finally { - if(channel!=null) { - GrpcConnectionPool.getPool().returnPool(channel, address); - } - - } - } - - private Metadata genHeader() { - Metadata header = new Metadata(); - header.put(CompositeHeaderKey.from("table_name").asMetaKey(), this.name); - header.put(CompositeHeaderKey.from("name_space").asMetaKey(), this.nameSpace); - header.put(CompositeHeaderKey.from("store_type").asMetaKey(), "LMDB"); - return header; - } -} diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java index cdde9a84..11060ef2 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java @@ -24,15 +24,15 @@ import org.apache.commons.pool2.impl.DefaultPooledObject; import org.apache.commons.pool2.impl.GenericObjectPool; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; public class GrpcConnectionPool { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(GrpcConnectionPool.class); static private GrpcConnectionPool pool = new GrpcConnectionPool(); ConcurrentHashMap> poolMap = new ConcurrentHashMap>(); private Integer maxTotal = 64; @@ -52,7 +52,7 @@ public void returnPool(ManagedChannel channel, String address) { poolMap.get(address).returnObject(channel); } catch (Exception e) { - LOGGER.error("return to pool error", e); + logger.error("return to pool error", e); } } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GuestInferenceLoggerPrinter.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GuestInferenceLoggerPrinter.java index 6cc866c1..41197b41 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GuestInferenceLoggerPrinter.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GuestInferenceLoggerPrinter.java @@ -17,20 +17,20 @@ package com.webank.ai.fate.serving.core.bean; import com.webank.ai.fate.serving.core.utils.GetSystemInfo; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class GuestInferenceLoggerPrinter implements LoggerPrinter { - static final String LOGGER_NAME = "flow"; + static final String logger_NAME = "flow"; - private static final Logger LOGGER = LogManager.getLogger(LOGGER_NAME); + private static final Logger logger = LoggerFactory.getLogger(logger_NAME); @Override public void printLog(Context context, Request req, ReturnResult resp) { - LOGGER.info("{}|{}|{}|{}|{}|{}|{}|{}", GetSystemInfo.getLocalIp(), context.getSeqNo(), req != null ? ((Request) req).getCaseid() : Dict.NONE, context.getActionType(), context.getCostTime(), + logger.info("{}|{}|{}|{}|{}|{}|{}|{}", GetSystemInfo.getLocalIp(), context.getSeqNo(), req != null ? ((Request) req).getCaseid() : Dict.NONE, context.getActionType(), context.getCostTime(), resp != null ? resp.getRetcode() : Dict.NONE, req, resp); } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/HostInferenceLoggerPrinter.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/HostInferenceLoggerPrinter.java index cee6a7dd..c8ccaa02 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/HostInferenceLoggerPrinter.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/HostInferenceLoggerPrinter.java @@ -18,22 +18,22 @@ import com.webank.ai.fate.serving.core.utils.GetSystemInfo; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Map; public class HostInferenceLoggerPrinter implements LoggerPrinter { - static final String LOGGER_NAME = "flow"; + static final String logger_NAME = "flow"; - private static final Logger LOGGER = LogManager.getLogger(LOGGER_NAME); + private static final Logger logger = LoggerFactory.getLogger(logger_NAME); @Override public void printLog(Context context, Map req, ReturnResult resp) { - LOGGER.info("{}|{}|{}|{}|{}|{}|{}|{}", + logger.info("{}|{}|{}|{}|{}|{}|{}|{}", GetSystemInfo.getLocalIp(), context.getSeqNo(), req != null ? ((Map) req).get(Dict.CASEID) : Dict.NONE, context.getActionType(), context.getCostTime(), resp != null ? resp.getRetcode() : Dict.NONE, req, resp ); diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/ReturnResult.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/ReturnResult.java index 95c81c1d..056a26ee 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/ReturnResult.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/ReturnResult.java @@ -16,17 +16,14 @@ package com.webank.ai.fate.serving.core.bean; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import java.net.URL; import java.util.HashMap; import java.util.Map; -import static org.apache.logging.log4j.message.MapMessage.MapFormat.JSON; - public class ReturnResult { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(ReturnResult.class); private int retcode; private String retmsg = ""; private String caseid = ""; diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java index b608cf4e..9752c957 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java @@ -16,34 +16,32 @@ package com.webank.ai.fate.serving.core.manager; -import com.google.common.base.Charsets; +import com.alibaba.fastjson.JSON; import com.google.common.base.Preconditions; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.collect.Maps; -import com.google.common.hash.Hashing; -import com.alibaba.fastjson.JSON; -//import com.webank.ai.fate.core.utils.ObjectTransform; import com.webank.ai.fate.serving.core.bean.*; import org.apache.commons.codec.digest.Md5Crypt; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.stereotype.Service; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.Pipeline; -import com.webank.ai.fate.serving.core.bean.CacheManager; import java.util.*; import java.util.concurrent.TimeUnit; +//import com.webank.ai.fate.core.utils.ObjectTransform; + @Service public class DefaultCacheManager implements CacheManager, InitializingBean { - private final Logger LOGGER = LogManager.getLogger(); + private final Logger logger = LoggerFactory.getLogger(DefaultCacheManager.class); private JedisPool jedisPool; private Cache inferenceResultCache; private Cache remoteModelInferenceResultCache; @@ -102,7 +100,7 @@ public void afterPropertiesSet() throws Exception { @Override public void store(Context context, String key, Object object) { - LOGGER.info("store key {} value {}", key, object); + logger.info("store key {} value {}", key, object); CacheValueConfig cacheValueConfig = getCacheValueConfig(key, CacheType.PROCESS_DATA); putIntoRedisCache(key, cacheValueConfig, object); @@ -114,7 +112,7 @@ public T restore(Context context, String key, Class dataType) { CacheValueConfig cacheValueConfig = getCacheValueConfig(key, CacheType.PROCESS_DATA); T result = getFromRedisCache(key, cacheValueConfig, dataType); - LOGGER.info("restore key {} value {}", key, result); + logger.info("restore key {} value {}", key, result); return result; } @@ -127,11 +125,11 @@ public void putInferenceResultCache(Context context, String partyId, String case String inferenceResultCacheKey = generateInferenceResultCacheKey(partyId, caseid); boolean putCacheSuccess = putIntoCache(inferenceResultCacheKey, CacheType.INFERENCE_RESULT, returnResult); if (putCacheSuccess) { - LOGGER.info("Put {} inference result into cache", inferenceResultCacheKey); + logger.info("Put {} inference result into cache", inferenceResultCacheKey); } } finally { long end = System.currentTimeMillis(); - LOGGER.info("caseid {} putInferenceResultCache cost {}", context.getCaseId(), end - beginTime); + logger.info("caseid {} putInferenceResultCache cost {}", context.getCaseId(), end - beginTime); } } @@ -141,7 +139,7 @@ public ReturnResult getInferenceResultCache(String partyId, String caseid) { String inferenceResultCacheKey = generateInferenceResultCacheKey(partyId, caseid); ReturnResult returnResult = getFromCache(inferenceResultCacheKey, CacheType.INFERENCE_RESULT); if (returnResult != null) { - LOGGER.info("Get {} inference result from cache.", inferenceResultCacheKey); + logger.info("Get {} inference result from cache.", inferenceResultCacheKey); } return returnResult; } @@ -154,7 +152,7 @@ public void putRemoteModelInferenceResult(FederatedParams guestFederatedParams, String remoteModelInferenceResultCacheKey = generateRemoteModelInferenceResultCacheKey(guestFederatedParams); boolean putCacheSuccess = putIntoCache(remoteModelInferenceResultCacheKey, CacheType.REMOTE_MODEL_INFERENCE_RESULT, returnResult); if (putCacheSuccess) { - LOGGER.info("put {} remote model inference result into cache", remoteModelInferenceResultCacheKey); + logger.info("put {} remote model inference result into cache", remoteModelInferenceResultCacheKey); } } @@ -167,7 +165,7 @@ public ReturnResult getRemoteModelInferenceResult(FederatedParams guestFederated String remoteModelInferenceResultCacheKey = generateRemoteModelInferenceResultCacheKey(guestFederatedParams); ReturnResult returnResult = getFromCache(remoteModelInferenceResultCacheKey, CacheType.REMOTE_MODEL_INFERENCE_RESULT); if (returnResult != null) { - LOGGER.info("get {} remote model inference result from cache", remoteModelInferenceResultCacheKey); + logger.info("get {} remote model inference result from cache", remoteModelInferenceResultCacheKey); } return returnResult; } @@ -305,7 +303,7 @@ private String generateRemoteModelInferenceResultCacheKey(FederatedParty remoteP String featureIdString = StringUtils.join(featureIdItemString, "_"); String cacheKey; cacheKey = StringUtils.join(Arrays.asList(remotePartyKey, featureIdString), "#"); - LOGGER.info(cacheKey); + logger.info(cacheKey); return cacheKey; } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/GetSystemInfo.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/GetSystemInfo.java index 10572133..39ab8d12 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/GetSystemInfo.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/GetSystemInfo.java @@ -18,8 +18,8 @@ import com.sun.management.OperatingSystemMXBean; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.lang.management.ManagementFactory; import java.net.Inet4Address; @@ -30,7 +30,7 @@ public class GetSystemInfo { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(GetSystemInfo.class); public static String localIp; @@ -62,7 +62,7 @@ public static String getLocalIp() { } } } catch (Throwable e) { - LOGGER.error(e.getMessage(), e); + logger.error(e.getMessage(), e); } return ""; } @@ -84,7 +84,7 @@ private static String getIpByEthNum(String ethNum) { } } } catch (SocketException e) { - LOGGER.error(e.getMessage(), e); + logger.error(e.getMessage(), e); } return ""; } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/ObjectTransform.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/ObjectTransform.java new file mode 100644 index 00000000..13a78644 --- /dev/null +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/ObjectTransform.java @@ -0,0 +1,51 @@ +/* + * Copyright 2019 The FATE Authors. All Rights Reserved. + * + * 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.webank.ai.fate.serving.core.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; + +public class ObjectTransform { + + public ObjectTransform() { + } + + public static String bean2Json(Object object) { + if (object == null) { + return ""; + } else { + try { + return (new ObjectMapper()).writeValueAsString(object); + } catch (JsonProcessingException var2) { + return ""; + } + } + } + + public static Object json2Bean(String json, Class objectType) { + if (StringUtils.isEmpty(json)) { + return null; + } else { + try { + return (new ObjectMapper()).readValue(json, objectType); + } catch (Exception var3) { + return null; + } + } + } +} diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/ProtobufUtils.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/ProtobufUtils.java index 1054b6c1..32c5631a 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/ProtobufUtils.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/ProtobufUtils.java @@ -17,23 +17,23 @@ package com.webank.ai.fate.serving.core.utils; import com.webank.ai.fate.core.mlmodel.buffer.DefaultEmptyFillProto; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ProtobufUtils { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(ProtobufUtils.class); public static T parseProtoObject(com.google.protobuf.Parser protoParser, byte[] protoString) throws com.google.protobuf.InvalidProtocolBufferException { T messageV3; try { messageV3 = protoParser.parseFrom(protoString); - LOGGER.info("parse {} proto object normal", messageV3.getClass().getSimpleName()); + logger.info("parse {} proto object normal", messageV3.getClass().getSimpleName()); return messageV3; } catch (Exception ex1) { try { DefaultEmptyFillProto.DefaultEmptyFillMessage defaultEmptyFillMessage = DefaultEmptyFillProto.DefaultEmptyFillMessage.parseFrom(protoString); messageV3 = protoParser.parseFrom(new byte[0]); - LOGGER.info("parse {} proto object with default values", messageV3.getClass().getSimpleName()); + logger.info("parse {} proto object with default values", messageV3.getClass().getSimpleName()); return messageV3; } catch (Exception ex2) { throw ex1; diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/DSLParser.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/DSLParser.java index 4875f2b2..63c60405 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/DSLParser.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/DSLParser.java @@ -19,15 +19,15 @@ import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.core.bean.StatusCode; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.json.JSONArray; import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.*; public class DSLParser { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(DSLParser.class); private HashMap componentModuleMap = new HashMap(); private HashMap componentIds = new HashMap(); private HashMap> downStream = new HashMap>(); @@ -36,27 +36,27 @@ public class DSLParser { private String modelPackage = "com.webank.ai.fate.serving.federatedml.model"; public int parseDagFromDSL(String jsonStr) { - LOGGER.info("start parse dag from dsl"); + logger.info("start parse dag from dsl"); try { JSONObject dsl = new JSONObject(jsonStr); JSONObject components = dsl.getJSONObject(Dict.DSL_COMPONENTS); - LOGGER.info("start topo sort"); + logger.info("start topo sort"); topoSort(components, this.topoRankComponent); - LOGGER.info("components size is {}", this.topoRankComponent.size()); + logger.info("components size is {}", this.topoRankComponent.size()); for (int i = 0; i < this.topoRankComponent.size(); ++i) { this.componentIds.put(this.topoRankComponent.get(i), i); } for (int i = 0; i < topoRankComponent.size(); ++i) { String componentName = topoRankComponent.get(i); - LOGGER.info("component is {}", componentName); + logger.info("component is {}", componentName); JSONObject component = components.getJSONObject(componentName); String[] codePath = ((String) component.get(Dict.DSL_CODE_PATH)).split("/", -1); - LOGGER.info("code path splits is {}", codePath); + logger.info("code path splits is {}", codePath); String module = codePath[codePath.length - 1]; - LOGGER.info("module is {}", module); + logger.info("module is {}", module); componentModuleMap.put(componentName, module); JSONObject upData = component.getJSONObject(Dict.DSL_INPUT).getJSONObject(Dict.DSL_DATA); @@ -85,9 +85,9 @@ public int parseDagFromDSL(String jsonStr) { } catch (Exception ex) { ex.printStackTrace(); - LOGGER.info("DSLParser init catch error:{}", ex); + logger.info("DSLParser init catch error:{}", ex); } - LOGGER.info("Finish init DSLParser"); + logger.info("Finish init DSLParser"); return StatusCode.OK; } @@ -137,7 +137,7 @@ public void topoSort(JSONObject components, ArrayList topoRankComponent) } } - LOGGER.info("end of construct edges"); + logger.info("end of construct edges"); for (int i = 0; i < index; i++) { if (inDegree[i] == 0) { stk.push(i); @@ -160,7 +160,7 @@ public void topoSort(JSONObject components, ArrayList topoRankComponent) } } - LOGGER.info("end of topo"); + logger.info("end of topo"); } public HashMap getComponentModuleMap() { diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java index 4742c6d0..420ff3da 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java @@ -22,15 +22,15 @@ import com.webank.ai.fate.core.mlmodel.buffer.PipelineProto; import com.webank.ai.fate.serving.core.bean.*; import com.webank.ai.fate.serving.federatedml.model.BaseModel; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.*; import static com.webank.ai.fate.serving.core.bean.Dict.PIPLELINE_IN_MODEL; public class PipelineTask { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(PipelineTask.class); private List pipeLineNode = new ArrayList<>(); private Map modelMap = new HashMap(); private DSLParser dslParser = new DSLParser(); @@ -39,10 +39,10 @@ public BaseModel getModelByComponentName(String name) { return this.modelMap.get(name); } public int initModel(Map modelProtoMap) { - LOGGER.info("start init pipeline,model components {}",modelProtoMap.keySet()); + logger.info("start init pipeline,model components {}",modelProtoMap.keySet()); try { Map newModelProtoMap = changeModelProto(modelProtoMap); - LOGGER.info("after parse pipeline {}",newModelProtoMap.keySet()); + logger.info("after parse pipeline {}",newModelProtoMap.keySet()); Preconditions.checkArgument(newModelProtoMap.get(PIPLELINE_IN_MODEL)!=null); PipelineProto.Pipeline pipeLineProto = PipelineProto.Pipeline.parseFrom(newModelProtoMap.get(PIPLELINE_IN_MODEL)); String dsl = pipeLineProto.getInferenceDsl().toStringUtf8(); //inference_dsl; @@ -53,7 +53,7 @@ public int initModel(Map modelProtoMap) { for (int i = 0; i < components.size(); ++i) { String componentName = components.get(i); String className = componentModuleMap.get(componentName); - LOGGER.info("try to get class:{}", className); + logger.info("try to get class:{}", className); try { Class modelClass = Class.forName(this.modelPackage + "." + className); BaseModel mlNode = (BaseModel) modelClass.getConstructor().newInstance(); @@ -63,30 +63,30 @@ public int initModel(Map modelProtoMap) { mlNode.initModel(protoMeta, protoParam); modelMap.put(componentName, mlNode); pipeLineNode.add(mlNode); - LOGGER.info(" Add class {} to pipeline task list", className); + logger.info(" Add class {} to pipeline task list", className); } catch (Exception ex) { pipeLineNode.add(null); - LOGGER.warn("Can not instance {} class", className); + logger.warn("Can not instance {} class", className); } } } catch (Exception ex) { ex.printStackTrace(); - LOGGER.info("initModel error:{}", ex); + logger.info("initModel error:{}", ex); throw new RuntimeException("initModel error"); } - LOGGER.info("Finish init Pipeline"); + logger.info("Finish init Pipeline"); return StatusCode.OK; } public Map predict(Context context, Map inputData, FederatedParams predictParams) { - LOGGER.info("Start Pipeline predict use {} model node.", this.pipeLineNode.size()); + logger.info("Start Pipeline predict use {} model node.", this.pipeLineNode.size()); List> outputData = new ArrayList<>(); for (int i = 0; i < this.pipeLineNode.size(); i++) { if (this.pipeLineNode.get(i) != null) { - LOGGER.info("component class is {}", this.pipeLineNode.get(i).getClass().getName()); + logger.info("component class is {}", this.pipeLineNode.get(i).getClass().getName()); } else { - LOGGER.info("component class is {}", this.pipeLineNode.get(i)); + logger.info("component class is {}", this.pipeLineNode.get(i)); } List> inputs = new ArrayList<>(); HashSet upInputComponents = this.dslParser.getUpInputComponents(i); @@ -115,7 +115,7 @@ public Map predict(Context context, Map inputDat inputData.put(Dict.RET_CODE, federatedResult.getRetcode()); } - LOGGER.info("Finish Pipeline predict"); + logger.info("Finish Pipeline predict"); if(outputData.size()>0){ return outputData.get(outputData.size() - 1); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java index 6f9e1642..ca5685dd 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java @@ -19,29 +19,25 @@ import com.alibaba.fastjson.JSON; import com.google.common.base.Preconditions; import com.google.protobuf.ByteString; -import com.webank.ai.eggroll.core.utils.ObjectTransform; import com.webank.ai.fate.api.networking.proxy.DataTransferServiceGrpc; import com.webank.ai.fate.api.networking.proxy.Proxy; - import com.webank.ai.fate.register.common.Constants; import com.webank.ai.fate.register.router.RouterService; import com.webank.ai.fate.register.url.URL; import com.webank.ai.fate.serving.core.bean.*; +import com.webank.ai.fate.serving.core.utils.ObjectTransform; import com.webank.ai.fate.serving.core.utils.ProtobufUtils; import io.grpc.ManagedChannel; -import io.grpc.netty.shaded.io.grpc.netty.NegotiationType; -import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.List; import java.util.Map; -import java.util.concurrent.TimeUnit; public abstract class BaseModel implements Predictor>, FederatedParams, Map> { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(BaseModel.class); public static RouterService routerService; protected String componentName; @@ -73,7 +69,7 @@ public Map predict(Context context, List> in long cost = endTime - beginTime; String caseId = context.getCaseId(); String className = this.getClass().getSimpleName(); - LOGGER.info("model {} caseid {} predict cost time {}", className, caseId, cost); + logger.info("model {} caseid {} predict cost time {}", className, caseId, cost); } @@ -107,7 +103,7 @@ protected ReturnResult getFederatedPredict(Context context, FederatedParams gues if (useCache) { ReturnResult remoteResultFromCache = CacheManager.getInstance().getRemoteModelInferenceResult(guestFederatedParams); if (remoteResultFromCache != null) { - LOGGER.info("caseid {} get remote party model inference result from cache", context.getCaseId()); + logger.info("caseid {} get remote party model inference result from cache", context.getCaseId()); context.putData(Dict.GET_REMOTE_PARTY_RESULT, false); context.hitCache(true); remoteResult = remoteResultFromCache; @@ -128,7 +124,7 @@ protected ReturnResult getFederatedPredict(Context context, FederatedParams gues remoteResult = getFederatedPredictFromRemote(context, srcParty, dstParty, hostFederatedParams, remoteMethodName); if (useCache&& remoteResult!=null&&remoteResult.getRetcode()==0) { CacheManager.getInstance().putRemoteModelInferenceResult(guestFederatedParams, remoteResult); - LOGGER.info("caseid {} get remote party model inference result from federated request.", context.getCaseId()); + logger.info("caseid {} get remote party model inference result from federated request.", context.getCaseId()); } return remoteResult; } finally { @@ -203,18 +199,18 @@ protected ReturnResult getFederatedPredictFromRemote(Context context, FederatedP return remoteResult; } catch (Exception e) { - LOGGER.error("getFederatedPredictFromRemote error", e); + logger.error("getFederatedPredictFromRemote error", e); throw new RuntimeException(e); } finally { long end = System.currentTimeMillis(); long cost = end - beginTime; - LOGGER.info("caseid {} getFederatedPredictFromRemote cost {} remote retcode {}", context.getCaseId(), cost, remoteResult != null ? remoteResult.getRetcode() : Dict.NONE); + logger.info("caseid {} getFederatedPredictFromRemote cost {} remote retcode {}", context.getCaseId(), cost, remoteResult != null ? remoteResult.getRetcode() : Dict.NONE); } } - public static void main(String[] args){ + /*public static void main(String[] args){ NettyChannelBuilder builder = NettyChannelBuilder @@ -234,13 +230,12 @@ public static void main(String[] args){ builder.negotiationType(NegotiationType.PLAINTEXT) .usePlaintext(); Proxy.Packet.Builder packetBuilder =Proxy.Packet.newBuilder(); - DataTransferServiceGrpc.DataTransferServiceBlockingStub stub1 = DataTransferServiceGrpc.newBlockingStub(builder.build()); - Proxy.Packet packet = stub1.unaryCall(packetBuilder.build()); - ReturnResult remoteResult = (ReturnResult) ObjectTransform.json2Bean(packet.getBody().getValue().toStringUtf8(), ReturnResult.class); - + DataTransferServiceGrpc.DataTransferServiceBlockingStub stub1 = DataTransferServiceGrpc.newBlockingStub(builder.build()); + Proxy.Packet packet = stub1.unaryCall(packetBuilder.build()); + ReturnResult remoteResult = (ReturnResult) ObjectTransform.json2Bean(packet.getBody().getValue().toStringUtf8(), ReturnResult.class); - } + }*/ diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/DataIO.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/DataIO.java index 194c14ae..ed5d7854 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/DataIO.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/DataIO.java @@ -22,14 +22,14 @@ import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.bean.FederatedParams; import com.webank.ai.fate.serving.core.bean.StatusCode; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.List; import java.util.Map; public class DataIO extends BaseModel { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(DataIO.class); private DataIOMeta dataIOMeta; private DataIOParam dataIOParam; private Imputer imputer; @@ -39,19 +39,19 @@ public class DataIO extends BaseModel { @Override public int initModel(byte[] protoMeta, byte[] protoParam) { - LOGGER.info("start init DataIO class"); + logger.info("start init DataIO class"); try { this.dataIOMeta = this.parseModel(DataIOMeta.parser(), protoMeta); this.dataIOParam = this.parseModel(DataIOParam.parser(), protoParam); this.isImputer = this.dataIOMeta.getImputerMeta().getIsImputer(); - LOGGER.info("data io isImputer {}", this.isImputer); + logger.info("data io isImputer {}", this.isImputer); if (this.isImputer) { this.imputer = new Imputer(this.dataIOMeta.getImputerMeta().getMissingValueList(), this.dataIOParam.getImputerParam().getMissingReplaceValue()); } this.isOutlier = this.dataIOMeta.getOutlierMeta().getIsOutlier(); - LOGGER.info("data io isOutlier {}", this.isOutlier); + logger.info("data io isOutlier {}", this.isOutlier); if (this.isOutlier) { this.outlier = new Outlier(this.dataIOMeta.getOutlierMeta().getOutlierValueList(), this.dataIOParam.getOutlierParam().getOutlierReplaceValue()); @@ -60,7 +60,7 @@ public int initModel(byte[] protoMeta, byte[] protoParam) { ex.printStackTrace(); return StatusCode.ILLEGALDATA; } - LOGGER.info("Finish init DataIO class"); + logger.info("Finish init DataIO class"); return StatusCode.OK; } diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/FeatureSelection.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/FeatureSelection.java index 1e345cd6..cc10108d 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/FeatureSelection.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/FeatureSelection.java @@ -23,8 +23,8 @@ import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.bean.FederatedParams; import com.webank.ai.fate.serving.core.bean.StatusCode; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.List; @@ -34,7 +34,7 @@ public class FeatureSelection extends BaseModel { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(FeatureSelection.class); private FeatureSelectionParam featureSelectionParam; private FeatureSelectionMeta featureSelectionMeta; private LeftCols finalLeftCols; @@ -42,7 +42,7 @@ public class FeatureSelection extends BaseModel { @Override public int initModel(byte[] protoMeta, byte[] protoParam) { - LOGGER.info("start init Feature Selection class"); + logger.info("start init Feature Selection class"); this.needRun = false; try { this.featureSelectionMeta = this.parseModel(FeatureSelectionMeta.parser(), protoMeta); @@ -53,13 +53,13 @@ public int initModel(byte[] protoMeta, byte[] protoParam) { ex.printStackTrace(); return StatusCode.ILLEGALDATA; } - LOGGER.info("Finish init Feature Selection class"); + logger.info("Finish init Feature Selection class"); return StatusCode.OK; } @Override public Map handlePredict(Context context, List> inputData, FederatedParams predictParams) { - LOGGER.info("Start Feature Selection predict"); + logger.info("Start Feature Selection predict"); HashMap outputData = new HashMap<>(8); Map firstData = inputData.get(0); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFeatureBinning.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFeatureBinning.java index 0f538cbf..1f7283ed 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFeatureBinning.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFeatureBinning.java @@ -1,18 +1,16 @@ package com.webank.ai.fate.serving.federatedml.model; -import com.webank.ai.fate.core.mlmodel.buffer.FeatureBinningMetaProto; import com.webank.ai.fate.core.mlmodel.buffer.FeatureBinningMetaProto.FeatureBinningMeta; import com.webank.ai.fate.core.mlmodel.buffer.FeatureBinningMetaProto.TransformMeta; -import com.webank.ai.fate.core.mlmodel.buffer.FeatureBinningParamProto; import com.webank.ai.fate.core.mlmodel.buffer.FeatureBinningParamProto.FeatureBinningParam; import com.webank.ai.fate.core.mlmodel.buffer.FeatureBinningParamProto.FeatureBinningResult; import com.webank.ai.fate.core.mlmodel.buffer.FeatureBinningParamProto.IVParam; import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.bean.FederatedParams; import com.webank.ai.fate.serving.core.bean.StatusCode; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.List; @@ -20,7 +18,7 @@ public class HeteroFeatureBinning extends BaseModel { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(HeteroFeatureBinning.class); private Map> splitPoints; private List transformCols; private List header; @@ -28,7 +26,7 @@ public class HeteroFeatureBinning extends BaseModel { @Override public int initModel(byte[] protoMeta, byte[] protoParam) { - LOGGER.info("start init Feature Binning class"); + logger.info("start init Feature Binning class"); this.needRun = false; this.splitPoints = new HashMap<>(8); @@ -51,13 +49,13 @@ public int initModel(byte[] protoMeta, byte[] protoParam) { ex.printStackTrace(); return StatusCode.ILLEGALDATA; } - LOGGER.info("Finish init Feature Binning class"); + logger.info("Finish init Feature Binning class"); return StatusCode.OK; } @Override public Map handlePredict(Context context, List> inputData, FederatedParams predictParams) { - LOGGER.info("Start Feature Binning predict"); + logger.info("Start Feature Binning predict"); HashMap outputData = new HashMap<>(8); Map firstData = inputData.get(0); if (!this.needRun) { @@ -85,11 +83,11 @@ public Map handlePredict(Context context, List weight; private Double intercept; @Override public int initModel(byte[] protoMeta, byte[] protoParam) { - LOGGER.info("start init HeteroLR class"); + logger.info("start init HeteroLR class"); try { LRModelParam lrModelParam = this.parseModel(LRModelParam.parser(), protoParam); @@ -46,7 +46,7 @@ public int initModel(byte[] protoMeta, byte[] protoParam) { ex.printStackTrace(); return StatusCode.ILLEGALDATA; } - LOGGER.info("Finish init HeteroLR class, model weight is {}", this.weight); + logger.info("Finish init HeteroLR class, model weight is {}", this.weight); return StatusCode.OK; } @@ -57,8 +57,8 @@ Map forward(List> inputDatas) { int inputDataHitCount = 0; int weightNum = this.weight.size(); int inputFeaturesNum = inputData.size(); - LOGGER.info("model weight number:{}", weightNum); - LOGGER.info("input data features number:{}", inputFeaturesNum); + logger.info("model weight number:{}", weightNum); + logger.info("input data features number:{}", inputFeaturesNum); double score = 0; for (String key : inputData.keySet()) { @@ -68,7 +68,7 @@ Map forward(List> inputDatas) { score += w * x; modelWeightHitCount += 1; inputDataHitCount += 1; - LOGGER.info("key {} weight is {}, value is {}", key, this.weight.get(key), inputData.get(key)); + logger.info("key {} weight is {}, value is {}", key, this.weight.get(key), inputData.get(key)); } } score += this.intercept; @@ -82,8 +82,8 @@ Map forward(List> inputDatas) { ex.printStackTrace(); } - LOGGER.info("model weight hit rate:{}", modelWeightHitRate); - LOGGER.info("input data features hit rate:{}", inputDataHitRate); + logger.info("model weight hit rate:{}", modelWeightHitRate); + logger.info("input data features hit rate:{}", inputDataHitRate); Map ret = new HashMap<>(8); ret.put(Dict.SCORE, score); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java index a2bcff43..cd08d0d4 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java @@ -22,8 +22,8 @@ import com.webank.ai.fate.serving.core.bean.FederatedParams; import com.webank.ai.fate.serving.core.bean.ReturnResult; import com.webank.ai.fate.serving.core.constant.InferenceRetCode; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.List; @@ -32,7 +32,7 @@ import static java.lang.Math.exp; public class HeteroLRGuest extends HeteroLR { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(HeteroLRGuest.class); private double sigmod(double x) { return 1. / (1. + exp(-x)); @@ -43,26 +43,26 @@ public Map handlePredict(Context context, List result = new HashMap<>(8); Map forwardRet = forward(inputData); double score = forwardRet.get(Dict.SCORE); - LOGGER.info("caseid {} guest score:{}", context.getCaseId(),score); + logger.info("caseid {} guest score:{}", context.getCaseId(),score); try { ReturnResult hostPredictResponse = this.getFederatedPredict(context, predictParams, Dict.FEDERATED_INFERENCE, true); if(hostPredictResponse !=null) { result.put(Dict.RET_CODE,hostPredictResponse.getRetcode()); - LOGGER.info("caseid {} host response is {}",context.getCaseId(),hostPredictResponse.getData()); + logger.info("caseid {} host response is {}",context.getCaseId(),hostPredictResponse.getData()); if (hostPredictResponse.getData() != null && hostPredictResponse.getData().get(Dict.SCORE) != null) { double hostScore = ((Number) hostPredictResponse.getData().get(Dict.SCORE)).doubleValue(); - LOGGER.info("caseid {} host score:{}",context.getCaseId(), hostScore); + logger.info("caseid {} host score:{}",context.getCaseId(), hostScore); score += hostScore; } }else{ - LOGGER.info("caseid {} host response is null",context.getCaseId()); + logger.info("caseid {} host response is null",context.getCaseId()); } } catch (io.grpc.StatusRuntimeException ex) { - LOGGER.error("get host predict failed:", ex); + logger.error("get host predict failed:", ex); result.put(Dict.RET_CODE, InferenceRetCode.NETWORK_ERROR); } catch(Exception ex){ - LOGGER.error("get host predict failed:", ex); + logger.error("get host predict failed:", ex); result.put(Dict.RET_CODE,InferenceRetCode.SYSTEM_ERROR); } double prob = sigmod(score); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRHost.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRHost.java index e678f8b1..0fe42667 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRHost.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRHost.java @@ -19,27 +19,27 @@ import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.core.bean.FederatedParams; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.List; import java.util.Map; public class HeteroLRHost extends HeteroLR { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(HeteroLRHost.class); @Override public Map handlePredict(Context context, List> inputData, FederatedParams predictParams) { - LOGGER.info("hetero lr host begin to predict"); + logger.info("hetero lr host begin to predict"); HashMap result = new HashMap<>(8); Map ret = forward(inputData); result.put(Dict.SCORE, ret.get(Dict.SCORE)); - LOGGER.info("hetero lr host predict ends, result is {}", result); + logger.info("hetero lr host predict ends, result is {}", result); return result; } diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoost.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoost.java index e4a727cd..d71b3d5a 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoost.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoost.java @@ -17,21 +17,20 @@ package com.webank.ai.fate.serving.federatedml.model; import com.google.common.collect.Maps; - import com.webank.ai.fate.core.mlmodel.buffer.BoostTreeModelParamMeta.BoostingTreeModelMeta; import com.webank.ai.fate.core.mlmodel.buffer.BoostTreeModelParamProto.BoostingTreeModelParam; import com.webank.ai.fate.core.mlmodel.buffer.BoostTreeModelParamProto.DecisionTreeModelParam; import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.bean.FederatedParams; import com.webank.ai.fate.serving.core.bean.StatusCode; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.List; import java.util.Map; public abstract class HeteroSecureBoost extends BaseModel { - public static final Logger LOGGER = LogManager.getLogger(); + public static final Logger logger = LoggerFactory.getLogger(HeteroSecureBoost.class); protected List> split_maskdict; protected Map featureNameFidMapping = Maps.newHashMap(); protected int treeNum; @@ -44,7 +43,7 @@ public abstract class HeteroSecureBoost extends BaseModel { @Override public int initModel(byte[] protoMeta, byte[] protoParam) { - LOGGER.info("start init HeteroLR class"); + logger.info("start init HeteroLR class"); try { BoostingTreeModelParam param = this.parseModel(BoostingTreeModelParam.parser(), protoParam); @@ -72,7 +71,7 @@ public int initModel(byte[] protoMeta, byte[] protoParam) { ex.printStackTrace(); return StatusCode.ILLEGALDATA; } - LOGGER.info("Finish init HeteroSecureBoost class"); + logger.info("Finish init HeteroSecureBoost class"); return StatusCode.OK; } diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeGuest.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeGuest.java index e49130ff..19242269 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeGuest.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeGuest.java @@ -76,7 +76,7 @@ Map forward(List> inputDatas) { ++featureHit; } } - LOGGER.info("feature hit rate : {}", 1.0 * featureHit / this.featureNameFidMapping.size()); + logger.info("feature hit rate : {}", 1.0 * featureHit / this.featureNameFidMapping.size()); } */ @@ -139,7 +139,7 @@ private Map getFinalPredict(double[] weights) { @Override public Map handlePredict(Context context, List> inputData, FederatedParams predictParams) { - LOGGER.info("HeteroSecureBoostingTreeGuest FederatedParams {}", predictParams); + logger.info("HeteroSecureBoostingTreeGuest FederatedParams {}", predictParams); Map input = inputData.get(0); HashMap fidValueMapping = new HashMap(8); @@ -153,7 +153,7 @@ public Map handlePredict(Context context, List handlePredict(Context context, List afterLocation = tempResult.getData(); - LOGGER.info("after loccation is {}", afterLocation); + logger.info("after loccation is {}", afterLocation); for (String location : afterLocation.keySet()) { treeNodeIds[new Integer(location)] = ((Number) afterLocation.get(location)).intValue(); } if (afterLocation == null) { - LOGGER.info("receive predict result of host is null"); + logger.info("receive predict result of host is null"); throw new Exception("Null Data"); } @@ -208,8 +208,8 @@ public Map handlePredict(Context context, List forward(List> inputDatas) { ++featureHit; } } - LOGGER.info("feature hit rate : {}", 1.0 * featureHit / this.featureNameFidMapping.size()); + logger.info("feature hit rate : {}", 1.0 * featureHit / this.featureNameFidMapping.size()); } */ @@ -72,7 +72,7 @@ public Map getData(Context context, String tag) { @Override public Map handlePredict(Context context, List> inputData, FederatedParams predictParams) { - LOGGER.info("HeteroSecureBoostingTreeHost FederatedParams {}", predictParams); + logger.info("HeteroSecureBoostingTreeHost FederatedParams {}", predictParams); Map input = inputData.get(0); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Imputer.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Imputer.java index e57fd59f..8443e096 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Imputer.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Imputer.java @@ -16,15 +16,15 @@ package com.webank.ai.fate.serving.federatedml.model; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.HashSet; import java.util.List; import java.util.Map; public class Imputer { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(Imputer.class); public HashSet missingValueSet; public Map missingReplaceValues; @@ -35,7 +35,7 @@ public Imputer(List missingValues, Map missingReplaceVal public Map transform(Map inputData) { if(inputData!=null) { - LOGGER.info("start imputer transform task"); + logger.info("start imputer transform task"); for (String key : inputData.keySet()) { if(inputData.get(key)!=null) { String value = inputData.get(key).toString(); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/MinMaxScale.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/MinMaxScale.java index 2d99aa28..f3dd7bdd 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/MinMaxScale.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/MinMaxScale.java @@ -17,16 +17,16 @@ package com.webank.ai.fate.serving.federatedml.model; import com.webank.ai.fate.core.mlmodel.buffer.ScaleParamProto.ColumnScaleParam; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Map; public class MinMaxScale { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(MinMaxScale.class); public Map transform(Map inputData, Map scales) { - LOGGER.info("Start MinMaxScale transform"); + logger.info("Start MinMaxScale transform"); for (String key : inputData.keySet()) { try { if (scales.containsKey(key)) { @@ -39,7 +39,7 @@ else if (value < scale.getColumnLower()) else { double range = scale.getColumnUpper() - scale.getColumnLower(); if (range < 0) { - LOGGER.warn("min_max_scale range may be error, it should be larger than 0, but is {}, set value to 0 ", range); + logger.warn("min_max_scale range may be error, it should be larger than 0, but is {}, set value to 0 ", range); value = 0; } else { if (Math.abs(range - 0) < 1e-6) { @@ -51,7 +51,7 @@ else if (value < scale.getColumnLower()) inputData.put(key, value); } else { - LOGGER.warn("feature {} is not in scale, maybe missing or do not need to be scaled", key); + logger.warn("feature {} is not in scale, maybe missing or do not need to be scaled", key); } } catch (Exception ex) { ex.printStackTrace(); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/OneHotEncoder.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/OneHotEncoder.java index 8aefed03..05d05927 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/OneHotEncoder.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/OneHotEncoder.java @@ -18,13 +18,13 @@ import com.webank.ai.fate.core.mlmodel.buffer.OneHotMetaProto.OneHotMeta; -import com.webank.ai.fate.core.mlmodel.buffer.OneHotParamProto.OneHotParam; import com.webank.ai.fate.core.mlmodel.buffer.OneHotParamProto.ColsMap; +import com.webank.ai.fate.core.mlmodel.buffer.OneHotParamProto.OneHotParam; import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.bean.FederatedParams; import com.webank.ai.fate.serving.core.bean.StatusCode; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.List; @@ -32,7 +32,7 @@ import java.util.regex.Pattern; public class OneHotEncoder extends BaseModel { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(OneHotEncoder.class); private List cols; private Map colsMapMap; @@ -42,7 +42,7 @@ public class OneHotEncoder extends BaseModel { @Override public int initModel(byte[] protoMeta, byte[] protoParam) { - LOGGER.info("start init OneHot Encoder class"); + logger.info("start init OneHot Encoder class"); try { OneHotMeta oneHotMeta = this.parseModel(OneHotMeta.parser(), protoMeta); OneHotParam oneHotParam = this.parseModel(OneHotParam.parser(), protoParam); @@ -54,19 +54,19 @@ public int initModel(byte[] protoMeta, byte[] protoParam) { ex.printStackTrace(); return StatusCode.ILLEGALDATA; } - LOGGER.info("Finish init OneHot Encoder class"); + logger.info("Finish init OneHot Encoder class"); return StatusCode.OK; } @Override public Map handlePredict(Context context, List> inputData, FederatedParams predictParams) { - LOGGER.info("Start OneHot Encoder transform"); - LOGGER.info("First time need run"); + logger.info("Start OneHot Encoder transform"); + logger.info("First time need run"); HashMap outputData = new HashMap<>(); Map firstData = inputData.get(0); - LOGGER.info("Need run is ", this.needRun); + logger.info("Need run is ", this.needRun); if (!this.needRun) { - LOGGER.info("Return firstData :", firstData); + logger.info("Return firstData :", firstData); return firstData; } for (String colName : firstData.keySet()) { @@ -92,7 +92,7 @@ public Map handlePredict(Context context, List handlePredict(Context context, List outlierValueSet; public Map outlierReplaceValues; @@ -34,14 +34,14 @@ public Outlier(List outlierValues, Map outlierReplaceVal } public Map transform(Map inputData) { - LOGGER.info("start outlier transform task"); + logger.info("start outlier transform task"); if(inputData!=null) { for (String key : inputData.keySet()) { if(inputData.get(key)!=null) { String value = inputData.get(key).toString(); if (this.outlierValueSet.contains(value.toLowerCase())) { try { - LOGGER.info("value:{}", value); + logger.info("value:{}", value); inputData.put(key, outlierReplaceValues.get(key)); } catch (Exception ex) { ex.printStackTrace(); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Scale.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Scale.java index 14f36f3e..f5373e89 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Scale.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Scale.java @@ -23,21 +23,21 @@ import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.core.bean.FederatedParams; import com.webank.ai.fate.serving.core.bean.StatusCode; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.List; import java.util.Map; public class Scale extends BaseModel { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(Scale.class); private ScaleMeta scaleMeta; private ScaleParam scaleParam; private boolean need_run; @Override public int initModel(byte[] protoMeta, byte[] protoParam) { - LOGGER.info("start init Scale class"); + logger.info("start init Scale class"); try { this.scaleMeta = this.parseModel(ScaleMeta.parser(), protoMeta); this.scaleParam = this.parseModel(ScaleParam.parser(), protoParam); @@ -46,7 +46,7 @@ public int initModel(byte[] protoMeta, byte[] protoParam) { ex.printStackTrace(); return StatusCode.ILLEGALDATA; } - LOGGER.info("Finish init Scale class"); + logger.info("Finish init Scale class"); return StatusCode.OK; } diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/StandardScale.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/StandardScale.java index 54112212..c4d0bfd7 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/StandardScale.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/StandardScale.java @@ -17,16 +17,16 @@ package com.webank.ai.fate.serving.federatedml.model; import com.webank.ai.fate.core.mlmodel.buffer.ScaleParamProto.ColumnScaleParam; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Map; public class StandardScale { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(StandardScale.class); public Map transform(Map inputData, Map standardScalesMap) { - LOGGER.info("Start StandardScale transform"); + logger.info("Start StandardScale transform"); for (String key : inputData.keySet()) { try { if (standardScalesMap.containsKey(key)) { @@ -47,7 +47,7 @@ else if (value < lower) value = (value - standardScale.getMean()) / std; inputData.put(key, value); } else { - LOGGER.warn("feature {} is not in scale, maybe missing or do not need to be scaled"); + logger.warn("feature {} is not in scale, maybe missing or do not need to be scaled"); } } catch (Exception ex) { diff --git a/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistry.java b/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistry.java index fafc4a63..18d74c3a 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistry.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistry.java @@ -25,8 +25,8 @@ import com.webank.ai.fate.register.url.URL; import com.webank.ai.fate.register.url.UrlUtils; import com.webank.ai.fate.register.utils.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.*; import java.nio.channels.FileChannel; @@ -46,7 +46,7 @@ public abstract class AbstractRegistry implements Registry { - private static final Logger logger = LogManager.getLogger(AbstractRegistry.class); + private static final Logger logger = LoggerFactory.getLogger(AbstractRegistry.class); private static final char URL_SEPARATOR = ' '; private static final String URL_SPLIT = "\\s+"; private static final int MAX_RETRY_TIMES_SAVE_PROPERTIES = 3; diff --git a/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistryFactory.java b/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistryFactory.java index cfaffc7f..668ee1d3 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistryFactory.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistryFactory.java @@ -20,8 +20,8 @@ import com.webank.ai.fate.register.interfaces.RegistryService; import com.webank.ai.fate.register.url.URL; import com.webank.ai.fate.register.utils.URLBuilder; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.Collections; @@ -36,7 +36,7 @@ public abstract class AbstractRegistryFactory implements RegistryFactory { // Log output - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(AbstractRegistryFactory.class); // The lock for the acquisition process of the registry @@ -55,8 +55,8 @@ public static Collection getRegistries() { */ public static void destroyAll() { - if (LOGGER.isInfoEnabled()) { - LOGGER.info("Close all registries " + getRegistries()); + if (logger.isInfoEnabled()) { + logger.info("Close all registries " + getRegistries()); } // Lock up the registry shutdown process LOCK.lock(); @@ -65,7 +65,7 @@ public static void destroyAll() { try { registry.destroy(); } catch (Throwable e) { - LOGGER.error(e.getMessage(), e); + logger.error(e.getMessage(), e); } } REGISTRIES.clear(); diff --git a/register/src/main/java/com/webank/ai/fate/register/common/AbstractZookeeperClient.java b/register/src/main/java/com/webank/ai/fate/register/common/AbstractZookeeperClient.java index fe28acac..cfe94db0 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/AbstractZookeeperClient.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/AbstractZookeeperClient.java @@ -19,8 +19,8 @@ import com.webank.ai.fate.register.url.URL; import com.webank.ai.fate.register.zookeeper.ZookeeperClient; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.List; import java.util.Set; @@ -32,7 +32,7 @@ public abstract class AbstractZookeeperClient implements ZookeeperClient { - private static final Logger logger = LogManager.getLogger(AbstractZookeeperClient.class); + private static final Logger logger = LoggerFactory.getLogger(AbstractZookeeperClient.class); private final URL url; diff --git a/register/src/main/java/com/webank/ai/fate/register/common/AbstractZookeeperTransporter.java b/register/src/main/java/com/webank/ai/fate/register/common/AbstractZookeeperTransporter.java index 760749c8..8aa77ae7 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/AbstractZookeeperTransporter.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/AbstractZookeeperTransporter.java @@ -20,8 +20,8 @@ import com.webank.ai.fate.register.url.URL; import com.webank.ai.fate.register.zookeeper.ZookeeperClient; import com.webank.ai.fate.register.zookeeper.ZookeeperTransporter; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -31,7 +31,7 @@ public abstract class AbstractZookeeperTransporter implements ZookeeperTransporter { - private static final Logger logger = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(AbstractZookeeperTransporter.class); private final Map zookeeperClientMap = new ConcurrentHashMap<>(); /** diff --git a/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java b/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java index 3549bf2e..030b1491 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java @@ -28,8 +28,6 @@ import org.apache.curator.framework.state.ConnectionState; import org.apache.curator.framework.state.ConnectionStateListener; import org.apache.curator.retry.RetryNTimes; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException.NoNodeException; import org.apache.zookeeper.KeeperException.NodeExistsException; @@ -39,6 +37,8 @@ import org.apache.zookeeper.data.Id; import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.server.auth.DigestAuthenticationProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.nio.charset.Charset; import java.util.ArrayList; @@ -56,7 +56,7 @@ public class CuratorZookeeperClient extends AbstractZookeeperClient treeCacheMap = new ConcurrentHashMap<>(); diff --git a/register/src/main/java/com/webank/ai/fate/register/common/FailbackRegistry.java b/register/src/main/java/com/webank/ai/fate/register/common/FailbackRegistry.java index 15dd026f..d3213d53 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/FailbackRegistry.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/FailbackRegistry.java @@ -20,8 +20,8 @@ import com.webank.ai.fate.register.task.*; import com.webank.ai.fate.register.url.CollectionUtils; import com.webank.ai.fate.register.url.URL; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -33,7 +33,7 @@ public abstract class FailbackRegistry extends AbstractRegistry { - private static final Logger logger = LogManager.getLogger(FailbackRegistry.class); + private static final Logger logger = LoggerFactory.getLogger(FailbackRegistry.class); private final ConcurrentMap failedRegistered = new ConcurrentHashMap(); diff --git a/register/src/main/java/com/webank/ai/fate/register/common/HashedWheelTimer.java b/register/src/main/java/com/webank/ai/fate/register/common/HashedWheelTimer.java index 8321567c..2d261e7a 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/HashedWheelTimer.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/HashedWheelTimer.java @@ -19,8 +19,8 @@ import com.webank.ai.fate.register.interfaces.Timeout; import com.webank.ai.fate.register.interfaces.Timer; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.*; import java.util.concurrent.*; @@ -36,7 +36,7 @@ public class HashedWheelTimer implements Timer { public static final String NAME = "hased"; - private static final Logger logger = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(HashedWheelTimer.class); private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger(); private static final AtomicBoolean WARNED_TOO_MANY_INSTANCES = new AtomicBoolean(); diff --git a/register/src/main/java/com/webank/ai/fate/register/provider/FateServer.java b/register/src/main/java/com/webank/ai/fate/register/provider/FateServer.java index f4500dd0..92f21421 100644 --- a/register/src/main/java/com/webank/ai/fate/register/provider/FateServer.java +++ b/register/src/main/java/com/webank/ai/fate/register/provider/FateServer.java @@ -19,8 +19,8 @@ import com.webank.ai.fate.register.annotions.RegisterService; import com.webank.ai.fate.register.interfaces.Registry; import io.grpc.Server; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.HashSet; @@ -31,7 +31,7 @@ public class FateServer extends Server { - private static final Logger logger = LogManager.getLogger(FateServer.class); + private static final Logger logger = LoggerFactory.getLogger(FateServer.class); public static Set serviceSets = new HashSet<>(); public String project; Server server; diff --git a/register/src/main/java/com/webank/ai/fate/register/provider/HelloWorldServer.java b/register/src/main/java/com/webank/ai/fate/register/provider/HelloWorldServer.java index f77225bd..40067279 100644 --- a/register/src/main/java/com/webank/ai/fate/register/provider/HelloWorldServer.java +++ b/register/src/main/java/com/webank/ai/fate/register/provider/HelloWorldServer.java @@ -12,8 +12,8 @@ //import io.grpc.Server; //import io.grpc.ServerBuilder; //import io.grpc.stub.StreamObserver; -//import org.apache.logging.log4j.LogManager; -//import org.apache.logging.log4j.Logger; +//import org.slf4j.LoggerFactory; +//import org.slf4j.Logger; // //import java.io.IOException; //import java.util.Map; @@ -21,7 +21,7 @@ // // //public class HelloWorldServer { -// private static final Logger logger = LogManager.getLogger(); +// private static final Logger logger = LoggerFactory.getLogger(); // // /* The port on which the server should run */ // private int port = 50051; diff --git a/register/src/main/java/com/webank/ai/fate/register/router/AbstractRouterService.java b/register/src/main/java/com/webank/ai/fate/register/router/AbstractRouterService.java index 73e30da3..c4d84022 100644 --- a/register/src/main/java/com/webank/ai/fate/register/router/AbstractRouterService.java +++ b/register/src/main/java/com/webank/ai/fate/register/router/AbstractRouterService.java @@ -29,8 +29,8 @@ import com.webank.ai.fate.register.url.CollectionUtils; import com.webank.ai.fate.register.url.URL; import com.webank.ai.fate.register.utils.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.List; @@ -39,7 +39,7 @@ public abstract class AbstractRouterService implements RouterService { protected LoadBalancerFactory loadBalancerFactory = new DefaultLoadBalancerFactory(); protected AbstractRegistry registry; - Logger logger = LogManager.getLogger(); + Logger logger = LoggerFactory.getLogger(AbstractRouterService.class); public Registry getRegistry() { return registry; diff --git a/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java b/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java index f19c81e1..f2009c9a 100644 --- a/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java +++ b/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java @@ -17,7 +17,6 @@ package com.webank.ai.fate.register.router; import com.webank.ai.fate.register.common.Constants; -import com.webank.ai.fate.register.loadbalance.LoadBalanceModel; import com.webank.ai.fate.register.loadbalance.LoadBalancer; import com.webank.ai.fate.register.url.CollectionUtils; import com.webank.ai.fate.register.url.URL; diff --git a/register/src/main/java/com/webank/ai/fate/register/task/AbstractRetryTask.java b/register/src/main/java/com/webank/ai/fate/register/task/AbstractRetryTask.java index 493d4ffa..e51d6886 100644 --- a/register/src/main/java/com/webank/ai/fate/register/task/AbstractRetryTask.java +++ b/register/src/main/java/com/webank/ai/fate/register/task/AbstractRetryTask.java @@ -24,15 +24,15 @@ import com.webank.ai.fate.register.interfaces.Timer; import com.webank.ai.fate.register.url.URL; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.concurrent.TimeUnit; public abstract class AbstractRetryTask implements TimerTask { - public static final Logger logger = LogManager.getLogger(); + public static final Logger logger = LoggerFactory.getLogger(AbstractRetryTask.class); /** * url for retry task */ diff --git a/register/src/main/java/com/webank/ai/fate/register/utils/NetUtils.java b/register/src/main/java/com/webank/ai/fate/register/utils/NetUtils.java index d18a1c7f..490a00d7 100644 --- a/register/src/main/java/com/webank/ai/fate/register/utils/NetUtils.java +++ b/register/src/main/java/com/webank/ai/fate/register/utils/NetUtils.java @@ -17,8 +17,8 @@ package com.webank.ai.fate.register.utils; import com.webank.ai.fate.register.url.URL; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.*; @@ -44,7 +44,7 @@ public static void main(String[] args) { System.out.println(NetUtils.getLocalAddress0("127.0.0.1")); } - private static final Logger logger = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(NetUtils.class); // returned port range is [30000, 39999] private static final int RND_PORT_START = 30000; private static final int RND_PORT_RANGE = 10000; @@ -293,7 +293,7 @@ private static InetAddress getLocalAddress0(String name) { return addressOp.get(); } } catch (Throwable e) { - logger.warn(e); + logger.warn(e.getMessage()); } try { @@ -326,15 +326,15 @@ private static InetAddress getLocalAddress0(String name) { } } } catch (Throwable e) { - logger.warn(e); + logger.warn(e.getMessage()); } } } catch (Throwable e) { - logger.warn(e); + logger.warn(e.getMessage()); } } } catch (Throwable e) { - logger.warn(e); + logger.warn(e.getMessage()); } return localAddress; } diff --git a/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java b/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java index cd79eaf1..97a03aef 100644 --- a/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java +++ b/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java @@ -28,8 +28,8 @@ import com.webank.ai.fate.register.utils.NetUtils; import com.webank.ai.fate.register.utils.StringUtils; import com.webank.ai.fate.register.utils.URLBuilder; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.HashSet; @@ -45,7 +45,7 @@ public class ZookeeperRegistry extends FailbackRegistry { - private static final Logger logger = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(ZookeeperRegistry.class); private final static int DEFAULT_ZOOKEEPER_PORT = 2181; private final static String DEFAULT_ROOT = "FATE-SERVICES"; private final static String ROOT_KEY = "root"; diff --git a/router/src/main/java/com/webank/ai/fate/networking/Proxy.java b/router/src/main/java/com/webank/ai/fate/networking/Proxy.java index b70d59df..5db533bd 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/Proxy.java +++ b/router/src/main/java/com/webank/ai/fate/networking/Proxy.java @@ -22,28 +22,24 @@ import com.webank.ai.fate.networking.proxy.grpc.client.DataTransferPipedClient; import com.webank.ai.fate.networking.proxy.manager.ServerConfManager; import com.webank.ai.fate.networking.proxy.model.ServerConf; -import com.webank.ai.fate.register.loadbalance.LoadBalancer; -import com.webank.ai.fate.register.loadbalance.RandomLoadBalance; import com.webank.ai.fate.register.provider.FateServer; import com.webank.ai.fate.register.router.DefaultRouterService; import com.webank.ai.fate.register.url.URL; import com.webank.ai.fate.register.zookeeper.ZookeeperRegistry; -import com.webank.ai.fate.serving.core.bean.Configuration; import com.webank.ai.fate.serving.core.bean.Dict; import io.grpc.Server; import org.apache.commons.cli.*; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; -import java.lang.management.ManagementFactory; import java.util.Properties; import java.util.Set; public class Proxy { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(Proxy.class); public static ZookeeperRegistry zookeeperRegistry; @@ -75,8 +71,8 @@ public static void main(String[] args) throws Exception { Server server = serverFactory.createServer(confFilePath); ServerConfManager serverConfManager = context.getBean(ServerConfManager.class); ServerConf serverConf = serverConfManager.getServerConf(); - LOGGER.info("Server started listening on port: {}", serverConf.getPort()); - LOGGER.info("server conf: {}", serverConf); + logger.info("Server started listening on port: {}", serverConf.getPort()); + logger.info("server conf: {}", serverConf); server.start(); Properties properties = serverConf.getProperties(); @@ -112,7 +108,7 @@ public static void main(String[] args) throws Exception { Set urls = Sets.newHashSet(); urls.addAll(registered); urls.forEach(url -> { - LOGGER.info("unregister {}", url); + logger.info("unregister {}", url); zookeeperRegistry.unregister(url); }); zookeeperRegistry.destroy(); diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/Main0Insecure.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/Main0Insecure.java index 8bcc471c..f83e0a1b 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/Main0Insecure.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/Main0Insecure.java @@ -27,8 +27,8 @@ import com.webank.ai.fate.networking.proxy.service.FdnRouter; import io.grpc.Server; import io.grpc.ServerBuilder; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -39,7 +39,7 @@ public class Main0Insecure { - private static final Logger LOGGER = LogManager.getLogger(Main0Insecure.class); + private static final Logger logger = LoggerFactory.getLogger(Main0Insecure.class); public static void main(String[] args) throws Exception { @@ -88,7 +88,7 @@ public static void main(String[] args) throws Exception { .build() .start(); - LOGGER.info("Server started listening on port: {}", port); + logger.info("Server started listening on port: {}", port); Runtime.getRuntime().addShutdownHook(new Thread() { diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/Main0Stream.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/Main0Stream.java index 3a6116da..d3e174ca 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/Main0Stream.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/Main0Stream.java @@ -27,8 +27,8 @@ import com.webank.ai.fate.networking.proxy.service.FdnRouter; import io.grpc.Server; import io.grpc.ServerBuilder; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -36,7 +36,7 @@ public class Main0Stream { - private static final Logger LOGGER = LogManager.getLogger(Main0Stream.class); + private static final Logger logger = LoggerFactory.getLogger(Main0Stream.class); public static void main(String[] args) throws Exception { @@ -89,7 +89,7 @@ public static void main(String[] args) throws Exception { .build() .start(); - LOGGER.info("Server started listening on port: {}", port); + logger.info("Server started listening on port: {}", port); Runtime.getRuntime().addShutdownHook(new Thread() { diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/Main1.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/Main1.java index f8bc1c1e..34f4de7a 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/Main1.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/Main1.java @@ -20,13 +20,13 @@ import com.webank.ai.fate.networking.proxy.factory.LocalBeanFactory; import com.webank.ai.fate.networking.proxy.model.ServerConf; import io.grpc.Server; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main1 { - private static final Logger LOGGER = LogManager.getLogger(Main1.class); + private static final Logger logger = LoggerFactory.getLogger(Main1.class); public static void main(String[] args) throws Exception { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-proxy.xml"); @@ -50,11 +50,11 @@ public static void main(String[] args) throws Exception { serverConf.setRouteTablePath("src/main/resources/route_tables/route_table1.json"); serverConf.setPort(port); - LOGGER.info("Server started listening on port: {}", port); + logger.info("Server started listening on port: {}", port); Server server = serverFactory.createServer(serverConf); - LOGGER.info("server conf: {}", serverConf); + logger.info("server conf: {}", serverConf); server.start(); server.awaitTermination(); diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/Main2.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/Main2.java index f69f3785..261e4579 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/Main2.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/Main2.java @@ -20,13 +20,13 @@ import com.webank.ai.fate.networking.proxy.factory.LocalBeanFactory; import com.webank.ai.fate.networking.proxy.model.ServerConf; import io.grpc.Server; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main2 { - private static final Logger LOGGER = LogManager.getLogger(Main2.class); + private static final Logger logger = LoggerFactory.getLogger(Main2.class); public static void main(String[] args) throws Exception { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-proxy.xml"); @@ -48,11 +48,11 @@ public static void main(String[] args) throws Exception { serverConf.setPort(port); serverConf.setCoordinator("webank"); - LOGGER.info("Server started listening on port: {}", port); + logger.info("Server started listening on port: {}", port); Server server = serverFactory.createServer(serverConf); - LOGGER.info("server conf: {}", serverConf); + logger.info("server conf: {}", serverConf); server.start(); server.awaitTermination(); diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/Main3.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/Main3.java index 51383939..5433df03 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/Main3.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/Main3.java @@ -25,8 +25,8 @@ import com.webank.ai.fate.networking.proxy.infra.Pipe; import com.webank.ai.fate.networking.proxy.model.ServerConf; import io.grpc.Server; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -36,7 +36,7 @@ import java.io.OutputStream; public class Main3 { - private static final Logger LOGGER = LogManager.getLogger(Main3.class); + private static final Logger logger = LoggerFactory.getLogger(Main3.class); public static void main(String[] args) throws Exception { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-proxy.xml"); @@ -70,11 +70,11 @@ public static void main(String[] args) throws Exception { ((DefaultPipeFactory) pipeFactory).createInputStreamOutputStreamNoStoragePipe(is, os, header); serverConf.setPipe(pipe); - LOGGER.info("Server started listening on port: {}", port); + logger.info("Server started listening on port: {}", port); Server server = serverFactory.createServer(serverConf); - LOGGER.info("server conf: {}", serverConf); + logger.info("server conf: {}", serverConf); server.start(); server.awaitTermination(); diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/event/listener/PipeHandleNotificationEventListener.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/event/listener/PipeHandleNotificationEventListener.java index 75a563ce..748e2544 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/event/listener/PipeHandleNotificationEventListener.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/event/listener/PipeHandleNotificationEventListener.java @@ -22,8 +22,8 @@ import com.webank.ai.fate.networking.proxy.model.PipeHandlerInfo; import com.webank.ai.fate.networking.proxy.service.CascadedCaller; import com.webank.ai.fate.networking.proxy.util.ToStringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; @@ -33,7 +33,7 @@ @Component @Scope("prototype") public class PipeHandleNotificationEventListener implements ApplicationListener { - private static final Logger LOGGER = LogManager.getLogger(PipeHandleNotificationEventListener.class); + private static final Logger logger = LoggerFactory.getLogger(PipeHandleNotificationEventListener.class); @Autowired private ApplicationContext applicationContext; @Autowired @@ -41,8 +41,8 @@ public class PipeHandleNotificationEventListener implements ApplicationListener< @Override public void onApplicationEvent(PipeHandleNotificationEvent pipeHandleNotificationEvent) { - // LOGGER.warn("event listened: {}", pipeHandleNotificationEvent.getPipeHandlerInfo()); - LOGGER.info("event metadata: {}", toStringUtils.toOneLineString(pipeHandleNotificationEvent.getPipeHandlerInfo().getMetadata())); + // logger.warn("event listened: {}", pipeHandleNotificationEvent.getPipeHandlerInfo()); + logger.info("event metadata: {}", toStringUtils.toOneLineString(pipeHandleNotificationEvent.getPipeHandlerInfo().getMetadata())); PipeHandlerInfo pipeHandlerInfo = pipeHandleNotificationEvent.getPipeHandlerInfo(); Pipe pipe = pipeHandlerInfo.getPipe(); diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcServerFactory.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcServerFactory.java index 4700b41a..e327e7cb 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcServerFactory.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcServerFactory.java @@ -31,10 +31,10 @@ import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext; import io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.config.ConfigurationSource; import org.apache.logging.log4j.core.config.Configurator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.core.task.TaskExecutor; @@ -55,7 +55,7 @@ @Component public class GrpcServerFactory { - private static final Logger LOGGER = LogManager.getLogger(GrpcServerFactory.class); + private static final Logger logger = LoggerFactory.getLogger(GrpcServerFactory.class); @Autowired private ApplicationContext applicationContext; @Autowired @@ -93,15 +93,15 @@ public Server createServer(ServerConf serverConf) { // NettyServerBuilder serverBuilder = null; FateServerBuilder serverBuilder = null; if (StringUtils.isBlank(serverConf.getIp())) { - LOGGER.info("server build on port only :{}", serverConf.getPort()); - // LOGGER.warn("this may cause trouble in multiple network devices. you may want to consider binding to a ip"); + logger.info("server build on port only :{}", serverConf.getPort()); + // logger.warn("this may cause trouble in multiple network devices. you may want to consider binding to a ip"); serverBuilder = (FateServerBuilder) ServerBuilder.forPort(serverConf.getPort()); } else { - LOGGER.info("server build on address {}:{}", serverConf.getIp(), serverConf.getPort()); + logger.info("server build on address {}:{}", serverConf.getIp(), serverConf.getPort()); InetSocketAddress inetSocketAddress = new InetSocketAddress( InetAddresses.forString(serverConf.getIp()), serverConf.getPort()); - LOGGER.info(inetSocketAddress); + logger.info(inetSocketAddress.toString()); SocketAddress addr = new InetSocketAddress( InetAddresses.forString(serverConf.getIp()), serverConf.getPort()); @@ -161,10 +161,10 @@ public Server createServer(ServerConf serverConf) { } - LOGGER.info("running in secure mode. server crt path: {}, server key path: {}, ca crt path: {}", + logger.info("running in secure mode. server crt path: {}, server key path: {}, ca crt path: {}", serverCrtPath, serverKeyPath, caCrtPath); } else { - LOGGER.info("running in insecure mode"); + logger.info("running in insecure mode"); } Server server = serverBuilder.build(); @@ -244,9 +244,9 @@ public Server createServer(String confPath) throws IOException { Configurator.initialize(null, configurationSource); serverConf.setLogPropertiesPath(logPropertiesPath); - LOGGER.info("using log conf file: {}", logPropertiesPath); + logger.info("using log conf file: {}", logPropertiesPath); } catch (Exception e) { - LOGGER.warn("failed to set log conf file at {}. using default conf", logPropertiesPath); + logger.warn("failed to set log conf file at {}. using default conf", logPropertiesPath); } } } @@ -278,7 +278,7 @@ public Server createServer(String confPath) throws IOException { serverConf.setDebugEnabled(false); } } catch (Exception e) { - LOGGER.error(e); + logger.error(e.getMessage()); throw e; } return createServer(serverConf); diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcStreamObserverFactory.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcStreamObserverFactory.java index 9114c000..4caecfbe 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcStreamObserverFactory.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcStreamObserverFactory.java @@ -25,8 +25,8 @@ import com.webank.ai.fate.networking.proxy.infra.ResultCallback; import com.webank.ai.fate.networking.proxy.util.ToStringUtils; import io.grpc.stub.StreamObserver; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -34,7 +34,7 @@ @Component public class GrpcStreamObserverFactory { - private static final Logger LOGGER = LogManager.getLogger(GrpcStreamObserverFactory.class); + private static final Logger logger = LoggerFactory.getLogger(GrpcStreamObserverFactory.class); @Autowired private LocalBeanFactory localBeanFactory; @Autowired diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcStubFactory.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcStubFactory.java index b7857bef..7f367822 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcStubFactory.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcStubFactory.java @@ -36,8 +36,8 @@ import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext; import io.grpc.stub.AbstractStub; import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; @@ -49,7 +49,7 @@ @Component public class GrpcStubFactory { - private static final Logger LOGGER = LogManager.getLogger(GrpcStubFactory.class); + private static final Logger logger = LoggerFactory.getLogger(GrpcStubFactory.class); @Autowired private ApplicationContext applicationContext; @Autowired @@ -72,19 +72,19 @@ public GrpcStubFactory() { BasicMeta.Endpoint endpoint = (BasicMeta.Endpoint) removalNotification.getKey(); ManagedChannel managedChannel = (ManagedChannel) removalNotification.getValue(); - LOGGER.info("Managed channel removed for ip: {}, port: {}, hostname: {}. reason: {}", + logger.info("Managed channel removed for ip: {}, port: {}, hostname: {}. reason: {}", endpoint.getIp(), endpoint.getPort(), endpoint.getHostname(), removalNotification.getCause()); - LOGGER.info("Managed channel state: isShutdown: {}, isTerminated: {}", + logger.info("Managed channel state: isShutdown: {}, isTerminated: {}", managedChannel.isShutdown(), managedChannel.isTerminated()); }) .build(new CacheLoader() { @Override public ManagedChannel load(BasicMeta.Endpoint endpoint) throws Exception { Preconditions.checkNotNull(endpoint); - LOGGER.info("creating channel for endpoint: ip: {}, port: {}, hostname: {}", + logger.info("creating channel for endpoint: ip: {}, port: {}, hostname: {}", endpoint.getIp(), endpoint.getPort(), endpoint.getHostname()); return createChannel(endpoint); } @@ -125,7 +125,7 @@ private AbstractStub getStubBase(BasicMeta.Endpoint endpoint, boolean isAsync) { break; } } catch (Exception e) { - LOGGER.warn("get channel failed. target: {}, \n {}", + logger.warn("get channel failed. target: {}, \n {}", toStringUtils.toOneLineString(endpoint), ExceptionUtils.getStackTrace(e)); try { Thread.sleep(1000); @@ -156,7 +156,7 @@ private ManagedChannel getChannel(BasicMeta.Endpoint endpoint) { try { result = channelCache.get(endpoint); ConnectivityState state = result.getState(true); - LOGGER.info("Managed channel state: isShutdown: {}, isTerminated: {}, state: [}", + logger.info("Managed channel state: isShutdown: {}, isTerminated: {}, state: [}", result.isShutdown(), result.isTerminated(), state.name()); if (result.isShutdown() || result.isTerminated()) { @@ -200,7 +200,7 @@ private ManagedChannel createChannel(BasicMeta.Endpoint endpoint) { SslContext sslContext = null; try { - LOGGER.info("use secure channel to {}", toStringUtils.toOneLineString(endpoint)); + logger.info("use secure channel to {}", toStringUtils.toOneLineString(endpoint)); // sslContext = GrpcSslContexts.forClient().trustManager(trustManagerFactory).build(); sslContext = GrpcSslContexts.forClient() .trustManager(caCrt) @@ -213,14 +213,14 @@ private ManagedChannel createChannel(BasicMeta.Endpoint endpoint) { } builder.sslContext(sslContext).useTransportSecurity().negotiationType(NegotiationType.TLS); } else { - LOGGER.info("use insecure channel to {}", toStringUtils.toOneLineString(endpoint)); + logger.info("use insecure channel to {}", toStringUtils.toOneLineString(endpoint)); builder.negotiationType(NegotiationType.PLAINTEXT); } ManagedChannel managedChannel = builder .build(); - LOGGER.info("created channel to {}", toStringUtils.toOneLineString(endpoint)); + logger.info("created channel to {}", toStringUtils.toOneLineString(endpoint)); return managedChannel; } diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java index bf37abf0..2887b234 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java @@ -30,9 +30,9 @@ import com.webank.ai.fate.networking.proxy.model.ServerConf; import com.webank.ai.fate.networking.proxy.service.ConfFileBasedFdnRouter; import com.webank.ai.fate.networking.proxy.service.FdnRouter; +import com.webank.ai.fate.networking.proxy.util.AuthUtils; import com.webank.ai.fate.networking.proxy.util.ErrorUtils; import com.webank.ai.fate.networking.proxy.util.ToStringUtils; -import com.webank.ai.fate.networking.proxy.util.AuthUtils; import com.webank.ai.fate.register.common.Constants; import com.webank.ai.fate.register.router.RouterService; import com.webank.ai.fate.register.url.CollectionUtils; @@ -44,8 +44,8 @@ import io.grpc.stub.StreamObserver; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -59,7 +59,7 @@ @Component @Scope("prototype") public class DataTransferPipedClient { - private static final Logger LOGGER = LogManager.getLogger(DataTransferPipedClient.class); + private static final Logger logger = LoggerFactory.getLogger(DataTransferPipedClient.class); @Autowired private GrpcStubFactory grpcStubFactory; @Autowired @@ -88,7 +88,7 @@ public DataTransferPipedClient() { public void push(Proxy.Metadata metadata, Pipe pipe) { String onelineStringMetadata = toStringUtils.toOneLineString(metadata); - LOGGER.info("[PUSH][CLIENT] client send push to server: {}", + logger.info("[PUSH][CLIENT] client send push to server: {}", onelineStringMetadata); DataTransferServiceGrpc.DataTransferServiceStub stub = getStub(metadata.getSrc(), metadata.getDst()); @@ -97,7 +97,7 @@ public void push(Proxy.Metadata metadata, Pipe pipe) { Proxy.Topic to = metadata.getDst(); stub = getStub(from, to); } catch (Exception e) { - LOGGER.error("[PUSH][CLIENT] error when creating push stub"); + logger.error("[PUSH][CLIENT] error when creating push stub"); pipe.onError(e); } @@ -108,7 +108,7 @@ public void push(Proxy.Metadata metadata, Pipe pipe) { grpcStreamObserverFactory.createClientPushResponseStreamObserver(resultCallback, finishLatch); StreamObserver requestObserver = stub.push(responseObserver); - LOGGER.info("[PUSH][CLIENT] push stub: {}, metadata: {}", + logger.info("[PUSH][CLIENT] push stub: {}, metadata: {}", stub.getChannel(), onelineStringMetadata); int emptyRetryCount = 0; @@ -122,19 +122,19 @@ public void push(Proxy.Metadata metadata, Pipe pipe) { } else { ++emptyRetryCount; if (emptyRetryCount % 60 == 0) { - LOGGER.info("[PUSH][CLIENT] push stub waiting. empty retry count: {}, metadata: {}", + logger.info("[PUSH][CLIENT] push stub waiting. empty retry count: {}, metadata: {}", emptyRetryCount, onelineStringMetadata); } } } while ((packet != null || !pipe.isDrained()) && emptyRetryCount < 30 && !pipe.hasError()); - LOGGER.info("[PUSH][CLIENT] break out from loop. Proxy.Packet is null? {} ; pipe.isDrained()? {}" + + logger.info("[PUSH][CLIENT] break out from loop. Proxy.Packet is null? {} ; pipe.isDrained()? {}" + ", pipe.hasError? {}, metadata: {}", packet == null, pipe.isDrained(), pipe.hasError(), onelineStringMetadata); if (pipe.hasError()) { Throwable error = pipe.getError(); - LOGGER.error("[PUSH][CLIENT] push error: {}, metadata: {}", + logger.error("[PUSH][CLIENT] push error: {}, metadata: {}", ExceptionUtils.getStackTrace(error), onelineStringMetadata); requestObserver.onError(error); @@ -145,7 +145,7 @@ public void push(Proxy.Metadata metadata, Pipe pipe) { try { finishLatch.await(MAX_AWAIT_HOURS, TimeUnit.HOURS); } catch (InterruptedException e) { - LOGGER.error("[PUSH][CLIENT] client push: finishLatch.await() interrupted"); + logger.error("[PUSH][CLIENT] client push: finishLatch.await() interrupted"); requestObserver.onError(errorUtils.toGrpcRuntimeException(e)); pipe.onError(e); Thread.currentThread().interrupt(); @@ -157,19 +157,19 @@ public void push(Proxy.Metadata metadata, Pipe pipe) { if (resultCallback.hasResult()) { convertedPipe.setResult(resultCallback.getResult()); } else { - LOGGER.warn("No Proxy.Metadata returned in pipe. request metadata: {}", + logger.warn("No Proxy.Metadata returned in pipe. request metadata: {}", onelineStringMetadata); } } pipe.onComplete(); - LOGGER.info("[PUSH][CLIENT] push closing pipe. metadata: {}", + logger.info("[PUSH][CLIENT] push closing pipe. metadata: {}", onelineStringMetadata); } public void pull(Proxy.Metadata metadata, Pipe pipe) { String onelineStringMetadata = toStringUtils.toOneLineString(metadata); - LOGGER.info("[PULL][CLIENT] client send pull to server: {}", onelineStringMetadata); + logger.info("[PULL][CLIENT] client send pull to server: {}", onelineStringMetadata); DataTransferServiceGrpc.DataTransferServiceStub stub = getStub(metadata.getDst(), metadata.getSrc()); final CountDownLatch finishLatch = new CountDownLatch(1); @@ -178,13 +178,13 @@ public void pull(Proxy.Metadata metadata, Pipe pipe) { grpcStreamObserverFactory.createClientPullResponseStreamObserver(pipe, finishLatch, metadata); stub.pull(metadata, responseObserver); - LOGGER.info("[PULL][CLIENT] pull stub: {}, metadata: {}", + logger.info("[PULL][CLIENT] pull stub: {}, metadata: {}", stub.getChannel(), onelineStringMetadata); try { finishLatch.await(MAX_AWAIT_HOURS, TimeUnit.HOURS); } catch (InterruptedException e) { - LOGGER.error("[PULL][CLIENT] client pull: finishLatch.await() interrupted"); + logger.error("[PULL][CLIENT] client pull: finishLatch.await() interrupted"); responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); pipe.onError(e); Thread.currentThread().interrupt(); @@ -205,7 +205,7 @@ public void unaryCall(Proxy.Packet packet, Pipe pipe) { try { String onelineStringMetadata = toStringUtils.toOneLineString(header); - LOGGER.info("[UNARYCALL][CLIENT] client send unary call to server: {}", onelineStringMetadata); + logger.info("[UNARYCALL][CLIENT] client send unary call to server: {}", onelineStringMetadata); packet = authUtils.addAuthInfo(packet); @@ -214,20 +214,20 @@ public void unaryCall(Proxy.Packet packet, Pipe pipe) { stub.unaryCall(packet, responseObserver); - LOGGER.info("[UNARYCALL][CLIENT] unary call stub: {}, metadata: {}", + logger.info("[UNARYCALL][CLIENT] unary call stub: {}, metadata: {}", stub.getChannel(), onelineStringMetadata); try { finishLatch.await(MAX_AWAIT_HOURS, TimeUnit.HOURS); } catch (InterruptedException e) { - LOGGER.error("[UNARYCALL][CLIENT] client unary call: finishLatch.await() interrupted"); + logger.error("[UNARYCALL][CLIENT] client unary call: finishLatch.await() interrupted"); responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); pipe.onError(e); Thread.currentThread().interrupt(); return; } } catch (Exception e) { - LOGGER.error("[UNARYCALL][CLIENT] client unary call: exception: ", e); + logger.error("[UNARYCALL][CLIENT] client unary call: exception: ", e); responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); pipe.onError(e); Thread.currentThread().interrupt(); @@ -256,10 +256,10 @@ private DataTransferServiceGrpc.DataTransferServiceStub getStub(Proxy.Topic from stub = routerByServiceRegister(from, to, pack); if (stub != null) { - LOGGER.info("appid {} register return stub", to.getPartyId()); + logger.info("appid {} register return stub", to.getPartyId()); return stub; } else { - LOGGER.info("appid {} register not return stub", to.getPartyId()); + logger.info("appid {} register not return stub", to.getPartyId()); return null; } @@ -278,7 +278,7 @@ private DataTransferServiceGrpc.DataTransferServiceStub getStub(Proxy.Topic from stub = grpcStubFactory.getAsyncStub(endpoint); } - LOGGER.info("[ROUTE] route info: {} routed to {}", toStringUtils.toOneLineString(to), + logger.info("[ROUTE] route info: {} routed to {}", toStringUtils.toOneLineString(to), toStringUtils.toOneLineString(fdnRouter.route(to))); fdnRouter.route(from); @@ -304,7 +304,7 @@ private DataTransferServiceGrpc.DataTransferServiceStub getStub(Proxy.Topic from stub = grpcStubFactory.getAsyncStub(endpoint); } - LOGGER.info("[ROUTE] route info: {} routed to {}", toStringUtils.toOneLineString(to), + logger.info("[ROUTE] route info: {} routed to {}", toStringUtils.toOneLineString(to), toStringUtils.toOneLineString(fdnRouter.route(to))); fdnRouter.route(from); @@ -340,7 +340,7 @@ private DataTransferServiceGrpc.DataTransferServiceStub routerByServiceRegister( } List urls = routerService.router(paramUrl); - LOGGER.info("try to find {} returns {}",urlString,urls); + logger.info("try to find {} returns {}",urlString,urls); if (CollectionUtils.isNotEmpty(urls)) { URL url = urls.get(0); BasicMeta.Endpoint.Builder builder = BasicMeta.Endpoint.newBuilder(); diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientPullResponseStreamObserver.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientPullResponseStreamObserver.java index 72581d84..c3793858 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientPullResponseStreamObserver.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientPullResponseStreamObserver.java @@ -27,8 +27,8 @@ import io.grpc.stub.StreamObserver; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -41,9 +41,9 @@ @Component @Scope("prototype") public class ClientPullResponseStreamObserver implements StreamObserver { - private static final Logger LOGGER = LogManager.getLogger(); - private static final Logger AUDIT = LogManager.getLogger("audit"); - private static final Logger DEBUGGING = LogManager.getLogger("debugging"); + private static final Logger logger = LoggerFactory.getLogger(ClientPullResponseStreamObserver.class); + private static final Logger AUDIT = LoggerFactory.getLogger("audit"); + private static final Logger DEBUGGING = LoggerFactory.getLogger("debugging"); private final CountDownLatch finishLatch; @Autowired private StatsManager statsManager; @@ -128,9 +128,9 @@ public void onNext(Proxy.Packet packet) { @Override public void onError(Throwable throwable) { - LOGGER.error("[PULL][OBSERVER][ONERROR] error in pull client: {}, metadata: {}, ackCount: {}", + logger.error("[PULL][OBSERVER][ONERROR] error in pull client: {}, metadata: {}, ackCount: {}", Status.fromThrowable(throwable), oneLineStringMetadata, ackCount.incrementAndGet()); - LOGGER.error(ExceptionUtils.getStackTrace(throwable)); + logger.error(ExceptionUtils.getStackTrace(throwable)); pipe.onError(throwable); @@ -141,19 +141,19 @@ public void onError(Throwable throwable) { @Override public void onCompleted() { long latestAckCount = ackCount.get(); - LOGGER.info("[PULL][OBSERVER][ONCOMPLETE] Client pull completed. metadata: {}, ackCount: {}", + logger.info("[PULL][OBSERVER][ONCOMPLETE] Client pull completed. metadata: {}, ackCount: {}", oneLineStringMetadata, latestAckCount); pipe.onComplete(); finishLatch.countDown(); try { - LOGGER.info("[PULL][OBSERVER][ONCOMPLETE] is streamStat set: {}", isStreamStatSet); + logger.info("[PULL][OBSERVER][ONCOMPLETE] is streamStat set: {}", isStreamStatSet); if (streamStat != null) { streamStat.onComplete(); } } catch (NullPointerException e) { - LOGGER.error("[PULL][OBSERVER][ONCOMPLETE] NullPointerException caught in pull onComplete. isStreamStatSet: {}", isStreamStatSet); + logger.error("[PULL][OBSERVER][ONCOMPLETE] NullPointerException caught in pull onComplete. isStreamStatSet: {}", isStreamStatSet); } } diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientPushResponseStreamObserver.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientPushResponseStreamObserver.java index f861043c..be712457 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientPushResponseStreamObserver.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientPushResponseStreamObserver.java @@ -21,8 +21,8 @@ import com.webank.ai.fate.networking.proxy.util.ToStringUtils; import io.grpc.stub.StreamObserver; import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -32,7 +32,7 @@ @Component @Scope("prototype") public class ClientPushResponseStreamObserver implements StreamObserver { - private static final Logger LOGGER = LogManager.getLogger(ClientPullResponseStreamObserver.class); + private static final Logger logger = LoggerFactory.getLogger(ClientPullResponseStreamObserver.class); private final CountDownLatch finishLatch; private final ResultCallback resultCallback; @Autowired @@ -48,20 +48,20 @@ public ClientPushResponseStreamObserver(ResultCallback resultCal public void onNext(Proxy.Metadata metadata) { this.metadata = metadata; resultCallback.setResult(metadata); - LOGGER.info("[PUSH][CLIENTOBSERVER][ONNEXT] ClientPushResponseStreamObserver.onNext(), metadata: {}", + logger.info("[PUSH][CLIENTOBSERVER][ONNEXT] ClientPushResponseStreamObserver.onNext(), metadata: {}", toStringUtils.toOneLineString(metadata)); } @Override public void onError(Throwable throwable) { - LOGGER.error("[PUSH][CLIENTOBSERVER][ONERROR] error in push client: {}, metadata: {}", toStringUtils.toOneLineString(metadata)); - LOGGER.error(ExceptionUtils.getStackTrace(throwable)); + logger.error("[PUSH][CLIENTOBSERVER][ONERROR] error in push client: {}, metadata: {}", toStringUtils.toOneLineString(metadata)); + logger.error(ExceptionUtils.getStackTrace(throwable)); finishLatch.countDown(); } @Override public void onCompleted() { - LOGGER.info("[PUSH][CLIENTOBSERVER][ONCOMPLETE] ClientPushResponseStreamObserver.onCompleted(), metadata: {}", + logger.info("[PUSH][CLIENTOBSERVER][ONCOMPLETE] ClientPushResponseStreamObserver.onCompleted(), metadata: {}", toStringUtils.toOneLineString(metadata)); finishLatch.countDown(); } diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientUnaryCallResponseStreamObserver.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientUnaryCallResponseStreamObserver.java index 96beab27..bc415fe3 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientUnaryCallResponseStreamObserver.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientUnaryCallResponseStreamObserver.java @@ -26,8 +26,8 @@ import io.grpc.Status; import io.grpc.stub.StreamObserver; import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -40,9 +40,9 @@ @Component @Scope("prototype") public class ClientUnaryCallResponseStreamObserver implements StreamObserver { - private static final Logger LOGGER = LogManager.getLogger(ClientUnaryCallResponseStreamObserver.class); - private static final Logger DEBUGGING = LogManager.getLogger("debugging"); - private static final Logger AUDIT = LogManager.getLogger("audit"); + private static final Logger logger = LoggerFactory.getLogger(ClientUnaryCallResponseStreamObserver.class); + private static final Logger DEBUGGING = LoggerFactory.getLogger("debugging"); + private static final Logger AUDIT = LoggerFactory.getLogger("audit"); private final CountDownLatch finishLatch; @Autowired private StatsManager statsManager; @@ -77,7 +77,7 @@ private synchronized void init() { @Override public void onNext(Proxy.Packet packet) { - // LOGGER.info("ClientPullResponseStreamObserver.onNext()"); + // logger.info("ClientPullResponseStreamObserver.onNext()"); pipe.write(packet); ackCount.incrementAndGet(); @@ -101,14 +101,14 @@ public void onNext(Proxy.Packet packet) { streamStat.increment(value.size()); } - // LOGGER.info("[UNARYCALL][OBSERVER][ONNEXT] result: {}", packet.getBody().getValue().toStringUtf8()); + // logger.info("[UNARYCALL][OBSERVER][ONNEXT] result: {}", packet.getBody().getValue().toStringUtf8()); } @Override public void onError(Throwable throwable) { - LOGGER.error("[UNARYCALL][OBSERVER][ONERROR] error in unary call response observer: {}, metadata: {}", + logger.error("[UNARYCALL][OBSERVER][ONERROR] error in unary call response observer: {}, metadata: {}", Status.fromThrowable(throwable), toStringUtils.toOneLineString(metadata)); - LOGGER.error(ExceptionUtils.getStackTrace(throwable)); + logger.error(ExceptionUtils.getStackTrace(throwable)); pipe.onError(throwable); @@ -118,7 +118,7 @@ public void onError(Throwable throwable) { @Override public void onCompleted() { - LOGGER.info("[UNARYCALL][OBSERVER][ONCOMPLETE] Client unary call completed. metadata: {}", + logger.info("[UNARYCALL][OBSERVER][ONCOMPLETE] Client unary call completed. metadata: {}", toStringUtils.toOneLineString(metadata)); pipe.onComplete(); diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ServerPushRequestStreamObserver.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ServerPushRequestStreamObserver.java index 14e4d864..51d26ac9 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ServerPushRequestStreamObserver.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ServerPushRequestStreamObserver.java @@ -35,8 +35,8 @@ import io.grpc.stub.StreamObserver; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Scope; @@ -49,9 +49,9 @@ @Component @Scope("prototype") public class ServerPushRequestStreamObserver implements StreamObserver { - private static final Logger LOGGER = LogManager.getLogger(ServerPushRequestStreamObserver.class); - private static final Logger AUDIT = LogManager.getLogger("audit"); - private static final Logger DEBUGGING = LogManager.getLogger("debugging"); + private static final Logger logger = LoggerFactory.getLogger(ServerPushRequestStreamObserver.class); + private static final Logger AUDIT = LoggerFactory.getLogger("audit"); + private static final Logger DEBUGGING = LoggerFactory.getLogger("debugging"); private final StreamObserver responseObserver; @Autowired private ApplicationEventPublisher applicationEventPublisher; @@ -103,10 +103,10 @@ public void onNext(Proxy.Packet packet) { oneLineStringInputMetadata = toStringUtils.toOneLineString(inputMetadata); statsManager.add(streamStat); - LOGGER.info(Grpc.TRANSPORT_ATTR_REMOTE_ADDR.toString()); + logger.info(Grpc.TRANSPORT_ATTR_REMOTE_ADDR.toString()); - LOGGER.info("[PUSH][OBSERVER][ONNEXT] metadata: {}", oneLineStringInputMetadata); - LOGGER.info("[PUSH][OBSERVER][ONNEXT] request src: {}, dst: {}", + logger.info("[PUSH][OBSERVER][ONNEXT] metadata: {}", oneLineStringInputMetadata); + logger.info("[PUSH][OBSERVER][ONNEXT] request src: {}, dst: {}", toStringUtils.toOneLineString(inputMetadata.getSrc()), toStringUtils.toOneLineString(inputMetadata.getDst())); @@ -132,7 +132,7 @@ public void onNext(Proxy.Packet packet) { // String operator = inputMetadata.getOperator(); - // LOGGER.info("onNext(): push task name: {}", operator); + // logger.info("onNext(): push task name: {}", operator); PipeHandleNotificationEvent event = eventFactory.createPipeHandleNotificationEvent( @@ -143,7 +143,7 @@ public void onNext(Proxy.Packet packet) { if (noError) { pipe.write(packet); ackCount.incrementAndGet(); - //LOGGER.info("myCoordinator: {}, Proxy.Packet coordinator: {}", myCoordinator, packet.getHeader().getSrc().getCoordinator()); + //logger.info("myCoordinator: {}, Proxy.Packet coordinator: {}", myCoordinator, packet.getHeader().getSrc().getCoordinator()); if (isAuditEnabled && packet.getHeader().getSrc().getPartyId().equals(myCoordinator)) { AUDIT.info(toStringUtils.toOneLineString(packet)); } @@ -167,15 +167,15 @@ public void onNext(Proxy.Packet packet) { } DEBUGGING.info("-------------"); } - //LOGGER.info("push server received size: {}, data size: {}", packet.getSerializedSize(), packet.getBody().getValue().size()); + //logger.info("push server received size: {}, data size: {}", packet.getSerializedSize(), packet.getBody().getValue().size()); } } @Override public void onError(Throwable throwable) { - LOGGER.error("[PUSH][OBSERVER][ONERROR] error in push server: {}, metadata: {}, ackCount: {}", + logger.error("[PUSH][OBSERVER][ONERROR] error in push server: {}, metadata: {}, ackCount: {}", Status.fromThrowable(throwable), oneLineStringInputMetadata, ackCount.get()); - LOGGER.error(ExceptionUtils.getStackTrace(throwable)); + logger.error(ExceptionUtils.getStackTrace(throwable)); pipe.setDrained(); @@ -186,7 +186,7 @@ public void onError(Throwable throwable) { } else { noError = false; pipe.onComplete(); - LOGGER.info("[PUSH][OBSERVER][ONERROR] connection cancelled. turning into completed."); + logger.info("[PUSH][OBSERVER][ONERROR] connection cancelled. turning into completed."); onCompleted(); streamStat.onComplete(); return; @@ -200,7 +200,7 @@ public void onError(Throwable throwable) { @Override public void onCompleted() { long lastestAckCount = ackCount.get(); - LOGGER.info("[PUSH][OBSERVER][ONCOMPLETE] trying to complete task. metadata: {}, ackCount: {}", + logger.info("[PUSH][OBSERVER][ONCOMPLETE] trying to complete task. metadata: {}, ackCount: {}", oneLineStringInputMetadata, lastestAckCount); long completionWaitStartTimestamp = System.currentTimeMillis(); @@ -209,14 +209,14 @@ public void onCompleted() { pipe.setDrained(); - /*LOGGER.info("closed: {}, completion timeout: {}, overall timeout: {}", + /*logger.info("closed: {}, completion timeout: {}, overall timeout: {}", pipe.isClosed(), timeouts.isTimeout(completionWaitTimeout, completionWaitStartTimestamp, loopEnd), timeouts.isTimeout(overallTimeout, overallStartTimestamp, loopEnd));*/ while (!pipe.isClosed() && !timeouts.isTimeout(completionWaitTimeout, completionWaitStartTimestamp, loopEndTimestamp) && !timeouts.isTimeout(overallTimeout, overallStartTimestamp, loopEndTimestamp)) { - // LOGGER.info("waiting for next level result"); + // logger.info("waiting for next level result"); try { pipe.awaitClosed(1, TimeUnit.SECONDS); } catch (InterruptedException e) { @@ -231,7 +231,7 @@ public void onCompleted() { PacketQueuePipe pqp = (PacketQueuePipe) pipe; extraInfo = "queueSize: " + pqp.getQueueSize(); } - LOGGER.info("[PUSH][OBSERVER][ONCOMPLETE] waiting push to complete. wait time: {}. metadata: {}, extrainfo: {}", + logger.info("[PUSH][OBSERVER][ONCOMPLETE] waiting push to complete. wait time: {}. metadata: {}, extrainfo: {}", (loopEndTimestamp - completionWaitStartTimestamp), oneLineStringInputMetadata, extraInfo); } } @@ -246,7 +246,7 @@ public void onCompleted() { + ", completionWaitStartTimestamp: " + completionWaitStartTimestamp + ", loopEndTimestamp: " + loopEndTimestamp + ", ackCount: " + lastestAckCount; - LOGGER.error(errmsg); + logger.error(errmsg); responseObserver.onError(new TimeoutException(errmsg)); streamStat.onError(); } else if (timeouts.isTimeout(overallTimeout, overallStartTimestamp, loopEndTimestamp)) { @@ -257,25 +257,25 @@ public void onCompleted() { + ", loopEndTimestamp: " + loopEndTimestamp + ", ackCount: " + lastestAckCount; - LOGGER.error(errmsg); + logger.error(errmsg); responseObserver.onError(new TimeoutException(errmsg)); streamStat.onError(); } else { Proxy.Metadata responseMetadata = pipeUtils.getResultFromPipe(pipe); if (responseMetadata == null) { - LOGGER.warn("[PUSH][OBSERVER][ONCOMPLETE] response Proxy.Metadata is null. inputMetadata: {}", + logger.warn("[PUSH][OBSERVER][ONCOMPLETE] response Proxy.Metadata is null. inputMetadata: {}", toStringUtils.toOneLineString(responseMetadata)); } responseObserver.onNext(responseMetadata); responseObserver.onCompleted(); - LOGGER.info("[PUSH][OBSERVER][ONCOMPLETE] push server complete. inputMetadata: {}", + logger.info("[PUSH][OBSERVER][ONCOMPLETE] push server complete. inputMetadata: {}", toStringUtils.toOneLineString(responseMetadata)); streamStat.onComplete(); } } catch (NullPointerException e) { - LOGGER.error("[PUSH][OBSERVER][ONCOMPLETE] NullPointerException caught in push onComplete. metadata: {}", + logger.error("[PUSH][OBSERVER][ONCOMPLETE] NullPointerException caught in push onComplete. metadata: {}", oneLineStringInputMetadata); } } diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/DataTransferPipedServerImpl.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/DataTransferPipedServerImpl.java index f682aadf..255217d2 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/DataTransferPipedServerImpl.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/DataTransferPipedServerImpl.java @@ -18,22 +18,20 @@ import com.webank.ai.fate.api.networking.proxy.DataTransferServiceGrpc; import com.webank.ai.fate.api.networking.proxy.Proxy; - -import com.webank.ai.fate.networking.proxy.util.AuthUtils; - import com.webank.ai.fate.networking.proxy.event.model.PipeHandleNotificationEvent; import com.webank.ai.fate.networking.proxy.factory.EventFactory; import com.webank.ai.fate.networking.proxy.factory.GrpcStreamObserverFactory; import com.webank.ai.fate.networking.proxy.factory.PipeFactory; import com.webank.ai.fate.networking.proxy.infra.Pipe; +import com.webank.ai.fate.networking.proxy.util.AuthUtils; import com.webank.ai.fate.networking.proxy.util.ErrorUtils; import com.webank.ai.fate.networking.proxy.util.Timeouts; import com.webank.ai.fate.networking.proxy.util.ToStringUtils; import com.webank.ai.fate.register.annotions.RegisterService; import io.grpc.stub.StreamObserver; import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Scope; @@ -45,7 +43,7 @@ @Component @Scope("prototype") public class DataTransferPipedServerImpl extends DataTransferServiceGrpc.DataTransferServiceImplBase { - private static final Logger LOGGER = LogManager.getLogger(DataTransferPipedServerImpl.class); + private static final Logger logger = LoggerFactory.getLogger(DataTransferPipedServerImpl.class); @Autowired private ApplicationEventPublisher applicationEventPublisher; @Autowired @@ -66,10 +64,10 @@ public class DataTransferPipedServerImpl extends DataTransferServiceGrpc.DataTra @RegisterService(serviceName = "push") @Override public StreamObserver push(StreamObserver responseObserver) { - LOGGER.info("[PUSH][SERVER] request received"); + logger.info("[PUSH][SERVER] request received"); Pipe pipe = getPipe(); - // LOGGER.info("push pipe: {}", pipe); + // logger.info("push pipe: {}", pipe); /* PipeHandleNotificationEvent event = eventFactory.createPipeHandleNotificationEvent( @@ -87,7 +85,7 @@ public StreamObserver push(StreamObserver response @RegisterService(serviceName = "pull") public void pull(Proxy.Metadata inputMetadata, StreamObserver responseObserver) { String oneLineStringInputMetadata = toStringUtils.toOneLineString(inputMetadata); - LOGGER.info("[PULL][SERVER] request received. metadata: {}", + logger.info("[PULL][SERVER] request received. metadata: {}", oneLineStringInputMetadata); long overallTimeout = timeouts.getOverallTimeout(inputMetadata); @@ -95,7 +93,7 @@ public void pull(Proxy.Metadata inputMetadata, StreamObserver resp Pipe pipe = getPipe(); - LOGGER.info("[PULL][SERVER] pull pipe: {}", pipe); + logger.info("[PULL][SERVER] pull pipe: {}", pipe); PipeHandleNotificationEvent event = eventFactory.createPipeHandleNotificationEvent( @@ -116,10 +114,10 @@ public void pull(Proxy.Metadata inputMetadata, StreamObserver resp && !timeouts.isTimeout(packetIntervalTimeout, lastPacketTimestamp, loopEndTimestamp) && !timeouts.isTimeout(overallTimeout, startTimestamp, loopEndTimestamp)) { packet = (Proxy.Packet) pipe.read(1, TimeUnit.SECONDS); - // LOGGER.info("packet is null: {}", Proxy.Packet == null); + // logger.info("packet is null: {}", Proxy.Packet == null); loopEndTimestamp = System.currentTimeMillis(); if (packet != null) { - // LOGGER.info("server pull onNext()"); + // logger.info("server pull onNext()"); responseObserver.onNext(packet); hasReturnedBefore = true; lastReturnedPacket = packet; @@ -128,7 +126,7 @@ public void pull(Proxy.Metadata inputMetadata, StreamObserver resp } else { long currentPacketInterval = loopEndTimestamp - lastPacketTimestamp; if (++emptyRetryCount % 60 == 0) { - LOGGER.info("[PULL][SERVER] pull waiting. current packetInterval: {}, packetIntervalTimeout: {}, metadata: {}", + logger.info("[PULL][SERVER] pull waiting. current packetInterval: {}, packetIntervalTimeout: {}, metadata: {}", currentPacketInterval, packetIntervalTimeout, oneLineStringInputMetadata); } } @@ -137,7 +135,7 @@ public void pull(Proxy.Metadata inputMetadata, StreamObserver resp boolean hasError = true; if (pipe.hasError()) { Throwable error = pipe.getError(); - LOGGER.error("[PULL][SERVER] pull finish with error: {}", ExceptionUtils.getStackTrace(error)); + logger.error("[PULL][SERVER] pull finish with error: {}", ExceptionUtils.getStackTrace(error)); responseObserver.onError(error); return; @@ -156,7 +154,7 @@ public void pull(Proxy.Metadata inputMetadata, StreamObserver resp String errorMsg = sb.toString(); - LOGGER.error(errorMsg); + logger.error(errorMsg); TimeoutException e = new TimeoutException(errorMsg); responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); @@ -171,7 +169,7 @@ public void pull(Proxy.Metadata inputMetadata, StreamObserver resp .append(", loopEndTimestamp: ") .append(loopEndTimestamp); String errorMsg = sb.toString(); - LOGGER.error(errorMsg); + logger.error(errorMsg); TimeoutException e = new TimeoutException(errorMsg); responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); @@ -181,9 +179,9 @@ public void pull(Proxy.Metadata inputMetadata, StreamObserver resp hasError = false; pipe.onComplete(); } - LOGGER.info("[PULL][SERVER] server pull finshed. hasReturnedBefore: {}, hasError: {}, metadata: {}", + logger.info("[PULL][SERVER] server pull finshed. hasReturnedBefore: {}, hasError: {}, metadata: {}", hasReturnedBefore, hasError, oneLineStringInputMetadata); - //LOGGER.warn("pull last returned packet: {}", lastReturnedPacket); + //logger.warn("pull last returned packet: {}", lastReturnedPacket); } @Override @@ -194,13 +192,13 @@ public void unaryCall(Proxy.Packet request, StreamObserver respons try { isAuthPass = authUtils.checkAuthentication(request); } catch (Exception e) { - LOGGER.error("checkAuthentication throw an exception: ", e); + logger.error("checkAuthentication throw an exception: ", e); responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); return; } if(false == isAuthPass){ String msg = "authentication not pass!"; - LOGGER.error(msg); + logger.error(msg); RuntimeException e = new RuntimeException(msg); responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); return; @@ -208,7 +206,7 @@ public void unaryCall(Proxy.Packet request, StreamObserver respons Proxy.Metadata inputMetadata = request.getHeader(); String oneLineStringInputMetadata = toStringUtils.toOneLineString(inputMetadata); - LOGGER.info("[UNARYCALL][SERVER] server unary request received. src: {}, dst: {}", + logger.info("[UNARYCALL][SERVER] server unary request received. src: {}, dst: {}", toStringUtils.toOneLineString(inputMetadata.getSrc()), toStringUtils.toOneLineString(inputMetadata.getDst())); @@ -217,7 +215,7 @@ public void unaryCall(Proxy.Packet request, StreamObserver respons Pipe pipe = getPipe(); - LOGGER.info("[UNARYCALL][SERVER] unary call pipe: {}", pipe); + logger.info("[UNARYCALL][SERVER] unary call pipe: {}", pipe); PipeHandleNotificationEvent event = eventFactory.createPipeHandleNotificationEvent( @@ -236,7 +234,7 @@ public void unaryCall(Proxy.Packet request, StreamObserver respons packet = (Proxy.Packet) pipe.read(1, TimeUnit.SECONDS); loopEndTimestamp = System.currentTimeMillis(); if (packet != null) { - // LOGGER.info("server pull onNext()"); + // logger.info("server pull onNext()"); responseObserver.onNext(packet); hasReturnedBefore = true; emptyRetryCount = 0; @@ -245,7 +243,7 @@ public void unaryCall(Proxy.Packet request, StreamObserver respons long currentOverallWaitTime = loopEndTimestamp - lastPacketTimestamp; if (++emptyRetryCount % 60 == 0) { - LOGGER.info("[UNARYCALL][SERVER] unary call waiting. current overallWaitTime: {}, packetIntervalTimeout: {}, metadata: {}", + logger.info("[UNARYCALL][SERVER] unary call waiting. current overallWaitTime: {}, packetIntervalTimeout: {}, metadata: {}", currentOverallWaitTime, packetIntervalTimeout, oneLineStringInputMetadata); } } @@ -254,7 +252,7 @@ public void unaryCall(Proxy.Packet request, StreamObserver respons if (pipe.hasError()) { Throwable error = pipe.getError(); - LOGGER.error("[UNARYCALL][SERVER] unary call finish with error: {}", ExceptionUtils.getStackTrace(error)); + logger.error("[UNARYCALL][SERVER] unary call finish with error: {}", ExceptionUtils.getStackTrace(error)); responseObserver.onError(error); return; @@ -266,7 +264,7 @@ public void unaryCall(Proxy.Packet request, StreamObserver respons + ", metadata: " + oneLineStringInputMetadata + ", lastPacketTimestamp: " + lastPacketTimestamp + ", loopEndTimestamp: " + loopEndTimestamp; - LOGGER.error(errorMsg); + logger.error(errorMsg); TimeoutException e = new TimeoutException(errorMsg); responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); @@ -287,7 +285,7 @@ public void unaryCall(Proxy.Packet request, StreamObserver respons pipe.onComplete(); } - LOGGER.info("[UNARYCALL][SERVER] server unary call completed. hasReturnedBefore: {}, hasError: {}, metadata: {}", + logger.info("[UNARYCALL][SERVER] server unary call completed. hasReturnedBefore: {}, hasError: {}, metadata: {}", hasReturnedBefore, hasError, oneLineStringInputMetadata); } diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/RouteServerImpl.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/RouteServerImpl.java index ab731514..8dadb8c1 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/RouteServerImpl.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/RouteServerImpl.java @@ -23,8 +23,8 @@ import com.webank.ai.fate.networking.proxy.util.ErrorUtils; import com.webank.ai.fate.networking.proxy.util.ToStringUtils; import io.grpc.stub.StreamObserver; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -33,7 +33,7 @@ @Component @Scope("prototype") public class RouteServerImpl extends RouteServiceGrpc.RouteServiceImplBase { - private static final Logger LOGGER = LogManager.getLogger(RouteServerImpl.class); + private static final Logger logger = LoggerFactory.getLogger(RouteServerImpl.class); @Autowired private FdnRouter fdnRouter; @Autowired @@ -44,7 +44,7 @@ public class RouteServerImpl extends RouteServiceGrpc.RouteServiceImplBase { @Override public void query(Proxy.Topic request, StreamObserver responseObserver) { String requestString = toStringUtils.toOneLineString(request); - LOGGER.info("[ROUTE] querying route for topic: {}", requestString); + logger.info("[ROUTE] querying route for topic: {}", requestString); BasicMeta.Endpoint result = fdnRouter.route(request); if (result == null) { @@ -52,7 +52,7 @@ public void query(Proxy.Topic request, StreamObserver respon responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); } - LOGGER.info("[ROUTE] querying route result for topic: {}, result: {}", requestString, toStringUtils.toOneLineString(result)); + logger.info("[ROUTE] querying route result for topic: {}, result: {}", requestString, toStringUtils.toOneLineString(result)); responseObserver.onNext(result); responseObserver.onCompleted(); } diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/InputStreamOutputStreamNoStoragePipe.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/InputStreamOutputStreamNoStoragePipe.java index 31ee9eb3..c0b27c2d 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/InputStreamOutputStreamNoStoragePipe.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/InputStreamOutputStreamNoStoragePipe.java @@ -19,8 +19,8 @@ import com.google.common.io.ByteStreams; import com.google.protobuf.ByteString; import com.webank.ai.fate.api.networking.proxy.Proxy; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -34,7 +34,7 @@ public class InputStreamOutputStreamNoStoragePipe extends BasePipe { private static final int MAX_EMPTY_READ_COUNT = 10; private static final int MAX_READ_COUNT = 50; - private static final Logger LOGGER = LogManager.getLogger(InputStreamOutputStreamNoStoragePipe.class); + private static final Logger logger = LoggerFactory.getLogger(InputStreamOutputStreamNoStoragePipe.class); private InputStream is; private OutputStream os; private Proxy.Metadata metadata; @@ -58,7 +58,7 @@ public InputStreamOutputStreamNoStoragePipe(InputStream is, OutputStream os, Pro @Override public Proxy.Packet read() { - LOGGER.info("read for the {} time", ++inCounter); + logger.info("read for the {} time", ++inCounter); ByteString value = null; ByteString cur = null; @@ -100,7 +100,7 @@ public Object read(long timeout, TimeUnit unit) { @Override public void write(Object o) { - LOGGER.info("write for the {} time", ++outCounter); + logger.info("write for the {} time", ++outCounter); if (o instanceof Proxy.Packet) { Proxy.Packet packet = (Proxy.Packet) o; try { diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/InputStreamToPacketUnidirectionalPipe.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/InputStreamToPacketUnidirectionalPipe.java index 2a45bee1..9090f03b 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/InputStreamToPacketUnidirectionalPipe.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/InputStreamToPacketUnidirectionalPipe.java @@ -20,8 +20,8 @@ import com.google.protobuf.ByteString; import com.webank.ai.fate.api.networking.proxy.Proxy; import org.apache.commons.io.IOUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -35,7 +35,7 @@ public class InputStreamToPacketUnidirectionalPipe extends BasePipe { private static final int MAX_EMPTY_READ_COUNT = 10; private static final int MAX_READ_COUNT = 50; private static final int DEFAULT_TRUNK_SIZE = 2 << 20; - private static final Logger LOGGER = LogManager.getLogger(InputStreamToPacketUnidirectionalPipe.class); + private static final Logger logger = LoggerFactory.getLogger(InputStreamToPacketUnidirectionalPipe.class); private final int trunkSize; private InputStream is; private Proxy.Metadata metadata; @@ -61,7 +61,7 @@ public InputStreamToPacketUnidirectionalPipe(InputStream is, Proxy.Metadata meta @Override public Proxy.Packet read() { - LOGGER.info("read for the {} time", ++counter); + logger.info("read for the {} time", ++counter); ByteString value = null; ByteString cur = null; diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketQueuePipe.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketQueuePipe.java index 7bd91520..b02a6463 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketQueuePipe.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketQueuePipe.java @@ -19,8 +19,8 @@ import com.webank.ai.fate.api.networking.proxy.Proxy; import com.webank.ai.fate.networking.proxy.factory.QueueFactory; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -32,7 +32,7 @@ @Component @Scope("prototype") public class PacketQueuePipe extends BasePipe { - private static final Logger LOGGER = LogManager.getLogger(PacketQueuePipe.class); + private static final Logger logger = LoggerFactory.getLogger(PacketQueuePipe.class); @Autowired private QueueFactory queueFactory; private Proxy.Metadata metadata; @@ -57,7 +57,7 @@ private void init() { @Override public Proxy.Packet read() { - // LOGGER.info("read for the {} time, queue size: {}", ++inCounter, queue.size()); + // logger.info("read for the {} time, queue size: {}", ++inCounter, queue.size()); return queue.poll(); } @@ -68,7 +68,7 @@ public Proxy.Packet read(long timeout, TimeUnit unit) { try { result = queue.poll(timeout, unit); } catch (InterruptedException e) { - LOGGER.debug("read wait timeout"); + logger.debug("read wait timeout"); Thread.currentThread().interrupt(); } @@ -77,7 +77,7 @@ public Proxy.Packet read(long timeout, TimeUnit unit) { @Override public void write(Object o) { - // LOGGER.info("write for the {} time, queue size: {}", ++outCounter, queue.size()); + // logger.info("write for the {} time, queue size: {}", ++outCounter, queue.size()); if (o instanceof Proxy.Packet) { queue.add((Proxy.Packet) o); } else { diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketQueueSingleResultPipe.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketQueueSingleResultPipe.java index 014009d2..4ef36139 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketQueueSingleResultPipe.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketQueueSingleResultPipe.java @@ -17,15 +17,15 @@ package com.webank.ai.fate.networking.proxy.infra.impl; import com.webank.ai.fate.api.networking.proxy.Proxy; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Component @Scope("prototype") public class PacketQueueSingleResultPipe extends PacketQueuePipe { - private static final Logger LOGGER = LogManager.getLogger(PacketQueueSingleResultPipe.class); + private static final Logger logger = LoggerFactory.getLogger(PacketQueueSingleResultPipe.class); private Proxy.Metadata result; public PacketQueueSingleResultPipe() { diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketToOutputStreamUnidirectionalPipe.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketToOutputStreamUnidirectionalPipe.java index 923136de..fb12a300 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketToOutputStreamUnidirectionalPipe.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketToOutputStreamUnidirectionalPipe.java @@ -18,8 +18,8 @@ import com.webank.ai.fate.api.networking.proxy.Proxy; import org.apache.commons.io.IOUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -30,7 +30,7 @@ @Component @Scope("prototype") public class PacketToOutputStreamUnidirectionalPipe extends BasePipe { - private static final Logger LOGGER = LogManager.getLogger(PacketToOutputStreamUnidirectionalPipe.class); + private static final Logger logger = LoggerFactory.getLogger(PacketToOutputStreamUnidirectionalPipe.class); private OutputStream os; private int counter = 0; @@ -51,7 +51,7 @@ public Object read(long timeout, TimeUnit unit) { @Override public void write(Object o) { - LOGGER.info("write for the {} time", ++counter); + logger.info("write for the {} time", ++counter); if (o instanceof Proxy.Packet) { Proxy.Packet packet = (Proxy.Packet) o; try { diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/manager/ExecutorManager.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/manager/ExecutorManager.java index ed2ded15..a0eb37fd 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/manager/ExecutorManager.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/manager/ExecutorManager.java @@ -19,8 +19,8 @@ import com.webank.ai.fate.networking.proxy.util.ToStringUtils; import org.apache.commons.text.StringSubstitutor; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; @@ -35,7 +35,7 @@ @Component public class ExecutorManager { private static final String LOG_TEMPLATE; - private static final Logger LOGGER = LogManager.getLogger("stat"); + private static final Logger logger = LoggerFactory.getLogger("stat"); static { LOG_TEMPLATE = "executor stat: pool name: ${name}, " + @@ -67,7 +67,7 @@ private void init() { } public void statExecutor() { - LOGGER.info("------------ executor stat ------------"); + logger.info("------------ executor stat ------------"); Map valuesMap = new HashMap<>(10); StringSubstitutor stringSubstitutor = new StringSubstitutor(valuesMap); @@ -79,7 +79,7 @@ public void statExecutor() { valuesMap.put("activeCount", String.valueOf(executor.getActiveCount())); String log = stringSubstitutor.replace(LOG_TEMPLATE); - LOGGER.info(log); + logger.info(log); } } } diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/manager/StatsManager.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/manager/StatsManager.java index aa26112a..17fcf440 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/manager/StatsManager.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/manager/StatsManager.java @@ -23,8 +23,8 @@ import io.grpc.netty.shaded.io.netty.util.internal.ConcurrentSet; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.text.StringSubstitutor; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -40,7 +40,7 @@ @Component public class StatsManager { private static final String LOG_TEMPLATE; - private static final Logger LOGGER = LogManager.getLogger("stat"); + private static final Logger logger = LoggerFactory.getLogger("stat"); static { LOG_TEMPLATE = "streaming stat: Proxy.Metadata [${metadata}], " + @@ -73,14 +73,14 @@ public void add(StreamStat streamStat) { streamStatsRwLock.writeLock().lock(); streamStats.add(streamStat); } catch (Exception e) { - LOGGER.error("unexpected error: {}", ExceptionUtils.getStackTrace(e)); + logger.error("unexpected error: {}", ExceptionUtils.getStackTrace(e)); } finally { streamStatsRwLock.writeLock().unlock(); } } public void logAllStatus() { - LOGGER.info("------------ streaming stat ------------"); + logger.info("------------ streaming stat ------------"); if (streamStats.size() <= 0) { return; @@ -93,7 +93,7 @@ public void logAllStatus() { oldStreamStats = streamStats; streamStats = new ConcurrentSet<>(); } catch (Exception e) { - LOGGER.error(ExceptionUtils.getStackTrace(e)); + logger.error(ExceptionUtils.getStackTrace(e)); } finally { streamStatsRwLock.writeLock().unlock(); } @@ -141,11 +141,11 @@ public void logAllStatus() { valuesMap.put("status", streamStat.getStatus()); String log = stringSubstitutor.replace(LOG_TEMPLATE); - LOGGER.info(log); + logger.info(log); ++logCount; } - LOGGER.info("stream logCount: {}, not finished: {}, not removed: {}", + logger.info("stream logCount: {}, not finished: {}, not removed: {}", logCount, notFinishedCount, notRemovedCount); } } diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/service/ConfFileBasedFdnRouter.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/service/ConfFileBasedFdnRouter.java index ba21f149..58795fac 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/service/ConfFileBasedFdnRouter.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/service/ConfFileBasedFdnRouter.java @@ -26,8 +26,8 @@ import com.webank.ai.fate.api.networking.proxy.Proxy; import com.webank.ai.fate.networking.proxy.model.ServerConf; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -44,7 +44,7 @@ public class ConfFileBasedFdnRouter implements FdnRouter { private static final String PORT = "port"; private static final String HOSTNAME = "hostname"; private static final String DEFAULT = "default"; - private static final Logger LOGGER = LogManager.getLogger(ConfFileBasedFdnRouter.class); + private static final Logger logger = LoggerFactory.getLogger(ConfFileBasedFdnRouter.class); private final BasicMeta.Endpoint emptyEndpoint; @Autowired private ServerConf serverConf; @@ -88,11 +88,11 @@ public Map>> getRouteTable() { @Override public synchronized void setRouteTable(String filename) { if (StringUtils.isBlank(routeTableFilename)) { - LOGGER.info("setting routeTable path to {}", filename); + logger.info("setting routeTable path to {}", filename); this.routeTableFilename = filename; init(); } else { - LOGGER.warn("trying to reset routeTable path. current path: {}, tried path: {}", + logger.warn("trying to reset routeTable path. current path: {}, tried path: {}", routeTableFilename, filename); } } @@ -121,7 +121,7 @@ public void init() { jsonReader = new JsonReader(new FileReader(routeTableFilename)); confJson = jsonParser.parse(jsonReader).getAsJsonObject(); } catch (FileNotFoundException e) { - LOGGER.error("File not found: {}", routeTableFilename); + logger.error("File not found: {}", routeTableFilename); throw new RuntimeException(e); } finally { if (jsonReader != null) { @@ -136,7 +136,7 @@ public void init() { initRouteTable(confJson.getAsJsonObject("route_table")); initPermission(confJson.getAsJsonObject("permission")); - LOGGER.info("refreshed route table at: {}", routeTableFilename); + logger.info("refreshed route table at: {}", routeTableFilename); } @Override @@ -224,7 +224,7 @@ public BasicMeta.Endpoint route(Proxy.Topic topic) { pos = pos % len; - LOGGER.info("route: hashcode: {}, len: {}, pos: {}", topic.hashCode(), len, pos); + logger.info("route: hashcode: {}, len: {}, pos: {}", topic.hashCode(), len, pos); result = endpoints.get(pos); } diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java index 04dacbe1..774c46f7 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java @@ -25,9 +25,8 @@ import com.webank.ai.fate.networking.proxy.manager.ServerConfManager; import com.webank.ai.fate.serving.core.bean.Dict; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.springframework.beans.factory.InitializingBean; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -39,7 +38,7 @@ @Component public class AuthUtils { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(AuthUtils.class); private static String confFilePath = System.getProperty(Dict.AUTH_FILE); private static Map KEY_SECRET_MAP = new HashMap<>(); private static Map PARTYID_KEY_MAP = new HashMap<>(); @@ -67,7 +66,7 @@ public void loadConfig(){ jsonReader = new JsonReader(new FileReader(confFilePath)); jsonObject = jsonParser.parse(jsonReader).getAsJsonObject(); } catch (FileNotFoundException e) { - LOGGER.error("File not found: {}", confFilePath); + logger.error("File not found: {}", confFilePath); throw new RuntimeException(e); } finally { if (jsonReader != null) { @@ -90,7 +89,7 @@ public void loadConfig(){ KEY_SECRET_MAP.put(allowKey.get("app_key").toString(), allowKey.get("app_secret").toString()); PARTYID_KEY_MAP.put(allowKey.get("party_id").toString(), allowKey.get("app_key").toString()); } - LOGGER.debug("refreshed auth cfg using file {}.", confFilePath); + logger.debug("refreshed auth cfg using file {}.", confFilePath); } private String getSecret(String appKey) { @@ -105,7 +104,7 @@ private String calSignature(Proxy.Metadata header, Proxy.Data body, long timesta String signature = ""; String appSecret = getSecret(appKey); if (StringUtils.isEmpty(appSecret)) { - LOGGER.error("appSecret not found"); + logger.error("appSecret not found"); return signature; } String encryptText = String.valueOf(timestamp) + "\n" @@ -145,14 +144,14 @@ public boolean checkAuthentication(Proxy.Packet packet) throws Exception { long currentTimeMillis = System.currentTimeMillis(); long requestTimeMillis = packet.getAuth().getTimestamp(); if (currentTimeMillis >= (requestTimeMillis + validRequestTimeoutSecond * 1000)) { - LOGGER.error("receive an expired request, currentTimeMillis:{}, requestTimeMillis{}.", currentTimeMillis, requestTimeMillis); + logger.error("receive an expired request, currentTimeMillis:{}, requestTimeMillis{}.", currentTimeMillis, requestTimeMillis); return false; } // check signature String reqSignature = packet.getAuth().getSignature(); String validSignature = calSignature(packet.getHeader(), packet.getBody(), requestTimeMillis, packet.getAuth().getAppKey()); if (!StringUtils.equals(reqSignature, validSignature)) { - LOGGER.error("invalid signature, request:{}, valid:{}", reqSignature, validSignature); + logger.error("invalid signature, request:{}, valid:{}", reqSignature, validSignature); return false; } } @@ -165,7 +164,7 @@ public void afterPropertiesSet() throws Exception { try { loadConfig(); } catch (Throwable e) { - LOGGER.error("load authencation keys error", e); + logger.error("load authencation keys error", e); } }*/ diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/Timeouts.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/Timeouts.java index c4dcb744..615d6a24 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/Timeouts.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/Timeouts.java @@ -18,8 +18,8 @@ import com.google.protobuf.Descriptors; import com.webank.ai.fate.api.networking.proxy.Proxy; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Component @@ -33,14 +33,14 @@ public class Timeouts { public static final long MAX_PACKET_INTERVAL_TIMEOUT = MAX_OVERALL_TIMEOUT; public static final long DEFAULT_PACKET_INTERVAL_TIMEOUT = 20L * 1000; // 20s - private static final Logger LOGGER = LogManager.getLogger(Timeouts.class); + private static final Logger logger = LoggerFactory.getLogger(Timeouts.class); public boolean isTimeout(long timeout, long startTimestamp) { return isTimeout(timeout, startTimestamp, System.currentTimeMillis()); } public boolean isTimeout(long timeout, long startTimestamp, long endTimestamp) { - /*LOGGER.info("timeout: {}, start: {}, end: {}, cal: {}, result: {}", + /*logger.info("timeout: {}, start: {}, end: {}, cal: {}, result: {}", timeout, startTimestamp, endTimestamp, (endTimestamp - startTimestamp), ((endTimestamp - startTimestamp) > timeout));*/ return (endTimestamp - startTimestamp) > timeout; @@ -100,7 +100,7 @@ private long getTimeout(Proxy.Metadata metadata, String timeoutFieldName, long d private long getValue(long proposedValue, long defaultValue, long minValue, long maxValue) { long result = proposedValue; - // LOGGER.info("proposed: {}, default: {}, min: {}, max: {}", proposedValue, defaultValue, minValue, maxValue); + // logger.info("proposed: {}, default: {}, min: {}, max: {}", proposedValue, defaultValue, minValue, maxValue); if (proposedValue < minValue) { result = defaultValue; diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/ToStringUtils.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/ToStringUtils.java index 5499ae75..bfc1e581 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/ToStringUtils.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/ToStringUtils.java @@ -19,8 +19,8 @@ import com.google.protobuf.Message; import com.google.protobuf.util.JsonFormat; import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -38,7 +38,7 @@ public class ToStringUtils { private static final String SEMICOLON = ";"; private static final String COMMA = ","; - private static final Logger LOGGER = LogManager.getLogger(ToStringUtils.class); + private static final Logger logger = LoggerFactory.getLogger(ToStringUtils.class); private JsonFormat.Printer protoPrinter = JsonFormat.printer() .preservingProtoFieldNames() @@ -48,14 +48,14 @@ public String toOneLineString(Message target) { String result = "[null]"; if (target == null) { - LOGGER.info("target is null"); + logger.info("target is null"); return result; } try { result = protoPrinter.print(target); } catch (Exception e) { - LOGGER.info(ExceptionUtils.getStackTrace(e)); + logger.info(ExceptionUtils.getStackTrace(e)); } return result; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ErrorMessageUtil.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ErrorMessageUtil.java index 257e184d..fbc38167 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ErrorMessageUtil.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ErrorMessageUtil.java @@ -2,7 +2,6 @@ import com.alibaba.csp.sentinel.slots.block.BlockException; import com.webank.ai.fate.serving.proxy.exceptions.*; -import com.webank.ai.fate.serving.proxy.exceptions.ErrorCode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/GetSystemInfo.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/GetSystemInfo.java index 5cb52262..67deb71d 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/GetSystemInfo.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/GetSystemInfo.java @@ -18,8 +18,8 @@ import com.sun.management.OperatingSystemMXBean; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.lang.management.ManagementFactory; import java.net.Inet4Address; @@ -30,7 +30,7 @@ public class GetSystemInfo { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(GetSystemInfo.class); public static String localIp; @@ -62,7 +62,7 @@ public static String getLocalIp() { } } } catch (Throwable e) { - LOGGER.error(e.getMessage(), e); + logger.error(e.getMessage(), e); } return ""; } @@ -84,7 +84,7 @@ private static String getIpByEthNum(String ethNum) { } } } catch (SocketException e) { - LOGGER.error(e.getMessage(), e); + logger.error(e.getMessage(), e); } return ""; } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/CharacterEncodingFilter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/CharacterEncodingFilter.java index 6734b9b4..bb730630 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/CharacterEncodingFilter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/CharacterEncodingFilter.java @@ -1,7 +1,6 @@ package com.webank.ai.fate.serving.proxy.config; import javax.servlet.*; -import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java index 9f3a2a4e..0c506f70 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java @@ -1,26 +1,19 @@ package com.webank.ai.fate.serving.proxy.config; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.servlet.FilterRegistrationBean; -import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.web.context.request.async.TimeoutCallableProcessingInterceptor; import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.servlet.ServletContext; -import java.nio.charset.Charset; -import java.util.ArrayList; import java.util.concurrent.ThreadPoolExecutor; @Configuration diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorCode.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorCode.java index b1d14fb5..fff44a09 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorCode.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorCode.java @@ -1,7 +1,5 @@ package com.webank.ai.fate.serving.proxy.exceptions; -import com.alibaba.csp.sentinel.slots.block.BlockException; - public class ErrorCode { public static String PARAM_ERROR ="100"; //参数错误 diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/GlobalExceptionHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/GlobalExceptionHandler.java index 27f6c788..91d0fa17 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/GlobalExceptionHandler.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/GlobalExceptionHandler.java @@ -2,20 +2,16 @@ import com.webank.ai.fate.serving.proxy.common.ErrorMessageUtil; -import com.webank.ai.fate.serving.proxy.common.ResponseResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; -import java.sql.SQLIntegrityConstraintViolationException; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.RejectedExecutionException; /** * 统一异常样处理类 diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java index 9e03e8e7..a7381f19 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java @@ -2,17 +2,15 @@ import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; -import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; import com.google.common.collect.Lists; import com.webank.ai.fate.serving.proxy.common.ErrorMessageUtil; -import com.webank.ai.fate.serving.proxy.exceptions.*; +import com.webank.ai.fate.serving.proxy.exceptions.ErrorCode; +import com.webank.ai.fate.serving.proxy.exceptions.ShowDownRejectException; import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcConnectionPool; -import com.webank.ai.fate.serving.proxy.rpc.router.RouterInterface; import io.grpc.stub.AbstractStub; -import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OverLoadLogger.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OverLoadLogger.java index 8e65ae11..5ad5fd7a 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OverLoadLogger.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OverLoadLogger.java @@ -4,11 +4,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.text.DateFormat; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - /** * @Description TODO * @Author diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyService.java index 069ea4c7..0a53d9f2 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyService.java @@ -6,9 +6,7 @@ package com.webank.ai.fate.serving.proxy.rpc.core; - import java.lang.annotation.*; -import java.util.List; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java index 51e173f3..a9f12008 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java @@ -2,7 +2,6 @@ import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; - import org.apache.commons.pool2.BasePooledObjectFactory; import org.apache.commons.pool2.PooledObject; import org.apache.commons.pool2.impl.DefaultEvictionPolicy; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterRequestHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterRequestHandler.java index 3785c40f..250afc2e 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterRequestHandler.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterRequestHandler.java @@ -3,15 +3,17 @@ package com.webank.ai.fate.serving.proxy.rpc.grpc; import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.serving.proxy.rpc.core.*; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import com.webank.ai.fate.serving.proxy.rpc.core.Context; +import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.proxy.rpc.core.ProxyServiceRegister; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class InterRequestHandler extends ProxyRequestHandler { - private static final Logger logger = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(InterRequestHandler.class); @Autowired ProxyServiceRegister proxyServiceRegister; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraRequestHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraRequestHandler.java index 3788ae65..2b14f5c8 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraRequestHandler.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraRequestHandler.java @@ -6,14 +6,14 @@ import com.webank.ai.fate.serving.proxy.rpc.core.Context; import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; import com.webank.ai.fate.serving.proxy.rpc.core.ProxyServiceRegister; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class IntraRequestHandler extends ProxyRequestHandler { - private static final Logger logger = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(IntraRequestHandler.class); @Autowired ProxyServiceRegister proxyServiceRegister; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java index ff372fb6..e70e970a 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java @@ -2,7 +2,6 @@ package com.webank.ai.fate.serving.proxy.rpc.grpc; -import com.google.common.collect.Maps; import com.webank.ai.fate.api.networking.proxy.DataTransferServiceGrpc; import com.webank.ai.fate.api.networking.proxy.Proxy; import com.webank.ai.fate.register.annotions.RegisterService; @@ -10,13 +9,11 @@ import com.webank.ai.fate.serving.proxy.rpc.core.*; import io.grpc.stub.StreamObserver; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public abstract class ProxyRequestHandler extends DataTransferServiceGrpc.DataTransferServiceImplBase { - private static final Logger logger = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(ProxyRequestHandler.class); public abstract ProxyServiceRegister getProxyServiceRegister(); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ServiceExceptionHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ServiceExceptionHandler.java index a109d518..ca3ede76 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ServiceExceptionHandler.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ServiceExceptionHandler.java @@ -5,7 +5,7 @@ import org.slf4j.LoggerFactory; public class ServiceExceptionHandler implements ServerInterceptor { - private static final Logger LOGGER = LoggerFactory.getLogger(ServiceExceptionHandler.class); + private static final Logger logger = LoggerFactory.getLogger(ServiceExceptionHandler.class); @Override public ServerCall.Listener interceptCall(ServerCall call, @@ -17,7 +17,7 @@ public void onHalfClose() { try { super.onHalfClose(); } catch (Exception e) { - LOGGER.info("ServiceException:", e); + logger.info("ServiceException:", e); call.close(Status.INTERNAL .withCause(e) .withDescription(e.getMessage()), new Metadata()); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java index 19c1cd5d..00351336 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java @@ -5,15 +5,15 @@ import com.webank.ai.fate.serving.proxy.rpc.core.Context; import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; import com.webank.ai.fate.serving.proxy.rpc.core.OutboundPackage; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.List; import java.util.concurrent.ThreadLocalRandom; public abstract class BaseServingRouter implements RouterInterface{ - private static final Logger logger = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(BaseServingRouter.class); public abstract List getRouterInfoList(Context context, InboundPackage inboundPackage); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java index 83b4963d..ea51418e 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java @@ -13,8 +13,8 @@ import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; import com.webank.ai.fate.serving.proxy.utils.FileUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; @@ -45,7 +45,7 @@ public class ConfigFileBasedServingRouter extends BaseServingRouter implements private String lastFileMd5; - private static final Logger logger = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(ConfigFileBasedServingRouter.class); private Map> allow; private Map> deny; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/DefaultServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/DefaultServingRouter.java index 7154ce78..b94a2542 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/DefaultServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/DefaultServingRouter.java @@ -8,8 +8,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Service; /** diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteTypeConvertor.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteTypeConvertor.java index ac32e17b..2bd44941 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteTypeConvertor.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteTypeConvertor.java @@ -1,6 +1,5 @@ package com.webank.ai.fate.serving.proxy.rpc.router; -import com.webank.ai.fate.serving.proxy.common.Dict; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java index 76304bd7..518ff884 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java @@ -8,9 +8,8 @@ import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcType; import com.webank.ai.fate.serving.proxy.utils.FederatedModelUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -40,7 +39,7 @@ public static void setZkRouterService(RouterService zkRouterService) { private static RouterService zkRouterService; - private static final Logger logger = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(ZkServingRouter.class); @Override public RouteType getRouteType(){ diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java index a5eab54a..0c630846 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java @@ -13,8 +13,8 @@ import com.webank.ai.fate.serving.proxy.rpc.router.RouterInfo; import io.grpc.ManagedChannel; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -36,7 +36,7 @@ public class InferenceService extends AbstractServiceAdaptor { - Logger logger = LogManager.getLogger(); + Logger logger = LoggerFactory.getLogger(InferenceService.class); @Autowired GrpcConnectionPool grpcConnectionPool; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java index 676e1a5b..1f0bed34 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java @@ -16,19 +16,18 @@ package com.webank.ai.fate.serving.proxy.security; -import com.webank.ai.fate.serving.proxy.utils.EncryptUtil; - import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.stream.JsonReader; import com.webank.ai.fate.api.networking.proxy.Proxy; +import com.webank.ai.fate.serving.proxy.utils.EncryptUtil; import com.webank.ai.fate.serving.proxy.utils.FileUtils; import com.webank.ai.fate.serving.proxy.utils.ToStringUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -42,7 +41,7 @@ @Component public class AuthUtils implements InitializingBean{ - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(AuthUtils.class); private static Map KEY_SECRET_MAP = new HashMap<>(); private static Map PARTYID_KEY_MAP = new HashMap<>(); private static int validRequestTimeoutSecond = 10; @@ -61,7 +60,7 @@ public class AuthUtils implements InitializingBean{ @Scheduled(fixedRate = 10000) public void loadConfig(){ - LOGGER.debug("start refreshed auth config..."); + logger.debug("start refreshed auth config..."); String fileMd5 = FileUtils.fileMd5(confFilePath); if(null != fileMd5 && fileMd5.equals(lastFileMd5)){ return; @@ -75,7 +74,7 @@ public void loadConfig(){ jsonReader = new JsonReader(new FileReader(confFilePath)); jsonObject = jsonParser.parse(jsonReader).getAsJsonObject(); } catch (FileNotFoundException e) { - LOGGER.error("File not found: {}", confFilePath); + logger.error("File not found: {}", confFilePath); throw new RuntimeException(e); } finally { if (jsonReader != null) { @@ -97,7 +96,7 @@ public void loadConfig(){ KEY_SECRET_MAP.put(allowKey.get("app_key").toString(), allowKey.get("app_secret").toString()); PARTYID_KEY_MAP.put(allowKey.get("party_id").toString(), allowKey.get("app_key").toString()); } - LOGGER.info("refreshed auth cfg using file {}.", confFilePath); + logger.info("refreshed auth cfg using file {}.", confFilePath); } private String getSecret(String appKey) { @@ -112,7 +111,7 @@ private String calSignature(Proxy.Metadata header, Proxy.Data body, long timesta String signature = ""; String appSecret = getSecret(appKey); if (StringUtils.isEmpty(appSecret)) { - LOGGER.error("appSecret not found"); + logger.error("appSecret not found"); return signature; } String encryptText = String.valueOf(timestamp) + "\n" @@ -147,14 +146,14 @@ public boolean checkAuthentication(Proxy.Packet packet) throws Exception { long currentTimeMillis = System.currentTimeMillis(); long requestTimeMillis = packet.getAuth().getTimestamp(); if (currentTimeMillis >= (requestTimeMillis + validRequestTimeoutSecond * 1000)) { - LOGGER.error("receive an expired request, currentTimeMillis:{}, requestTimeMillis{}.", currentTimeMillis, requestTimeMillis); + logger.error("receive an expired request, currentTimeMillis:{}, requestTimeMillis{}.", currentTimeMillis, requestTimeMillis); return false; } // check signature String reqSignature = packet.getAuth().getSignature(); String validSignature = calSignature(packet.getHeader(), packet.getBody(), requestTimeMillis, packet.getAuth().getAppKey()); if (!StringUtils.equals(reqSignature, validSignature)) { - LOGGER.error("invalid signature, request:{}, valid:{}", reqSignature, validSignature); + logger.error("invalid signature, request:{}, valid:{}", reqSignature, validSignature); return false; } } @@ -167,7 +166,7 @@ public void afterPropertiesSet() throws Exception { try { loadConfig(); } catch (Throwable e) { - LOGGER.error("load authencation keys fail. ", e); + logger.error("load authencation keys fail. ", e); } } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/FileUtils.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/FileUtils.java index 0157c36e..6bc54006 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/FileUtils.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/FileUtils.java @@ -1,15 +1,15 @@ package com.webank.ai.fate.serving.proxy.utils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.DigestUtils; + import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.springframework.util.DigestUtils; - public class FileUtils { - private static final Logger LOGGER = LogManager.getLogger(FileUtils.class); + private static final Logger logger = LoggerFactory.getLogger(FileUtils.class); public static String fileMd5(String filePath) { InputStream in = null; @@ -17,13 +17,13 @@ public static String fileMd5(String filePath) { in = new FileInputStream(filePath); return DigestUtils.md5DigestAsHex(in); } catch (Exception e) { - LOGGER.error(e); + logger.error(e.getMessage()); } finally { if (in != null) { try { in.close(); } catch (IOException e) { - LOGGER.error(e); + logger.error(e.getMessage()); } } } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/ToStringUtils.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/ToStringUtils.java index b0c7d941..dc968257 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/ToStringUtils.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/ToStringUtils.java @@ -19,8 +19,8 @@ import com.google.protobuf.Message; import com.google.protobuf.util.JsonFormat; import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -38,7 +38,7 @@ public class ToStringUtils { private static final String SEMICOLON = ";"; private static final String COMMA = ","; - private static final Logger LOGGER = LogManager.getLogger(ToStringUtils.class); + private static final Logger logger = LoggerFactory.getLogger(ToStringUtils.class); private JsonFormat.Printer protoPrinter = JsonFormat.printer() .preservingProtoFieldNames() @@ -48,14 +48,14 @@ public String toOneLineString(Message target) { String result = "[null]"; if (target == null) { - LOGGER.info("target is null"); + logger.info("target is null"); return result; } try { result = protoPrinter.print(target); } catch (Exception e) { - LOGGER.info(ExceptionUtils.getStackTrace(e)); + logger.info(ExceptionUtils.getStackTrace(e)); } return result; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index 9db94991..541a5149 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -35,15 +35,14 @@ import io.grpc.ServerBuilder; import io.grpc.ServerInterceptors; import org.apache.commons.cli.*; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.SpringApplication; import org.springframework.context.ApplicationContext; import java.io.File; import java.io.IOException; -import java.util.ArrayList; import java.util.HashMap; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; @@ -52,7 +51,7 @@ import java.util.concurrent.TimeUnit; public class ServingServer implements InitializingBean { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(ServingServer.class); static ApplicationContext applicationContext; private Server server; private boolean useRegister = false; @@ -65,7 +64,6 @@ public ServingServer(String confPath) { this.confPath = new File(confPath).getAbsolutePath(); System.setProperty(Dict.CONFIGPATH, confPath); new Configuration(confPath).load(); - new com.webank.ai.eggroll.core.utils.Configuration(confPath).load(); if(Configuration.getProperty(Dict.ACL_USERNAME)!=null) { System.setProperty(Dict.ACL_USERNAME, Configuration.getProperty(Dict.ACL_USERNAME)); } @@ -117,11 +115,11 @@ private void start(String[] args) throws IOException { serverBuilder.addService(ServerInterceptors.intercept(applicationContext.getBean(ModelService.class), new ServiceExceptionHandler(), new ServiceOverloadProtectionHandle()), ModelService.class); serverBuilder.addService(ServerInterceptors.intercept(applicationContext.getBean(ProxyService.class), new ServiceExceptionHandler(), new ServiceOverloadProtectionHandle()), ProxyService.class); server = serverBuilder.build(); - LOGGER.info("server started listening on port: {}, use configuration: {}", port, this.confPath); + logger.info("server started listening on port: {}, use configuration: {}", port, this.confPath); server.start(); String userRegisterString = Configuration.getProperty(Dict.USE_REGISTER); useRegister = Boolean.valueOf(userRegisterString); - LOGGER.info("serving useRegister {}", useRegister); + logger.info("serving useRegister {}", useRegister); if (useRegister) { ZookeeperRegistry zookeeperRegistry = applicationContext.getBean(ZookeeperRegistry.class); zookeeperRegistry.subProject(Dict.PROPERTY_PROXY_ADDRESS); @@ -140,7 +138,7 @@ private void start(String[] args) throws IOException { } } } catch (Throwable e) { - LOGGER.error("parse interface weight error", e); + logger.error("parse interface weight error", e); } }); @@ -159,9 +157,9 @@ private void start(String[] args) throws IOException { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { - LOGGER.info("*** shutting down gRPC server since JVM is shutting down"); + logger.info("*** shutting down gRPC server since JVM is shutting down"); ServingServer.this.stop(); - LOGGER.info("*** server shut down"); + logger.info("*** server shut down"); } }); } @@ -174,7 +172,7 @@ private void stop() { Set urls = Sets.newHashSet(); urls.addAll(registered); urls.forEach(url -> { - LOGGER.info("unregister {}", url); + logger.info("unregister {}", url); zookeeperRegistry.unregister(url); }); @@ -196,24 +194,10 @@ private void blockUntilShutdown() throws InterruptedException { } private void initialize() { - this.initializeClientPool(); HttpClientPool.initPool(); InferenceWorkerManager.prestartAllCoreThreads(); } - private void initializeClientPool() { - ArrayList serverAddress = new ArrayList<>(); - serverAddress.add(Configuration.getProperty("proxy")); - serverAddress.add(Configuration.getProperty("roll")); - new Thread(new Runnable() { - @Override - public void run() { - com.webank.ai.eggroll.core.network.grpc.client.ClientPool.init_pool(serverAddress); - } - }).start(); - LOGGER.info("Finish init client pool"); - } - @Override public void afterPropertiesSet() throws Exception { diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java b/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java index c165e720..33c8d812 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java @@ -1,10 +1,11 @@ package com.webank.ai.fate.serving; -import com.codahale.metrics.*; +import com.codahale.metrics.ConsoleReporter; +import com.codahale.metrics.Counter; +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.jmx.JmxReporter; -import com.webank.ai.fate.register.loadbalance.LoadBalancer; -import com.webank.ai.fate.register.loadbalance.RandomLoadBalance; import com.webank.ai.fate.register.router.DefaultRouterService; import com.webank.ai.fate.register.router.RouterService; import com.webank.ai.fate.register.zookeeper.ZookeeperRegistry; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/DTest.java b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/DTest.java index 0414807a..5149db2b 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/DTest.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/DTest.java @@ -16,16 +16,15 @@ package com.webank.ai.fate.serving.adapter.dataaccess; -import com.webank.ai.eggroll.core.utils.ObjectTransform; - import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.core.bean.ReturnResult; import com.webank.ai.fate.serving.core.constant.InferenceRetCode; +import com.webank.ai.fate.serving.core.utils.ObjectTransform; import com.webank.ai.fate.serving.utils.HttpClientPool; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.List; @@ -33,7 +32,7 @@ import java.util.Optional; public class DTest implements FeatureData { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(DTest.class); @Override public ReturnResult getData(Context context, Map featureIds) { diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/TestFile.java b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/TestFile.java index de7d655b..e8d28885 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/TestFile.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/TestFile.java @@ -22,8 +22,8 @@ import com.webank.ai.fate.serving.core.bean.ReturnResult; import com.webank.ai.fate.serving.core.constant.InferenceRetCode; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.nio.file.Files; import java.nio.file.Paths; @@ -32,7 +32,7 @@ import java.util.Map; public class TestFile implements FeatureData { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(TestFile.class); @Override public ReturnResult getData(Context context, Map featureIds) { @@ -49,7 +49,7 @@ public ReturnResult getData(Context context, Map featureIds) { returnResult.setData(data); returnResult.setRetcode(InferenceRetCode.OK); } catch (Exception ex) { - LOGGER.error(ex); + logger.error(ex.getMessage()); returnResult.setRetcode(InferenceRetCode.GET_FEATURE_FAILED); } return returnResult; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/CommonPostProcessing.java b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/CommonPostProcessing.java index 353592d6..3c078d02 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/CommonPostProcessing.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/CommonPostProcessing.java @@ -6,17 +6,16 @@ import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.core.bean.ReturnResult; import com.webank.ai.fate.serving.core.constant.InferenceRetCode; -import com.webank.ai.fate.serving.host.DefaultHostInferenceProvider; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; - -import java.util.*; +import java.util.HashMap; +import java.util.Map; public class CommonPostProcessing implements PostProcessing { - private static final Logger LOGGER = LogManager.getLogger(CommonPostProcessing.class); + private static final Logger logger = LoggerFactory.getLogger(CommonPostProcessing.class); @Override public PostProcessingResult getResult(Context context, Map featureData, Map modelResult) { diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/PassPreProcessing.java b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/PassPreProcessing.java index 6fb7eea8..f6b5a5c9 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/PassPreProcessing.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/PassPreProcessing.java @@ -16,10 +16,10 @@ package com.webank.ai.fate.serving.adapter.processing; -import com.webank.ai.eggroll.core.utils.ObjectTransform; import com.webank.ai.fate.serving.bean.PreProcessingResult; import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.bean.Dict; +import com.webank.ai.fate.serving.core.utils.ObjectTransform; import java.util.Arrays; import java.util.HashMap; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java b/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java index 5f49edbd..98f92dfb 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java @@ -17,7 +17,6 @@ package com.webank.ai.fate.serving.bean; import com.alibaba.fastjson.JSON; -import com.fasterxml.jackson.databind.ObjectMapper; import com.webank.ai.fate.serving.core.bean.Request; import com.webank.ai.fate.serving.utils.InferenceUtils; import org.apache.commons.lang3.StringUtils; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java index 7629a890..b5706a0a 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java @@ -18,8 +18,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Maps; - -import com.webank.ai.eggroll.core.utils.ObjectTransform; import com.webank.ai.fate.serving.adapter.processing.PostProcessing; import com.webank.ai.fate.serving.adapter.processing.PreProcessing; import com.webank.ai.fate.serving.bean.InferenceRequest; @@ -28,13 +26,14 @@ import com.webank.ai.fate.serving.bean.PreProcessingResult; import com.webank.ai.fate.serving.core.bean.*; import com.webank.ai.fate.serving.core.constant.InferenceRetCode; +import com.webank.ai.fate.serving.core.utils.ObjectTransform; import com.webank.ai.fate.serving.federatedml.PipelineTask; import com.webank.ai.fate.serving.interfaces.ModelManager; import com.webank.ai.fate.serving.manger.InferenceWorkerManager; import com.webank.ai.fate.serving.utils.InferenceUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -44,7 +43,7 @@ @Service public class DefaultGuestInferenceProvider implements GuestInferenceProvider, InitializingBean { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(DefaultGuestInferenceProvider.class); @Autowired ModelManager modelManager; @Autowired @@ -99,7 +98,7 @@ public ReturnResult runInference(Context context, InferenceRequest inferenceRequ return inferenceResult; } - LOGGER.info("use model to inference for {}, id: {}, version: {}", inferenceRequest.getAppid(), modelNamespace, modelName); + logger.info("use model to inference for {}, id: {}, version: {}", inferenceRequest.getAppid(), modelNamespace, modelName); Map rawFeatureData = inferenceRequest.getFeatureData(); if (rawFeatureData == null) { @@ -114,7 +113,7 @@ public ReturnResult runInference(Context context, InferenceRequest inferenceRequ preProcessingResult = getPreProcessingFeatureData(context, rawFeatureData); } catch (Exception ex) { - LOGGER.error("feature data preprocessing failed", ex); + logger.error("feature data preprocessing failed", ex); inferenceResult.setRetcode(InferenceRetCode.INVALID_FEATURE + 1000); inferenceResult.setRetmsg(ex.getMessage()); return inferenceResult; @@ -147,7 +146,7 @@ public ReturnResult runInference(Context context, InferenceRequest inferenceRequ postProcessingResult = getPostProcessedResult(context, featureData, modelResult); inferenceResult = postProcessingResult.getProcessingResult(); } catch (Exception ex) { - LOGGER.error("model result postprocessing failed", ex); + logger.error("model result postprocessing failed", ex); if(inferenceResult!=null) { inferenceResult.setRetcode(InferenceRetCode.COMPUTE_ERROR); inferenceResult.setRetmsg(ex.getMessage()); @@ -201,7 +200,7 @@ private PreProcessingResult getPreProcessingFeatureData(Context context, Map return featureData.getData(context,featureIds); } catch (Exception ex) { defaultReturnResult.setRetcode(InferenceRetCode.GET_FEATURE_FAILED); - LOGGER.error("get feature data error:", ex); + logger.error("get feature data error:", ex); return defaultReturnResult; } } @@ -84,7 +84,7 @@ public ReturnResult federatedInference(Context context, HostFederatedParams fede returnResult.setRetmsg("Can not found model."); return returnResult; } - LOGGER.info("use model to inference on {} {}, id: {}, version: {}", party.getRole(), party.getPartyId(), modelInfo.getNamespace(), modelInfo.getName()); + logger.info("use model to inference on {} {}, id: {}, version: {}", party.getRole(), party.getPartyId(), modelInfo.getNamespace(), modelInfo.getName()); Map predictParams = new HashMap<>(8); predictParams.put(Dict.FEDERATED_PARAMS, federatedParams); @@ -105,14 +105,14 @@ public ReturnResult federatedInference(Context context, HostFederatedParams fede returnResult.setRetcode(getFeatureDataResult.getRetcode()); } } catch (Exception ex) { - LOGGER.info("federatedInference error:", ex); + logger.info("federatedInference error:", ex); returnResult.setRetcode(InferenceRetCode.SYSTEM_ERROR); returnResult.setRetmsg(ex.getMessage()); } long endTime = System.currentTimeMillis(); long federatedInferenceElapsed = endTime - startTime; // InferenceUtils.logInference(context ,federatedParams, party, federatedRoles, returnResult, federatedInferenceElapsed, false, billing); - LOGGER.info(returnResult.getData()); + logger.info(JSONObject.toJSONString(returnResult.getData())); return returnResult; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelCache.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelCache.java index 0257f103..145cb65f 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelCache.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelCache.java @@ -23,8 +23,8 @@ import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.federatedml.PipelineTask; import com.webank.ai.fate.serving.interfaces.ModelCache; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.util.Set; @@ -33,7 +33,7 @@ @Service public class DefaultModelCache implements ModelCache { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(DefaultModelCache.class); private LoadingCache modelCache; public DefaultModelCache() { @@ -59,7 +59,7 @@ public PipelineTask get(String modelKey) { try { return modelCache.get(modelKey); } catch (ExecutionException ex) { - LOGGER.error(ex); + logger.error(ex.getMessage()); return null; } } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java index c88277cd..f7b45c03 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java @@ -17,9 +17,7 @@ package com.webank.ai.fate.serving.manger; -import com.webank.ai.fate.register.common.Constants; import com.webank.ai.fate.register.provider.FateServer; -import com.webank.ai.fate.register.url.URL; import com.webank.ai.fate.register.zookeeper.ZookeeperRegistry; import com.webank.ai.fate.serving.bean.ModelNamespaceData; import com.webank.ai.fate.serving.core.bean.*; @@ -29,12 +27,11 @@ import com.webank.ai.fate.serving.interfaces.ModelCache; import com.webank.ai.fate.serving.interfaces.ModelManager; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import sun.security.provider.MD5; import java.io.File; import java.util.HashMap; @@ -44,7 +41,7 @@ @Service public class DefaultModelManager implements ModelManager, InitializingBean { - private final Logger logger = LogManager.getLogger(); + private final Logger logger = LoggerFactory.getLogger(DefaultModelManager.class); private final AtomicLong lastCacheChanged = new AtomicLong(); @Autowired(required = false) ZookeeperRegistry zookeeperRegistry; @@ -147,7 +144,7 @@ public ReturnResult publishLoadModel(Context context,FederatedParty federatedPar logger.info("load the model successfully"); return returnResult; } catch (Exception ex) { - logger.error(ex); + logger.error(ex.getMessage()); ex.printStackTrace(); returnResult.setRetcode(InferenceRetCode.SYSTEM_ERROR); } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/InferenceWorkerManager.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/InferenceWorkerManager.java index 5501a1e9..a9253880 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/InferenceWorkerManager.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/InferenceWorkerManager.java @@ -19,14 +19,14 @@ import com.webank.ai.fate.serving.core.bean.Configuration; import com.webank.ai.fate.serving.core.bean.Dict; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; public class InferenceWorkerManager { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(InferenceWorkerManager.class); private static ThreadPoolExecutor threadPoolExecutor; static { @@ -56,7 +56,7 @@ static class InferenceWorkerThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, "inference-worker-thread-" + mThreadNum.getAndIncrement()); - LOGGER.info("{} thead has ben created.", t.getName()); + logger.info("{} thead has ben created.", t.getName()); return t; } } @@ -64,7 +64,7 @@ public Thread newThread(Runnable r) { public static class InferenceWorkerThreadRejectedPolicy implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { - LOGGER.info("{} rejected.", r.toString()); + logger.info("{} rejected.", r.toString()); } } } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java index f936c1ac..76004b2d 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java @@ -17,8 +17,6 @@ package com.webank.ai.fate.serving.manger; import com.alibaba.fastjson.JSONObject; -import com.webank.ai.eggroll.core.storage.dtable.DTable; -import com.webank.ai.eggroll.core.storage.dtable.DTableFactory; import com.webank.ai.fate.api.mlmodel.manager.ModelServiceProto; import com.webank.ai.fate.register.router.RouterService; import com.webank.ai.fate.register.url.URL; @@ -29,8 +27,8 @@ import com.webank.ai.fate.serving.federatedml.PipelineTask; import com.webank.ai.fate.serving.utils.HttpClientPool; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -39,7 +37,7 @@ @Component public class ModelUtils { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(ModelUtils.class); private static final String modelKeySeparator = "&"; private static ModelUtils modelUtils; @@ -53,7 +51,7 @@ private void init() { } public static Map readModel(String name, String namespace) { - LOGGER.info("read model, name: {} namespace: {}", name, namespace); + logger.info("read model, name: {} namespace: {}", name, namespace); String requestUrl = ""; boolean useRegister = Boolean.valueOf(Configuration.getProperty(Dict.USE_REGISTER)); @@ -61,7 +59,7 @@ public static Map readModel(String name, String namespace) { URL url = URL.valueOf("flow/online/transfer"); List urls = modelUtils.routerService.router(url); if (urls == null || urls.isEmpty()) { - LOGGER.info("url not found, {}", url); + logger.info("url not found, {}", url); return null; } @@ -72,7 +70,7 @@ public static Map readModel(String name, String namespace) { } if (StringUtils.isBlank(requestUrl)) { - LOGGER.info("roll address not found"); + logger.info("roll address not found"); return null; } @@ -84,23 +82,23 @@ public static Map readModel(String name, String namespace) { String responseBody = HttpClientPool.transferPost(requestUrl, requestData); long end = System.currentTimeMillis(); - LOGGER.info("{}|{}|{}|{}", requestUrl, start, end, (end - start) + " ms"); + logger.info("{}|{}|{}|{}", requestUrl, start, end, (end - start) + " ms"); if (StringUtils.isEmpty(responseBody)) { - LOGGER.info("read model fail, {}, {}", name, namespace); + logger.info("read model fail, {}, {}", name, namespace); return null; } JSONObject responseData = JSONObject.parseObject(responseBody); if (responseData.getInteger("retcode") != 0) { - LOGGER.info("read model fail, {}, {}, {}", name, namespace, responseData.getString("retmsg")); + logger.info("read model fail, {}, {}, {}", name, namespace, responseData.getString("retmsg")); return null; } Map resultMap = new HashMap<>(); Map dataMap = responseData.getJSONObject("data"); if (dataMap == null || dataMap.isEmpty()) { - LOGGER.info("read model fail, {}, {}, {}", name, namespace, dataMap); + logger.info("read model fail, {}, {}, {}", name, namespace, dataMap); return null; } @@ -114,7 +112,7 @@ public static Map readModel(String name, String namespace) { public static PipelineTask loadModel(String name, String namespace) { Map modelBytes = readModel(name, namespace); if (modelBytes == null || modelBytes.size() == 0) { - LOGGER.info("loadModel error {} {}",name,namespace); + logger.info("loadModel error {} {}",name,namespace); return null; } PipelineTask pipelineTask = new PipelineTask(); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ReentrantReadWriteMapPool.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ReentrantReadWriteMapPool.java index 941b700a..63855723 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ReentrantReadWriteMapPool.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ReentrantReadWriteMapPool.java @@ -18,8 +18,8 @@ import com.webank.ai.fate.serving.core.bean.BaseMapPool; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Map; @@ -29,7 +29,7 @@ public class ReentrantReadWriteMapPool extends BaseMapPool { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(ReentrantReadWriteMapPool.class); private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock readLock = lock.readLock(); private final Lock writeLock = lock.writeLock(); @@ -51,7 +51,7 @@ public void put(K key, V value) { try { pool.put(key, value); } catch (Exception ex) { - LOGGER.error(ex); + logger.error(ex.getMessage()); } finally { this.writeLock.unlock(); } @@ -63,7 +63,7 @@ public void putIfAbsent(K key, V value) { try { pool.putIfAbsent(key, value); } catch (Exception ex) { - LOGGER.error(ex); + logger.error(ex.getMessage()); } finally { this.writeLock.unlock(); } @@ -75,7 +75,7 @@ public void putAll(Map kv) { try { pool.putAll(kv); } catch (Exception ex) { - LOGGER.error(ex); + logger.error(ex.getMessage()); } finally { this.writeLock.unlock(); } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java index 701c842c..996abe27 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java @@ -18,21 +18,19 @@ import com.alibaba.fastjson.JSON; import com.codahale.metrics.MetricRegistry; -import com.codahale.metrics.Timer; import com.google.protobuf.ByteString; -import com.webank.ai.eggroll.core.utils.ObjectTransform; import com.webank.ai.fate.api.serving.InferenceServiceGrpc; import com.webank.ai.fate.api.serving.InferenceServiceProto.InferenceMessage; - import com.webank.ai.fate.register.annotions.RegisterService; import com.webank.ai.fate.serving.bean.InferenceRequest; import com.webank.ai.fate.serving.core.bean.*; import com.webank.ai.fate.serving.core.constant.InferenceRetCode; +import com.webank.ai.fate.serving.core.utils.ObjectTransform; import com.webank.ai.fate.serving.guest.GuestInferenceProvider; import com.webank.ai.fate.serving.utils.InferenceUtils; import io.grpc.stub.StreamObserver; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -40,8 +38,8 @@ @Service public class InferenceService extends InferenceServiceGrpc.InferenceServiceImplBase { - private static final Logger LOGGER = LogManager.getLogger(); - private static final Logger accessLOGGER = LogManager.getLogger(Dict.ACCESS); + private static final Logger logger = LoggerFactory.getLogger(InferenceService.class); + private static final Logger accesslogger = LoggerFactory.getLogger(Dict.ACCESS); @Autowired GuestInferenceProvider guestInferenceProvider; @Autowired @@ -105,7 +103,7 @@ private void inferenceServiceAction(InferenceMessage req, StreamObserver ServerCall.Listener interceptCall(ServerCall call, @@ -33,7 +33,7 @@ public void onHalfClose() { try { super.onHalfClose(); } catch (Exception e) { - LOGGER.info("ServiceException:", e); + logger.info("ServiceException:", e); call.close(Status.INTERNAL .withCause(e) .withDescription(e.getMessage()), new Metadata()); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ServiceOverloadProtectionHandle.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ServiceOverloadProtectionHandle.java index 9b1d1538..903fb8cf 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ServiceOverloadProtectionHandle.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ServiceOverloadProtectionHandle.java @@ -16,14 +16,13 @@ package com.webank.ai.fate.serving.service; -import com.webank.ai.fate.serving.core.bean.Dict; import io.grpc.*; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ServiceOverloadProtectionHandle implements ServerInterceptor { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(ServiceOverloadProtectionHandle.class); @Override public ServerCall.Listener interceptCall(ServerCall serverCall, Metadata metadata, ServerCallHandler serverCallHandler) { @@ -42,7 +41,7 @@ public void onHalfClose() { super.onHalfClose(); } catch (Exception e) { - LOGGER.info("ServiceException:", e); + logger.info("ServiceException:", e); serverCall.close(Status.CANCELLED.withCause(e).withDescription(e.getMessage()), metadata); } } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/utils/DTableUtils.java b/serving-server/src/main/java/com/webank/ai/fate/serving/utils/DTableUtils.java deleted file mode 100644 index 426de34b..00000000 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/utils/DTableUtils.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.serving.utils; - - -import com.webank.ai.eggroll.core.storage.dtable.DTableInfo; - -import com.webank.ai.fate.serving.core.bean.Dict; -import com.webank.ai.fate.serving.core.bean.FederatedRoles; -import com.webank.ai.fate.serving.core.manager.SceneUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.util.Arrays; -import java.util.Map; - -public class DTableUtils { - private static final Logger LOGGER = LogManager.getLogger(); - - public static DTableInfo genTableInfo(String tableName, String namespace, String role, String partyId, FederatedRoles federatedRoles, String dataType) { - - if (StringUtils.isEmpty(namespace)) { - namespace = getSceneNamespace(SceneUtils.genSceneKey(role, partyId, federatedRoles), dataType); - } - if (StringUtils.isEmpty(tableName)) { - Map versionInfo = VersionControl.getVersionInfo(namespace, "", "", Dict.BRANCH_MASTER); - if (versionInfo != null) { - tableName = versionInfo.get(Dict.COMMIT_ID); - } - } - return new DTableInfo(tableName, namespace); - } - - public static String getSceneNamespace(String sceneKey, String dataType) { - return StringUtils.join(Arrays.asList(sceneKey, dataType), "_"); - } -} diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/utils/HttpClientPool.java b/serving-server/src/main/java/com/webank/ai/fate/serving/utils/HttpClientPool.java index b5e006a8..e32cd64a 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/utils/HttpClientPool.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/utils/HttpClientPool.java @@ -16,9 +16,8 @@ package com.webank.ai.fate.serving.utils; -import com.webank.ai.eggroll.core.utils.ObjectTransform; - import com.webank.ai.fate.serving.core.bean.Dict; +import com.webank.ai.fate.serving.core.utils.ObjectTransform; import org.apache.http.HttpEntity; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; @@ -39,8 +38,8 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.util.EntityUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.security.KeyManagementException; @@ -50,7 +49,7 @@ import java.util.concurrent.TimeUnit; public class HttpClientPool { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(HttpClientPool.class); private static PoolingHttpClientConnectionManager poolConnManager; private static RequestConfig requestConfig; private static CloseableHttpClient httpClient; @@ -89,7 +88,7 @@ public static void initPool() { connectTimeout).build(); httpClient = getConnection(); } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException ex) { - LOGGER.error("init http client pool failed:", ex); + logger.error("init http client pool failed:", ex); } } @@ -145,14 +144,14 @@ private static String getResponse(HttpRequestBase request) { EntityUtils.consume(entity); return result; } catch (IOException ex) { - LOGGER.error("get http response failed:", ex); + logger.error("get http response failed:", ex); return null; } finally { try { if (response != null) response.close(); } catch (IOException ex) { - LOGGER.error("get http response failed:", ex); + logger.error("get http response failed:", ex); } } } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/utils/InferenceUtils.java b/serving-server/src/main/java/com/webank/ai/fate/serving/utils/InferenceUtils.java index 5a1c54ac..fb65ea2c 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/utils/InferenceUtils.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/utils/InferenceUtils.java @@ -17,13 +17,12 @@ package com.webank.ai.fate.serving.utils; - -import com.webank.ai.eggroll.core.utils.ObjectTransform; import com.webank.ai.fate.serving.core.bean.*; import com.webank.ai.fate.serving.core.manager.FederatedUtils; import com.webank.ai.fate.serving.core.utils.GetSystemInfo; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import com.webank.ai.fate.serving.core.utils.ObjectTransform; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Base64; import java.util.HashMap; @@ -31,9 +30,9 @@ import java.util.UUID; public class InferenceUtils { - private static final Logger LOGGER = LogManager.getLogger(); - private static final Logger inferenceAuditLogger = LogManager.getLogger(Dict.INFERENCE_AUDIT); - private static final Logger inferenceLogger = LogManager.getLogger(Dict.INFERENCE); + private static final Logger logger = LoggerFactory.getLogger(InferenceUtils.class); + private static final Logger inferenceAuditLogger = LoggerFactory.getLogger(Dict.INFERENCE_AUDIT); + private static final Logger inferenceLogger = LoggerFactory.getLogger(Dict.INFERENCE); public static String generateCaseid() { return UUID.randomUUID().toString().replace("-", ""); @@ -57,12 +56,12 @@ public static Object getClassByName(String classPath) { Class thisClass = Class.forName(classPath); return thisClass.getConstructor().newInstance(); } catch (ClassNotFoundException ex) { - LOGGER.error("Can not found this class: {}.", classPath); + logger.error("Can not found this class: {}.", classPath); } catch (NoSuchMethodException ex) { - LOGGER.error("Can not get this class({}) constructor.", classPath); + logger.error("Can not get this class({}) constructor.", classPath); } catch (Exception ex) { - LOGGER.error(ex); - LOGGER.error("Can not create class({}) instance.", classPath); + logger.error(ex.getMessage()); + logger.error("Can not create class({}) instance.", classPath); } return null; } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/utils/VersionControl.java b/serving-server/src/main/java/com/webank/ai/fate/serving/utils/VersionControl.java deleted file mode 100644 index 130a5a25..00000000 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/utils/VersionControl.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.serving.utils; - - -import com.webank.ai.eggroll.core.storage.dtable.DTable; -import com.webank.ai.eggroll.core.storage.dtable.DTableFactory; -import com.webank.ai.eggroll.core.utils.ObjectTransform; - -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.util.HashMap; -import java.util.Map; - -public class VersionControl { - private static final Logger LOGGER = LogManager.getLogger(); - - public static Map getVersionInfo(String namespace, String commitId, String tag, String branch) { - DTable versionDTable = getVersionTable(namespace); - if (!StringUtils.isEmpty(commitId)) { - return getVersionInfo(versionDTable, namespace, commitId); - } else if (!StringUtils.isEmpty(branch)) { - String branchCurrentCommit = getCurrentBranchCommit(versionDTable, branch); - if (StringUtils.isEmpty(branchCurrentCommit)) { - return null; - } - return getVersionInfo(versionDTable, namespace, branchCurrentCommit); - } else { - return null; - } - - } - - public static Map getVersionInfo(String namespace, String commitId) { - DTable versionDTable = getVersionTable(namespace); - return getVersionInfo(versionDTable, namespace, commitId); - } - - public static Map getVersionInfo(DTable versionDTable, String namespace, String commitId) { - byte[] tmp = versionDTable.get(commitId); - if (tmp == null) { - return null; - } - return (Map) ObjectTransform.json2Bean(new String(tmp), HashMap.class); - } - - public static DTable getVersionTable(String dataNamespace) { - DTable dataTable = DTableFactory.getDTable(dataNamespace, "version_control", 1); - return dataTable; - } - - public static String getCurrentBranchCommit(DTable versionDTable, String branchName) { - byte[] tmp = versionDTable.get(branchName); - if (tmp == null) { - return null; - } - return new String(tmp); - } -} From db70efc584b9dbdefc1bbabf804541b2e26a1e48 Mon Sep 17 00:00:00 2001 From: qianduoduo <1002502212@qq.com> Date: Fri, 27 Dec 2019 11:16:39 +0800 Subject: [PATCH 065/190] =?UTF-8?q?=E6=96=87=E6=A1=A3=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...50\347\275\262\346\226\207\346\241\243.docx" | Bin 0 -> 26575 bytes ...50\347\275\262\346\226\207\346\241\243.docx" | Bin 59610 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 "serving-server/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" delete mode 100644 "serving-server/doc/serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" diff --git "a/serving-server/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" "b/serving-server/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" new file mode 100644 index 0000000000000000000000000000000000000000..22977e48be943a85f48d5b333a772db4511249d7 GIT binary patch literal 26575 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 K(7naG-v0qQi-BVR From f37f1fe69c872220ab1b57952b763cd61c385a8d Mon Sep 17 00:00:00 2001 From: qianduoduo <1002502212@qq.com> Date: Fri, 27 Dec 2019 11:33:53 +0800 Subject: [PATCH 066/190] =?UTF-8?q?=E8=84=9A=E6=9C=AC=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serving-server/cluster-deploy/scripts/services.sh | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/serving-server/cluster-deploy/scripts/services.sh b/serving-server/cluster-deploy/scripts/services.sh index 40e9b281..df4b0c55 100644 --- a/serving-server/cluster-deploy/scripts/services.sh +++ b/serving-server/cluster-deploy/scripts/services.sh @@ -25,13 +25,8 @@ main() { action=$2 echo "--------------" echo "[INFO] processing: ${module}" - if [[ "${module}" == "apigateway" ]];then - cd ${module}/bin/ - bash service.sh ${action} - else - cd ${module} - bash service.sh ${action} - fi + cd ${module} + bash service.sh ${action} echo "--------------" echo } From 0553b5c48fbc65ad63d47041e7ed645ab96a99df Mon Sep 17 00:00:00 2001 From: qianduoduo <1002502212@qq.com> Date: Fri, 27 Dec 2019 17:45:53 +0800 Subject: [PATCH 067/190] =?UTF-8?q?=E8=84=9A=E6=9C=AC=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- serving-server/cluster-deploy/scripts/package.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/serving-server/cluster-deploy/scripts/package.sh b/serving-server/cluster-deploy/scripts/package.sh index d12ceb63..7ae90cb0 100644 --- a/serving-server/cluster-deploy/scripts/package.sh +++ b/serving-server/cluster-deploy/scripts/package.sh @@ -20,6 +20,7 @@ router_jar=fate-serving-router-*.jar common=$public_path/fate-serving/common router=router router_path=$fate_serving_path/router + if [ ! -d $common ]; then mkdir -p $common else @@ -28,6 +29,7 @@ fi cp jdk_install.sh $common cp redis_install.sh $common cp services.sh $fate_serving_path + cd $common echo "[INFO] jdk install" #sudo sh jdk_install.sh @@ -38,6 +40,7 @@ sudo rm -rf redis_install.sh cd redis/conf sudo sed -i.bak "s/# requirepass foobared/requirepass ${redis_password}/g" ./redis.conf sudo sed -i.bak "s/databases 16/databases 50/g" ./redis.conf + cd ${cwd}/../../../ echo "[INFO] mvn clean package start" mvn clean package -DskipTests @@ -108,6 +111,7 @@ fi cd $cwd echo "-------zkui_instal start" sh zkui_install.sh + sudo cp redis/service.sh $common/redis cp zk/service.sh $common/zookeeper* cp zkui/service.sh $common/zkui From cc7403299ee5fd3e4f5277d485256c16a05e7abb Mon Sep 17 00:00:00 2001 From: zengjice Date: Fri, 27 Dec 2019 22:35:13 +0800 Subject: [PATCH 068/190] fix bugs that federatedml proto is different with fate repo --- .../federatedml/model/HeteroFeatureBinning.java | 2 +- .../federatedml/model/HeteroSecureBoost.java | 2 +- proto/boosting-tree-model-meta.proto | 4 +++- proto/boosting-tree-model-param.proto | 1 + proto/data-io-meta.proto | 2 +- proto/feature-selection-meta.proto | 1 + proto/feature-selection-param.proto | 3 +++ proto/lr-model-meta.proto | 6 +++--- proto/lr-model-param.proto | 17 +++++++++++++++-- 9 files changed, 29 insertions(+), 9 deletions(-) diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFeatureBinning.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFeatureBinning.java index 0f538cbf..8ed41293 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFeatureBinning.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFeatureBinning.java @@ -48,7 +48,7 @@ public int initModel(byte[] protoMeta, byte[] protoParam) { this.splitPoints.put(key, splitPoints); } } catch (Exception ex) { - ex.printStackTrace(); + LOGGER.error("init model error:", ex); return StatusCode.ILLEGALDATA; } LOGGER.info("Finish init Feature Binning class"); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoost.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoost.java index e4a727cd..2b8496bc 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoost.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoost.java @@ -18,7 +18,7 @@ import com.google.common.collect.Maps; -import com.webank.ai.fate.core.mlmodel.buffer.BoostTreeModelParamMeta.BoostingTreeModelMeta; +import com.webank.ai.fate.core.mlmodel.buffer.BoostTreeModelMetaProto.BoostingTreeModelMeta; import com.webank.ai.fate.core.mlmodel.buffer.BoostTreeModelParamProto.BoostingTreeModelParam; import com.webank.ai.fate.core.mlmodel.buffer.BoostTreeModelParamProto.DecisionTreeModelParam; import com.webank.ai.fate.serving.core.bean.Context; diff --git a/proto/boosting-tree-model-meta.proto b/proto/boosting-tree-model-meta.proto index 4f8c0906..b48004ec 100644 --- a/proto/boosting-tree-model-meta.proto +++ b/proto/boosting-tree-model-meta.proto @@ -1,6 +1,8 @@ syntax = "proto3"; + package com.webank.ai.fate.core.mlmodel.buffer; -option java_outer_classname = "BoostTreeModelParamMeta"; +option java_outer_classname = "BoostTreeModelMetaProto"; + message ObjectiveMeta { string objective = 1; repeated double param = 2; diff --git a/proto/boosting-tree-model-param.proto b/proto/boosting-tree-model-param.proto index 5b977590..186e8cee 100644 --- a/proto/boosting-tree-model-param.proto +++ b/proto/boosting-tree-model-param.proto @@ -1,4 +1,5 @@ syntax = "proto3"; + package com.webank.ai.fate.core.mlmodel.buffer; option java_outer_classname = "BoostTreeModelParamProto"; diff --git a/proto/data-io-meta.proto b/proto/data-io-meta.proto index 86829a9b..ffa12db1 100644 --- a/proto/data-io-meta.proto +++ b/proto/data-io-meta.proto @@ -22,7 +22,7 @@ message DataIOMeta { bool tag_with_value = 4; string tag_value_delimitor = 5; bool with_label = 6; - int32 label_idx = 7; + string label_name = 7; string label_type = 8; string output_format = 9; ImputerMeta imputer_meta = 10; diff --git a/proto/feature-selection-meta.proto b/proto/feature-selection-meta.proto index 9d8279e3..8d6b3521 100644 --- a/proto/feature-selection-meta.proto +++ b/proto/feature-selection-meta.proto @@ -38,6 +38,7 @@ message UniqueValueMeta { message IVValueSelectionMeta{ double value_threshold = 1; } + message IVPercentileSelectionMeta{ double percentile_threshold = 1; } diff --git a/proto/feature-selection-param.proto b/proto/feature-selection-param.proto index 03c1560f..239f470f 100644 --- a/proto/feature-selection-param.proto +++ b/proto/feature-selection-param.proto @@ -40,4 +40,7 @@ message FeatureSelectionFilterParam{ message FeatureSelectionParam{ repeated FeatureSelectionFilterParam results = 1; LeftCols final_left_cols = 2; + repeated string col_names = 3; + repeated string host_col_names = 4; + repeated string header = 5; } diff --git a/proto/lr-model-meta.proto b/proto/lr-model-meta.proto index 907ec714..3816ccd4 100644 --- a/proto/lr-model-meta.proto +++ b/proto/lr-model-meta.proto @@ -21,16 +21,16 @@ option java_outer_classname = "LRModelMetaProto"; message LRModelMeta { string penalty = 1; - double eps = 2; + double tol = 2; double alpha = 3; string optimizer = 4; double party_weight = 5; int64 batch_size = 6; double learning_rate = 7; int64 max_iter = 8; - string converge_func = 9; + string early_stop = 9; int64 re_encrypt_batches = 10; - bool need_run=11; + bool fit_intercept=11; bool need_one_vs_rest = 12; } diff --git a/proto/lr-model-param.proto b/proto/lr-model-param.proto index fef6ffcd..dbdd1ef3 100644 --- a/proto/lr-model-param.proto +++ b/proto/lr-model-param.proto @@ -26,7 +26,20 @@ message LRModelParam { map weight = 4; double intercept = 5; repeated string header = 6; - bool need_one_vs_rest = 7; - repeated string one_vs_rest_classes = 8; + OneVsRestResult one_vs_rest_result = 7; + bool need_one_vs_rest = 8; } +message SingleModel { + int32 iters = 1; + repeated double loss_history = 2; + bool is_converged = 3; + map weight = 4; + double intercept = 5; + repeated string header = 6; +} + +message OneVsRestResult { + repeated SingleModel completed_models = 1; + repeated string one_vs_rest_classes = 2; +} \ No newline at end of file From 9eb8a5c696ffc7fe2147adbb638738f1afc8caee Mon Sep 17 00:00:00 2001 From: zengjice Date: Fri, 27 Dec 2019 22:41:02 +0800 Subject: [PATCH 069/190] fix bugs that federatedml proto is different with fate repo --- proto/data-io-meta.proto | 1 + proto/feature-binning-param.proto | 4 +++- proto/feature-selection-meta.proto | 10 ++++++++-- proto/feature-selection-param.proto | 10 +++++++--- proto/model_service.proto | 2 +- 5 files changed, 20 insertions(+), 7 deletions(-) diff --git a/proto/data-io-meta.proto b/proto/data-io-meta.proto index ffa12db1..3c579333 100644 --- a/proto/data-io-meta.proto +++ b/proto/data-io-meta.proto @@ -28,5 +28,6 @@ message DataIOMeta { ImputerMeta imputer_meta = 10; OutlierMeta outlier_meta = 11; bool need_run = 12; + map exclusive_data_type = 13; } diff --git a/proto/feature-binning-param.proto b/proto/feature-binning-param.proto index 2d353a8d..15aa0a93 100644 --- a/proto/feature-binning-param.proto +++ b/proto/feature-binning-param.proto @@ -40,11 +40,13 @@ message IVParam { message FeatureBinningResult { map binning_result = 1; + string role = 2; + string party_id = 3; } message FeatureBinningParam { FeatureBinningResult binning_result = 1; - map host_results = 2; + repeated FeatureBinningResult host_results = 2; repeated string header = 3; } \ No newline at end of file diff --git a/proto/feature-selection-meta.proto b/proto/feature-selection-meta.proto index 8d6b3521..819fbb7b 100644 --- a/proto/feature-selection-meta.proto +++ b/proto/feature-selection-meta.proto @@ -21,14 +21,14 @@ option java_outer_classname = "FeatureSelectionMetaProto"; message FeatureSelectionMeta { repeated string filter_methods = 1; - bool local_only = 2; repeated string cols = 3; UniqueValueMeta unique_meta = 4; IVValueSelectionMeta iv_value_meta = 5; IVPercentileSelectionMeta iv_percentile_meta = 6; VarianceOfCoeSelectionMeta variance_coe_meta = 7; OutlierColsSelectionMeta outlier_meta = 8; - bool need_run = 9; + ManuallyFilterMeta manually_meta = 9; + bool need_run = 10; } message UniqueValueMeta { @@ -37,10 +37,12 @@ message UniqueValueMeta { message IVValueSelectionMeta{ double value_threshold = 1; + bool local_only = 2; } message IVPercentileSelectionMeta{ double percentile_threshold = 1; + bool local_only = 2; } message VarianceOfCoeSelectionMeta { @@ -50,4 +52,8 @@ message VarianceOfCoeSelectionMeta { message OutlierColsSelectionMeta { double percentile = 1; double upper_threshold = 2; +} + +message ManuallyFilterMeta { + repeated string filter_out_names = 1; } \ No newline at end of file diff --git a/proto/feature-selection-param.proto b/proto/feature-selection-param.proto index 239f470f..eb207dae 100644 --- a/proto/feature-selection-param.proto +++ b/proto/feature-selection-param.proto @@ -31,9 +31,9 @@ message LeftCols { message FeatureSelectionFilterParam{ map feature_values = 1; - map host_feature_values = 2; + repeated FeatureValue host_feature_values = 2; LeftCols left_cols = 3; - map host_left_cols = 4; + repeated LeftCols host_left_cols = 4; string filter_name = 5; } @@ -41,6 +41,10 @@ message FeatureSelectionParam{ repeated FeatureSelectionFilterParam results = 1; LeftCols final_left_cols = 2; repeated string col_names = 3; - repeated string host_col_names = 4; + repeated HostColNames host_col_names = 4; repeated string header = 5; } + +message HostColNames{ + repeated string col_names = 1; +} \ No newline at end of file diff --git a/proto/model_service.proto b/proto/model_service.proto index db44ef48..215ae69c 100644 --- a/proto/model_service.proto +++ b/proto/model_service.proto @@ -38,6 +38,6 @@ message PublishResponse{ service ModelService{ rpc publishLoad(PublishRequest) returns (PublishResponse); - rpc publishOnline(PublishRequest) returns (PublishResponse); rpc publishBind(PublishRequest) returns (PublishResponse); + rpc publishOnline(PublishRequest) returns (PublishResponse); } From 7dfc707cb3da8c27508647d6e4911b61e4b596ff Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Mon, 30 Dec 2019 09:42:20 +0800 Subject: [PATCH 070/190] =?UTF-8?q?=E5=8F=98=E9=87=8F=E5=90=8D=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/fate/serving/federatedml/model/HeteroFeatureBinning.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFeatureBinning.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFeatureBinning.java index e65c4357..fba8f6cc 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFeatureBinning.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFeatureBinning.java @@ -46,7 +46,7 @@ public int initModel(byte[] protoMeta, byte[] protoParam) { this.splitPoints.put(key, splitPoints); } } catch (Exception ex) { - LOGGER.error("init model error:", ex); + logger.error("init model error:", ex); return StatusCode.ILLEGALDATA; } logger.info("Finish init Feature Binning class"); From ddc5ef58d0d2f040edcdab05e51a155562487bac Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Mon, 30 Dec 2019 14:44:09 +0800 Subject: [PATCH 071/190] check code guideline Signed-off-by: v_dylanxu <136539068@qq.com> --- .../ai/fate/serving/core/bean/BaseMapPool.java | 4 ++++ .../federatedml/model/HeteroSecureBoost.java | 1 - .../model/HeteroSecureBoostingTreeGuest.java | 3 ++- .../serving/federatedml/model/MinMaxScale.java | 6 +++--- .../serving/federatedml/model/StandardScale.java | 8 +++++--- .../fate/register/common/AbstractRegistry.java | 10 +++++----- .../proxy/infra/impl/PacketQueuePipe.java | 4 +++- .../networking/proxy/model/PipeHandlerInfo.java | 16 ++++++++++++---- .../fate/networking/proxy/model/StreamStat.java | 16 ++++++++++++---- .../proxy/rpc/core/ProxyServiceRegister.java | 3 ++- .../proxy/rpc/grpc/InterRequestHandler.java | 2 ++ .../proxy/rpc/grpc/IntraRequestHandler.java | 2 ++ .../proxy/rpc/router/ZkServingRouter.java | 2 +- .../com/webank/ai/fate/serving/SpringConfig.java | 5 +++-- .../ai/fate/serving/service/ModelService.java | 7 ++++--- .../ai/fate/serving/utils/HttpClientPool.java | 3 ++- 16 files changed, 62 insertions(+), 30 deletions(-) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseMapPool.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseMapPool.java index c2d33ef2..a2957e73 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseMapPool.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseMapPool.java @@ -19,12 +19,16 @@ import java.util.Map; public abstract class BaseMapPool implements MapPool { + @Override public abstract void put(K key, V value); + @Override public abstract void putIfAbsent(K key, V value); + @Override public abstract void putAll(Map kv); + @Override public abstract V get(K key); @Override diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoost.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoost.java index 83ad850e..1e78c0ca 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoost.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoost.java @@ -17,7 +17,6 @@ package com.webank.ai.fate.serving.federatedml.model; import com.google.common.collect.Maps; - import com.webank.ai.fate.core.mlmodel.buffer.BoostTreeModelMetaProto.BoostingTreeModelMeta; import com.webank.ai.fate.core.mlmodel.buffer.BoostTreeModelParamProto.BoostingTreeModelParam; import com.webank.ai.fate.core.mlmodel.buffer.BoostTreeModelParamProto.DecisionTreeModelParam; diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeGuest.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeGuest.java index 19242269..6aea0f8c 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeGuest.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeGuest.java @@ -121,8 +121,9 @@ private Map getFinalPredict(double[] weights) { sumWeights[i % this.treeDim] += weights[i] * this.learningRate; } - for (int i = 0; i < this.treeDim; i++) + for (int i = 0; i < this.treeDim; i++) { sumWeights[i] += this.initScore.get(i); + } ret = softmax(sumWeights); } else { diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/MinMaxScale.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/MinMaxScale.java index f3dd7bdd..515d852e 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/MinMaxScale.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/MinMaxScale.java @@ -32,11 +32,11 @@ public Map transform(Map inputData, Map scale.getColumnUpper()) + if (value > scale.getColumnUpper()) { value = 1; - else if (value < scale.getColumnLower()) + } else if (value < scale.getColumnLower()) { value = 0; - else { + } else { double range = scale.getColumnUpper() - scale.getColumnLower(); if (range < 0) { logger.warn("min_max_scale range may be error, it should be larger than 0, but is {}, set value to 0 ", range); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/StandardScale.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/StandardScale.java index c4d0bfd7..803c86a9 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/StandardScale.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/StandardScale.java @@ -35,14 +35,16 @@ public Map transform(Map inputData, Map upper) + if (value > upper) { value = upper; - else if (value < lower) + } else if (value < lower) { value = lower; + } double std = standardScale.getStd(); - if (std == 0) + if (std == 0) { std = 1; + } value = (value - standardScale.getMean()) / std; inputData.put(key, value); diff --git a/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistry.java b/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistry.java index 18d74c3a..ba2827b6 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistry.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistry.java @@ -32,10 +32,7 @@ import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -51,7 +48,10 @@ public abstract class AbstractRegistry implements Registry { private static final String URL_SPLIT = "\\s+"; private static final int MAX_RETRY_TIMES_SAVE_PROPERTIES = 3; private final Properties properties = new Properties(); - private final ExecutorService registryCacheExecutor = Executors.newFixedThreadPool(1, new NamedThreadFactory("FateSaveRegistryCache", true)); + private final ExecutorService registryCacheExecutor = new ThreadPoolExecutor(1, 1, + 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), + new NamedThreadFactory("FateSaveRegistryCache", true)); + private final boolean syncSaveFile; private final AtomicLong lastCacheChanged = new AtomicLong(); private final AtomicInteger savePropertiesRetryTimes = new AtomicInteger(); diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketQueuePipe.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketQueuePipe.java index b02a6463..0c1711e6 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketQueuePipe.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketQueuePipe.java @@ -64,7 +64,9 @@ public Proxy.Packet read() { @Override public Proxy.Packet read(long timeout, TimeUnit unit) { Proxy.Packet result = null; - if (isDrained()) return result; + if (isDrained()) { + return result; + } try { result = queue.poll(timeout, unit); } catch (InterruptedException e) { diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/model/PipeHandlerInfo.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/model/PipeHandlerInfo.java index df6d84f9..6bad2c99 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/model/PipeHandlerInfo.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/model/PipeHandlerInfo.java @@ -86,13 +86,21 @@ public String toString() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof PipeHandlerInfo)) return false; + if (this == o) { + return true; + } + if (!(o instanceof PipeHandlerInfo)) { + return false; + } PipeHandlerInfo that = (PipeHandlerInfo) o; - if (type != that.type) return false; - if (!metadata.equals(that.metadata)) return false; + if (type != that.type) { + return false; + } + if (!metadata.equals(that.metadata)) { + return false; + } return pipe.equals(that.pipe); } diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/model/StreamStat.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/model/StreamStat.java index 3cd4a3f5..b0c0e402 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/model/StreamStat.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/model/StreamStat.java @@ -115,13 +115,21 @@ public String toString() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof StreamStat)) return false; + if (this == o) { + return true; + } + if (!(o instanceof StreamStat)) { + return false; + } StreamStat that = (StreamStat) o; - if (size != that.size) return false; - if (startTimestamp != that.startTimestamp) return false; + if (size != that.size) { + return false; + } + if (startTimestamp != that.startTimestamp) { + return false; + } return lastUpdateTimestamp == that.lastUpdateTimestamp; } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyServiceRegister.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyServiceRegister.java index 105975e6..dd317e87 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyServiceRegister.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyServiceRegister.java @@ -28,8 +28,9 @@ public class ProxyServiceRegister implements ServiceRegister, ApplicationContext public ServiceAdaptor getServiceAdaptor(String name) { if( serviceAdaptorMap.get(name)!=null){ return serviceAdaptorMap.get(name); - }else + }else { return serviceAdaptorMap.get("NotFound"); + } } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterRequestHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterRequestHandler.java index 250afc2e..1ab20646 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterRequestHandler.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterRequestHandler.java @@ -18,10 +18,12 @@ public class InterRequestHandler extends ProxyRequestHandler { @Autowired ProxyServiceRegister proxyServiceRegister; + @Override public ProxyServiceRegister getProxyServiceRegister(){ return proxyServiceRegister; } + @Override public void setExtraInfo(Context context, InboundPackage inboundPackage, Proxy.Packet req){ context.setGrpcType(GrpcType.INTER_GRPC); } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraRequestHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraRequestHandler.java index 2b14f5c8..5f0966cb 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraRequestHandler.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraRequestHandler.java @@ -18,10 +18,12 @@ public class IntraRequestHandler extends ProxyRequestHandler { @Autowired ProxyServiceRegister proxyServiceRegister; + @Override public ProxyServiceRegister getProxyServiceRegister(){ return proxyServiceRegister; } + @Override public void setExtraInfo(Context context, InboundPackage inboundPackage, Proxy.Packet req){ context.setGrpcType(GrpcType.INTRA_GRPC); } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java index 518ff884..ab91db55 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java @@ -52,7 +52,7 @@ public List getRouterInfoList(Context context, InboundPackage inboun return null; } String environment = getEnvironment(context, inboundPackage); - List list =this.zkRouterService.router("serving", environment, context.getServiceName()); + List list = zkRouterService.router("serving", environment, context.getServiceName()); logger.info("try to find zk ,{}:{}:{}, result {}", "serving", environment, context.getServiceName(), list); if(null == list || list.isEmpty()){ return null; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java b/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java index 33c8d812..520809c3 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java @@ -40,11 +40,12 @@ ZookeeperRegistry getServiceRegistry() { String useRegisterString = com.webank.ai.fate.serving .core.bean.Configuration.getProperty("useRegister"); - if (Boolean.valueOf(useRegisterString)) + if (Boolean.valueOf(useRegisterString)) { return ZookeeperRegistry.getRegistery(com.webank.ai.fate.serving.core.bean.Configuration.getProperty("zk.url"), "serving", "online", com.webank.ai.fate.serving.core.bean.Configuration.getPropertyInt(Dict.PORT)); - else + } else { return null; + } } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java index 537ea312..18bece6b 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java @@ -44,7 +44,9 @@ import java.nio.charset.Charset; import java.util.*; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @Service @@ -74,10 +76,9 @@ public String toString(){ String md5; } - LinkedHashMap publishLoadReqMap = new LinkedHashMap(); LinkedHashMap publicOnlineReqMap = new LinkedHashMap(); - ExecutorService executorService = Executors.newSingleThreadExecutor(); + ExecutorService executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); File publishLoadStoreFile; File publishOnlineStoreFile; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/utils/HttpClientPool.java b/serving-server/src/main/java/com/webank/ai/fate/serving/utils/HttpClientPool.java index e32cd64a..9edbf06f 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/utils/HttpClientPool.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/utils/HttpClientPool.java @@ -148,8 +148,9 @@ private static String getResponse(HttpRequestBase request) { return null; } finally { try { - if (response != null) + if (response != null) { response.close(); + } } catch (IOException ex) { logger.error("get http response failed:", ex); } From beee93816355c8efb7040ae4b8b0c98aa361a049 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Mon, 30 Dec 2019 16:40:34 +0800 Subject: [PATCH 072/190] check code guideline Signed-off-by: v_dylanxu <136539068@qq.com> --- .../fate/serving/core/bean/BaseContext.java | 2 +- .../serving/core/bean/BaseLoggerPrinter.java | 4 +- .../ai/fate/serving/core/bean/Context.java | 2 +- .../fate/serving/core/bean/EncryptMethod.java | 6 + .../core/bean/FederatedInferenceType.java | 6 + .../bean/GuestInferenceLoggerPrinter.java | 4 +- .../core/bean/HostInferenceLoggerPrinter.java | 4 +- .../core/bean/InferenceActionType.java | 9 ++ .../serving/core/bean/ModelActionType.java | 6 + .../core/manager/DefaultCacheManager.java | 12 +- .../fate/serving/core/manager/SceneUtils.java | 2 +- .../fate/serving/core/utils/EncryptUtils.java | 19 ++- .../serving/core/utils/GetSystemInfo.java | 19 +-- .../fate/serving/federatedml/DSLParser.java | 2 +- .../serving/federatedml/PipelineTask.java | 3 +- .../serving/federatedml/model/BaseModel.java | 1 - .../federatedml/model/HeteroSecureBoost.java | 2 +- .../model/HeteroSecureBoostingTreeGuest.java | 2 +- .../model/HeteroSecureBoostingTreeHost.java | 3 +- .../fate/serving/federatedml/model/Scale.java | 6 +- .../register/common/ConfigChangeType.java | 12 +- .../common/CuratorZookeeperClient.java | 43 +++--- .../ai/fate/register/common/EventType.java | 6 +- .../ai/fate/register/common/RouterModel.java | 15 ++ .../loadbalance/LoadBalanceModel.java | 6 + .../fate/register/task/AbstractRetryTask.java | 10 +- .../com/webank/ai/fate/register/url/URL.java | 21 ++- .../webank/ai/fate/register/url/UrlUtils.java | 9 +- .../ai/fate/register/utils/NetUtils.java | 13 +- .../ai/fate/register/utils/StringUtils.java | 8 +- .../register/zookeeper/ZookeeperRegistry.java | 2 +- .../model/PipeHandleNotificationEvent.java | 11 +- .../proxy/factory/GrpcStubFactory.java | 2 +- .../grpc/client/DataTransferPipedClient.java | 12 +- .../proxy/service/ConfFileBasedFdnRouter.java | 2 +- .../fate/networking/proxy/util/AuthUtils.java | 3 +- .../networking/proxy/util/EncryptUtils.java | 38 ----- .../serving/proxy/common/GetSystemInfo.java | 144 ------------------ .../common/GlobalResponseController.java | 2 +- .../fate/serving/proxy/rpc/grpc/GrpcType.java | 6 + .../router/ConfigFileBasedServingRouter.java | 5 +- .../serving/proxy/rpc/router/RouteType.java | 6 + .../serving/proxy/security/AuthUtils.java | 6 +- .../fate/serving/proxy/utils/EncryptUtil.java | 37 ----- .../proxy/utils/FederatedModelUtils.java | 4 +- .../webank/ai/fate/serving/ServingServer.java | 9 +- .../processing/CommonPreProcessing.java | 2 +- .../ai/fate/serving/manger/ModelUtils.java | 6 +- .../ai/fate/serving/service/ModelService.java | 4 +- 49 files changed, 229 insertions(+), 329 deletions(-) delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/util/EncryptUtils.java delete mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/GetSystemInfo.java delete mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/EncryptUtil.java diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java index 468f159d..6ac78bd1 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java @@ -28,7 +28,7 @@ public class BaseContext implements Context { - private static final Logger logger = LoggerFactory.getLogger(logger_NAME); + private static final Logger logger = LoggerFactory.getLogger(LOGGER_NAME); public static ApplicationContext applicationContext; long timestamp; LoggerPrinter loggerPrinter; diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseLoggerPrinter.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseLoggerPrinter.java index 65916452..ef325b25 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseLoggerPrinter.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseLoggerPrinter.java @@ -25,9 +25,9 @@ public class BaseLoggerPrinter implements LoggerPrinter { - static final String logger_NAME = "flow"; + static final String LOGGER_NAME = "flow"; - private static final Logger logger = LoggerFactory.getLogger(logger_NAME); + private static final Logger logger = LoggerFactory.getLogger(LOGGER_NAME); @Override public void printLog(Context context, Object req, ReturnResult resp) { diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Context.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Context.java index 668706e6..ab41e27a 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Context.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Context.java @@ -21,7 +21,7 @@ public interface Context { - static final String logger_NAME = "flow"; + static final String LOGGER_NAME = "flow"; public void preProcess(); diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/EncryptMethod.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/EncryptMethod.java index bf28a7c4..4e590680 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/EncryptMethod.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/EncryptMethod.java @@ -17,6 +17,12 @@ package com.webank.ai.fate.serving.core.bean; public enum EncryptMethod { + /** + * type MD5 + */ MD5, + /** + * type SHA256 + */ SHA256, } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/FederatedInferenceType.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/FederatedInferenceType.java index 3d3fabdf..77641315 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/FederatedInferenceType.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/FederatedInferenceType.java @@ -17,6 +17,12 @@ package com.webank.ai.fate.serving.core.bean; public enum FederatedInferenceType { + /** + * INITIATED + */ INITIATED, + /** + * FEDERATED + */ FEDERATED, } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GuestInferenceLoggerPrinter.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GuestInferenceLoggerPrinter.java index 41197b41..57bdca7f 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GuestInferenceLoggerPrinter.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GuestInferenceLoggerPrinter.java @@ -22,9 +22,9 @@ public class GuestInferenceLoggerPrinter implements LoggerPrinter { - static final String logger_NAME = "flow"; + static final String LOGGER_NAME = "flow"; - private static final Logger logger = LoggerFactory.getLogger(logger_NAME); + private static final Logger logger = LoggerFactory.getLogger(LOGGER_NAME); @Override public void printLog(Context context, Request req, ReturnResult resp) { diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/HostInferenceLoggerPrinter.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/HostInferenceLoggerPrinter.java index c8ccaa02..a087e5a0 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/HostInferenceLoggerPrinter.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/HostInferenceLoggerPrinter.java @@ -25,9 +25,9 @@ public class HostInferenceLoggerPrinter implements LoggerPrinter { - static final String logger_NAME = "flow"; + static final String LOGGER_NAME = "flow"; - private static final Logger logger = LoggerFactory.getLogger(logger_NAME); + private static final Logger logger = LoggerFactory.getLogger(LOGGER_NAME); @Override public void printLog(Context context, Map req, ReturnResult resp) { diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/InferenceActionType.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/InferenceActionType.java index 33b9cef1..13cab054 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/InferenceActionType.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/InferenceActionType.java @@ -17,7 +17,16 @@ package com.webank.ai.fate.serving.core.bean; public enum InferenceActionType { + /** + * SYNC_RUN + */ SYNC_RUN, + /** + * ASYNC_RUN + */ ASYNC_RUN, + /** + * GET_RESULT + */ GET_RESULT, } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/ModelActionType.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/ModelActionType.java index 39793845..8d5d967b 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/ModelActionType.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/ModelActionType.java @@ -17,6 +17,12 @@ package com.webank.ai.fate.serving.core.bean; public enum ModelActionType { + /** + * MODEL_LOAD + */ MODEL_LOAD, + /** + * MODEL_PUBLISH_ONLINE + */ MODEL_PUBLISH_ONLINE } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java index 9752c957..c2140d2d 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java @@ -36,8 +36,6 @@ import java.util.*; import java.util.concurrent.TimeUnit; -//import com.webank.ai.fate.core.utils.ObjectTransform; - @Service public class DefaultCacheManager implements CacheManager, InitializingBean { @@ -276,7 +274,6 @@ private String generateRemoteModelInferenceResultCacheKey(FederatedParams feder Preconditions.checkNotNull(federatedParams.getFeatureIdMap()); String namespace = federatedParams.getModelInfo().getNamespace(); String name = federatedParams.getModelInfo().getName(); - // Set featureIdKey = federatedParams.getFeatureIdMap(); Map sortedMap = Maps.newTreeMap(); federatedParams.getFeatureIdMap().forEach((k,v)->{ @@ -308,8 +305,17 @@ private String generateRemoteModelInferenceResultCacheKey(FederatedParty remoteP } private enum CacheType { + /** + * INFERENCE_RESULT + */ INFERENCE_RESULT, + /** + * REMOTE_MODEL_INFERENCE_RESULT + */ REMOTE_MODEL_INFERENCE_RESULT, + /** + * PROCESS_DATA + */ PROCESS_DATA } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/SceneUtils.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/SceneUtils.java index 0b545bcc..b63c3125 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/SceneUtils.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/SceneUtils.java @@ -23,7 +23,7 @@ import java.util.Arrays; public class SceneUtils { - private static final String sceneKeySeparator = "#"; + private static final String SCENE_KEY_SEPARATOR = "#"; public SceneUtils() { } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/EncryptUtils.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/EncryptUtils.java index d08adb45..9c0de0b9 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/EncryptUtils.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/EncryptUtils.java @@ -18,18 +18,21 @@ import com.webank.ai.fate.serving.core.bean.EncryptMethod; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; import java.security.MessageDigest; public class EncryptUtils { public static final String UTF8 = "UTF-8"; - private static final String HMACSHA1 = "HmacSHA1"; + private static final String HMAC_SHA1 = "HmacSHA1"; public static String encrypt(String originString, EncryptMethod encryptMethod) { try { MessageDigest m = MessageDigest.getInstance(getEncryptMethodString(encryptMethod)); m.update(originString.getBytes("UTF8")); - byte s[] = m.digest(); + byte[] s = m.digest(); String result = ""; for (int i = 0; i < s.length; i++) { result += Integer.toHexString((0x000000FF & s[i]) | 0xFFFFFF00).substring(6); @@ -42,6 +45,16 @@ public static String encrypt(String originString, EncryptMethod encryptMethod) { return ""; } + public static byte[] hmacSha1Encrypt(String encryptText, String encryptKey) throws Exception { + byte[] data = encryptKey.getBytes(UTF8); + SecretKey secretKey = new SecretKeySpec(data, HMAC_SHA1); + Mac mac = Mac.getInstance(HMAC_SHA1); + mac.init(secretKey); + + byte[] text = encryptText.getBytes(UTF8); + return mac.doFinal(text); + } + private static String getEncryptMethodString(EncryptMethod encryptMethod) { String methodString = ""; switch (encryptMethod) { @@ -51,6 +64,8 @@ private static String getEncryptMethodString(EncryptMethod encryptMethod) { case SHA256: methodString = "SHA-256"; break; + default: + break; } return methodString; } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/GetSystemInfo.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/GetSystemInfo.java index 39ab8d12..b60289a5 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/GetSystemInfo.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/GetSystemInfo.java @@ -45,13 +45,14 @@ public static String getLocalIp() { String ip; try { - if (sysType.toLowerCase().startsWith("win")) { - String localIP = null; + String winOs = "win"; + if (sysType.toLowerCase().startsWith(winOs)) { + String localIp = null; - localIP = InetAddress.getLocalHost().getHostAddress(); + localIp = InetAddress.getLocalHost().getHostAddress(); - if (localIP != null) { - return localIP; + if (localIp != null) { + return localIp; } } else { ip = getIpByEthNum("eth0"); @@ -100,16 +101,16 @@ public static String getOsName() { public static double getSystemCpuLoad() { OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory .getOperatingSystemMXBean(); - double SystemCpuLoad = osmxb.getSystemCpuLoad(); - return SystemCpuLoad; + double systemCpuLoad = osmxb.getSystemCpuLoad(); + return systemCpuLoad; } public static double getProcessCpuLoad() { OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory .getOperatingSystemMXBean(); - double ProcessCpuLoad = osmxb.getProcessCpuLoad(); - return ProcessCpuLoad; + double processCpuLoad = osmxb.getProcessCpuLoad(); + return processCpuLoad; } diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/DSLParser.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/DSLParser.java index 63c60405..8e961396 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/DSLParser.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/DSLParser.java @@ -105,7 +105,7 @@ public void topoSort(JSONObject components, ArrayList topoRankComponent) componentList.add(componentName); } - int inDegree[] = new int[index]; + int[] inDegree = new int[index]; HashMap> edges = new HashMap>(8); for (int i = 0; i < componentList.size(); ++i) { diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java index 420ff3da..3ca72a9b 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java @@ -45,7 +45,8 @@ public int initModel(Map modelProtoMap) { logger.info("after parse pipeline {}",newModelProtoMap.keySet()); Preconditions.checkArgument(newModelProtoMap.get(PIPLELINE_IN_MODEL)!=null); PipelineProto.Pipeline pipeLineProto = PipelineProto.Pipeline.parseFrom(newModelProtoMap.get(PIPLELINE_IN_MODEL)); - String dsl = pipeLineProto.getInferenceDsl().toStringUtf8(); //inference_dsl; + //inference_dsl + String dsl = pipeLineProto.getInferenceDsl().toStringUtf8(); dslParser.parseDagFromDSL(dsl); ArrayList components = dslParser.getAllComponent(); HashMap componentModuleMap = dslParser.getComponentModuleMap(); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java index ca5685dd..08a99a15 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java @@ -113,7 +113,6 @@ protected ReturnResult getFederatedPredict(Context context, FederatedParams gues HostFederatedParams hostFederatedParams = new HostFederatedParams(); hostFederatedParams.setCaseId(guestFederatedParams.getCaseId()); hostFederatedParams.setSeqNo(guestFederatedParams.getSeqNo()); -// hostFederatedParams.setFeatureIdMap(guestFederatedParams.getFeatureIdMap()); hostFederatedParams.getFeatureIdMap().putAll(guestFederatedParams.getFeatureIdMap()); hostFederatedParams.setLocal(dstParty); hostFederatedParams.setPartnerLocal(srcParty); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoost.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoost.java index 1e78c0ca..9c1adebc 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoost.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoost.java @@ -31,7 +31,7 @@ public abstract class HeteroSecureBoost extends BaseModel { public static final Logger logger = LoggerFactory.getLogger(HeteroSecureBoost.class); - protected List> split_maskdict; + protected List> splitMaskdict; protected Map featureNameFidMapping = Maps.newHashMap(); protected int treeNum; protected List initScore; diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeGuest.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeGuest.java index 6aea0f8c..b33077c9 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeGuest.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeGuest.java @@ -35,7 +35,7 @@ private double sigmoid(double x) { return 1. / (1. + Math.exp(-x)); } - private Map softmax(double weights[]) { + private Map softmax(double[] weights) { int n = weights.length; double max = weights[0]; int maxIndex = 0; diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeHost.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeHost.java index db61cd68..4b059184 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeHost.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeHost.java @@ -27,7 +27,8 @@ public class HeteroSecureBoostingTreeHost extends HeteroSecureBoost { private final String site = "host"; - private final String modelId = "HeteroSecureBoostingTreeHost"; // need to change + // need to change + private final String modelId = "HeteroSecureBoostingTreeHost"; // DefaultCacheManager cacheManager = BaseContext.applicationContext.getBean(DefaultCacheManager.class); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Scale.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Scale.java index f5373e89..545fd597 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Scale.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Scale.java @@ -33,7 +33,7 @@ public class Scale extends BaseModel { private static final Logger logger = LoggerFactory.getLogger(Scale.class); private ScaleMeta scaleMeta; private ScaleParam scaleParam; - private boolean need_run; + private boolean needRun; @Override public int initModel(byte[] protoMeta, byte[] protoParam) { @@ -41,7 +41,7 @@ public int initModel(byte[] protoMeta, byte[] protoParam) { try { this.scaleMeta = this.parseModel(ScaleMeta.parser(), protoMeta); this.scaleParam = this.parseModel(ScaleParam.parser(), protoParam); - this.need_run = this.scaleMeta.getNeedRun(); + this.needRun = this.scaleMeta.getNeedRun(); } catch (Exception ex) { ex.printStackTrace(); return StatusCode.ILLEGALDATA; @@ -53,7 +53,7 @@ public int initModel(byte[] protoMeta, byte[] protoParam) { @Override public Map handlePredict(Context context, List> inputDatas, FederatedParams predictParams) { Map outputData = inputDatas.get(0); - if (this.need_run) { + if (this.needRun) { String scaleMethod = this.scaleMeta.getMethod(); if (scaleMethod.toLowerCase().equals(Dict.MIN_MAX_SCALE)) { MinMaxScale minMaxScale = new MinMaxScale(); diff --git a/register/src/main/java/com/webank/ai/fate/register/common/ConfigChangeType.java b/register/src/main/java/com/webank/ai/fate/register/common/ConfigChangeType.java index 78c6062b..06d2327c 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/ConfigChangeType.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/ConfigChangeType.java @@ -18,10 +18,16 @@ public enum ConfigChangeType { - + /** + * add config + */ ADDED, - + /** + * modify config + */ MODIFIED, - + /** + * delete + */ DELETED } diff --git a/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java b/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java index 030b1491..faf4326e 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java @@ -60,11 +60,11 @@ public class CuratorZookeeperClient extends AbstractZookeeperClient treeCacheMap = new ConcurrentHashMap<>(); - private static final List acls = new ArrayList<>(); + private static final List ACLS = new ArrayList<>(); - private static final String scheme = "digest"; - private static final String aclUserName = System.getProperty("acl.username"); - private static final String aclPassword = System.getProperty("acl.password"); + private static final String SCHEME = "digest"; + private static final String ACL_USERNAME = System.getProperty("acl.username"); + private static final String ACL_PASSWORD = System.getProperty("acl.password"); public CuratorZookeeperClient(URL url) { @@ -76,12 +76,12 @@ public CuratorZookeeperClient(URL url) { .retryPolicy(new RetryNTimes(1, 1000)) .connectionTimeoutMs(timeout); - if (StringUtils.isNotEmpty(aclUserName) && StringUtils.isNotEmpty(aclPassword) ) { - builder.authorization(scheme, (aclUserName + ":" + aclPassword).getBytes()); + if (StringUtils.isNotEmpty(ACL_USERNAME) && StringUtils.isNotEmpty(ACL_PASSWORD) ) { + builder.authorization(SCHEME, (ACL_USERNAME + ":" + ACL_PASSWORD).getBytes()); - Id allow = new Id(scheme, DigestAuthenticationProvider.generateDigest(aclUserName + ":" + aclPassword)); + Id allow = new Id(SCHEME, DigestAuthenticationProvider.generateDigest(ACL_USERNAME + ":" + ACL_PASSWORD)); // add more - acls.add(new ACL(ZooDefs.Perms.ALL, allow)); + ACLS.add(new ACL(ZooDefs.Perms.ALL, allow)); } client = builder.build(); @@ -102,7 +102,7 @@ public void stateChanged(CuratorFramework client, ConnectionState state) { }); client.start(); - client.setACL().withACL(acls).forPath("/"); + client.setACL().withACL(ACLS).forPath("/"); } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); } @@ -113,8 +113,8 @@ public void createPersistent(String path) { try { logger.info("createPersistent {}", path); - if (acls.size() > 0) { - client.create().withACL(acls).forPath(path); + if (ACLS.size() > 0) { + client.create().withACL(ACLS).forPath(path); } else { client.create().forPath(path); } @@ -128,8 +128,8 @@ public void createPersistent(String path) { public void createEphemeral(String path) { try { logger.info("createEphemeral {}", path); - if (acls.size() > 0) { - client.create().withMode(CreateMode.EPHEMERAL).withACL(acls).forPath(path); + if (ACLS.size() > 0) { + client.create().withMode(CreateMode.EPHEMERAL).withACL(ACLS).forPath(path); } else { client.create().withMode(CreateMode.EPHEMERAL).forPath(path); } @@ -144,14 +144,14 @@ protected void createPersistent(String path, String data) { byte[] dataBytes = data.getBytes(CHARSET); try { logger.info("createPersistent {} data {}", path, data); - if (acls.size() > 0) { - client.create().withACL(acls).forPath(path, dataBytes); + if (ACLS.size() > 0) { + client.create().withACL(ACLS).forPath(path, dataBytes); } else { client.create().forPath(path, dataBytes); } } catch (NodeExistsException e) { try { - if (acls.size() > 0) { + if (ACLS.size() > 0) { Stat stat = client.checkExists().forPath(path); client.setData().withVersion(stat.getAversion()).forPath(path, dataBytes); } else { @@ -170,14 +170,14 @@ protected void createEphemeral(String path, String data) { byte[] dataBytes = data.getBytes(CHARSET); try { logger.info("createEphemeral {} data {}", path, data); - if (acls.size() > 0) { - client.create().withMode(CreateMode.EPHEMERAL).withACL(acls).forPath(path, dataBytes); + if (ACLS.size() > 0) { + client.create().withMode(CreateMode.EPHEMERAL).withACL(ACLS).forPath(path, dataBytes); } else { client.create().withMode(CreateMode.EPHEMERAL).forPath(path, dataBytes); } } catch (NodeExistsException e) { try { - if (acls.size() > 0) { + if (ACLS.size() > 0) { Stat stat = client.checkExists().forPath(path); client.setData().withVersion(stat.getAversion()).forPath(path, dataBytes); } else { @@ -194,7 +194,7 @@ protected void createEphemeral(String path, String data) { @Override public void delete(String path) { try { - if (acls.size() > 0) { + if (ACLS.size() > 0) { Stat stat = client.checkExists().forPath(path); client.delete().withVersion(stat.getAversion()).forPath(path); } else { @@ -394,7 +394,8 @@ public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exc case CONNECTION_SUSPENDED: eventType = EventType.CONNECTION_SUSPENDED; break; - + default: + break; } dataListener.dataChanged(path, content, eventType); } diff --git a/register/src/main/java/com/webank/ai/fate/register/common/EventType.java b/register/src/main/java/com/webank/ai/fate/register/common/EventType.java index 1727dcc9..36296993 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/EventType.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/EventType.java @@ -32,8 +32,10 @@ public enum EventType { CONNECTION_LOST(12), INITIALIZED(10); - - private final int intValue; // Integer representation of value + /** + * Integer representation of value + */ + private final int intValue; // for sending over wire EventType(int intValue) { diff --git a/register/src/main/java/com/webank/ai/fate/register/common/RouterModel.java b/register/src/main/java/com/webank/ai/fate/register/common/RouterModel.java index 6a67d43c..e6608344 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/RouterModel.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/RouterModel.java @@ -17,10 +17,25 @@ package com.webank.ai.fate.register.common; public enum RouterModel { + /** + * VERSION_BIGER + */ VERSION_BIGER, + /** + * VERSION_BIGTHAN_OR_EQUAL + */ VERSION_BIGTHAN_OR_EQUAL, + /** + * VERSION_SMALLER + */ VERSION_SMALLER, + /** + * VERSION_EQUALS + */ VERSION_EQUALS, + /** + * ALL_ALLOWED + */ ALL_ALLOWED } diff --git a/register/src/main/java/com/webank/ai/fate/register/loadbalance/LoadBalanceModel.java b/register/src/main/java/com/webank/ai/fate/register/loadbalance/LoadBalanceModel.java index cb1627b8..530f44de 100644 --- a/register/src/main/java/com/webank/ai/fate/register/loadbalance/LoadBalanceModel.java +++ b/register/src/main/java/com/webank/ai/fate/register/loadbalance/LoadBalanceModel.java @@ -17,6 +17,12 @@ package com.webank.ai.fate.register.loadbalance; public enum LoadBalanceModel { + /** + * random + */ random, + /** + * random_with_weight + */ random_with_weight } \ No newline at end of file diff --git a/register/src/main/java/com/webank/ai/fate/register/task/AbstractRetryTask.java b/register/src/main/java/com/webank/ai/fate/register/task/AbstractRetryTask.java index e51d6886..62e1ce70 100644 --- a/register/src/main/java/com/webank/ai/fate/register/task/AbstractRetryTask.java +++ b/register/src/main/java/com/webank/ai/fate/register/task/AbstractRetryTask.java @@ -53,8 +53,8 @@ public abstract class AbstractRetryTask implements TimerTask { * task name for this task */ private final String taskName; - int DEFAULT_REGISTRY_RETRY_PERIOD = 5 * 1000; - String REGISTRY_RETRY_TIMES_KEY = "retry.times"; + private static int DEFAULT_REGISTRY_RETRY_PERIOD = 5 * 1000; + private static String REGISTRY_RETRY_TIMES_KEY = "retry.times"; /** * times of retry. * retry task is execute in single thread so that the times is not need volatile. @@ -127,5 +127,11 @@ public void run(Timeout timeout) throws Exception { } + /** + * doRetry + * @param url + * @param registry + * @param timeout + */ protected abstract void doRetry(URL url, FailbackRegistry registry, Timeout timeout); } diff --git a/register/src/main/java/com/webank/ai/fate/register/url/URL.java b/register/src/main/java/com/webank/ai/fate/register/url/URL.java index 70b573d9..43518669 100644 --- a/register/src/main/java/com/webank/ai/fate/register/url/URL.java +++ b/register/src/main/java/com/webank/ai/fate/register/url/URL.java @@ -138,7 +138,8 @@ public static URL valueOf(String url, String project, String environment) { int port = 0; String path = null; Map parameters = null; - int i = url.indexOf("?"); // separator between body and parameters + // separator between body and parameters + int i = url.indexOf("?"); if (i >= 0) { String[] parts = url.substring(i + 1).split("&"); parameters = new HashMap<>(8); @@ -243,7 +244,8 @@ public static URL valueOf(String url) { int port = 0; String path = null; Map parameters = null; - int i = url.indexOf("?"); // separator between body and parameters + // separator between body and parameters + int i = url.indexOf("?"); if (i >= 0) { String[] parts = url.substring(i + 1).split("&"); parameters = new HashMap<>(8); @@ -1052,7 +1054,8 @@ public URL addParameter(String key, String value) { return this; } // if value doesn't change, return immediately - if (value.equals(getParameters().get(key))) { // value != null + // value != null + if (value.equals(getParameters().get(key))) { return this; } @@ -1223,22 +1226,26 @@ public String toString() { if (string != null) { return string; } - return string = buildString(false, true); // no show username and password + // no show username and password + return string = buildString(false, true); } public String toString(String... parameters) { - return buildString(false, true, parameters); // no show username and password + // no show username and password + return buildString(false, true, parameters); } public String toIdentityString() { if (identity != null) { return identity; } - return identity = buildString(true, false); // only return identity message, see the method "equals" and "hashCode" + // only return identity message, see the method "equals" and "hashCode" + return identity = buildString(true, false); } public String toIdentityString(String... parameters) { - return buildString(true, false, parameters); // only return identity message, see the method "equals" and "hashCode" + // only return identity message, see the method "equals" and "hashCode" + return buildString(true, false, parameters); } public String toFullString() { diff --git a/register/src/main/java/com/webank/ai/fate/register/url/UrlUtils.java b/register/src/main/java/com/webank/ai/fate/register/url/UrlUtils.java index 3c6d37a6..01e5c7ab 100644 --- a/register/src/main/java/com/webank/ai/fate/register/url/UrlUtils.java +++ b/register/src/main/java/com/webank/ai/fate/register/url/UrlUtils.java @@ -36,7 +36,7 @@ public class UrlUtils { */ private final static String URL_PARAM_STARTING_SYMBOL = "?"; - public static URL parseURL(String address, Map defaults) { + public static URL parseUrl(String address, Map defaults) { if (address == null || address.length() == 0) { return null; } @@ -133,17 +133,18 @@ public static URL parseURL(String address, Map defaults) { return u; } - public static List parseURLs(String address, Map defaults) { + public static List parseUrls(String address, Map defaults) { if (address == null || address.length() == 0) { return null; } String[] addresses = REGISTRY_SPLIT_PATTERN.split(address); if (addresses == null || addresses.length == 0) { - return null; //here won't be empty + //here won't be empty + return null; } List registries = new ArrayList(); for (String addr : addresses) { - registries.add(parseURL(addr, defaults)); + registries.add(parseUrl(addr, defaults)); } return registries; } diff --git a/register/src/main/java/com/webank/ai/fate/register/utils/NetUtils.java b/register/src/main/java/com/webank/ai/fate/register/utils/NetUtils.java index 490a00d7..3dd7fdb5 100644 --- a/register/src/main/java/com/webank/ai/fate/register/utils/NetUtils.java +++ b/register/src/main/java/com/webank/ai/fate/register/utils/NetUtils.java @@ -139,7 +139,7 @@ static boolean isValidV4Address(InetAddress address) { * * @return true if it is reachable */ - static boolean isPreferIPV6Address() { + static boolean isPreferIpv6Address() { boolean preferIpv6 = Boolean.getBoolean("java.net.preferIPv6Addresses"); if (!preferIpv6) { return false; @@ -215,7 +215,7 @@ public static InetAddress getLocalAddress() { private static Optional toValidAddress(InetAddress address) { if (address instanceof Inet6Address) { Inet6Address v6Address = (Inet6Address) address; - if (isPreferIPV6Address()) { + if (isPreferIpv6Address()) { return Optional.ofNullable(normalizeV6Address(v6Address)); } } @@ -233,12 +233,11 @@ public static String getLocalIp() { try { if (sysType.toLowerCase().startsWith("win")) { - String localIP = null; - localIP = InetAddress.getLocalHost().getHostAddress(); + String localIp = InetAddress.getLocalHost().getHostAddress(); - if (localIP != null) { - return localIP; + if (localIp != null) { + return localIp; } } else { ip = getIpByEthNum("eth0"); @@ -370,7 +369,7 @@ public static InetSocketAddress toAddress(String address) { return new InetSocketAddress(host, port); } - public static String toURL(String protocol, String host, int port, String path) { + public static String toUrl(String protocol, String host, int port, String path) { StringBuilder sb = new StringBuilder(); sb.append(protocol).append("://"); sb.append(host).append(':').append(port); diff --git a/register/src/main/java/com/webank/ai/fate/register/utils/StringUtils.java b/register/src/main/java/com/webank/ai/fate/register/utils/StringUtils.java index 84fd0618..6261c10a 100644 --- a/register/src/main/java/com/webank/ai/fate/register/utils/StringUtils.java +++ b/register/src/main/java/com/webank/ai/fate/register/utils/StringUtils.java @@ -38,8 +38,10 @@ public final class StringUtils { public static final int INDEX_NOT_FOUND = -1; public static final String[] EMPTY_STRING_ARRAY = new String[0]; - - private static final Pattern KVP_PATTERN = Pattern.compile("([_.a-zA-Z0-9][-_.a-zA-Z0-9]*)[=](.*)"); //key value pair pattern. + /** + * key value pair pattern. + */ + private static final Pattern KVP_PATTERN = Pattern.compile("([_.a-zA-Z0-9][-_.a-zA-Z0-9]*)[=](.*)"); private static final Pattern INT_PATTERN = Pattern.compile("^\\d+$"); private static final int PAD_LIMIT = 8192; @@ -789,7 +791,7 @@ public static String trim(String str) { return str == null ? null : str.trim(); } - public static String toURLKey(String key) { + public static String toUrlKey(String key) { return key.toLowerCase().replaceAll(SEPARATOR_REGEX, HIDE_KEY_PREFIX); } diff --git a/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java b/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java index 97a03aef..05f7484b 100644 --- a/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java +++ b/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java @@ -56,7 +56,7 @@ public class ZookeeperRegistry extends FailbackRegistry { ; private final ZookeeperClient zkClient; Set registedString = Sets.newHashSet(); - String DYNAMIC_KEY = "dynamic"; + private static String DYNAMIC_KEY = "dynamic"; Set anyServices = new HashSet(); private String environment; private Set dynamicEnvironments = new HashSet(); diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/event/model/PipeHandleNotificationEvent.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/event/model/PipeHandleNotificationEvent.java index 325b9415..788e08a8 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/event/model/PipeHandleNotificationEvent.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/event/model/PipeHandleNotificationEvent.java @@ -38,9 +38,18 @@ public PipeHandlerInfo getPipeHandlerInfo() { return pipeHandlerInfo; } - public static enum Type { + public enum Type { + /** + * PUSH + */ PUSH, + /** + * PULL + */ PULL, + /** + * UNARY_CALL + */ UNARY_CALL; } } diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcStubFactory.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcStubFactory.java index 7f367822..4a2f363a 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcStubFactory.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcStubFactory.java @@ -188,7 +188,7 @@ private ManagedChannel createChannel(BasicMeta.Endpoint endpoint) { .maxInboundMessageSize(32 << 20) .enableRetry() .retryBufferSize(16 << 20) - .maxRetryAttempts(20); // todo: configurable + .maxRetryAttempts(20); // if secure client defined and endpoint is not in intranet if (serverConf.isSecureClient() && diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java index 2887b234..5c136cfa 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java @@ -80,7 +80,8 @@ public class DataTransferPipedClient { private BasicMeta.Endpoint endpoint; private boolean needSecureChannel; - private long MAX_AWAIT_HOURS = 24; + private static long MAX_AWAIT_HOURS = 24; + private static String SERVICE_ROLE_NAME = "serving-1.0"; public DataTransferPipedClient() { needSecureChannel = false; @@ -112,6 +113,7 @@ public void push(Proxy.Metadata metadata, Pipe pipe) { stub.getChannel(), onelineStringMetadata); int emptyRetryCount = 0; + int maxRetryCount = 60; Proxy.Packet packet = null; do { packet = (Proxy.Packet) pipe.read(1, TimeUnit.SECONDS); @@ -121,7 +123,7 @@ public void push(Proxy.Metadata metadata, Pipe pipe) { emptyRetryCount = 0; } else { ++emptyRetryCount; - if (emptyRetryCount % 60 == 0) { + if (emptyRetryCount % maxRetryCount == 0) { logger.info("[PUSH][CLIENT] push stub waiting. empty retry count: {}, metadata: {}", emptyRetryCount, onelineStringMetadata); } @@ -252,7 +254,7 @@ private DataTransferServiceGrpc.DataTransferServiceStub getStub(Proxy.Topic from ConfFileBasedFdnRouter confFileBasedFdnRouter = (ConfFileBasedFdnRouter) fdnRouter; Map>> routerTable = confFileBasedFdnRouter.getRouteTable(); - if (routerTable.containsKey(to.getPartyId()) &&routerTable.get(to.getPartyId()).get("serving-1.0")!=null&& "serving-1.0".equals(to.getRole())) { + if (routerTable.containsKey(to.getPartyId()) &&routerTable.get(to.getPartyId()).get(SERVICE_ROLE_NAME)!=null&& SERVICE_ROLE_NAME.equals(to.getRole())) { stub = routerByServiceRegister(from, to, pack); if (stub != null) { @@ -312,10 +314,10 @@ private DataTransferServiceGrpc.DataTransferServiceStub getStub(Proxy.Topic from return stub; } - private static final String modelKeySeparator = "&"; + private static final String MODEL_KEY_SEPARATOR = "&"; public static String genModelKey(String name, String namespace) { - return StringUtils.join(Arrays.asList(name, namespace), modelKeySeparator); + return StringUtils.join(Arrays.asList(name, namespace), MODEL_KEY_SEPARATOR); } diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/service/ConfFileBasedFdnRouter.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/service/ConfFileBasedFdnRouter.java index 58795fac..45742a9a 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/service/ConfFileBasedFdnRouter.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/service/ConfFileBasedFdnRouter.java @@ -409,7 +409,7 @@ private boolean hasRule(Map> target, Proxy.Topic f } if (rules == null) { - return result; // false + return result; } diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java index 774c46f7..b653edbb 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java @@ -24,6 +24,7 @@ import com.webank.ai.fate.api.networking.proxy.Proxy; import com.webank.ai.fate.networking.proxy.manager.ServerConfManager; import com.webank.ai.fate.serving.core.bean.Dict; +import com.webank.ai.fate.serving.core.utils.EncryptUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -111,7 +112,7 @@ private String calSignature(Proxy.Metadata header, Proxy.Data body, long timesta + toStringUtils.toOneLineString(header) + "\n" + toStringUtils.toOneLineString(body); encryptText = new String(encryptText.getBytes(), EncryptUtils.UTF8); - signature = Base64.getEncoder().encodeToString(EncryptUtils.HmacSHA1Encrypt(encryptText, appSecret)); + signature = Base64.getEncoder().encodeToString(EncryptUtils.hmacSha1Encrypt(encryptText, appSecret)); return signature; } diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/EncryptUtils.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/EncryptUtils.java deleted file mode 100644 index c903a039..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/EncryptUtils.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.util; - -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -public class EncryptUtils { - - public static final String UTF8 = "UTF-8"; - private static final String HMACSHA1 = "HmacSHA1"; - - public static byte[] HmacSHA1Encrypt(String encryptText, String encryptKey) throws Exception { - byte[] data = encryptKey.getBytes(UTF8); - SecretKey secretKey = new SecretKeySpec(data, HMACSHA1); - Mac mac = Mac.getInstance(HMACSHA1); - mac.init(secretKey); - - byte[] text = encryptText.getBytes(UTF8); - return mac.doFinal(text); - } - -} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/GetSystemInfo.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/GetSystemInfo.java deleted file mode 100644 index 67deb71d..00000000 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/GetSystemInfo.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.serving.proxy.common; - - -import com.sun.management.OperatingSystemMXBean; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.management.ManagementFactory; -import java.net.Inet4Address; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.util.Enumeration; - -public class GetSystemInfo { - - private static final Logger logger = LoggerFactory.getLogger(GetSystemInfo.class); - - - public static String localIp; - - static { - localIp = getLocalIp(); - } - - public static String getLocalIp() { - - String sysType = System.getProperties().getProperty("os.name"); - String ip; - - try { - if (sysType.toLowerCase().startsWith("win")) { - String localIP = null; - - localIP = InetAddress.getLocalHost().getHostAddress(); - - if (localIP != null) { - return localIP; - } - } else { - ip = getIpByEthNum("eth0"); - if (ip != null) { - return ip; - - - } - } - } catch (Throwable e) { - logger.error(e.getMessage(), e); - } - return ""; - } - - private static String getIpByEthNum(String ethNum) { - try { - Enumeration allNetInterfaces = NetworkInterface.getNetworkInterfaces(); - InetAddress ip; - while (allNetInterfaces.hasMoreElements()) { - NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement(); - if (ethNum.equals(netInterface.getName())) { - Enumeration addresses = netInterface.getInetAddresses(); - while (addresses.hasMoreElements()) { - ip = (InetAddress) addresses.nextElement(); - if (ip != null && ip instanceof Inet4Address) { - return ip.getHostAddress(); - } - } - } - } - } catch (SocketException e) { - logger.error(e.getMessage(), e); - } - return ""; - } - - - public static String getOsName() { - - String osName = System.getProperty("os.name"); - return osName; - } - - - public static double getSystemCpuLoad() { - OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory - .getOperatingSystemMXBean(); - double SystemCpuLoad = osmxb.getSystemCpuLoad(); - return SystemCpuLoad; - } - - - public static double getProcessCpuLoad() { - OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory - .getOperatingSystemMXBean(); - double ProcessCpuLoad = osmxb.getProcessCpuLoad(); - return ProcessCpuLoad; - } - - - public static long getTotalMemorySize() { - int kb = 1024; - OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory - .getOperatingSystemMXBean(); - long totalMemorySize = osmxb.getTotalPhysicalMemorySize() / kb; - return totalMemorySize; - } - - - public static long getFreePhysicalMemorySize() { - int kb = 1024; - OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory - .getOperatingSystemMXBean(); - long freePhysicalMemorySize = osmxb.getFreePhysicalMemorySize() / kb; - return freePhysicalMemorySize; - } - - - public static long getUsedMemory() { - int kb = 1024; - OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory - .getOperatingSystemMXBean(); - long usedMemory = (osmxb.getTotalPhysicalMemorySize() - osmxb.getFreePhysicalMemorySize()) / kb; - return usedMemory; - } - - -} - diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/GlobalResponseController.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/GlobalResponseController.java index 624621e5..347a9a4e 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/GlobalResponseController.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/GlobalResponseController.java @@ -26,7 +26,7 @@ public boolean supports(MethodParameter methodParameter, Class converterType) { Boolean isRest = AnnotationUtils.isAnnotationDeclaredLocally( RestController.class, methodParameter.getContainingClass()); ResponseBody responseBody = AnnotationUtils.findAnnotation( - methodParameter.getMethod(), ResponseBody.class); //得到方法上的注解 + methodParameter.getMethod(), ResponseBody.class); if (responseBody != null || isRest) { return true; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcType.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcType.java index c10a1a10..f9af71ba 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcType.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcType.java @@ -5,7 +5,13 @@ * @Author **/ public enum GrpcType { + /** + * INTRA_GRPC + */ INTRA_GRPC, + /** + * INTRA_GRPC + */ INTER_GRPC } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java index ea51418e..835bde6e 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java @@ -68,8 +68,7 @@ public RouteType getRouteType(){ public List getRouterInfoList(Context context, InboundPackage inboundPackage){ Proxy.Topic dstTopic; Proxy.Topic srcTopic; - String service_name = context.getServiceName(); - if("inference".equals(service_name)) { + if("inference".equals(context.getServiceName())) { Proxy.Topic.Builder topicBuilder = Proxy.Topic.newBuilder(); dstTopic = topicBuilder.setPartyId(selfCoordinator). setRole(inferenceServiceName) @@ -187,7 +186,7 @@ private boolean hasRule(Map> target, Proxy.Topic f } if (rules == null) { - return result; // false + return result; } stage = 0; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteType.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteType.java index 2b983384..d6a20fe8 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteType.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteType.java @@ -5,7 +5,13 @@ * @Author **/ public enum RouteType { + /** + * RANDOM_ROUTE + */ RANDOM_ROUTE, + /** + * CONSISTENT_HASH_ROUTE + */ CONSISTENT_HASH_ROUTE } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java index 1f0bed34..036e7bde 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java @@ -22,7 +22,7 @@ import com.google.gson.JsonParser; import com.google.gson.stream.JsonReader; import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.serving.proxy.utils.EncryptUtil; +import com.webank.ai.fate.serving.core.utils.EncryptUtils; import com.webank.ai.fate.serving.proxy.utils.FileUtils; import com.webank.ai.fate.serving.proxy.utils.ToStringUtils; import org.apache.commons.lang3.StringUtils; @@ -117,8 +117,8 @@ private String calSignature(Proxy.Metadata header, Proxy.Data body, long timesta String encryptText = String.valueOf(timestamp) + "\n" + toStringUtils.toOneLineString(header) + "\n" + toStringUtils.toOneLineString(body); - encryptText = new String(encryptText.getBytes(), EncryptUtil.UTF8); - signature = Base64.getEncoder().encodeToString(EncryptUtil.HmacSHA1Encrypt(encryptText, appSecret)); + encryptText = new String(encryptText.getBytes(), EncryptUtils.UTF8); + signature = Base64.getEncoder().encodeToString(EncryptUtils.hmacSha1Encrypt(encryptText, appSecret)); return signature; } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/EncryptUtil.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/EncryptUtil.java deleted file mode 100644 index 1ebcdcb9..00000000 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/EncryptUtil.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.webank.ai.fate.serving.proxy.utils; - -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import java.util.Base64; - -public class EncryptUtil { - - public static final String UTF8 = "UTF-8"; - private static final String HMACSHA1 = "HmacSHA1"; - - public static byte[] HmacSHA1Encrypt(String encryptText, String encryptKey) throws Exception { - byte[] data = encryptKey.getBytes(UTF8); - SecretKey secretKey = new SecretKeySpec(data, HMACSHA1); - Mac mac = Mac.getInstance(HMACSHA1); - mac.init(secretKey); - - byte[] text = encryptText.getBytes(UTF8); - return mac.doFinal(text); - } - - public static String generateSignature(String applyId, String timestamp, String nonce, - String appKey, String appSecret, String uri, String body) { - try { - String encryptText = applyId + "\n" + timestamp + "\n" + nonce + "\n" + appKey + "\n" + uri + "\n" + body; - encryptText = new String(encryptText.getBytes(), EncryptUtil.UTF8); - return Base64.getEncoder().encodeToString(EncryptUtil.HmacSHA1Encrypt(encryptText, appSecret)); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - - -} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/FederatedModelUtils.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/FederatedModelUtils.java index 730139a6..2c47c381 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/FederatedModelUtils.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/utils/FederatedModelUtils.java @@ -12,10 +12,10 @@ public class FederatedModelUtils { - private static final String modelKeySeparator = "&"; + private static final String MODEL_KEY_SEPARATOR = "&"; public static String genModelKey(String name, String namespace) { - return StringUtils.join(Arrays.asList(name, namespace), modelKeySeparator); + return StringUtils.join(Arrays.asList(name, namespace), MODEL_KEY_SEPARATOR); } public static String getModelRouteKey(Proxy.Packet packet) { diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index 541a5149..46f1591f 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -19,6 +19,7 @@ import com.codahale.metrics.ConsoleReporter; import com.codahale.metrics.jmx.JmxReporter; import com.google.common.collect.Sets; +import com.webank.ai.fate.register.common.NamedThreadFactory; import com.webank.ai.fate.register.provider.FateServer; import com.webank.ai.fate.register.provider.FateServerBuilder; import com.webank.ai.fate.register.router.RouterService; @@ -45,10 +46,7 @@ import java.io.IOException; import java.util.HashMap; import java.util.Set; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.Executor; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; public class ServingServer implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(ServingServer.class); @@ -106,7 +104,8 @@ private void start(String[] args) throws IOException { Integer maxPoolSize = Configuration.getPropertyInt("serving.max.pool.size",100); Integer aliveTime = Configuration.getPropertyInt("serving.pool.alive.time",1000); Integer queueSize = Configuration.getPropertyInt("serving.pool.queue.size",10); - Executor executor = new ThreadPoolExecutor(corePoolSize,maxPoolSize,aliveTime.longValue(),TimeUnit.MILLISECONDS,new ArrayBlockingQueue<>(queueSize)); + Executor executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, aliveTime.longValue(), TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(queueSize), new NamedThreadFactory("ServingServer", true)); FateServerBuilder serverBuilder = (FateServerBuilder) ServerBuilder.forPort(port); serverBuilder.executor(executor); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/CommonPreProcessing.java b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/CommonPreProcessing.java index 0286d5e7..be2a0505 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/CommonPreProcessing.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing/CommonPreProcessing.java @@ -15,7 +15,7 @@ public PreProcessingResult getResult(Context context , String paras) { PreProcessingResult preProcessingResult = new PreProcessingResult(); preProcessingResult.setProcessingResult(preProcessing(paras)); Map featureIds = new HashMap<>(); - JSONObject para_obj = new JSONObject(paras); + JSONObject paraObj = new JSONObject(paras); preProcessingResult.setFeatureIds(featureIds); return preProcessingResult; } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java index 76004b2d..d75b71fd 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java @@ -38,7 +38,7 @@ @Component public class ModelUtils { private static final Logger logger = LoggerFactory.getLogger(ModelUtils.class); - private static final String modelKeySeparator = "&"; + private static final String MODEL_KEY_SEPARATOR = "&"; private static ModelUtils modelUtils; @@ -121,11 +121,11 @@ public static PipelineTask loadModel(String name, String namespace) { } public static String genModelKey(String name, String namespace) { - return StringUtils.join(Arrays.asList(name, namespace), modelKeySeparator); + return StringUtils.join(Arrays.asList(name, namespace), MODEL_KEY_SEPARATOR); } public static String[] splitModelKey(String key) { - return StringUtils.split(key, modelKeySeparator); + return StringUtils.split(key, MODEL_KEY_SEPARATOR); } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java index 18bece6b..5c383f0e 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java @@ -25,6 +25,7 @@ import com.webank.ai.fate.api.mlmodel.manager.ModelServiceProto.PublishRequest; import com.webank.ai.fate.api.mlmodel.manager.ModelServiceProto.PublishResponse; import com.webank.ai.fate.register.annotions.RegisterService; +import com.webank.ai.fate.register.common.NamedThreadFactory; import com.webank.ai.fate.serving.core.bean.*; import com.webank.ai.fate.serving.core.utils.ObjectTransform; import com.webank.ai.fate.serving.interfaces.ModelManager; @@ -78,7 +79,8 @@ public String toString(){ LinkedHashMap publishLoadReqMap = new LinkedHashMap(); LinkedHashMap publicOnlineReqMap = new LinkedHashMap(); - ExecutorService executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); + ExecutorService executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(), new NamedThreadFactory("ModelService", true)); File publishLoadStoreFile; File publishOnlineStoreFile; From 93743c3532f255910fdcc7f4785aae79958bacbf Mon Sep 17 00:00:00 2001 From: utu Date: Mon, 30 Dec 2019 23:04:30 +0800 Subject: [PATCH 073/190] =?UTF-8?q?proxy=20refactor=EF=BC=9Aadd=20metrics?= =?UTF-8?q?=20as=20an=20spring=20boot=20starter.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fate-metrics-api/pom.xml | 16 +++ .../metrics/api/DefaultMetricFactory.java | 105 ++++++++++++++++++ .../ai/fate/serving/metrics/api/ICounter.java | 9 ++ .../serving/metrics/api/IMetricFactory.java | 12 ++ .../ai/fate/serving/metrics/api/ITimer.java | 10 ++ fate-metrics-micrometer/pom.xml | 41 +++++++ .../micrometer/MmAutoConfiguration.java | 22 ++++ .../serving/metrics/micrometer/MmCounter.java | 37 ++++++ .../serving/metrics/micrometer/MmGuage.java | 35 ++++++ .../serving/metrics/micrometer/MmMeter.java | 74 ++++++++++++ .../metrics/micrometer/MmMetricFactory.java | 81 ++++++++++++++ .../metrics/micrometer/MmMetricsRegistry.java | 54 +++++++++ .../serving/metrics/micrometer/MmTimer.java | 38 +++++++ .../main/resources/META-INF/spring.factories | 1 + fate-serving-core/pom.xml | 2 +- federatedml/pom.xml | 2 +- pom.xml | 4 +- register/pom.xml | 4 +- router/pom.xml | 4 +- serving-proxy/pom.xml | 18 ++- .../proxy/controller/ProxyController.java | 9 ++ .../serving/proxy/exceptions/ErrorCode.java | 20 ++-- .../exceptions/GlobalExceptionHandler.java | 6 +- .../proxy/exceptions/SqlAttactException.java | 7 -- .../proxy/exceptions/SysException.java | 12 +- .../proxy/rpc/core/InterceptorChain.java | 2 +- .../proxy/rpc/grpc/ProxyRequestHandler.java | 13 +++ .../proxy/rpc/services/InferenceService.java | 10 ++ .../proxy/rpc/services/UnaryCallService.java | 10 ++ .../src/main/resources/application.properties | 4 + serving-server/pom.xml | 2 +- 31 files changed, 619 insertions(+), 45 deletions(-) create mode 100644 fate-metrics-api/pom.xml create mode 100644 fate-metrics-api/src/main/java/com/webank/ai/fate/serving/metrics/api/DefaultMetricFactory.java create mode 100644 fate-metrics-api/src/main/java/com/webank/ai/fate/serving/metrics/api/ICounter.java create mode 100644 fate-metrics-api/src/main/java/com/webank/ai/fate/serving/metrics/api/IMetricFactory.java create mode 100644 fate-metrics-api/src/main/java/com/webank/ai/fate/serving/metrics/api/ITimer.java create mode 100644 fate-metrics-micrometer/pom.xml create mode 100644 fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmAutoConfiguration.java create mode 100644 fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmCounter.java create mode 100644 fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmGuage.java create mode 100644 fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmMeter.java create mode 100644 fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmMetricFactory.java create mode 100644 fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmMetricsRegistry.java create mode 100644 fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmTimer.java create mode 100644 fate-metrics-micrometer/src/main/resources/META-INF/spring.factories delete mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/SqlAttactException.java diff --git a/fate-metrics-api/pom.xml b/fate-metrics-api/pom.xml new file mode 100644 index 00000000..c48480de --- /dev/null +++ b/fate-metrics-api/pom.xml @@ -0,0 +1,16 @@ + + + + + fate-serving + com.webank.ai.fate + ${fate.version} + + 4.0.0 + + fate-metrics-api + jar + + \ No newline at end of file diff --git a/fate-metrics-api/src/main/java/com/webank/ai/fate/serving/metrics/api/DefaultMetricFactory.java b/fate-metrics-api/src/main/java/com/webank/ai/fate/serving/metrics/api/DefaultMetricFactory.java new file mode 100644 index 00000000..aaeee352 --- /dev/null +++ b/fate-metrics-api/src/main/java/com/webank/ai/fate/serving/metrics/api/DefaultMetricFactory.java @@ -0,0 +1,105 @@ +package com.webank.ai.fate.serving.metrics.api; + +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +public class DefaultMetricFactory implements IMetricFactory { + private Timer timer; + private int period; + private boolean mute; + + public DefaultMetricFactory() { + this(10000); + } + + public DefaultMetricFactory(int period) { + this(period, false); + } + + public DefaultMetricFactory(int period, boolean mute) { + System.out.println("default metrics factory created"); + timer = new Timer("default_metrics_factory_timer", true); + this.period = period; + this.mute = mute; + } + + @Override + public ICounter counter(String name, String desc, String... tags) { + return new ICounter() { + private AtomicLong counter = new AtomicLong(0); + private String metricName = name; + private String description = desc; + + @Override + public void increment() { + if (!mute) { + System.out.println( + "metric name: " + metricName + ", desc: " + description + + ", counter: " + counter.incrementAndGet()); + } + } + + @Override + public void increment(double delta) { + if (!mute) { + System.out.println( + "metric name: " + metricName + ", desc: " + description + + ", counter: " + counter.addAndGet((long)delta)); + } + } + }; + } + + @Override + public ITimer timer(String name, String desc, String... tags) { + return new ITimer() { + private int limit = 1_000_000; + private SortedMap storage = buildStorage(); + + private SortedMap buildStorage() { + return Collections.synchronizedSortedMap(new TreeMap<>()); + } + + @Override + public void record(long milliSeconds) { + if (storage.size() >= limit) { + storage = buildStorage(); + } + storage.put(System.currentTimeMillis(), milliSeconds); + if (!mute) { + System.out.println("time consumed: " + milliSeconds + " ms."); + } + } + + @Override + public void record(long duration, TimeUnit unit) { + this.record(TimeUnit.MILLISECONDS.convert(duration, unit)); + } + }; + } + + @Override + public void gauge(String name, String desc, Callable callable, String... tags) { + try { + if (!mute) { + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + try { + System.out.println( + "metric name: " + name + ", desc: " + desc + + ", value: " + callable.call()); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, 0, period); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + +} \ No newline at end of file diff --git a/fate-metrics-api/src/main/java/com/webank/ai/fate/serving/metrics/api/ICounter.java b/fate-metrics-api/src/main/java/com/webank/ai/fate/serving/metrics/api/ICounter.java new file mode 100644 index 00000000..f48f52ee --- /dev/null +++ b/fate-metrics-api/src/main/java/com/webank/ai/fate/serving/metrics/api/ICounter.java @@ -0,0 +1,9 @@ +package com.webank.ai.fate.serving.metrics.api; + +public interface ICounter { + + void increment(double delta); + + void increment(); +} + diff --git a/fate-metrics-api/src/main/java/com/webank/ai/fate/serving/metrics/api/IMetricFactory.java b/fate-metrics-api/src/main/java/com/webank/ai/fate/serving/metrics/api/IMetricFactory.java new file mode 100644 index 00000000..fda120c6 --- /dev/null +++ b/fate-metrics-api/src/main/java/com/webank/ai/fate/serving/metrics/api/IMetricFactory.java @@ -0,0 +1,12 @@ +package com.webank.ai.fate.serving.metrics.api; + +import java.util.concurrent.Callable; + +public interface IMetricFactory { + + ICounter counter(String name, String desc, String... tags); + + ITimer timer(String name, String desc, String... tags); + + void gauge(String name, String desc, Callable callable, String... tags); +} diff --git a/fate-metrics-api/src/main/java/com/webank/ai/fate/serving/metrics/api/ITimer.java b/fate-metrics-api/src/main/java/com/webank/ai/fate/serving/metrics/api/ITimer.java new file mode 100644 index 00000000..66dfac3d --- /dev/null +++ b/fate-metrics-api/src/main/java/com/webank/ai/fate/serving/metrics/api/ITimer.java @@ -0,0 +1,10 @@ +package com.webank.ai.fate.serving.metrics.api; + +import java.util.concurrent.TimeUnit; + +public interface ITimer { + + void record(long duration, TimeUnit unit); + + void record(long milliSeconds); +} diff --git a/fate-metrics-micrometer/pom.xml b/fate-metrics-micrometer/pom.xml new file mode 100644 index 00000000..e7166d2d --- /dev/null +++ b/fate-metrics-micrometer/pom.xml @@ -0,0 +1,41 @@ + + + + + fate-serving + com.webank.ai.fate + ${fate.version} + + 4.0.0 + + fate-metrics-micrometer + jar + + + + org.springframework.boot + spring-boot-starter-actuator + ${spring.boot.version} + + + org.springframework.boot + spring-boot-autoconfigure + ${spring.boot.version} + + + org.springframework.boot + spring-boot-configuration-processor + ${spring.boot.version} + true + + + com.webank.ai.fate + fate-metrics-api + ${fate.version} + + + + + \ No newline at end of file diff --git a/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmAutoConfiguration.java b/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmAutoConfiguration.java new file mode 100644 index 00000000..d44c4926 --- /dev/null +++ b/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmAutoConfiguration.java @@ -0,0 +1,22 @@ +package com.webank.ai.fate.serving.metrics.micrometer; + +import com.webank.ai.fate.serving.metrics.api.IMetricFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConditionalOnClass(IMetricFactory.class) +public class MmAutoConfiguration { + @Bean("mmMetricFactory") + @ConditionalOnMissingBean + public IMetricFactory mmMetricFactory() { + return new MmMetricFactory(); + } + + @Bean + public MmMetricsRegistry mmMetricsRegistry() { + return new MmMetricsRegistry(); + } +} diff --git a/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmCounter.java b/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmCounter.java new file mode 100644 index 00000000..1e57cf4e --- /dev/null +++ b/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmCounter.java @@ -0,0 +1,37 @@ +package com.webank.ai.fate.serving.metrics.micrometer; + +import com.webank.ai.fate.serving.metrics.api.ICounter; +import io.micrometer.core.instrument.Counter; + + +public class MmCounter extends MmMeter implements ICounter { + private Counter counter; + + public MmCounter(String name, String desc, Counter counter, String... tags) { + super(name, desc, tags); + this.counter = counter; + } + + @Override + public void increment() { + this.counter.increment(); + } + + @Override + public void increment(double delta) { + this.counter.increment(delta); + } + + public Counter getCounter() { + return counter; + } + + public void setCounter(Counter counter) { + this.counter = counter; + } + + @Override + public String toString() { + return "MmCounter{" + super.toString() + ", counter=" + counter.count() + '}'; + } +} diff --git a/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmGuage.java b/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmGuage.java new file mode 100644 index 00000000..a2cab4ba --- /dev/null +++ b/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmGuage.java @@ -0,0 +1,35 @@ +package com.webank.ai.fate.serving.metrics.micrometer; + +import java.util.concurrent.Callable; +import java.util.function.ToDoubleFunction; + +public class MmGuage extends MmMeter { + public static final ToDoubleFunction> metricFunc = doubleCallable -> { + try { + return doubleCallable.call(); + } catch (Exception e) { + e.printStackTrace(); + return 0L; + } + }; + + private Callable callable; + + public MmGuage(String name, String desc, Callable callable, String... tags) { + super(name, desc, tags); + this.callable = callable; + } + + public Callable getCallable() { + return callable; + } + + public void setCallable(Callable callable) { + this.callable = callable; + } + + @Override + public String getTagsString() { + return "MmGuage{" + super.toString() + '}'; + } +} diff --git a/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmMeter.java b/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmMeter.java new file mode 100644 index 00000000..ef7e1310 --- /dev/null +++ b/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmMeter.java @@ -0,0 +1,74 @@ +package com.webank.ai.fate.serving.metrics.micrometer; + +import java.util.Objects; + +public abstract class MmMeter { + private String name; + private String tagsString; + private String desc; + + public MmMeter(String name, String desc, String... tags) { + this.name = name; + this.desc = desc; + this.tagsString = kvArray2String(tags); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public String getTagsString() { + return tagsString; + } + + public void setTagsString(String tagsString) { + this.tagsString = tagsString; + } + + @Override + public int hashCode() { + return Objects.hash(name, tagsString); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + MmMeter that = (MmMeter) obj; + return Objects.equals(name, that.name) && Objects.equals(tagsString, that.tagsString); + } + + @Override + public String toString() { + return "name='" + name + '\'' + ", tagsString=" + + tagsString + ", desc=" + desc; + } + + private String kvArray2String(String... keyValues) { + String result = ""; + if (keyValues != null && keyValues.length != 0) { + if (keyValues.length % 2 == 1) { + throw new IllegalArgumentException("size must be even, it is a set of key=value pairs"); + } else { + for(int i = 0; i < keyValues.length; i += 2) { + result += (keyValues[i] + ':' + keyValues[i + 1] + ';'); + } + } + } + return result; + } +} diff --git a/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmMetricFactory.java b/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmMetricFactory.java new file mode 100644 index 00000000..99e9a04d --- /dev/null +++ b/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmMetricFactory.java @@ -0,0 +1,81 @@ +package com.webank.ai.fate.serving.metrics.micrometer; + +import com.webank.ai.fate.serving.metrics.api.ICounter; +import com.webank.ai.fate.serving.metrics.api.IMetricFactory; +import com.webank.ai.fate.serving.metrics.api.ITimer; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.annotation.PreDestroy; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class MmMetricFactory implements IMetricFactory { + private static Logger logger = LoggerFactory.getLogger(MmMetricFactory.class); + @Autowired + private MmMetricsRegistry register; + private Set gauges = ConcurrentHashMap.newKeySet(); + private ConcurrentMap counters = new ConcurrentHashMap<>(); + private ConcurrentMap timers = new ConcurrentHashMap<>(); + + @PreDestroy + public void destroy() { + gauges.clear(); + counters.clear(); + timers.clear(); + } + + @Override + public ICounter counter(String name, String desc, String... tags) { + ICounter c; + String key = getKey(name, tags); + if ((c = counters.get(key)) == null) { + c = counters.computeIfAbsent(key, k -> new MmCounter(name, desc, register.registerCounter(name, desc, tags), tags)); + } + return c; + } + + @Override + public ITimer timer(String name, String desc, String... tags) { + ITimer t; + String key = getKey(name, tags); + if ((t = timers.get(key)) == null) { + t = timers.computeIfAbsent(key, k -> new MmTimer(name, desc, register.registerTimer(name, desc, tags), tags)); + } + return t; + } + + @Override + public void gauge(String name, String desc, Callable callable, String... tags) { + MmGuage g = new MmGuage(name, desc, callable, tags); + if (gauges.add(g)) { + register.registerGauge(g, tags); + logger.info("gauge metric added for: {}", g); + + } else { + logger.warn("duplicated gauge: {}", g); + } + } + + private String getKey(String name, String... tags) { + return name + StringUtils.join(tags); + } + + public Set getGauges() { + return gauges; + } + + public Map getCounters() { + return counters; + } + + public Map getTimers() { + return timers; + } + +} diff --git a/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmMetricsRegistry.java b/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmMetricsRegistry.java new file mode 100644 index 00000000..78b463f6 --- /dev/null +++ b/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmMetricsRegistry.java @@ -0,0 +1,54 @@ +package com.webank.ai.fate.serving.metrics.micrometer; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Timer; +import io.micrometer.core.instrument.binder.MeterBinder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PreDestroy; + +public class MmMetricsRegistry implements MeterBinder { + private static Logger logger = LoggerFactory.getLogger(MmMetricsRegistry.class); + + private MeterRegistry registry; + + @PreDestroy + public void destroy() { + if (registry != null && !registry.isClosed()) { + registry.close(); + } + } + + @Override + public void bindTo(MeterRegistry registry) { + if (this.registry == null) { + this.registry = registry; + logger.info("MeterRegistry is set..."); + } + } + + public void registerGauge(MmGuage g, String... tags) { + checkState(); + Gauge.builder(g.getName(), g.getCallable(), MmGuage.metricFunc).tags(tags) + .description(g.getDesc()).register(this.registry); + } + + public Counter registerCounter(String name, String desc, String... tags) { + checkState(); + return Counter.builder(name).tags(tags).description(desc).register(this.registry); + } + + public Timer registerTimer(String name, String desc, String... tags) { + checkState(); + return Timer.builder(name).tags(tags).description(desc).register(this.registry); + } + + private void checkState() { + if (this.registry == null) { + throw new IllegalStateException("Metrics registry is not initialized yet!"); + } + } +} diff --git a/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmTimer.java b/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmTimer.java new file mode 100644 index 00000000..991f84c6 --- /dev/null +++ b/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmTimer.java @@ -0,0 +1,38 @@ +package com.webank.ai.fate.serving.metrics.micrometer; + +import com.webank.ai.fate.serving.metrics.api.ITimer; +import io.micrometer.core.instrument.Timer; + +import java.util.concurrent.TimeUnit; + +public class MmTimer extends MmMeter implements ITimer { + private Timer timer; + + public MmTimer(String name, String desc, Timer timer, String... tags) { + super(name, desc, tags); + this.timer = timer; + } + + @Override + public void record(long millis) { + this.record(millis, TimeUnit.MILLISECONDS); + } + + @Override + public void record(long time, TimeUnit unit) { + timer.record(time, unit); + } + + public Timer getTimer() { + return timer; + } + + public void setTimer(Timer timer) { + this.timer = timer; + } + + @Override + public String toString() { + return "com.webank.ai.fate.serving.metrics.micrometer.MmTimer{" + super.toString() + '}'; + } +} diff --git a/fate-metrics-micrometer/src/main/resources/META-INF/spring.factories b/fate-metrics-micrometer/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..c4f66baf --- /dev/null +++ b/fate-metrics-micrometer/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.webank.ai.fate.serving.metrics.micrometer.MmAutoConfiguration \ No newline at end of file diff --git a/fate-serving-core/pom.xml b/fate-serving-core/pom.xml index 2794ac00..15567d7d 100644 --- a/fate-serving-core/pom.xml +++ b/fate-serving-core/pom.xml @@ -20,7 +20,7 @@ fate-serving com.webank.ai.fate - 1.2.0 + ${fate.version} 4.0.0 diff --git a/federatedml/pom.xml b/federatedml/pom.xml index 1cc61eb2..1de3cd2c 100644 --- a/federatedml/pom.xml +++ b/federatedml/pom.xml @@ -20,7 +20,7 @@ fate-serving com.webank.ai.fate - 1.2.0 + ${fate.version} 4.0.0 diff --git a/pom.xml b/pom.xml index 2d451c04..b8c01b5d 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> com.webank.ai.fate fate-serving - 1.2.0 + ${fate.version} pom 4.0.0 @@ -31,6 +31,8 @@ register router serving-proxy + fate-metrics-api + fate-metrics-micrometer diff --git a/register/pom.xml b/register/pom.xml index 7880e95b..0a8e29cf 100644 --- a/register/pom.xml +++ b/register/pom.xml @@ -20,12 +20,12 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> fate-register - + fate-serving com.webank.ai.fate - 1.2.0 + ${fate.version} jar diff --git a/router/pom.xml b/router/pom.xml index f40ef51d..a7c5f1af 100644 --- a/router/pom.xml +++ b/router/pom.xml @@ -20,13 +20,13 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> fate-serving-router - + fate-serving com.webank.ai.fate - 1.2.0 + ${fate.version} jar diff --git a/serving-proxy/pom.xml b/serving-proxy/pom.xml index 8f8544d2..1637df61 100644 --- a/serving-proxy/pom.xml +++ b/serving-proxy/pom.xml @@ -4,11 +4,12 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> fate-serving-proxy + ${fate.version} fate-serving com.webank.ai.fate - 1.2.0 + ${fate.version} jar @@ -20,7 +21,7 @@ com.webank.ai.fate fate-register - ${project.version} + ${fate.version} @@ -214,9 +215,20 @@ com.webank.ai.fate fate-serving-core - ${project.version} + ${fate.version} compile + + com.webank.ai.fate + fate-metrics-api + ${fate.version} + + + + com.webank.ai.fate + fate-metrics-micrometer + ${fate.version} +
diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java index 776cafd7..7e0532db 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java @@ -2,6 +2,8 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; +import com.webank.ai.fate.serving.metrics.api.ICounter; +import com.webank.ai.fate.serving.metrics.api.IMetricFactory; import com.webank.ai.fate.serving.proxy.common.Dict; import com.webank.ai.fate.serving.proxy.rpc.core.*; import com.webank.ai.fate.serving.proxy.utils.WebUtil; @@ -30,6 +32,9 @@ public class ProxyController { @Autowired ProxyServiceRegister proxyServiceRegister; + @Autowired + IMetricFactory metricFactory; + @Value("${coordinator:9999}") private String selfCoordinator; @@ -52,6 +57,7 @@ public Callable federation(@PathVariable String version, HttpServletRequest httpServletRequest, @RequestHeader HttpHeaders headers ) throws Exception { + metricFactory.counter("http.inference", "inference request", "request", "all").increment(); return new Callable() { @Override @@ -71,6 +77,9 @@ public String call() throws Exception { result.getData().remove("warn"); result.getData().remove("caseid"); } + + metricFactory.counter("http.inference", "inference response", "response", "all").increment(); + return JSON.toJSONString(result.getData()); } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorCode.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorCode.java index fff44a09..22f24b65 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorCode.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorCode.java @@ -2,16 +2,16 @@ public class ErrorCode { - public static String PARAM_ERROR ="100"; //参数错误 - public static String ROLE_ERROR ="101"; //鉴权错误 - public static String SERVICE_NOT_FOUND= "102"; //服务不存在 - public static String SYSTEM_ERROR = "103";//系统错误 - public static String LIMIT_ERROR="104";// 系统限流 - public static String QUOTA_ERROR="105";// 配额耗尽 - public static String ORDER_ERROR="106";// 订单信息异常 - public static String NET_ERROR="107";// 网络异常 - public static String SHUTDOWN_ERROR="108";// 服务器关闭异常 - public static String ROUTER_ERROR="109";// 路由信息异常 + public static String PARAM_ERROR ="100"; + public static String ROLE_ERROR ="101"; + public static String SERVICE_NOT_FOUND= "102"; + public static String SYSTEM_ERROR = "103"; + public static String LIMIT_ERROR="104"; + public static String QUOTA_ERROR="105"; + public static String ORDER_ERROR="106"; + public static String NET_ERROR="107"; + public static String SHUTDOWN_ERROR="108"; + public static String ROUTER_ERROR="109"; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/GlobalExceptionHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/GlobalExceptionHandler.java index 91d0fa17..35f35648 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/GlobalExceptionHandler.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/GlobalExceptionHandler.java @@ -13,11 +13,7 @@ import java.util.HashMap; import java.util.Map; -/** - * 统一异常样处理类 - * - * @Author - */ + @RestController @ControllerAdvice public class GlobalExceptionHandler { diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/SqlAttactException.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/SqlAttactException.java deleted file mode 100644 index 463b8700..00000000 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/SqlAttactException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.webank.ai.fate.serving.proxy.exceptions; - -/** - * sql 注入 - */ -public class SqlAttactException extends RuntimeException{ -} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/SysException.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/SysException.java index 9b35ec87..37d0dae4 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/SysException.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/SysException.java @@ -3,21 +3,11 @@ import org.springframework.core.NestedRuntimeException; public class SysException extends NestedRuntimeException { - /** - * 构造函数。 - * - * @param msg 异常描述 - */ + public SysException(String msg) { super(msg); } - /** - * 构造函数。 - * - * @param msg 异常描述 - * @param ex 异常 - */ public SysException(String msg, Throwable ex) { super(msg, ex); } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/InterceptorChain.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/InterceptorChain.java index b1f82e0b..c48f957f 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/InterceptorChain.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/InterceptorChain.java @@ -1,7 +1,7 @@ package com.webank.ai.fate.serving.proxy.rpc.core; /** - * @Description 拦截器链 + * @Description * @Author **/ public interface InterceptorChain extends Interceptor { diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java index e70e970a..f0dbf2e7 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java @@ -5,14 +5,20 @@ import com.webank.ai.fate.api.networking.proxy.DataTransferServiceGrpc; import com.webank.ai.fate.api.networking.proxy.Proxy; import com.webank.ai.fate.register.annotions.RegisterService; +import com.webank.ai.fate.serving.metrics.api.IMetricFactory; import com.webank.ai.fate.serving.proxy.common.Dict; import com.webank.ai.fate.serving.proxy.rpc.core.*; import io.grpc.stub.StreamObserver; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; public abstract class ProxyRequestHandler extends DataTransferServiceGrpc.DataTransferServiceImplBase { + + @Autowired + IMetricFactory metricFactory; + private static final Logger logger = LoggerFactory.getLogger(ProxyRequestHandler.class); public abstract ProxyServiceRegister getProxyServiceRegister(); @@ -23,12 +29,16 @@ public abstract class ProxyRequestHandler extends DataTransferServiceGrpc.DataTr @Override public void unaryCall(Proxy.Packet req, StreamObserver responseObserver) { + metricFactory.counter("grpc.unaryCall", "unaryCall request", "request", "all").increment(); + logger.info("unaryCall req {}",req); ServiceAdaptor unaryCallService = getProxyServiceRegister().getServiceAdaptor("unaryCall"); Context context = new Context(); InboundPackage inboundPackage = buildInboundPackage(context, req); setExtraInfo(context, inboundPackage, req); + metricFactory.counter("grpc.unaryCall", "unaryCall request", "request", context.getGrpcType().toString()).increment(); + OutboundPackage outboundPackage = null; try { outboundPackage = unaryCallService.service(context,inboundPackage); @@ -39,6 +49,9 @@ public void unaryCall(Proxy.Packet req, StreamObserver responseObs Proxy.Packet result = (Proxy.Packet)outboundPackage.getData(); responseObserver.onNext(result); responseObserver.onCompleted(); + + metricFactory.counter("grpc.unaryCall", "unaryCall response", "response", context.getGrpcType().toString()).increment(); + metricFactory.counter("grpc.unaryCall", "unaryCall response", "response", "all").increment(); } public InboundPackage buildInboundPackage(Context context, Proxy.Packet req){ diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java index 0c630846..a976fef9 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java @@ -6,6 +6,7 @@ import com.google.protobuf.ByteString; import com.webank.ai.fate.api.serving.InferenceServiceGrpc; import com.webank.ai.fate.api.serving.InferenceServiceProto; +import com.webank.ai.fate.serving.metrics.api.IMetricFactory; import com.webank.ai.fate.serving.proxy.common.Dict; import com.webank.ai.fate.serving.proxy.exceptions.NoResultException; import com.webank.ai.fate.serving.proxy.rpc.core.*; @@ -37,6 +38,10 @@ public class InferenceService extends AbstractServiceAdaptor { Logger logger = LoggerFactory.getLogger(InferenceService.class); + + @Autowired + IMetricFactory metricFactory; + @Autowired GrpcConnectionPool grpcConnectionPool; @@ -78,11 +83,16 @@ public Map doService(Context context, InboundPackage data, OutboundPackage< reqBuilder.setBody(ByteString.copyFrom(JSON.toJSONString(inferenceReqMap).getBytes())); InferenceServiceGrpc.InferenceServiceFutureStub futureStub = InferenceServiceGrpc.newFutureStub(managedChannel); + + metricFactory.counter("http.inference", "send to self serving server", "send", "self.serving-server").increment(); + ListenableFuture resultFuture = futureStub.inference(reqBuilder.build()); InferenceServiceProto.InferenceMessage result = resultFuture.get(timeout,TimeUnit.MILLISECONDS); + metricFactory.counter("http.inference", "receive from self serving server", "receive", "self.serving-server", "result", "success").increment(); logger.info("routerinfo {} send {} result {}",routerInfo,inferenceReqMap,result); resultString = new String(result.getBody().toByteArray()); } catch (Exception e) { + metricFactory.counter("http.inference", "receive from self serving server", "receive", "self.serving-server", "result", "grpc.error").increment(); logger.error("get grpc result error", e); throw new NoResultException(); } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java index d4888336..5c134ff6 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java @@ -6,6 +6,7 @@ import com.google.protobuf.ByteString; import com.webank.ai.fate.api.networking.proxy.DataTransferServiceGrpc; import com.webank.ai.fate.api.networking.proxy.Proxy; +import com.webank.ai.fate.serving.metrics.api.IMetricFactory; import com.webank.ai.fate.serving.proxy.common.Dict; import com.webank.ai.fate.serving.proxy.exceptions.ErrorCode; import com.webank.ai.fate.serving.proxy.rpc.core.*; @@ -36,6 +37,9 @@ "defaultServingRouter"}) public class UnaryCallService extends AbstractServiceAdaptor { + @Autowired + IMetricFactory metricFactory; + @Autowired GrpcConnectionPool grpcConnectionPool; @@ -64,15 +68,21 @@ public Proxy.Packet doService(Context context, InboundPackage data stub1.withDeadlineAfter(timeout, TimeUnit.MILLISECONDS); + metricFactory.counter("grpc.unaryCall", "send out to self serving-server or other proxy", "send", "self serving-server or other proxy").increment(); + context.setDownstreamBegin(System.currentTimeMillis()); ListenableFuture future= stub1.unaryCall(sourcePackage); Proxy.Packet packet = future.get(timeout,TimeUnit.MILLISECONDS); + metricFactory.counter("grpc.unaryCall", "receive from self serving-server or other proxy", "receive", "self serving-server or other proxy", "result", "success").increment(); + return packet; } catch (Exception e) { + metricFactory.counter("grpc.unaryCall", "receive from self serving-server or other proxy", "receive", "self serving-server or other proxy", "result", "grpc.error").increment(); + e.printStackTrace(); logger.error("unaryCall error ",e); }finally { diff --git a/serving-proxy/src/main/resources/application.properties b/serving-proxy/src/main/resources/application.properties index 3497ae9e..5ca19f36 100644 --- a/serving-proxy/src/main/resources/application.properties +++ b/serving-proxy/src/main/resources/application.properties @@ -3,6 +3,10 @@ coordinator=9999 server.port=8080 +# actuator +management.server.port=10087 +management.endpoints.web.exposure.include=health,info,metrics + #random, consistent routeType=random diff --git a/serving-server/pom.xml b/serving-server/pom.xml index 313cda2a..c7fe7b0d 100644 --- a/serving-server/pom.xml +++ b/serving-server/pom.xml @@ -20,7 +20,7 @@ fate-serving com.webank.ai.fate - 1.2.0 + ${fate.version} 4.0.0 From 2b43ae7f3106b97b3dbcedaa46abb132978d97cf Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Tue, 31 Dec 2019 14:52:47 +0800 Subject: [PATCH 074/190] move common class to core Signed-off-by: v_dylanxu <136539068@qq.com> --- .../core}/exceptions/BaseException.java | 2 +- .../core}/exceptions/CustomException.java | 2 +- .../serving/core}/exceptions/ErrorCode.java | 2 +- .../core}/exceptions/ErrorResponseEntity.java | 2 +- .../exceptions/InvalidRoleInfoException.java | 2 +- .../core}/exceptions/NoResultException.java | 2 +- .../core}/exceptions/NoRouteInfoException.java | 2 +- .../core}/exceptions/OverLoadException.java | 2 +- .../exceptions/ShowDownRejectException.java | 2 +- .../serving/core}/exceptions/SysException.java | 2 +- .../fate/serving/core}/rpc/core/Context.java | 6 +++--- .../rpc/core/DefaultInterceptorChain.java | 2 +- .../serving/core}/rpc/core/InboundPackage.java | 9 ++++----- .../serving/core}/rpc/core/Interceptor.java | 2 +- .../core}/rpc/core/InterceptorChain.java | 2 +- .../core}/rpc/core/OutboundPackage.java | 2 +- .../serving/core}/rpc/core/OverLoadLogger.java | 2 +- .../serving/core}/rpc/core/ProxyService.java | 2 +- .../serving/core}/rpc/core/ServiceAdaptor.java | 4 ++-- .../core}/rpc/core/ServiceRegister.java | 2 +- .../fate/serving/core}/rpc/grpc/GrpcType.java | 2 +- .../serving/core}/rpc/router/RouteType.java | 2 +- .../core}/rpc/router/RouteTypeConvertor.java | 2 +- .../serving/core}/rpc/router/RouterInfo.java | 2 +- .../core/rpc/router/RouterInterface.java | 10 ++++++++++ .../serving/proxy/common/ErrorMessageUtil.java | 18 +++++++++--------- .../proxy/controller/ProxyController.java | 11 +++++++---- .../proxy/rpc/core/AbstractServiceAdaptor.java | 11 ++++++----- .../proxy/rpc/core/ProxyServiceRegister.java | 5 +++++ .../proxy/rpc/grpc/InterRequestHandler.java | 5 +++-- .../proxy/rpc/grpc/IntraRequestHandler.java | 5 +++-- .../proxy/rpc/grpc/ProxyRequestHandler.java | 10 +++++++--- .../proxy/rpc/router/BaseServingRouter.java | 13 ++++++++----- .../router/ConfigFileBasedServingRouter.java | 7 +++++-- .../proxy/rpc/router/DefaultServingRouter.java | 11 ++++++----- .../proxy/rpc/router/RouterInterface.java | 10 ---------- .../proxy/rpc/router/ZkServingRouter.java | 9 ++++++--- .../proxy/rpc/services/InferenceService.java | 12 ++++++++---- .../proxy/rpc/services/NotFoundService.java | 8 ++++++-- .../proxy/rpc/services/UnaryCallService.java | 10 +++++++--- .../proxy/security/DefaultAuthentication.java | 12 ++++++------ .../security/FederationParamValidator.java | 8 ++++---- .../security/InferenceParamValidator.java | 8 ++++---- .../proxy/security/OverloadMonitor.java | 8 ++++---- 44 files changed, 144 insertions(+), 108 deletions(-) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/exceptions/BaseException.java (82%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/exceptions/CustomException.java (89%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/exceptions/ErrorCode.java (90%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/exceptions/ErrorResponseEntity.java (86%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/exceptions/InvalidRoleInfoException.java (78%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/exceptions/NoResultException.java (65%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/exceptions/NoRouteInfoException.java (66%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/exceptions/OverLoadException.java (66%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/exceptions/ShowDownRejectException.java (71%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/exceptions/SysException.java (83%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/rpc/core/Context.java (93%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/rpc/core/DefaultInterceptorChain.java (96%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/rpc/core/InboundPackage.java (83%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/rpc/core/Interceptor.java (86%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/rpc/core/InterceptorChain.java (78%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/rpc/core/OutboundPackage.java (88%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/rpc/core/OverLoadLogger.java (98%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/rpc/core/ProxyService.java (87%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/rpc/core/ServiceAdaptor.java (55%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/rpc/core/ServiceRegister.java (73%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/rpc/grpc/GrpcType.java (76%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/rpc/router/RouteType.java (78%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/rpc/router/RouteTypeConvertor.java (95%) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/rpc/router/RouterInfo.java (91%) create mode 100644 fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/router/RouterInterface.java delete mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouterInterface.java diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/BaseException.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/BaseException.java similarity index 82% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/BaseException.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/BaseException.java index 4e02e85e..c400854f 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/BaseException.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/BaseException.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.proxy.exceptions; +package com.webank.ai.fate.serving.core.exceptions; public class BaseException extends Exception { diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/CustomException.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/CustomException.java similarity index 89% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/CustomException.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/CustomException.java index 00624f95..2fb13cd7 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/CustomException.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/CustomException.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.proxy.exceptions; +package com.webank.ai.fate.serving.core.exceptions; public class CustomException extends RuntimeException { diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorCode.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/ErrorCode.java similarity index 90% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorCode.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/ErrorCode.java index 22f24b65..430e5215 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorCode.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/ErrorCode.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.proxy.exceptions; +package com.webank.ai.fate.serving.core.exceptions; public class ErrorCode { diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorResponseEntity.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/ErrorResponseEntity.java similarity index 86% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorResponseEntity.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/ErrorResponseEntity.java index 17098704..fb095f6b 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ErrorResponseEntity.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/ErrorResponseEntity.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.proxy.exceptions; +package com.webank.ai.fate.serving.core.exceptions; public class ErrorResponseEntity { private int code; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/InvalidRoleInfoException.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/InvalidRoleInfoException.java similarity index 78% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/InvalidRoleInfoException.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/InvalidRoleInfoException.java index f1014b9e..da2a7b66 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/InvalidRoleInfoException.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/InvalidRoleInfoException.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.proxy.exceptions; +package com.webank.ai.fate.serving.core.exceptions; /** * @Description TODO diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/NoResultException.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/NoResultException.java similarity index 65% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/NoResultException.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/NoResultException.java index 063ab29b..35c1719d 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/NoResultException.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/NoResultException.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.proxy.exceptions; +package com.webank.ai.fate.serving.core.exceptions; /** * @Description TODO diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/NoRouteInfoException.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/NoRouteInfoException.java similarity index 66% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/NoRouteInfoException.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/NoRouteInfoException.java index 4f381e9c..ae4bf9c7 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/NoRouteInfoException.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/NoRouteInfoException.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.proxy.exceptions; +package com.webank.ai.fate.serving.core.exceptions; /** * @Description TODO diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/OverLoadException.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/OverLoadException.java similarity index 66% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/OverLoadException.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/OverLoadException.java index 430db3bb..a647bd8d 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/OverLoadException.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/OverLoadException.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.proxy.exceptions; +package com.webank.ai.fate.serving.core.exceptions; /** * @Description TODO diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ShowDownRejectException.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/ShowDownRejectException.java similarity index 71% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ShowDownRejectException.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/ShowDownRejectException.java index c6199974..b3324604 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/ShowDownRejectException.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/ShowDownRejectException.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.proxy.exceptions; +package com.webank.ai.fate.serving.core.exceptions; /** * @Description 停机时拒绝请求异常 diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/SysException.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/SysException.java similarity index 83% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/SysException.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/SysException.java index 37d0dae4..3e31d905 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/SysException.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/SysException.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.proxy.exceptions; +package com.webank.ai.fate.serving.core.exceptions; import org.springframework.core.NestedRuntimeException; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/Context.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/Context.java similarity index 93% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/Context.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/Context.java index c9b1b96a..5f8b1d27 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/Context.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/Context.java @@ -1,7 +1,7 @@ -package com.webank.ai.fate.serving.proxy.rpc.core; +package com.webank.ai.fate.serving.core.rpc.core; -import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcType; -import com.webank.ai.fate.serving.proxy.rpc.router.RouterInfo; +import com.webank.ai.fate.serving.core.rpc.grpc.GrpcType; +import com.webank.ai.fate.serving.core.rpc.router.RouterInfo; /** * @Description TODO diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/DefaultInterceptorChain.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/DefaultInterceptorChain.java similarity index 96% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/DefaultInterceptorChain.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/DefaultInterceptorChain.java index 1747235f..0809c789 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/DefaultInterceptorChain.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/DefaultInterceptorChain.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.proxy.rpc.core; +package com.webank.ai.fate.serving.core.rpc.core; import com.google.common.collect.Lists; import org.slf4j.Logger; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/InboundPackage.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/InboundPackage.java similarity index 83% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/InboundPackage.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/InboundPackage.java index 2156e16e..c5526f22 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/InboundPackage.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/InboundPackage.java @@ -1,11 +1,10 @@ -package com.webank.ai.fate.serving.proxy.rpc.core; +package com.webank.ai.fate.serving.core.rpc.core; -import com.webank.ai.fate.serving.proxy.rpc.router.RouterInfo; +import com.webank.ai.fate.serving.core.rpc.router.RouterInfo; import io.grpc.ManagedChannel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.http.HttpServletRequest; import java.util.Map; /** @@ -29,7 +28,7 @@ public void setManagedChannel(ManagedChannel managedChannel) { RouterInfo routerInfo; - public HttpServletRequest getHttpServletRequest() { + /*public HttpServletRequest getHttpServletRequest() { return httpServletRequest; } @@ -37,7 +36,7 @@ public void setHttpServletRequest(HttpServletRequest httpServletRequest) { this.httpServletRequest = httpServletRequest; } - HttpServletRequest httpServletRequest; + HttpServletRequest httpServletRequest;*/ public RouterInfo getRouterInfo() { return routerInfo; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/Interceptor.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/Interceptor.java similarity index 86% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/Interceptor.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/Interceptor.java index fd556a00..b10074c4 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/Interceptor.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/Interceptor.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.proxy.rpc.core; +package com.webank.ai.fate.serving.core.rpc.core; public interface Interceptor { diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/InterceptorChain.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/InterceptorChain.java similarity index 78% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/InterceptorChain.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/InterceptorChain.java index c48f957f..a0de6b06 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/InterceptorChain.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/InterceptorChain.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.proxy.rpc.core; +package com.webank.ai.fate.serving.core.rpc.core; /** * @Description diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OutboundPackage.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/OutboundPackage.java similarity index 88% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OutboundPackage.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/OutboundPackage.java index 07f64208..cc603d67 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OutboundPackage.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/OutboundPackage.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.proxy.rpc.core; +package com.webank.ai.fate.serving.core.rpc.core; /** * @Description TODO diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OverLoadLogger.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/OverLoadLogger.java similarity index 98% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OverLoadLogger.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/OverLoadLogger.java index 5ad5fd7a..fe077ed4 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/OverLoadLogger.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/OverLoadLogger.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.proxy.rpc.core; +package com.webank.ai.fate.serving.core.rpc.core; import org.slf4j.Logger; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyService.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/ProxyService.java similarity index 87% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyService.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/ProxyService.java index 0a53d9f2..ab1ebf07 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyService.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/ProxyService.java @@ -3,7 +3,7 @@ // (powered by Fernflower decompiler) // -package com.webank.ai.fate.serving.proxy.rpc.core; +package com.webank.ai.fate.serving.core.rpc.core; import java.lang.annotation.*; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ServiceAdaptor.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/ServiceAdaptor.java similarity index 55% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ServiceAdaptor.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/ServiceAdaptor.java index 0eb7b986..ac9f6c1f 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ServiceAdaptor.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/ServiceAdaptor.java @@ -1,9 +1,9 @@ -package com.webank.ai.fate.serving.proxy.rpc.core; +package com.webank.ai.fate.serving.core.rpc.core; import java.util.List; public interface ServiceAdaptor { - public OutboundPackage service(Context context,InboundPackage inboundPackage) throws Exception; + public OutboundPackage service(Context context, InboundPackage inboundPackage) throws Exception; public OutboundPackage serviceFail( Context context,InboundPackage data,List e) throws Exception; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ServiceRegister.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/ServiceRegister.java similarity index 73% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ServiceRegister.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/ServiceRegister.java index 34ed838e..08130312 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ServiceRegister.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/ServiceRegister.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.proxy.rpc.core; +package com.webank.ai.fate.serving.core.rpc.core; /** * @Description TODO diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcType.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/grpc/GrpcType.java similarity index 76% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcType.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/grpc/GrpcType.java index f9af71ba..bb59eea0 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcType.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/grpc/GrpcType.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.proxy.rpc.grpc; +package com.webank.ai.fate.serving.core.rpc.grpc; /** * @Description TODO diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteType.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/router/RouteType.java similarity index 78% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteType.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/router/RouteType.java index d6a20fe8..f83ca559 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteType.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/router/RouteType.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.proxy.rpc.router; +package com.webank.ai.fate.serving.core.rpc.router; /** * @Description TODO diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteTypeConvertor.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/router/RouteTypeConvertor.java similarity index 95% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteTypeConvertor.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/router/RouteTypeConvertor.java index 2bd44941..7421a86f 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouteTypeConvertor.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/router/RouteTypeConvertor.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.proxy.rpc.router; +package com.webank.ai.fate.serving.core.rpc.router; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouterInfo.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/router/RouterInfo.java similarity index 91% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouterInfo.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/router/RouterInfo.java index 85cde2ea..d0604e6f 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouterInfo.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/router/RouterInfo.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.proxy.rpc.router; +package com.webank.ai.fate.serving.core.rpc.router; public class RouterInfo { private String host; diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/router/RouterInterface.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/router/RouterInterface.java new file mode 100644 index 00000000..56a1127a --- /dev/null +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/router/RouterInterface.java @@ -0,0 +1,10 @@ +package com.webank.ai.fate.serving.core.rpc.router; + + +import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.core.rpc.core.Interceptor; + +public interface RouterInterface extends Interceptor { + RouterInfo route(Context context, InboundPackage inboundPackage); +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ErrorMessageUtil.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ErrorMessageUtil.java index fbc38167..f9b8e229 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ErrorMessageUtil.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ErrorMessageUtil.java @@ -1,7 +1,7 @@ package com.webank.ai.fate.serving.proxy.common; import com.alibaba.csp.sentinel.slots.block.BlockException; -import com.webank.ai.fate.serving.proxy.exceptions.*; +import com.webank.ai.fate.serving.core.exceptions.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,33 +21,33 @@ public class ErrorMessageUtil { public static Map handleException(Map result,Throwable e){ if (e instanceof IllegalArgumentException) { - result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.PARAM_ERROR); + result.put(CODE, ErrorCode.PARAM_ERROR); result.put(MESSAGE,"PARAM_ERROR"); } else if(e instanceof NoRouteInfoException){ - result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.ROUTER_ERROR); + result.put(CODE, ErrorCode.ROUTER_ERROR); result.put(MESSAGE, "ROUTER_ERROR"); } else if (e instanceof SysException) { - result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.SYSTEM_ERROR); + result.put(CODE, ErrorCode.SYSTEM_ERROR); result.put(MESSAGE, "SYSTEM_ERROR"); } else if (e instanceof BlockException) { - result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.LIMIT_ERROR); + result.put(CODE, ErrorCode.LIMIT_ERROR); result.put(MESSAGE, "OVERLOAD"); } else if (e instanceof InvalidRoleInfoException) { - result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.ROLE_ERROR); + result.put(CODE, ErrorCode.ROLE_ERROR); result.put(MESSAGE, "ROLE_ERROR"); - } else if (e instanceof ShowDownRejectException){ - result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.SHUTDOWN_ERROR); + } else if (e instanceof ShowDownRejectException){ + result.put(CODE, ErrorCode.SHUTDOWN_ERROR); result.put(MESSAGE, "SHUTDOWN_ERROR"); } else if (e instanceof NoResultException) { logger.error("NET_ERROR ",e); - result.put(CODE, com.webank.ai.fate.serving.proxy.exceptions.ErrorCode.NET_ERROR); + result.put(CODE, ErrorCode.NET_ERROR); result.put(MESSAGE, "NET_ERROR"); } else { logger.error("SYSTEM_ERROR ",e); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java index 7e0532db..8d452bc8 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java @@ -2,10 +2,13 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; -import com.webank.ai.fate.serving.metrics.api.ICounter; +import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; +import com.webank.ai.fate.serving.core.rpc.core.ServiceAdaptor; import com.webank.ai.fate.serving.metrics.api.IMetricFactory; import com.webank.ai.fate.serving.proxy.common.Dict; -import com.webank.ai.fate.serving.proxy.rpc.core.*; +import com.webank.ai.fate.serving.proxy.rpc.core.ProxyServiceRegister; import com.webank.ai.fate.serving.proxy.utils.WebUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,7 +74,7 @@ public String call() throws Exception { InboundPackage inboundPackage = buildInboundPackageFederation(context, data, httpServletRequest); - OutboundPackage result = serviceAdaptor.service(context,inboundPackage ); + OutboundPackage result = serviceAdaptor.service(context,inboundPackage ); if(result!=null&&result.getData()!=null) { result.getData().remove("log"); result.getData().remove("warn"); @@ -106,7 +109,7 @@ private InboundPackage buildInboundPackageFederation(Context context , Str InboundPackage inboundPackage = new InboundPackage(); inboundPackage.setBody(body); inboundPackage.setHead(head); - inboundPackage.setHttpServletRequest(httpServletRequest); +// inboundPackage.setHttpServletRequest(httpServletRequest); return inboundPackage; } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java index a7381f19..032178aa 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java @@ -6,9 +6,10 @@ import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; import com.google.common.collect.Lists; +import com.webank.ai.fate.serving.core.exceptions.ErrorCode; +import com.webank.ai.fate.serving.core.exceptions.ShowDownRejectException; +import com.webank.ai.fate.serving.core.rpc.core.*; import com.webank.ai.fate.serving.proxy.common.ErrorMessageUtil; -import com.webank.ai.fate.serving.proxy.exceptions.ErrorCode; -import com.webank.ai.fate.serving.proxy.exceptions.ShowDownRejectException; import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcConnectionPool; import io.grpc.stub.AbstractStub; import org.slf4j.Logger; @@ -116,7 +117,7 @@ public void setServiceName(String serviceName) { * @throws Exception */ @Override - public OutboundPackage service( Context context ,InboundPackage data) throws Exception { + public OutboundPackage service(Context context , InboundPackage data) throws Exception { OutboundPackage outboundPackage= new OutboundPackage(); long begin = System.currentTimeMillis(); @@ -127,7 +128,7 @@ public OutboundPackage service( Context context ,InboundPackage data /** * 系统关闭中 ,直接返回拒绝,关闭线程将等待所有处理完成 */ - return this.serviceFailInner(context,data,new ShowDownRejectException()); + return this.serviceFailInner(context,data,new ShowDownRejectException()); } try { @@ -186,7 +187,7 @@ private OutboundPackage serviceFailInner(Context context, InboundPackage OutboundPackage outboundPackage = new OutboundPackage(); result.put(MESSAGE, e.getMessage()); ErrorMessageUtil.handleException(result,e); - context.setReturnCode(result.get(CODE)!=null?result.get(CODE).toString():ErrorCode.SYSTEM_ERROR.toString()); + context.setReturnCode(result.get(CODE)!=null?result.get(CODE).toString(): ErrorCode.SYSTEM_ERROR.toString()); Entry entry=null; try { StringBuilder sb = new StringBuilder("ERROR_"); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyServiceRegister.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyServiceRegister.java index dd317e87..9851b287 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyServiceRegister.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyServiceRegister.java @@ -1,4 +1,9 @@ package com.webank.ai.fate.serving.proxy.rpc.core; + +import com.webank.ai.fate.serving.core.rpc.core.Interceptor; +import com.webank.ai.fate.serving.core.rpc.core.ProxyService; +import com.webank.ai.fate.serving.core.rpc.core.ServiceAdaptor; +import com.webank.ai.fate.serving.core.rpc.core.ServiceRegister; import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcConnectionPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterRequestHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterRequestHandler.java index 1ab20646..3203fd8e 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterRequestHandler.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterRequestHandler.java @@ -3,8 +3,9 @@ package com.webank.ai.fate.serving.proxy.rpc.grpc; import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.serving.proxy.rpc.core.Context; -import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.core.rpc.grpc.GrpcType; import com.webank.ai.fate.serving.proxy.rpc.core.ProxyServiceRegister; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraRequestHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraRequestHandler.java index 5f0966cb..1beed633 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraRequestHandler.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraRequestHandler.java @@ -3,8 +3,9 @@ package com.webank.ai.fate.serving.proxy.rpc.grpc; import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.serving.proxy.rpc.core.Context; -import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.core.rpc.grpc.GrpcType; import com.webank.ai.fate.serving.proxy.rpc.core.ProxyServiceRegister; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java index f0dbf2e7..d6873fa5 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java @@ -5,9 +5,13 @@ import com.webank.ai.fate.api.networking.proxy.DataTransferServiceGrpc; import com.webank.ai.fate.api.networking.proxy.Proxy; import com.webank.ai.fate.register.annotions.RegisterService; +import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; +import com.webank.ai.fate.serving.core.rpc.core.ServiceAdaptor; import com.webank.ai.fate.serving.metrics.api.IMetricFactory; import com.webank.ai.fate.serving.proxy.common.Dict; -import com.webank.ai.fate.serving.proxy.rpc.core.*; +import com.webank.ai.fate.serving.proxy.rpc.core.ProxyServiceRegister; import io.grpc.stub.StreamObserver; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -23,7 +27,7 @@ public abstract class ProxyRequestHandler extends DataTransferServiceGrpc.DataTr public abstract ProxyServiceRegister getProxyServiceRegister(); - public abstract void setExtraInfo(Context context, InboundPackage inboundPackage, Proxy.Packet req); + public abstract void setExtraInfo(Context context, InboundPackage inboundPackage, Proxy.Packet req); @RegisterService(serviceName = "unaryCall") @Override @@ -39,7 +43,7 @@ public void unaryCall(Proxy.Packet req, StreamObserver responseObs metricFactory.counter("grpc.unaryCall", "unaryCall request", "request", context.getGrpcType().toString()).increment(); - OutboundPackage outboundPackage = null; + OutboundPackage outboundPackage = null; try { outboundPackage = unaryCallService.service(context,inboundPackage); } catch (Exception e) { diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java index 00351336..65a41534 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java @@ -1,10 +1,13 @@ package com.webank.ai.fate.serving.proxy.rpc.router; import com.google.common.hash.Hashing; -import com.webank.ai.fate.serving.proxy.exceptions.NoRouteInfoException; -import com.webank.ai.fate.serving.proxy.rpc.core.Context; -import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; -import com.webank.ai.fate.serving.proxy.rpc.core.OutboundPackage; +import com.webank.ai.fate.serving.core.exceptions.NoRouteInfoException; +import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; +import com.webank.ai.fate.serving.core.rpc.router.RouteType; +import com.webank.ai.fate.serving.core.rpc.router.RouterInfo; +import com.webank.ai.fate.serving.core.rpc.router.RouterInterface; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,7 +15,7 @@ import java.util.concurrent.ThreadLocalRandom; -public abstract class BaseServingRouter implements RouterInterface{ +public abstract class BaseServingRouter implements RouterInterface { private static final Logger logger = LoggerFactory.getLogger(BaseServingRouter.class); public abstract List getRouterInfoList(Context context, InboundPackage inboundPackage); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java index 835bde6e..24ba6c58 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java @@ -8,9 +8,12 @@ import com.google.gson.stream.JsonReader; import com.webank.ai.fate.api.core.BasicMeta; import com.webank.ai.fate.api.networking.proxy.Proxy; +import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.core.rpc.router.RouteType; +import com.webank.ai.fate.serving.core.rpc.router.RouteTypeConvertor; +import com.webank.ai.fate.serving.core.rpc.router.RouterInfo; import com.webank.ai.fate.serving.proxy.common.Dict; -import com.webank.ai.fate.serving.proxy.rpc.core.Context; -import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; import com.webank.ai.fate.serving.proxy.utils.FileUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/DefaultServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/DefaultServingRouter.java index b94a2542..0c68641e 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/DefaultServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/DefaultServingRouter.java @@ -1,10 +1,11 @@ package com.webank.ai.fate.serving.proxy.rpc.router; -import com.webank.ai.fate.serving.proxy.exceptions.NoRouteInfoException; -import com.webank.ai.fate.serving.proxy.rpc.core.Context; -import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; -import com.webank.ai.fate.serving.proxy.rpc.core.Interceptor; -import com.webank.ai.fate.serving.proxy.rpc.core.OutboundPackage; +import com.webank.ai.fate.serving.core.exceptions.NoRouteInfoException; +import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.core.rpc.core.Interceptor; +import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; +import com.webank.ai.fate.serving.core.rpc.router.RouterInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouterInterface.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouterInterface.java deleted file mode 100644 index 3066c381..00000000 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/RouterInterface.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.webank.ai.fate.serving.proxy.rpc.router; - - -import com.webank.ai.fate.serving.proxy.rpc.core.Context; -import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; -import com.webank.ai.fate.serving.proxy.rpc.core.Interceptor; - -public interface RouterInterface extends Interceptor{ - RouterInfo route(Context context, InboundPackage inboundPackage); -} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java index ab91db55..a8d706ec 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java @@ -3,10 +3,13 @@ import com.webank.ai.fate.api.networking.proxy.Proxy; import com.webank.ai.fate.register.router.RouterService; import com.webank.ai.fate.register.url.URL; +import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.core.rpc.grpc.GrpcType; +import com.webank.ai.fate.serving.core.rpc.router.RouteType; +import com.webank.ai.fate.serving.core.rpc.router.RouteTypeConvertor; +import com.webank.ai.fate.serving.core.rpc.router.RouterInfo; import com.webank.ai.fate.serving.proxy.common.Dict; -import com.webank.ai.fate.serving.proxy.rpc.core.Context; -import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; -import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcType; import com.webank.ai.fate.serving.proxy.utils.FederatedModelUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java index a976fef9..376c7555 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java @@ -6,12 +6,16 @@ import com.google.protobuf.ByteString; import com.webank.ai.fate.api.serving.InferenceServiceGrpc; import com.webank.ai.fate.api.serving.InferenceServiceProto; +import com.webank.ai.fate.serving.core.exceptions.NoResultException; +import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; +import com.webank.ai.fate.serving.core.rpc.core.ProxyService; +import com.webank.ai.fate.serving.core.rpc.router.RouterInfo; import com.webank.ai.fate.serving.metrics.api.IMetricFactory; import com.webank.ai.fate.serving.proxy.common.Dict; -import com.webank.ai.fate.serving.proxy.exceptions.NoResultException; -import com.webank.ai.fate.serving.proxy.rpc.core.*; +import com.webank.ai.fate.serving.proxy.rpc.core.AbstractServiceAdaptor; import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcConnectionPool; -import com.webank.ai.fate.serving.proxy.rpc.router.RouterInfo; import io.grpc.ManagedChannel; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -35,7 +39,7 @@ "inferenceParamValidator", "defaultServingRouter"}) -public class InferenceService extends AbstractServiceAdaptor { +public class InferenceService extends AbstractServiceAdaptor { Logger logger = LoggerFactory.getLogger(InferenceService.class); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/NotFoundService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/NotFoundService.java index 93c24ab2..e8d077a6 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/NotFoundService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/NotFoundService.java @@ -2,9 +2,13 @@ import com.alibaba.fastjson.JSON; import com.google.common.collect.Maps; +import com.webank.ai.fate.serving.core.exceptions.ErrorCode; +import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; +import com.webank.ai.fate.serving.core.rpc.core.ProxyService; import com.webank.ai.fate.serving.proxy.common.Dict; -import com.webank.ai.fate.serving.proxy.exceptions.ErrorCode; -import com.webank.ai.fate.serving.proxy.rpc.core.*; +import com.webank.ai.fate.serving.proxy.rpc.core.AbstractServiceAdaptor; import java.util.List; import java.util.Map; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java index 5c134ff6..047afec4 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java @@ -6,12 +6,16 @@ import com.google.protobuf.ByteString; import com.webank.ai.fate.api.networking.proxy.DataTransferServiceGrpc; import com.webank.ai.fate.api.networking.proxy.Proxy; +import com.webank.ai.fate.serving.core.exceptions.ErrorCode; +import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; +import com.webank.ai.fate.serving.core.rpc.core.ProxyService; +import com.webank.ai.fate.serving.core.rpc.router.RouterInfo; import com.webank.ai.fate.serving.metrics.api.IMetricFactory; import com.webank.ai.fate.serving.proxy.common.Dict; -import com.webank.ai.fate.serving.proxy.exceptions.ErrorCode; -import com.webank.ai.fate.serving.proxy.rpc.core.*; +import com.webank.ai.fate.serving.proxy.rpc.core.AbstractServiceAdaptor; import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcConnectionPool; -import com.webank.ai.fate.serving.proxy.rpc.router.RouterInfo; import com.webank.ai.fate.serving.proxy.security.AuthUtils; import io.grpc.ManagedChannel; import org.slf4j.Logger; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/DefaultAuthentication.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/DefaultAuthentication.java index 1e3d562d..8adf131c 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/DefaultAuthentication.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/DefaultAuthentication.java @@ -1,12 +1,12 @@ package com.webank.ai.fate.serving.proxy.security; import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.serving.proxy.exceptions.InvalidRoleInfoException; -import com.webank.ai.fate.serving.proxy.rpc.core.Context; -import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; -import com.webank.ai.fate.serving.proxy.rpc.core.Interceptor; -import com.webank.ai.fate.serving.proxy.rpc.core.OutboundPackage; -import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcType; +import com.webank.ai.fate.serving.core.exceptions.InvalidRoleInfoException; +import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.core.rpc.core.Interceptor; +import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; +import com.webank.ai.fate.serving.core.rpc.grpc.GrpcType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/FederationParamValidator.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/FederationParamValidator.java index 66d6d128..6797d4f2 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/FederationParamValidator.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/FederationParamValidator.java @@ -1,10 +1,10 @@ package com.webank.ai.fate.serving.proxy.security; import com.google.common.base.Preconditions; -import com.webank.ai.fate.serving.proxy.rpc.core.Context; -import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; -import com.webank.ai.fate.serving.proxy.rpc.core.Interceptor; -import com.webank.ai.fate.serving.proxy.rpc.core.OutboundPackage; +import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.core.rpc.core.Interceptor; +import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java index 8d402d49..af0a3dec 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java @@ -1,11 +1,11 @@ package com.webank.ai.fate.serving.proxy.security; import com.google.common.base.Preconditions; +import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.core.rpc.core.Interceptor; +import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; import com.webank.ai.fate.serving.proxy.common.Dict; -import com.webank.ai.fate.serving.proxy.rpc.core.Context; -import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; -import com.webank.ai.fate.serving.proxy.rpc.core.Interceptor; -import com.webank.ai.fate.serving.proxy.rpc.core.OutboundPackage; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/OverloadMonitor.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/OverloadMonitor.java index 33f94ba2..ad116bfd 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/OverloadMonitor.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/OverloadMonitor.java @@ -3,10 +3,10 @@ import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; -import com.webank.ai.fate.serving.proxy.rpc.core.Context; -import com.webank.ai.fate.serving.proxy.rpc.core.InboundPackage; -import com.webank.ai.fate.serving.proxy.rpc.core.Interceptor; -import com.webank.ai.fate.serving.proxy.rpc.core.OutboundPackage; +import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; +import com.webank.ai.fate.serving.core.rpc.core.Interceptor; +import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; From 5b825d8392f0590b5a38d954fb33c2b57e9cd0b1 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Tue, 31 Dec 2019 15:48:11 +0800 Subject: [PATCH 075/190] merge Context Signed-off-by: v_dylanxu <136539068@qq.com> --- .../fate/serving/core/bean/BaseContext.java | 157 ++++++++++++++++-- .../ai/fate/serving/core/bean/Context.java | 54 +++++- .../ai/fate/serving/core/bean/Dict.java | 15 +- .../fate/serving/core/rpc/core/Context.java | 139 ---------------- .../rpc/core/DefaultInterceptorChain.java | 3 +- .../serving/core/rpc/core/Interceptor.java | 4 +- .../serving/core/rpc/core/ServiceAdaptor.java | 2 + .../core/rpc/router/RouterInterface.java | 2 +- .../proxy/controller/ProxyController.java | 5 +- .../rpc/core/AbstractServiceAdaptor.java | 3 +- .../proxy/rpc/grpc/InterRequestHandler.java | 4 +- .../proxy/rpc/grpc/IntraRequestHandler.java | 4 +- .../proxy/rpc/grpc/ProxyRequestHandler.java | 5 +- .../proxy/rpc/router/BaseServingRouter.java | 2 +- .../router/ConfigFileBasedServingRouter.java | 2 +- .../rpc/router/DefaultServingRouter.java | 4 +- .../proxy/rpc/router/ZkServingRouter.java | 2 +- .../proxy/rpc/services/InferenceService.java | 2 +- .../proxy/rpc/services/NotFoundService.java | 2 +- .../proxy/rpc/services/UnaryCallService.java | 2 +- .../proxy/security/DefaultAuthentication.java | 2 +- .../security/FederationParamValidator.java | 2 +- .../security/InferenceParamValidator.java | 4 +- .../proxy/security/OverloadMonitor.java | 4 +- .../webank/ai/fate/serving/ServingServer.java | 5 +- 25 files changed, 242 insertions(+), 188 deletions(-) delete mode 100644 fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/Context.java diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java index 6ac78bd1..6b8a5aca 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java @@ -20,6 +20,8 @@ import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; import com.google.common.collect.Maps; +import com.webank.ai.fate.serving.core.rpc.grpc.GrpcType; +import com.webank.ai.fate.serving.core.rpc.router.RouterInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; @@ -34,16 +36,17 @@ public class BaseContext implements Context { @@ -40,8 +42,6 @@ public interface Context { public default void postProcess(Req req, Resp resp) { } - ; - public ReturnResult getFederatedResult(); public void setFederatedResult(ReturnResult returnResult); @@ -60,5 +60,55 @@ public default void postProcess(Req req, Resp resp) { public long getCostTime(); + /** + * proxy + */ + public GrpcType getGrpcType(); + + public void setGrpcType(GrpcType grpcType); + + public String getVersion(); + + public void setVersion(String version); + + public String getGuestAppId(); + + public void setGuestAppId(String guestAppId); + + public String getHostAppid(); + + public void setHostAppid(String hostAppid); + + public RouterInfo getRouterInfo(); + + public void setRouterInfo(RouterInfo routerInfo); + + public Object getResultData(); + + public void setResultData(Object resultData); + + public String getReturnCode(); + + public void setReturnCode(String returnCode); + + public long getDownstreamCost(); + + public void setDownstreamCost(long downstreamCost); + + public long getDownstreamBegin(); + + public void setDownstreamBegin(long downstreamBegin); + + public long getRouteBasis(); + + public void setRouteBasis(long routeBasis); + + public String getSourceIp(); + + public void setSourceIp(String sourceIp); + + public String getServiceName(); + + public void setServiceName(String serviceName); } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java index fa4c60b2..45178e75 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java @@ -42,8 +42,19 @@ public class Dict { public static final String MODEL_FEDERATED_PARTY = "model_federated_party"; public static final String MODEL_FEDERATED_ROLES = "model_federated_roles"; public static final String VERSION ="version"; - - // configuration property key + public static final String GRPC_TYPE ="grpcType"; + public static final String ROUTER_INFO ="routerInfo"; + public static final String RESULT_DATA ="resultData"; + public static final String RETURN_CODE ="returnCode"; + public static final String DOWN_STREAM_COST ="downstreamCost"; + public static final String DOWN_STREAM_BEGIN ="downstreamBegin"; + public static final String ROUTE_BASIS ="routeBasis"; + public static final String SOURCE_IP ="sourceIp"; + + + /** + * configuration property key + */ public static final String PROPERTY_REMOTE_MODEL_INFERENCE_RESULT_CACHE_TTL = "remoteModelInferenceResultCacheTTL"; public static final String PROPERTY_REMOTE_MODEL_INFERENCE_RESULT_CACHE_MAX_SIZE = "remoteModelInferenceResultCacheMaxSize"; public static final String PROPERTY_INFERENCE_RESULT_CACHE_TTL = "inferenceResultCacheTTL"; diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/Context.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/Context.java deleted file mode 100644 index 5f8b1d27..00000000 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/Context.java +++ /dev/null @@ -1,139 +0,0 @@ -package com.webank.ai.fate.serving.core.rpc.core; - -import com.webank.ai.fate.serving.core.rpc.grpc.GrpcType; -import com.webank.ai.fate.serving.core.rpc.router.RouterInfo; - -/** - * @Description TODO - * @Author - **/ -public class Context { - - public GrpcType getGrpcType() { - return grpcType; - } - - public void setGrpcType(GrpcType grpcType) { - this.grpcType = grpcType; - } - - private GrpcType grpcType; - - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - - private String version; - - private String guestAppId; - - private String hostAppid; - - - public String getGuestAppId() { - return guestAppId; - } - - public void setGuestAppId(String guestAppId) { - this.guestAppId = guestAppId; - } - - public String getHostAppid() { - return hostAppid; - } - - public void setHostAppid(String hostAppid) { - this.hostAppid = hostAppid; - } - - public RouterInfo getRouterInfo() { - return routerInfo; - } - - public void setRouterInfo(RouterInfo routerInfo) { - this.routerInfo = routerInfo; - } - - private RouterInfo routerInfo; - - public String getCaseId() { - return caseId; - } - - public void setCaseId(String caseId) { - this.caseId = caseId; - } - - private String caseId; - - public Object getResultData() { - return resultData; - } - - public void setResultData(Object resultData) { - this.resultData = resultData; - } - - private Object resultData; - - - public String getReturnCode() { - return returnCode; - } - - public void setReturnCode(String returnCode) { - this.returnCode = returnCode; - } - - - private String returnCode; - - public long getDownstreamCost() { - return downstreamCost; - } - - public void setDownstreamCost(long downstreamCost) { - this.downstreamCost = downstreamCost; - } - - private long downstreamCost; - - public long getDownstreamBegin() { - return downstreamBegin; - } - - public void setDownstreamBegin(long downstreamBegin) { - this.downstreamBegin = downstreamBegin; - } - - private long downstreamBegin; - - public long getRouteBasis() { - return routeBasis; - } - public void setRouteBasis(long routeBasis) { - this.routeBasis = routeBasis; - } - private long routeBasis; - - public String getSourceIp() { - return sourceIp; - } - public void setSourceIp(String sourceIp) { - this.sourceIp = sourceIp; - } - private String sourceIp; - - public String getServiceName() { - return serviceName; - } - public void setServiceName(String serviceName) { - this.serviceName = serviceName; - } - String serviceName; - -} diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/DefaultInterceptorChain.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/DefaultInterceptorChain.java index 0809c789..31b915ab 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/DefaultInterceptorChain.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/DefaultInterceptorChain.java @@ -1,6 +1,7 @@ package com.webank.ai.fate.serving.core.rpc.core; import com.google.common.collect.Lists; +import com.webank.ai.fate.serving.core.bean.Context; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,7 +31,7 @@ public void addInterceptor(Interceptor interceptor) { * @throws Exception */ @Override - public void doPreProcess(Context context, InboundPackage inboundPackage ,OutboundPackage outboundPackage) throws Exception { + public void doPreProcess(Context context, InboundPackage inboundPackage , OutboundPackage outboundPackage) throws Exception { for(Interceptor interceptor:chain){ interceptor.doPreProcess(context,inboundPackage,outboundPackage); diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/Interceptor.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/Interceptor.java index b10074c4..59fcc11e 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/Interceptor.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/Interceptor.java @@ -1,8 +1,10 @@ package com.webank.ai.fate.serving.core.rpc.core; +import com.webank.ai.fate.serving.core.bean.Context; + public interface Interceptor { - public void doPreProcess(Context context,InboundPackage inboundPackage,OutboundPackage outboundPackage) throws Exception; + public void doPreProcess(Context context, InboundPackage inboundPackage, OutboundPackage outboundPackage) throws Exception; public void doPostProcess(Context context,InboundPackage inboundPackage,OutboundPackage outboundPackage) throws Exception; diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/ServiceAdaptor.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/ServiceAdaptor.java index ac9f6c1f..a9f02d09 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/ServiceAdaptor.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/ServiceAdaptor.java @@ -1,5 +1,7 @@ package com.webank.ai.fate.serving.core.rpc.core; +import com.webank.ai.fate.serving.core.bean.Context; + import java.util.List; public interface ServiceAdaptor { diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/router/RouterInterface.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/router/RouterInterface.java index 56a1127a..bbb0e727 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/router/RouterInterface.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/router/RouterInterface.java @@ -1,7 +1,7 @@ package com.webank.ai.fate.serving.core.rpc.router; -import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.core.Interceptor; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java index 8d452bc8..333dda9f 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java @@ -2,7 +2,8 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; -import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.bean.BaseContext; +import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; import com.webank.ai.fate.serving.core.rpc.core.ServiceAdaptor; @@ -69,7 +70,7 @@ public String call() throws Exception { final ServiceAdaptor serviceAdaptor = proxyServiceRegister.getServiceAdaptor("inference"); - Context context = new Context(); + Context context = new BaseContext(); context.setVersion(version); InboundPackage inboundPackage = buildInboundPackageFederation(context, data, httpServletRequest); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java index 032178aa..2e987979 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java @@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; import com.google.common.collect.Lists; +import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.exceptions.ErrorCode; import com.webank.ai.fate.serving.core.exceptions.ShowDownRejectException; import com.webank.ai.fate.serving.core.rpc.core.*; @@ -108,7 +109,7 @@ public void setServiceName(String serviceName) { - public abstract resp doService(Context context,InboundPackage data,OutboundPackage outboundPackage) ; + public abstract resp doService(Context context, InboundPackage data, OutboundPackage outboundPackage) ; /** * @param context diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterRequestHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterRequestHandler.java index 3203fd8e..ce169dd7 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterRequestHandler.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/InterRequestHandler.java @@ -3,7 +3,7 @@ package com.webank.ai.fate.serving.proxy.rpc.grpc; import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.grpc.GrpcType; import com.webank.ai.fate.serving.proxy.rpc.core.ProxyServiceRegister; @@ -25,7 +25,7 @@ public ProxyServiceRegister getProxyServiceRegister(){ } @Override - public void setExtraInfo(Context context, InboundPackage inboundPackage, Proxy.Packet req){ + public void setExtraInfo(Context context, InboundPackage inboundPackage, Proxy.Packet req){ context.setGrpcType(GrpcType.INTER_GRPC); } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraRequestHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraRequestHandler.java index 1beed633..61bbc785 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraRequestHandler.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraRequestHandler.java @@ -3,7 +3,7 @@ package com.webank.ai.fate.serving.proxy.rpc.grpc; import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.grpc.GrpcType; import com.webank.ai.fate.serving.proxy.rpc.core.ProxyServiceRegister; @@ -25,7 +25,7 @@ public ProxyServiceRegister getProxyServiceRegister(){ } @Override - public void setExtraInfo(Context context, InboundPackage inboundPackage, Proxy.Packet req){ + public void setExtraInfo(Context context, InboundPackage inboundPackage, Proxy.Packet req){ context.setGrpcType(GrpcType.INTRA_GRPC); } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java index d6873fa5..1ab1f7f9 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java @@ -5,7 +5,8 @@ import com.webank.ai.fate.api.networking.proxy.DataTransferServiceGrpc; import com.webank.ai.fate.api.networking.proxy.Proxy; import com.webank.ai.fate.register.annotions.RegisterService; -import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.bean.BaseContext; +import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; import com.webank.ai.fate.serving.core.rpc.core.ServiceAdaptor; @@ -37,7 +38,7 @@ public void unaryCall(Proxy.Packet req, StreamObserver responseObs logger.info("unaryCall req {}",req); ServiceAdaptor unaryCallService = getProxyServiceRegister().getServiceAdaptor("unaryCall"); - Context context = new Context(); + Context context = new BaseContext(); InboundPackage inboundPackage = buildInboundPackage(context, req); setExtraInfo(context, inboundPackage, req); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java index 65a41534..edf950ef 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java @@ -1,8 +1,8 @@ package com.webank.ai.fate.serving.proxy.rpc.router; import com.google.common.hash.Hashing; +import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.exceptions.NoRouteInfoException; -import com.webank.ai.fate.serving.core.rpc.core.Context; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; import com.webank.ai.fate.serving.core.rpc.router.RouteType; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java index 24ba6c58..9f86435d 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java @@ -8,7 +8,7 @@ import com.google.gson.stream.JsonReader; import com.webank.ai.fate.api.core.BasicMeta; import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.router.RouteType; import com.webank.ai.fate.serving.core.rpc.router.RouteTypeConvertor; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/DefaultServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/DefaultServingRouter.java index 0c68641e..c126e204 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/DefaultServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/DefaultServingRouter.java @@ -1,7 +1,7 @@ package com.webank.ai.fate.serving.proxy.rpc.router; +import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.exceptions.NoRouteInfoException; -import com.webank.ai.fate.serving.core.rpc.core.Context; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.core.Interceptor; import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; @@ -26,7 +26,7 @@ public class DefaultServingRouter implements Interceptor{ private ConfigFileBasedServingRouter configFileBasedServingRouter; @Override - public void doPreProcess(Context context, InboundPackage inboundPackage,OutboundPackage outboundPackage) throws Exception { + public void doPreProcess(Context context, InboundPackage inboundPackage, OutboundPackage outboundPackage) throws Exception { RouterInfo routerInfo =zkServingRouter.route(context, inboundPackage); if(null == routerInfo){ routerInfo = configFileBasedServingRouter.route(context, inboundPackage); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java index a8d706ec..a5bb928f 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java @@ -3,7 +3,7 @@ import com.webank.ai.fate.api.networking.proxy.Proxy; import com.webank.ai.fate.register.router.RouterService; import com.webank.ai.fate.register.url.URL; -import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.grpc.GrpcType; import com.webank.ai.fate.serving.core.rpc.router.RouteType; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java index 376c7555..9cc68bf9 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java @@ -6,8 +6,8 @@ import com.google.protobuf.ByteString; import com.webank.ai.fate.api.serving.InferenceServiceGrpc; import com.webank.ai.fate.api.serving.InferenceServiceProto; +import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.exceptions.NoResultException; -import com.webank.ai.fate.serving.core.rpc.core.Context; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; import com.webank.ai.fate.serving.core.rpc.core.ProxyService; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/NotFoundService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/NotFoundService.java index e8d077a6..22af8189 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/NotFoundService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/NotFoundService.java @@ -2,8 +2,8 @@ import com.alibaba.fastjson.JSON; import com.google.common.collect.Maps; +import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.exceptions.ErrorCode; -import com.webank.ai.fate.serving.core.rpc.core.Context; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; import com.webank.ai.fate.serving.core.rpc.core.ProxyService; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java index 047afec4..965b622c 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java @@ -6,8 +6,8 @@ import com.google.protobuf.ByteString; import com.webank.ai.fate.api.networking.proxy.DataTransferServiceGrpc; import com.webank.ai.fate.api.networking.proxy.Proxy; +import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.exceptions.ErrorCode; -import com.webank.ai.fate.serving.core.rpc.core.Context; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; import com.webank.ai.fate.serving.core.rpc.core.ProxyService; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/DefaultAuthentication.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/DefaultAuthentication.java index 8adf131c..fceeaf87 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/DefaultAuthentication.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/DefaultAuthentication.java @@ -1,8 +1,8 @@ package com.webank.ai.fate.serving.proxy.security; import com.webank.ai.fate.api.networking.proxy.Proxy; +import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.exceptions.InvalidRoleInfoException; -import com.webank.ai.fate.serving.core.rpc.core.Context; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.core.Interceptor; import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/FederationParamValidator.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/FederationParamValidator.java index 6797d4f2..d104c54e 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/FederationParamValidator.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/FederationParamValidator.java @@ -1,7 +1,7 @@ package com.webank.ai.fate.serving.proxy.security; import com.google.common.base.Preconditions; -import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.core.Interceptor; import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java index af0a3dec..7cab5869 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java @@ -1,7 +1,7 @@ package com.webank.ai.fate.serving.proxy.security; import com.google.common.base.Preconditions; -import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.core.Interceptor; import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; @@ -17,7 +17,7 @@ public class InferenceParamValidator implements Interceptor{ @Override - public void doPreProcess(Context context, InboundPackage inboundPackage,OutboundPackage outboundPackage) throws Exception { + public void doPreProcess(Context context, InboundPackage inboundPackage, OutboundPackage outboundPackage) throws Exception { Preconditions.checkArgument(StringUtils.isNotEmpty(context.getCaseId()),"case id is null"); Preconditions.checkArgument(null != inboundPackage.getHead(),Dict.HEAD + " is null"); Preconditions.checkArgument(null != inboundPackage.getBody(),Dict.BODY + " is null"); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/OverloadMonitor.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/OverloadMonitor.java index ad116bfd..c74f4614 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/OverloadMonitor.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/OverloadMonitor.java @@ -3,7 +3,7 @@ import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.slots.block.BlockException; -import com.webank.ai.fate.serving.core.rpc.core.Context; +import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.core.Interceptor; import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; @@ -20,7 +20,7 @@ public class OverloadMonitor implements Interceptor{ Logger logger = LoggerFactory.getLogger(OverloadMonitor.class); @Override - public void doPreProcess(Context context, InboundPackage inboundPackage,OutboundPackage outboundPackage) throws Exception { + public void doPreProcess(Context context, InboundPackage inboundPackage, OutboundPackage outboundPackage) throws Exception { Entry entry = null; try { SphU.entry(context.getServiceName()); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index 46f1591f..59783ab0 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -46,7 +46,10 @@ import java.io.IOException; import java.util.HashMap; import java.util.Set; -import java.util.concurrent.*; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; public class ServingServer implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(ServingServer.class); From a9a53fa2101ec1614863095b54d542316bbf9b11 Mon Sep 17 00:00:00 2001 From: utu Date: Tue, 31 Dec 2019 16:22:08 +0800 Subject: [PATCH 076/190] =?UTF-8?q?proxy=20refactor=EF=BC=9Aadd=20metrics.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fate/serving/proxy/controller/ProxyController.java | 4 ++-- .../serving/proxy/rpc/grpc/ProxyRequestHandler.java | 10 ++++++---- .../serving/proxy/rpc/services/InferenceService.java | 6 +++--- .../serving/proxy/rpc/services/UnaryCallService.java | 6 +++--- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java index 333dda9f..dde20265 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java @@ -61,7 +61,7 @@ public Callable federation(@PathVariable String version, HttpServletRequest httpServletRequest, @RequestHeader HttpHeaders headers ) throws Exception { - metricFactory.counter("http.inference", "inference request", "request", "all").increment(); + metricFactory.counter("http.inference.request", "http inference request").increment(); return new Callable() { @Override @@ -82,7 +82,7 @@ public String call() throws Exception { result.getData().remove("caseid"); } - metricFactory.counter("http.inference", "inference response", "response", "all").increment(); + metricFactory.counter("http.inference.response", "http inference response").increment(); return JSON.toJSONString(result.getData()); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java index 1ab1f7f9..95c8a510 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java @@ -34,7 +34,8 @@ public abstract class ProxyRequestHandler extends DataTransferServiceGrpc.DataTr @Override public void unaryCall(Proxy.Packet req, StreamObserver responseObserver) { - metricFactory.counter("grpc.unaryCall", "unaryCall request", "request", "all").increment(); + metricFactory.counter("grpc.unaryCall.request", "grpc unaryCall request", + "src", req.getHeader().getSrc().getPartyId(), "dst", req.getHeader().getDst().getPartyId()).increment(); logger.info("unaryCall req {}",req); ServiceAdaptor unaryCallService = getProxyServiceRegister().getServiceAdaptor("unaryCall"); @@ -42,7 +43,7 @@ public void unaryCall(Proxy.Packet req, StreamObserver responseObs InboundPackage inboundPackage = buildInboundPackage(context, req); setExtraInfo(context, inboundPackage, req); - metricFactory.counter("grpc.unaryCall", "unaryCall request", "request", context.getGrpcType().toString()).increment(); + metricFactory.counter("grpc.unaryCall", "grpc unaryCall","direction", "request", "grpc.type", context.getGrpcType().toString()).increment(); OutboundPackage outboundPackage = null; try { @@ -55,8 +56,9 @@ public void unaryCall(Proxy.Packet req, StreamObserver responseObs responseObserver.onNext(result); responseObserver.onCompleted(); - metricFactory.counter("grpc.unaryCall", "unaryCall response", "response", context.getGrpcType().toString()).increment(); - metricFactory.counter("grpc.unaryCall", "unaryCall response", "response", "all").increment(); + metricFactory.counter("grpc.unaryCall.response", "grpc unaryCall response", + "src", req.getHeader().getSrc().getPartyId(), "dst", req.getHeader().getDst().getPartyId()).increment(); + metricFactory.counter("grpc.unaryCall", "grpc unaryCall", "direction", "response", "grpc.type", context.getGrpcType().toString()).increment(); } public InboundPackage buildInboundPackage(Context context, Proxy.Packet req){ diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java index 9cc68bf9..b9452116 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java @@ -88,15 +88,15 @@ public Map doService(Context context, InboundPackage data, OutboundPackage< InferenceServiceGrpc.InferenceServiceFutureStub futureStub = InferenceServiceGrpc.newFutureStub(managedChannel); - metricFactory.counter("http.inference", "send to self serving server", "send", "self.serving-server").increment(); + metricFactory.counter("http.inference.service", "in doService", "direction", "to.self.serving-server", "result", "success").increment(); ListenableFuture resultFuture = futureStub.inference(reqBuilder.build()); InferenceServiceProto.InferenceMessage result = resultFuture.get(timeout,TimeUnit.MILLISECONDS); - metricFactory.counter("http.inference", "receive from self serving server", "receive", "self.serving-server", "result", "success").increment(); + metricFactory.counter("http.inference.service", "in doService", "direction", "from.self.serving-server", "result", "success").increment(); logger.info("routerinfo {} send {} result {}",routerInfo,inferenceReqMap,result); resultString = new String(result.getBody().toByteArray()); } catch (Exception e) { - metricFactory.counter("http.inference", "receive from self serving server", "receive", "self.serving-server", "result", "grpc.error").increment(); + metricFactory.counter("http.inference.service", "in doService", "direction", "from.self.serving-server", "result", "grpc.error").increment(); logger.error("get grpc result error", e); throw new NoResultException(); } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java index 965b622c..3fe12286 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java @@ -72,7 +72,7 @@ public Proxy.Packet doService(Context context, InboundPackage data stub1.withDeadlineAfter(timeout, TimeUnit.MILLISECONDS); - metricFactory.counter("grpc.unaryCall", "send out to self serving-server or other proxy", "send", "self serving-server or other proxy").increment(); + metricFactory.counter("grpc.unaryCall.service", "in doService", "direction", "out", "result", "success").increment(); context.setDownstreamBegin(System.currentTimeMillis()); @@ -80,12 +80,12 @@ public Proxy.Packet doService(Context context, InboundPackage data Proxy.Packet packet = future.get(timeout,TimeUnit.MILLISECONDS); - metricFactory.counter("grpc.unaryCall", "receive from self serving-server or other proxy", "receive", "self serving-server or other proxy", "result", "success").increment(); + metricFactory.counter("grpc.unaryCall.service", "in doService", "direction", "in", "result", "success").increment(); return packet; } catch (Exception e) { - metricFactory.counter("grpc.unaryCall", "receive from self serving-server or other proxy", "receive", "self serving-server or other proxy", "result", "grpc.error").increment(); + metricFactory.counter("grpc.unaryCall.service", "in doService", "direction", "in", "result", "error").increment(); e.printStackTrace(); logger.error("unaryCall error ",e); From 4b7dcf62afa67d26d75c7ccb0082f4416c7baa2f Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Tue, 31 Dec 2019 17:59:42 +0800 Subject: [PATCH 077/190] shell auto cluster project --- .../allinone_cluster_configurations.sh | 15 +++ .../cluster-deploy/scripts/package.sh | 110 +++++++----------- .../scripts/zookeeper_install.sh | 42 ------- ...\347\275\262\346\226\207\346\241\243.docx" | Bin 26575 -> 191880 bytes 4 files changed, 60 insertions(+), 107 deletions(-) create mode 100644 serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh delete mode 100644 serving-server/cluster-deploy/scripts/zookeeper_install.sh diff --git a/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh b/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh new file mode 100644 index 00000000..620919d6 --- /dev/null +++ b/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh @@ -0,0 +1,15 @@ +#!/bin/bash +user=app +host_guest=(172.16.153.9 172.16.153.113) +roll_hostAndguest=(172.16.153.9 172.16.153.113) +deploy_dir=/data/projects +host_redis_ip=127.0.0.2 +host_redis_port=63792 +host_redis_password=fate_dev_host +guest_redis_ip=127.0.0.3 +guest_redis_port=63793 +guest_redis_password=fate_dev_guest +apply_zk=true +host_zk_url=zookeeper://localhost:2182 +guest_zk_url=zookeeper://localhost:2183 +workMode=1 diff --git a/serving-server/cluster-deploy/scripts/package.sh b/serving-server/cluster-deploy/scripts/package.sh index 7ae90cb0..f7ae2d0c 100644 --- a/serving-server/cluster-deploy/scripts/package.sh +++ b/serving-server/cluster-deploy/scripts/package.sh @@ -17,30 +17,20 @@ fate_serving_zip=fate-serving-server-*-release.zip fate_serving_jar=fate-serving-server-*.jar router_zip=fate-serving-router-*-release.zip router_jar=fate-serving-router-*.jar -common=$public_path/fate-serving/common router=router router_path=$fate_serving_path/router -if [ ! -d $common ]; then - mkdir -p $common -else - echo [INFO] $common dir exist +if [ ! -d $fate_serving_path ]; then + mkdir -p $fate_serving_path + if [ ! -d $sering_path ]; then + cd $fate_serving_path + mkdir $serving + mkdir $router + else + echo [INFO] $fate_serving_path dir exist + fi fi -cp jdk_install.sh $common -cp redis_install.sh $common cp services.sh $fate_serving_path - -cd $common -echo "[INFO] jdk install" -#sudo sh jdk_install.sh -#sudo rm -rf jdk_install.sh -echo "[INFO] redis install" -sudo sh redis_install.sh -sudo rm -rf redis_install.sh -cd redis/conf -sudo sed -i.bak "s/# requirepass foobared/requirepass ${redis_password}/g" ./redis.conf -sudo sed -i.bak "s/databases 16/databases 50/g" ./redis.conf - cd ${cwd}/../../../ echo "[INFO] mvn clean package start" mvn clean package -DskipTests @@ -48,74 +38,58 @@ if [ $? -eq 0 ]; then echo "[INFO] mvn clean package success" else echo "[INFO] mvn clan package filed" + exit fi -if [ ! -d $fate_serving_path ]; then - cd $public_path - mkdir $fate_serving -else - echo [INFO] $fate_serving_path dir exist -fi - -if [ -d $fate_serving_path ]; then - if [ ! -d $sering_path ]; then - cd $fate_serving_path - mkdir $serving - mkdir $router - else - echo [INFO] $fate_serving_path dir exist - fi - -else - echo "" -fi cd $cwd/../../target cp $fate_serving_zip $sering_path -if [ $? -eq 0 ]; then - echo "[INFO] cp fate-serving-server-*-release.zip success" -else - echo "[INFO] cp fate-serving-server-*-release.zip filed" -fi cd $sering_path unzip $fate_serving_zip ln -s $fate_serving_jar fate-serving-server.jar - -echo "[INFO] cp fate-serving-router-*-release.zip success" cd $cwd/../../../router/target cp $router_zip $router_path -if [ $? -eq 0 ]; then - echo "[INFO] cp fate-serving-router-*-release.zip success" -else - echo "[INFO] cp fate-serving-server-*-release.zip filed" -fi -echo "-------------------routerpach" cd $router_path unzip $router_zip ln -s $router_jar fate-serving-router.jar echo "--------------------ln -s" +cd $sering_path/conf +sed -i 's#redis.ip=127.0.0.1#'redis.ip=${host_redis_ip}'#' serving-server.properties +sed -i 's#redis.port=6379#'redis.port=${host_redis_port}'#' serving-server.properties +sed -i 's#redis.password=fate_dev#'redis.password=${host_redis_password}'#' serving-server.properties +sed -i "/^workMode=/cworkMode=${workMode}" serving-server.properties +echo "------------------------- sed" cd $cwd -cp start_env/services.sh $public_path/fate-serving/common -echo "------------apply----" ${apply_zk} - +echo "-------------------- cwd" $cwd if [ ${apply_zk} = "false" ] then + echo "---------- apply_zk false" cd $router_path/conf sed -i 's#useRegister=true#'useRegister=false'#' proxy.properties sed -i 's#useZkRouter=true#'useZkRouter=false'#' proxy.properties cd $sering_path/conf sed -i 's#useRegister=true#'useRegister=false'#' serving-server.properties sed -i 's#useZkRouter=true#'useZkRouter=false'#' serving-server.properties +elif [ ${apply_zk} = "true" ] +then + echo "---------apply _zk true" + cd $router_path/conf + sleep 6 + echo "----------------" ${host_zk_url} + echo "----------------" ${host_zk_url} + echo "----------------" ${host_zk_url} + echo "----------------" ${host_zk_url} + echo "----------------" ${host_zk_url} + echo "----------------" ${host_zk_url} + echo "----------------" pwd + echo "----------------" $pwd + sed -i "/^zk.url=/czk.url=${host_zk_url}" proxy.properties + cd $sering_path/conf + sed -i 's#zk.url=zookeeper://localhost:2181#'zk.url=${host_zk_url}'#' serving-server.properties +else + echo "" fi - sh zookeeper_install.sh - cd $cwd - echo "-------zkui_instal start" - sh zkui_install.sh - -sudo cp redis/service.sh $common/redis -cp zk/service.sh $common/zookeeper* -cp zkui/service.sh $common/zkui -echo "---------------------sed roll" +echo "------------- zk uil" if [[ ${host_guest[0]} ]] then cd ${public_path}/fate-serving/serving/conf @@ -135,8 +109,8 @@ init_env() { eeooff cd ${public_path} tar -zcvf fate-serving.tar.gz fate-serving/ - - scp ${public_path}/fate-serving.tar.gz @${host_guest[1]}:${public_path} + echo "-----------------scp-:" ${host_guest[1]} + scp ${public_path}/fate-serving.tar.gz ${host_guest[1]}:${public_path} ssh -tt ${user}@${host_guest[1]} << eeooff cd ${public_path} echo "public_path-------" ${public_path} @@ -144,6 +118,12 @@ eeooff rm -rf fate-serving.tar.gz cd ${public_path}/fate-serving/serving/conf sed -i 's#${roll_hostAndguest[0]}#'${roll_hostAndguest[1]}'#' serving-server.properties + sed -i 's#zk.url=${host_zk_url}#'zk.url=${guest_zk_url}'#' serving-server.properties + sed -i 's#redis.ip=${host_redis_ip}#'redis.ip=${guest_redis_ip}'#' serving-server.properties + sed -i 's#redis.port=${host_redis_port}#'redis.port=${guest_redis_port}'#' serving-server.properties + sed -i 's#redis.password=${host_redis_password}#'redis.password=${guest_redis_password}'#' serving-server.properties + cd ${router_path}/conf + sed -i '/^zk.url=/czk.url=${guest_zk_url}' proxy.properties exit eeooff diff --git a/serving-server/cluster-deploy/scripts/zookeeper_install.sh b/serving-server/cluster-deploy/scripts/zookeeper_install.sh deleted file mode 100644 index b58baeb0..00000000 --- a/serving-server/cluster-deploy/scripts/zookeeper_install.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash -set -e -source ./allinone_cluster_configurations.sh -zk_version=zookeeper-3.4.14 -common_path=$deploy_dir/fate-serving/common -if [ ! -d $common_path ]; then - echo "[INFO] mkdir -p $common_path" - mkdir -p $common_path -else - echo [INFO] $fate_serving_path dir exist -fi -cd $common_path - -#zk的压缩包名字 -zk_tar=$zk_version.tar.gz - -url=https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/$zk_version/$zk_version.tar.gz - -#下载zk -wget $url -#解压 -tar -xvf $zk_tar -rm -rf $zk_tar -cd $zk_version -#创建日志文件夹 -mkdir logs -mkdir datar - -zk_path=$(cd `dirname $0`; pwd) -cd conf -echo "sh zkServer.sh start ------" $sh zkServer.sh start -cp zoo_sample.cfg zoo.cfg -sed -i 's#/tmp/zookeepe#'$zk_path'/data#' zoo.cfg -sed -i '/^dataDir/a\dataLogDir='$zk_path'/logs\' zoo.cfg -sed -i '$a\admin.serverPort=9080' zoo.cfg -#cd ../bin -#sh zkServer.sh start -#if [ $? -eq 0 ]; then -# echo "[INFO] zookeeper start success" -#else -# echo "[INFO] zookeeper start filed" -#fi diff --git "a/serving-server/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" "b/serving-server/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" index 22977e48be943a85f48d5b333a772db4511249d7..a8ee794d9cf800413cf8e79a0569c6f6026e5e69 100644 GIT binary patch delta 185259 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 delta 24060 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 ZJ_Ct@u4t3#=t!X%XCQpk=Lhy2_CLG+k<0)9 From c3a24ddb66952eb0a0e232d24298fe03b66dc249 Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Thu, 2 Jan 2020 15:58:33 +0800 Subject: [PATCH 079/190] shell kill bug repair --- router/bin/service.sh | 20 ++++++++++++++------ serving-proxy/bin/service.sh | 18 +++++++++++++----- serving-server/bin/service.sh | 3 +-- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/router/bin/service.sh b/router/bin/service.sh index 52319e07..df673008 100644 --- a/router/bin/service.sh +++ b/router/bin/service.sh @@ -26,13 +26,18 @@ main_class=com.webank.ai.fate.networking.Proxy module_version=1.2.0 getpid() { - pid=`ps aux | grep ${module} | grep -v grep | awk '{print $2}'` + # pid=`ps aux | grep ${module} | grep -v grep | awk '{print $2}'` - if [[ -n ${pid} ]]; then - return 1 - else - return 0 - fi + if [ ! -e "./${module}_pid" ];then + touch ./${module}_pid + echo "" >./${module}_pid + fi + pid=`cat ./${module}_pid` + if [[ -n ${pid} ]]; then + return 1 + else + return 0 + fi } mklogsdir() { @@ -62,6 +67,8 @@ start() { fi java -DauthFile=${configpath}/auth_config.json -Drouter_file=${configpath}/route_table.json -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/proxy.properties >> logs/console.log 2>>logs/error.log & if [[ $? -eq 0 ]]; then + sleep 2 + echo $!>./${module}_pid getpid echo "service start sucessfully. pid: ${pid}" else @@ -79,6 +86,7 @@ stop() { `ps aux | grep ${pid} | grep -v grep`" kill -9 ${pid} if [[ $? -eq 0 ]]; then + rm -rf ./${module}_pid echo "killed" else echo "kill error" diff --git a/serving-proxy/bin/service.sh b/serving-proxy/bin/service.sh index 679d8d78..e311a343 100644 --- a/serving-proxy/bin/service.sh +++ b/serving-proxy/bin/service.sh @@ -27,11 +27,16 @@ getpid() { sleep 1 pid=`ps aux | grep ${module} | grep -v grep | awk '{print $2}'` - if [[ -n ${pid} ]]; then - return 1 - else - return 0 - fi + if [ ! -e "./${module}_pid" ];then + touch ./${module}_pid + echo "" >./${module}_pid + fi + pid=`cat ./${module}_pid` + if [[ -n ${pid} ]]; then + return 1 + else + return 0 + fi } mklogsdir() { @@ -58,6 +63,8 @@ start() { mklogsdir java -Dspring.config.location=${configpath}/application.properties -DconfPath=$configpath -Xmx2048m -Xms2048m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/application.properties >> logs/console.log 2>>logs/error.log & if [[ $? -eq 0 ]]; then + sleep 2 + echo $!>./${module}_pid getpid echo "service start sucessfully. pid: ${pid}" else @@ -75,6 +82,7 @@ stop() { `ps aux | grep ${pid} | grep -v grep`" kill -9 ${pid} if [[ $? -eq 0 ]]; then + rm -rf ./${module}_pid echo "killed" else echo "kill error" diff --git a/serving-server/bin/service.sh b/serving-server/bin/service.sh index e160d7d7..0040ebea 100644 --- a/serving-server/bin/service.sh +++ b/serving-server/bin/service.sh @@ -30,8 +30,7 @@ getpid() { touch ./${module}_pid echo "" >./${module}_pid fi - module_pid=`cat ./${module}_pid` - pid=`ps aux | grep ${module_pid} | grep -v grep | grep -v $0 | awk '{print $2}'` + pid=`cat ./${module}_pid` if [[ -n ${pid} ]]; then return 1 else From fccf43436c1567c3d5281bfd10230c3e6bd1bbf3 Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Fri, 3 Jan 2020 12:02:37 +0800 Subject: [PATCH 080/190] shell ip problem --- .../allinone_cluster_configurations.sh | 6 +++--- ...\347\275\262\346\226\207\346\241\243.docx" | Bin 191663 -> 28251 bytes 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh b/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh index b2908587..9b6fec9d 100644 --- a/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh +++ b/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh @@ -1,10 +1,10 @@ #!/bin/bash user=app -host_guest=(172.16.153.9 172.16.153.113) -roll_hostAndguest=(172.16.153.9 172.16.153.113) +host_guest=(192.168.0.1 192.168.0.2) +roll_hostAndguest=(192.168.0.1 192.168.0.2) deploy_dir=/data/projects host_redis_ip=127.0.0.1 -host_redis_port=63792 +host_redis_port=6379 host_redis_password=fate_dev guest_redis_ip=127.0.0.1 guest_redis_port=6379 diff --git "a/serving-server/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" "b/serving-server/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" index c0b50d49dc9a7e6232f24a6f91adc9cc03ebf824..d0781f60b4dbdb9f0207b9f43587e180ab2fc854 100644 GIT binary patch delta 19036 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�H#KtbVPgIJXMGf9r*f*h5%x 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%0AEQ?HvBVkHPB`n8Bh*@84SIe&0xbLYC; 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 Date: Fri, 3 Jan 2020 15:16:40 +0800 Subject: [PATCH 081/190] shell kill pid bug repair --- serving-server/bin/service.sh | 4 ++-- serving-server/cluster-deploy/scripts/package.sh | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/serving-server/bin/service.sh b/serving-server/bin/service.sh index 0040ebea..4a7eca55 100644 --- a/serving-server/bin/service.sh +++ b/serving-server/bin/service.sh @@ -48,7 +48,7 @@ status() { getpid if [[ -n ${pid} ]]; then echo "status: - `ps aux | grep ${pid} | grep -v grep`" + `ps -p ${pid} `" exit 1 else echo "service not running" @@ -81,7 +81,7 @@ stop() { getpid if [[ -n ${pid} ]]; then echo "killing: - `ps aux | grep ${pid} | grep -v grep`" + `ps -p ${pid}`" kill -9 ${pid} if [[ $? -eq 0 ]]; then rm -rf ./${module}_pid diff --git a/serving-server/cluster-deploy/scripts/package.sh b/serving-server/cluster-deploy/scripts/package.sh index 7c0c7d19..42e373ba 100644 --- a/serving-server/cluster-deploy/scripts/package.sh +++ b/serving-server/cluster-deploy/scripts/package.sh @@ -1,7 +1,4 @@ -#!/bean package -DskipTestsin/bash -# -#Copyright 2019 The serving Authors. All Rights Reserved. -# +#!/bin/bash set -e source ./allinone_cluster_configurations.sh From 92874268a1f25ee81fa69eb18b2ee505728cc78a Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Fri, 3 Jan 2020 17:24:07 +0800 Subject: [PATCH 082/190] shell dram common --- bin/common.sh | 84 +++++++++++++++++++++++++++++++++ serving-proxy/bin/service.sh | 85 ++++----------------------------- serving-proxy/package.xml | 7 +++ serving-server/bin/service.sh | 89 ++++------------------------------- serving-server/package.xml | 7 +++ 5 files changed, 118 insertions(+), 154 deletions(-) create mode 100644 bin/common.sh diff --git a/bin/common.sh b/bin/common.sh new file mode 100644 index 00000000..2b008014 --- /dev/null +++ b/bin/common.sh @@ -0,0 +1,84 @@ +#!/bin/bash +set -e +getpid() { + # pid=`ps aux | grep ${main_class} | grep -v grep | awk '{print $2}'` + module=$1 + if [ ! -e "./${module}_pid" ];then + touch ./${module}_pid + echo "" >./${module}_pid + fi + pid=`cat ./${module}_pid` + if [[ -n ${pid} ]]; then + break 1 + else + break 0 + fi +} + +mklogsdir() { + if [[ ! -d "logs" ]]; then + mkdir logs + fi +} + +start() { + getpid ${module} + if [[ $? -eq 0 ]]; then + mklogsdir + if [[ ! -e "fate-${module}.jar" ]]; then + ln -s fate-${module}-${module_version}.jar fate-${module}.jar + fi + if [ ${module} = "serving-server" ] + then + java -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/${module}.properties >> logs/console.log 2>>logs/error.log & + elif [ ${module} = "serving-proxy" ] + then + java -Dspring.config.location=${configpath}/application.properties -DconfPath=$configpath -Xmx2048m -Xms2048m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/application.properties >> logs/console.log 2>>logs/error.log & + + else + echo "" + fi + if [[ $? -eq 0 ]]; then + sleep 2 + echo $!>./${module}_pid + getpid $module + echo "service start sucessfully. pid: ${pid}" + else + echo "service start failed" + fi + else + echo "service already started. pid: ${pid}" + fi +} + +status() { + getpid $1 + if [[ -n ${pid} ]]; then + echo "status: + `ps -p ${pid} `" + exit 1 + else + echo "service not running" + exit 0 + fi +} + + +stop() { + getpid $1 + if [[ -n ${pid} ]]; then + echo "killing: + `ps -p ${pid}`" + echo "--------------" ${pid} + kill -9 ${pid} + if [[ $? -eq 0 ]]; then + rm -rf ./${module}_pid + echo "killed" + else + echo "kill error" + fi + else + echo "service not running" + fi +} + diff --git a/serving-proxy/bin/service.sh b/serving-proxy/bin/service.sh index e311a343..53ff6db5 100644 --- a/serving-proxy/bin/service.sh +++ b/serving-proxy/bin/service.sh @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +set -e +source ./common.sh basepath=$(cd `dirname $0`;pwd) export JAVA_HOME=/data/projects/common/jdk/jdk1.8.0_192 export PATH=$PATH:$JAVA_HOME/bin @@ -22,93 +24,26 @@ configpath=$(cd $basepath/conf;pwd) module=serving-proxy main_class=com.webank.ai.fate.serving.proxy.bootstrap.Bootstrap +module_version=1.2.0 -getpid() { - sleep 1 - pid=`ps aux | grep ${module} | grep -v grep | awk '{print $2}'` - - if [ ! -e "./${module}_pid" ];then - touch ./${module}_pid - echo "" >./${module}_pid - fi - pid=`cat ./${module}_pid` - if [[ -n ${pid} ]]; then - return 1 - else - return 0 - fi -} - -mklogsdir() { - if [[ ! -d "logs" ]]; then - mkdir logs - fi -} - -status() { - getpid - if [[ -n ${pid} ]]; then - echo "status: - `ps aux | grep ${pid} | grep -v grep`" - exit 1 - else - echo "service not running" - exit 0 - fi -} - -start() { - getpid - if [[ $? -eq 0 ]]; then - mklogsdir - java -Dspring.config.location=${configpath}/application.properties -DconfPath=$configpath -Xmx2048m -Xms2048m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/application.properties >> logs/console.log 2>>logs/error.log & - if [[ $? -eq 0 ]]; then - sleep 2 - echo $!>./${module}_pid - getpid - echo "service start sucessfully. pid: ${pid}" - else - echo "service start failed" - fi - else - echo "service already started. pid: ${pid}" - fi -} - -stop() { - getpid - if [[ -n ${pid} ]]; then - echo "killing: - `ps aux | grep ${pid} | grep -v grep`" - kill -9 ${pid} - if [[ $? -eq 0 ]]; then - rm -rf ./${module}_pid - echo "killed" - else - echo "kill error" - fi - else - echo "service not running" - fi -} case "$1" in start) - start - status + start $module + status $module ;; stop) - stop + stop $module ;; status) - status + status $module ;; restart) - stop - start - status + stop $module + start $module + status $module ;; *) echo "usage: $0 {start|stop|status|restart}" diff --git a/serving-proxy/package.xml b/serving-proxy/package.xml index 80864c18..e8075ce7 100644 --- a/serving-proxy/package.xml +++ b/serving-proxy/package.xml @@ -35,6 +35,13 @@ * + + / + ../bin + + * + + diff --git a/serving-server/bin/service.sh b/serving-server/bin/service.sh index 4a7eca55..d6391a26 100644 --- a/serving-server/bin/service.sh +++ b/serving-server/bin/service.sh @@ -18,101 +18,32 @@ #export JAVA_HOME=/data/projects/common/jdk/jdk1.8.0_192 #export PATH=$PATH:$JAVA_HOME/bin - +set -e +source ./common.sh module=serving-server main_class=com.webank.ai.fate.serving.ServingServer module_version=1.2.0 -getpid() { - # pid=`ps aux | grep ${main_class} | grep -v grep | awk '{print $2}'` - - if [ ! -e "./${module}_pid" ];then - touch ./${module}_pid - echo "" >./${module}_pid - fi - pid=`cat ./${module}_pid` - if [[ -n ${pid} ]]; then - return 1 - else - return 0 - fi -} - -mklogsdir() { - if [[ ! -d "logs" ]]; then - mkdir logs - fi -} - -status() { - getpid - if [[ -n ${pid} ]]; then - echo "status: - `ps -p ${pid} `" - exit 1 - else - echo "service not running" - exit 0 - fi -} - -start() { - getpid - if [[ $? -eq 0 ]]; then - mklogsdir - if [[ ! -e "fate-${module}.jar" ]]; then - ln -s fate-${module}-${module_version}.jar fate-${module}.jar - fi - java -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/${module}.properties >> logs/console.log 2>>logs/error.log & - if [[ $? -eq 0 ]]; then - sleep 2 - echo $!>./${module}_pid - getpid - echo "service start sucessfully. pid: ${pid}" - else - echo "service start failed" - fi - else - echo "service already started. pid: ${pid}" - fi -} - -stop() { - getpid - if [[ -n ${pid} ]]; then - echo "killing: - `ps -p ${pid}`" - kill -9 ${pid} - if [[ $? -eq 0 ]]; then - rm -rf ./${module}_pid - echo "killed" - else - echo "kill error" - fi - else - echo "service not running" - fi -} case "$1" in start) - start - status + start $module + status $module ;; stop) - stop + stop $module ;; status) - status + status $module ;; restart) - stop - start - status + stop $module + start $module + status $module ;; *) echo "usage: $0 {start|stop|status|restart}" exit -1 -esac \ No newline at end of file +esac diff --git a/serving-server/package.xml b/serving-server/package.xml index 606afad8..e66aa7f4 100644 --- a/serving-server/package.xml +++ b/serving-server/package.xml @@ -37,6 +37,13 @@ * + + / + ../bin + + * + + From 85a909adf676e3f8ab3d5692712e04464848c0df Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Fri, 3 Jan 2020 18:35:52 +0800 Subject: [PATCH 083/190] ok Signed-off-by: v_dylanxu <136539068@qq.com> --- .../java/com/webank/ai/fate/serving/manger/ModelUtils.java | 4 +++- serving-server/src/main/resources/serving-server.properties | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java index d75b71fd..745518d7 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java @@ -65,7 +65,9 @@ public static Map readModel(String name, String namespace) { url = urls.get(0); requestUrl = url.toFullString(); - } else { + } + + if (StringUtils.isBlank(requestUrl)) { requestUrl = Configuration.getProperty(Dict.MODEL_TRANSFER_URL); } diff --git a/serving-server/src/main/resources/serving-server.properties b/serving-server/src/main/resources/serving-server.properties index 3cd4b5db..3597b168 100644 --- a/serving-server/src/main/resources/serving-server.properties +++ b/serving-server/src/main/resources/serving-server.properties @@ -54,7 +54,7 @@ InferencePreProcessingAdapter=PassPreProcessing proxy=127.0.0.1:9370 roll=127.0.0.1:8011 # model transfer -model.transfer.url= +model.transfer.url=http://127.0.0.1:9380/v1/model/transfer # zk router zk.url=zookeeper://localhost:2181?backup=localhost:2182,localhost:2183 useRegister=false From 88712f18fc7eea2bbf9b1fe778aa73dba7be34b2 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Fri, 3 Jan 2020 19:24:29 +0800 Subject: [PATCH 084/190] ok Signed-off-by: v_dylanxu <136539068@qq.com> --- .../java/com/webank/ai/fate/serving/manger/ModelUtils.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java index 745518d7..856bbf51 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java @@ -60,11 +60,10 @@ public static Map readModel(String name, String namespace) { List urls = modelUtils.routerService.router(url); if (urls == null || urls.isEmpty()) { logger.info("url not found, {}", url); - return null; + } else { + url = urls.get(0); + requestUrl = url.toFullString(); } - - url = urls.get(0); - requestUrl = url.toFullString(); } if (StringUtils.isBlank(requestUrl)) { From e3daf7f14ae0dffcf4d79a2122e49479985e88c6 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Fri, 3 Jan 2020 20:34:29 +0800 Subject: [PATCH 085/190] add slf4j-log4j-impl Signed-off-by: v_dylanxu <136539068@qq.com> --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index b8c01b5d..57bc6f19 100644 --- a/pom.xml +++ b/pom.xml @@ -101,6 +101,11 @@ log4j-core ${log4j.version} + + org.apache.logging.log4j + log4j-slf4j-impl + ${log4j.version} + com.lmax From 24126a08b6bf4fee452b7b30ea2b346c2d84301e Mon Sep 17 00:00:00 2001 From: utu Date: Mon, 6 Jan 2020 17:48:32 +0800 Subject: [PATCH 086/190] =?UTF-8?q?proxy=20refactor=EF=BC=9Aadd=20http=20a?= =?UTF-8?q?pi=20doc=20to=20README.md.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/README.md b/README.md index cf6f63c0..5919e5a4 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,64 @@ Please use FATE-Flow Client which in the fate-flow to operate, refer to **Online ### Inference +#### Inference using HTTP +##### inference + +**URL** +- ` http://ip:port/federation/v1/inference ` +- where ip:port is the address of guest serving proxy + +**request type** +- POST +- content-application/json + +**request parameters** + +|name|allow null|type|desc| +|:---- |:---|:----- |----- | +|head |no |json object | | +|body |no |json object | the elements and features used by the model| + + - **head object** + +|name|allow null|type|desc| +|:---- |:---|:----- |----- | +|serviceId |yes |string | the serviceId used when bind model in fate-flow| + + + **example ** + +``` + { + "head": { + "serviceId": "111111111" + }, + "body": { + "device_id": "aaaaa", + "phone_num": "122222222" + } + } + +``` + + **response** + +|name|type|desc| +|:----- |:-----|----- | +|retcode |int |0: success, otherwise: error. | +|retmsg |string |error message | +|data | json object | the inference result of model| +|flag |int |the reserved field | + + **a simple example of inference** +```shell +(venv) [***]$ curl -X POST -H 'Content-Type: application/json' -d ' {"head":{"serviceId":"654321"},"body":{"device_id":"123456","phone_num":"1234567899"}}' 'http://localhost:8086/federation/v1/inference' +{"flag":0,"data":{"prob":0.30684422824464636,"guestInputDataHitRate:{}":0.0,"guestModelWeightHitRate:{}":0.0,"retcode":0},"retmsg":"success","retcode":0} +(venv) [***]$ +``` + + +#### Inference using grpc Serving currently supports three inference-related interfaces, using the grpc protocol. - inference: Initiate an inference request and get the result From ff53498335f49f6f302b55ae32790cc151b8ceb9 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Mon, 6 Jan 2020 19:10:13 +0800 Subject: [PATCH 087/190] add zk acl switch, when zk register destroy set default acl Signed-off-by: v_dylanxu <136539068@qq.com> --- .../ai/fate/serving/core/bean/Dict.java | 1 + .../common/CuratorZookeeperClient.java | 81 +++++++++++++------ .../register/zookeeper/ZookeeperClient.java | 2 + .../com/webank/ai/fate/networking/Proxy.java | 5 +- serving-proxy/pom.xml | 8 ++ .../proxy/rpc/grpc/IntraGrpcServer.java | 14 +++- .../src/main/resources/application.properties | 7 +- .../webank/ai/fate/serving/ServingServer.java | 10 +-- .../main/resources/serving-server.properties | 3 +- 9 files changed, 90 insertions(+), 41 deletions(-) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java index 45178e75..6be6ba1b 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java @@ -145,6 +145,7 @@ public class Dict { public static final String HOST_APP_ID = "hostAppId"; public static final String SERVICE_ID = "serviceId"; public static final String CONFIGPATH = "configpath"; + public static final String ACL_ENABLE = "acl.enable"; public static final String ACL_USERNAME = "acl.username"; public static final String ACL_PASSWORD = "acl.password"; public static final String AUTH_FILE = "authFile"; diff --git a/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java b/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java index faf4326e..f9197dc3 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java @@ -60,12 +60,11 @@ public class CuratorZookeeperClient extends AbstractZookeeperClient treeCacheMap = new ConcurrentHashMap<>(); - private static final List ACLS = new ArrayList<>(); - private static final String SCHEME = "digest"; - private static final String ACL_USERNAME = System.getProperty("acl.username"); - private static final String ACL_PASSWORD = System.getProperty("acl.password"); - + private boolean aclEnable; + private String aclUsername; + private String aclPassword; + private List acls = new ArrayList<>(); public CuratorZookeeperClient(URL url) { super(url); @@ -76,12 +75,25 @@ public CuratorZookeeperClient(URL url) { .retryPolicy(new RetryNTimes(1, 1000)) .connectionTimeoutMs(timeout); - if (StringUtils.isNotEmpty(ACL_USERNAME) && StringUtils.isNotEmpty(ACL_PASSWORD) ) { - builder.authorization(SCHEME, (ACL_USERNAME + ":" + ACL_PASSWORD).getBytes()); + try { + aclEnable = Boolean.parseBoolean(System.getProperty("acl.enable", "false")); + } catch (Exception e) { + aclEnable = false; + } + + if (aclEnable) { + aclUsername = System.getProperty("acl.username", ""); + aclPassword = System.getProperty("acl.password", ""); - Id allow = new Id(SCHEME, DigestAuthenticationProvider.generateDigest(ACL_USERNAME + ":" + ACL_PASSWORD)); - // add more - ACLS.add(new ACL(ZooDefs.Perms.ALL, allow)); + if (StringUtils.isBlank(aclUsername) || StringUtils.isBlank(aclPassword)) { + aclEnable = false; + } else { + builder.authorization(SCHEME, (aclUsername + ":" + aclPassword).getBytes()); + + Id allow = new Id(SCHEME, DigestAuthenticationProvider.generateDigest(aclUsername + ":" + aclPassword)); + // add more + acls.add(new ACL(ZooDefs.Perms.ALL, allow)); + } } client = builder.build(); @@ -102,7 +114,9 @@ public void stateChanged(CuratorFramework client, ConnectionState state) { }); client.start(); - client.setACL().withACL(ACLS).forPath("/"); + if (aclEnable) { + client.setACL().withACL(acls).forPath("/"); + } } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); } @@ -113,8 +127,8 @@ public void createPersistent(String path) { try { logger.info("createPersistent {}", path); - if (ACLS.size() > 0) { - client.create().withACL(ACLS).forPath(path); + if (aclEnable) { + client.create().withACL(acls).forPath(path); } else { client.create().forPath(path); } @@ -128,8 +142,8 @@ public void createPersistent(String path) { public void createEphemeral(String path) { try { logger.info("createEphemeral {}", path); - if (ACLS.size() > 0) { - client.create().withMode(CreateMode.EPHEMERAL).withACL(ACLS).forPath(path); + if (aclEnable) { + client.create().withMode(CreateMode.EPHEMERAL).withACL(acls).forPath(path); } else { client.create().withMode(CreateMode.EPHEMERAL).forPath(path); } @@ -144,14 +158,14 @@ protected void createPersistent(String path, String data) { byte[] dataBytes = data.getBytes(CHARSET); try { logger.info("createPersistent {} data {}", path, data); - if (ACLS.size() > 0) { - client.create().withACL(ACLS).forPath(path, dataBytes); + if (aclEnable) { + client.create().withACL(acls).forPath(path, dataBytes); } else { client.create().forPath(path, dataBytes); } } catch (NodeExistsException e) { try { - if (ACLS.size() > 0) { + if (aclEnable) { Stat stat = client.checkExists().forPath(path); client.setData().withVersion(stat.getAversion()).forPath(path, dataBytes); } else { @@ -170,14 +184,14 @@ protected void createEphemeral(String path, String data) { byte[] dataBytes = data.getBytes(CHARSET); try { logger.info("createEphemeral {} data {}", path, data); - if (ACLS.size() > 0) { - client.create().withMode(CreateMode.EPHEMERAL).withACL(ACLS).forPath(path, dataBytes); + if (aclEnable) { + client.create().withMode(CreateMode.EPHEMERAL).withACL(acls).forPath(path, dataBytes); } else { client.create().withMode(CreateMode.EPHEMERAL).forPath(path, dataBytes); } } catch (NodeExistsException e) { try { - if (ACLS.size() > 0) { + if (aclEnable) { Stat stat = client.checkExists().forPath(path); client.setData().withVersion(stat.getAversion()).forPath(path, dataBytes); } else { @@ -194,12 +208,12 @@ protected void createEphemeral(String path, String data) { @Override public void delete(String path) { try { - if (ACLS.size() > 0) { - Stat stat = client.checkExists().forPath(path); - client.delete().withVersion(stat.getAversion()).forPath(path); - } else { - client.delete().forPath(path); + if (aclEnable) { +// Stat stat = client.checkExists().forPath(path); +// client.delete().withVersion(stat.getAversion()).forPath(path); + this.clearAcl(path); } + client.delete().forPath(path); } catch (NoNodeException e) { } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); @@ -248,6 +262,9 @@ public String doGetContent(String path) { @Override public void doClose() { + if (aclEnable) { + this.clearAcl("/"); + } client.close(); } @@ -309,6 +326,18 @@ public void removeTargetChildListener(String path, CuratorWatcherImpl listener) listener.unwatch(); } + @Override + public void clearAcl(String path) { + if (aclEnable) { + logger.info("clear acl {}", path); + try { + client.setACL().withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE).forPath(path); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + /** * just for unit test * diff --git a/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperClient.java b/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperClient.java index 4325260c..6682a848 100644 --- a/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperClient.java +++ b/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperClient.java @@ -59,4 +59,6 @@ public interface ZookeeperClient { String getContent(String path); + void clearAcl(String path); + } diff --git a/router/src/main/java/com/webank/ai/fate/networking/Proxy.java b/router/src/main/java/com/webank/ai/fate/networking/Proxy.java index 5db533bd..cd85e4a1 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/Proxy.java +++ b/router/src/main/java/com/webank/ai/fate/networking/Proxy.java @@ -76,8 +76,9 @@ public static void main(String[] args) throws Exception { server.start(); Properties properties = serverConf.getProperties(); - System.setProperty(Dict.ACL_USERNAME, properties.getProperty(Dict.ACL_USERNAME)); - System.setProperty(Dict.ACL_PASSWORD, properties.getProperty(Dict.ACL_PASSWORD)); + System.setProperty(Dict.ACL_ENABLE, properties.getProperty(Dict.ACL_ENABLE, "")); + System.setProperty(Dict.ACL_USERNAME, properties.getProperty(Dict.ACL_USERNAME, "")); + System.setProperty(Dict.ACL_PASSWORD, properties.getProperty(Dict.ACL_PASSWORD, "")); useRegister = Boolean.valueOf(properties.getProperty("useRegister", "false")); useZkRouter = Boolean.valueOf(properties.getProperty("useZkRouter", "false")); diff --git a/serving-proxy/pom.xml b/serving-proxy/pom.xml index 1637df61..178f36a9 100644 --- a/serving-proxy/pom.xml +++ b/serving-proxy/pom.xml @@ -246,6 +246,14 @@ src/main/resources BOOT-INF/classes/ + + src/main/resources + + **/*.properties + **/*.json + + false + diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java index e0c3f0a4..e6efada8 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java @@ -1,4 +1,5 @@ package com.webank.ai.fate.serving.proxy.rpc.grpc; + import com.webank.ai.fate.register.provider.FateServer; import com.webank.ai.fate.register.provider.FateServerBuilder; import com.webank.ai.fate.register.router.DefaultRouterService; @@ -17,6 +18,7 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; +import java.util.Optional; import java.util.concurrent.Executor; /** @@ -35,10 +37,13 @@ public class IntraGrpcServer implements InitializingBean { @Value("${useZkRouter:false}") private String useZkRouter; - @Value("${acl.username:fate}") + @Value("${acl.enable}") + private String aclEnable; + + @Value("${acl.username}") private String aclUsername; - @Value("${acl.password:fate}") + @Value("${acl.password}") private String aclPassword; ZookeeperRegistry zookeeperRegistry; @@ -64,8 +69,9 @@ public void afterPropertiesSet() throws Exception { if("true".equals(useZkRouter)&& StringUtils.isNotEmpty(zkUrl)) { - System.setProperty("acl.username", aclUsername); - System.setProperty("acl.password", aclPassword); + System.setProperty("acl.enable", Optional.ofNullable(aclEnable).orElse("")); + System.setProperty("acl.username", Optional.ofNullable(aclUsername).orElse("")); + System.setProperty("acl.password", Optional.ofNullable(aclPassword).orElse("")); zookeeperRegistry = ZookeeperRegistry.getRegistery(zkUrl, Dict.SELF_PROJECT_NAME, Dict.SELF_ENVIRONMENT, Integer.valueOf(port)); zookeeperRegistry.register(FateServer.serviceSets); diff --git a/serving-proxy/src/main/resources/application.properties b/serving-proxy/src/main/resources/application.properties index 5ca19f36..dcb2c4d2 100644 --- a/serving-proxy/src/main/resources/application.properties +++ b/serving-proxy/src/main/resources/application.properties @@ -15,8 +15,11 @@ auth.file=/data/projects/serving-proxy/conf/auth_config.json useZkRouter=false zk.url=zookeeper://localhost:2181 -acl.username=fate -acl.password=fate + +# zk acl +acl.enable=false +acl.username= +acl.password= # intra-partyid port proxy.grpc.intra.port=8867 diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index 59783ab0..79204c87 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -65,12 +65,10 @@ public ServingServer(String confPath) { this.confPath = new File(confPath).getAbsolutePath(); System.setProperty(Dict.CONFIGPATH, confPath); new Configuration(confPath).load(); - if(Configuration.getProperty(Dict.ACL_USERNAME)!=null) { - System.setProperty(Dict.ACL_USERNAME, Configuration.getProperty(Dict.ACL_USERNAME)); - } - if(Configuration.getProperty(Dict.ACL_PASSWORD)!=null) { - System.setProperty(Dict.ACL_PASSWORD, Configuration.getProperty(Dict.ACL_PASSWORD)); - } + + System.setProperty(Dict.ACL_ENABLE, Configuration.getProperty(Dict.ACL_ENABLE, "")); + System.setProperty(Dict.ACL_USERNAME, Configuration.getProperty(Dict.ACL_USERNAME, "")); + System.setProperty(Dict.ACL_PASSWORD, Configuration.getProperty(Dict.ACL_PASSWORD, "")); } public static void main(String[] args) { diff --git a/serving-server/src/main/resources/serving-server.properties b/serving-server/src/main/resources/serving-server.properties index 3597b168..3702003b 100644 --- a/serving-server/src/main/resources/serving-server.properties +++ b/serving-server/src/main/resources/serving-server.properties @@ -59,6 +59,7 @@ model.transfer.url=http://127.0.0.1:9380/v1/model/transfer zk.url=zookeeper://localhost:2181?backup=localhost:2182,localhost:2183 useRegister=false useZkRouter=false -#zk acl +# zk acl +acl.enable=false acl.username= acl.password= \ No newline at end of file From 74163457b51561661d1bcf591c248e3c2eca4450 Mon Sep 17 00:00:00 2001 From: utu Date: Tue, 7 Jan 2020 13:05:18 +0800 Subject: [PATCH 088/190] =?UTF-8?q?proxy=20refactor=EF=BC=9Aadd=20async=20?= =?UTF-8?q?http=20api.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/common.sh | 2 +- .../fate/serving/core/bean/BaseContext.java | 8 +++++ .../ai/fate/serving/core/bean/Context.java | 3 ++ .../ai/fate/serving/core/bean/Dict.java | 1 + .../exceptions/UnSurpportMethodException.java | 13 +++++++ .../ai/fate/serving/proxy/common/Dict.java | 3 ++ .../proxy/controller/ProxyController.java | 22 ++++++------ .../router/ConfigFileBasedServingRouter.java | 2 +- .../proxy/rpc/router/ZkServingRouter.java | 2 +- .../proxy/rpc/services/InferenceService.java | 36 ++++++++++++++----- .../security/InferenceParamValidator.java | 2 +- .../src/main/resources/application.properties | 1 + 12 files changed, 73 insertions(+), 22 deletions(-) create mode 100644 fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/UnSurpportMethodException.java diff --git a/bin/common.sh b/bin/common.sh index 2b008014..af4ac73e 100644 --- a/bin/common.sh +++ b/bin/common.sh @@ -55,7 +55,7 @@ status() { getpid $1 if [[ -n ${pid} ]]; then echo "status: - `ps -p ${pid} `" + `ps -f -p ${pid} `" exit 1 else echo "service not running" diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java index 6b8a5aca..fa02488a 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java @@ -287,5 +287,13 @@ public void setServiceName(String serviceName) { dataMap.put(Dict.SERVICE_NAME, serviceName); } + @Override + public String getCallName() { + return (String) dataMap.get(Dict.CALL_NAME); + } + @Override + public void setCallName(String callName) { + dataMap.put(Dict.CALL_NAME, callName); + } } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Context.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Context.java index 6c32ace5..1a880259 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Context.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Context.java @@ -111,4 +111,7 @@ public default void postProcess(Req req, Resp resp) { public void setServiceName(String serviceName); + public void setCallName(String callName); + + public String getCallName(); } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java index 45178e75..5ab15984 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java @@ -132,6 +132,7 @@ public class Dict { public static final String COMPONENT_NAME = "componentName"; public static final String TREE_COMPUTE_ROUND = "treeComputeRound"; public static final String SERVICE_NAME = "serviceName"; + public static final String CALL_NAME = "callName"; public static final String TREE_LOCATION = "treeLocation"; public static final String UNARYCALL = "unaryCall"; diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/UnSurpportMethodException.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/UnSurpportMethodException.java new file mode 100644 index 00000000..f3f0cb75 --- /dev/null +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/UnSurpportMethodException.java @@ -0,0 +1,13 @@ +package com.webank.ai.fate.serving.core.exceptions; + +/** + * @Description TODO + * @Author + **/ +public class UnSurpportMethodException extends RuntimeException { + + public UnSurpportMethodException(){ + + super("UnSurpportMethod"); + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/Dict.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/Dict.java index b48f2fec..78bdad4e 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/Dict.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/Dict.java @@ -20,4 +20,7 @@ public class Dict { public static final String SELF_ENVIRONMENT = "online"; public static final String HEAD = "head"; public static final String BODY = "body"; + public static final String SERVICENAME_INFERENCE = "inference"; + public static final String SERVICENAME_START_INFERENCE_JOB = "startInferenceJob"; + public static final String SERVICENAME_GET_INFERENCE_RESULT = "getInferenceResult"; } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java index dde20265..84661ce6 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java @@ -54,23 +54,25 @@ String binaryReader(HttpServletRequest request) throws IOException { } - @RequestMapping(value = "/federation/{version}/inference", method = {RequestMethod.POST, RequestMethod.GET}) + @RequestMapping(value = "/federation/{version}/{callName}", method = {RequestMethod.POST, RequestMethod.GET}) @ResponseBody public Callable federation(@PathVariable String version, + @PathVariable String callName, @RequestBody String data, HttpServletRequest httpServletRequest, @RequestHeader HttpHeaders headers ) throws Exception { - metricFactory.counter("http.inference.request", "http inference request").increment(); + metricFactory.counter("http.inference.request", "http inference request","callName", callName).increment(); return new Callable() { @Override public String call() throws Exception { logger.info("receive : {} headers {}", data, headers.toSingleValueMap()); - final ServiceAdaptor serviceAdaptor = proxyServiceRegister.getServiceAdaptor("inference"); + final ServiceAdaptor serviceAdaptor = proxyServiceRegister.getServiceAdaptor(Dict.SERVICENAME_INFERENCE); Context context = new BaseContext(); + context.setCallName(callName); context.setVersion(version); InboundPackage inboundPackage = buildInboundPackageFederation(context, data, httpServletRequest); @@ -82,7 +84,7 @@ public String call() throws Exception { result.getData().remove("caseid"); } - metricFactory.counter("http.inference.response", "http inference response").increment(); + metricFactory.counter("http.inference.response", "http inference response","callName", callName).increment(); return JSON.toJSONString(result.getData()); @@ -96,21 +98,21 @@ private InboundPackage buildInboundPackageFederation(Context context , Str HttpServletRequest httpServletRequest) { String sourceIp = WebUtil.getIpAddr(httpServletRequest); context.setSourceIp(sourceIp); - context.setCaseId(UUID.randomUUID().toString()); context.setGuestAppId(selfCoordinator); JSONObject jsonObject =JSON.parseObject(data); - Map head = JSON.parseObject(jsonObject.getString(Dict.HEAD), Map.class); - Map body = JSON.parseObject(jsonObject.getString(Dict.BODY), Map.class); + Map head = JSON.parseObject(jsonObject.getString(Dict.HEAD)!=null?jsonObject.getString(Dict.HEAD):"{}", Map.class); + Map body = JSON.parseObject(jsonObject.getString(Dict.BODY)!=null?jsonObject.getString(Dict.BODY):"{}", Map.class); - if(null != head){ - context.setHostAppid((String) head.get(Dict.APP_ID)); + context.setHostAppid( head.get(Dict.APP_ID)!= null?head.getOrDefault(Dict.APP_ID,"").toString():""); + context.setCaseId( head.get(Dict.CASE_ID)!= null?head.getOrDefault(Dict.CASE_ID,"").toString():""); + if(null == context.getCaseId() || context.getCaseId().isEmpty()){ + context.setCaseId(UUID.randomUUID().toString()); } InboundPackage inboundPackage = new InboundPackage(); inboundPackage.setBody(body); inboundPackage.setHead(head); -// inboundPackage.setHttpServletRequest(httpServletRequest); return inboundPackage; } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java index 9f86435d..b0768aaa 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java @@ -71,7 +71,7 @@ public RouteType getRouteType(){ public List getRouterInfoList(Context context, InboundPackage inboundPackage){ Proxy.Topic dstTopic; Proxy.Topic srcTopic; - if("inference".equals(context.getServiceName())) { + if(Dict.SERVICENAME_INFERENCE.equals(context.getServiceName())) { Proxy.Topic.Builder topicBuilder = Proxy.Topic.newBuilder(); dstTopic = topicBuilder.setPartyId(selfCoordinator). setRole(inferenceServiceName) diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java index a5bb928f..2d320aca 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java @@ -74,7 +74,7 @@ public List getRouterInfoList(Context context, InboundPackage inboun // TODO utu: sucks! have to reconstruct the entire protocol of online serving private String getEnvironment(Context context, InboundPackage inboundPackage){ - if("inference".equals(context.getServiceName())){ + if(Dict.SERVICENAME_INFERENCE.equals(context.getServiceName())){ // guest, proxy -> serving return (String)inboundPackage.getHead().get(Dict.SERVICE_ID); } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java index b9452116..73a52a93 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java @@ -8,6 +8,7 @@ import com.webank.ai.fate.api.serving.InferenceServiceProto; import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.exceptions.NoResultException; +import com.webank.ai.fate.serving.core.exceptions.UnSurpportMethodException; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; import com.webank.ai.fate.serving.core.rpc.core.ProxyService; @@ -34,7 +35,7 @@ @Service // TODO utu: may load from cfg file is a better choice compare to using annotation? -@ProxyService(name = "inference",preChain = { +@ProxyService(name = Dict.SERVICENAME_INFERENCE, preChain = { "overloadMonitor", "inferenceParamValidator", "defaultServingRouter"}) @@ -54,6 +55,9 @@ public InferenceService() {} @Value("${proxy.grpc.inference.timeout:3000}") private int timeout; + @Value("${proxy.grpc.inference.async.timeout:3000}") + private int asyncTimeout; + @Override public Map doService(Context context, InboundPackage data, OutboundPackage outboundPackage) { @@ -63,6 +67,7 @@ public Map doService(Context context, InboundPackage data, OutboundPackage< ManagedChannel managedChannel = null; String resultString=null; + String callName = context.getCallName(); try { try { logger.info("try to get grpc connection"); @@ -87,16 +92,31 @@ public Map doService(Context context, InboundPackage data, OutboundPackage< reqBuilder.setBody(ByteString.copyFrom(JSON.toJSONString(inferenceReqMap).getBytes())); InferenceServiceGrpc.InferenceServiceFutureStub futureStub = InferenceServiceGrpc.newFutureStub(managedChannel); - - metricFactory.counter("http.inference.service", "in doService", "direction", "to.self.serving-server", "result", "success").increment(); - - ListenableFuture resultFuture = futureStub.inference(reqBuilder.build()); - InferenceServiceProto.InferenceMessage result = resultFuture.get(timeout,TimeUnit.MILLISECONDS); - metricFactory.counter("http.inference.service", "in doService", "direction", "from.self.serving-server", "result", "success").increment(); + ListenableFuture resultFuture; + int timeWait = timeout; + + metricFactory.counter("http.inference.service", "in doService", "callName", callName, "direction", "to.self.serving-server", "result", "success").increment(); + + if(callName.equals(Dict.SERVICENAME_INFERENCE)) { + resultFuture = futureStub.inference(reqBuilder.build()); + timeWait = timeout; + } else if(callName.equals(Dict.SERVICENAME_GET_INFERENCE_RESULT)){ + resultFuture = futureStub.getInferenceResult(reqBuilder.build()); + timeWait = asyncTimeout; + }else if(callName.equals(Dict.SERVICENAME_START_INFERENCE_JOB)){ + resultFuture = futureStub.startInferenceJob(reqBuilder.build()); + timeWait = asyncTimeout; + }else{ + logger.error("unknown callName {}.", callName); + throw new UnSurpportMethodException(); + } + + InferenceServiceProto.InferenceMessage result = resultFuture.get(timeWait,TimeUnit.MILLISECONDS); + metricFactory.counter("http.inference.service", "in doService", "callName", callName, "direction", "from.self.serving-server", "result", "success").increment(); logger.info("routerinfo {} send {} result {}",routerInfo,inferenceReqMap,result); resultString = new String(result.getBody().toByteArray()); } catch (Exception e) { - metricFactory.counter("http.inference.service", "in doService", "direction", "from.self.serving-server", "result", "grpc.error").increment(); + metricFactory.counter("http.inference.service", "in doService", "callName", callName, "direction", "from.self.serving-server", "result", "grpc.error").increment(); logger.error("get grpc result error", e); throw new NoResultException(); } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java index 7cab5869..bf132829 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java @@ -20,7 +20,7 @@ public class InferenceParamValidator implements Interceptor{ public void doPreProcess(Context context, InboundPackage inboundPackage, OutboundPackage outboundPackage) throws Exception { Preconditions.checkArgument(StringUtils.isNotEmpty(context.getCaseId()),"case id is null"); Preconditions.checkArgument(null != inboundPackage.getHead(),Dict.HEAD + " is null"); - Preconditions.checkArgument(null != inboundPackage.getBody(),Dict.BODY + " is null"); + //Preconditions.checkArgument(null != inboundPackage.getBody(),Dict.BODY + " is null"); //Preconditions.checkArgument(StringUtils.isNotEmpty((String) inboundPackage.getHead().get(Dict.SERVICE_ID)), Dict.SERVICE_ID + " is null"); } diff --git a/serving-proxy/src/main/resources/application.properties b/serving-proxy/src/main/resources/application.properties index 5ca19f36..246ac223 100644 --- a/serving-proxy/src/main/resources/application.properties +++ b/serving-proxy/src/main/resources/application.properties @@ -25,6 +25,7 @@ proxy.grpc.intra.port=8867 proxy.grpc.inter.port=8869 proxy.grpc.inference.timeout=3000 +proxy.grpc.inference.async.timeout=1000 proxy.grpc.unaryCall.timeout=3000 inference.service.name=serving From 4b405911316480708863b888cfeadd0c683bf9cf Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Wed, 8 Jan 2020 18:01:45 +0800 Subject: [PATCH 089/190] change kill signal Signed-off-by: v_dylanxu <136539068@qq.com> --- bin/common.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/common.sh b/bin/common.sh index af4ac73e..d0c9d02d 100644 --- a/bin/common.sh +++ b/bin/common.sh @@ -70,7 +70,7 @@ stop() { echo "killing: `ps -p ${pid}`" echo "--------------" ${pid} - kill -9 ${pid} + kill ${pid} if [[ $? -eq 0 ]]; then rm -rf ./${module}_pid echo "killed" From fd5a304865c52693aa66cef2ad8d1e40a54e00d5 Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Wed, 8 Jan 2020 20:39:21 +0800 Subject: [PATCH 090/190] shell deploy serving --- .../src/main/resources/route_table.json | 8 -- .../allinone_cluster_configurations.sh | 1 + .../cluster-deploy/scripts/package.sh | 73 +++++++++++------- .../cluster-deploy/scripts/services.sh | 4 +- ...\347\275\262\346\226\207\346\241\243.docx" | Bin 28251 -> 28776 bytes 5 files changed, 46 insertions(+), 40 deletions(-) diff --git a/serving-proxy/src/main/resources/route_table.json b/serving-proxy/src/main/resources/route_table.json index 06a6f7b4..cbc6d104 100644 --- a/serving-proxy/src/main/resources/route_table.json +++ b/serving-proxy/src/main/resources/route_table.json @@ -15,14 +15,6 @@ "port": 8889 } ] - }, - "9999": { - "default": [ - { - "ip": "127.0.0.1", - "port": 8890 - } - ] } }, "permission": { diff --git a/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh b/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh index 9b6fec9d..7ac37319 100644 --- a/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh +++ b/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh @@ -3,6 +3,7 @@ user=app host_guest=(192.168.0.1 192.168.0.2) roll_hostAndguest=(192.168.0.1 192.168.0.2) deploy_dir=/data/projects +party_list=(10000 9999) host_redis_ip=127.0.0.1 host_redis_port=6379 host_redis_password=fate_dev diff --git a/serving-server/cluster-deploy/scripts/package.sh b/serving-server/cluster-deploy/scripts/package.sh index 42e373ba..02550e24 100644 --- a/serving-server/cluster-deploy/scripts/package.sh +++ b/serving-server/cluster-deploy/scripts/package.sh @@ -1,4 +1,7 @@ -#!/bin/bash +#!/bean package -DskipTestsin/bash +# +#Copyright 2019 The serving Authors. All Rights Reserved. +# set -e source ./allinone_cluster_configurations.sh @@ -12,17 +15,17 @@ fate_serving=fate-serving serving=serving fate_serving_zip=fate-serving-server-*-release.zip fate_serving_jar=fate-serving-server-*.jar -router_zip=fate-serving-router-*-release.zip -router_jar=fate-serving-router-*.jar -router=router -router_path=$fate_serving_path/router - +fate_serving_proxy_zip=fate-serving-proxy-*-release.zip +fate_serving_proxy_jar=fate-serving-proxy-*.jar +serving_proxy=serving-proxy +serving_proxy_path=$fate_serving_path/serving-proxy +echo "--------------------start" if [ ! -d $fate_serving_path ]; then mkdir -p $fate_serving_path if [ ! -d $sering_path ]; then cd $fate_serving_path mkdir $serving - mkdir $router + mkdir $serving_proxy else echo [INFO] $fate_serving_path dir exist fi @@ -44,37 +47,44 @@ cp $fate_serving_zip $sering_path cd $sering_path unzip $fate_serving_zip ln -s $fate_serving_jar fate-serving-server.jar -cd $cwd/../../../router/target -cp $router_zip $router_path -cd $router_path -unzip $router_zip -ln -s $router_jar fate-serving-router.jar -echo "--------------------ln -s" +cd $cwd/../../../$serving_proxy/target +cp $fate_serving_proxy_zip $serving_proxy_path +cd $serving_proxy_path +unzip $fate_serving_proxy_zip +ln -s $fate_serving_proxy_jar fate-serving-proxy.jar cd $sering_path/conf sed -i 's#redis.ip=127.0.0.1#'redis.ip=${host_redis_ip}'#' serving-server.properties sed -i 's#redis.port=6379#'redis.port=${host_redis_port}'#' serving-server.properties sed -i 's#redis.password=fate_dev#'redis.password=${host_redis_password}'#' serving-server.properties sed -i "/^workMode=/cworkMode=${workMode}" serving-server.properties -echo "------------------------- sed" cd $cwd -echo "-------------------- cwd" $cwd if [ ${apply_zk} = "false" ] then echo "---------- apply_zk false" - cd $router_path/conf - sed -i 's#useRegister=true#'useRegister=false'#' proxy.properties - sed -i 's#useZkRouter=true#'useZkRouter=false'#' proxy.properties + cd $serving_proxy_path/conf + sed -i 's#useRegister=true#'useRegister=false'#' application.properties + sed -i 's#useZkRouter=true#'useZkRouter=false'#' application.properties + sed -i "/^route.table=/croute.table=${deploy_dir}/fate-serving/serving-proxy/conf/route_table.json" application.properties + sed -i "/^auth.file=/cauth.file=${deploy_dir}/fate-serving/serving-proxy/conf/auth_config.json" application.properties + sed -i "6c \"ip\":\"${host_guest[1]}\"," route_table.json + sed -i 's#10000#'${party_list[0]}'#' route_table.json + sed -i '7c "prot":8000' route_table.json + sed -i "17a ,\"serving\":[{\"ip\":\"${host_guest[0]}\",\"prot\":8080}]" route_table.json cd $sering_path/conf sed -i 's#useRegister=true#'useRegister=false'#' serving-server.properties sed -i 's#useZkRouter=true#'useZkRouter=false'#' serving-server.properties elif [ ${apply_zk} = "true" ] then echo "---------apply _zk true" - cd $router_path/conf - sed -i "/^zk.url=/czk.url=${host_zk_url}" proxy.properties + cd $serving_proxy_path/conf + echo "----------------" $pwd + sed -i "/^zk.url=/czk.url=${host_zk_url}" application.properties + sed -i "/^route.table=/croute.table=${deploy_dir}/fate-serving/serving-proxy/conf/route_table.json" application.properties + sed -i "/^auth.file=/cauth.file=${deploy_dir}/fate-serving/serving-proxy/conf/auth_config.json" application.properties + sed -i 's#10000#'${party_list[0]}'#' route_table.json cd $sering_path/conf sed -i 's#zk.url=zookeeper://localhost:2181#'zk.url=${host_zk_url}'#' serving-server.properties -else +else echo "" fi echo "------------- zk uil" @@ -97,7 +107,6 @@ init_env() { eeooff cd ${public_path} tar -zcvf fate-serving.tar.gz fate-serving/ - echo "-----------------scp-:" ${host_guest[1]} scp ${public_path}/fate-serving.tar.gz ${host_guest[1]}:${public_path} ssh -tt ${user}@${host_guest[1]} << eeooff cd ${public_path} @@ -110,14 +119,22 @@ eeooff sed -i 's#redis.ip=${host_redis_ip}#'redis.ip=${guest_redis_ip}'#' serving-server.properties sed -i 's#redis.port=${host_redis_port}#'redis.port=${guest_redis_port}'#' serving-server.properties sed -i 's#redis.password=${host_redis_password}#'redis.password=${guest_redis_password}'#' serving-server.properties - cd ${router_path}/conf - sed -i '/^zk.url=/czk.url=${guest_zk_url}' proxy.properties + cd ${serving_proxy_path}/conf + sed -i '/^zk.url=/czk.url=${guest_zk_url}' application.properties + sed -i '6c "ip":"${host_guest[0]}",' route_table.json + sed -i 's#${party_list[0]}#'${party_list[1]}'#' route_table.json + sed -i '7c "prot":8000' route_table.json + grep serving route_table.json >/dev/null + if [ $? -eq 0 ] + then + sed -i '18d' route_table.json + sed -i "18i ,\"serving\":[{\"ip\":\"${host_guest[1]}\",\"prot\":18080}]" route_table.json + fi exit eeooff } -echo "host_guest1--if-----" ${host_guest[1]} if [[ ${host_guest[1]} ]] then echo "------guest hava host_guest1 -----is not null" @@ -130,8 +147,4 @@ then else echo "no have guest--------false no null" -fi - - - - +fi \ No newline at end of file diff --git a/serving-server/cluster-deploy/scripts/services.sh b/serving-server/cluster-deploy/scripts/services.sh index df4b0c55..0c721773 100644 --- a/serving-server/cluster-deploy/scripts/services.sh +++ b/serving-server/cluster-deploy/scripts/services.sh @@ -16,7 +16,7 @@ # limitations under the License. # -modules=(router serving) +modules=(serving-proxy serving) cwd=`pwd` @@ -70,4 +70,4 @@ esac cd ${cwd} echo "==================" -echo "process finished" +echo "process finished" \ No newline at end of file diff --git "a/serving-server/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" "b/serving-server/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" index d0781f60b4dbdb9f0207b9f43587e180ab2fc854..3008e3138cbf593a6961bc3284605a1d7d60089a 100644 GIT binary patch delta 18012 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 W;UAL%a5n)8lR9uH2JC160000*HkD@p From 22677446c68ef99b366deddacdf92aff7c5e6ef3 Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Thu, 9 Jan 2020 11:55:01 +0800 Subject: [PATCH 091/190] prot modify --- serving-server/cluster-deploy/scripts/package.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serving-server/cluster-deploy/scripts/package.sh b/serving-server/cluster-deploy/scripts/package.sh index 02550e24..dd664a73 100644 --- a/serving-server/cluster-deploy/scripts/package.sh +++ b/serving-server/cluster-deploy/scripts/package.sh @@ -128,7 +128,7 @@ eeooff if [ $? -eq 0 ] then sed -i '18d' route_table.json - sed -i "18i ,\"serving\":[{\"ip\":\"${host_guest[1]}\",\"prot\":18080}]" route_table.json + sed -i "18i ,\"serving\":[{\"ip\":\"${host_guest[1]}\",\"prot\":8080}]" route_table.json fi exit eeooff From 755ffeca1bfc36047645824bc364fa21e6e18c7b Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Thu, 9 Jan 2020 14:29:12 +0800 Subject: [PATCH 092/190] shell zk switch --- .../cluster-deploy/scripts/package.sh | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/serving-server/cluster-deploy/scripts/package.sh b/serving-server/cluster-deploy/scripts/package.sh index dd664a73..ff504716 100644 --- a/serving-server/cluster-deploy/scripts/package.sh +++ b/serving-server/cluster-deploy/scripts/package.sh @@ -60,16 +60,16 @@ sed -i "/^workMode=/cworkMode=${workMode}" serving-server.properties cd $cwd if [ ${apply_zk} = "false" ] then - echo "---------- apply_zk false" + cd $serving_proxy_path/conf - sed -i 's#useRegister=true#'useRegister=false'#' application.properties - sed -i 's#useZkRouter=true#'useZkRouter=false'#' application.properties + sed -i "/^useRegister=/cuseRegister=false" application.properties + sed -i "/^useZkRouter=/cuseZkRouter=false" application.properties sed -i "/^route.table=/croute.table=${deploy_dir}/fate-serving/serving-proxy/conf/route_table.json" application.properties sed -i "/^auth.file=/cauth.file=${deploy_dir}/fate-serving/serving-proxy/conf/auth_config.json" application.properties sed -i "6c \"ip\":\"${host_guest[1]}\"," route_table.json sed -i 's#10000#'${party_list[0]}'#' route_table.json - sed -i '7c "prot":8000' route_table.json - sed -i "17a ,\"serving\":[{\"ip\":\"${host_guest[0]}\",\"prot\":8080}]" route_table.json + sed -i '7c "prot":8000' route_table.json + sed -i "17a ,\"serving\":[{\"ip\":\"${host_guest[0]}\",\"prot\":8080}]" route_table.json cd $sering_path/conf sed -i 's#useRegister=true#'useRegister=false'#' serving-server.properties sed -i 's#useZkRouter=true#'useZkRouter=false'#' serving-server.properties @@ -79,11 +79,15 @@ then cd $serving_proxy_path/conf echo "----------------" $pwd sed -i "/^zk.url=/czk.url=${host_zk_url}" application.properties + sed -i "/^useRegister=/cuseRegister=true" application.properties + sed -i "/^useZkRouter=/cuseZkRouter=true" application.properties sed -i "/^route.table=/croute.table=${deploy_dir}/fate-serving/serving-proxy/conf/route_table.json" application.properties - sed -i "/^auth.file=/cauth.file=${deploy_dir}/fate-serving/serving-proxy/conf/auth_config.json" application.properties + sed -i "/^auth.file=/cauth.file=${deploy_dir}/fate-serving/serving-proxy/conf/auth_config.json" application.properties sed -i 's#10000#'${party_list[0]}'#' route_table.json cd $sering_path/conf sed -i 's#zk.url=zookeeper://localhost:2181#'zk.url=${host_zk_url}'#' serving-server.properties + sed -i "/^useRegister=/cuseRegister=true" serving-server.properties + sed -i "/^useZkRouter=/cuseZkRouter=true" serving-server.properties else echo "" fi @@ -147,4 +151,4 @@ then else echo "no have guest--------false no null" -fi \ No newline at end of file +fi From 90c6c30e8ed44825d20bea0c1d838bd1d4eb7c79 Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Thu, 9 Jan 2020 15:54:23 +0800 Subject: [PATCH 093/190] shell add fate_flow address --- .../allinone_cluster_configurations.sh | 3 ++- .../cluster-deploy/scripts/package.sh | 16 ++++------------ ...\347\275\262\346\226\207\346\241\243.docx" | Bin 28776 -> 29196 bytes 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh b/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh index 7ac37319..558a7432 100644 --- a/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh +++ b/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh @@ -1,7 +1,6 @@ #!/bin/bash user=app host_guest=(192.168.0.1 192.168.0.2) -roll_hostAndguest=(192.168.0.1 192.168.0.2) deploy_dir=/data/projects party_list=(10000 9999) host_redis_ip=127.0.0.1 @@ -14,3 +13,5 @@ apply_zk=true host_zk_url=zookeeper://localhost:2181 guest_zk_url=zookeeper://localhost:2181 workMode=1 +host_model_transfer=http://127.0.0.1:9380 +guest_model_transfer=http://127.0.0.1:9380 diff --git a/serving-server/cluster-deploy/scripts/package.sh b/serving-server/cluster-deploy/scripts/package.sh index ff504716..95aae9b7 100644 --- a/serving-server/cluster-deploy/scripts/package.sh +++ b/serving-server/cluster-deploy/scripts/package.sh @@ -57,10 +57,10 @@ sed -i 's#redis.ip=127.0.0.1#'redis.ip=${host_redis_ip}'#' serving-server.proper sed -i 's#redis.port=6379#'redis.port=${host_redis_port}'#' serving-server.properties sed -i 's#redis.password=fate_dev#'redis.password=${host_redis_password}'#' serving-server.properties sed -i "/^workMode=/cworkMode=${workMode}" serving-server.properties +sed -i "/^model.transfer.url=/cmodel.transfer.url=${host_model_transfer}/v1/model/transfer" serving-server.properties cd $cwd if [ ${apply_zk} = "false" ] then - cd $serving_proxy_path/conf sed -i "/^useRegister=/cuseRegister=false" application.properties sed -i "/^useZkRouter=/cuseZkRouter=false" application.properties @@ -91,15 +91,7 @@ then else echo "" fi -echo "------------- zk uil" -if [[ ${host_guest[0]} ]] -then - cd ${public_path}/fate-serving/serving/conf - sed -i 's#127.0.0.1:8011#'${roll_hostAndguest[0]}'#' serving-server.properties -else - echo "no have host--------false no null" - exit -fi + init_env() { ssh -tt ${user}@${host_guest[1]} << eeooff @@ -118,11 +110,11 @@ eeooff tar -zxvf fate-serving.tar.gz rm -rf fate-serving.tar.gz cd ${public_path}/fate-serving/serving/conf - sed -i 's#${roll_hostAndguest[0]}#'${roll_hostAndguest[1]}'#' serving-server.properties sed -i 's#zk.url=${host_zk_url}#'zk.url=${guest_zk_url}'#' serving-server.properties sed -i 's#redis.ip=${host_redis_ip}#'redis.ip=${guest_redis_ip}'#' serving-server.properties sed -i 's#redis.port=${host_redis_port}#'redis.port=${guest_redis_port}'#' serving-server.properties sed -i 's#redis.password=${host_redis_password}#'redis.password=${guest_redis_password}'#' serving-server.properties + sed -i "/^model.transfer.url=/cmodel.transfer.url=${guest_model_transfer}/v1/model/transfer" serving-server.properties cd ${serving_proxy_path}/conf sed -i '/^zk.url=/czk.url=${guest_zk_url}' application.properties sed -i '6c "ip":"${host_guest[0]}",' route_table.json @@ -142,7 +134,7 @@ eeooff if [[ ${host_guest[1]} ]] then echo "------guest hava host_guest1 -----is not null" - if [[ ${roll_hostAndguest[1]} != "" ]]; then + if [[ ${host_guest[1]} != "" ]]; then init_env else echo "please input guest from allinone_cluster_configurations" diff --git "a/serving-server/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" "b/serving-server/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" index 3008e3138cbf593a6961bc3284605a1d7d60089a..acb7b85a3224635d356980d76952e109fad1a8f9 100644 GIT binary patch delta 19270 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

l65Kvozc!Elo<$ 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

  • D3PQzIc*aVF|MT3_VVW=~zCXGY+@H#cp+ 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 J5i$Fx{sROI)HDD9 delta 18894 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 From c560bae7920ba1e2933ecc403fc11dfb9387b75a Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Thu, 9 Jan 2020 16:38:04 +0800 Subject: [PATCH 094/190] shell add zk.url for serving --- serving-server/cluster-deploy/scripts/package.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/serving-server/cluster-deploy/scripts/package.sh b/serving-server/cluster-deploy/scripts/package.sh index 95aae9b7..d913a325 100644 --- a/serving-server/cluster-deploy/scripts/package.sh +++ b/serving-server/cluster-deploy/scripts/package.sh @@ -71,8 +71,8 @@ then sed -i '7c "prot":8000' route_table.json sed -i "17a ,\"serving\":[{\"ip\":\"${host_guest[0]}\",\"prot\":8080}]" route_table.json cd $sering_path/conf - sed -i 's#useRegister=true#'useRegister=false'#' serving-server.properties - sed -i 's#useZkRouter=true#'useZkRouter=false'#' serving-server.properties + sed -i "/^useRegister=/cuseRegister=false" serving-server.properties + sed -i "/^useZkRouter=/cuseZkRouter=false" serving-server.properties elif [ ${apply_zk} = "true" ] then echo "---------apply _zk true" @@ -85,9 +85,9 @@ then sed -i "/^auth.file=/cauth.file=${deploy_dir}/fate-serving/serving-proxy/conf/auth_config.json" application.properties sed -i 's#10000#'${party_list[0]}'#' route_table.json cd $sering_path/conf - sed -i 's#zk.url=zookeeper://localhost:2181#'zk.url=${host_zk_url}'#' serving-server.properties + sed -i "/^zk.url=/czk.url=${host_zk_url}" serving-server.properties sed -i "/^useRegister=/cuseRegister=true" serving-server.properties - sed -i "/^useZkRouter=/cuseZkRouter=true" serving-server.properties + sed -i "/^useZkRouter=/cuseZkRouter=true" serving-server.properties else echo "" fi @@ -110,13 +110,13 @@ eeooff tar -zxvf fate-serving.tar.gz rm -rf fate-serving.tar.gz cd ${public_path}/fate-serving/serving/conf - sed -i 's#zk.url=${host_zk_url}#'zk.url=${guest_zk_url}'#' serving-server.properties + sed -i "/^zk.url=/czk.url=${guest_zk_url}" serving-server.properties sed -i 's#redis.ip=${host_redis_ip}#'redis.ip=${guest_redis_ip}'#' serving-server.properties sed -i 's#redis.port=${host_redis_port}#'redis.port=${guest_redis_port}'#' serving-server.properties sed -i 's#redis.password=${host_redis_password}#'redis.password=${guest_redis_password}'#' serving-server.properties sed -i "/^model.transfer.url=/cmodel.transfer.url=${guest_model_transfer}/v1/model/transfer" serving-server.properties - cd ${serving_proxy_path}/conf - sed -i '/^zk.url=/czk.url=${guest_zk_url}' application.properties + cd ${serving_proxy_path}/conf + sed -i '/^zk.url=/czk.url=${guest_zk_url}' application.properties sed -i '6c "ip":"${host_guest[0]}",' route_table.json sed -i 's#${party_list[0]}#'${party_list[1]}'#' route_table.json sed -i '7c "prot":8000' route_table.json From 1329d724898626a8f351c5992fecc56aeb5f1983 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Thu, 9 Jan 2020 17:45:07 +0800 Subject: [PATCH 095/190] optimize shutdown method Signed-off-by: v_dylanxu <136539068@qq.com> --- .../exceptions/UnSupportMethodException.java | 13 +++ .../exceptions/UnSurpportMethodException.java | 13 --- serving-proxy/pom.xml | 4 +- .../serving/proxy/bootstrap/Bootstrap.java | 81 ++++++++++++------- .../serving/proxy/config/RegistryConfig.java | 77 ++++++++++++++++++ .../proxy/rpc/grpc/IntraGrpcServer.java | 53 ++---------- .../proxy/rpc/router/ZkServingRouter.java | 11 +-- .../proxy/rpc/services/InferenceService.java | 4 +- 8 files changed, 156 insertions(+), 100 deletions(-) create mode 100644 fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/UnSupportMethodException.java delete mode 100644 fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/UnSurpportMethodException.java create mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/UnSupportMethodException.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/UnSupportMethodException.java new file mode 100644 index 00000000..d67babf2 --- /dev/null +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/UnSupportMethodException.java @@ -0,0 +1,13 @@ +package com.webank.ai.fate.serving.core.exceptions; + +/** + * @Description TODO + * @Author + **/ +public class UnSupportMethodException extends RuntimeException { + + public UnSupportMethodException(){ + + super("UnSupportMethod"); + } +} diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/UnSurpportMethodException.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/UnSurpportMethodException.java deleted file mode 100644 index f3f0cb75..00000000 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/UnSurpportMethodException.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.webank.ai.fate.serving.core.exceptions; - -/** - * @Description TODO - * @Author - **/ -public class UnSurpportMethodException extends RuntimeException { - - public UnSurpportMethodException(){ - - super("UnSurpportMethod"); - } -} diff --git a/serving-proxy/pom.xml b/serving-proxy/pom.xml index 178f36a9..2facebe5 100644 --- a/serving-proxy/pom.xml +++ b/serving-proxy/pom.xml @@ -36,11 +36,11 @@ - + com.googlecode.json-simple diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java index f00c5aab..715abd6a 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java @@ -1,14 +1,21 @@ package com.webank.ai.fate.serving.proxy.bootstrap; +import com.google.common.collect.Sets; +import com.webank.ai.fate.register.url.URL; +import com.webank.ai.fate.register.zookeeper.ZookeeperRegistry; +import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.proxy.rpc.core.AbstractServiceAdaptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.PropertySource; import org.springframework.scheduling.annotation.EnableScheduling; +import java.util.Set; + /** * @Description TODO * @Author @@ -19,41 +26,55 @@ @EnableScheduling public class Bootstrap { + Logger logger = LoggerFactory.getLogger(Bootstrap.class); - static Logger logger = LoggerFactory.getLogger(Bootstrap.class); - - public static void main(String[] args) { - - SpringApplication.run(Bootstrap.class, args); - - - Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { + private static ApplicationContext applicationContext; + public void start(String[] args) { + applicationContext = SpringApplication.run(Bootstrap.class, args); + } + public void stop() { + logger.info("try to shutdown server ==============!!!!!!!!!!!!!!!!!!!!!"); + AbstractServiceAdaptor.isOpen = false; + int tryNum = 0; + /** + * 3秒 + */ + while (AbstractServiceAdaptor.requestInHandle.get() > 0 && tryNum < 30) { + logger.info("try to shundown,try count {}, remain {}", tryNum, AbstractServiceAdaptor.requestInHandle.get()); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + boolean useZkRouter = Boolean.parseBoolean(applicationContext.getEnvironment().getProperty(Dict.USE_ZK_ROUTER, "false")); + if (useZkRouter) { + ZookeeperRegistry zookeeperRegistry = applicationContext.getBean(ZookeeperRegistry.class); + Set registered = zookeeperRegistry.getRegistered(); + Set urls = Sets.newHashSet(); + urls.addAll(registered); + urls.forEach(url -> { + logger.info("unregister {}", url); + zookeeperRegistry.unregister(url); + }); + + zookeeperRegistry.destroy(); + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } - @Override - public void run() { - - logger.info("try to shutdown server ==============!!!!!!!!!!!!!!!!!!!!!"); - AbstractServiceAdaptor.isOpen=false; - int tryNum= 0; - /** - * 3秒 - */ - while(AbstractServiceAdaptor.requestInHandle.get()>0&&tryNum<30){ - - logger.info("try to shundown,try count {}, remain {}",tryNum,AbstractServiceAdaptor.requestInHandle.get()); - try { - Thread.sleep(100); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - }) - + public static void main(String[] args) { + Bootstrap bootstrap = new Bootstrap(); + bootstrap.start(args); - ); + Runtime.getRuntime().addShutdownHook(new Thread(() -> bootstrap.stop())); } } \ No newline at end of file diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java new file mode 100644 index 00000000..97251940 --- /dev/null +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java @@ -0,0 +1,77 @@ +/* + * Copyright 2019 The FATE Authors. All Rights Reserved. + * + * 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.webank.ai.fate.serving.proxy.config; + +import com.webank.ai.fate.register.provider.FateServer; +import com.webank.ai.fate.register.router.DefaultRouterService; +import com.webank.ai.fate.register.router.RouterService; +import com.webank.ai.fate.register.zookeeper.ZookeeperRegistry; +import com.webank.ai.fate.serving.proxy.common.Dict; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Optional; + +@Configuration +public class RegistryConfig { + + @Value("${proxy.grpc.intra.port:8867}") + private Integer port; + + @Value("${zk.url:zookeeper://localhost:2181}") + private String zkUrl; + + @Value("${useZkRouter:false}") + private String useZkRouter; + + @Value("${acl.enable}") + private String aclEnable; + + @Value("${acl.username}") + private String aclUsername; + + @Value("${acl.password}") + private String aclPassword; + + @Bean + public ZookeeperRegistry zookeeperRegistry() { + if ("true".equals(useZkRouter) && StringUtils.isNotEmpty(zkUrl)) { + System.setProperty("acl.enable", Optional.ofNullable(aclEnable).orElse("")); + System.setProperty("acl.username", Optional.ofNullable(aclUsername).orElse("")); + System.setProperty("acl.password", Optional.ofNullable(aclPassword).orElse("")); + + ZookeeperRegistry zookeeperRegistry = ZookeeperRegistry.getRegistery(zkUrl, Dict.SELF_PROJECT_NAME, + Dict.SELF_ENVIRONMENT, Integer.valueOf(port)); + zookeeperRegistry.register(FateServer.serviceSets); + zookeeperRegistry.subProject("serving"); + return zookeeperRegistry; + } + return null; + } + + @Bean + public RouterService routerService(ZookeeperRegistry zookeeperRegistry) { + if (zookeeperRegistry != null) { + DefaultRouterService defaultRouterService = new DefaultRouterService(); + defaultRouterService.setRegistry(zookeeperRegistry); + return defaultRouterService; + } + return null; + } +} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java index e6efada8..fcb98a44 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java @@ -1,15 +1,10 @@ package com.webank.ai.fate.serving.proxy.rpc.grpc; -import com.webank.ai.fate.register.provider.FateServer; import com.webank.ai.fate.register.provider.FateServerBuilder; -import com.webank.ai.fate.register.router.DefaultRouterService; import com.webank.ai.fate.register.zookeeper.ZookeeperRegistry; -import com.webank.ai.fate.serving.proxy.common.Dict; -import com.webank.ai.fate.serving.proxy.rpc.router.ZkServingRouter; import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.ServerInterceptors; -import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; @@ -18,7 +13,6 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; -import java.util.Optional; import java.util.concurrent.Executor; /** @@ -27,63 +21,30 @@ **/ @Service public class IntraGrpcServer implements InitializingBean { + Logger logger = LoggerFactory.getLogger(InterGrpcServer.class); @Value("${proxy.grpc.intra.port:8867}") private Integer port; - @Value("${zk.url:zookeeper://localhost:2181}") - private String zkUrl ; - - @Value("${useZkRouter:false}") - private String useZkRouter; - - @Value("${acl.enable}") - private String aclEnable; - - @Value("${acl.username}") - private String aclUsername; - - @Value("${acl.password}") - private String aclPassword; - - ZookeeperRegistry zookeeperRegistry; - - Logger logger = LoggerFactory.getLogger(InterGrpcServer.class); - - Server server ; + @Autowired + ZookeeperRegistry zookeeperRegistry; @Autowired IntraRequestHandler intraRequestHandler; - @Resource(name="grpcExecutorPool") + @Resource(name = "grpcExecutorPool") Executor executor; + Server server; + @Override public void afterPropertiesSet() throws Exception { - FateServerBuilder serverBuilder = (FateServerBuilder)ServerBuilder.forPort(port); + FateServerBuilder serverBuilder = (FateServerBuilder) ServerBuilder.forPort(port); serverBuilder.executor(executor); serverBuilder.addService(ServerInterceptors.intercept(intraRequestHandler, new ServiceExceptionHandler())); serverBuilder.addService(intraRequestHandler); server = serverBuilder.build(); server.start(); - - if("true".equals(useZkRouter)&& StringUtils.isNotEmpty(zkUrl)) { - - System.setProperty("acl.enable", Optional.ofNullable(aclEnable).orElse("")); - System.setProperty("acl.username", Optional.ofNullable(aclUsername).orElse("")); - System.setProperty("acl.password", Optional.ofNullable(aclPassword).orElse("")); - - zookeeperRegistry = ZookeeperRegistry.getRegistery(zkUrl, Dict.SELF_PROJECT_NAME, Dict.SELF_ENVIRONMENT, Integer.valueOf(port)); - zookeeperRegistry.register(FateServer.serviceSets); - zookeeperRegistry.subProject("serving"); - - DefaultRouterService defaultRouterService = new DefaultRouterService(); - - defaultRouterService.setRegistry(zookeeperRegistry); - - ZkServingRouter.setZkRouterService(defaultRouterService); - } - } } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java index 2d320aca..e20e8008 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java @@ -14,6 +14,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -22,7 +23,7 @@ @Service -public class ZkServingRouter extends BaseServingRouter implements InitializingBean{ +public class ZkServingRouter extends BaseServingRouter implements InitializingBean{ @Value("${useZkRouter:false}") private String useZkRouter; @@ -35,12 +36,8 @@ public class ZkServingRouter extends BaseServingRouter implements InitializingB @Value("${coordinator:9999}") private String selfCoordinator; - public static void setZkRouterService(RouterService zkRouterService) { - ZkServingRouter.zkRouterService = zkRouterService; - } - - private static RouterService zkRouterService; - + @Autowired + private RouterService zkRouterService; private static final Logger logger = LoggerFactory.getLogger(ZkServingRouter.class); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java index 73a52a93..0f40349f 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java @@ -8,7 +8,7 @@ import com.webank.ai.fate.api.serving.InferenceServiceProto; import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.exceptions.NoResultException; -import com.webank.ai.fate.serving.core.exceptions.UnSurpportMethodException; +import com.webank.ai.fate.serving.core.exceptions.UnSupportMethodException; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; import com.webank.ai.fate.serving.core.rpc.core.ProxyService; @@ -108,7 +108,7 @@ public Map doService(Context context, InboundPackage data, OutboundPackage< timeWait = asyncTimeout; }else{ logger.error("unknown callName {}.", callName); - throw new UnSurpportMethodException(); + throw new UnSupportMethodException(); } InferenceServiceProto.InferenceMessage result = resultFuture.get(timeWait,TimeUnit.MILLISECONDS); From 508c29ec6dbdbee15a600d3b4aa867212cec467e Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Fri, 10 Jan 2020 10:45:07 +0800 Subject: [PATCH 096/190] update fate_flow_url --- .../cluster-deploy/scripts/allinone_cluster_configurations.sh | 4 ++-- serving-server/cluster-deploy/scripts/package.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh b/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh index 558a7432..fd4de146 100644 --- a/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh +++ b/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh @@ -13,5 +13,5 @@ apply_zk=true host_zk_url=zookeeper://localhost:2181 guest_zk_url=zookeeper://localhost:2181 workMode=1 -host_model_transfer=http://127.0.0.1:9380 -guest_model_transfer=http://127.0.0.1:9380 +host_fate_flow_url=http://127.0.0.1:9380 +guest_fate_flow_url=http://127.0.0.1:9380 diff --git a/serving-server/cluster-deploy/scripts/package.sh b/serving-server/cluster-deploy/scripts/package.sh index d913a325..1c56444f 100644 --- a/serving-server/cluster-deploy/scripts/package.sh +++ b/serving-server/cluster-deploy/scripts/package.sh @@ -57,7 +57,7 @@ sed -i 's#redis.ip=127.0.0.1#'redis.ip=${host_redis_ip}'#' serving-server.proper sed -i 's#redis.port=6379#'redis.port=${host_redis_port}'#' serving-server.properties sed -i 's#redis.password=fate_dev#'redis.password=${host_redis_password}'#' serving-server.properties sed -i "/^workMode=/cworkMode=${workMode}" serving-server.properties -sed -i "/^model.transfer.url=/cmodel.transfer.url=${host_model_transfer}/v1/model/transfer" serving-server.properties +sed -i "/^model.transfer.url=/cmodel.transfer.url=${host_fate_flow_url}/v1/model/transfer" serving-server.properties cd $cwd if [ ${apply_zk} = "false" ] then @@ -114,7 +114,7 @@ eeooff sed -i 's#redis.ip=${host_redis_ip}#'redis.ip=${guest_redis_ip}'#' serving-server.properties sed -i 's#redis.port=${host_redis_port}#'redis.port=${guest_redis_port}'#' serving-server.properties sed -i 's#redis.password=${host_redis_password}#'redis.password=${guest_redis_password}'#' serving-server.properties - sed -i "/^model.transfer.url=/cmodel.transfer.url=${guest_model_transfer}/v1/model/transfer" serving-server.properties + sed -i "/^model.transfer.url=/cmodel.transfer.url=${guest_fate_flow_url}/v1/model/transfer" serving-server.properties cd ${serving_proxy_path}/conf sed -i '/^zk.url=/czk.url=${guest_zk_url}' application.properties sed -i '6c "ip":"${host_guest[0]}",' route_table.json From 2b826f15ddf780bfb2bbc914844cd8996a3f6dd7 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Mon, 13 Jan 2020 16:47:18 +0800 Subject: [PATCH 097/190] add auth support Signed-off-by: v_dylanxu <136539068@qq.com> --- .../ai/fate/serving/guest/DefaultGuestInferenceProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java index b5706a0a..b351e513 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java @@ -72,7 +72,7 @@ public ReturnResult runInference(Context context, InferenceRequest inferenceRequ String modelName = inferenceRequest.getModelVersion(); String modelNamespace = inferenceRequest.getModelId(); String serviceId = inferenceRequest.getServiceId(); - + context.setServiceId(serviceId); if (StringUtils.isEmpty(modelNamespace) ) { if(StringUtils.isNotEmpty(inferenceRequest.getServiceId())){ From c85df5a5adeac24353747f589b4d0c82ae007d83 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Mon, 13 Jan 2020 16:48:56 +0800 Subject: [PATCH 098/190] add auth support Signed-off-by: v_dylanxu <136539068@qq.com> --- .../webank/ai/fate/serving/core/bean/BaseContext.java | 11 ++++++++++- .../com/webank/ai/fate/serving/core/bean/Context.java | 5 ++++- .../ai/fate/serving/federatedml/model/BaseModel.java | 1 + proto/proxy.proto | 1 + 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java index fa02488a..4c515d87 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java @@ -28,7 +28,6 @@ import java.util.Map; - public class BaseContext implements Context { private static final Logger logger = LoggerFactory.getLogger(LOGGER_NAME); public static ApplicationContext applicationContext; @@ -296,4 +295,14 @@ public String getCallName() { public void setCallName(String callName) { dataMap.put(Dict.CALL_NAME, callName); } + + @Override + public String getServiceId() { + return (String) this.dataMap.getOrDefault(Dict.SERVICE_ID, ""); + } + + @Override + public void setServiceId(String serviceId) { + dataMap.put(Dict.SERVICE_ID, serviceId); + } } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Context.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Context.java index 1a880259..9adbf373 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Context.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Context.java @@ -22,7 +22,6 @@ public interface Context { - static final String LOGGER_NAME = "flow"; public void preProcess(); @@ -114,4 +113,8 @@ public default void postProcess(Req req, Resp resp) { public void setCallName(String callName); public String getCallName(); + + public String getServiceId(); + + public void setServiceId(String serviceId); } diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java index 08a99a15..787d470c 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java @@ -165,6 +165,7 @@ protected ReturnResult getFederatedPredictFromRemote(Context context, FederatedP Proxy.AuthInfo.Builder authBuilder = Proxy.AuthInfo.newBuilder(); authBuilder.setNonce(context.getCaseId()); authBuilder.setVersion(version); + authBuilder.setServiceId(context.getServiceId()); packetBuilder.setAuth(authBuilder.build()); GrpcConnectionPool grpcConnectionPool = GrpcConnectionPool.getPool(); diff --git a/proto/proxy.proto b/proto/proxy.proto index ee9cafa9..a19b72df 100644 --- a/proto/proxy.proto +++ b/proto/proxy.proto @@ -77,6 +77,7 @@ message AuthInfo { string applyId = 4; string nonce = 5; string version = 6; + string serviceId = 7; } // data streaming packet From e8ae972237befd9ef1dc7b08ea9201dd30d61aec Mon Sep 17 00:00:00 2001 From: kaideng Date: Mon, 13 Jan 2020 18:41:39 +0800 Subject: [PATCH 099/190] merge master Signed-off-by: kaideng --- .../core/bean/HostInferenceLoggerPrinter.java | 13 ++- .../common/CuratorZookeeperClient.java | 87 +++++++------------ .../com/webank/ai/fate/networking/Proxy.java | 22 +++-- .../grpc/client/DataTransferPipedClient.java | 66 +++++++------- .../serving/adapter/dataaccess/TestFile.java | 3 + 5 files changed, 88 insertions(+), 103 deletions(-) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/HostInferenceLoggerPrinter.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/HostInferenceLoggerPrinter.java index a087e5a0..98964cd0 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/HostInferenceLoggerPrinter.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/HostInferenceLoggerPrinter.java @@ -17,25 +17,24 @@ package com.webank.ai.fate.serving.core.bean; -import com.webank.ai.fate.serving.core.utils.GetSystemInfo; -import org.slf4j.Logger; +import com.webank.ai.fate.serving.core.utils.GetSystemInfo;import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map; -public class HostInferenceLoggerPrinter implements LoggerPrinter { +public class HostInferenceLoggerPrinter implements LoggerPrinter { static final String LOGGER_NAME = "flow"; private static final Logger logger = LoggerFactory.getLogger(LOGGER_NAME); @Override - public void printLog(Context context, Map req, ReturnResult resp) { + public void printLog(Context context, HostFederatedParams req, ReturnResult resp) { - logger.info("{}|{}|{}|{}|{}|{}|{}|{}", - GetSystemInfo.getLocalIp(), context.getSeqNo(), req != null ? ((Map) req).get(Dict.CASEID) : Dict.NONE, context.getActionType(), context.getCostTime(), - resp != null ? resp.getRetcode() : Dict.NONE, req, resp + logger.info("{}|{}|{}|{}|{}|{}|{}|{}", + GetSystemInfo.getLocalIp(), context.getSeqNo(), req != null ? req.getCaseId() : Dict.NONE, context.getActionType(), context.getCostTime(), + resp != null ? resp.getRetcode() : Dict.NONE, req.getFeatureIdMap(), resp ); } diff --git a/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java b/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java index f9197dc3..a241104c 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java @@ -28,6 +28,8 @@ import org.apache.curator.framework.state.ConnectionState; import org.apache.curator.framework.state.ConnectionStateListener; import org.apache.curator.retry.RetryNTimes; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException.NoNodeException; import org.apache.zookeeper.KeeperException.NodeExistsException; @@ -37,8 +39,6 @@ import org.apache.zookeeper.data.Id; import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.server.auth.DigestAuthenticationProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.nio.charset.Charset; import java.util.ArrayList; @@ -56,15 +56,16 @@ public class CuratorZookeeperClient extends AbstractZookeeperClient treeCacheMap = new ConcurrentHashMap<>(); - private static final String SCHEME = "digest"; - private boolean aclEnable; - private String aclUsername; - private String aclPassword; - private List acls = new ArrayList<>(); + private static final List acls = new ArrayList<>(); + + private static final String scheme = "digest"; + private static final String aclUserName = System.getProperty("acl.username"); + private static final String aclPassword = System.getProperty("acl.password"); + public CuratorZookeeperClient(URL url) { super(url); @@ -75,25 +76,12 @@ public CuratorZookeeperClient(URL url) { .retryPolicy(new RetryNTimes(1, 1000)) .connectionTimeoutMs(timeout); - try { - aclEnable = Boolean.parseBoolean(System.getProperty("acl.enable", "false")); - } catch (Exception e) { - aclEnable = false; - } - - if (aclEnable) { - aclUsername = System.getProperty("acl.username", ""); - aclPassword = System.getProperty("acl.password", ""); - - if (StringUtils.isBlank(aclUsername) || StringUtils.isBlank(aclPassword)) { - aclEnable = false; - } else { - builder.authorization(SCHEME, (aclUsername + ":" + aclPassword).getBytes()); + if (StringUtils.isNotEmpty(aclUserName) && StringUtils.isNotEmpty(aclPassword) ) { + builder.authorization(scheme, (aclUserName + ":" + aclPassword).getBytes()); - Id allow = new Id(SCHEME, DigestAuthenticationProvider.generateDigest(aclUsername + ":" + aclPassword)); - // add more - acls.add(new ACL(ZooDefs.Perms.ALL, allow)); - } + Id allow = new Id(scheme, DigestAuthenticationProvider.generateDigest(aclUserName + ":" + aclPassword)); + // add more + acls.add(new ACL(ZooDefs.Perms.ALL, allow)); } client = builder.build(); @@ -114,7 +102,7 @@ public void stateChanged(CuratorFramework client, ConnectionState state) { }); client.start(); - if (aclEnable) { + if (StringUtils.isNotEmpty(aclUserName) && StringUtils.isNotEmpty(aclPassword) ) { client.setACL().withACL(acls).forPath("/"); } } catch (Exception e) { @@ -127,7 +115,7 @@ public void createPersistent(String path) { try { logger.info("createPersistent {}", path); - if (aclEnable) { + if (acls.size() > 0) { client.create().withACL(acls).forPath(path); } else { client.create().forPath(path); @@ -142,7 +130,7 @@ public void createPersistent(String path) { public void createEphemeral(String path) { try { logger.info("createEphemeral {}", path); - if (aclEnable) { + if (acls.size() > 0) { client.create().withMode(CreateMode.EPHEMERAL).withACL(acls).forPath(path); } else { client.create().withMode(CreateMode.EPHEMERAL).forPath(path); @@ -158,14 +146,14 @@ protected void createPersistent(String path, String data) { byte[] dataBytes = data.getBytes(CHARSET); try { logger.info("createPersistent {} data {}", path, data); - if (aclEnable) { + if (acls.size() > 0) { client.create().withACL(acls).forPath(path, dataBytes); } else { client.create().forPath(path, dataBytes); } } catch (NodeExistsException e) { try { - if (aclEnable) { + if (acls.size() > 0) { Stat stat = client.checkExists().forPath(path); client.setData().withVersion(stat.getAversion()).forPath(path, dataBytes); } else { @@ -184,14 +172,14 @@ protected void createEphemeral(String path, String data) { byte[] dataBytes = data.getBytes(CHARSET); try { logger.info("createEphemeral {} data {}", path, data); - if (aclEnable) { + if (acls.size() > 0) { client.create().withMode(CreateMode.EPHEMERAL).withACL(acls).forPath(path, dataBytes); } else { client.create().withMode(CreateMode.EPHEMERAL).forPath(path, dataBytes); } } catch (NodeExistsException e) { try { - if (aclEnable) { + if (acls.size() > 0) { Stat stat = client.checkExists().forPath(path); client.setData().withVersion(stat.getAversion()).forPath(path, dataBytes); } else { @@ -208,12 +196,12 @@ protected void createEphemeral(String path, String data) { @Override public void delete(String path) { try { - if (aclEnable) { -// Stat stat = client.checkExists().forPath(path); -// client.delete().withVersion(stat.getAversion()).forPath(path); - this.clearAcl(path); + if (acls.size() > 0) { + Stat stat = client.checkExists().forPath(path); + client.delete().withVersion(stat.getAversion()).forPath(path); + } else { + client.delete().forPath(path); } - client.delete().forPath(path); } catch (NoNodeException e) { } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); @@ -247,6 +235,11 @@ public boolean isConnected() { return client.getZookeeperClient().isConnected(); } + @Override + public void clearAcl(String path) { + + } + @Override public String doGetContent(String path) { try { @@ -262,9 +255,6 @@ public String doGetContent(String path) { @Override public void doClose() { - if (aclEnable) { - this.clearAcl("/"); - } client.close(); } @@ -326,18 +316,6 @@ public void removeTargetChildListener(String path, CuratorWatcherImpl listener) listener.unwatch(); } - @Override - public void clearAcl(String path) { - if (aclEnable) { - logger.info("clear acl {}", path); - try { - client.setACL().withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE).forPath(path); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - /** * just for unit test * @@ -423,8 +401,7 @@ public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exc case CONNECTION_SUSPENDED: eventType = EventType.CONNECTION_SUSPENDED; break; - default: - break; + } dataListener.dataChanged(path, content, eventType); } diff --git a/router/src/main/java/com/webank/ai/fate/networking/Proxy.java b/router/src/main/java/com/webank/ai/fate/networking/Proxy.java index cd85e4a1..816093d3 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/Proxy.java +++ b/router/src/main/java/com/webank/ai/fate/networking/Proxy.java @@ -26,11 +26,12 @@ import com.webank.ai.fate.register.router.DefaultRouterService; import com.webank.ai.fate.register.url.URL; import com.webank.ai.fate.register.zookeeper.ZookeeperRegistry; +import com.webank.ai.fate.serving.core.bean.Configuration; import com.webank.ai.fate.serving.core.bean.Dict; import io.grpc.Server; import org.apache.commons.cli.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -39,7 +40,7 @@ public class Proxy { - private static final Logger logger = LoggerFactory.getLogger(Proxy.class); + private static final Logger LOGGER = LogManager.getLogger(); public static ZookeeperRegistry zookeeperRegistry; @@ -71,14 +72,17 @@ public static void main(String[] args) throws Exception { Server server = serverFactory.createServer(confFilePath); ServerConfManager serverConfManager = context.getBean(ServerConfManager.class); ServerConf serverConf = serverConfManager.getServerConf(); - logger.info("Server started listening on port: {}", serverConf.getPort()); - logger.info("server conf: {}", serverConf); + LOGGER.info("Server started listening on port: {}", serverConf.getPort()); + LOGGER.info("server conf: {}", serverConf); server.start(); Properties properties = serverConf.getProperties(); - System.setProperty(Dict.ACL_ENABLE, properties.getProperty(Dict.ACL_ENABLE, "")); - System.setProperty(Dict.ACL_USERNAME, properties.getProperty(Dict.ACL_USERNAME, "")); - System.setProperty(Dict.ACL_PASSWORD, properties.getProperty(Dict.ACL_PASSWORD, "")); + if(properties.getProperty(Dict.ACL_USERNAME) != null) { + System.setProperty(Dict.ACL_USERNAME, properties.getProperty(Dict.ACL_USERNAME)); + } + if(properties.getProperty(Dict.ACL_PASSWORD) != null) { + System.setProperty(Dict.ACL_PASSWORD, properties.getProperty(Dict.ACL_PASSWORD)); + } useRegister = Boolean.valueOf(properties.getProperty("useRegister", "false")); useZkRouter = Boolean.valueOf(properties.getProperty("useZkRouter", "false")); @@ -109,7 +113,7 @@ public static void main(String[] args) throws Exception { Set urls = Sets.newHashSet(); urls.addAll(registered); urls.forEach(url -> { - logger.info("unregister {}", url); + LOGGER.info("unregister {}", url); zookeeperRegistry.unregister(url); }); zookeeperRegistry.destroy(); diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java index 5c136cfa..f15dd8f1 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java +++ b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java @@ -30,9 +30,9 @@ import com.webank.ai.fate.networking.proxy.model.ServerConf; import com.webank.ai.fate.networking.proxy.service.ConfFileBasedFdnRouter; import com.webank.ai.fate.networking.proxy.service.FdnRouter; -import com.webank.ai.fate.networking.proxy.util.AuthUtils; import com.webank.ai.fate.networking.proxy.util.ErrorUtils; import com.webank.ai.fate.networking.proxy.util.ToStringUtils; +import com.webank.ai.fate.networking.proxy.util.AuthUtils; import com.webank.ai.fate.register.common.Constants; import com.webank.ai.fate.register.router.RouterService; import com.webank.ai.fate.register.url.CollectionUtils; @@ -44,8 +44,8 @@ import io.grpc.stub.StreamObserver; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -59,7 +59,7 @@ @Component @Scope("prototype") public class DataTransferPipedClient { - private static final Logger logger = LoggerFactory.getLogger(DataTransferPipedClient.class); + private static final Logger LOGGER = LogManager.getLogger(DataTransferPipedClient.class); @Autowired private GrpcStubFactory grpcStubFactory; @Autowired @@ -80,8 +80,7 @@ public class DataTransferPipedClient { private BasicMeta.Endpoint endpoint; private boolean needSecureChannel; - private static long MAX_AWAIT_HOURS = 24; - private static String SERVICE_ROLE_NAME = "serving-1.0"; + private long MAX_AWAIT_HOURS = 24; public DataTransferPipedClient() { needSecureChannel = false; @@ -89,7 +88,7 @@ public DataTransferPipedClient() { public void push(Proxy.Metadata metadata, Pipe pipe) { String onelineStringMetadata = toStringUtils.toOneLineString(metadata); - logger.info("[PUSH][CLIENT] client send push to server: {}", + LOGGER.info("[PUSH][CLIENT] client send push to server: {}", onelineStringMetadata); DataTransferServiceGrpc.DataTransferServiceStub stub = getStub(metadata.getSrc(), metadata.getDst()); @@ -98,7 +97,7 @@ public void push(Proxy.Metadata metadata, Pipe pipe) { Proxy.Topic to = metadata.getDst(); stub = getStub(from, to); } catch (Exception e) { - logger.error("[PUSH][CLIENT] error when creating push stub"); + LOGGER.error("[PUSH][CLIENT] error when creating push stub"); pipe.onError(e); } @@ -109,11 +108,10 @@ public void push(Proxy.Metadata metadata, Pipe pipe) { grpcStreamObserverFactory.createClientPushResponseStreamObserver(resultCallback, finishLatch); StreamObserver requestObserver = stub.push(responseObserver); - logger.info("[PUSH][CLIENT] push stub: {}, metadata: {}", + LOGGER.info("[PUSH][CLIENT] push stub: {}, metadata: {}", stub.getChannel(), onelineStringMetadata); int emptyRetryCount = 0; - int maxRetryCount = 60; Proxy.Packet packet = null; do { packet = (Proxy.Packet) pipe.read(1, TimeUnit.SECONDS); @@ -123,20 +121,20 @@ public void push(Proxy.Metadata metadata, Pipe pipe) { emptyRetryCount = 0; } else { ++emptyRetryCount; - if (emptyRetryCount % maxRetryCount == 0) { - logger.info("[PUSH][CLIENT] push stub waiting. empty retry count: {}, metadata: {}", + if (emptyRetryCount % 60 == 0) { + LOGGER.info("[PUSH][CLIENT] push stub waiting. empty retry count: {}, metadata: {}", emptyRetryCount, onelineStringMetadata); } } } while ((packet != null || !pipe.isDrained()) && emptyRetryCount < 30 && !pipe.hasError()); - logger.info("[PUSH][CLIENT] break out from loop. Proxy.Packet is null? {} ; pipe.isDrained()? {}" + + LOGGER.info("[PUSH][CLIENT] break out from loop. Proxy.Packet is null? {} ; pipe.isDrained()? {}" + ", pipe.hasError? {}, metadata: {}", packet == null, pipe.isDrained(), pipe.hasError(), onelineStringMetadata); if (pipe.hasError()) { Throwable error = pipe.getError(); - logger.error("[PUSH][CLIENT] push error: {}, metadata: {}", + LOGGER.error("[PUSH][CLIENT] push error: {}, metadata: {}", ExceptionUtils.getStackTrace(error), onelineStringMetadata); requestObserver.onError(error); @@ -147,7 +145,7 @@ public void push(Proxy.Metadata metadata, Pipe pipe) { try { finishLatch.await(MAX_AWAIT_HOURS, TimeUnit.HOURS); } catch (InterruptedException e) { - logger.error("[PUSH][CLIENT] client push: finishLatch.await() interrupted"); + LOGGER.error("[PUSH][CLIENT] client push: finishLatch.await() interrupted"); requestObserver.onError(errorUtils.toGrpcRuntimeException(e)); pipe.onError(e); Thread.currentThread().interrupt(); @@ -159,19 +157,19 @@ public void push(Proxy.Metadata metadata, Pipe pipe) { if (resultCallback.hasResult()) { convertedPipe.setResult(resultCallback.getResult()); } else { - logger.warn("No Proxy.Metadata returned in pipe. request metadata: {}", + LOGGER.warn("No Proxy.Metadata returned in pipe. request metadata: {}", onelineStringMetadata); } } pipe.onComplete(); - logger.info("[PUSH][CLIENT] push closing pipe. metadata: {}", + LOGGER.info("[PUSH][CLIENT] push closing pipe. metadata: {}", onelineStringMetadata); } public void pull(Proxy.Metadata metadata, Pipe pipe) { String onelineStringMetadata = toStringUtils.toOneLineString(metadata); - logger.info("[PULL][CLIENT] client send pull to server: {}", onelineStringMetadata); + LOGGER.info("[PULL][CLIENT] client send pull to server: {}", onelineStringMetadata); DataTransferServiceGrpc.DataTransferServiceStub stub = getStub(metadata.getDst(), metadata.getSrc()); final CountDownLatch finishLatch = new CountDownLatch(1); @@ -180,13 +178,13 @@ public void pull(Proxy.Metadata metadata, Pipe pipe) { grpcStreamObserverFactory.createClientPullResponseStreamObserver(pipe, finishLatch, metadata); stub.pull(metadata, responseObserver); - logger.info("[PULL][CLIENT] pull stub: {}, metadata: {}", + LOGGER.info("[PULL][CLIENT] pull stub: {}, metadata: {}", stub.getChannel(), onelineStringMetadata); try { finishLatch.await(MAX_AWAIT_HOURS, TimeUnit.HOURS); } catch (InterruptedException e) { - logger.error("[PULL][CLIENT] client pull: finishLatch.await() interrupted"); + LOGGER.error("[PULL][CLIENT] client pull: finishLatch.await() interrupted"); responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); pipe.onError(e); Thread.currentThread().interrupt(); @@ -207,7 +205,7 @@ public void unaryCall(Proxy.Packet packet, Pipe pipe) { try { String onelineStringMetadata = toStringUtils.toOneLineString(header); - logger.info("[UNARYCALL][CLIENT] client send unary call to server: {}", onelineStringMetadata); + LOGGER.info("[UNARYCALL][CLIENT] client send unary call to server: {}", onelineStringMetadata); packet = authUtils.addAuthInfo(packet); @@ -216,20 +214,20 @@ public void unaryCall(Proxy.Packet packet, Pipe pipe) { stub.unaryCall(packet, responseObserver); - logger.info("[UNARYCALL][CLIENT] unary call stub: {}, metadata: {}", + LOGGER.info("[UNARYCALL][CLIENT] unary call stub: {}, metadata: {}", stub.getChannel(), onelineStringMetadata); try { finishLatch.await(MAX_AWAIT_HOURS, TimeUnit.HOURS); } catch (InterruptedException e) { - logger.error("[UNARYCALL][CLIENT] client unary call: finishLatch.await() interrupted"); + LOGGER.error("[UNARYCALL][CLIENT] client unary call: finishLatch.await() interrupted"); responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); pipe.onError(e); Thread.currentThread().interrupt(); return; } } catch (Exception e) { - logger.error("[UNARYCALL][CLIENT] client unary call: exception: ", e); + LOGGER.error("[UNARYCALL][CLIENT] client unary call: exception: ", e); responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); pipe.onError(e); Thread.currentThread().interrupt(); @@ -254,14 +252,18 @@ private DataTransferServiceGrpc.DataTransferServiceStub getStub(Proxy.Topic from ConfFileBasedFdnRouter confFileBasedFdnRouter = (ConfFileBasedFdnRouter) fdnRouter; Map>> routerTable = confFileBasedFdnRouter.getRouteTable(); - if (routerTable.containsKey(to.getPartyId()) &&routerTable.get(to.getPartyId()).get(SERVICE_ROLE_NAME)!=null&& SERVICE_ROLE_NAME.equals(to.getRole())) { + if (routerTable.containsKey(to.getPartyId()) && + ((routerTable.get(to.getPartyId()).get("serving-1.0")!=null&& "serving-1.0".equals(to.getRole()))|| + (routerTable.get(to.getPartyId()).get("serving")!=null&& "serving".equals(to.getRole()))) + + ) { stub = routerByServiceRegister(from, to, pack); if (stub != null) { - logger.info("appid {} register return stub", to.getPartyId()); + LOGGER.info("appid {} register return stub", to.getPartyId()); return stub; } else { - logger.info("appid {} register not return stub", to.getPartyId()); + LOGGER.info("appid {} register not return stub", to.getPartyId()); return null; } @@ -280,7 +282,7 @@ private DataTransferServiceGrpc.DataTransferServiceStub getStub(Proxy.Topic from stub = grpcStubFactory.getAsyncStub(endpoint); } - logger.info("[ROUTE] route info: {} routed to {}", toStringUtils.toOneLineString(to), + LOGGER.info("[ROUTE] route info: {} routed to {}", toStringUtils.toOneLineString(to), toStringUtils.toOneLineString(fdnRouter.route(to))); fdnRouter.route(from); @@ -306,7 +308,7 @@ private DataTransferServiceGrpc.DataTransferServiceStub getStub(Proxy.Topic from stub = grpcStubFactory.getAsyncStub(endpoint); } - logger.info("[ROUTE] route info: {} routed to {}", toStringUtils.toOneLineString(to), + LOGGER.info("[ROUTE] route info: {} routed to {}", toStringUtils.toOneLineString(to), toStringUtils.toOneLineString(fdnRouter.route(to))); fdnRouter.route(from); @@ -314,10 +316,10 @@ private DataTransferServiceGrpc.DataTransferServiceStub getStub(Proxy.Topic from return stub; } - private static final String MODEL_KEY_SEPARATOR = "&"; + private static final String modelKeySeparator = "&"; public static String genModelKey(String name, String namespace) { - return StringUtils.join(Arrays.asList(name, namespace), MODEL_KEY_SEPARATOR); + return StringUtils.join(Arrays.asList(name, namespace), modelKeySeparator); } @@ -342,7 +344,7 @@ private DataTransferServiceGrpc.DataTransferServiceStub routerByServiceRegister( } List urls = routerService.router(paramUrl); - logger.info("try to find {} returns {}",urlString,urls); + LOGGER.info("try to find {} returns {}",urlString,urls); if (CollectionUtils.isNotEmpty(urls)) { URL url = urls.get(0); BasicMeta.Endpoint.Builder builder = BasicMeta.Endpoint.newBuilder(); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/TestFile.java b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/TestFile.java index e8d28885..2c7e5043 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/TestFile.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/TestFile.java @@ -27,6 +27,7 @@ import java.nio.file.Files; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -34,11 +35,13 @@ public class TestFile implements FeatureData { private static final Logger logger = LoggerFactory.getLogger(TestFile.class); + @Override public ReturnResult getData(Context context, Map featureIds) { ReturnResult returnResult = new ReturnResult(); Map data = new HashMap<>(); try { + List lines = Files.readAllLines(Paths.get(System.getProperty(Dict.PROPERTY_USER_DIR), "host_data.csv")); lines.forEach(line -> { for (String kv : StringUtils.split(line, ",")) { From 87014175629368a4abe1821cfee283a3d907706a Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Mon, 13 Jan 2020 19:12:08 +0800 Subject: [PATCH 100/190] restore Signed-off-by: v_dylanxu <136539068@qq.com> --- .../common/CuratorZookeeperClient.java | 87 ++++++++++++------- .../com/webank/ai/fate/networking/Proxy.java | 22 ++--- 2 files changed, 64 insertions(+), 45 deletions(-) diff --git a/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java b/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java index a241104c..f9197dc3 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java @@ -28,8 +28,6 @@ import org.apache.curator.framework.state.ConnectionState; import org.apache.curator.framework.state.ConnectionStateListener; import org.apache.curator.retry.RetryNTimes; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException.NoNodeException; import org.apache.zookeeper.KeeperException.NodeExistsException; @@ -39,6 +37,8 @@ import org.apache.zookeeper.data.Id; import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.server.auth.DigestAuthenticationProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.nio.charset.Charset; import java.util.ArrayList; @@ -56,16 +56,15 @@ public class CuratorZookeeperClient extends AbstractZookeeperClient treeCacheMap = new ConcurrentHashMap<>(); - private static final List acls = new ArrayList<>(); - - private static final String scheme = "digest"; - private static final String aclUserName = System.getProperty("acl.username"); - private static final String aclPassword = System.getProperty("acl.password"); - + private static final String SCHEME = "digest"; + private boolean aclEnable; + private String aclUsername; + private String aclPassword; + private List acls = new ArrayList<>(); public CuratorZookeeperClient(URL url) { super(url); @@ -76,12 +75,25 @@ public CuratorZookeeperClient(URL url) { .retryPolicy(new RetryNTimes(1, 1000)) .connectionTimeoutMs(timeout); - if (StringUtils.isNotEmpty(aclUserName) && StringUtils.isNotEmpty(aclPassword) ) { - builder.authorization(scheme, (aclUserName + ":" + aclPassword).getBytes()); + try { + aclEnable = Boolean.parseBoolean(System.getProperty("acl.enable", "false")); + } catch (Exception e) { + aclEnable = false; + } + + if (aclEnable) { + aclUsername = System.getProperty("acl.username", ""); + aclPassword = System.getProperty("acl.password", ""); + + if (StringUtils.isBlank(aclUsername) || StringUtils.isBlank(aclPassword)) { + aclEnable = false; + } else { + builder.authorization(SCHEME, (aclUsername + ":" + aclPassword).getBytes()); - Id allow = new Id(scheme, DigestAuthenticationProvider.generateDigest(aclUserName + ":" + aclPassword)); - // add more - acls.add(new ACL(ZooDefs.Perms.ALL, allow)); + Id allow = new Id(SCHEME, DigestAuthenticationProvider.generateDigest(aclUsername + ":" + aclPassword)); + // add more + acls.add(new ACL(ZooDefs.Perms.ALL, allow)); + } } client = builder.build(); @@ -102,7 +114,7 @@ public void stateChanged(CuratorFramework client, ConnectionState state) { }); client.start(); - if (StringUtils.isNotEmpty(aclUserName) && StringUtils.isNotEmpty(aclPassword) ) { + if (aclEnable) { client.setACL().withACL(acls).forPath("/"); } } catch (Exception e) { @@ -115,7 +127,7 @@ public void createPersistent(String path) { try { logger.info("createPersistent {}", path); - if (acls.size() > 0) { + if (aclEnable) { client.create().withACL(acls).forPath(path); } else { client.create().forPath(path); @@ -130,7 +142,7 @@ public void createPersistent(String path) { public void createEphemeral(String path) { try { logger.info("createEphemeral {}", path); - if (acls.size() > 0) { + if (aclEnable) { client.create().withMode(CreateMode.EPHEMERAL).withACL(acls).forPath(path); } else { client.create().withMode(CreateMode.EPHEMERAL).forPath(path); @@ -146,14 +158,14 @@ protected void createPersistent(String path, String data) { byte[] dataBytes = data.getBytes(CHARSET); try { logger.info("createPersistent {} data {}", path, data); - if (acls.size() > 0) { + if (aclEnable) { client.create().withACL(acls).forPath(path, dataBytes); } else { client.create().forPath(path, dataBytes); } } catch (NodeExistsException e) { try { - if (acls.size() > 0) { + if (aclEnable) { Stat stat = client.checkExists().forPath(path); client.setData().withVersion(stat.getAversion()).forPath(path, dataBytes); } else { @@ -172,14 +184,14 @@ protected void createEphemeral(String path, String data) { byte[] dataBytes = data.getBytes(CHARSET); try { logger.info("createEphemeral {} data {}", path, data); - if (acls.size() > 0) { + if (aclEnable) { client.create().withMode(CreateMode.EPHEMERAL).withACL(acls).forPath(path, dataBytes); } else { client.create().withMode(CreateMode.EPHEMERAL).forPath(path, dataBytes); } } catch (NodeExistsException e) { try { - if (acls.size() > 0) { + if (aclEnable) { Stat stat = client.checkExists().forPath(path); client.setData().withVersion(stat.getAversion()).forPath(path, dataBytes); } else { @@ -196,12 +208,12 @@ protected void createEphemeral(String path, String data) { @Override public void delete(String path) { try { - if (acls.size() > 0) { - Stat stat = client.checkExists().forPath(path); - client.delete().withVersion(stat.getAversion()).forPath(path); - } else { - client.delete().forPath(path); + if (aclEnable) { +// Stat stat = client.checkExists().forPath(path); +// client.delete().withVersion(stat.getAversion()).forPath(path); + this.clearAcl(path); } + client.delete().forPath(path); } catch (NoNodeException e) { } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); @@ -235,11 +247,6 @@ public boolean isConnected() { return client.getZookeeperClient().isConnected(); } - @Override - public void clearAcl(String path) { - - } - @Override public String doGetContent(String path) { try { @@ -255,6 +262,9 @@ public String doGetContent(String path) { @Override public void doClose() { + if (aclEnable) { + this.clearAcl("/"); + } client.close(); } @@ -316,6 +326,18 @@ public void removeTargetChildListener(String path, CuratorWatcherImpl listener) listener.unwatch(); } + @Override + public void clearAcl(String path) { + if (aclEnable) { + logger.info("clear acl {}", path); + try { + client.setACL().withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE).forPath(path); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + /** * just for unit test * @@ -401,7 +423,8 @@ public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exc case CONNECTION_SUSPENDED: eventType = EventType.CONNECTION_SUSPENDED; break; - + default: + break; } dataListener.dataChanged(path, content, eventType); } diff --git a/router/src/main/java/com/webank/ai/fate/networking/Proxy.java b/router/src/main/java/com/webank/ai/fate/networking/Proxy.java index 816093d3..cd85e4a1 100644 --- a/router/src/main/java/com/webank/ai/fate/networking/Proxy.java +++ b/router/src/main/java/com/webank/ai/fate/networking/Proxy.java @@ -26,12 +26,11 @@ import com.webank.ai.fate.register.router.DefaultRouterService; import com.webank.ai.fate.register.url.URL; import com.webank.ai.fate.register.zookeeper.ZookeeperRegistry; -import com.webank.ai.fate.serving.core.bean.Configuration; import com.webank.ai.fate.serving.core.bean.Dict; import io.grpc.Server; import org.apache.commons.cli.*; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -40,7 +39,7 @@ public class Proxy { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger logger = LoggerFactory.getLogger(Proxy.class); public static ZookeeperRegistry zookeeperRegistry; @@ -72,17 +71,14 @@ public static void main(String[] args) throws Exception { Server server = serverFactory.createServer(confFilePath); ServerConfManager serverConfManager = context.getBean(ServerConfManager.class); ServerConf serverConf = serverConfManager.getServerConf(); - LOGGER.info("Server started listening on port: {}", serverConf.getPort()); - LOGGER.info("server conf: {}", serverConf); + logger.info("Server started listening on port: {}", serverConf.getPort()); + logger.info("server conf: {}", serverConf); server.start(); Properties properties = serverConf.getProperties(); - if(properties.getProperty(Dict.ACL_USERNAME) != null) { - System.setProperty(Dict.ACL_USERNAME, properties.getProperty(Dict.ACL_USERNAME)); - } - if(properties.getProperty(Dict.ACL_PASSWORD) != null) { - System.setProperty(Dict.ACL_PASSWORD, properties.getProperty(Dict.ACL_PASSWORD)); - } + System.setProperty(Dict.ACL_ENABLE, properties.getProperty(Dict.ACL_ENABLE, "")); + System.setProperty(Dict.ACL_USERNAME, properties.getProperty(Dict.ACL_USERNAME, "")); + System.setProperty(Dict.ACL_PASSWORD, properties.getProperty(Dict.ACL_PASSWORD, "")); useRegister = Boolean.valueOf(properties.getProperty("useRegister", "false")); useZkRouter = Boolean.valueOf(properties.getProperty("useZkRouter", "false")); @@ -113,7 +109,7 @@ public static void main(String[] args) throws Exception { Set urls = Sets.newHashSet(); urls.addAll(registered); urls.forEach(url -> { - LOGGER.info("unregister {}", url); + logger.info("unregister {}", url); zookeeperRegistry.unregister(url); }); zookeeperRegistry.destroy(); From b46f37bbc3f176d48372190af52ad6b88e3e0347 Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Mon, 13 Jan 2020 21:50:47 +0800 Subject: [PATCH 101/190] shell optimize --- .../src/main/resources/route_table.json | 6 + .../cluster-deploy/scripts/modify_json.py | 48 ++++ .../cluster-deploy/scripts/package.sh | 257 +++++++++++------- 3 files changed, 213 insertions(+), 98 deletions(-) create mode 100644 serving-server/cluster-deploy/scripts/modify_json.py diff --git a/serving-proxy/src/main/resources/route_table.json b/serving-proxy/src/main/resources/route_table.json index cbc6d104..57c58e4a 100644 --- a/serving-proxy/src/main/resources/route_table.json +++ b/serving-proxy/src/main/resources/route_table.json @@ -14,6 +14,12 @@ "ip": "127.0.0.1", "port": 8889 } + ], + "serving": [ + { + "ip": "127.0.0.1", + "prot": 8080 + } ] } }, diff --git a/serving-server/cluster-deploy/scripts/modify_json.py b/serving-server/cluster-deploy/scripts/modify_json.py new file mode 100644 index 00000000..1799c97f --- /dev/null +++ b/serving-server/cluster-deploy/scripts/modify_json.py @@ -0,0 +1,48 @@ +import sys +import json +default_default_ip = "" +default_default_port = None +party_id = "10000" +role_default_ip = "" +role_default_port = None +role_serving_ip = "" +role_serving_port = None + + +def get_new_json(filepath): + with open(filepath, 'rb') as f: + global party_id + json_data = json.load(f) + data = json_data + if default_default_ip: + data['route_table']['default']['default'][0]['ip'] = default_default_ip + if default_default_port: + data['route_table']['default']['default'][0]['port'] = default_default_port + role_default_conf = data['route_table']['10000']['default'] + if role_default_ip: + role_default_conf[0]['ip'] = role_def + role_default_conf[0]['port'] = role_default_portault_ip + if role_default_port: + if party_id != "10000": + del data['route_table']['10000'] + data['route_table'][party_id] = {} + data['route_table'][party_id]['default'] = role_default_conf + if role_serving_ip and role_serving_port: + data['route_table'][party_id]["serving"] = [{ + 'ip': role_serving_ip, + 'port': role_serving_port + }] + f.close() + return json_data +2 + +def rewrite_json_file(filepath, json_data): + with open(filepath, 'w') as f: + json.dump(json_data, f, indent=4, separators=(',', ': ')) + f.close() + + +if __name__ == '__main__': + json_path = sys.argv[1] + m_json_data = get_new_json(json_path) + rewrite_json_file(json_path, m_json_data) \ No newline at end of file diff --git a/serving-server/cluster-deploy/scripts/package.sh b/serving-server/cluster-deploy/scripts/package.sh index 1c56444f..fe061c0a 100644 --- a/serving-server/cluster-deploy/scripts/package.sh +++ b/serving-server/cluster-deploy/scripts/package.sh @@ -19,18 +19,10 @@ fate_serving_proxy_zip=fate-serving-proxy-*-release.zip fate_serving_proxy_jar=fate-serving-proxy-*.jar serving_proxy=serving-proxy serving_proxy_path=$fate_serving_path/serving-proxy -echo "--------------------start" -if [ ! -d $fate_serving_path ]; then - mkdir -p $fate_serving_path - if [ ! -d $sering_path ]; then - cd $fate_serving_path - mkdir $serving - mkdir $serving_proxy - else - echo [INFO] $fate_serving_path dir exist - fi -fi -cp services.sh $fate_serving_path + +mkdir -p ./${fate_serving}/${serving} +mkdir -p ./${fate_serving}/${serving_proxy} +cp services.sh ./${fate_serving} cd ${cwd}/../../../ echo "[INFO] mvn clean package start" mvn clean package -DskipTests @@ -41,106 +33,175 @@ else exit fi - -cd $cwd/../../target -cp $fate_serving_zip $sering_path -cd $sering_path +#serving +cd ${cwd}/../../target +cp ${fate_serving_zip} ${cwd}/${fate_serving}/${serving} +cd ${cwd}/${fate_serving}/${serving} unzip $fate_serving_zip ln -s $fate_serving_jar fate-serving-server.jar -cd $cwd/../../../$serving_proxy/target -cp $fate_serving_proxy_zip $serving_proxy_path -cd $serving_proxy_path + +#serving-proxy +cd ${cwd}/../../../$serving_proxy/target +cp $fate_serving_proxy_zip ${cwd}/${fate_serving}/${serving_proxy} +cd ${cwd}/${fate_serving}/${serving_proxy} unzip $fate_serving_proxy_zip ln -s $fate_serving_proxy_jar fate-serving-proxy.jar -cd $sering_path/conf -sed -i 's#redis.ip=127.0.0.1#'redis.ip=${host_redis_ip}'#' serving-server.properties -sed -i 's#redis.port=6379#'redis.port=${host_redis_port}'#' serving-server.properties -sed -i 's#redis.password=fate_dev#'redis.password=${host_redis_password}'#' serving-server.properties -sed -i "/^workMode=/cworkMode=${workMode}" serving-server.properties -sed -i "/^model.transfer.url=/cmodel.transfer.url=${host_fate_flow_url}/v1/model/transfer" serving-server.properties -cd $cwd -if [ ${apply_zk} = "false" ] -then - cd $serving_proxy_path/conf - sed -i "/^useRegister=/cuseRegister=false" application.properties - sed -i "/^useZkRouter=/cuseZkRouter=false" application.properties - sed -i "/^route.table=/croute.table=${deploy_dir}/fate-serving/serving-proxy/conf/route_table.json" application.properties - sed -i "/^auth.file=/cauth.file=${deploy_dir}/fate-serving/serving-proxy/conf/auth_config.json" application.properties - sed -i "6c \"ip\":\"${host_guest[1]}\"," route_table.json - sed -i 's#10000#'${party_list[0]}'#' route_table.json - sed -i '7c "prot":8000' route_table.json - sed -i "17a ,\"serving\":[{\"ip\":\"${host_guest[0]}\",\"prot\":8080}]" route_table.json - cd $sering_path/conf - sed -i "/^useRegister=/cuseRegister=false" serving-server.properties - sed -i "/^useZkRouter=/cuseZkRouter=false" serving-server.properties -elif [ ${apply_zk} = "true" ] -then - echo "---------apply _zk true" - cd $serving_proxy_path/conf - echo "----------------" $pwd - sed -i "/^zk.url=/czk.url=${host_zk_url}" application.properties - sed -i "/^useRegister=/cuseRegister=true" application.properties - sed -i "/^useZkRouter=/cuseZkRouter=true" application.properties - sed -i "/^route.table=/croute.table=${deploy_dir}/fate-serving/serving-proxy/conf/route_table.json" application.properties - sed -i "/^auth.file=/cauth.file=${deploy_dir}/fate-serving/serving-proxy/conf/auth_config.json" application.properties - sed -i 's#10000#'${party_list[0]}'#' route_table.json - cd $sering_path/conf - sed -i "/^zk.url=/czk.url=${host_zk_url}" serving-server.properties - sed -i "/^useRegister=/cuseRegister=true" serving-server.properties - sed -i "/^useZkRouter=/cuseZkRouter=true" serving-server.properties -else - echo "" -fi +#cp modify_json.py +cd ${cwd} +cp modify_json.py ./fate-serving/serving-proxy/conf +tar -zcvf fate-serving.tar.gz fate-serving -init_env() { - ssh -tt ${user}@${host_guest[1]} << eeooff - if [ ! -d ${public_path} ] - then - mkdir -p ${public_path} - fi - exit +cp_serving() { + for node_ip in "${host_guest[@]}"; do + echo "[INFO] install on ${node_ip}" + ssh -tt ${user}@${node_ip} << eeooff +if [ ! -d ${deploy_dir} ]; then + mkdir -p ${deploy_dir} +fi +exit eeooff - cd ${public_path} - tar -zcvf fate-serving.tar.gz fate-serving/ - scp ${public_path}/fate-serving.tar.gz ${host_guest[1]}:${public_path} - ssh -tt ${user}@${host_guest[1]} << eeooff - cd ${public_path} - echo "public_path-------" ${public_path} +scp fate-serving.tar.gz ${user}@${node_ip}:${deploy_dir} + ssh -tt ${user}@${node_ip} << eeooff + cd ${deploy_dir} tar -zxvf fate-serving.tar.gz rm -rf fate-serving.tar.gz - cd ${public_path}/fate-serving/serving/conf - sed -i "/^zk.url=/czk.url=${guest_zk_url}" serving-server.properties - sed -i 's#redis.ip=${host_redis_ip}#'redis.ip=${guest_redis_ip}'#' serving-server.properties - sed -i 's#redis.port=${host_redis_port}#'redis.port=${guest_redis_port}'#' serving-server.properties - sed -i 's#redis.password=${host_redis_password}#'redis.password=${guest_redis_password}'#' serving-server.properties - sed -i "/^model.transfer.url=/cmodel.transfer.url=${guest_fate_flow_url}/v1/model/transfer" serving-server.properties - cd ${serving_proxy_path}/conf - sed -i '/^zk.url=/czk.url=${guest_zk_url}' application.properties - sed -i '6c "ip":"${host_guest[0]}",' route_table.json - sed -i 's#${party_list[0]}#'${party_list[1]}'#' route_table.json - sed -i '7c "prot":8000' route_table.json - grep serving route_table.json >/dev/null - if [ $? -eq 0 ] - then - sed -i '18d' route_table.json - sed -i "18i ,\"serving\":[{\"ip\":\"${host_guest[1]}\",\"prot\":8080}]" route_table.json - fi + exit +eeooff + done +} +cp_serving +if [ $? -eq 0 ]; then + echo "[INFO] cp fate-serving.tar.gz success" +else + echo "[INFO] cp fate-serving.tar.gz filed" + exit +fi + + +update_config () { + for ((i=0;i<${#host_guest[*]};i++)) + do + #update serving-proxy config path + ssh -tt ${user}@${host_guest[i]} << eeooff + cd ${deploy_dir}/fate-serving/serving-proxy/conf + sed -i "/^route.table=/croute.table=${deploy_dir}/fate-serving/serving-proxy/conf/route_table.json" application.properties + sed -i "/^auth.file=/cauth.file=${deploy_dir}/fate-serving/serving-proxy/conf/auth_config.json" application.properties +exit +eeooff + + + temp=$(( $i % 2 )) + if [ $temp = 0 ] + then + ssh -tt ${user}@${host_guest[i]} << eeooff + cd ${deploy_dir}/fate-serving/serving/conf + sed -i "/^redis.ip=/credis.ip=${host_redis_ip}" serving-server.properties + sed -i "/^redis.port=/credis.port=${host_redis_port}" serving-server.properties + sed -i "/^redis.password=/credis.password=${host_redis_password}" serving-server.properties + sed -i "/^workMode=/cworkMode=${workMode}" serving-server.properties + sed -i "/^model.transfer.url=/cmodel.transfer.url=${host_fate_flow_url}/v1/model/transfer" serving-server.properties exit eeooff + else + ssh -tt ${user}@${host_guest[i]} << eeooff + cd ${deploy_dir}/fate-serving/serving/conf + sed -i "/^redis.ip=/credis.ip=${guest_redis_ip}" serving-server.properties + sed -i "/^redis.port=/credis.port=${guest_redis_port}" serving-server.properties + sed -i "/^redis.password=/credis.password=${guest_redis_password}" serving-server.properties + sed -i "/^workMode=/cworkMode=${workMode}" serving-server.properties + sed -i "/^model.transfer.url=/cmodel.transfer.url=${guest_model_transfer}/v1/model/transfer" serving-server.properties +exit +eeooff + fi + done + } +update_config -if [[ ${host_guest[1]} ]] -then - echo "------guest hava host_guest1 -----is not null" - if [[ ${host_guest[1]} != "" ]]; then - init_env - else - echo "please input guest from allinone_cluster_configurations" - exit 0 - fi +#apply zookeepre the config +update_zk_config () { + echo "---------apply _zk true" + for ((i=0;i<${#host_guest[*]};i++)) + do + temp=$(( $i % 2 )) + if [ $temp = 0 ] + then + ssh -tt ${user}@${host_guest[i]} << eeooff + cd ${deploy_dir}/fate-serving/serving/conf + sed -i "/^zk.url=/czk.url=${host_zk_url}" serving-server.properties + sed -i "/^useRegister=/cuseRegister=true" serving-server.properties + sed -i "/^useZkRouter=/cuseZkRouter=true" serving-server.properties + cd ${deploy_dir}/fate-serving/serving-proxy/conf + sed -i "/^zk.url=/czk.url=${host_zk_url}" application.properties + sed -i "/^useRegister=/cuseRegister=true" application.properties + sed -i "/^useZkRouter=/cuseZkRouter=true" application.properties +exit +eeooff + else + ssh -tt ${user}@${host_guest[i]} << eeooff + cd ${deploy_dir}/fate-serving/serving/conf + sed -i "/^zk.url=/czk.url=${host_zk_url}" serving-server.properties + sed -i "/^useRegister=/cuseRegister=true" serving-server.properties + sed -i "/^useZkRouter=/cuseZkRouter=true" serving-server.properties + cd ${deploy_dir}/fate-serving/serving-proxy/conf + sed -i "/^zk.url=/czk.url=${host_zk_url}" application.properties + sed -i "/^useRegister=/cuseRegister=true" application.properties + sed -i "/^useZkRouter=/cuseZkRouter=true" application.properties +exit +eeooff + fi + done +} +#no zookeepre the config +update_nozk_config () { + for ((i=0;i<${#host_guest[*]};i++)) + do + ssh -tt ${user}@${host_guest[i]} << eeooff + cd ${deploy_dir}/fate-serving/serving/conf + sed -i "/^useRegister=/cuseRegister=false" serving-server.properties + sed -i "/^useZkRouter=/cuseZkRouter=false" serving-server.properties + cd ${deploy_dir}/fate-serving/serving-proxy/conf + sed -i "/^useZkRouter=/cuseZkRouter=false" application.properties +exit +eeooff + temp=$(( $i % 2 )) + if [ $temp = 0 ] + then + ssh -tt ${user}@${host_guest[i]} << eeooff + echo "------------------------host----${host_guest[i]} " + sed -i "3c default_default_ip=\"${host_guest[i+1]}\"" modify_json.py + sed -i "5c party_id=${party_list[i]} " modify_json.py + sed -i "8c role_serving_ip=\"${host_guest[i]}\"" modify_json.py + sed -i "9c role_serving_port=8080" modify_json.py + python modify_json.py route_table.json + rm -rf modify_json.py +exit +eeooff + else + ssh -tt ${user}@${host_guest[i]} << eeooff + cd ${deploy_dir}/fate-serving/serving-proxy/conf + sed -i "3c default_default_ip=\"${host_guest[i-1]}\"" modify_json.py + sed -i "5c party_id=${party_list[i]} " modify_json.py + sed -i "8c role_serving_ip=\"${host_guest[i]}\"" modify_json.py + sed -i "9c role_serving_port=8080" modify_json.py + python modify_json.py route_table.json + rm -rf modify_json.py +exit +eeooff + fi + done +} + +if [ ${apply_zk} = "true" ] +then + update_zk_config +elif [ ${apply_zk} = "false" ] +then + update_nozk_config else - echo "no have guest--------false no null" + echo "" fi +rm -rf fate-serving* From c73dab5cf30c50259b2d8c933e6087e50f6e4643 Mon Sep 17 00:00:00 2001 From: kaideng Date: Tue, 14 Jan 2020 14:13:28 +0800 Subject: [PATCH 102/190] change connection pool Signed-off-by: kaideng --- .../serving/core/bean/GrpcConnectionPool.java | 4 +- .../rpc/core/AbstractServiceAdaptor.java | 2 + .../proxy/rpc/grpc/GrpcConnectionPool.java | 40 +++++++++---------- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java index 11060ef2..ab05ac0e 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java @@ -36,7 +36,7 @@ public class GrpcConnectionPool { static private GrpcConnectionPool pool = new GrpcConnectionPool(); ConcurrentHashMap> poolMap = new ConcurrentHashMap>(); private Integer maxTotal = 64; - private Integer maxIdle = 16; + private Integer maxIdle = 64; private GrpcConnectionPool() { @@ -106,7 +106,7 @@ public ManagedChannelFactory(String ip, int port) { @Override public ManagedChannel create() throws Exception { - + logger.info("create ManagedChannel"); NettyChannelBuilder builder = NettyChannelBuilder .forAddress(ip, port) .keepAliveTime(6, TimeUnit.MINUTES) diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java index 2e987979..0c8d3a7f 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java @@ -168,6 +168,8 @@ public OutboundPackage service(Context context , InboundPackage data requestInHandle.decrementAndGet(); long end = System.currentTimeMillis(); long cost = end - begin; + + if(exceptions.size()!=0){ try { outboundPackage = this.serviceFail(context, data, exceptions); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java index a9f12008..6ef1ffd7 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java @@ -61,23 +61,23 @@ public ManagedChannel getManagedChannel(String host, int port) throws Exception poolConfig.setMaxWaitMillis(-1); - poolConfig.setLifo(true); - - poolConfig.setTestOnBorrow(true); - - poolConfig.setTestWhileIdle(true); - - poolConfig.setNumTestsPerEvictionRun(1); - - poolConfig.setTimeBetweenEvictionRunsMillis(1000); - - poolConfig.setEvictionPolicy(new DefaultEvictionPolicy()); - - poolConfig.setMinEvictableIdleTimeMillis(3000); - - poolConfig.setSoftMinEvictableIdleTimeMillis(3000); - - poolConfig.setBlockWhenExhausted(true); +// poolConfig.setLifo(true); + +// poolConfig.setTestOnBorrow(true); +// +// poolConfig.setTestWhileIdle(true); +// +// poolConfig.setNumTestsPerEvictionRun(1); +// +// poolConfig.setTimeBetweenEvictionRunsMillis(1000); +// +// poolConfig.setEvictionPolicy(new DefaultEvictionPolicy()); +// +// poolConfig.setMinEvictableIdleTimeMillis(3000); +// +// poolConfig.setSoftMinEvictableIdleTimeMillis(3000); +// +// poolConfig.setBlockWhenExhausted(true); poolMap.putIfAbsent(key, new GenericObjectPool (new ManagedChannelFactory(host, port), poolConfig)); @@ -87,7 +87,7 @@ public ManagedChannel getManagedChannel(String host, int port) throws Exception GenericObjectPool objectPool =poolMap.get(key); - logger.info("grpc pool host {} active num {} idle num {}",key,objectPool.getNumActive(),objectPool.getNumIdle()); + logger.info("grpc pool host {} active num {} idle1 num {}",key,objectPool.getNumActive(),objectPool.getNumIdle()); return objectPool.borrowObject(); } @@ -105,7 +105,7 @@ public ManagedChannelFactory(String host, int port) { @Override public ManagedChannel create() throws Exception { - + logger.info("create managedChannel"); ManagedChannelBuilder builder = ManagedChannelBuilder .forAddress(host,port) @@ -139,7 +139,7 @@ public PooledObject wrap(ManagedChannel managedChannel) { @Override public void destroyObject(PooledObject p) throws Exception { - //System.err.println("destroyObject ================"); + logger.info("destroyObject ================"); try { p.getObject().shutdownNow(); From be740644b59709e850f124f811c9383d9937ac32 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Tue, 14 Jan 2020 14:54:24 +0800 Subject: [PATCH 103/190] init model throw ex Signed-off-by: v_dylanxu <136539068@qq.com> --- .../ai/fate/serving/federatedml/PipelineTask.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java index 3ca72a9b..434ae932 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java @@ -61,10 +61,14 @@ public int initModel(Map modelProtoMap) { mlNode.setComponentName(componentName); byte[] protoMeta = newModelProtoMap.get(componentName + ".Meta"); byte[] protoParam = newModelProtoMap.get(componentName + ".Param"); - mlNode.initModel(protoMeta, protoParam); - modelMap.put(componentName, mlNode); - pipeLineNode.add(mlNode); - logger.info(" Add class {} to pipeline task list", className); + int returnCode = mlNode.initModel(protoMeta, protoParam); + if (returnCode == StatusCode.OK) { + modelMap.put(componentName, mlNode); + pipeLineNode.add(mlNode); + logger.info(" Add class {} to pipeline task list", className); + } else { + throw new RuntimeException("initModel error"); + } } catch (Exception ex) { pipeLineNode.add(null); logger.warn("Can not instance {} class", className); From 7dcb8226c95ef805d035506068af44171f32ef55 Mon Sep 17 00:00:00 2001 From: utu Date: Tue, 14 Jan 2020 15:22:17 +0800 Subject: [PATCH 104/190] modify: remove unused injection. --- .../ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java index fcb98a44..61c30dc0 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java @@ -1,7 +1,6 @@ package com.webank.ai.fate.serving.proxy.rpc.grpc; import com.webank.ai.fate.register.provider.FateServerBuilder; -import com.webank.ai.fate.register.zookeeper.ZookeeperRegistry; import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.ServerInterceptors; @@ -26,9 +25,6 @@ public class IntraGrpcServer implements InitializingBean { @Value("${proxy.grpc.intra.port:8867}") private Integer port; - @Autowired - ZookeeperRegistry zookeeperRegistry; - @Autowired IntraRequestHandler intraRequestHandler; From 570bf8f22df4c964ac67d475fb6ccbfe5752d73b Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Tue, 14 Jan 2020 15:38:08 +0800 Subject: [PATCH 105/190] shell python bug repair --- .../cluster-deploy/scripts/modify_json.py | 6 +++--- serving-server/cluster-deploy/scripts/package.sh | 13 ++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/serving-server/cluster-deploy/scripts/modify_json.py b/serving-server/cluster-deploy/scripts/modify_json.py index 1799c97f..22010d3e 100644 --- a/serving-server/cluster-deploy/scripts/modify_json.py +++ b/serving-server/cluster-deploy/scripts/modify_json.py @@ -20,9 +20,9 @@ def get_new_json(filepath): data['route_table']['default']['default'][0]['port'] = default_default_port role_default_conf = data['route_table']['10000']['default'] if role_default_ip: - role_default_conf[0]['ip'] = role_def - role_default_conf[0]['port'] = role_default_portault_ip - if role_default_port: + role_default_conf[0]['ip'] = role_default_ip + if role_default_port: + role_default_conf[0]['port'] = role_default_port if party_id != "10000": del data['route_table']['10000'] data['route_table'][party_id] = {} diff --git a/serving-server/cluster-deploy/scripts/package.sh b/serving-server/cluster-deploy/scripts/package.sh index fe061c0a..e0e212d0 100644 --- a/serving-server/cluster-deploy/scripts/package.sh +++ b/serving-server/cluster-deploy/scripts/package.sh @@ -110,7 +110,7 @@ eeooff sed -i "/^redis.port=/credis.port=${guest_redis_port}" serving-server.properties sed -i "/^redis.password=/credis.password=${guest_redis_password}" serving-server.properties sed -i "/^workMode=/cworkMode=${workMode}" serving-server.properties - sed -i "/^model.transfer.url=/cmodel.transfer.url=${guest_model_transfer}/v1/model/transfer" serving-server.properties + sed -i "/^model.transfer.url=/cmodel.transfer.url=${guest_fate_flow_url}/v1/model/transfer" serving-server.properties exit eeooff fi @@ -122,7 +122,6 @@ update_config #apply zookeepre the config update_zk_config () { - echo "---------apply _zk true" for ((i=0;i<${#host_guest[*]};i++)) do temp=$(( $i % 2 )) @@ -142,11 +141,11 @@ eeooff else ssh -tt ${user}@${host_guest[i]} << eeooff cd ${deploy_dir}/fate-serving/serving/conf - sed -i "/^zk.url=/czk.url=${host_zk_url}" serving-server.properties + sed -i "/^zk.url=/czk.url=${guest_zk_url}" serving-server.properties sed -i "/^useRegister=/cuseRegister=true" serving-server.properties sed -i "/^useZkRouter=/cuseZkRouter=true" serving-server.properties cd ${deploy_dir}/fate-serving/serving-proxy/conf - sed -i "/^zk.url=/czk.url=${host_zk_url}" application.properties + sed -i "/^zk.url=/czk.url=${guest_zk_url}" application.properties sed -i "/^useRegister=/cuseRegister=true" application.properties sed -i "/^useZkRouter=/cuseZkRouter=true" application.properties exit @@ -171,9 +170,9 @@ eeooff if [ $temp = 0 ] then ssh -tt ${user}@${host_guest[i]} << eeooff - echo "------------------------host----${host_guest[i]} " + cd ${deploy_dir}/fate-serving/serving-proxy/conf sed -i "3c default_default_ip=\"${host_guest[i+1]}\"" modify_json.py - sed -i "5c party_id=${party_list[i]} " modify_json.py + sed -i "5c party_id=${party_list[i]}" modify_json.py sed -i "8c role_serving_ip=\"${host_guest[i]}\"" modify_json.py sed -i "9c role_serving_port=8080" modify_json.py python modify_json.py route_table.json @@ -184,7 +183,7 @@ eeooff ssh -tt ${user}@${host_guest[i]} << eeooff cd ${deploy_dir}/fate-serving/serving-proxy/conf sed -i "3c default_default_ip=\"${host_guest[i-1]}\"" modify_json.py - sed -i "5c party_id=${party_list[i]} " modify_json.py + sed -i "5c party_id=${party_list[i]}" modify_json.py sed -i "8c role_serving_ip=\"${host_guest[i]}\"" modify_json.py sed -i "9c role_serving_port=8080" modify_json.py python modify_json.py route_table.json From ec522fbf99c045e5e25bfaf66607497fccb3911b Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Tue, 14 Jan 2020 15:54:15 +0800 Subject: [PATCH 106/190] fix when useZkRouter set false startup failed Signed-off-by: v_dylanxu <136539068@qq.com> --- .../fate/serving/proxy/config/RegistryConfig.java | 14 +++++--------- .../serving/proxy/rpc/grpc/IntraGrpcServer.java | 4 ---- .../serving/proxy/rpc/router/ZkServingRouter.java | 8 +++++--- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java index 97251940..6ec0f249 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java @@ -18,9 +18,9 @@ import com.webank.ai.fate.register.provider.FateServer; import com.webank.ai.fate.register.router.DefaultRouterService; -import com.webank.ai.fate.register.router.RouterService; import com.webank.ai.fate.register.zookeeper.ZookeeperRegistry; import com.webank.ai.fate.serving.proxy.common.Dict; +import com.webank.ai.fate.serving.proxy.rpc.router.ZkServingRouter; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -60,18 +60,14 @@ public ZookeeperRegistry zookeeperRegistry() { Dict.SELF_ENVIRONMENT, Integer.valueOf(port)); zookeeperRegistry.register(FateServer.serviceSets); zookeeperRegistry.subProject("serving"); - return zookeeperRegistry; - } - return null; - } - @Bean - public RouterService routerService(ZookeeperRegistry zookeeperRegistry) { - if (zookeeperRegistry != null) { DefaultRouterService defaultRouterService = new DefaultRouterService(); defaultRouterService.setRegistry(zookeeperRegistry); - return defaultRouterService; + ZkServingRouter.setZkRouterService(defaultRouterService); + + return zookeeperRegistry; } return null; } + } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java index fcb98a44..61c30dc0 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/IntraGrpcServer.java @@ -1,7 +1,6 @@ package com.webank.ai.fate.serving.proxy.rpc.grpc; import com.webank.ai.fate.register.provider.FateServerBuilder; -import com.webank.ai.fate.register.zookeeper.ZookeeperRegistry; import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.ServerInterceptors; @@ -26,9 +25,6 @@ public class IntraGrpcServer implements InitializingBean { @Value("${proxy.grpc.intra.port:8867}") private Integer port; - @Autowired - ZookeeperRegistry zookeeperRegistry; - @Autowired IntraRequestHandler intraRequestHandler; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java index e20e8008..8091008a 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java @@ -14,7 +14,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -36,8 +35,11 @@ public class ZkServingRouter extends BaseServingRouter implements InitializingBe @Value("${coordinator:9999}") private String selfCoordinator; - @Autowired - private RouterService zkRouterService; + public static void setZkRouterService(RouterService zkRouterService) { + ZkServingRouter.zkRouterService = zkRouterService; + } + + private static RouterService zkRouterService; private static final Logger logger = LoggerFactory.getLogger(ZkServingRouter.class); From 13524ffc69ba764392fb228929b62aca2c7d6b09 Mon Sep 17 00:00:00 2001 From: utu Date: Tue, 14 Jan 2020 17:45:19 +0800 Subject: [PATCH 107/190] bugfix: bean initialization. --- .../webank/ai/fate/serving/proxy/config/RegistryConfig.java | 3 ++- .../ai/fate/serving/proxy/rpc/router/ZkServingRouter.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java index 97251940..9bcf4a78 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java @@ -22,6 +22,7 @@ import com.webank.ai.fate.register.zookeeper.ZookeeperRegistry; import com.webank.ai.fate.serving.proxy.common.Dict; import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -66,7 +67,7 @@ public ZookeeperRegistry zookeeperRegistry() { } @Bean - public RouterService routerService(ZookeeperRegistry zookeeperRegistry) { + public RouterService routerService(@Autowired(required=false) ZookeeperRegistry zookeeperRegistry) { if (zookeeperRegistry != null) { DefaultRouterService defaultRouterService = new DefaultRouterService(); defaultRouterService.setRegistry(zookeeperRegistry); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java index e20e8008..122bcea1 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java @@ -36,7 +36,7 @@ public class ZkServingRouter extends BaseServingRouter implements InitializingBe @Value("${coordinator:9999}") private String selfCoordinator; - @Autowired + @Autowired(required=false) private RouterService zkRouterService; private static final Logger logger = LoggerFactory.getLogger(ZkServingRouter.class); From e020d29ba9c393400c7936a8df7b65e4c95f782c Mon Sep 17 00:00:00 2001 From: kaideng Date: Tue, 14 Jan 2020 19:37:55 +0800 Subject: [PATCH 108/190] change some thing Signed-off-by: kaideng --- .../serving/federatedml/model/BaseModel.java | 2 +- .../register/common/FailbackRegistry.java | 1 + .../ai/fate/register/provider/FateServer.java | 4 +- .../register/zookeeper/ZookeeperRegistry.java | 48 +++++++++++-------- .../serving/proxy/config/RegistryConfig.java | 10 +++- .../webank/ai/fate/serving/ServingServer.java | 5 +- .../serving/service/InferenceService.java | 2 +- .../ai/fate/serving/service/ModelService.java | 10 +++- serving-server/src/main/resources/log4j2.xml | 6 +++ 9 files changed, 61 insertions(+), 27 deletions(-) diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java index 787d470c..5a40dd09 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java @@ -199,7 +199,7 @@ protected ReturnResult getFederatedPredictFromRemote(Context context, FederatedP return remoteResult; } catch (Exception e) { - logger.error("getFederatedPredictFromRemote error", e); + logger.error("getFederatedPredictFromRemote error", e.getMessage()); throw new RuntimeException(e); } finally { long end = System.currentTimeMillis(); diff --git a/register/src/main/java/com/webank/ai/fate/register/common/FailbackRegistry.java b/register/src/main/java/com/webank/ai/fate/register/common/FailbackRegistry.java index d3213d53..100eceb4 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/FailbackRegistry.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/FailbackRegistry.java @@ -126,6 +126,7 @@ public void addFailedSubscribedProjectTask(String project) { private void addFailedRegistered(URL url) { + logger.info("try to add failed registed url {}",url); FailedRegisteredTask oldOne = failedRegistered.get(url); if (oldOne != null) { return; diff --git a/register/src/main/java/com/webank/ai/fate/register/provider/FateServer.java b/register/src/main/java/com/webank/ai/fate/register/provider/FateServer.java index 92f21421..6ce8a61b 100644 --- a/register/src/main/java/com/webank/ai/fate/register/provider/FateServer.java +++ b/register/src/main/java/com/webank/ai/fate/register/provider/FateServer.java @@ -67,7 +67,9 @@ public void setEnvironment(String environment) { @Override public Server start() throws IOException { - this.server.start(); + + Server server =this.server.start(); + // register(); return this; } diff --git a/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java b/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java index 05f7484b..1fad5ed4 100644 --- a/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java +++ b/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java @@ -199,33 +199,41 @@ private String parseRegisterService(RegisterService registerService) { } public synchronized void register(Set sets) { - + logger.info("prepare to register {}",sets); String hostAddress = NetUtils.getLocalIp(); Preconditions.checkArgument(port != 0); Preconditions.checkArgument(StringUtils.isNotEmpty(environment)); Set registered = this.getRegistered(); for (RegisterService service : sets) { - URL serviceUrl = URL.valueOf("grpc://" + hostAddress + ":" + port + Constants.PATH_SEPARATOR + parseRegisterService(service)); - if (service.useDynamicEnvironment()) { - - if (CollectionUtils.isNotEmpty(dynamicEnvironments)) { - dynamicEnvironments.forEach(environment -> { - URL newServiceUrl = serviceUrl.setEnvironment(environment); - String serviceName = service.serviceName() + environment; - if (!registedString.contains(serviceName)) { - this.register(newServiceUrl); - this.registedString.add(serviceName); - } else { - logger.info("url {} is already registed,will not do anything ", newServiceUrl); - } - }); - } - } else { - if (!registedString.contains(service.serviceName())) { - this.register(serviceUrl); - this.registedString.add(service.serviceName()); + try { + URL serviceUrl = URL.valueOf("grpc://" + hostAddress + ":" + port + Constants.PATH_SEPARATOR + parseRegisterService(service)); + if (service.useDynamicEnvironment()) { + + if (CollectionUtils.isNotEmpty(dynamicEnvironments)) { + dynamicEnvironments.forEach(environment -> { + URL newServiceUrl = serviceUrl.setEnvironment(environment); + String serviceName = service.serviceName() + environment; + if (!registedString.contains(serviceName)) { + this.register(newServiceUrl); + this.registedString.add(serviceName); + } else { + logger.info("url {} is already registed,will not do anything ", newServiceUrl); + } + }); + } + } else { + if (!registedString.contains(service.serviceName())) { + logger.info("try to register url {}", serviceUrl); + this.register(serviceUrl); + this.registedString.add(service.serviceName()); + } else { + logger.info("url {} is already registed,will not do anything ", service.serviceName()); + } } + }catch(Exception e){ + e.printStackTrace(); + logger.error("try to register service {} failed",service); } } logger.info("registed urls {}", registered); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java index 97251940..898385af 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java @@ -21,7 +21,10 @@ import com.webank.ai.fate.register.router.RouterService; import com.webank.ai.fate.register.zookeeper.ZookeeperRegistry; import com.webank.ai.fate.serving.proxy.common.Dict; +import com.webank.ai.fate.serving.proxy.rpc.grpc.InterGrpcServer; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -31,6 +34,9 @@ @Configuration public class RegistryConfig { + private static final Logger logger = LoggerFactory.getLogger(GrpcConfigration.class); + + @Value("${proxy.grpc.intra.port:8867}") private Integer port; @@ -50,7 +56,8 @@ public class RegistryConfig { private String aclPassword; @Bean - public ZookeeperRegistry zookeeperRegistry() { + public ZookeeperRegistry zookeeperRegistry(InterGrpcServer interGrpcServer) { + logger.info("prepare to create zookeeper registry ,use zk {}",useZkRouter); if ("true".equals(useZkRouter) && StringUtils.isNotEmpty(zkUrl)) { System.setProperty("acl.enable", Optional.ofNullable(aclEnable).orElse("")); System.setProperty("acl.username", Optional.ofNullable(aclUsername).orElse("")); @@ -58,6 +65,7 @@ public ZookeeperRegistry zookeeperRegistry() { ZookeeperRegistry zookeeperRegistry = ZookeeperRegistry.getRegistery(zkUrl, Dict.SELF_PROJECT_NAME, Dict.SELF_ENVIRONMENT, Integer.valueOf(port)); + logger.info("registe zk , {}",FateServer.serviceSets); zookeeperRegistry.register(FateServer.serviceSets); zookeeperRegistry.subProject("serving"); return zookeeperRegistry; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index 79204c87..bb57eab0 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -142,11 +142,14 @@ private void start(String[] args) throws IOException { } }); - zookeeperRegistry.register(FateServer.serviceSets); + zookeeperRegistry.register(FateServer.serviceSets); } + ModelService modelService = applicationContext.getBean(ModelService.class); + modelService.restore(); + ConsoleReporter reporter = applicationContext.getBean(ConsoleReporter.class); reporter.start(1, TimeUnit.SECONDS); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java index 996abe27..956f2e94 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/InferenceService.java @@ -103,7 +103,7 @@ private void inferenceServiceAction(InferenceMessage req, StreamObserver loadProperties(File file, Map } - @Override - public void afterPropertiesSet() throws Exception { + public void restore(){ List publishLoadList = loadProperties(publishLoadStoreFile, publishLoadReqMap); List publishOnlineList = loadProperties(publishOnlineStoreFile, publicOnlineReqMap); @@ -397,5 +396,12 @@ public void afterPropertiesSet() throws Exception { } + @Override + public void afterPropertiesSet() throws Exception { + + // restore(); + + } + } diff --git a/serving-server/src/main/resources/log4j2.xml b/serving-server/src/main/resources/log4j2.xml index ae3da2d8..529aab5f 100644 --- a/serving-server/src/main/resources/log4j2.xml +++ b/serving-server/src/main/resources/log4j2.xml @@ -124,6 +124,12 @@ + + + + + + From 0a0e8cb0159c2a74b4a98cc7fa5e1d4b53933b19 Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Wed, 15 Jan 2020 10:56:23 +0800 Subject: [PATCH 109/190] shell python bug repair --- serving-server/cluster-deploy/scripts/modify_json.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serving-server/cluster-deploy/scripts/modify_json.py b/serving-server/cluster-deploy/scripts/modify_json.py index 22010d3e..dbaa8f70 100644 --- a/serving-server/cluster-deploy/scripts/modify_json.py +++ b/serving-server/cluster-deploy/scripts/modify_json.py @@ -34,7 +34,7 @@ def get_new_json(filepath): }] f.close() return json_data -2 + def rewrite_json_file(filepath, json_data): with open(filepath, 'w') as f: From d4cbdf2741541b46bffd96183392346eb435f612 Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Wed, 15 Jan 2020 17:48:50 +0800 Subject: [PATCH 110/190] shell restart sleep 5s --- serving-server/bin/service.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/serving-server/bin/service.sh b/serving-server/bin/service.sh index d6391a26..246e168d 100644 --- a/serving-server/bin/service.sh +++ b/serving-server/bin/service.sh @@ -40,6 +40,7 @@ case "$1" in restart) stop $module + sleep 5 start $module status $module ;; From 8946e88d2674dd5833f352cc712ea531bc4ef295 Mon Sep 17 00:00:00 2001 From: kaideng Date: Thu, 16 Jan 2020 10:21:00 +0800 Subject: [PATCH 111/190] change proxy log and something Signed-off-by: kaideng --- .../fate/serving/core/bean/BaseContext.java | 7 +- .../serving/proxy/config/RegistryConfig.java | 1 - .../rpc/core/AbstractServiceAdaptor.java | 53 +++++---- .../src/main/resources/log4j2.properties | 105 ------------------ serving-proxy/src/main/resources/log4j2.xml | 91 +++++++++++++++ .../webank/ai/fate/serving/ServingServer.java | 3 +- 6 files changed, 133 insertions(+), 127 deletions(-) delete mode 100644 serving-proxy/src/main/resources/log4j2.properties create mode 100644 serving-proxy/src/main/resources/log4j2.xml diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java index 4c515d87..75589f4b 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java @@ -238,7 +238,12 @@ public void setReturnCode(String returnCode) { @Override public long getDownstreamCost() { - return (long) dataMap.get(Dict.DOWN_STREAM_COST); + + if(dataMap.get(Dict.DOWN_STREAM_COST)!=null) { + + return (long) dataMap.get(Dict.DOWN_STREAM_COST); + } + return 0; } @Override diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java index 55198e04..9bdaf734 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java @@ -63,7 +63,6 @@ public ZookeeperRegistry zookeeperRegistry(InterGrpcServer interGrpcServer) { System.setProperty("acl.enable", Optional.ofNullable(aclEnable).orElse("")); System.setProperty("acl.username", Optional.ofNullable(aclUsername).orElse("")); System.setProperty("acl.password", Optional.ofNullable(aclPassword).orElse("")); - ZookeeperRegistry zookeeperRegistry = ZookeeperRegistry.getRegistery(zkUrl, Dict.SELF_PROJECT_NAME, Dict.SELF_ENVIRONMENT, Integer.valueOf(port)); logger.info("registe zk , {}",FateServer.serviceSets); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java index 0c8d3a7f..bf172bfe 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java @@ -2,10 +2,12 @@ import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; +import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.exceptions.ErrorCode; import com.webank.ai.fate.serving.core.exceptions.ShowDownRejectException; @@ -19,6 +21,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import static com.webank.ai.fate.serving.proxy.common.Dict.CODE; @@ -31,13 +34,17 @@ public abstract class AbstractServiceAdaptor implements ServiceAdaptor { - Logger logger; + Logger flowLogger = LoggerFactory.getLogger( "flow"); + + Logger logger = LoggerFactory.getLogger( this.getClass().getName()); + + public AbstractServiceAdaptor(){ /** * */ - logger = LoggerFactory.getLogger( this.getClass().getName()); + } @@ -168,7 +175,17 @@ public OutboundPackage service(Context context , InboundPackage data requestInHandle.decrementAndGet(); long end = System.currentTimeMillis(); long cost = end - begin; - + try { + logger.info("kaideng test"); + flowLogger.info("{}|{}|{}|{}|" + + "{}|{}|{}|{}|" + + "{}|{}|{}", + begin, context.getSourceIp(), context.getCaseId(), context.getGuestAppId(), + context.getHostAppid(), context.getReturnCode(), end - begin, + context.getDownstreamCost(), serviceName, context.getRouterInfo() != null ? context.getRouterInfo() : "NO_ROUTER_INFO"); + }catch(Exception e){ + logger.error("print flow log error",e); + } if(exceptions.size()!=0){ try { @@ -184,30 +201,14 @@ public OutboundPackage service(Context context , InboundPackage data private OutboundPackage serviceFailInner(Context context, InboundPackage data, Throwable e) throws Exception{ - - Map result = new HashMap(); OutboundPackage outboundPackage = new OutboundPackage(); result.put(MESSAGE, e.getMessage()); ErrorMessageUtil.handleException(result,e); context.setReturnCode(result.get(CODE)!=null?result.get(CODE).toString(): ErrorCode.SYSTEM_ERROR.toString()); - Entry entry=null; - try { - StringBuilder sb = new StringBuilder("ERROR_"); - sb.append(serviceName); - String errResourceName= sb.append("_").append(result.get(CODE).toString()).toString(); - entry = SphU.entry(errResourceName); - } - finally { - if(entry!=null){ - entry.exit(); - } - } resp rsp = transformErrorMap(context ,result); outboundPackage.setData(rsp); return outboundPackage; - - } @@ -227,4 +228,18 @@ private String objectToJson(Object obj) { } + public static void main(String[] args){ + + while(true) { + try (Entry entry = SphU.entry("mytest")) { + + } catch (BlockException e) { + e.printStackTrace(); + } + } + } + + + + } \ No newline at end of file diff --git a/serving-proxy/src/main/resources/log4j2.properties b/serving-proxy/src/main/resources/log4j2.properties deleted file mode 100644 index 0829a0d3..00000000 --- a/serving-proxy/src/main/resources/log4j2.properties +++ /dev/null @@ -1,105 +0,0 @@ -# -# Copyright 2019 The FATE Authors. All Rights Reserved. -# -# 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. -# -name=PropertiesConfig -property.auditDir=audit -property.logDir=logs -property.project=fate -property.module=serving-proxy -property.logPattern=[%-5level] %d{yyyy-MM-dd}T%d{HH:mm:ss,SSS} [%t] [%c{1}:%L] - %msg%n -# console -appender.console.type=Console -appender.console.name=STDOUT -appender.console.layout.type=PatternLayout -appender.console.layout.pattern=${logPattern} -# default file -appender.file.type=RollingFile -appender.file.name=LOGFILE -appender.file.fileName=${logDir}/${project}-${module}.log -appender.file.filePattern=${logDir}/%d{yyyy}/%d{MM}/%d{dd}/${project}-${module}.log.%d{yyyy-MM-dd-HH} -appender.file.layout.type=PatternLayout -appender.file.layout.pattern=${logPattern} -appender.file.policies.type=Policies -appender.file.policies.time.type=TimeBasedTriggeringPolicy -appender.file.policies.time.interval=1 -appender.file.policies.time.modulate=true -appender.file.strategy.type=DefaultRolloverStrategy -# debug -appender.debugging.type=RollingFile -appender.debugging.name=LOGDEBUGGING -appender.debugging.fileName=${logDir}/${project}-${module}-debug.log -appender.debugging.filePattern=${logDir}/%d{yyyy}/%d{MM}/%d{dd}/${project}-${module}-debug.log.%d{yyyy-MM-dd-HH-mm} -appender.debugging.layout.type=PatternLayout -appender.debugging.layout.pattern=${logPattern} -appender.debugging.policies.type=Policies -appender.debugging.policies.time.type=TimeBasedTriggeringPolicy -appender.debugging.policies.time.interval=1 -appender.debugging.policies.time.modulate=true -appender.debugging.strategy.type=DefaultRolloverStrategy -# audit -appender.audit.type=RollingFile -appender.audit.name=LOGAUDIT -appender.audit.fileName=${auditDir}/${project}-${module}-audit.log -appender.audit.filePattern=${auditDir}/%d{yyyy}/%d{MM}/%d{dd}/${project}-${module}-audit.log.%d{yyyy-MM-dd-HH} -appender.audit.layout.type=PatternLayout -appender.audit.layout.pattern=[%d{yyyy-MM-dd}T%d{HH:mm:ss,SSS}]%msg%n -appender.audit.policies.type=Policies -appender.audit.policies.time.type=TimeBasedTriggeringPolicy -appender.audit.policies.time.interval=1 -appender.audit.policies.time.modulate=true -appender.audit.strategy.type=DefaultRolloverStrategy -# stat -appender.stat.type=RollingFile -appender.stat.name=LOGSTAT -appender.stat.fileName=${logDir}/${project}-${module}-stat.log -appender.stat.filePattern=${logDir}/%d{yyyy}/%d{MM}/%d{dd}/${project}-${module}-stat.log.%d{yyyy-MM-dd-HH} -appender.stat.layout.type=PatternLayout -appender.stat.layout.pattern=${logPattern} -appender.stat.policies.type=Policies -appender.stat.policies.time.type=TimeBasedTriggeringPolicy -appender.stat.policies.time.interval=1 -appender.stat.policies.time.modulate=true -appender.stat.strategy.type=DefaultRolloverStrategy -# loggers -loggers=file, debugging, audit, stat -# logger - file -logger.file.name=file -logger.file.level=info -logger.file.appenderRefs=file -logger.file.appenderRef.file.ref=LOGFILE -logger.file.additivity=false -# logger - debugging -logger.debugging.name=debugging -logger.debugging.level=info -logger.debugging.appenderRefs=debugging -logger.debugging.appenderRef.debugging.ref=LOGDEBUGGING -logger.debugging.additivity=false -# logger - audit -logger.audit.name=audit -logger.audit.level=info -logger.audit.appenderRefs=audit -logger.audit.appenderRef.file.ref=LOGAUDIT -logger.audit.additivity=false -# logger - stat -logger.stat.name=stat -logger.stat.level=info -logger.stat.appenderRefs=stat -logger.stat.appenderRef.file.ref=LOGSTAT -logger.stat.additivity=false -# logger - root -rootLogger.level=info -rootLogger.appenderRefs=stdout, file -rootLogger.appenderRef.stdout.ref=STDOUT -rootLogger.appenderRef.file.ref=LOGFILE diff --git a/serving-proxy/src/main/resources/log4j2.xml b/serving-proxy/src/main/resources/log4j2.xml new file mode 100644 index 00000000..6d836875 --- /dev/null +++ b/serving-proxy/src/main/resources/log4j2.xml @@ -0,0 +1,91 @@ + + + + + logs + fate + serving-proxy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index bb57eab0..397f7202 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -90,11 +90,12 @@ public static void main(String[] args) { ServingServer a = new ServingServer(cmd.getOptionValue("c")); a.start(args); } catch (Exception ex) { + logger.error("server start error",ex); ex.printStackTrace(); } } - private void start(String[] args) throws IOException { + private void start(String[] args) throws Exception { this.initialize(); applicationContext = SpringApplication.run(SpringConfig.class, args); ApplicationHolder.applicationContext = applicationContext; From 45a98d5ad0c2b35a338b8fd698a873492856407c Mon Sep 17 00:00:00 2001 From: kaideng Date: Thu, 16 Jan 2020 11:14:02 +0800 Subject: [PATCH 112/190] modified: src/main/resources/log4j2.xml --- serving-proxy/src/main/resources/log4j2.xml | 42 ++----- serving-server/src/main/resources/log4j2.xml | 117 ++----------------- 2 files changed, 19 insertions(+), 140 deletions(-) diff --git a/serving-proxy/src/main/resources/log4j2.xml b/serving-proxy/src/main/resources/log4j2.xml index 6d836875..6c2abab7 100644 --- a/serving-proxy/src/main/resources/log4j2.xml +++ b/serving-proxy/src/main/resources/log4j2.xml @@ -24,67 +24,41 @@ - + - - + + - - - + + - - + + - - - - - - - - - - - - - - - - + diff --git a/serving-server/src/main/resources/log4j2.xml b/serving-server/src/main/resources/log4j2.xml index 529aab5f..819aef85 100644 --- a/serving-server/src/main/resources/log4j2.xml +++ b/serving-server/src/main/resources/log4j2.xml @@ -14,11 +14,11 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + logs fate - serving + serving-server @@ -27,134 +27,39 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + \ No newline at end of file From 8d4f2adfc7260cd77775fb9c778712eef32149ee Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Thu, 16 Jan 2020 12:02:07 +0800 Subject: [PATCH 113/190] shell restart sleep 5s --- serving-proxy/bin/service.sh | 1 + .../Fate-serving_deployment_guide_build_zh.md | 136 ++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 serving-server/doc/Fate-serving_deployment_guide_build_zh.md diff --git a/serving-proxy/bin/service.sh b/serving-proxy/bin/service.sh index 53ff6db5..096a6523 100644 --- a/serving-proxy/bin/service.sh +++ b/serving-proxy/bin/service.sh @@ -42,6 +42,7 @@ case "$1" in restart) stop $module + sleep 5 start $module status $module ;; diff --git a/serving-server/doc/Fate-serving_deployment_guide_build_zh.md b/serving-server/doc/Fate-serving_deployment_guide_build_zh.md new file mode 100644 index 00000000..e5348db8 --- /dev/null +++ b/serving-server/doc/Fate-serving_deployment_guide_build_zh.md @@ -0,0 +1,136 @@ +Serving部署指南 +1.服务器配置 +服务器 +数量 1 or 2 +配置 8 core /16GB memory / 500GB硬盘/10M带宽 +操作系统 CentOS linux 7.2及以上 +依赖包 yum源: gcc gcc-c++ make openssl-devel supervisor gmp-devel mpfr-devel +libmpc-devel libaio numactl autoconf automake libtool libffi-devel snappy +snappy-devel zlib zlib-devel bzip2 bzip2-devel lz4-devel libasan +(可以使用初始化脚本env.sh安装) +用户 用户:app,属主:apps(app用户需可以sudo su root而无需密码) +文件系统 1. 500G硬盘挂载在/ data目录下; 2.创建/ data / projects目录,目录属主为:app:apps +2.集群规划 +party 主机名 IP地址 操作系统 +PartyA VM_0_1_centos 192.168.0.1 CentOS 7.2 +PartyB VM_0_2_centos 192.168.0.2 CentOS 7.2 +3.基础环境配置 +3.1 hostname配置(可选) +1)修改主机名 +在192.168.0.1 root用户下执行: +hostnamectl set-hostname VM_0_1_centos +在192.168.0.2 root用户下执行: +hostnamectl set-hostname VM_0_2_centos +2)加入主机映射 +在目标服务器(192.168.0.1 192.168.0.2)root用户下执行: +vim /etc/hosts +192.168.0.1 VM_0_1_centos +192.168.0.2 VM_0_2_centos +3.2 关闭selinux(可选) +在目标服务器(192.168.0.1 192.168.0.2)root用户下执行: +sed -i '/^SELINUX/s/=.*/=disabled/' /etc/selinux/config +setenforce 0 +3.3 修改Linux最大打开文件数 +在目标服务器(192.168.0.1 192.168.0.2)root用户下执行: +vim /etc/security/limits.conf +* soft nofile 65536 +* hard nofile 65536 +3.4 关闭防火墙(可选) +在目标服务器(192.168.0.1 192.168.0.2)root用户下执行 +systemctl disable firewalld.service +systemctl stop firewalld.service +systemctl status firewalld.service +3.5 软件环境初始化 +1)创建用户 +在目标服务器(192.168.0.1 192.168.0.2)root用户下执行 +groupadd -g 6000 apps +useradd -s /bin/bash -g apps -d /home/app app +passwd app +2)配置sudo +在目标服务器(192.168.0.1 192.168.0.2)root用户下执行 +vim /etc/sudoers.d/app +app ALL=(ALL) ALL +app ALL=(ALL) NOPASSWD: ALL +Defaults !env_reset +3)配置ssh无密登录 +a. 在目标服务器(192.168.0.1 192.168.0.2)app用户下执行 +su app +ssh-keygen -t rsa +cat ~/.ssh/id_rsa.pub >> /home/app/.ssh/authorized_keys +chmod 600 ~/.ssh/authorized_keys +b.合并id_rsa_pub文件 +拷贝192.168.0.1的authorized_keys 到192.168.0.2 ~/.ssh目录下,追加到192.168.0.2的id_rsa.pub到authorized_keys,然后再拷贝到192.168.0.1 +在192.168.0.1 app用户下执行 +scp ~/.ssh/authorized_keys app@192.168.0.2:/home/app/.ssh +输入密码 +在192.168.0.2 app用户下执行 +cat ~/.ssh/id_rsa.pub >> /home/app/.ssh/authorized_keys +scp ~/.ssh/authorized_keys app@192.168.0.1:/home/app/.ssh +覆盖之前的文件 +c. 在目标服务器(192.168.0.1 192.168.0.2)app用户下执行ssh 测试 +ssh app@192.168.0.1 +ssh app@192.168.0.2 + + + +4)需要的软件版本 +Git 1.8+ +Maven 3.5+ +Redis 4.0+ +Jdk 1.8+ +Zookeeper 3.5.5+ + + + + + +4.项目部署 +注:此指导安装目录默认为/data/projects/,执行用户为app,安装时根据具体实际情况修改。 +4.1 代码获取和打包 +在目标服务器(192.168.0.1 具备外网环境)app用户下执行: +注意:服务器需已安装好git和maven 3.5+ +进入执行节点的/data/projects/目录,执行: +cd /data/projects/ +git clone https://github.com/FederatedAI/FATE-Serving.git + +4.2 配置文件修改和示例 +在目标服务器(192.168.0.1)app用户下执行 +进入到FATE-Serving目录下的FATE-Serving/cluster-deploy/scripts目录下,修改配置文件allinone_cluster_configurations.sh. +配置文件allinone_cluster_configurations.sh说明: +配置项 配置项意义 配置项值 说明 +user 操作用户 默认为app 使用默认值 +host_guest Host和guest的服务器ip (192.168.0.1 192.168.0.2) 部署host,只填写一个ip, +部署host和guest,填写两个ip。 + +deploy_dir Serving安装路径 默认为 /data/projects 使用默认值 +party_list 模型的partyid host(10000) guest(9999) +apply_zk 是否使用zk 默认true 使用默认值 +host_redis_ip Host连接的redis的ip 127.0.0.1 +host_redis_port Host连接的redis的端口号 6379 +host_redis_password Host连接的redis的密码 fate_dev +guest_redis_ip Guest连接的redis的ip 127.0.0.1 +guest_redis_port Guest连接的redis的端口号 6379 +guest_redis_password Guest连接的redis的密码 fate_dev +host_zk_url Host连接的zk地址 zookeeper://localhost:2181 +guest_zk_url Guest连接的zk地址 zookeeper://localhost:2181 +workMode Fate_flow工作模式 1:集群 0:单机 1 +host_model_transfer Host的fate_flow的地址和端口 http://127.0.0.1:9380 +guest_model_transfer Guest的fate_flow的地址和端口 http://127.0.0.1:9380 + + +4.3 部署 +1)打包 +按照上述配置含义修改allinone_cluster_configurations.sh文件对应的配置项后,然后在 +FATE-Serving/serving-server/cluster-deploy/scripts目录下执行部署脚本 : sh package.sh +2)启动项目 +cd /data/projects/fate-serving +sh services.sh all start +如果是两台进各自的分别执行一个 +4)查看所有状态 +sh services.sh all status +5)关闭所有 +sh services.sh all stop +5.配置文件详解 + +详情查看: +https://github.com/FederatedAI/FATE-Serving/blob/master/README.md From 8d97dd18f98b5020f0b9c3511f671a0f83fd59f4 Mon Sep 17 00:00:00 2001 From: kaideng Date: Thu, 16 Jan 2020 15:34:58 +0800 Subject: [PATCH 114/190] add null check Signed-off-by: kaideng --- .../fate/serving/federatedml/model/BaseModel.java | 13 +++++++++---- .../guest/DefaultGuestInferenceProvider.java | 1 - 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java index 5a40dd09..cf46f28b 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java @@ -161,11 +161,16 @@ protected ReturnResult getFederatedPredictFromRemote(Context context, FederatedP String version = Configuration.getProperty(Dict.VERSION,""); metaDataBuilder.setOperator(Configuration.getProperty(Dict.VERSION,"")); packetBuilder.setHeader(metaDataBuilder.build()); - Proxy.AuthInfo.Builder authBuilder = Proxy.AuthInfo.newBuilder(); - authBuilder.setNonce(context.getCaseId()); - authBuilder.setVersion(version); - authBuilder.setServiceId(context.getServiceId()); + if(context.getCaseId()!=null) { + authBuilder.setNonce(context.getCaseId()); + } + if(version!=null) { + authBuilder.setVersion(version); + } + if(context.getServiceId()!=null) { + authBuilder.setServiceId( context.getServiceId()); + } packetBuilder.setAuth(authBuilder.build()); GrpcConnectionPool grpcConnectionPool = GrpcConnectionPool.getPool(); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java index b351e513..265764ce 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java @@ -66,7 +66,6 @@ public ReturnResult runInference(Context context, InferenceRequest inferenceRequ long startTime = System.currentTimeMillis(); context.setCaseId(inferenceRequest.getCaseid()); - ReturnResult inferenceResult = new ReturnResult(); inferenceResult.setCaseid(inferenceRequest.getCaseid()); String modelName = inferenceRequest.getModelVersion(); From 1ebd10c28269420b3da1c7e5d9f5a2bcb6f6e861 Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Thu, 16 Jan 2020 16:24:45 +0800 Subject: [PATCH 115/190] serving file --- .../Fate-serving_deployment_guide_build_zh.md | 220 ++++++++++++------ 1 file changed, 147 insertions(+), 73 deletions(-) diff --git a/serving-server/doc/Fate-serving_deployment_guide_build_zh.md b/serving-server/doc/Fate-serving_deployment_guide_build_zh.md index e5348db8..5f88a16d 100644 --- a/serving-server/doc/Fate-serving_deployment_guide_build_zh.md +++ b/serving-server/doc/Fate-serving_deployment_guide_build_zh.md @@ -1,79 +1,145 @@ -Serving部署指南 +# Serving 部署指南 + 1.服务器配置 -服务器 -数量 1 or 2 -配置 8 core /16GB memory / 500GB硬盘/10M带宽 -操作系统 CentOS linux 7.2及以上 -依赖包 yum源: gcc gcc-c++ make openssl-devel supervisor gmp-devel mpfr-devel -libmpc-devel libaio numactl autoconf automake libtool libffi-devel snappy -snappy-devel zlib zlib-devel bzip2 bzip2-devel lz4-devel libasan -(可以使用初始化脚本env.sh安装) -用户 用户:app,属主:apps(app用户需可以sudo su root而无需密码) -文件系统 1. 500G硬盘挂载在/ data目录下; 2.创建/ data / projects目录,目录属主为:app:apps +============ + +| 服务器 | | +| :------: | ------------------------------------------------------------ | +| 数量 | 1 or 2 | +| 配置 | 8 core /16GB memory / 500GB硬盘/10M带宽 | +| 操作系统 | CentOS linux 7.2及以上 | +| 依赖包 | yum源: gcc gcc-c++ make openssl-devel supervisor gmp-devel mpfr-devel
    libmpc-devel libaio numactl autoconf automake libtool libffi-devel snappy
    snappy-devel zlib zlib-devel bzip2 bzip2-devel lz4-devel libasan
    (可以使用初始化脚本env.sh安装) | +| 用户 | 用户:app,属主:apps(app用户需可以sudo su root而无需密码) | +| 文件系统 | 1. 500G硬盘挂载在/ data目录下; 2.创建/ data / projects目录,目录属主为:app:apps | + 2.集群规划 -party 主机名 IP地址 操作系统 -PartyA VM_0_1_centos 192.168.0.1 CentOS 7.2 -PartyB VM_0_2_centos 192.168.0.2 CentOS 7.2 +========== + +| party | 主机名 | IP地址 | 操作系统 | +| ------ | ------------- | ----------- | ---------- | +| PartyA | VM_0_1_centos | 192.168.0.1 | CentOS 7.2 | +| PartyB | VM_0_2_centos | 192.168.0.2 | CentOS 7.2 | + 3.基础环境配置 +============== + 3.1 hostname配置(可选) -1)修改主机名 -在192.168.0.1 root用户下执行: +---------------- + +**1)修改主机名** + +**在192.168.0.1 root用户下执行:** + hostnamectl set-hostname VM_0_1_centos -在192.168.0.2 root用户下执行: + +**在192.168.0.2 root用户下执行:** + hostnamectl set-hostname VM_0_2_centos -2)加入主机映射 -在目标服务器(192.168.0.1 192.168.0.2)root用户下执行: + +**2)加入主机映射** + +**在目标服务器(192.168.0.1 192.168.0.2)root用户下执行:** + vim /etc/hosts + 192.168.0.1 VM_0_1_centos + 192.168.0.2 VM_0_2_centos + 3.2 关闭selinux(可选) -在目标服务器(192.168.0.1 192.168.0.2)root用户下执行: -sed -i '/^SELINUX/s/=.*/=disabled/' /etc/selinux/config +--------------- + +**在目标服务器(192.168.0.1 192.168.0.2)root用户下执行:** + +sed -i '/\^SELINUX/s/=.\*/=disabled/' /etc/selinux/config + setenforce 0 + 3.3 修改Linux最大打开文件数 -在目标服务器(192.168.0.1 192.168.0.2)root用户下执行: +--------------------------- + +**在目标服务器(192.168.0.1 192.168.0.2)root用户下执行:** + vim /etc/security/limits.conf -* soft nofile 65536 -* hard nofile 65536 + +\* soft nofile 65536 + +\* hard nofile 65536 + 3.4 关闭防火墙(可选) -在目标服务器(192.168.0.1 192.168.0.2)root用户下执行 +-------------- + +**在目标服务器(192.168.0.1 192.168.0.2)root用户下执行** + systemctl disable firewalld.service + systemctl stop firewalld.service + systemctl status firewalld.service + 3.5 软件环境初始化 -1)创建用户 -在目标服务器(192.168.0.1 192.168.0.2)root用户下执行 +------------------ + +**1)创建用户** + +**在目标服务器(192.168.0.1 192.168.0.2)root用户下执行** + +``` groupadd -g 6000 apps useradd -s /bin/bash -g apps -d /home/app app passwd app -2)配置sudo -在目标服务器(192.168.0.1 192.168.0.2)root用户下执行 +``` + +**2)配置sudo** + +**在目标服务器(192.168.0.1 192.168.0.2)root用户下执行** + vim /etc/sudoers.d/app + app ALL=(ALL) ALL + app ALL=(ALL) NOPASSWD: ALL + Defaults !env_reset -3)配置ssh无密登录 -a. 在目标服务器(192.168.0.1 192.168.0.2)app用户下执行 + +**3)配置ssh无密登录** + +**a. 在目标服务器(192.168.0.1 192.168.0.2)app用户下执行** + su app + ssh-keygen -t rsa -cat ~/.ssh/id_rsa.pub >> /home/app/.ssh/authorized_keys -chmod 600 ~/.ssh/authorized_keys -b.合并id_rsa_pub文件 -拷贝192.168.0.1的authorized_keys 到192.168.0.2 ~/.ssh目录下,追加到192.168.0.2的id_rsa.pub到authorized_keys,然后再拷贝到192.168.0.1 -在192.168.0.1 app用户下执行 -scp ~/.ssh/authorized_keys app@192.168.0.2:/home/app/.ssh + +cat \~/.ssh/id_rsa.pub \>\> /home/app/.ssh/authorized_keys + +chmod 600 \~/.ssh/authorized_keys + +**b.合并id_rsa_pub文件** + +拷贝192.168.0.1的authorized_keys 到192.168.0.2 +\~/.ssh目录下,追加到192.168.0.2的id_rsa.pub到authorized_keys,然后再拷贝到192.168.0.1 + +**在192.168.0.1 app用户下执行** + +scp \~/.ssh/authorized_keys app\@192.168.0.2:/home/app/.ssh + 输入密码 -在192.168.0.2 app用户下执行 -cat ~/.ssh/id_rsa.pub >> /home/app/.ssh/authorized_keys -scp ~/.ssh/authorized_keys app@192.168.0.1:/home/app/.ssh + +**在192.168.0.2 app用户下执行** + +cat \~/.ssh/id_rsa.pub \>\> /home/app/.ssh/authorized_keys + +scp \~/.ssh/authorized_keys app\@192.168.0.1:/home/app/.ssh + 覆盖之前的文件 -c. 在目标服务器(192.168.0.1 192.168.0.2)app用户下执行ssh 测试 -ssh app@192.168.0.1 -ssh app@192.168.0.2 +**c. 在目标服务器(192.168.0.1 192.168.0.2)app用户下执行ssh 测试** +ssh app\@192.168.0.1 -4)需要的软件版本 +ssh app\@192.168.0.2 + +**4)需要的软件版本** Git 1.8+ Maven 3.5+ Redis 4.0+ @@ -81,56 +147,64 @@ Jdk 1.8+ Zookeeper 3.5.5+ - - - 4.项目部署 +========== + 注:此指导安装目录默认为/data/projects/,执行用户为app,安装时根据具体实际情况修改。 + 4.1 代码获取和打包 -在目标服务器(192.168.0.1 具备外网环境)app用户下执行: -注意:服务器需已安装好git和maven 3.5+ +------------ + +**在目标服务器(192.168.0.1 具备外网环境)app用户下执行**: + +**注意:服务器需已安装好git和maven 3.5+** + 进入执行节点的/data/projects/目录,执行: + cd /data/projects/ git clone https://github.com/FederatedAI/FATE-Serving.git 4.2 配置文件修改和示例 -在目标服务器(192.168.0.1)app用户下执行 -进入到FATE-Serving目录下的FATE-Serving/cluster-deploy/scripts目录下,修改配置文件allinone_cluster_configurations.sh. -配置文件allinone_cluster_configurations.sh说明: -配置项 配置项意义 配置项值 说明 -user 操作用户 默认为app 使用默认值 -host_guest Host和guest的服务器ip (192.168.0.1 192.168.0.2) 部署host,只填写一个ip, -部署host和guest,填写两个ip。 - -deploy_dir Serving安装路径 默认为 /data/projects 使用默认值 -party_list 模型的partyid host(10000) guest(9999) -apply_zk 是否使用zk 默认true 使用默认值 -host_redis_ip Host连接的redis的ip 127.0.0.1 -host_redis_port Host连接的redis的端口号 6379 -host_redis_password Host连接的redis的密码 fate_dev -guest_redis_ip Guest连接的redis的ip 127.0.0.1 -guest_redis_port Guest连接的redis的端口号 6379 -guest_redis_password Guest连接的redis的密码 fate_dev -host_zk_url Host连接的zk地址 zookeeper://localhost:2181 -guest_zk_url Guest连接的zk地址 zookeeper://localhost:2181 -workMode Fate_flow工作模式 1:集群 0:单机 1 -host_model_transfer Host的fate_flow的地址和端口 http://127.0.0.1:9380 -guest_model_transfer Guest的fate_flow的地址和端口 http://127.0.0.1:9380 +------------ +**在目标服务器(192.168.0.1)app用户下执行** + +进入到FATE-Serving目录下的FATE-Serving/serving-server/cluster-deploy/scripts目录下,修改配置文件allinone_cluster_configurations.sh. +配置文件allinone_cluster_configurations.sh说明: +| 配置项 | 配置项意义 | 配置项值 | 说明 | +| -------------------| -------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| user | 操作用户 | 默认为app | 使用默认值 | +| host_guest | Host和guest的服务器ip | (192.168.0.1 192.168.0.2) | 根据对应部署的服务器填写ip | +| deploy_dir | Serving安装路径 | 默认为 /data/projects | 使用默认值 | +| party_list | 模型的partyid | 每个数组元素代表一个partyid,只支持数字,比如10000,9999 | 只部署一个party,只填写一个partyid,部署两个party,填写两个partyid。 | +| apply_zk | 是否使用zk | true使用,false不使用 | 默认true +| host_redis_ip | host连接的redis的ip | 127.0.0.1 | 根据host连接的redis配置ip | +| host_redis_port | host连接的redis的端口号 | 6379 | 根据host连接的redis配置端口| +|host_redis_password | host连接的redis的密码 |fate_dev | 根据host连接的redis配置密码| +|guest_redis_ip | guest连接的redis的ip | 127.0.0.1 | 根据guest连接的redis配置ip| +|guest_redis_port | guest连接的redis的端口号 | 6379 | 根据guest连接的redis配置端口| +|guest_redis_password| guest连接的redis的密码 | fate_dev | 根据guest连接的redis配置密码| +|host_zk_url | host连接的zk地址 | zookeeper://localhost:2181 | 根据host连接的zk配置,若是集群就配置集群地址| +|guest_zk_url | guest连接的zk地址 | zookeeper://localhost:2181 | 根据guest连接的zk配置,若是集群就配置集群地址| +|workMode | fate_flow工作模式 | 1:集群 0:单机 | 使用默认值 | +|host_model_transfer | host的fate_flow的地址和端口 | http://127.0.0.1:9380 | 根据连接的 fate_flow配置ip和端口号| +|guest_model_transfer| guest的fate_flow的地址和端口 | http://127.0.0.1:9380 | 根据连接的 fate_flow配置ip和端口号| 4.3 部署 +------------ 1)打包 按照上述配置含义修改allinone_cluster_configurations.sh文件对应的配置项后,然后在 FATE-Serving/serving-server/cluster-deploy/scripts目录下执行部署脚本 : sh package.sh 2)启动项目 cd /data/projects/fate-serving + sh services.sh all start 如果是两台进各自的分别执行一个 4)查看所有状态 sh services.sh all status 5)关闭所有 sh services.sh all stop -5.配置文件详解 +5.5.配置文件详解 详情查看: -https://github.com/FederatedAI/FATE-Serving/blob/master/README.md +https://github.com/FederatedAI/FATE-Serving/blob/master/README.md \ No newline at end of file From 29d3216bd91749081173f7d52dfa44a97f6ab506 Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Thu, 16 Jan 2020 16:36:40 +0800 Subject: [PATCH 116/190] serving file --- serving-server/doc/Fate-serving_deployment_guide_build_zh.md | 1 + 1 file changed, 1 insertion(+) diff --git a/serving-server/doc/Fate-serving_deployment_guide_build_zh.md b/serving-server/doc/Fate-serving_deployment_guide_build_zh.md index 5f88a16d..1b0df06a 100644 --- a/serving-server/doc/Fate-serving_deployment_guide_build_zh.md +++ b/serving-server/doc/Fate-serving_deployment_guide_build_zh.md @@ -171,6 +171,7 @@ git clone https://github.com/FederatedAI/FATE-Serving.git 进入到FATE-Serving目录下的FATE-Serving/serving-server/cluster-deploy/scripts目录下,修改配置文件allinone_cluster_configurations.sh. 配置文件allinone_cluster_configurations.sh说明: + | 配置项 | 配置项意义 | 配置项值 | 说明 | | -------------------| -------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | user | 操作用户 | 默认为app | 使用默认值 | From e593556d27084995435da8be49b75bac4dafc98c Mon Sep 17 00:00:00 2001 From: utu Date: Thu, 16 Jan 2020 17:02:34 +0800 Subject: [PATCH 117/190] modify: to change default value of server.port. --- serving-proxy/src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serving-proxy/src/main/resources/application.properties b/serving-proxy/src/main/resources/application.properties index 507e4a7d..bfd41805 100644 --- a/serving-proxy/src/main/resources/application.properties +++ b/serving-proxy/src/main/resources/application.properties @@ -1,7 +1,7 @@ # same as partyid coordinator=9999 -server.port=8080 +server.port=8081 # actuator management.server.port=10087 From c93e0ae2d8347f893151989f46ea2be02323af7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lindazheng=28=E9=83=91=E7=90=B3=E7=90=B3=29?= Date: Thu, 16 Jan 2020 20:19:55 +0800 Subject: [PATCH 118/190] add fm online predict --- .../ai/fate/serving/core/bean/Dict.java | 1 + .../serving/federatedml/model/HeteroFM.java | 122 ++++++++++++++++++ .../federatedml/model/HeteroFMGuest.java | 83 ++++++++++++ .../federatedml/model/HeteroFMHost.java | 45 +++++++ proto/fm-model-meta.proto | 36 ++++++ proto/fm-model-param.proto | 54 ++++++++ 6 files changed, 341 insertions(+) create mode 100644 federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFM.java create mode 100644 federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMGuest.java create mode 100644 federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMHost.java create mode 100644 proto/fm-model-meta.proto create mode 100644 proto/fm-model-param.proto diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java index d9fa4ad2..a6dae336 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java @@ -121,6 +121,7 @@ public class Dict { public static final String INFERENCE = "inference"; public static final String INFERENCE_REQUEST = "inferenceRequest"; public static final String INFERENCE_RESULT = "inferenceResult"; + public static final String FM_CROSS = "fm_cross"; public static final String CONTENT_TYPE = "Content-Type"; public static final String CONTENT_TYPE_JSON_UTF8 = "application/json;charset=UTF-8"; diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFM.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFM.java new file mode 100644 index 00000000..98753b3d --- /dev/null +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFM.java @@ -0,0 +1,122 @@ +/* + * Copyright 2019 The FATE Authors. All Rights Reserved. + * + * 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.webank.ai.fate.serving.federatedml.model; + + +import com.webank.ai.fate.core.mlmodel.buffer.fm.FMModelParamProto.Embedding; +import com.webank.ai.fate.core.mlmodel.buffer.fm.FMModelParamProto.FMModelParam; +import com.webank.ai.fate.serving.core.bean.Context; +import com.webank.ai.fate.serving.core.bean.Dict; +import com.webank.ai.fate.serving.core.bean.FederatedParams; +import com.webank.ai.fate.serving.core.bean.StatusCode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public abstract class HeteroFM extends BaseModel { + private static final Logger logger = LoggerFactory.getLogger(HeteroFM.class); + private Map weight; + private Double intercept; + private Map embedding; + private int embedSize; + + @Override + public int initModel(byte[] protoMeta, byte[] protoParam) { + logger.info("start init HeteroFM class"); + try { + FMModelParam fmModelParam = this.parseModel(FMModelParam.parser(), protoParam); + + this.weight = fmModelParam.getWeightMap(); + this.intercept = fmModelParam.getIntercept(); + this.embedding = fmModelParam.getEmbeddingMap(); + this.embedSize = fmModelParam.getEmbedSize(); + } catch (Exception ex) { + ex.printStackTrace(); + return StatusCode.ILLEGALDATA; + } + logger.info("Finish init HeteroFM class, model weight is {}, model embedding is {}", this.weight, this.embedding); + return StatusCode.OK; + } + + Map forward(List> inputDatas) { + Map inputData = inputDatas.get(0); + + int modelWeightHitCount = 0; + int inputDataHitCount = 0; + int weightNum = this.weight.size(); + int inputFeaturesNum = inputData.size(); + logger.info("model weight number:{}", weightNum); + logger.info("input data features number:{}", inputFeaturesNum); + + double score = 0; + for (String key : inputData.keySet()) { + if (this.weight.containsKey(key)) { + Double x = new Double(inputData.get(key).toString()); + Double w = new Double(this.weight.get(key).toString()); + score += w * x; + modelWeightHitCount += 1; + inputDataHitCount += 1; + logger.info("key {} weight is {}, value is {}", key, this.weight.get(key), inputData.get(key)); + } + } + + double[] multiplies = new double[this.embedSize]; + double[] squares = new double[this.embedSize]; + for (String key: this.embedding.keySet()) { + if (inputData.containsKey(key)) { + Double x = new Double(inputData.get(key).toString()); + List wList = this.embedding.get(key).getWeightList(); + for (int i = 0; i < this.embedSize; i++) { + multiplies[i] = multiplies[i] + wList.get(i) * x; + squares[i] = squares[i] + Math.pow(wList.get(i) * x, 2); + } + } + } + double cross = 0.0; + for (int i =0; i < this.embedSize; i++) { + cross += (Math.pow(multiplies[i],2) - squares[i]); + } + score += cross * 0.5; + score += this.intercept; + + double modelWeightHitRate = -1.0; + double inputDataHitRate = -1.0; + try { + modelWeightHitRate = (double) modelWeightHitCount / weightNum; + inputDataHitRate = (double) inputDataHitCount / inputFeaturesNum; + } catch (Exception ex) { + ex.printStackTrace(); + } + + logger.info("model weight hit rate:{}", modelWeightHitRate); + logger.info("input data features hit rate:{}", inputDataHitRate); + + Map ret = new HashMap<>(); + ret.put(Dict.SCORE, score); + ret.put(Dict.MODEL_WRIGHT_HIT_RATE, modelWeightHitRate); + ret.put(Dict.INPUT_DATA_HIT_RATE, inputDataHitRate); + ret.put(Dict.FM_CROSS, multiplies); + + return ret; + } + + @Override + public abstract Map handlePredict(Context context, List> inputData, FederatedParams predictParams); +} diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMGuest.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMGuest.java new file mode 100644 index 00000000..3a0b7307 --- /dev/null +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMGuest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2019 The FATE Authors. All Rights Reserved. + * + * 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.webank.ai.fate.serving.federatedml.model; + + +import com.alibaba.fastjson.JSON; +import com.webank.ai.fate.serving.core.bean.Context; +import com.webank.ai.fate.serving.core.bean.Dict; +import com.webank.ai.fate.serving.core.bean.FederatedParams; +import com.webank.ai.fate.serving.core.bean.ReturnResult; +import com.webank.ai.fate.serving.core.constant.InferenceRetCode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.lang.Math.exp; + +public class HeteroFMGuest extends HeteroFM { + private static final Logger logger = LoggerFactory.getLogger(HeteroFMGuest.class); + + private double sigmod(double x) { + return 1. / (1. + exp(-x)); + } + + @Override + public Map handlePredict(Context context, List> inputData, FederatedParams predictParams) { + Map result = new HashMap<>(); + Map forwardRet = forward(inputData); + double score = new Double(forwardRet.get(Dict.SCORE).toString()); + double[] guestCrosses = (double[]) forwardRet.get(Dict.FM_CROSS); + logger.info("caseid {} guest score:{}, cross data:{}", context.getCaseId(), score, guestCrosses); + try { + ReturnResult hostPredictResponse = this.getFederatedPredict(context, predictParams, Dict.FEDERATED_INFERENCE, true); + if(hostPredictResponse !=null) { + result.put(Dict.RET_CODE,hostPredictResponse.getRetcode()); + logger.info("caseid {} host response is {}",context.getCaseId(),hostPredictResponse.getData()); + if (hostPredictResponse.getData() != null && hostPredictResponse.getData().get(Dict.SCORE) != null) { + double hostScore = ((Number) hostPredictResponse.getData().get(Dict.SCORE)).doubleValue(); + List hostCrosses = JSON.parseArray(hostPredictResponse.getData().get(Dict.FM_CROSS).toString(),double.class); + logger.info("caseid {} host score:{}, cross data: {}",context.getCaseId(), hostScore, hostCrosses); + score += hostScore; + if (hostCrosses == null || hostCrosses.size() != guestCrosses.length) { + throw new RuntimeException("the length of the cross part is not match"); + } + for (int i = 0; i < guestCrosses.length; i++) { + score += hostCrosses.get(i) * guestCrosses[i]; + } + } + }else{ + logger.info("caseid {} host response is null",context.getCaseId()); + } + } catch (io.grpc.StatusRuntimeException ex) { + logger.error("merge host predict failed:", ex); + result.put(Dict.RET_CODE, InferenceRetCode.NETWORK_ERROR); + } + catch(Exception ex){ + logger.error("merge host predict failed:", ex); + result.put(Dict.RET_CODE, InferenceRetCode.SYSTEM_ERROR); + } + double prob = sigmod(score); + result.put(Dict.PROB, prob); + result.put(Dict.GUEST_MODEL_WEIGHT_HIT_RATE + ":{}", forwardRet.get(Dict.MODEL_WRIGHT_HIT_RATE)); + result.put(Dict.GUEST_INPUT_DATA_HIT_RATE + ":{}", forwardRet.get(Dict.INPUT_DATA_HIT_RATE)); + return result; + } +} diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMHost.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMHost.java new file mode 100644 index 00000000..7e36ace0 --- /dev/null +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMHost.java @@ -0,0 +1,45 @@ +/* + * Copyright 2019 The FATE Authors. All Rights Reserved. + * + * 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.webank.ai.fate.serving.federatedml.model; + +import com.webank.ai.fate.serving.core.bean.Context; +import com.webank.ai.fate.serving.core.bean.Dict; +import com.webank.ai.fate.serving.core.bean.FederatedParams; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class HeteroFMHost extends HeteroFM { + private static final Logger logger = LoggerFactory.getLogger(HeteroFMHost.class); + + @Override + public Map handlePredict(Context context, List> inputData, FederatedParams predictParams) { + + logger.info("hetero fm host begin to predict"); + HashMap result = new HashMap<>(); + Map ret = forward(inputData); + result.put(Dict.SCORE, ret.get(Dict.SCORE)); + result.put(Dict.FM_CROSS, ret.get(Dict.FM_CROSS)); + + logger.info("hetero fm host predict ends, result is {}", result); + + return result; + } +} diff --git a/proto/fm-model-meta.proto b/proto/fm-model-meta.proto new file mode 100644 index 00000000..abca6416 --- /dev/null +++ b/proto/fm-model-meta.proto @@ -0,0 +1,36 @@ +/* + * Copyright 2019 The FATE Authors. All Rights Reserved. + * + * 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. + */ + +syntax = "proto3"; + +package com.webank.ai.fate.core.mlmodel.buffer.fm; +option java_outer_classname = "FMModelMetaProto"; + +message FMModelMeta { + string penalty = 1; + double eps = 2; + double alpha = 3; + string optimizer = 4; + double party_weight = 5; + int64 batch_size = 6; + double learning_rate = 7; + int64 max_iter = 8; + string converge_func = 9; + int64 re_encrypt_batches = 10; + bool need_run=11; + bool need_one_vs_rest = 12; +} + diff --git a/proto/fm-model-param.proto b/proto/fm-model-param.proto new file mode 100644 index 00000000..3dc2f72a --- /dev/null +++ b/proto/fm-model-param.proto @@ -0,0 +1,54 @@ +/* + * Copyright 2019 The FATE Authors. All Rights Reserved. + * + * 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. + */ + +syntax = "proto3"; + +package com.webank.ai.fate.core.mlmodel.buffer.fm; +option java_outer_classname = "FMModelParamProto"; + +message Embedding { + repeated double weight = 1; +} + +message FMModelParam { + int32 iters = 1; + repeated double loss_history = 2; + bool is_converged = 3; + map weight = 4; + map embedding = 5; + int32 embed_size = 6; + double intercept = 7; + repeated string header = 8; + OneVsRestResult one_vs_rest_result = 9; + bool need_one_vs_rest = 10; +} + +message SingleModel { + int32 iters = 1; + repeated double loss_history = 2; + bool is_converged = 3; + map weight = 4; + map embedding = 5; + int32 embed_size = 6; + double intercept = 7; + repeated string header = 8; +} + +message OneVsRestResult { + repeated SingleModel completed_models = 1; + repeated string one_vs_rest_classes = 2; +} + From d03d648d5bf7410e5e3ad94caf6df4f26a3adebd Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Fri, 17 Jan 2020 10:31:51 +0800 Subject: [PATCH 119/190] add auth support Signed-off-by: v_dylanxu <136539068@qq.com> --- .../src/main/java/com/webank/ai/fate/serving/ServingServer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index 397f7202..41c88913 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -92,6 +92,7 @@ public static void main(String[] args) { } catch (Exception ex) { logger.error("server start error",ex); ex.printStackTrace(); + System.exit(1); } } From 1ab00a402155988d654118862466afd02957b498 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Fri, 17 Jan 2020 10:31:51 +0800 Subject: [PATCH 120/190] add exit Signed-off-by: v_dylanxu <136539068@qq.com> --- .../src/main/java/com/webank/ai/fate/serving/ServingServer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index 397f7202..41c88913 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -92,6 +92,7 @@ public static void main(String[] args) { } catch (Exception ex) { logger.error("server start error",ex); ex.printStackTrace(); + System.exit(1); } } From 2a6e5e34897ea0c6930f134a093caeaf55ea2486 Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Fri, 17 Jan 2020 11:10:52 +0800 Subject: [PATCH 121/190] shell script optimize --- bin/common.sh | 3 +-- serving-proxy/bin/service.sh | 2 +- serving-server/bin/service.sh | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/bin/common.sh b/bin/common.sh index d0c9d02d..732ed285 100644 --- a/bin/common.sh +++ b/bin/common.sh @@ -80,5 +80,4 @@ stop() { else echo "service not running" fi -} - +} \ No newline at end of file diff --git a/serving-proxy/bin/service.sh b/serving-proxy/bin/service.sh index 096a6523..324c2491 100644 --- a/serving-proxy/bin/service.sh +++ b/serving-proxy/bin/service.sh @@ -48,5 +48,5 @@ case "$1" in ;; *) echo "usage: $0 {start|stop|status|restart}" - exit -1 + exit 1 esac diff --git a/serving-server/bin/service.sh b/serving-server/bin/service.sh index 246e168d..0647f590 100644 --- a/serving-server/bin/service.sh +++ b/serving-server/bin/service.sh @@ -46,5 +46,5 @@ case "$1" in ;; *) echo "usage: $0 {start|stop|status|restart}" - exit -1 -esac + exit 1 +esac \ No newline at end of file From 0120a8a3637e4e859863236531beb908a0ab5101 Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Fri, 17 Jan 2020 11:29:56 +0800 Subject: [PATCH 122/190] shell script optimize --- bin/common.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/common.sh b/bin/common.sh index 732ed285..dc0c3920 100644 --- a/bin/common.sh +++ b/bin/common.sh @@ -33,7 +33,7 @@ start() { java -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/${module}.properties >> logs/console.log 2>>logs/error.log & elif [ ${module} = "serving-proxy" ] then - java -Dspring.config.location=${configpath}/application.properties -DconfPath=$configpath -Xmx2048m -Xms2048m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/application.properties >> logs/console.log 2>>logs/error.log & + java -Dspring.config.location=${configpath}/application.properties -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/application.properties >> logs/console.log 2>>logs/error.log & else echo "" From 8884883d03e0fd47027b7e8df059bda7f9c08d3e Mon Sep 17 00:00:00 2001 From: utu Date: Fri, 17 Jan 2020 14:35:34 +0800 Subject: [PATCH 123/190] add: a new adapter of host test. --- .../adapter/dataaccess/TestFilePick.java | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/TestFilePick.java diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/TestFilePick.java b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/TestFilePick.java new file mode 100644 index 00000000..78903770 --- /dev/null +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/TestFilePick.java @@ -0,0 +1,73 @@ +/* + * Copyright 2019 The FATE Authors. All Rights Reserved. + * + * 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.webank.ai.fate.serving.adapter.dataaccess; + + +import com.webank.ai.fate.serving.core.bean.Context; +import com.webank.ai.fate.serving.core.bean.Dict; +import com.webank.ai.fate.serving.core.bean.ReturnResult; +import com.webank.ai.fate.serving.core.constant.InferenceRetCode; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TestFilePick implements FeatureData { + private static final Logger logger = LoggerFactory.getLogger(TestFilePick.class); + private Map > featureMaps = null; + + + @Override + public ReturnResult getData(Context context, Map featureIds) { + ReturnResult returnResult = new ReturnResult(); + + try { + if(null == featureMaps){ + featureMaps = new HashMap<>(); + List lines = Files.readAllLines(Paths.get(System.getProperty(Dict.PROPERTY_USER_DIR), "host_data.csv")); + lines.forEach(line -> { + String[] idFeats = StringUtils.split(line, ","); + if(idFeats.length > 1){ + Map data = new HashMap<>(); + for (String kv : StringUtils.split(idFeats[1], ";")) { + String[] a = StringUtils.split(kv, ":"); + data.put(a[0], Double.valueOf(a[1])); + } + featureIds.put(idFeats[0], data); + } + }); + } + Map fdata = featureMaps.get(featureIds.get(Dict.DEVICE_ID)); + if(fdata != null) { + returnResult.setData(fdata); + returnResult.setRetcode(InferenceRetCode.OK); + } else{ + logger.error("cant not find features for {}.", featureIds.get(Dict.DEVICE_ID)); + returnResult.setRetcode(InferenceRetCode.GET_FEATURE_FAILED); + } + } catch (Exception ex) { + logger.error(ex.getMessage()); + returnResult.setRetcode(InferenceRetCode.GET_FEATURE_FAILED); + } + return returnResult; + } +} From 3f08405c182ee5bc9700c989ac20829e16ef79f4 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Fri, 17 Jan 2020 15:56:32 +0800 Subject: [PATCH 124/190] OK Signed-off-by: v_dylanxu <136539068@qq.com> --- .../webank/ai/fate/serving/adapter/dataaccess/TestFilePick.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/TestFilePick.java b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/TestFilePick.java index 78903770..0f64cb8b 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/TestFilePick.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/TestFilePick.java @@ -52,7 +52,7 @@ public ReturnResult getData(Context context, Map featureIds) { String[] a = StringUtils.split(kv, ":"); data.put(a[0], Double.valueOf(a[1])); } - featureIds.put(idFeats[0], data); + featureMaps.put(idFeats[0], data); } }); } From 440e2ba663300d72a61d8182fe6273de3aa6b27f Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Fri, 17 Jan 2020 17:23:46 +0800 Subject: [PATCH 125/190] change default maxIdle Signed-off-by: v_dylanxu <136539068@qq.com> --- serving-proxy/src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serving-proxy/src/main/resources/application.properties b/serving-proxy/src/main/resources/application.properties index bfd41805..1999a3b8 100644 --- a/serving-proxy/src/main/resources/application.properties +++ b/serving-proxy/src/main/resources/application.properties @@ -42,5 +42,5 @@ proxy.async.coresize=10 proxy.async.maxsize=100 proxy.grpc.pool.maxTotal=64 -proxy.grpc.pool.maxIdle=1 +proxy.grpc.pool.maxIdle=64 From ec218a67bb7b2c0b6ccd418ba5acfbe7b70eb347 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Fri, 17 Jan 2020 17:33:21 +0800 Subject: [PATCH 126/190] ok Signed-off-by: v_dylanxu <136539068@qq.com> --- .../ai/fate/serving/adapter/dataaccess/TestFilePick.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/TestFilePick.java b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/TestFilePick.java index 0f64cb8b..18c7d00a 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/TestFilePick.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/TestFilePick.java @@ -33,7 +33,7 @@ public class TestFilePick implements FeatureData { private static final Logger logger = LoggerFactory.getLogger(TestFilePick.class); - private Map > featureMaps = null; + private static final Map > featureMaps = new HashMap<>(); @Override @@ -41,8 +41,7 @@ public ReturnResult getData(Context context, Map featureIds) { ReturnResult returnResult = new ReturnResult(); try { - if(null == featureMaps){ - featureMaps = new HashMap<>(); + if(featureMaps.isEmpty()){ List lines = Files.readAllLines(Paths.get(System.getProperty(Dict.PROPERTY_USER_DIR), "host_data.csv")); lines.forEach(line -> { String[] idFeats = StringUtils.split(line, ","); @@ -70,4 +69,4 @@ public ReturnResult getData(Context context, Map featureIds) { } return returnResult; } -} +} \ No newline at end of file From 1dd606011cbf68c23dc5a6bd2b27d29a681b577e Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Fri, 17 Jan 2020 17:34:09 +0800 Subject: [PATCH 127/190] shell update route_table --- .../cluster-deploy/scripts/package.sh | 78 +++++++++++-------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/serving-server/cluster-deploy/scripts/package.sh b/serving-server/cluster-deploy/scripts/package.sh index e0e212d0..0af764eb 100644 --- a/serving-server/cluster-deploy/scripts/package.sh +++ b/serving-server/cluster-deploy/scripts/package.sh @@ -115,8 +115,6 @@ exit eeooff fi done - - } update_config @@ -154,43 +152,57 @@ eeooff done } -#no zookeepre the config -update_nozk_config () { - for ((i=0;i<${#host_guest[*]};i++)) - do - ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/fate-serving/serving/conf - sed -i "/^useRegister=/cuseRegister=false" serving-server.properties - sed -i "/^useZkRouter=/cuseZkRouter=false" serving-server.properties - cd ${deploy_dir}/fate-serving/serving-proxy/conf - sed -i "/^useZkRouter=/cuseZkRouter=false" application.properties +update_route_table () { + for ((i=0;i<${#host_guest[*]};i++)) + do + temp=$(( $i % 2 )) + if [ $temp = 0 ] + then + ssh -tt ${user}@${host_guest[i]} << eeooff + cd ${deploy_dir}/fate-serving/serving-proxy/conf + sed -i "3c default_default_ip=\"${host_guest[i+1]}\"" modify_json.py + sed -i "4c default_default_port=8869" modify_json.py + sed -i "5c party_id=${party_list[i]}" modify_json.py + sed -i "6c role_default_ip=\"${host_guest[i]}\"" modify_json.py + sed -i "7c role_default_port=8867" modify_json.py + sed -i "8c role_serving_ip=\"${host_guest[i]}\"" modify_json.py + sed -i "9c role_serving_port=8000" modify_json.py + python modify_json.py route_table.json + rm -rf modify_json.py exit eeooff - temp=$(( $i % 2 )) - if [ $temp = 0 ] - then - ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/fate-serving/serving-proxy/conf - sed -i "3c default_default_ip=\"${host_guest[i+1]}\"" modify_json.py - sed -i "5c party_id=${party_list[i]}" modify_json.py - sed -i "8c role_serving_ip=\"${host_guest[i]}\"" modify_json.py - sed -i "9c role_serving_port=8080" modify_json.py - python modify_json.py route_table.json - rm -rf modify_json.py + else + ssh -tt ${user}@${host_guest[i]} << eeooff + cd ${deploy_dir}/fate-serving/serving-proxy/conf + sed -i "3c default_default_ip=\"${host_guest[i-1]}\"" modify_json.py + sed -i "4c default_default_port=8869" modify_json.py + sed -i "5c party_id=${party_list[i]}" modify_json.py + sed -i "6c role_default_ip=\"${host_guest[i]}\"" modify_json.py + sed -i "7c role_default_port=8867" modify_json.py + sed -i "8c role_serving_ip=\"${host_guest[i]}\"" modify_json.py + sed -i "9c role_serving_port=8000" modify_json.py + python modify_json.py route_table.json + rm -rf modify_json.py exit eeooff - else - ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/fate-serving/serving-proxy/conf - sed -i "3c default_default_ip=\"${host_guest[i-1]}\"" modify_json.py - sed -i "5c party_id=${party_list[i]}" modify_json.py - sed -i "8c role_serving_ip=\"${host_guest[i]}\"" modify_json.py - sed -i "9c role_serving_port=8080" modify_json.py - python modify_json.py route_table.json - rm -rf modify_json.py + fi + done + +} +update_route_table + +#no zookeepre the config +update_nozk_config () { + for ((i=0;i<${#host_guest[*]};i++)) + do + ssh -tt ${user}@${host_guest[i]} << eeooff + cd ${deploy_dir}/fate-serving/serving/conf + sed -i "/^useRegister=/cuseRegister=false" serving-server.properties + sed -i "/^useZkRouter=/cuseZkRouter=false" serving-server.properties + cd ${deploy_dir}/fate-serving/serving-proxy/conf + sed -i "/^useZkRouter=/cuseZkRouter=false" application.properties exit eeooff - fi done } From 4f4509230c1c01579880a8b5fa86861b3f54c4b4 Mon Sep 17 00:00:00 2001 From: utu Date: Sun, 19 Jan 2020 16:55:14 +0800 Subject: [PATCH 128/190] bugfix: flowlog. --- .../ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java index bf172bfe..571076a7 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java @@ -179,7 +179,7 @@ public OutboundPackage service(Context context , InboundPackage data logger.info("kaideng test"); flowLogger.info("{}|{}|{}|{}|" + "{}|{}|{}|{}|" + - "{}|{}|{}", + "{}|{}", begin, context.getSourceIp(), context.getCaseId(), context.getGuestAppId(), context.getHostAppid(), context.getReturnCode(), end - begin, context.getDownstreamCost(), serviceName, context.getRouterInfo() != null ? context.getRouterInfo() : "NO_ROUTER_INFO"); From a9c35964f0c691937e4e0c8093b2fd4f296d63cd Mon Sep 17 00:00:00 2001 From: kaideng Date: Mon, 20 Jan 2020 15:38:45 +0800 Subject: [PATCH 129/190] fix bug Signed-off-by: kaideng --- .../webank/ai/fate/serving/proxy/security/OverloadMonitor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/OverloadMonitor.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/OverloadMonitor.java index c74f4614..cc55b6df 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/OverloadMonitor.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/OverloadMonitor.java @@ -23,7 +23,7 @@ public class OverloadMonitor implements Interceptor{ public void doPreProcess(Context context, InboundPackage inboundPackage, OutboundPackage outboundPackage) throws Exception { Entry entry = null; try { - SphU.entry(context.getServiceName()); + entry= SphU.entry(context.getServiceName()); } catch (BlockException ex){ logger.warn("request was block by overload monitor, serviceName:{}.", context.getServiceName()); throw ex; From cb9faad372ca8936dcab193d572164d062e3f1c9 Mon Sep 17 00:00:00 2001 From: kaideng Date: Tue, 21 Jan 2020 11:40:31 +0800 Subject: [PATCH 130/190] change struct Signed-off-by: kaideng --- fate-serving-core/pom.xml | 5 + .../ai/fate/serving/core/bean/Dict.java | 19 + .../rpc/core/AbstractServiceAdaptor.java | 62 +-- .../core/rpc/core/ErrorMessageUtil.java | 61 +++ lib/eggroll-core-1.1.jar | Bin 878520 -> 0 bytes pom.xml | 1 - .../org.eclipse.core.resources.prefs | 5 - router/.settings/org.eclipse.jdt.core.prefs | 5 - router/bin/service.sh | 120 ----- router/package.xml | 62 --- router/pom.xml | 117 ----- .../com/webank/ai/fate/networking/Proxy.java | 130 ------ .../proxy/FdnCommunicationServer.java | 33 -- .../fate/networking/proxy/Main0Insecure.java | 106 ----- .../ai/fate/networking/proxy/Main0Stream.java | 107 ----- .../ai/fate/networking/proxy/Main1.java | 62 --- .../ai/fate/networking/proxy/Main2.java | 60 --- .../ai/fate/networking/proxy/Main3.java | 82 ---- .../PipeHandleNotificationEventListener.java | 55 --- .../model/PipeHandleNotificationEvent.java | 55 --- .../proxy/factory/DefaultPipeFactory.java | 66 --- .../proxy/factory/EventFactory.java | 53 --- .../proxy/factory/GrpcServerFactory.java | 286 ------------ .../factory/GrpcStreamObserverFactory.java | 69 --- .../proxy/factory/GrpcStubFactory.java | 227 --------- .../proxy/factory/LocalBeanFactory.java | 43 -- .../networking/proxy/factory/PipeFactory.java | 27 -- .../proxy/factory/QueueFactory.java | 33 -- .../grpc/client/DataTransferPipedClient.java | 374 --------------- .../ClientPullResponseStreamObserver.java | 163 ------- .../ClientPushResponseStreamObserver.java | 68 --- ...ClientUnaryCallResponseStreamObserver.java | 132 ------ .../ServerPushRequestStreamObserver.java | 282 ----------- .../service/DataTransferPipedServerImpl.java | 316 ------------- .../proxy/grpc/service/RouteServerImpl.java | 59 --- .../proxy/helper/ModelValidationHelper.java | 29 -- .../ai/fate/networking/proxy/infra/Cache.java | 30 -- .../ai/fate/networking/proxy/infra/Pipe.java | 47 -- .../proxy/infra/ResultCallback.java | 25 - .../networking/proxy/infra/impl/BasePipe.java | 104 ----- .../InputStreamOutputStreamNoStoragePipe.java | 153 ------ ...InputStreamToPacketUnidirectionalPipe.java | 139 ------ .../proxy/infra/impl/PacketQueuePipe.java | 104 ----- .../impl/PacketQueueSingleResultPipe.java | 54 --- ...acketToOutputStreamUnidirectionalPipe.java | 83 ---- .../infra/impl/SingleResultCallback.java | 38 -- .../proxy/manager/ExecutorManager.java | 85 ---- .../proxy/manager/ServerConfManager.java | 33 -- .../proxy/manager/StatsManager.java | 151 ------ .../proxy/model/PipeHandlerInfo.java | 114 ----- .../networking/proxy/model/ServerConf.java | 202 -------- .../networking/proxy/model/StreamStat.java | 143 ------ .../SimpleTrustAllCertsManagerFactory.java | 48 -- .../proxy/security/TrustAllCertsManager.java | 44 -- .../proxy/service/CascadedCaller.java | 67 --- .../proxy/service/ConfFileBasedFdnRouter.java | 440 ------------------ .../networking/proxy/service/FdnRouter.java | 33 -- .../fate/networking/proxy/util/AuthUtils.java | 172 ------- .../proxy/util/CommandLineOptions.java | 45 -- .../networking/proxy/util/ErrorUtils.java | 47 -- .../fate/networking/proxy/util/PipeUtils.java | 39 -- .../fate/networking/proxy/util/Timeouts.java | 113 ----- .../networking/proxy/util/ToStringUtils.java | 63 --- router/src/main/java/utils/Constants.java | 21 - router/src/main/java/utils/Utils.java | 51 -- .../resources/applicationContext-proxy.xml | 45 -- router/src/main/resources/auth_config.json | 15 - router/src/main/resources/log4j2.properties | 105 ----- router/src/main/resources/proxy.properties | 35 -- .../resources/route_tables/route_table.json | 31 -- .../serving/proxy/bootstrap/Bootstrap.java | 2 +- .../ai/fate/serving/proxy/common/Dict.java | 26 -- .../proxy/common/ErrorMessageUtil.java | 61 --- .../serving/proxy/config/RegistryConfig.java | 2 +- .../proxy/controller/ProxyController.java | 2 +- .../exceptions/GlobalExceptionHandler.java | 2 +- .../proxy/rpc/core/ProxyServiceRegister.java | 5 +- .../proxy/rpc/grpc/ProxyRequestHandler.java | 2 +- .../router/ConfigFileBasedServingRouter.java | 2 +- .../proxy/rpc/router/ZkServingRouter.java | 2 +- .../proxy/rpc/services/InferenceService.java | 5 +- .../proxy/rpc/services/NotFoundService.java | 5 +- .../proxy/rpc/services/UnaryCallService.java | 5 +- .../security/InferenceParamValidator.java | 4 +- 84 files changed, 115 insertions(+), 6303 deletions(-) rename {serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy => fate-serving-core/src/main/java/com/webank/ai/fate/serving/core}/rpc/core/AbstractServiceAdaptor.java (76%) create mode 100644 fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/ErrorMessageUtil.java delete mode 100644 lib/eggroll-core-1.1.jar delete mode 100644 router/.settings/org.eclipse.core.resources.prefs delete mode 100644 router/.settings/org.eclipse.jdt.core.prefs delete mode 100644 router/bin/service.sh delete mode 100644 router/package.xml delete mode 100644 router/pom.xml delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/Proxy.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/FdnCommunicationServer.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/Main0Insecure.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/Main0Stream.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/Main1.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/Main2.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/Main3.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/event/listener/PipeHandleNotificationEventListener.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/event/model/PipeHandleNotificationEvent.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/factory/DefaultPipeFactory.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/factory/EventFactory.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcServerFactory.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcStreamObserverFactory.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcStubFactory.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/factory/LocalBeanFactory.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/factory/PipeFactory.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/factory/QueueFactory.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientPullResponseStreamObserver.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientPushResponseStreamObserver.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientUnaryCallResponseStreamObserver.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ServerPushRequestStreamObserver.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/DataTransferPipedServerImpl.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/RouteServerImpl.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/helper/ModelValidationHelper.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/infra/Cache.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/infra/Pipe.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/infra/ResultCallback.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/BasePipe.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/InputStreamOutputStreamNoStoragePipe.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/InputStreamToPacketUnidirectionalPipe.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketQueuePipe.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketQueueSingleResultPipe.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketToOutputStreamUnidirectionalPipe.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/SingleResultCallback.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/manager/ExecutorManager.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/manager/ServerConfManager.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/manager/StatsManager.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/model/PipeHandlerInfo.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/model/ServerConf.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/model/StreamStat.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/security/SimpleTrustAllCertsManagerFactory.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/security/TrustAllCertsManager.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/service/CascadedCaller.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/service/ConfFileBasedFdnRouter.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/service/FdnRouter.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/util/CommandLineOptions.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/util/ErrorUtils.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/util/PipeUtils.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/util/Timeouts.java delete mode 100644 router/src/main/java/com/webank/ai/fate/networking/proxy/util/ToStringUtils.java delete mode 100644 router/src/main/java/utils/Constants.java delete mode 100644 router/src/main/java/utils/Utils.java delete mode 100644 router/src/main/resources/applicationContext-proxy.xml delete mode 100644 router/src/main/resources/auth_config.json delete mode 100644 router/src/main/resources/log4j2.properties delete mode 100644 router/src/main/resources/proxy.properties delete mode 100644 router/src/main/resources/route_tables/route_table.json delete mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/Dict.java delete mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ErrorMessageUtil.java diff --git a/fate-serving-core/pom.xml b/fate-serving-core/pom.xml index 15567d7d..6dca2ad7 100644 --- a/fate-serving-core/pom.xml +++ b/fate-serving-core/pom.xml @@ -80,6 +80,11 @@ metrics-core 4.1.0-rc2
    + + com.alibaba.csp + sentinel-core + RELEASE + diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java index a6dae336..285f9fb6 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java @@ -157,4 +157,23 @@ public class Dict { public static final String MD5_SALT = "$1$ML"; + public static final String CASE_ID="caseid"; + public static final String CODE ="code"; + public static final String MESSAGE ="message"; + public static final String MODEL_ID = "modelId"; + public static final String MODEL_VERSION = "modelVersion"; + public static final String APP_ID = "appid"; + public static final String PARTY_ID = "partyId"; + public static final String FEATURE_DATA = "featureData"; + + public static final String DEFAULT_VERSION = "1.0"; + public static final String SELF_PROJECT_NAME = "proxy"; + public static final String SELF_ENVIRONMENT = "online"; + public static final String HEAD = "head"; + public static final String BODY = "body"; + public static final String SERVICENAME_INFERENCE = "inference"; + public static final String SERVICENAME_START_INFERENCE_JOB = "startInferenceJob"; + public static final String SERVICENAME_GET_INFERENCE_RESULT = "getInferenceResult"; + + } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/AbstractServiceAdaptor.java similarity index 76% rename from serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java rename to fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/AbstractServiceAdaptor.java index 571076a7..ed10988d 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/AbstractServiceAdaptor.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/AbstractServiceAdaptor.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.proxy.rpc.core; +package com.webank.ai.fate.serving.core.rpc.core; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; @@ -7,13 +7,12 @@ import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; import com.google.common.collect.Lists; -import com.google.common.collect.Maps; import com.webank.ai.fate.serving.core.bean.Context; +import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.core.exceptions.ErrorCode; import com.webank.ai.fate.serving.core.exceptions.ShowDownRejectException; -import com.webank.ai.fate.serving.core.rpc.core.*; -import com.webank.ai.fate.serving.proxy.common.ErrorMessageUtil; -import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcConnectionPool; + + import io.grpc.stub.AbstractStub; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,11 +20,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; -import static com.webank.ai.fate.serving.proxy.common.Dict.CODE; -import static com.webank.ai.fate.serving.proxy.common.Dict.MESSAGE; + /** * @Description 默认的服务适配器 @@ -39,11 +36,8 @@ public abstract class AbstractServiceAdaptor implements ServiceAdaptor Logger logger = LoggerFactory.getLogger( this.getClass().getName()); + public AbstractServiceAdaptor(){ - public AbstractServiceAdaptor(){ - /** - * - */ } @@ -56,22 +50,11 @@ public void addPreProcessor(Interceptor interceptor){ public void addPostProcessor(Interceptor interceptor){ postChain.addInterceptor(interceptor); }; - /** - * 处理中的request,用于优雅停机 - */ + static public AtomicInteger requestInHandle = new AtomicInteger(0); public static boolean isOpen=true; - - public GrpcConnectionPool getGrpcConnectionPool() { - return grpcConnectionPool; - } - - public void setGrpcConnectionPool(GrpcConnectionPool grpcConnectionPool) { - this.grpcConnectionPool = grpcConnectionPool; - } - public ServiceAdaptor getServiceAdaptor() { return serviceAdaptor; } @@ -80,18 +63,11 @@ public void setServiceAdaptor(ServiceAdaptor serviceAdaptor) { this.serviceAdaptor = serviceAdaptor; } - GrpcConnectionPool grpcConnectionPool; ServiceAdaptor serviceAdaptor; - /** - * 处理逻辑前调用链 - */ InterceptorChain preChain = new DefaultInterceptorChain(); - /** - * 处理逻辑后调用链 - */ InterceptorChain postChain = new DefaultInterceptorChain(); public AbstractStub getServiceStub() { @@ -114,8 +90,6 @@ public void setServiceName(String serviceName) { String serviceName; - - public abstract resp doService(Context context, InboundPackage data, OutboundPackage outboundPackage) ; /** @@ -133,9 +107,6 @@ public OutboundPackage service(Context context , InboundPackage data List exceptions = Lists.newArrayList(); context.setReturnCode("0"); if(!isOpen){ - /** - * 系统关闭中 ,直接返回拒绝,关闭线程将等待所有处理完成 - */ return this.serviceFailInner(context,data,new ShowDownRejectException()); } @@ -145,9 +116,7 @@ public OutboundPackage service(Context context , InboundPackage data resp result=null; context.setServiceName(this.serviceName); - /** - * preChain 不要放在try中,因为多数是校验逻辑, 抛错就直接中断流程 - */ + preChain.doPreProcess(context,data,outboundPackage); try { @@ -199,13 +168,13 @@ public OutboundPackage service(Context context , InboundPackage data } - private OutboundPackage serviceFailInner(Context context, InboundPackage data, Throwable e) throws Exception{ + protected OutboundPackage serviceFailInner(Context context, InboundPackage data, Throwable e) throws Exception{ Map result = new HashMap(); OutboundPackage outboundPackage = new OutboundPackage(); - result.put(MESSAGE, e.getMessage()); + result.put(Dict.MESSAGE, e.getMessage()); ErrorMessageUtil.handleException(result,e); - context.setReturnCode(result.get(CODE)!=null?result.get(CODE).toString(): ErrorCode.SYSTEM_ERROR.toString()); + context.setReturnCode(result.get(Dict.CODE)!=null?result.get(Dict.CODE).toString(): ErrorCode.SYSTEM_ERROR.toString()); resp rsp = transformErrorMap(context ,result); outboundPackage.setData(rsp); return outboundPackage; @@ -228,16 +197,7 @@ private String objectToJson(Object obj) { } - public static void main(String[] args){ - while(true) { - try (Entry entry = SphU.entry("mytest")) { - - } catch (BlockException e) { - e.printStackTrace(); - } - } - } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/ErrorMessageUtil.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/ErrorMessageUtil.java new file mode 100644 index 00000000..875146aa --- /dev/null +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/ErrorMessageUtil.java @@ -0,0 +1,61 @@ +package com.webank.ai.fate.serving.core.rpc.core; + +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.webank.ai.fate.serving.core.bean.Dict; +import com.webank.ai.fate.serving.core.exceptions.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + + + +/** + * @Description TODO + * @Author + **/ +public class ErrorMessageUtil { + + static Logger logger = LoggerFactory.getLogger(ErrorMessageUtil.class); + + public static Map handleException(Map result,Throwable e){ + + if (e instanceof IllegalArgumentException) { + result.put(Dict.CODE, ErrorCode.PARAM_ERROR); + result.put(Dict.MESSAGE,"PARAM_ERROR"); + } + else if(e instanceof NoRouteInfoException){ + result.put(Dict.CODE, ErrorCode.ROUTER_ERROR); + result.put(Dict.MESSAGE, "ROUTER_ERROR"); + } else if (e instanceof SysException) { + result.put(Dict.CODE, ErrorCode.SYSTEM_ERROR); + result.put(Dict.MESSAGE, "SYSTEM_ERROR"); + + + } else if (e instanceof BlockException) { + result.put(Dict.CODE, ErrorCode.LIMIT_ERROR); + + result.put(Dict.MESSAGE, "OVERLOAD"); + + } else if (e instanceof InvalidRoleInfoException) { + result.put(Dict.CODE, ErrorCode.ROLE_ERROR); + result.put(Dict.MESSAGE, "ROLE_ERROR"); + } else if (e instanceof ShowDownRejectException){ + result.put(Dict.CODE, ErrorCode.SHUTDOWN_ERROR); + result.put(Dict.MESSAGE, "SHUTDOWN_ERROR"); + + } + else if (e instanceof NoResultException) { + logger.error("NET_ERROR ",e); + result.put(Dict.CODE, ErrorCode.NET_ERROR); + result.put(Dict.MESSAGE, "NET_ERROR"); + } else { + logger.error("SYSTEM_ERROR ",e); + result.put(Dict.CODE, ErrorCode.SYSTEM_ERROR); + result.put(Dict.MESSAGE, "SYSTEM_ERROR"); + } + + return result; + + } +} diff --git a/lib/eggroll-core-1.1.jar b/lib/eggroll-core-1.1.jar deleted file mode 100644 index 634f7adb01c7ab90e24b07f599a1b656fecd179c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 878520 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%0A-#vKIC$+h{n-UOj_7c$%Fqz44%MjrlG@l# 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®KQ&Q&FSe0l17cUD5aL(3MtspevL;a(B*m{HumR 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

    $U|ZGc*b3uxh8peT1)yFNh}e` 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 `WtFEi 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

    Rl$Po}=Er~&CM6e_fDPi=>3_}}p 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 z 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 fate-serving-core register - router serving-proxy fate-metrics-api fate-metrics-micrometer diff --git a/router/.settings/org.eclipse.core.resources.prefs b/router/.settings/org.eclipse.core.resources.prefs deleted file mode 100644 index 839d647e..00000000 --- a/router/.settings/org.eclipse.core.resources.prefs +++ /dev/null @@ -1,5 +0,0 @@ -eclipse.preferences.version=1 -encoding//src/main/java=UTF-8 -encoding//src/main/resources=UTF-8 -encoding//src/test/java=UTF-8 -encoding/=UTF-8 diff --git a/router/.settings/org.eclipse.jdt.core.prefs b/router/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 714351ae..00000000 --- a/router/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,5 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 -org.eclipse.jdt.core.compiler.compliance=1.8 -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=1.8 diff --git a/router/bin/service.sh b/router/bin/service.sh deleted file mode 100644 index df673008..00000000 --- a/router/bin/service.sh +++ /dev/null @@ -1,120 +0,0 @@ -#!/bin/bash - -# -# Copyright 2019 The FATE Authors. All Rights Reserved. -# -# 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. -# -basepath=$(cd `dirname $0`;pwd) -#export JAVA_HOME=/data/projects/common/jdk/jdk1.8.0_192 -#export PATH=$PATH:$JAVA_HOME/bin -configpath=$(cd $basepath/conf;pwd) - - -module=serving-router -main_class=com.webank.ai.fate.networking.Proxy -module_version=1.2.0 - -getpid() { - # pid=`ps aux | grep ${module} | grep -v grep | awk '{print $2}'` - - if [ ! -e "./${module}_pid" ];then - touch ./${module}_pid - echo "" >./${module}_pid - fi - pid=`cat ./${module}_pid` - if [[ -n ${pid} ]]; then - return 1 - else - return 0 - fi -} - -mklogsdir() { - if [[ ! -d "logs" ]]; then - mkdir logs - fi -} - -status() { - getpid - if [[ -n ${pid} ]]; then - echo "status: - `ps aux | grep ${pid} | grep -v grep`" - exit 1 - else - echo "service not running" - exit 0 - fi -} - -start() { - getpid - if [[ $? -eq 0 ]]; then - mklogsdir - if [[ ! -e "fate-${module}.jar" ]]; then - ln -s fate-${module}-${module_version}.jar fate-${module}.jar - fi - java -DauthFile=${configpath}/auth_config.json -Drouter_file=${configpath}/route_table.json -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/proxy.properties >> logs/console.log 2>>logs/error.log & - if [[ $? -eq 0 ]]; then - sleep 2 - echo $!>./${module}_pid - getpid - echo "service start sucessfully. pid: ${pid}" - else - echo "service start failed" - fi - else - echo "service already started. pid: ${pid}" - fi -} - -stop() { - getpid - if [[ -n ${pid} ]]; then - echo "killing: - `ps aux | grep ${pid} | grep -v grep`" - kill -9 ${pid} - if [[ $? -eq 0 ]]; then - rm -rf ./${module}_pid - echo "killed" - else - echo "kill error" - fi - else - echo "service not running" - fi -} - -case "$1" in - start) - start - status - ;; - - stop) - stop - ;; - status) - status - ;; - - restart) - stop - start - status - ;; - *) - echo "usage: $0 {start|stop|status|restart}" - exit -1 -esac \ No newline at end of file diff --git a/router/package.xml b/router/package.xml deleted file mode 100644 index ca7a6cce..00000000 --- a/router/package.xml +++ /dev/null @@ -1,62 +0,0 @@ - - release - - - - zip - - - - - false - - - - / - target - - *.jar - - - - - - - /lib - target/lib - - *.jar - - - - - / - bin - - * - - - - - - /conf - src/main/resources - - proxy.properties - log4j2.properties - applicationContext-proxy.xml - auth_config.json - - - - - /conf - src/main/resources/route_tables - - route_table.json - - - - - \ No newline at end of file diff --git a/router/pom.xml b/router/pom.xml deleted file mode 100644 index a7c5f1af..00000000 --- a/router/pom.xml +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - fate-serving-router - - - - - fate-serving - com.webank.ai.fate - ${fate.version} - - - jar - 4.0.0 - - - fate-serving-router - - - - - com.webank.ai.fate - fate-register - ${project.version} - - - - - - org.springframework.boot - spring-boot-starter - 2.0.4.RELEASE - - - org.springframework.boot - spring-boot-starter-logging - - - - - - com.webank.ai.fate - fate-serving-core - ${project.version} - - - - - - - - - - org.xolstice.maven.plugins - protobuf-maven-plugin - ${protobuf-maven-plugin.version} - - com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} - - grpc-java - io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} - - ${basedir}/../../proto - - - - - compile - compile-custom - - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 2.2.1 - - - package.xml - - - - - make-assembly - package - - single - - - - - - - - - \ No newline at end of file diff --git a/router/src/main/java/com/webank/ai/fate/networking/Proxy.java b/router/src/main/java/com/webank/ai/fate/networking/Proxy.java deleted file mode 100644 index cd85e4a1..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/Proxy.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking; - -import com.google.common.collect.Sets; -import com.webank.ai.fate.networking.proxy.factory.GrpcServerFactory; -import com.webank.ai.fate.networking.proxy.factory.LocalBeanFactory; -import com.webank.ai.fate.networking.proxy.grpc.client.DataTransferPipedClient; -import com.webank.ai.fate.networking.proxy.manager.ServerConfManager; -import com.webank.ai.fate.networking.proxy.model.ServerConf; -import com.webank.ai.fate.register.provider.FateServer; -import com.webank.ai.fate.register.router.DefaultRouterService; -import com.webank.ai.fate.register.url.URL; -import com.webank.ai.fate.register.zookeeper.ZookeeperRegistry; -import com.webank.ai.fate.serving.core.bean.Dict; -import io.grpc.Server; -import org.apache.commons.cli.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; - -import java.util.Properties; -import java.util.Set; - -public class Proxy { - - private static final Logger logger = LoggerFactory.getLogger(Proxy.class); - - public static ZookeeperRegistry zookeeperRegistry; - - private static boolean useRegister = false; - - private static boolean useZkRouter = false; - - public static void main(String[] args) throws Exception { - Options options = new Options(); - Option config = Option.builder("c") - .argName("file") - .longOpt("config") - .hasArg() - .numberOfArgs(1) - .required() - .desc("configuration file") - .build(); - - options.addOption(config); - - CommandLineParser parser = new DefaultParser(); - CommandLine cmd = parser.parse(options, args); - - String confFilePath = cmd.getOptionValue("c"); - ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-proxy.xml"); - LocalBeanFactory localBeanFactory = context.getBean(LocalBeanFactory.class); - localBeanFactory.setApplicationContext(context); - GrpcServerFactory serverFactory = context.getBean(GrpcServerFactory.class); - Server server = serverFactory.createServer(confFilePath); - ServerConfManager serverConfManager = context.getBean(ServerConfManager.class); - ServerConf serverConf = serverConfManager.getServerConf(); - logger.info("Server started listening on port: {}", serverConf.getPort()); - logger.info("server conf: {}", serverConf); - server.start(); - Properties properties = serverConf.getProperties(); - - System.setProperty(Dict.ACL_ENABLE, properties.getProperty(Dict.ACL_ENABLE, "")); - System.setProperty(Dict.ACL_USERNAME, properties.getProperty(Dict.ACL_USERNAME, "")); - System.setProperty(Dict.ACL_PASSWORD, properties.getProperty(Dict.ACL_PASSWORD, "")); - - useRegister = Boolean.valueOf(properties.getProperty("useRegister", "false")); - useZkRouter = Boolean.valueOf(properties.getProperty("useZkRouter", "false")); - if (useRegister) { - String zkUrl = properties.getProperty("zk.url"); - zookeeperRegistry = ZookeeperRegistry.getRegistery(zkUrl, "proxy", "online", serverConf.getPort()); - zookeeperRegistry.register(FateServer.serviceSets); - zookeeperRegistry.subProject("serving"); - - if(useZkRouter){ - - DefaultRouterService routerService = new DefaultRouterService(); - - routerService.setRegistry(zookeeperRegistry); - - DataTransferPipedClient.routerService = routerService; - - } - - } - - - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - // Use stderr here since the logger may have been reset by its JVM shutdown hook. - System.err.println("*** shutting down gRPC server since JVM is shutting down"); - if (server != null && useRegister) { - Set registered = zookeeperRegistry.getRegistered(); - Set urls = Sets.newHashSet(); - urls.addAll(registered); - urls.forEach(url -> { - logger.info("unregister {}", url); - zookeeperRegistry.unregister(url); - }); - zookeeperRegistry.destroy(); - try { - Thread.sleep(5000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - server.shutdown(); - System.err.println("*** server shut down"); - })); - - server.awaitTermination(); - } - - -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/FdnCommunicationServer.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/FdnCommunicationServer.java deleted file mode 100644 index 0b8d95b8..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/FdnCommunicationServer.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy; - -/* - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication - -*/ - -public class FdnCommunicationServer { -/* - public static void main(String[] args) { - SpringApplication.run(FdnCommunicationServer.class, args); - }*/ -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/Main0Insecure.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/Main0Insecure.java deleted file mode 100644 index f83e0a1b..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/Main0Insecure.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy; - -import com.webank.ai.fate.api.core.BasicMeta; -import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.networking.proxy.factory.DefaultPipeFactory; -import com.webank.ai.fate.networking.proxy.factory.LocalBeanFactory; -import com.webank.ai.fate.networking.proxy.factory.PipeFactory; -import com.webank.ai.fate.networking.proxy.grpc.service.DataTransferPipedServerImpl; -import com.webank.ai.fate.networking.proxy.infra.Pipe; -import com.webank.ai.fate.networking.proxy.service.ConfFileBasedFdnRouter; -import com.webank.ai.fate.networking.proxy.service.FdnRouter; -import io.grpc.Server; -import io.grpc.ServerBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.InputStream; -import java.io.OutputStream; - - -public class Main0Insecure { - private static final Logger logger = LoggerFactory.getLogger(Main0Insecure.class); - - public static void main(String[] args) throws Exception { - - ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-proxy.xml"); - LocalBeanFactory localBeanFactory = context.getBean(LocalBeanFactory.class); - localBeanFactory.setApplicationContext(context); - - // DataTransferServiceImpl dataTransferService = context.getBean(DataTransferServiceImpl.class); - - DataTransferPipedServerImpl dataTransferPipedServer = - context.getBean(DataTransferPipedServerImpl.class); - - String routeTableFile = "src/main/resources/route_tables/route_table2.json"; - FdnRouter fdnRouter = context.getBean(ConfFileBasedFdnRouter.class); - fdnRouter.setRouteTable(routeTableFile); - - PipeFactory pipeFactory = context.getBean(DefaultPipeFactory.class); - - InputStream is = new ByteArrayInputStream("hello world from main".getBytes()); - OutputStream os = System.out; - - - BasicMeta.Endpoint endpoint = BasicMeta.Endpoint.newBuilder().setIp("127.0.0.1").setPort(8888).build(); - Proxy.Metadata header = Proxy.Metadata.newBuilder() - .setTask(Proxy.Task.newBuilder().setTaskId("123")) - //.setDst(endpoint) - // .setSrc(endpoint) - .setOperator("operator") - .build(); - - Pipe pipe = - ((DefaultPipeFactory) pipeFactory).createInputStreamOutputStreamNoStoragePipe(is, os, header); - - dataTransferPipedServer.setPipeFactory(pipeFactory); - - int port = 8888; - - File crt = new File("src/main/resources/server.crt"); - File key = new File("src/main/resources/server-private.pem"); - - System.out.println(crt.getAbsolutePath()); - Server server = ServerBuilder - .forPort(port) - // .useTransportSecurity(crt, key) - .addService(dataTransferPipedServer) - .build() - .start(); - - logger.info("Server started listening on port: {}", port); - - - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - // Use stderr here since the logger may have been reset by its JVM shutdown hook. - System.err.println("*** shutting down gRPC server since JVM is shutting down"); - this.stop(); - System.err.println("*** server shut down"); - } - }); - - server.awaitTermination(); - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/Main0Stream.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/Main0Stream.java deleted file mode 100644 index d3e174ca..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/Main0Stream.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy; - -import com.webank.ai.fate.api.core.BasicMeta; -import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.networking.proxy.factory.DefaultPipeFactory; -import com.webank.ai.fate.networking.proxy.factory.LocalBeanFactory; -import com.webank.ai.fate.networking.proxy.factory.PipeFactory; -import com.webank.ai.fate.networking.proxy.grpc.service.DataTransferPipedServerImpl; -import com.webank.ai.fate.networking.proxy.infra.Pipe; -import com.webank.ai.fate.networking.proxy.service.ConfFileBasedFdnRouter; -import com.webank.ai.fate.networking.proxy.service.FdnRouter; -import io.grpc.Server; -import io.grpc.ServerBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; - -import java.io.*; - - -public class Main0Stream { - private static final Logger logger = LoggerFactory.getLogger(Main0Stream.class); - - public static void main(String[] args) throws Exception { - - ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-proxy.xml"); - LocalBeanFactory localBeanFactory = context.getBean(LocalBeanFactory.class); - - localBeanFactory.setApplicationContext(context); - - - BasicMeta.Endpoint endpoint = BasicMeta.Endpoint.newBuilder().setIp("127.0.0.1").setPort(8888).build(); - Proxy.Metadata header = Proxy.Metadata.newBuilder() - .setTask(Proxy.Task.newBuilder().setTaskId("123")) - // .setDst(endpoint) - // .setSrc(endpoint) - .setOperator("operator") - .build(); - - // DataTransferServiceImpl dataTransferService = context.getBean(DataTransferServiceImpl.class); - - DataTransferPipedServerImpl dataTransferPipedServer = - context.getBean(DataTransferPipedServerImpl.class); - - String routeTableFile = "src/main/resources/route_tables/route_table1.json"; - FdnRouter fdnRouter = context.getBean(ConfFileBasedFdnRouter.class); - fdnRouter.setRouteTable(routeTableFile); - - InputStream is = new FileInputStream("/Users/max-webank/Downloads/software/StarUML-3.0.1.dmg"); - OutputStream os = new FileOutputStream("/tmp/testout"); - - PipeFactory pipeFactory = context.getBean(DefaultPipeFactory.class); - Pipe pipe = - ((DefaultPipeFactory) pipeFactory).createInputStreamOutputStreamNoStoragePipe(is, os, header); - - // dataTransferPipedServer.setPipeFactory(pipeFactory); - dataTransferPipedServer.setDefaultPipe(pipe); - - int port = 8888; - - // File crt = new File("src/main/resources/certs/server.crt"); - // File key = new File("src/main/resources/certs/server-private.pem"); - - File crt = new File("/Users/max-webank/Documents/zmodem/127.0.0.1.crt"); - File key = new File("/Users/max-webank/Documents/zmodem/127.0.0.1-private.pem"); - - System.out.println(crt.getAbsolutePath()); - Server server = ServerBuilder - .forPort(port) - .useTransportSecurity(crt, key) - .addService(dataTransferPipedServer) - .build() - .start(); - - logger.info("Server started listening on port: {}", port); - - - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - // Use stderr here since the logger may have been reset by its JVM shutdown hook. - System.err.println("*** shutting down gRPC server since JVM is shutting down"); - this.stop(); - System.err.println("*** server shut down"); - } - }); - - server.awaitTermination(); - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/Main1.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/Main1.java deleted file mode 100644 index 34f4de7a..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/Main1.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy; - -import com.webank.ai.fate.networking.proxy.factory.GrpcServerFactory; -import com.webank.ai.fate.networking.proxy.factory.LocalBeanFactory; -import com.webank.ai.fate.networking.proxy.model.ServerConf; -import io.grpc.Server; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; - -public class Main1 { - private static final Logger logger = LoggerFactory.getLogger(Main1.class); - - public static void main(String[] args) throws Exception { - ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-proxy.xml"); - LocalBeanFactory localBeanFactory = context.getBean(LocalBeanFactory.class); - localBeanFactory.setApplicationContext(context); - GrpcServerFactory serverFactory = context.getBean(GrpcServerFactory.class); - - int port = 8888; - - ServerConf serverConf = context.getBean(ServerConf.class); - - serverConf.setCoordinator("10000"); - serverConf.setSecureServer(false); - serverConf.setSecureClient(true); -/* - - serverConf.setServerKeyPath("/Users/max-webank/Documents/zmodem/server.key"); - serverConf.setServerCrtPath("/Users/max-webank/Documents/zmodem/server.crt"); -*/ - - serverConf.setRouteTablePath("src/main/resources/route_tables/route_table1.json"); - serverConf.setPort(port); - - logger.info("Server started listening on port: {}", port); - - Server server = serverFactory.createServer(serverConf); - - logger.info("server conf: {}", serverConf); - - server.start(); - server.awaitTermination(); - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/Main2.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/Main2.java deleted file mode 100644 index 261e4579..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/Main2.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy; - -import com.webank.ai.fate.networking.proxy.factory.GrpcServerFactory; -import com.webank.ai.fate.networking.proxy.factory.LocalBeanFactory; -import com.webank.ai.fate.networking.proxy.model.ServerConf; -import io.grpc.Server; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; - -public class Main2 { - private static final Logger logger = LoggerFactory.getLogger(Main2.class); - - public static void main(String[] args) throws Exception { - ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-proxy.xml"); - LocalBeanFactory localBeanFactory = context.getBean(LocalBeanFactory.class); - localBeanFactory.setApplicationContext(context); - GrpcServerFactory serverFactory = context.getBean(GrpcServerFactory.class); - - int port = 9000; - - ServerConf serverConf = context.getBean(ServerConf.class); - - serverConf.setSecureServer(true); - serverConf.setServerCrtPath("src/main/resources/certs/server.crt"); - serverConf.setServerKeyPath("src/main/resources/certs/server-private.pem"); - - serverConf.setSecureClient(true); - - serverConf.setRouteTablePath("src/main/resources/route_tables/route_table3.json"); - serverConf.setPort(port); - serverConf.setCoordinator("webank"); - - logger.info("Server started listening on port: {}", port); - - Server server = serverFactory.createServer(serverConf); - - logger.info("server conf: {}", serverConf); - - server.start(); - server.awaitTermination(); - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/Main3.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/Main3.java deleted file mode 100644 index 5433df03..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/Main3.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy; - -import com.webank.ai.fate.api.core.BasicMeta; -import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.networking.proxy.factory.DefaultPipeFactory; -import com.webank.ai.fate.networking.proxy.factory.GrpcServerFactory; -import com.webank.ai.fate.networking.proxy.factory.LocalBeanFactory; -import com.webank.ai.fate.networking.proxy.factory.PipeFactory; -import com.webank.ai.fate.networking.proxy.infra.Pipe; -import com.webank.ai.fate.networking.proxy.model.ServerConf; -import io.grpc.Server; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; - -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.OutputStream; - -public class Main3 { - private static final Logger logger = LoggerFactory.getLogger(Main3.class); - - public static void main(String[] args) throws Exception { - ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-proxy.xml"); - LocalBeanFactory localBeanFactory = context.getBean(LocalBeanFactory.class); - localBeanFactory.setApplicationContext(context); - GrpcServerFactory serverFactory = context.getBean(GrpcServerFactory.class); - - int port = 9999; - - ServerConf serverConf = context.getBean(ServerConf.class); - - serverConf.setSecureServer(true); - serverConf.setSecureClient(true); - serverConf.setRouteTablePath("src/main/resources/route_tables/route_table3.json"); - serverConf.setPort(port); - - InputStream is = new FileInputStream("test.txt"); - OutputStream os = new FileOutputStream("/tmp/testout"); - // is = new ByteArrayInputStream("world".getBytes()); - - BasicMeta.Endpoint endpoint = BasicMeta.Endpoint.newBuilder().setIp("127.0.0.1").setPort(8888).build(); - Proxy.Metadata header = Proxy.Metadata.newBuilder() - .setTask(Proxy.Task.newBuilder().setTaskId("123")) - //.setDst(endpoint) - //.setSrc(endpoint) - .setOperator("operator") - .build(); - - PipeFactory pipeFactory = context.getBean(DefaultPipeFactory.class); - Pipe pipe = - ((DefaultPipeFactory) pipeFactory).createInputStreamOutputStreamNoStoragePipe(is, os, header); - serverConf.setPipe(pipe); - - logger.info("Server started listening on port: {}", port); - - Server server = serverFactory.createServer(serverConf); - - logger.info("server conf: {}", serverConf); - - server.start(); - server.awaitTermination(); - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/event/listener/PipeHandleNotificationEventListener.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/event/listener/PipeHandleNotificationEventListener.java deleted file mode 100644 index 748e2544..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/event/listener/PipeHandleNotificationEventListener.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.event.listener; - -import com.webank.ai.fate.networking.proxy.event.model.PipeHandleNotificationEvent; -import com.webank.ai.fate.networking.proxy.infra.Pipe; -import com.webank.ai.fate.networking.proxy.infra.impl.PacketQueuePipe; -import com.webank.ai.fate.networking.proxy.model.PipeHandlerInfo; -import com.webank.ai.fate.networking.proxy.service.CascadedCaller; -import com.webank.ai.fate.networking.proxy.util.ToStringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationListener; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -@Component -@Scope("prototype") -public class PipeHandleNotificationEventListener implements ApplicationListener { - private static final Logger logger = LoggerFactory.getLogger(PipeHandleNotificationEventListener.class); - @Autowired - private ApplicationContext applicationContext; - @Autowired - private ToStringUtils toStringUtils; - - @Override - public void onApplicationEvent(PipeHandleNotificationEvent pipeHandleNotificationEvent) { - // logger.warn("event listened: {}", pipeHandleNotificationEvent.getPipeHandlerInfo()); - logger.info("event metadata: {}", toStringUtils.toOneLineString(pipeHandleNotificationEvent.getPipeHandlerInfo().getMetadata())); - - PipeHandlerInfo pipeHandlerInfo = pipeHandleNotificationEvent.getPipeHandlerInfo(); - Pipe pipe = pipeHandlerInfo.getPipe(); - - if (pipe instanceof PacketQueuePipe) { - CascadedCaller cascadedCaller = applicationContext.getBean(CascadedCaller.class, pipeHandlerInfo); - cascadedCaller.run(); - } - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/event/model/PipeHandleNotificationEvent.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/event/model/PipeHandleNotificationEvent.java deleted file mode 100644 index 788e08a8..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/event/model/PipeHandleNotificationEvent.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.event.model; - -import com.webank.ai.fate.networking.proxy.model.PipeHandlerInfo; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -import javax.annotation.concurrent.Immutable; - -@Component -@Scope("prototype") -@Immutable -public class PipeHandleNotificationEvent extends ApplicationEvent { - private PipeHandlerInfo pipeHandlerInfo; - - public PipeHandleNotificationEvent(Object source, PipeHandlerInfo pipeHandlerInfo) { - super(source); - this.pipeHandlerInfo = pipeHandlerInfo; - } - - public PipeHandlerInfo getPipeHandlerInfo() { - return pipeHandlerInfo; - } - - public enum Type { - /** - * PUSH - */ - PUSH, - /** - * PULL - */ - PULL, - /** - * UNARY_CALL - */ - UNARY_CALL; - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/DefaultPipeFactory.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/DefaultPipeFactory.java deleted file mode 100644 index df207d10..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/DefaultPipeFactory.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.factory; - -import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.networking.proxy.infra.Pipe; -import com.webank.ai.fate.networking.proxy.infra.impl.*; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.io.InputStream; -import java.io.OutputStream; - -@Component("defaultPipeFactory") -public class DefaultPipeFactory implements PipeFactory { - @Autowired - private LocalBeanFactory localBeanFactory; - - public InputStreamOutputStreamNoStoragePipe createInputStreamOutputStreamNoStoragePipe(InputStream is, - OutputStream os, - Proxy.Metadata metadata) { - return (InputStreamOutputStreamNoStoragePipe) localBeanFactory - .getBean(InputStreamOutputStreamNoStoragePipe.class, is, os, metadata); - } - - public InputStreamToPacketUnidirectionalPipe createInputStreamToPacketUnidirectionalPipe(InputStream is, - Proxy.Metadata metadata) { - return (InputStreamToPacketUnidirectionalPipe) localBeanFactory - .getBean(InputStreamToPacketUnidirectionalPipe.class, is, metadata); - } - - public InputStreamToPacketUnidirectionalPipe createInputStreamToPacketUnidirectionalPipe(InputStream is, - Proxy.Metadata metadata, - int trunkSize) { - return (InputStreamToPacketUnidirectionalPipe) localBeanFactory - .getBean(InputStreamToPacketUnidirectionalPipe.class, is, metadata, trunkSize); - } - - public PacketToOutputStreamUnidirectionalPipe createPacketToOutputStreamUnidirectionalPipe(OutputStream os) { - return (PacketToOutputStreamUnidirectionalPipe) localBeanFactory - .getBean(PacketToOutputStreamUnidirectionalPipe.class, os); - } - - public PacketQueuePipe createPacketQueuePipe(Proxy.Metadata metadata) { - return (PacketQueuePipe) localBeanFactory.getBean(PacketQueuePipe.class, metadata); - } - - @Override - public Pipe create() { - return (PacketQueueSingleResultPipe) localBeanFactory.getBean(PacketQueueSingleResultPipe.class); - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/EventFactory.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/EventFactory.java deleted file mode 100644 index 0a5e6255..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/EventFactory.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.factory; - - -import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.networking.proxy.event.model.PipeHandleNotificationEvent; -import com.webank.ai.fate.networking.proxy.infra.Pipe; -import com.webank.ai.fate.networking.proxy.model.PipeHandlerInfo; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -@Component -public class EventFactory { - @Autowired - private LocalBeanFactory localBeanFactory; - - public PipeHandleNotificationEvent createPipeHandleNotificationEvent(Object source, - PipeHandleNotificationEvent.Type type, - Proxy.Metadata metadata, - Pipe pipe) { - PipeHandlerInfo pipeHandlerInfo = (PipeHandlerInfo) localBeanFactory - .getBean(PipeHandlerInfo.class, type, metadata, pipe); - - return (PipeHandleNotificationEvent) localBeanFactory - .getBean(PipeHandleNotificationEvent.class, source, pipeHandlerInfo); - } - - public PipeHandleNotificationEvent createPipeHandleNotificationEvent(Object source, - PipeHandleNotificationEvent.Type type, - Proxy.Packet packet, - Pipe pipe) { - PipeHandlerInfo pipeHandlerInfo = (PipeHandlerInfo) localBeanFactory - .getBean(PipeHandlerInfo.class, type, packet, pipe); - - return (PipeHandleNotificationEvent) localBeanFactory - .getBean(PipeHandleNotificationEvent.class, source, pipeHandlerInfo); - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcServerFactory.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcServerFactory.java deleted file mode 100644 index e327e7cb..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcServerFactory.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.factory; - -import com.google.common.net.InetAddresses; -import com.webank.ai.fate.networking.proxy.grpc.service.DataTransferPipedServerImpl; -import com.webank.ai.fate.networking.proxy.grpc.service.RouteServerImpl; -import com.webank.ai.fate.networking.proxy.manager.ServerConfManager; -import com.webank.ai.fate.networking.proxy.model.ServerConf; -import com.webank.ai.fate.networking.proxy.service.FdnRouter; -import com.webank.ai.fate.register.common.Constants; -import com.webank.ai.fate.register.provider.FateServerBuilder; -import io.grpc.Server; -import io.grpc.ServerBuilder; -import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts; -import io.grpc.netty.shaded.io.netty.handler.ssl.ClientAuth; -import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext; -import io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.core.config.ConfigurationSource; -import org.apache.logging.log4j.core.config.Configurator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.core.task.TaskExecutor; -import org.springframework.stereotype.Component; - -import javax.net.ssl.SSLException; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Properties; -import java.util.concurrent.TimeUnit; - - -@Component -public class GrpcServerFactory { - private static final Logger logger = LoggerFactory.getLogger(GrpcServerFactory.class); - @Autowired - private ApplicationContext applicationContext; - @Autowired - private ServerConfManager serverConfManager; - @Autowired - private FdnRouter fdnRouter; - @Autowired - private PipeFactory pipeFactory; - @Autowired - private DataTransferPipedServerImpl dataTransferPipedServer; - @Autowired - private RouteServerImpl routeServer; - - public Server createServer(ServerConf serverConf) { - this.serverConfManager.setServerConf(serverConf); - - String routeTablePath = serverConf.getRouteTablePath(); - - String routerFile=System.getProperty("router_file"); - - if(StringUtils.isNotEmpty(routerFile)) { - - fdnRouter.setRouteTable(routerFile); - }else{ - fdnRouter.setRouteTable(routeTablePath); - } - if (serverConf.getPipe() != null) { - dataTransferPipedServer.setDefaultPipe(serverConf.getPipe()); - } else if (serverConf.getPipeFactory() != null) { - dataTransferPipedServer.setPipeFactory(serverConf.getPipeFactory()); - } else { - dataTransferPipedServer.setPipeFactory(pipeFactory); - } - - // NettyServerBuilder serverBuilder = null; - FateServerBuilder serverBuilder = null; - if (StringUtils.isBlank(serverConf.getIp())) { - logger.info("server build on port only :{}", serverConf.getPort()); - // logger.warn("this may cause trouble in multiple network devices. you may want to consider binding to a ip"); - serverBuilder = (FateServerBuilder) ServerBuilder.forPort(serverConf.getPort()); - } else { - logger.info("server build on address {}:{}", serverConf.getIp(), serverConf.getPort()); - InetSocketAddress inetSocketAddress = new InetSocketAddress( - InetAddresses.forString(serverConf.getIp()), serverConf.getPort()); - - logger.info(inetSocketAddress.toString()); - SocketAddress addr = - new InetSocketAddress( - InetAddresses.forString(serverConf.getIp()), serverConf.getPort()); - - - serverBuilder = FateServerBuilder.forNettyServerBuilderAddress(addr); - - } - - - serverBuilder.addService(dataTransferPipedServer) - - .addService(routeServer) - .setZkRegisterLocation(serverConf.getData(Constants.ZOOKEEPER_REGISTER, null)) - .setApplication("proxy") - .maxConcurrentCallsPerConnection(20000) - .maxInboundMessageSize(32 << 20) - .flowControlWindow(32 << 20) - .keepAliveTime(6, TimeUnit.MINUTES) - .keepAliveTimeout(24, TimeUnit.HOURS) - .maxConnectionIdle(1, TimeUnit.HOURS) - .permitKeepAliveTime(1, TimeUnit.SECONDS) - .permitKeepAliveWithoutCalls(true) - .executor((TaskExecutor) applicationContext.getBean("grpcServiceExecutor")) - .maxConnectionAge(24, TimeUnit.HOURS) - .maxConnectionAgeGrace(24, TimeUnit.HOURS) - .setProject("proxy").setEnvironment("online"); - - - if (serverConf.isSecureServer()) { - String serverCrtPath = serverConf.getServerCrtPath(); - String serverKeyPath = serverConf.getServerKeyPath(); - String caCrtPath = serverConf.getCaCrtPath(); - - File serverCrt = new File(serverCrtPath); - File serverKey = new File(serverKeyPath); - File caCrt = new File(caCrtPath); - - try { - SslContextBuilder sslContextBuilder = GrpcSslContexts.forServer(serverCrt, serverKey) - .trustManager(caCrt); - - if (serverConf.isNeighbourInsecureChannelEnabled()) { - sslContextBuilder.clientAuth(ClientAuth.OPTIONAL); - } else { - sslContextBuilder.clientAuth(ClientAuth.REQUIRE); - } - - SslContext sslContext = sslContextBuilder - .sessionTimeout(3600 << 4) - .sessionCacheSize(65536) - .build(); - - serverBuilder.sslContext(sslContext); - } catch (SSLException e) { - throw new SecurityException(e); - } - - - logger.info("running in secure mode. server crt path: {}, server key path: {}, ca crt path: {}", - serverCrtPath, serverKeyPath, caCrtPath); - } else { - logger.info("running in insecure mode"); - } - - Server server = serverBuilder.build(); - - return server; - } - - private void stop() { - - } - - - public Server createServer(String confPath) throws IOException { - ServerConf serverConf = applicationContext.getBean(ServerConf.class); - Properties properties = new Properties(); - serverConf.setProperties(properties); - - Path absolutePath = Paths.get(confPath).toAbsolutePath(); - String finalConfPath = absolutePath.toString(); - - try (InputStream is = new FileInputStream(finalConfPath)) { - properties.load(is); - - String coordinator = properties.getProperty("coordinator", null); - if (coordinator == null) { - throw new IllegalArgumentException("coordinator cannot be null"); - } else { - serverConf.setCoordinator(coordinator); - } - - String ipString = properties.getProperty("ip", null); - serverConf.setIp(ipString); - - String portString = properties.getProperty("port", null); - if (portString == null) { - throw new IllegalArgumentException("port cannot be null"); - } else { - int port = Integer.valueOf(portString); - serverConf.setPort(port); - } - - String routeTablePath = properties.getProperty("route.table", null); - if (routeTablePath == null) { - throw new IllegalArgumentException("route table cannot be null"); - } else { - serverConf.setRouteTablePath(routeTablePath); - } - - String serverCrt = properties.getProperty("server.crt"); - String serverKey = properties.getProperty("server.key"); - - serverConf.setServerCrtPath(serverCrt); - serverConf.setServerKeyPath(serverKey); - - if (StringUtils.isBlank(serverCrt) && StringUtils.isBlank(serverKey)) { - serverConf.setSecureServer(false); - } else { - serverConf.setSecureServer(true); - } - - String caCrt = properties.getProperty("ca.crt"); - serverConf.setCaCrtPath(caCrt); - - if (StringUtils.isBlank(caCrt)) { - serverConf.setSecureClient(false); - } else { - serverConf.setSecureClient(true); - } - - String logPropertiesPath = properties.getProperty("log.properties"); - if (StringUtils.isNotBlank(logPropertiesPath)) { - File logConfFile = new File(logPropertiesPath); - if (logConfFile.exists() && logConfFile.isFile()) { - try { - ConfigurationSource configurationSource = - new ConfigurationSource(new FileInputStream(logConfFile), logConfFile); - Configurator.initialize(null, configurationSource); - - serverConf.setLogPropertiesPath(logPropertiesPath); - logger.info("using log conf file: {}", logPropertiesPath); - } catch (Exception e) { - logger.warn("failed to set log conf file at {}. using default conf", logPropertiesPath); - } - } - } - - String isAuditEnabled = properties.getProperty("audit.enabled"); - if (StringUtils.isNotBlank(isAuditEnabled) - && ("true".equals(isAuditEnabled.toLowerCase())) - || ("1".equals(isAuditEnabled))) { - serverConf.setAuditEnabled(true); - } else { - serverConf.setAuditEnabled(false); - } - - String isNeighbourInsecureChannelEnabled = properties.getProperty("neighbour.insecure.channel.enabled"); - if (StringUtils.isNotBlank(isNeighbourInsecureChannelEnabled) - && ("true".equals(isNeighbourInsecureChannelEnabled.toLowerCase())) - || ("1".equals(isNeighbourInsecureChannelEnabled))) { - serverConf.setNeighbourInsecureChannelEnabled(true); - } else { - serverConf.setNeighbourInsecureChannelEnabled(false); - } - - String isDebugEnabled = properties.getProperty("debug.enabled"); - if (StringUtils.isNotBlank(isDebugEnabled) - && ("true".equals(isDebugEnabled.toLowerCase())) - || ("1".equals(isDebugEnabled))) { - serverConf.setDebugEnabled(true); - } else { - serverConf.setDebugEnabled(false); - } - } catch (Exception e) { - logger.error(e.getMessage()); - throw e; - } - return createServer(serverConf); - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcStreamObserverFactory.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcStreamObserverFactory.java deleted file mode 100644 index 4caecfbe..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcStreamObserverFactory.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.factory; - -import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.networking.proxy.grpc.observer.ClientPullResponseStreamObserver; -import com.webank.ai.fate.networking.proxy.grpc.observer.ClientPushResponseStreamObserver; -import com.webank.ai.fate.networking.proxy.grpc.observer.ClientUnaryCallResponseStreamObserver; -import com.webank.ai.fate.networking.proxy.grpc.observer.ServerPushRequestStreamObserver; -import com.webank.ai.fate.networking.proxy.infra.Pipe; -import com.webank.ai.fate.networking.proxy.infra.ResultCallback; -import com.webank.ai.fate.networking.proxy.util.ToStringUtils; -import io.grpc.stub.StreamObserver; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.util.concurrent.CountDownLatch; - -@Component -public class GrpcStreamObserverFactory { - private static final Logger logger = LoggerFactory.getLogger(GrpcStreamObserverFactory.class); - @Autowired - private LocalBeanFactory localBeanFactory; - @Autowired - private ToStringUtils toStringUtils; - - public ClientPullResponseStreamObserver createClientPullResponseStreamObserver(Pipe pipe, - CountDownLatch finishLatch, - Proxy.Metadata metadata) { - return (ClientPullResponseStreamObserver) localBeanFactory - .getBean(ClientPullResponseStreamObserver.class, pipe, finishLatch, metadata); - } - - public ClientPushResponseStreamObserver createClientPushResponseStreamObserver(ResultCallback resultCallback, - CountDownLatch finishLatch) { - return (ClientPushResponseStreamObserver) localBeanFactory - .getBean(ClientPushResponseStreamObserver.class, resultCallback, finishLatch); - } - - public ServerPushRequestStreamObserver - createServerPushRequestStreamObserver(Pipe pipe, - StreamObserver responseObserver) { - return (ServerPushRequestStreamObserver) localBeanFactory - .getBean(ServerPushRequestStreamObserver.class, pipe, responseObserver); - } - - public ClientUnaryCallResponseStreamObserver createClientUnaryCallResponseStreamObserver(Pipe pipe, - CountDownLatch finishLatch, - Proxy.Metadata metadata) { - return (ClientUnaryCallResponseStreamObserver) localBeanFactory - .getBean(ClientUnaryCallResponseStreamObserver.class, pipe, finishLatch, metadata); - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcStubFactory.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcStubFactory.java deleted file mode 100644 index 4a2f363a..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/GrpcStubFactory.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.factory; - -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import com.webank.ai.fate.api.core.BasicMeta; -import com.webank.ai.fate.api.networking.proxy.DataTransferServiceGrpc; -import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.networking.proxy.model.ServerConf; -import com.webank.ai.fate.networking.proxy.security.SimpleTrustAllCertsManagerFactory; -import com.webank.ai.fate.networking.proxy.service.FdnRouter; -import com.webank.ai.fate.networking.proxy.util.ToStringUtils; -import io.grpc.ConnectivityState; -import io.grpc.ManagedChannel; -import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts; -import io.grpc.netty.shaded.io.grpc.netty.NegotiationType; -import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; -import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext; -import io.grpc.stub.AbstractStub; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.stereotype.Component; - -import javax.net.ssl.SSLException; -import java.io.File; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; - -@Component -public class GrpcStubFactory { - private static final Logger logger = LoggerFactory.getLogger(GrpcStubFactory.class); - @Autowired - private ApplicationContext applicationContext; - @Autowired - private SimpleTrustAllCertsManagerFactory trustManagerFactory; - @Autowired - private FdnRouter fdnRouter; - @Autowired - private ServerConf serverConf; - @Autowired - private ToStringUtils toStringUtils; - private LoadingCache channelCache; - - public GrpcStubFactory() { - channelCache = CacheBuilder.newBuilder() - .maximumSize(100) - .expireAfterWrite(20, TimeUnit.MINUTES) - .recordStats() - .weakValues() - .removalListener(removalNotification -> { - BasicMeta.Endpoint endpoint = (BasicMeta.Endpoint) removalNotification.getKey(); - ManagedChannel managedChannel = (ManagedChannel) removalNotification.getValue(); - - logger.info("Managed channel removed for ip: {}, port: {}, hostname: {}. reason: {}", - endpoint.getIp(), - endpoint.getPort(), - endpoint.getHostname(), - removalNotification.getCause()); - logger.info("Managed channel state: isShutdown: {}, isTerminated: {}", - managedChannel.isShutdown(), managedChannel.isTerminated()); - }) - .build(new CacheLoader() { - @Override - public ManagedChannel load(BasicMeta.Endpoint endpoint) throws Exception { - Preconditions.checkNotNull(endpoint); - logger.info("creating channel for endpoint: ip: {}, port: {}, hostname: {}", - endpoint.getIp(), endpoint.getPort(), endpoint.getHostname()); - return createChannel(endpoint); - } - } - ); - } - - public DataTransferServiceGrpc.DataTransferServiceBlockingStub getBlockingStub(Proxy.Topic topic) { - BasicMeta.Endpoint endpoint = fdnRouter.route(topic); - - return getBlockingStub(endpoint); - } - - public DataTransferServiceGrpc.DataTransferServiceBlockingStub getBlockingStub(BasicMeta.Endpoint endpoint) { - return (DataTransferServiceGrpc.DataTransferServiceBlockingStub) getStubBase( - endpoint, false); - } - - public DataTransferServiceGrpc.DataTransferServiceStub getAsyncStub(Proxy.Topic topic) { - BasicMeta.Endpoint endpoint = fdnRouter.route(topic); - - return getAsyncStub(endpoint); - } - - public DataTransferServiceGrpc.DataTransferServiceStub getAsyncStub(BasicMeta.Endpoint endpoint) { - return (DataTransferServiceGrpc.DataTransferServiceStub) getStubBase( - endpoint, true); - } - - // todo: use retry framework - private AbstractStub getStubBase(BasicMeta.Endpoint endpoint, boolean isAsync) { - ManagedChannel managedChannel = null; - - for (int i = 0; i < 10; ++i) { - try { - managedChannel = getChannel(endpoint); - if (managedChannel != null) { - break; - } - } catch (Exception e) { - logger.warn("get channel failed. target: {}, \n {}", - toStringUtils.toOneLineString(endpoint), ExceptionUtils.getStackTrace(e)); - try { - Thread.sleep(1000); - } catch (InterruptedException ignore) { - Thread.currentThread().interrupt(); - } - channelCache.invalidate(endpoint); - } - } - - if (managedChannel == null) { - throw new IllegalStateException("Error getting channel to " + toStringUtils.toOneLineString(endpoint)); - } - - AbstractStub result; - - if (isAsync) { - result = DataTransferServiceGrpc.newStub(managedChannel); - } else { - result = DataTransferServiceGrpc.newBlockingStub(managedChannel); - } - - return result; - } - - private ManagedChannel getChannel(BasicMeta.Endpoint endpoint) { - ManagedChannel result = null; - try { - result = channelCache.get(endpoint); - ConnectivityState state = result.getState(true); - logger.info("Managed channel state: isShutdown: {}, isTerminated: {}, state: [}", - result.isShutdown(), result.isTerminated(), state.name()); - - if (result.isShutdown() || result.isTerminated()) { - channelCache.invalidate(result); - result = channelCache.get(endpoint); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - - return result; - } - - private ManagedChannel createChannel(BasicMeta.Endpoint endpoint) { - String target = endpoint.getIp(); - if (Strings.isNullOrEmpty(target)) { - target = endpoint.getHostname(); - } - - NettyChannelBuilder builder = NettyChannelBuilder - .forAddress(target, endpoint.getPort()) - .executor((Executor) applicationContext.getBean("grpcClientExecutor")) - .keepAliveTime(6, TimeUnit.MINUTES) - .keepAliveTimeout(1, TimeUnit.HOURS) - .keepAliveWithoutCalls(true) - .idleTimeout(1, TimeUnit.HOURS) - .perRpcBufferLimit(16 << 20) - .flowControlWindow(32 << 20) - .maxInboundMessageSize(32 << 20) - .enableRetry() - .retryBufferSize(16 << 20) - .maxRetryAttempts(20); - - // if secure client defined and endpoint is not in intranet - if (serverConf.isSecureClient() && - (!serverConf.isNeighbourInsecureChannelEnabled() || !fdnRouter.isIntranet(endpoint))) { - // todo: add configuration reading mechanism - File caCrt = new File(serverConf.getCaCrtPath()); - File serverCrt = new File(serverConf.getServerCrtPath()); - File serverKey = new File(serverConf.getServerKeyPath()); - - SslContext sslContext = null; - try { - logger.info("use secure channel to {}", toStringUtils.toOneLineString(endpoint)); - // sslContext = GrpcSslContexts.forClient().trustManager(trustManagerFactory).build(); - sslContext = GrpcSslContexts.forClient() - .trustManager(caCrt) - .keyManager(serverCrt, serverKey) - .sessionTimeout(3600 << 4) - .sessionCacheSize(65536) - .build(); - } catch (SSLException e) { - throw new SecurityException(e); - } - builder.sslContext(sslContext).useTransportSecurity().negotiationType(NegotiationType.TLS); - } else { - logger.info("use insecure channel to {}", toStringUtils.toOneLineString(endpoint)); - builder.negotiationType(NegotiationType.PLAINTEXT); - } - - ManagedChannel managedChannel = builder - .build(); - - logger.info("created channel to {}", toStringUtils.toOneLineString(endpoint)); - return managedChannel; - } - -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/LocalBeanFactory.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/LocalBeanFactory.java deleted file mode 100644 index 56fa2771..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/LocalBeanFactory.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.factory; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.stereotype.Component; - -@Component -public class LocalBeanFactory { - @Autowired - private ApplicationContext applicationContext; - - public void setApplicationContext(ApplicationContext applicationContext) { - this.applicationContext = applicationContext; - } - - public Object getBean(Class clazz) { - return applicationContext.getBean(clazz); - } - - public Object getBean(Class clazz, Object... objects) { - return applicationContext.getBean(clazz, objects); - } - - public Object getAutowireCapableBeanFactory() { - return applicationContext.getAutowireCapableBeanFactory(); - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/PipeFactory.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/PipeFactory.java deleted file mode 100644 index 872b7ff8..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/PipeFactory.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.factory; - -import com.webank.ai.fate.networking.proxy.infra.Pipe; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -@Component -@Scope("prototype") -public interface PipeFactory { - public Pipe create(); -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/QueueFactory.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/QueueFactory.java deleted file mode 100644 index 02e31fdf..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/factory/QueueFactory.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.factory; - -import org.springframework.stereotype.Component; - -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.LinkedBlockingQueue; - -@Component -public class QueueFactory { - public ConcurrentLinkedQueue createConcurrentLinkedQueue() { - return new ConcurrentLinkedQueue(); - } - - public LinkedBlockingQueue createLinkedBlockingQueue(int capacity) { - return new LinkedBlockingQueue<>(capacity); - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java deleted file mode 100644 index f15dd8f1..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/client/DataTransferPipedClient.java +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.grpc.client; - -import com.alibaba.fastjson.JSON; -import com.google.common.base.Preconditions; -import com.webank.ai.fate.api.core.BasicMeta; -import com.webank.ai.fate.api.networking.proxy.DataTransferServiceGrpc; -import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.networking.proxy.factory.GrpcStreamObserverFactory; -import com.webank.ai.fate.networking.proxy.factory.GrpcStubFactory; -import com.webank.ai.fate.networking.proxy.infra.Pipe; -import com.webank.ai.fate.networking.proxy.infra.ResultCallback; -import com.webank.ai.fate.networking.proxy.infra.impl.PacketQueueSingleResultPipe; -import com.webank.ai.fate.networking.proxy.infra.impl.SingleResultCallback; -import com.webank.ai.fate.networking.proxy.model.ServerConf; -import com.webank.ai.fate.networking.proxy.service.ConfFileBasedFdnRouter; -import com.webank.ai.fate.networking.proxy.service.FdnRouter; -import com.webank.ai.fate.networking.proxy.util.ErrorUtils; -import com.webank.ai.fate.networking.proxy.util.ToStringUtils; -import com.webank.ai.fate.networking.proxy.util.AuthUtils; -import com.webank.ai.fate.register.common.Constants; -import com.webank.ai.fate.register.router.RouterService; -import com.webank.ai.fate.register.url.CollectionUtils; -import com.webank.ai.fate.register.url.URL; -import com.webank.ai.fate.serving.core.bean.EncryptMethod; -import com.webank.ai.fate.serving.core.bean.HostFederatedParams; -import com.webank.ai.fate.serving.core.bean.ModelInfo; -import com.webank.ai.fate.serving.core.utils.EncryptUtils; -import io.grpc.stub.StreamObserver; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -@Component -@Scope("prototype") -public class DataTransferPipedClient { - private static final Logger LOGGER = LogManager.getLogger(DataTransferPipedClient.class); - @Autowired - private GrpcStubFactory grpcStubFactory; - @Autowired - private GrpcStreamObserverFactory grpcStreamObserverFactory; - @Autowired - private ServerConf serverConf; - @Autowired - private FdnRouter fdnRouter; - @Autowired - private ToStringUtils toStringUtils; - @Autowired - private ErrorUtils errorUtils; - @Autowired - private AuthUtils authUtils; - - public static RouterService routerService; - - - private BasicMeta.Endpoint endpoint; - private boolean needSecureChannel; - private long MAX_AWAIT_HOURS = 24; - - public DataTransferPipedClient() { - needSecureChannel = false; - } - - public void push(Proxy.Metadata metadata, Pipe pipe) { - String onelineStringMetadata = toStringUtils.toOneLineString(metadata); - LOGGER.info("[PUSH][CLIENT] client send push to server: {}", - onelineStringMetadata); - DataTransferServiceGrpc.DataTransferServiceStub stub = getStub(metadata.getSrc(), metadata.getDst()); - - try { - Proxy.Topic from = metadata.getSrc(); - Proxy.Topic to = metadata.getDst(); - stub = getStub(from, to); - } catch (Exception e) { - LOGGER.error("[PUSH][CLIENT] error when creating push stub"); - pipe.onError(e); - } - - final CountDownLatch finishLatch = new CountDownLatch(1); - final ResultCallback resultCallback = new SingleResultCallback(); - - StreamObserver responseObserver = - grpcStreamObserverFactory.createClientPushResponseStreamObserver(resultCallback, finishLatch); - - StreamObserver requestObserver = stub.push(responseObserver); - LOGGER.info("[PUSH][CLIENT] push stub: {}, metadata: {}", - stub.getChannel(), onelineStringMetadata); - - int emptyRetryCount = 0; - Proxy.Packet packet = null; - do { - packet = (Proxy.Packet) pipe.read(1, TimeUnit.SECONDS); - - if (packet != null) { - requestObserver.onNext(packet); - emptyRetryCount = 0; - } else { - ++emptyRetryCount; - if (emptyRetryCount % 60 == 0) { - LOGGER.info("[PUSH][CLIENT] push stub waiting. empty retry count: {}, metadata: {}", - emptyRetryCount, onelineStringMetadata); - } - } - } while ((packet != null || !pipe.isDrained()) && emptyRetryCount < 30 && !pipe.hasError()); - - LOGGER.info("[PUSH][CLIENT] break out from loop. Proxy.Packet is null? {} ; pipe.isDrained()? {}" + - ", pipe.hasError? {}, metadata: {}", - packet == null, pipe.isDrained(), pipe.hasError(), onelineStringMetadata); - - if (pipe.hasError()) { - Throwable error = pipe.getError(); - LOGGER.error("[PUSH][CLIENT] push error: {}, metadata: {}", - ExceptionUtils.getStackTrace(error), onelineStringMetadata); - requestObserver.onError(error); - - return; - } - - requestObserver.onCompleted(); - try { - finishLatch.await(MAX_AWAIT_HOURS, TimeUnit.HOURS); - } catch (InterruptedException e) { - LOGGER.error("[PUSH][CLIENT] client push: finishLatch.await() interrupted"); - requestObserver.onError(errorUtils.toGrpcRuntimeException(e)); - pipe.onError(e); - Thread.currentThread().interrupt(); - return; - } - - if (pipe instanceof PacketQueueSingleResultPipe) { - PacketQueueSingleResultPipe convertedPipe = (PacketQueueSingleResultPipe) pipe; - if (resultCallback.hasResult()) { - convertedPipe.setResult(resultCallback.getResult()); - } else { - LOGGER.warn("No Proxy.Metadata returned in pipe. request metadata: {}", - onelineStringMetadata); - } - } - pipe.onComplete(); - - LOGGER.info("[PUSH][CLIENT] push closing pipe. metadata: {}", - onelineStringMetadata); - } - - public void pull(Proxy.Metadata metadata, Pipe pipe) { - String onelineStringMetadata = toStringUtils.toOneLineString(metadata); - LOGGER.info("[PULL][CLIENT] client send pull to server: {}", onelineStringMetadata); - DataTransferServiceGrpc.DataTransferServiceStub stub = getStub(metadata.getDst(), metadata.getSrc()); - - final CountDownLatch finishLatch = new CountDownLatch(1); - - StreamObserver responseObserver = - grpcStreamObserverFactory.createClientPullResponseStreamObserver(pipe, finishLatch, metadata); - - stub.pull(metadata, responseObserver); - LOGGER.info("[PULL][CLIENT] pull stub: {}, metadata: {}", - stub.getChannel(), onelineStringMetadata); - - try { - finishLatch.await(MAX_AWAIT_HOURS, TimeUnit.HOURS); - } catch (InterruptedException e) { - LOGGER.error("[PULL][CLIENT] client pull: finishLatch.await() interrupted"); - responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); - pipe.onError(e); - Thread.currentThread().interrupt(); - return; - } - - responseObserver.onCompleted(); - } - - public void unaryCall(Proxy.Packet packet, Pipe pipe) { - Preconditions.checkNotNull(packet); - - Proxy.Metadata header = packet.getHeader(); - - final CountDownLatch finishLatch = new CountDownLatch(1); - StreamObserver responseObserver = grpcStreamObserverFactory - .createClientUnaryCallResponseStreamObserver(pipe, finishLatch, header); - - try { - String onelineStringMetadata = toStringUtils.toOneLineString(header); - LOGGER.info("[UNARYCALL][CLIENT] client send unary call to server: {}", onelineStringMetadata); - - packet = authUtils.addAuthInfo(packet); - - DataTransferServiceGrpc.DataTransferServiceStub stub = getStub( - packet.getHeader().getSrc(), packet.getHeader().getDst(), packet); - - stub.unaryCall(packet, responseObserver); - - LOGGER.info("[UNARYCALL][CLIENT] unary call stub: {}, metadata: {}", - stub.getChannel(), onelineStringMetadata); - - try { - finishLatch.await(MAX_AWAIT_HOURS, TimeUnit.HOURS); - } catch (InterruptedException e) { - LOGGER.error("[UNARYCALL][CLIENT] client unary call: finishLatch.await() interrupted"); - responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); - pipe.onError(e); - Thread.currentThread().interrupt(); - return; - } - } catch (Exception e) { - LOGGER.error("[UNARYCALL][CLIENT] client unary call: exception: ", e); - responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); - pipe.onError(e); - Thread.currentThread().interrupt(); - return; - } - - responseObserver.onCompleted(); - } - - private DataTransferServiceGrpc.DataTransferServiceStub getStub(Proxy.Topic from, Proxy.Topic to, Proxy.Packet pack - ) { - - - - DataTransferServiceGrpc.DataTransferServiceStub stub = null; - - String useZkRouterString = serverConf.getProperties().getProperty("useZkRouter", "false"); - - boolean useZkRouter = Boolean.valueOf(useZkRouterString); - if (fdnRouter instanceof ConfFileBasedFdnRouter && useZkRouter) { - - ConfFileBasedFdnRouter confFileBasedFdnRouter = (ConfFileBasedFdnRouter) fdnRouter; - Map>> routerTable = confFileBasedFdnRouter.getRouteTable(); - - if (routerTable.containsKey(to.getPartyId()) && - ((routerTable.get(to.getPartyId()).get("serving-1.0")!=null&& "serving-1.0".equals(to.getRole()))|| - (routerTable.get(to.getPartyId()).get("serving")!=null&& "serving".equals(to.getRole()))) - - ) { - - stub = routerByServiceRegister(from, to, pack); - if (stub != null) { - LOGGER.info("appid {} register return stub", to.getPartyId()); - return stub; - } else { - LOGGER.info("appid {} register not return stub", to.getPartyId()); - return null; - } - - } - } - - if (endpoint == null && !fdnRouter.isAllowed(from, to)) { - throw new SecurityException("no permission from " + toStringUtils.toOneLineString(from) - + " to " + toStringUtils.toOneLineString(to)); - } - - - if (endpoint == null) { - stub = grpcStubFactory.getAsyncStub(to); - } else { - stub = grpcStubFactory.getAsyncStub(endpoint); - } - - LOGGER.info("[ROUTE] route info: {} routed to {}", toStringUtils.toOneLineString(to), - toStringUtils.toOneLineString(fdnRouter.route(to))); - - fdnRouter.route(from); - - return stub; - } - - - private DataTransferServiceGrpc.DataTransferServiceStub getStub(Proxy.Topic from, Proxy.Topic to - ) { - - DataTransferServiceGrpc.DataTransferServiceStub stub = null; - - if (endpoint == null && !fdnRouter.isAllowed(from, to)) { - throw new SecurityException("no permission from " + toStringUtils.toOneLineString(from) - + " to " + toStringUtils.toOneLineString(to)); - } - - - if (endpoint == null) { - stub = grpcStubFactory.getAsyncStub(to); - } else { - stub = grpcStubFactory.getAsyncStub(endpoint); - } - - LOGGER.info("[ROUTE] route info: {} routed to {}", toStringUtils.toOneLineString(to), - toStringUtils.toOneLineString(fdnRouter.route(to))); - - fdnRouter.route(from); - - return stub; - } - - private static final String modelKeySeparator = "&"; - - public static String genModelKey(String name, String namespace) { - return StringUtils.join(Arrays.asList(name, namespace), modelKeySeparator); - } - - - private DataTransferServiceGrpc.DataTransferServiceStub routerByServiceRegister(Proxy.Topic from, Proxy.Topic to, Proxy.Packet pack) { - DataTransferServiceGrpc.DataTransferServiceStub stub = null; - String partId = to.getPartyId(); - String role = to.getRole(); - String name = to.getName(); - String serviceName = pack.getHeader().getCommand().getName(); - String version = pack.getHeader().getOperator(); - String data = pack.getBody().getValue().toStringUtf8(); - HostFederatedParams requestData = JSON.parseObject(data, HostFederatedParams.class); - ModelInfo partnerModelInfo = requestData.getPartnerModelInfo(); - // (partnerModelInfo.getName(), partnerModelInfo.getNamespace() - String key =genModelKey(partnerModelInfo.getName(), partnerModelInfo.getNamespace()); - String md5Key = EncryptUtils.encrypt(key, EncryptMethod.MD5); - String urlString = "serving/" + md5Key + "/unaryCall"; - URL paramUrl = URL.valueOf(urlString); - if(StringUtils.isNotEmpty(version)) { - paramUrl= paramUrl.addParameter(Constants.VERSION_KEY,version - ); - } - - List urls = routerService.router(paramUrl); - LOGGER.info("try to find {} returns {}",urlString,urls); - if (CollectionUtils.isNotEmpty(urls)) { - URL url = urls.get(0); - BasicMeta.Endpoint.Builder builder = BasicMeta.Endpoint.newBuilder(); - builder.setIp(url.getHost()); - builder.setPort(url.getPort()); - BasicMeta.Endpoint endpoint = builder.build(); - stub = grpcStubFactory.getAsyncStub(endpoint); - } - return stub; - } - - public boolean isNeedSecureChannel() { - return needSecureChannel; - } - - public void setNeedSecureChannel(boolean needSecureChannel) { - this.needSecureChannel = needSecureChannel; - } - - public BasicMeta.Endpoint getEndpoint() { - return endpoint; - } - - public void setEndpoint(BasicMeta.Endpoint endpoint) { - this.endpoint = endpoint; - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientPullResponseStreamObserver.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientPullResponseStreamObserver.java deleted file mode 100644 index c3793858..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientPullResponseStreamObserver.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.grpc.observer; - -import com.google.protobuf.ByteString; -import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.networking.proxy.infra.Pipe; -import com.webank.ai.fate.networking.proxy.manager.StatsManager; -import com.webank.ai.fate.networking.proxy.model.ServerConf; -import com.webank.ai.fate.networking.proxy.model.StreamStat; -import com.webank.ai.fate.networking.proxy.util.ToStringUtils; -import io.grpc.Status; -import io.grpc.stub.StreamObserver; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicLong; - - -@Component -@Scope("prototype") -public class ClientPullResponseStreamObserver implements StreamObserver { - private static final Logger logger = LoggerFactory.getLogger(ClientPullResponseStreamObserver.class); - private static final Logger AUDIT = LoggerFactory.getLogger("audit"); - private static final Logger DEBUGGING = LoggerFactory.getLogger("debugging"); - private final CountDownLatch finishLatch; - @Autowired - private StatsManager statsManager; - @Autowired - private ServerConf serverConf; - @Autowired - private ToStringUtils toStringUtils; - private StreamStat streamStat; - private volatile boolean isStreamStatSet; - private Proxy.Metadata metadata; - private String oneLineStringMetadata; - private Pipe pipe; - private String myCoordinator; - private boolean isAuditEnabled; - private boolean isDebugEnabled; - private boolean isInited; - private AtomicLong ackCount; - - public ClientPullResponseStreamObserver(Pipe pipe, CountDownLatch finishLatch, Proxy.Metadata metadata) { - this.finishLatch = finishLatch; - this.pipe = pipe; - this.isStreamStatSet = false; - this.metadata = metadata; - - this.streamStat = new StreamStat(metadata, StreamStat.PULL); - this.ackCount = new AtomicLong(0L); - } - - @PostConstruct - private synchronized void init() { - if (isInited) { - return; - } - this.oneLineStringMetadata = toStringUtils.toOneLineString(metadata); - - statsManager.add(streamStat); - - if (streamStat != null) { - this.isStreamStatSet = true; - } - - if (StringUtils.isBlank(myCoordinator)) { - myCoordinator = serverConf.getCoordinator(); - } - - isAuditEnabled = serverConf.isAuditEnabled(); - isDebugEnabled = serverConf.isDebugEnabled(); - - isInited = true; - } - - @Override - public void onNext(Proxy.Packet packet) { - pipe.write(packet); - ackCount.incrementAndGet(); - - if (!isInited) { - init(); - } - - if (isAuditEnabled && packet.getHeader().getSrc().getPartyId().equals(myCoordinator)) { - AUDIT.info(toStringUtils.toOneLineString(packet)); - } - - if (isDebugEnabled) { - DEBUGGING.info("[PULL][OBSERVER][ONNEXT] pull: {}, ackCount: {}", packet, ackCount.get()); - if (packet.getBody() != null && packet.getBody().getValue() != null) { - ByteString value = packet.getBody().getValue(); - streamStat.increment(value.size()); - DEBUGGING.info("[PULL][OBSERVER][ONNEXT] length: {}, metadata: {}", - packet.getBody().getValue().size(), oneLineStringMetadata); - } else { - DEBUGGING.info("[PULL][OBSERVER][ONNEXT] length: null, metadata: {}", oneLineStringMetadata); - } - DEBUGGING.info("-------------"); - } - if (packet.getBody() != null && packet.getBody().getValue() != null) { - ByteString value = packet.getBody().getValue(); - streamStat.increment(value.size()); - } - } - - @Override - public void onError(Throwable throwable) { - logger.error("[PULL][OBSERVER][ONERROR] error in pull client: {}, metadata: {}, ackCount: {}", - Status.fromThrowable(throwable), oneLineStringMetadata, ackCount.incrementAndGet()); - logger.error(ExceptionUtils.getStackTrace(throwable)); - - pipe.onError(throwable); - - finishLatch.countDown(); - streamStat.onError(); - } - - @Override - public void onCompleted() { - long latestAckCount = ackCount.get(); - logger.info("[PULL][OBSERVER][ONCOMPLETE] Client pull completed. metadata: {}, ackCount: {}", - oneLineStringMetadata, latestAckCount); - - pipe.onComplete(); - finishLatch.countDown(); - - try { - logger.info("[PULL][OBSERVER][ONCOMPLETE] is streamStat set: {}", isStreamStatSet); - if (streamStat != null) { - streamStat.onComplete(); - } - } catch (NullPointerException e) { - logger.error("[PULL][OBSERVER][ONCOMPLETE] NullPointerException caught in pull onComplete. isStreamStatSet: {}", isStreamStatSet); - } - } - - public CountDownLatch getFinishLatch() { - return this.finishLatch; - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientPushResponseStreamObserver.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientPushResponseStreamObserver.java deleted file mode 100644 index be712457..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientPushResponseStreamObserver.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.grpc.observer; - -import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.networking.proxy.infra.ResultCallback; -import com.webank.ai.fate.networking.proxy.util.ToStringUtils; -import io.grpc.stub.StreamObserver; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -import java.util.concurrent.CountDownLatch; - -@Component -@Scope("prototype") -public class ClientPushResponseStreamObserver implements StreamObserver { - private static final Logger logger = LoggerFactory.getLogger(ClientPullResponseStreamObserver.class); - private final CountDownLatch finishLatch; - private final ResultCallback resultCallback; - @Autowired - private ToStringUtils toStringUtils; - private Proxy.Metadata metadata; - - public ClientPushResponseStreamObserver(ResultCallback resultCallback, CountDownLatch finishLatch) { - this.resultCallback = resultCallback; - this.finishLatch = finishLatch; - } - - @Override - public void onNext(Proxy.Metadata metadata) { - this.metadata = metadata; - resultCallback.setResult(metadata); - logger.info("[PUSH][CLIENTOBSERVER][ONNEXT] ClientPushResponseStreamObserver.onNext(), metadata: {}", - toStringUtils.toOneLineString(metadata)); - } - - @Override - public void onError(Throwable throwable) { - logger.error("[PUSH][CLIENTOBSERVER][ONERROR] error in push client: {}, metadata: {}", toStringUtils.toOneLineString(metadata)); - logger.error(ExceptionUtils.getStackTrace(throwable)); - finishLatch.countDown(); - } - - @Override - public void onCompleted() { - logger.info("[PUSH][CLIENTOBSERVER][ONCOMPLETE] ClientPushResponseStreamObserver.onCompleted(), metadata: {}", - toStringUtils.toOneLineString(metadata)); - finishLatch.countDown(); - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientUnaryCallResponseStreamObserver.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientUnaryCallResponseStreamObserver.java deleted file mode 100644 index bc415fe3..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ClientUnaryCallResponseStreamObserver.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.grpc.observer; - -import com.google.protobuf.ByteString; -import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.networking.proxy.infra.Pipe; -import com.webank.ai.fate.networking.proxy.manager.StatsManager; -import com.webank.ai.fate.networking.proxy.model.ServerConf; -import com.webank.ai.fate.networking.proxy.model.StreamStat; -import com.webank.ai.fate.networking.proxy.util.ToStringUtils; -import io.grpc.Status; -import io.grpc.stub.StreamObserver; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicLong; - - -@Component -@Scope("prototype") -public class ClientUnaryCallResponseStreamObserver implements StreamObserver { - private static final Logger logger = LoggerFactory.getLogger(ClientUnaryCallResponseStreamObserver.class); - private static final Logger DEBUGGING = LoggerFactory.getLogger("debugging"); - private static final Logger AUDIT = LoggerFactory.getLogger("audit"); - private final CountDownLatch finishLatch; - @Autowired - private StatsManager statsManager; - @Autowired - private ToStringUtils toStringUtils; - @Autowired - private ServerConf serverConf; - private StreamStat streamStat; - private Proxy.Metadata metadata; - private Pipe pipe; - private boolean isInited; - private AtomicLong ackCount; - - public ClientUnaryCallResponseStreamObserver(Pipe pipe, CountDownLatch finishLatch, Proxy.Metadata metadata) { - this.finishLatch = finishLatch; - this.pipe = pipe; - this.metadata = metadata; - - this.streamStat = new StreamStat(metadata, StreamStat.UNARY_CALL); - this.ackCount = new AtomicLong(0L); - } - - @PostConstruct - private synchronized void init() { - if (isInited) { - return; - } - - statsManager.add(streamStat); - isInited = true; - } - - @Override - public void onNext(Proxy.Packet packet) { - // logger.info("ClientPullResponseStreamObserver.onNext()"); - pipe.write(packet); - ackCount.incrementAndGet(); - - if (!isInited) { - init(); - } - - if (serverConf.isDebugEnabled()) { - DEBUGGING.info("[UNARYCALL][OBSERVER][ONNEXT]: {}", packet); - DEBUGGING.info("-------------"); - } - - if (serverConf.isAuditEnabled() - && packet.getHeader().getSrc().getPartyId().equals(serverConf.getCoordinator())) { - AUDIT.info(toStringUtils.toOneLineString(packet)); - } - - - if (packet.getBody() != null && packet.getBody().getValue() != null) { - ByteString value = packet.getBody().getValue(); - streamStat.increment(value.size()); - } - - // logger.info("[UNARYCALL][OBSERVER][ONNEXT] result: {}", packet.getBody().getValue().toStringUtf8()); - } - - @Override - public void onError(Throwable throwable) { - logger.error("[UNARYCALL][OBSERVER][ONERROR] error in unary call response observer: {}, metadata: {}", - Status.fromThrowable(throwable), toStringUtils.toOneLineString(metadata)); - logger.error(ExceptionUtils.getStackTrace(throwable)); - - pipe.onError(throwable); - - streamStat.onError(); - finishLatch.countDown(); - } - - @Override - public void onCompleted() { - logger.info("[UNARYCALL][OBSERVER][ONCOMPLETE] Client unary call completed. metadata: {}", - toStringUtils.toOneLineString(metadata)); - - pipe.onComplete(); - finishLatch.countDown(); - streamStat.onComplete(); - } - - public CountDownLatch getFinishLatch() { - return this.finishLatch; - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ServerPushRequestStreamObserver.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ServerPushRequestStreamObserver.java deleted file mode 100644 index 51d26ac9..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/observer/ServerPushRequestStreamObserver.java +++ /dev/null @@ -1,282 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.grpc.observer; - -import com.google.protobuf.ByteString; -import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.networking.proxy.event.model.PipeHandleNotificationEvent; -import com.webank.ai.fate.networking.proxy.factory.EventFactory; -import com.webank.ai.fate.networking.proxy.helper.ModelValidationHelper; -import com.webank.ai.fate.networking.proxy.infra.Pipe; -import com.webank.ai.fate.networking.proxy.infra.impl.PacketQueuePipe; -import com.webank.ai.fate.networking.proxy.manager.StatsManager; -import com.webank.ai.fate.networking.proxy.model.ServerConf; -import com.webank.ai.fate.networking.proxy.model.StreamStat; -import com.webank.ai.fate.networking.proxy.util.ErrorUtils; -import com.webank.ai.fate.networking.proxy.util.PipeUtils; -import com.webank.ai.fate.networking.proxy.util.Timeouts; -import com.webank.ai.fate.networking.proxy.util.ToStringUtils; -import io.grpc.Grpc; -import io.grpc.Status; -import io.grpc.stub.StreamObserver; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicLong; - -@Component -@Scope("prototype") -public class ServerPushRequestStreamObserver implements StreamObserver { - private static final Logger logger = LoggerFactory.getLogger(ServerPushRequestStreamObserver.class); - private static final Logger AUDIT = LoggerFactory.getLogger("audit"); - private static final Logger DEBUGGING = LoggerFactory.getLogger("debugging"); - private final StreamObserver responseObserver; - @Autowired - private ApplicationEventPublisher applicationEventPublisher; - @Autowired - private EventFactory eventFactory; - @Autowired - private ModelValidationHelper modelValidationHelper; - @Autowired - private Timeouts timeouts; - @Autowired - private StatsManager statsManager; - @Autowired - private ToStringUtils toStringUtils; - @Autowired - private ServerConf serverConf; - @Autowired - private ErrorUtils errorUtils; - @Autowired - private PipeUtils pipeUtils; - private Pipe pipe; - private Proxy.Metadata inputMetadata; - private StreamStat streamStat; - private String myCoordinator; - private long overallStartTimestamp; - private long overallTimeout; - private long completionWaitTimeout; - private String oneLineStringInputMetadata; - private boolean noError; - private boolean isAuditEnabled; - private boolean isDebugEnabled; - private AtomicLong ackCount; - - public ServerPushRequestStreamObserver(Pipe pipe, StreamObserver responseObserver) { - this.pipe = pipe; - this.responseObserver = responseObserver; - this.completionWaitTimeout = Timeouts.DEFAULT_COMPLETION_WAIT_TIMEOUT; - this.overallTimeout = Timeouts.DEFAULT_OVERALL_TIMEOUT; - - this.noError = true; - this.ackCount = new AtomicLong(0L); - } - - @Override - public void onNext(Proxy.Packet packet) { - if (inputMetadata == null) { - overallStartTimestamp = System.currentTimeMillis(); - inputMetadata = packet.getHeader(); - streamStat = new StreamStat(inputMetadata, StreamStat.PUSH); - oneLineStringInputMetadata = toStringUtils.toOneLineString(inputMetadata); - statsManager.add(streamStat); - - logger.info(Grpc.TRANSPORT_ATTR_REMOTE_ADDR.toString()); - - logger.info("[PUSH][OBSERVER][ONNEXT] metadata: {}", oneLineStringInputMetadata); - logger.info("[PUSH][OBSERVER][ONNEXT] request src: {}, dst: {}", - toStringUtils.toOneLineString(inputMetadata.getSrc()), - toStringUtils.toOneLineString(inputMetadata.getDst())); - - if (StringUtils.isBlank(myCoordinator)) { - myCoordinator = serverConf.getCoordinator(); - } - - if (inputMetadata.hasConf()) { - overallTimeout = timeouts.getOverallTimeout(inputMetadata); - completionWaitTimeout = timeouts.getCompletionWaitTimeout(inputMetadata); - } - - isAuditEnabled = serverConf.isAuditEnabled(); - isDebugEnabled = serverConf.isDebugEnabled(); - - // check if topics are valid - if (!modelValidationHelper.checkTopic(inputMetadata.getDst()) - || !modelValidationHelper.checkTopic(inputMetadata.getSrc())) { - onError(new IllegalArgumentException("At least one of topic name, coordinator, role is blank.")); - noError = false; - return; - } - - // String operator = inputMetadata.getOperator(); - - // logger.info("onNext(): push task name: {}", operator); - - PipeHandleNotificationEvent event = - eventFactory.createPipeHandleNotificationEvent( - this, PipeHandleNotificationEvent.Type.PUSH, inputMetadata, pipe); - applicationEventPublisher.publishEvent(event); - } - - if (noError) { - pipe.write(packet); - ackCount.incrementAndGet(); - //logger.info("myCoordinator: {}, Proxy.Packet coordinator: {}", myCoordinator, packet.getHeader().getSrc().getCoordinator()); - if (isAuditEnabled && packet.getHeader().getSrc().getPartyId().equals(myCoordinator)) { - AUDIT.info(toStringUtils.toOneLineString(packet)); - } - - if (timeouts.isTimeout(overallTimeout, overallStartTimestamp)) { - onError(new IllegalStateException("push overall wait timeout exceeds overall timeout: " + overallTimeout - + ", metadata: " + oneLineStringInputMetadata)); - pipe.close(); - return; - } - - if (isDebugEnabled) { - DEBUGGING.info("[PUSH][OBSERVER][ONNEXT] server: {}, ackCount: {}", packet, ackCount.get()); - if (packet.getBody() != null && packet.getBody().getValue() != null) { - ByteString value = packet.getBody().getValue(); - streamStat.increment(value.size()); - DEBUGGING.info("[PUSH][OBSERVER][ONNEXT] length: {}, metadata: {}", - packet.getBody().getValue().size(), oneLineStringInputMetadata); - } else { - DEBUGGING.info("[PUSH][OBSERVER][ONNEXT] length : null, metadata: {}", oneLineStringInputMetadata); - } - DEBUGGING.info("-------------"); - } - //logger.info("push server received size: {}, data size: {}", packet.getSerializedSize(), packet.getBody().getValue().size()); - } - } - - @Override - public void onError(Throwable throwable) { - logger.error("[PUSH][OBSERVER][ONERROR] error in push server: {}, metadata: {}, ackCount: {}", - Status.fromThrowable(throwable), oneLineStringInputMetadata, ackCount.get()); - logger.error(ExceptionUtils.getStackTrace(throwable)); - - pipe.setDrained(); - -/* if (Status.fromThrowable(throwable).getCode() != Status.Code.CANCELLED) { - pipe.onError(throwable); - responseObserver.onError(errorUtils.toGrpcRuntimeException(throwable)); - streamStat.onError(); - } else { - noError = false; - pipe.onComplete(); - logger.info("[PUSH][OBSERVER][ONERROR] connection cancelled. turning into completed."); - onCompleted(); - streamStat.onComplete(); - return; - }*/ - - pipe.onError(throwable); - responseObserver.onError(errorUtils.toGrpcRuntimeException(throwable)); - streamStat.onError(); - } - - @Override - public void onCompleted() { - long lastestAckCount = ackCount.get(); - logger.info("[PUSH][OBSERVER][ONCOMPLETE] trying to complete task. metadata: {}, ackCount: {}", - oneLineStringInputMetadata, lastestAckCount); - - long completionWaitStartTimestamp = System.currentTimeMillis(); - long loopEndTimestamp = completionWaitStartTimestamp; - long waitCount = 0; - - pipe.setDrained(); - - /*logger.info("closed: {}, completion timeout: {}, overall timeout: {}", - pipe.isClosed(), - timeouts.isTimeout(completionWaitTimeout, completionWaitStartTimestamp, loopEnd), - timeouts.isTimeout(overallTimeout, overallStartTimestamp, loopEnd));*/ - while (!pipe.isClosed() - && !timeouts.isTimeout(completionWaitTimeout, completionWaitStartTimestamp, loopEndTimestamp) - && !timeouts.isTimeout(overallTimeout, overallStartTimestamp, loopEndTimestamp)) { - // logger.info("waiting for next level result"); - try { - pipe.awaitClosed(1, TimeUnit.SECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } finally { - loopEndTimestamp = System.currentTimeMillis(); - } - if (++waitCount % 60 == 0) { - - String extraInfo = ""; - if (pipe instanceof PacketQueuePipe) { - PacketQueuePipe pqp = (PacketQueuePipe) pipe; - extraInfo = "queueSize: " + pqp.getQueueSize(); - } - logger.info("[PUSH][OBSERVER][ONCOMPLETE] waiting push to complete. wait time: {}. metadata: {}, extrainfo: {}", - (loopEndTimestamp - completionWaitStartTimestamp), oneLineStringInputMetadata, extraInfo); - } - } - - pipe.onComplete(); - - try { - if (timeouts.isTimeout(completionWaitTimeout, completionWaitStartTimestamp, loopEndTimestamp)) { - String errmsg = "[PUSH][OBSERVER][ONCOMPLETE] push server completion wait exceeds completionWaitTimeout. " - + "completionWaitTimeout: " + completionWaitTimeout - + ", metadata: " + oneLineStringInputMetadata - + ", completionWaitStartTimestamp: " + completionWaitStartTimestamp - + ", loopEndTimestamp: " + loopEndTimestamp - + ", ackCount: " + lastestAckCount; - logger.error(errmsg); - responseObserver.onError(new TimeoutException(errmsg)); - streamStat.onError(); - } else if (timeouts.isTimeout(overallTimeout, overallStartTimestamp, loopEndTimestamp)) { - String errmsg = "[PUSH][OBSERVER][ONCOMPLETE] push server overall time exceeds overallTimeout. " - + "overallTimeout: " + overallTimeout - + ", metadata: " + oneLineStringInputMetadata - + ", overallStartTimestamp: " + overallStartTimestamp - + ", loopEndTimestamp: " + loopEndTimestamp - + ", ackCount: " + lastestAckCount; - - logger.error(errmsg); - responseObserver.onError(new TimeoutException(errmsg)); - streamStat.onError(); - } else { - Proxy.Metadata responseMetadata = pipeUtils.getResultFromPipe(pipe); - if (responseMetadata == null) { - logger.warn("[PUSH][OBSERVER][ONCOMPLETE] response Proxy.Metadata is null. inputMetadata: {}", - toStringUtils.toOneLineString(responseMetadata)); - } - - responseObserver.onNext(responseMetadata); - responseObserver.onCompleted(); - - logger.info("[PUSH][OBSERVER][ONCOMPLETE] push server complete. inputMetadata: {}", - toStringUtils.toOneLineString(responseMetadata)); - streamStat.onComplete(); - } - } catch (NullPointerException e) { - logger.error("[PUSH][OBSERVER][ONCOMPLETE] NullPointerException caught in push onComplete. metadata: {}", - oneLineStringInputMetadata); - } - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/DataTransferPipedServerImpl.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/DataTransferPipedServerImpl.java deleted file mode 100644 index 255217d2..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/DataTransferPipedServerImpl.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.grpc.service; - -import com.webank.ai.fate.api.networking.proxy.DataTransferServiceGrpc; -import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.networking.proxy.event.model.PipeHandleNotificationEvent; -import com.webank.ai.fate.networking.proxy.factory.EventFactory; -import com.webank.ai.fate.networking.proxy.factory.GrpcStreamObserverFactory; -import com.webank.ai.fate.networking.proxy.factory.PipeFactory; -import com.webank.ai.fate.networking.proxy.infra.Pipe; -import com.webank.ai.fate.networking.proxy.util.AuthUtils; -import com.webank.ai.fate.networking.proxy.util.ErrorUtils; -import com.webank.ai.fate.networking.proxy.util.Timeouts; -import com.webank.ai.fate.networking.proxy.util.ToStringUtils; -import com.webank.ai.fate.register.annotions.RegisterService; -import io.grpc.stub.StreamObserver; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -@Component -@Scope("prototype") -public class DataTransferPipedServerImpl extends DataTransferServiceGrpc.DataTransferServiceImplBase { - private static final Logger logger = LoggerFactory.getLogger(DataTransferPipedServerImpl.class); - @Autowired - private ApplicationEventPublisher applicationEventPublisher; - @Autowired - private GrpcStreamObserverFactory grpcStreamObserverFactory; - @Autowired - private Timeouts timeouts; - @Autowired - private EventFactory eventFactory; - @Autowired - private ToStringUtils toStringUtils; - @Autowired - private ErrorUtils errorUtils; - private Pipe defaultPipe; - private PipeFactory pipeFactory; - @Autowired - private AuthUtils authUtils; - - @RegisterService(serviceName = "push") - @Override - public StreamObserver push(StreamObserver responseObserver) { - logger.info("[PUSH][SERVER] request received"); - - Pipe pipe = getPipe(); - // logger.info("push pipe: {}", pipe); -/* - PipeHandleNotificationEvent event = - eventFactory.createPipeHandleNotificationEvent( - this, PipeHandleNotificationEvent.Type.PUSH, null, pipe); - applicationEventPublisher.publishEvent(event); -*/ - - StreamObserver requestObserver = grpcStreamObserverFactory - .createServerPushRequestStreamObserver(pipe, responseObserver); - - return requestObserver; - } - - @Override - @RegisterService(serviceName = "pull") - public void pull(Proxy.Metadata inputMetadata, StreamObserver responseObserver) { - String oneLineStringInputMetadata = toStringUtils.toOneLineString(inputMetadata); - logger.info("[PULL][SERVER] request received. metadata: {}", - oneLineStringInputMetadata); - - long overallTimeout = timeouts.getOverallTimeout(inputMetadata); - long packetIntervalTimeout = timeouts.getPacketIntervalTimeout(inputMetadata); - - Pipe pipe = getPipe(); - - logger.info("[PULL][SERVER] pull pipe: {}", pipe); - - PipeHandleNotificationEvent event = - eventFactory.createPipeHandleNotificationEvent( - this, PipeHandleNotificationEvent.Type.PULL, inputMetadata, pipe); - applicationEventPublisher.publishEvent(event); - - long startTimestamp = System.currentTimeMillis(); - long lastPacketTimestamp = startTimestamp; - long loopEndTimestamp = lastPacketTimestamp; - - Proxy.Packet packet = null; - boolean hasReturnedBefore = false; - int emptyRetryCount = 0; - Proxy.Packet lastReturnedPacket = null; - - while ((!hasReturnedBefore || !pipe.isDrained()) - && !pipe.hasError() - && !timeouts.isTimeout(packetIntervalTimeout, lastPacketTimestamp, loopEndTimestamp) - && !timeouts.isTimeout(overallTimeout, startTimestamp, loopEndTimestamp)) { - packet = (Proxy.Packet) pipe.read(1, TimeUnit.SECONDS); - // logger.info("packet is null: {}", Proxy.Packet == null); - loopEndTimestamp = System.currentTimeMillis(); - if (packet != null) { - // logger.info("server pull onNext()"); - responseObserver.onNext(packet); - hasReturnedBefore = true; - lastReturnedPacket = packet; - lastPacketTimestamp = loopEndTimestamp; - emptyRetryCount = 0; - } else { - long currentPacketInterval = loopEndTimestamp - lastPacketTimestamp; - if (++emptyRetryCount % 60 == 0) { - logger.info("[PULL][SERVER] pull waiting. current packetInterval: {}, packetIntervalTimeout: {}, metadata: {}", - currentPacketInterval, packetIntervalTimeout, oneLineStringInputMetadata); - } - } - } - - boolean hasError = true; - if (pipe.hasError()) { - Throwable error = pipe.getError(); - logger.error("[PULL][SERVER] pull finish with error: {}", ExceptionUtils.getStackTrace(error)); - responseObserver.onError(error); - - return; - } - - StringBuilder sb = new StringBuilder(); - if (timeouts.isTimeout(packetIntervalTimeout, lastPacketTimestamp, loopEndTimestamp)) { - sb.append("[PULL][SERVER] pull server error: Proxy.Packet interval exceeds timeout: ") - .append(packetIntervalTimeout) - .append(", metadata: ") - .append(oneLineStringInputMetadata) - .append(", lastPacketTimestamp: ") - .append(lastPacketTimestamp) - .append(", loopEndTimestamp: ") - .append(loopEndTimestamp); - - String errorMsg = sb.toString(); - - logger.error(errorMsg); - - TimeoutException e = new TimeoutException(errorMsg); - responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); - pipe.onError(e); - } else if (timeouts.isTimeout(overallTimeout, startTimestamp, loopEndTimestamp)) { - sb.append("[PULL][SERVER] pull server error: overall process time exceeds timeout: ") - .append(overallTimeout) - .append(", metadata: ") - .append(oneLineStringInputMetadata) - .append(", startTimestamp: ") - .append(startTimestamp) - .append(", loopEndTimestamp: ") - .append(loopEndTimestamp); - String errorMsg = sb.toString(); - logger.error(errorMsg); - - TimeoutException e = new TimeoutException(errorMsg); - responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); - pipe.onError(e); - } else { - responseObserver.onCompleted(); - hasError = false; - pipe.onComplete(); - } - logger.info("[PULL][SERVER] server pull finshed. hasReturnedBefore: {}, hasError: {}, metadata: {}", - hasReturnedBefore, hasError, oneLineStringInputMetadata); - //logger.warn("pull last returned packet: {}", lastReturnedPacket); - } - - @Override - @RegisterService(serviceName = "unaryCall") - public void unaryCall(Proxy.Packet request, StreamObserver responseObserver) { - // check authentication - boolean isAuthPass = false; - try { - isAuthPass = authUtils.checkAuthentication(request); - } catch (Exception e) { - logger.error("checkAuthentication throw an exception: ", e); - responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); - return; - } - if(false == isAuthPass){ - String msg = "authentication not pass!"; - logger.error(msg); - RuntimeException e = new RuntimeException(msg); - responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); - return; - } - - Proxy.Metadata inputMetadata = request.getHeader(); - String oneLineStringInputMetadata = toStringUtils.toOneLineString(inputMetadata); - logger.info("[UNARYCALL][SERVER] server unary request received. src: {}, dst: {}", - toStringUtils.toOneLineString(inputMetadata.getSrc()), - toStringUtils.toOneLineString(inputMetadata.getDst())); - - long overallTimeout = timeouts.getOverallTimeout(inputMetadata); - long packetIntervalTimeout = timeouts.getPacketIntervalTimeout(inputMetadata); - - Pipe pipe = getPipe(); - - logger.info("[UNARYCALL][SERVER] unary call pipe: {}", pipe); - - PipeHandleNotificationEvent event = - eventFactory.createPipeHandleNotificationEvent( - this, PipeHandleNotificationEvent.Type.UNARY_CALL, request, pipe); - applicationEventPublisher.publishEvent(event); - - long startTimestamp = System.currentTimeMillis(); - long lastPacketTimestamp = startTimestamp; - Proxy.Packet packet = null; - boolean hasReturnedBefore = false; - int emptyRetryCount = 0; - long loopEndTimestamp = System.currentTimeMillis(); - while ((!hasReturnedBefore || !pipe.isDrained()) - && !pipe.hasError() - && !timeouts.isTimeout(overallTimeout, startTimestamp, loopEndTimestamp)) { - packet = (Proxy.Packet) pipe.read(1, TimeUnit.SECONDS); - loopEndTimestamp = System.currentTimeMillis(); - if (packet != null) { - // logger.info("server pull onNext()"); - responseObserver.onNext(packet); - hasReturnedBefore = true; - emptyRetryCount = 0; - break; - } else { - long currentOverallWaitTime = loopEndTimestamp - lastPacketTimestamp; - - if (++emptyRetryCount % 60 == 0) { - logger.info("[UNARYCALL][SERVER] unary call waiting. current overallWaitTime: {}, packetIntervalTimeout: {}, metadata: {}", - currentOverallWaitTime, packetIntervalTimeout, oneLineStringInputMetadata); - } - } - } - boolean hasError = true; - - if (pipe.hasError()) { - Throwable error = pipe.getError(); - logger.error("[UNARYCALL][SERVER] unary call finish with error: {}", ExceptionUtils.getStackTrace(error)); - responseObserver.onError(error); - - return; - } - - if (!hasReturnedBefore) { - if (timeouts.isTimeout(overallTimeout, startTimestamp, loopEndTimestamp)) { - String errorMsg = "[UNARYCALL][SERVER] unary call server error: overall process time exceeds timeout: " + overallTimeout - + ", metadata: " + oneLineStringInputMetadata - + ", lastPacketTimestamp: " + lastPacketTimestamp - + ", loopEndTimestamp: " + loopEndTimestamp; - logger.error(errorMsg); - - TimeoutException e = new TimeoutException(errorMsg); - responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); - pipe.onError(e); - } else { - String errorMsg = "[PULL][SERVER] pull server error: overall process time exceeds timeout: " + overallTimeout - + ", metadata: " + oneLineStringInputMetadata - + ", startTimestamp: " + startTimestamp - + ", loopEndTimestamp: " + loopEndTimestamp; - - TimeoutException e = new TimeoutException(errorMsg); - responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); - pipe.onError(e); - } - } else { - hasError = false; - responseObserver.onCompleted(); - pipe.onComplete(); - } - - logger.info("[UNARYCALL][SERVER] server unary call completed. hasReturnedBefore: {}, hasError: {}, metadata: {}", - hasReturnedBefore, hasError, oneLineStringInputMetadata); - } - - private void checkNotNull() { - if (defaultPipe == null && pipeFactory == null) { - throw new NullPointerException("defaultPipe and pipeFactory are both null"); - } - } - - private Pipe getPipe() { - checkNotNull(); - - Pipe result = defaultPipe; - if (pipeFactory != null) { - result = pipeFactory.create(); - } - - return result; - } - - public void setDefaultPipe(Pipe defaultPipe) { - this.defaultPipe = defaultPipe; - } - - public void setPipeFactory(PipeFactory pipeFactory) { - this.pipeFactory = pipeFactory; - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/RouteServerImpl.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/RouteServerImpl.java deleted file mode 100644 index 8dadb8c1..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/grpc/service/RouteServerImpl.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.grpc.service; - -import com.webank.ai.fate.api.core.BasicMeta; -import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.api.networking.proxy.RouteServiceGrpc; -import com.webank.ai.fate.networking.proxy.service.FdnRouter; -import com.webank.ai.fate.networking.proxy.util.ErrorUtils; -import com.webank.ai.fate.networking.proxy.util.ToStringUtils; -import io.grpc.stub.StreamObserver; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - - -@Component -@Scope("prototype") -public class RouteServerImpl extends RouteServiceGrpc.RouteServiceImplBase { - private static final Logger logger = LoggerFactory.getLogger(RouteServerImpl.class); - @Autowired - private FdnRouter fdnRouter; - @Autowired - private ToStringUtils toStringUtils; - @Autowired - private ErrorUtils errorUtils; - - @Override - public void query(Proxy.Topic request, StreamObserver responseObserver) { - String requestString = toStringUtils.toOneLineString(request); - logger.info("[ROUTE] querying route for topic: {}", requestString); - BasicMeta.Endpoint result = fdnRouter.route(request); - - if (result == null) { - NullPointerException e = new NullPointerException("no valid route for topic: " + requestString); - responseObserver.onError(errorUtils.toGrpcRuntimeException(e)); - } - - logger.info("[ROUTE] querying route result for topic: {}, result: {}", requestString, toStringUtils.toOneLineString(result)); - responseObserver.onNext(result); - responseObserver.onCompleted(); - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/helper/ModelValidationHelper.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/helper/ModelValidationHelper.java deleted file mode 100644 index c75e1e8a..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/helper/ModelValidationHelper.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.helper; - -import com.webank.ai.fate.api.networking.proxy.Proxy; -import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Component; - -@Component -public class ModelValidationHelper { - public boolean checkTopic(Proxy.Topic topic) { - return topic != null - && StringUtils.isNoneBlank(topic.getName(), topic.getPartyId(), topic.getRole()); - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/Cache.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/Cache.java deleted file mode 100644 index 082337a7..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/Cache.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.infra; - -import com.webank.ai.fate.api.core.BasicMeta; -import com.webank.ai.fate.api.networking.proxy.Proxy; - -import java.io.InputStream; - -public interface Cache { - public InputStream read(Proxy.Metadata metadata); - - public byte[] write(Proxy.Metadata metadata, byte[] data); - - public Proxy.Metadata getCachedMetadata(BasicMeta.Endpoint dst); -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/Pipe.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/Pipe.java deleted file mode 100644 index 5cf04693..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/Pipe.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.infra; - -import java.util.concurrent.TimeUnit; - -public interface Pipe { - public Object read(); - - public Object read(long timeout, TimeUnit unit); - - public void write(Object o); - - public void onError(Throwable t); - - public void onComplete(); - - public boolean isDrained(); - - public void setDrained(); - - public void close(); - - public boolean isClosed(); - - public void awaitClosed() throws InterruptedException; - - public void awaitClosed(long timeout, TimeUnit unit) throws InterruptedException; - - public boolean hasError(); - - public Throwable getError(); -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/ResultCallback.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/ResultCallback.java deleted file mode 100644 index 397d1f43..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/ResultCallback.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.infra; - -public interface ResultCallback { - public T getResult(); - - public void setResult(T t); - - public boolean hasResult(); -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/BasePipe.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/BasePipe.java deleted file mode 100644 index b72cfff6..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/BasePipe.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.infra.impl; - -import com.webank.ai.fate.networking.proxy.infra.Pipe; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -public abstract class BasePipe implements Pipe { - private final CountDownLatch closeLatch; - private volatile boolean closed = false; - private volatile boolean drained = false; - private volatile Throwable throwable = null; - - public BasePipe() { - this.closeLatch = new CountDownLatch(1); - } - - @Override - public Object read() { - throw new UnsupportedOperationException("Operation not implemented"); - } - - @Override - public void write(Object o) { - throw new UnsupportedOperationException("Operation not implemented"); - } - - @Override - public void onError(Throwable t) { - setDrained(); - close(); - this.throwable = t; - } - - @Override - public void onComplete() { - // setDrained(); - close(); - } - - @Override - public synchronized void close() { - setDrained(); - closeLatch.countDown(); - this.closed = true; - } - - @Override - public boolean isClosed() { - return closed; - } - - @Override - public void setDrained() { - this.drained = true; - } - - @Override - public boolean isDrained() { - return this.drained; - } - - @Override - public void awaitClosed() throws InterruptedException { - closeLatch.await(); - } - - @Override - public void awaitClosed(long timeout, TimeUnit unit) throws InterruptedException { - closeLatch.await(timeout, unit); - } - - @Override - public boolean hasError() { - return throwable != null; - } - - @Override - public Throwable getError() { - return throwable; - } - - public void checkNotClosed() { - if (isClosed()) { - throw new IllegalStateException("pipe closed"); - } - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/InputStreamOutputStreamNoStoragePipe.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/InputStreamOutputStreamNoStoragePipe.java deleted file mode 100644 index c0b27c2d..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/InputStreamOutputStreamNoStoragePipe.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.infra.impl; - -import com.google.common.io.ByteStreams; -import com.google.protobuf.ByteString; -import com.webank.ai.fate.api.networking.proxy.Proxy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.concurrent.TimeUnit; - -@Component -@Scope("prototype") -public class InputStreamOutputStreamNoStoragePipe extends BasePipe { - private static final int MAX_EMPTY_READ_COUNT = 10; - private static final int MAX_READ_COUNT = 50; - private static final Logger logger = LoggerFactory.getLogger(InputStreamOutputStreamNoStoragePipe.class); - private InputStream is; - private OutputStream os; - private Proxy.Metadata metadata; - private Proxy.Packet.Builder packetBuilder; - private Proxy.Data.Builder dataBuilder; - private boolean hasReadBefore; - private int inCounter = 0; - private int outCounter = 0; - private int trunkSize = 2 << 20; - - public InputStreamOutputStreamNoStoragePipe(InputStream is, OutputStream os, Proxy.Metadata metadata) { - super(); - this.is = is; - this.os = os; - this.metadata = metadata; - - this.packetBuilder = Proxy.Packet.newBuilder().setHeader(metadata); - this.dataBuilder = Proxy.Data.newBuilder(); - this.hasReadBefore = false; - } - - @Override - public Proxy.Packet read() { - logger.info("read for the {} time", ++inCounter); - - ByteString value = null; - ByteString cur = null; - Proxy.Packet packet = null; - int readCount = 0; - int curSize = 0; - - try { - value = ByteString.EMPTY; - while (readCount++ < MAX_READ_COUNT) { - curSize = trunkSize - value.size(); - cur = ByteString.readFrom(ByteStreams.limit(is, curSize)); - value = value.concat(cur); - if (value.size() >= trunkSize) { - break; - } - } - - if (hasReadBefore && value.size() == 0) { - return null; - } - - packet = packetBuilder - .setBody(dataBuilder.setValue(value)) - .build(); - - hasReadBefore = true; - } catch (IOException e) { - throw new RuntimeException(e); - } - - return packet; - } - - @Override - public Object read(long timeout, TimeUnit unit) { - throw new UnsupportedOperationException("Operation not supported"); - } - - @Override - public void write(Object o) { - logger.info("write for the {} time", ++outCounter); - if (o instanceof Proxy.Packet) { - Proxy.Packet packet = (Proxy.Packet) o; - try { - packet.getBody().getValue().writeTo(os); - } catch (IOException e) { - throw new RuntimeException(e); - } - } else { - throw new IllegalArgumentException("object o is of type: " + o.getClass().getCanonicalName() - + ", which is not of type " + Proxy.Packet.class.getCanonicalName()); - } - } - - @Override - public boolean isDrained() { - boolean result = true; - if (isClosed() || super.isDrained()) { - return result; - } - - try { - for (int i = 0; i < MAX_EMPTY_READ_COUNT; ++i) { - if (is.available() > 0) { - result = false; - break; - } - } - } catch (IOException e) { - throw new RuntimeException(e); - } - - if (result == true) { - this.setDrained(); - } - - return result; - } - - @Override - public synchronized void close() { - super.close(); - try { - os.flush(); - } catch (IOException ignore) { - ; - } -/* IOUtils.closeQuietly(is); - IOUtils.closeQuietly(os);*/ - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/InputStreamToPacketUnidirectionalPipe.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/InputStreamToPacketUnidirectionalPipe.java deleted file mode 100644 index 9090f03b..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/InputStreamToPacketUnidirectionalPipe.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.infra.impl; - -import com.google.common.io.ByteStreams; -import com.google.protobuf.ByteString; -import com.webank.ai.fate.api.networking.proxy.Proxy; -import org.apache.commons.io.IOUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -import java.io.IOException; -import java.io.InputStream; -import java.util.concurrent.TimeUnit; - -@Component -@Scope("prototype") -public class InputStreamToPacketUnidirectionalPipe extends BasePipe { - private static final int MAX_EMPTY_READ_COUNT = 10; - private static final int MAX_READ_COUNT = 50; - private static final int DEFAULT_TRUNK_SIZE = 2 << 20; - private static final Logger logger = LoggerFactory.getLogger(InputStreamToPacketUnidirectionalPipe.class); - private final int trunkSize; - private InputStream is; - private Proxy.Metadata metadata; - private Proxy.Packet.Builder packetBuilder; - private Proxy.Data.Builder dataBuilder; - private boolean hasReadBefore; - private int counter = 0; - - public InputStreamToPacketUnidirectionalPipe(InputStream is, Proxy.Metadata metadata) { - this(is, metadata, DEFAULT_TRUNK_SIZE); - } - - public InputStreamToPacketUnidirectionalPipe(InputStream is, Proxy.Metadata metadata, int trunkSize) { - super(); - this.is = is; - this.metadata = metadata; - this.trunkSize = trunkSize; - - this.packetBuilder = Proxy.Packet.newBuilder().setHeader(metadata); - this.dataBuilder = Proxy.Data.newBuilder(); - this.hasReadBefore = false; - } - - @Override - public Proxy.Packet read() { - logger.info("read for the {} time", ++counter); - - ByteString value = null; - ByteString cur = null; - Proxy.Packet packet = null; - int readCount = 0; - int curSize = 0; - - try { - value = ByteString.EMPTY; - while (readCount++ < MAX_READ_COUNT && !isDrained()) { - curSize = trunkSize - value.size(); - cur = ByteString.readFrom(ByteStreams.limit(is, curSize)); - value = value.concat(cur); - if (value.size() >= trunkSize) { - break; - } - } - - if (hasReadBefore && value.size() == 0) { - return null; - } - - packet = packetBuilder - .setBody(dataBuilder.setValue(value)) - .build(); - - hasReadBefore = true; - } catch (IOException e) { - throw new RuntimeException(e); - } - - return packet; - } - - @Override - public Object read(long timeout, TimeUnit unit) { - throw new UnsupportedOperationException("Operation not supported"); - } - - @Override - public void write(Object o) { - throw new UnsupportedOperationException("Operation not supported"); - } - - @Override - public boolean isDrained() { - boolean result = true; - if (isClosed() || super.isDrained()) { - return result; - } - - try { - for (int i = 0; i < MAX_EMPTY_READ_COUNT; ++i) { - if (is.available() > 0) { - result = false; - break; - } - } - } catch (IOException e) { - throw new RuntimeException(e); - } - - if (result == true) { - this.setDrained(); - } - - return result; - } - - @Override - public synchronized void close() { - super.close(); - IOUtils.closeQuietly(is); - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketQueuePipe.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketQueuePipe.java deleted file mode 100644 index 0c1711e6..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketQueuePipe.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.infra.impl; - - -import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.networking.proxy.factory.QueueFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; - -@Component -@Scope("prototype") -public class PacketQueuePipe extends BasePipe { - private static final Logger logger = LoggerFactory.getLogger(PacketQueuePipe.class); - @Autowired - private QueueFactory queueFactory; - private Proxy.Metadata metadata; - private LinkedBlockingQueue queue; - private int inCounter = 0; - private int outCounter = 0; - - public PacketQueuePipe() { - this(null); - } - - public PacketQueuePipe(Proxy.Metadata metadata) { - super(); - // this.queue = queueFactory.createConcurrentLinkedQueue(); - this.metadata = metadata; - } - - @PostConstruct - private void init() { - this.queue = queueFactory.createLinkedBlockingQueue(3000); - } - - @Override - public Proxy.Packet read() { - // logger.info("read for the {} time, queue size: {}", ++inCounter, queue.size()); - return queue.poll(); - } - - @Override - public Proxy.Packet read(long timeout, TimeUnit unit) { - Proxy.Packet result = null; - if (isDrained()) { - return result; - } - try { - result = queue.poll(timeout, unit); - } catch (InterruptedException e) { - logger.debug("read wait timeout"); - Thread.currentThread().interrupt(); - } - - return result; - } - - @Override - public void write(Object o) { - // logger.info("write for the {} time, queue size: {}", ++outCounter, queue.size()); - if (o instanceof Proxy.Packet) { - queue.add((Proxy.Packet) o); - } else { - throw new IllegalArgumentException("object o is of type: " + o.getClass().getCanonicalName() - + ", which is not of type " + Proxy.Packet.class.getCanonicalName()); - } - } - - @Override - public boolean isDrained() { - return super.isDrained() && queue.isEmpty(); - } - - @Override - public synchronized void close() { - super.close(); - } - - public int getQueueSize() { - return queue.size(); - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketQueueSingleResultPipe.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketQueueSingleResultPipe.java deleted file mode 100644 index 4ef36139..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketQueueSingleResultPipe.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.infra.impl; - -import com.webank.ai.fate.api.networking.proxy.Proxy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -@Component -@Scope("prototype") -public class PacketQueueSingleResultPipe extends PacketQueuePipe { - private static final Logger logger = LoggerFactory.getLogger(PacketQueueSingleResultPipe.class); - private Proxy.Metadata result; - - public PacketQueueSingleResultPipe() { - this(null); - } - - public PacketQueueSingleResultPipe(Proxy.Metadata metadata) { - super(metadata); - // this.queue = queueFactory.createConcurrentLinkedQueue(); - } - - public Proxy.Metadata getResult() { - return result; - } - - public void setResult(Proxy.Metadata metadata) { - if (hasResult()) { - throw new IllegalStateException("result has been set"); - } - this.result = metadata; - } - - public boolean hasResult() { - return result != null; - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketToOutputStreamUnidirectionalPipe.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketToOutputStreamUnidirectionalPipe.java deleted file mode 100644 index fb12a300..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/PacketToOutputStreamUnidirectionalPipe.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.infra.impl; - -import com.webank.ai.fate.api.networking.proxy.Proxy; -import org.apache.commons.io.IOUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.concurrent.TimeUnit; - -@Component -@Scope("prototype") -public class PacketToOutputStreamUnidirectionalPipe extends BasePipe { - private static final Logger logger = LoggerFactory.getLogger(PacketToOutputStreamUnidirectionalPipe.class); - private OutputStream os; - private int counter = 0; - - public PacketToOutputStreamUnidirectionalPipe(OutputStream os) { - super(); - this.os = os; - } - - @Override - public Object read() { - throw new UnsupportedOperationException("Operation not supported"); - } - - @Override - public Object read(long timeout, TimeUnit unit) { - throw new UnsupportedOperationException("Operation not supported"); - } - - @Override - public void write(Object o) { - logger.info("write for the {} time", ++counter); - if (o instanceof Proxy.Packet) { - Proxy.Packet packet = (Proxy.Packet) o; - try { - packet.getBody().getValue().writeTo(os); - } catch (IOException e) { - throw new RuntimeException(e); - } - } else { - throw new IllegalArgumentException("object o is of type: " + o.getClass().getCanonicalName() - + ", which is not of type " + Proxy.Packet.class.getCanonicalName()); - } - } - - @Override - public boolean isDrained() { - throw new UnsupportedOperationException("Operation not supported"); - } - - @Override - public void close() { - try { - os.flush(); - } catch (IOException ignore) { - ; - } - IOUtils.closeQuietly(os); - super.close(); - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/SingleResultCallback.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/SingleResultCallback.java deleted file mode 100644 index b9b313ab..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/infra/impl/SingleResultCallback.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.infra.impl; - -import com.webank.ai.fate.networking.proxy.infra.ResultCallback; - -public class SingleResultCallback implements ResultCallback { - private T result; - - @Override - public T getResult() { - return result; - } - - @Override - public void setResult(T t) { - result = t; - } - - @Override - public boolean hasResult() { - return result != null; - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/manager/ExecutorManager.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/manager/ExecutorManager.java deleted file mode 100644 index a0eb37fd..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/manager/ExecutorManager.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.manager; - - -import com.webank.ai.fate.networking.proxy.util.ToStringUtils; -import org.apache.commons.text.StringSubstitutor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -@Component -public class ExecutorManager { - private static final String LOG_TEMPLATE; - private static final Logger logger = LoggerFactory.getLogger("stat"); - - static { - LOG_TEMPLATE = "executor stat: pool name: ${name}, " + - "size: ${size}, " + - "active count: ${activeCount}"; - } - - @Autowired - private ThreadPoolTaskExecutor asyncThreadPool; - @Autowired - private ThreadPoolTaskExecutor grpcServiceExecutor; - @Autowired - private ThreadPoolTaskExecutor grpcClientExecutor; - @Autowired - private ThreadPoolTaskScheduler routineScheduler; - @Autowired - private ToStringUtils toStringUtils; - private List threadPoolExecutors; - - public ExecutorManager() { - threadPoolExecutors = new LinkedList<>(); - } - - @PostConstruct - private void init() { - threadPoolExecutors.add(asyncThreadPool); - threadPoolExecutors.add(grpcServiceExecutor); - threadPoolExecutors.add(grpcClientExecutor); - } - - public void statExecutor() { - logger.info("------------ executor stat ------------"); - - Map valuesMap = new HashMap<>(10); - StringSubstitutor stringSubstitutor = new StringSubstitutor(valuesMap); - - for (ThreadPoolTaskExecutor executor : threadPoolExecutors) { - valuesMap.clear(); - valuesMap.put("name", executor.getThreadNamePrefix().replace("-", "")); - valuesMap.put("size", String.valueOf(executor.getPoolSize())); - valuesMap.put("activeCount", String.valueOf(executor.getActiveCount())); - - String log = stringSubstitutor.replace(LOG_TEMPLATE); - logger.info(log); - } - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/manager/ServerConfManager.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/manager/ServerConfManager.java deleted file mode 100644 index 22ba4b44..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/manager/ServerConfManager.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.manager; - -import com.webank.ai.fate.networking.proxy.model.ServerConf; -import org.springframework.stereotype.Component; - -@Component -public class ServerConfManager { - private ServerConf serverConf; - - public ServerConf getServerConf() { - return serverConf; - } - - public void setServerConf(ServerConf serverConf) { - this.serverConf = serverConf; - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/manager/StatsManager.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/manager/StatsManager.java deleted file mode 100644 index 17fcf440..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/manager/StatsManager.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.manager; - - -import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.networking.proxy.model.StreamStat; -import com.webank.ai.fate.networking.proxy.util.ToStringUtils; -import io.grpc.netty.shaded.io.netty.util.internal.ConcurrentSet; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.commons.text.StringSubstitutor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.text.NumberFormat; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -@Component -public class StatsManager { - private static final String LOG_TEMPLATE; - private static final Logger logger = LoggerFactory.getLogger("stat"); - - static { - LOG_TEMPLATE = "streaming stat: Proxy.Metadata [${metadata}], " + - "bytes sent: ${bytesSent}, " + - "avg speed: ${avgSpeed} byte/s, " + - "last updated: ${lastUpdateTime}, " + - "started: ${startTime}, " + - "time eclapsed: ${secEclapsed}, " + - "status: ${status}, " + - "finished: ${isFinished}"; - } - - @Autowired - private ToStringUtils toStringUtils; - private ConcurrentSet streamStats; - private ReadWriteLock streamStatsRwLock; - private NumberFormat numberFormat; - private SimpleDateFormat simpleDateFormat; - - public StatsManager() { - this.streamStats = new ConcurrentSet<>(); - this.streamStatsRwLock = new ReentrantReadWriteLock(); - - this.numberFormat = NumberFormat.getNumberInstance(Locale.CHINA); - this.simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS"); - } - - public void add(StreamStat streamStat) { - try { - streamStatsRwLock.writeLock().lock(); - streamStats.add(streamStat); - } catch (Exception e) { - logger.error("unexpected error: {}", ExceptionUtils.getStackTrace(e)); - } finally { - streamStatsRwLock.writeLock().unlock(); - } - } - - public void logAllStatus() { - logger.info("------------ streaming stat ------------"); - - if (streamStats.size() <= 0) { - return; - } - long now = System.currentTimeMillis(); - - ConcurrentSet oldStreamStats = null; - try { - streamStatsRwLock.writeLock().lock(); - oldStreamStats = streamStats; - streamStats = new ConcurrentSet<>(); - } catch (Exception e) { - logger.error(ExceptionUtils.getStackTrace(e)); - } finally { - streamStatsRwLock.writeLock().unlock(); - } - - Map valuesMap = new ConcurrentHashMap<>(10); - StringSubstitutor stringSubstitutor = new StringSubstitutor(valuesMap); - - int logCount = 0; - int notFinishedCount = 0; - int notRemovedCount = 0; - - for (StreamStat streamStat : oldStreamStats) { - valuesMap.clear(); - - Proxy.Metadata metadata = streamStat.getMetadata(); - - valuesMap.put("metadata", toStringUtils.toOneLineString(metadata)); - valuesMap.put("bytesSent", numberFormat.format(streamStat.getSize())); - valuesMap.put("lastUpdateTime", simpleDateFormat.format(new Date(streamStat.getLastUpdateTimestamp()))); - valuesMap.put("startTime", simpleDateFormat.format(streamStat.getStartDate())); - - long avgSpeedTimeline = 0; - - if (streamStat.canBeDeleted()) { - valuesMap.put("isFinished", "true"); - avgSpeedTimeline = streamStat.getLastUpdateTimestamp(); - } else { - valuesMap.put("isFinished", "false"); - avgSpeedTimeline = now; - ++notFinishedCount; - - if (now - streamStat.getLastUpdateTimestamp() <= 60000) { - streamStats.add(streamStat); - ++notRemovedCount; - } - } - - long secEclapsed = (avgSpeedTimeline - streamStat.getStartTimestamp()) / 1000; - if (secEclapsed <= 0) { - secEclapsed = 1; - } - - valuesMap.put("secEclapsed", numberFormat.format(secEclapsed)); - valuesMap.put("avgSpeed", numberFormat.format(streamStat.getSize() / secEclapsed)); - valuesMap.put("status", streamStat.getStatus()); - - String log = stringSubstitutor.replace(LOG_TEMPLATE); - logger.info(log); - ++logCount; - } - - logger.info("stream logCount: {}, not finished: {}, not removed: {}", - logCount, notFinishedCount, notRemovedCount); - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/model/PipeHandlerInfo.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/model/PipeHandlerInfo.java deleted file mode 100644 index 6bad2c99..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/model/PipeHandlerInfo.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.model; - -import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.networking.proxy.event.model.PipeHandleNotificationEvent; -import com.webank.ai.fate.networking.proxy.infra.Pipe; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -@Component -@Scope("prototype") -public class PipeHandlerInfo { - private PipeHandleNotificationEvent.Type type; - private Proxy.Metadata metadata; - private Pipe pipe; - private Proxy.Packet packet; - - public PipeHandlerInfo(PipeHandleNotificationEvent.Type type, Proxy.Metadata metadata, Pipe pipe) { - this.type = type; - this.metadata = metadata; - this.pipe = pipe; - } - - public PipeHandlerInfo(PipeHandleNotificationEvent.Type type, Proxy.Packet packet, Pipe pipe) { - this.type = type; - this.packet = packet; - this.metadata = packet.getHeader(); - this.pipe = pipe; - } - - public PipeHandleNotificationEvent.Type getType() { - return type; - } - - public void setType(PipeHandleNotificationEvent.Type type) { - this.type = type; - } - - public Proxy.Metadata getMetadata() { - return metadata; - } - - public void setMetadata(Proxy.Metadata metadata) { - this.metadata = metadata; - } - - public Pipe getPipe() { - return pipe; - } - - public void setPipe(Pipe pipe) { - this.pipe = pipe; - } - - public Proxy.Packet getPacket() { - return packet; - } - - public void setPacket(Proxy.Packet packet) { - this.packet = packet; - } - - @Override - public String toString() { - return "PipeHandlerInfo{" + - "type=" + type + - ", metadata=" + metadata + - ", pipe=" + pipe + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof PipeHandlerInfo)) { - return false; - } - - PipeHandlerInfo that = (PipeHandlerInfo) o; - - if (type != that.type) { - return false; - } - if (!metadata.equals(that.metadata)) { - return false; - } - return pipe.equals(that.pipe); - } - - @Override - public int hashCode() { - int result = type.hashCode(); - result = 31 * result + metadata.hashCode(); - result = 31 * result + pipe.hashCode(); - return result; - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/model/ServerConf.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/model/ServerConf.java deleted file mode 100644 index 60c25f73..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/model/ServerConf.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.model; - - -import com.webank.ai.fate.networking.proxy.factory.PipeFactory; -import com.webank.ai.fate.networking.proxy.infra.Pipe; -import org.springframework.stereotype.Component; - -import java.util.Properties; - -@Component -public class ServerConf { - Properties properties; - private String ip; - private int port; - private String routeTablePath; - private boolean isSecureServer; - private String serverCrtPath; - private String serverKeyPath; - private boolean isSecureClient; - private String caCrtPath; - private Pipe pipe; - private PipeFactory pipeFactory; - private String coordinator; - private String logPropertiesPath; - private boolean isAuditEnabled; - private boolean isNeighbourInsecureChannelEnabled; - private boolean isDebugEnabled; - - public Properties getProperties() { - return properties; - } - - public void setProperties(Properties properties) { - this.properties = properties; - } - - public String getData(String key, String defaultValue) { - - if (properties != null) { - return properties.getProperty(key, defaultValue); - } - return null; - } - - @Override - public String toString() { - return "ServerConf{" + - "ip='" + ip + '\'' + - ", port=" + port + - ", routeTablePath='" + routeTablePath + '\'' + - ", isSecureServer=" + isSecureServer + - ", serverCrtPath='" + serverCrtPath + '\'' + - ", serverKeyPath='" + serverKeyPath + '\'' + - ", isSecureClient=" + isSecureClient + - ", caCrtPath='" + caCrtPath + '\'' + - ", pipe=" + pipe + - ", pipeFactory=" + pipeFactory + - ", coordinator='" + coordinator + '\'' + - ", logPropertiesPath='" + logPropertiesPath + '\'' + - ", isAuditEnabled=" + isAuditEnabled + - ", isNeighbourInsecureChannelEnabled=" + isNeighbourInsecureChannelEnabled + - ", isDebugEnabled=" + isDebugEnabled + - '}'; - } - - public String getIp() { - return ip; - } - - public void setIp(String ip) { - this.ip = ip; - } - - public int getPort() { - return port; - } - - public void setPort(int port) { - this.port = port; - } - - public String getRouteTablePath() { - return routeTablePath; - } - - public void setRouteTablePath(String routeTablePath) { - this.routeTablePath = routeTablePath; - } - - public boolean isSecureServer() { - return isSecureServer; - } - - public void setSecureServer(boolean secureServer) { - isSecureServer = secureServer; - } - - public String getServerCrtPath() { - return serverCrtPath; - } - - public void setServerCrtPath(String serverCrtPath) { - this.serverCrtPath = serverCrtPath; - } - - public String getServerKeyPath() { - return serverKeyPath; - } - - public void setServerKeyPath(String serverKeyPath) { - this.serverKeyPath = serverKeyPath; - } - - public boolean isSecureClient() { - return isSecureClient; - } - - public void setSecureClient(boolean secureClient) { - isSecureClient = secureClient; - } - - public String getCaCrtPath() { - return caCrtPath; - } - - public void setCaCrtPath(String caCrtPath) { - this.caCrtPath = caCrtPath; - } - - public Pipe getPipe() { - return pipe; - } - - public void setPipe(Pipe pipe) { - this.pipe = pipe; - } - - public PipeFactory getPipeFactory() { - return pipeFactory; - } - - public void setPipeFactory(PipeFactory pipeFactory) { - this.pipeFactory = pipeFactory; - } - - public String getCoordinator() { - return coordinator; - } - - public void setCoordinator(String coordinator) { - this.coordinator = coordinator; - } - - public String getLogPropertiesPath() { - return logPropertiesPath; - } - - public void setLogPropertiesPath(String logPropertiesPath) { - this.logPropertiesPath = logPropertiesPath; - } - - public boolean isAuditEnabled() { - return isAuditEnabled; - } - - public void setAuditEnabled(boolean auditEnabled) { - this.isAuditEnabled = auditEnabled; - } - - public boolean isNeighbourInsecureChannelEnabled() { - return isNeighbourInsecureChannelEnabled; - } - - public void setNeighbourInsecureChannelEnabled(boolean neighbourInsecureChannelEnabled) { - isNeighbourInsecureChannelEnabled = neighbourInsecureChannelEnabled; - } - - public boolean isDebugEnabled() { - return isDebugEnabled; - } - - public void setDebugEnabled(boolean debugEnabled) { - isDebugEnabled = debugEnabled; - } - -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/model/StreamStat.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/model/StreamStat.java deleted file mode 100644 index b0c0e402..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/model/StreamStat.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.model; - - -import com.webank.ai.fate.api.networking.proxy.Proxy; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -import java.util.Date; - -@Component -@Scope("prototype") -public class StreamStat { - public static final String INIT = "init"; - public static final String RUNNING = "running"; - public static final String COMPLETED = "completed"; - public static final String ERROR = "error"; - public static final String PUSH = "push"; - public static final String PULL = "pull"; - public static final String UNARY_CALL = "unary_call"; - private final long startTimestamp; - private volatile long size; - private long lastUpdateTimestamp; - private Date startDate; - private String status; - private String operation; - private Proxy.Metadata metadata; - - public StreamStat(Proxy.Metadata metadata, String operation) { - long now = System.currentTimeMillis(); - size = 0L; - startTimestamp = now; - lastUpdateTimestamp = now; - startDate = new Date(now); - status = INIT; - - this.metadata = metadata; - this.operation = operation; - } - - public void increment(long size) { - this.size += size; - this.lastUpdateTimestamp = System.currentTimeMillis(); - this.status = RUNNING; - } - - public void onError() { - this.setStatus(ERROR); - } - - public void onComplete() { - this.setStatus(COMPLETED); - } - - public boolean canBeDeleted() { - return COMPLETED.equals(status) || ERROR.equals(status); - } - - public long getSize() { - return size; - } - - public long getStartTimestamp() { - return startTimestamp; - } - - public long getLastUpdateTimestamp() { - return lastUpdateTimestamp; - } - - public Date getStartDate() { - return startDate; - } - - public String getStatus() { - return status; - } - - public void setStatus(String status) { - this.status = status; - this.lastUpdateTimestamp = System.currentTimeMillis(); - } - - public String getOperation() { - return operation; - } - - public Proxy.Metadata getMetadata() { - return metadata; - } - - @Override - public String toString() { - return "StreamStat{" + - "size=" + size + - ", startTimestamp=" + startTimestamp + - ", lastUpdateTimestamp=" + lastUpdateTimestamp + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof StreamStat)) { - return false; - } - - StreamStat that = (StreamStat) o; - - if (size != that.size) { - return false; - } - if (startTimestamp != that.startTimestamp) { - return false; - } - return lastUpdateTimestamp == that.lastUpdateTimestamp; - } - - @Override - public int hashCode() { - int result = (int) (size ^ (size >>> 32)); - result = 31 * result + (int) (startTimestamp ^ (startTimestamp >>> 32)); - result = 31 * result + (int) (lastUpdateTimestamp ^ (lastUpdateTimestamp >>> 32)); - return result; - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/security/SimpleTrustAllCertsManagerFactory.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/security/SimpleTrustAllCertsManagerFactory.java deleted file mode 100644 index 0a2eb566..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/security/SimpleTrustAllCertsManagerFactory.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.security; - -import io.grpc.netty.shaded.io.netty.handler.ssl.util.SimpleTrustManagerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -import javax.net.ssl.ManagerFactoryParameters; -import javax.net.ssl.TrustManager; -import java.security.KeyStore; - -@Component -@Scope("prototype") -public class SimpleTrustAllCertsManagerFactory extends SimpleTrustManagerFactory { - @Autowired - private TrustAllCertsManager trustAllCertsManager; - - @Override - protected void engineInit(KeyStore keyStore) throws Exception { - - } - - @Override - protected void engineInit(ManagerFactoryParameters managerFactoryParameters) throws Exception { - - } - - @Override - protected TrustManager[] engineGetTrustManagers() { - return new TrustManager[]{trustAllCertsManager}; - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/security/TrustAllCertsManager.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/security/TrustAllCertsManager.java deleted file mode 100644 index 97d5af52..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/security/TrustAllCertsManager.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.security; - -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -import javax.net.ssl.X509TrustManager; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; - -@Component -@Scope("prototype") -public class TrustAllCertsManager implements X509TrustManager { - - @Override - public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { - - } - - @Override - public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { - - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/service/CascadedCaller.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/service/CascadedCaller.java deleted file mode 100644 index cd9bc5d1..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/service/CascadedCaller.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.service; - -import com.google.common.base.Preconditions; -import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.networking.proxy.event.model.PipeHandleNotificationEvent; -import com.webank.ai.fate.networking.proxy.grpc.client.DataTransferPipedClient; -import com.webank.ai.fate.networking.proxy.infra.Pipe; -import com.webank.ai.fate.networking.proxy.model.PipeHandlerInfo; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Scope; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Component; - -@Component -@Scope("prototype") -public class CascadedCaller implements Runnable { - @Autowired - private DataTransferPipedClient client; - - private PipeHandlerInfo pipeHandlerInfo; - - public CascadedCaller() { - } - - public CascadedCaller(PipeHandlerInfo pipeHandlerInfo) { - this.pipeHandlerInfo = pipeHandlerInfo; - } - - public void setPipeHandlerInfo(PipeHandlerInfo pipeHandlerInfo) { - this.pipeHandlerInfo = pipeHandlerInfo; - } - - @Override - @Async - public void run() { - Preconditions.checkNotNull(pipeHandlerInfo); - - Pipe pipe = pipeHandlerInfo.getPipe(); - - Proxy.Metadata metadata = pipeHandlerInfo.getMetadata(); - PipeHandleNotificationEvent.Type type = pipeHandlerInfo.getType(); - - if (PipeHandleNotificationEvent.Type.PUSH == type) { - client.push(metadata, pipe); - } else if (PipeHandleNotificationEvent.Type.PULL == type) { - client.pull(metadata, pipe); - } else { - client.unaryCall(pipeHandlerInfo.getPacket(), pipe); - } - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/service/ConfFileBasedFdnRouter.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/service/ConfFileBasedFdnRouter.java deleted file mode 100644 index 45742a9a..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/service/ConfFileBasedFdnRouter.java +++ /dev/null @@ -1,440 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.service; - -import com.google.common.base.Preconditions; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.stream.JsonReader; -import com.webank.ai.fate.api.core.BasicMeta; -import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.networking.proxy.model.ServerConf; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.security.SecureRandom; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -@Component -public class ConfFileBasedFdnRouter implements FdnRouter { - private static final String IP = "ip"; - private static final String PORT = "port"; - private static final String HOSTNAME = "hostname"; - private static final String DEFAULT = "default"; - private static final Logger logger = LoggerFactory.getLogger(ConfFileBasedFdnRouter.class); - private final BasicMeta.Endpoint emptyEndpoint; - @Autowired - private ServerConf serverConf; - private Map>> routeTable; - private Map topicEndpointMapping; - private BasicMeta.Endpoint.Builder endpointBuilder; - private JsonObject confJson; - private Set routeNeighbours; - private Set intranetEndpoints; - private JsonParser jsonParser; - private Map> allow; - private Map> deny; - private boolean defaultAllow; - private String routeTableFilename; - private SecureRandom random; - - public ConfFileBasedFdnRouter() { - routeTable = new ConcurrentHashMap<>(); - topicEndpointMapping = new WeakHashMap<>(); - endpointBuilder = BasicMeta.Endpoint.newBuilder(); - routeNeighbours = new HashSet<>(); - intranetEndpoints = new HashSet<>(); - - allow = new ConcurrentHashMap<>(); - deny = new ConcurrentHashMap<>(); - defaultAllow = false; - - random = new SecureRandom(); - emptyEndpoint = endpointBuilder.build(); - } - - public ConfFileBasedFdnRouter(String filename) { - this(); - setRouteTable(filename); - } - - public Map>> getRouteTable() { - return routeTable; - } - - @Override - public synchronized void setRouteTable(String filename) { - if (StringUtils.isBlank(routeTableFilename)) { - logger.info("setting routeTable path to {}", filename); - this.routeTableFilename = filename; - init(); - } else { - logger.warn("trying to reset routeTable path. current path: {}, tried path: {}", - routeTableFilename, filename); - } - } - - @Override - public boolean isAllowed(Proxy.Topic from, Proxy.Topic to) { - if (hasRule(deny, from, to)) { - return false; - } else if (hasRule(allow, from, to)) { - return true; - } else { - return defaultAllow; - } - } - - @Override - public boolean isIntranet(BasicMeta.Endpoint endpoint) { - return intranetEndpoints.contains(endpoint); - } - - public void init() { - jsonParser = new JsonParser(); - - JsonReader jsonReader = null; - try { - jsonReader = new JsonReader(new FileReader(routeTableFilename)); - confJson = jsonParser.parse(jsonReader).getAsJsonObject(); - } catch (FileNotFoundException e) { - logger.error("File not found: {}", routeTableFilename); - throw new RuntimeException(e); - } finally { - if (jsonReader != null) { - try { - jsonReader.close(); - } catch (IOException ignore) { - ; - } - } - } - - initRouteTable(confJson.getAsJsonObject("route_table")); - initPermission(confJson.getAsJsonObject("permission")); - - logger.info("refreshed route table at: {}", routeTableFilename); - } - - @Override - public BasicMeta.Endpoint route(Proxy.Topic topic) { - Preconditions.checkNotNull(topic, "topic cannot be null"); - String topicName = topic.getName(); - String coordinator = topic.getPartyId(); - String role = topic.getRole(); -// topic -// -// if(role.equals("serving")&& useServiceManagment){ -// com.webank.ai.fate.networking.Proxy.zookeeperRegistry.l -// -// } - - - BasicMeta.Endpoint result = topicEndpointMapping.getOrDefault(topic, null); - - // 1st priority: routed and fully match - if (result != null) { - return result; - } - - - // todo: add callback check - BasicMeta.Endpoint callback = topic.getCallback(); - boolean overridden = false; - - // 2nd priority: callback - if (callback != null && serverConf.getCoordinator().equals(coordinator)) { - String ip = callback.getIp(); - String hostname = callback.getHostname(); - - if (!StringUtils.isAllBlank(ip, hostname) - && (routeNeighbours.contains(ip) || routeNeighbours.contains(hostname))) { - result = callback; - overridden = true; - } - } - Proxy.Topic noCallbackTopic = null; - // 3rd priority: routed endpoint - if (!overridden) { - noCallbackTopic = getNoCallbackTopic(topic); - result = topicEndpointMapping.getOrDefault(noCallbackTopic, null); - - if (result != null) { - overridden = true; - } - } - - // 4th priority: route table - if (!overridden) { - if (StringUtils.isAnyBlank(topicName, coordinator, role)) { - throw new IllegalArgumentException("one of topic name, coordinator, role is null. topic: " + topic); - } - - Map> roleTable = - routeTable.getOrDefault(coordinator, routeTable.getOrDefault(DEFAULT, null)); - if (roleTable == null) { - /* throw new IllegalStateException("No available endpoint for the coordinator. " + - "Considering adding a default endpoint?"); - */ - return null; - } - - - List endpoints = - roleTable.getOrDefault(role, roleTable.getOrDefault(DEFAULT, null)); - - if (endpoints == null || endpoints.isEmpty()) { - return null; - /* throw new IllegalStateException("No available endpoint for this role. " + - "Considering adding a default endpoint, or check if the list is empty?"); - */ - } - - // actual route algorithm, using a hash pattern now - int len = endpoints.size(); - int hashCode = topic.hashCode(); - - int pos = Math.abs(hashCode); - if (pos == Integer.MIN_VALUE) { - pos = 0; - } - - pos = pos % len; - - logger.info("route: hashcode: {}, len: {}, pos: {}", topic.hashCode(), len, pos); - - result = endpoints.get(pos); - } - - topicEndpointMapping.put(topic, result); - if (noCallbackTopic != null && !topicEndpointMapping.containsKey(noCallbackTopic)) { - topicEndpointMapping.put(noCallbackTopic, result); - } - - return result; - } - - private Proxy.Topic getNoCallbackTopic(Proxy.Topic topic) { - Preconditions.checkNotNull(topic); - - Proxy.Topic.Builder topicBuilder = Proxy.Topic.newBuilder().mergeFrom(topic); - Proxy.Topic result = topicBuilder.setCallback(emptyEndpoint).build(); - - return result; - } - - private void initRouteTable(JsonObject confJson) { - Map>> newRouteTable = new ConcurrentHashMap<>(); - Set newRouteNeighbours = new HashSet<>(); - Set newIntranetEndpoints = new HashSet<>(); - - boolean isIntranet = false; - // loop through coordinator - for (Map.Entry coordinatorEntry : confJson.entrySet()) { - String coordinatorKey = coordinatorEntry.getKey(); - JsonObject coordinatorValue = coordinatorEntry.getValue().getAsJsonObject(); - - Map> roleTable = newRouteTable.get(coordinatorKey); - if (roleTable == null) { - roleTable = new ConcurrentHashMap<>(4); - newRouteTable.put(coordinatorKey, roleTable); - } - - if (coordinatorKey.equals(serverConf.getCoordinator())) { - isIntranet = true; - } - - // loop through role in coordinator - for (Map.Entry roleEntry : coordinatorValue.entrySet()) { - String roleKey = roleEntry.getKey(); - JsonArray roleValue = roleEntry.getValue().getAsJsonArray(); - - List endpoints = roleTable.get(roleKey); - if (endpoints == null) { - endpoints = new ArrayList<>(); - roleTable.put(roleKey, endpoints); - } - - // loop through endpoints - for (JsonElement endpointElement : roleValue) { - endpointBuilder.clear(); - JsonObject endpointJson = endpointElement.getAsJsonObject(); - - if (endpointJson.has(IP)) { - String targetIp = endpointJson.get(IP).getAsString(); - endpointBuilder.setIp(targetIp); - newRouteNeighbours.add(targetIp); - } - - if (endpointJson.has(PORT)) { - int targetPort = endpointJson.get(PORT).getAsInt(); - endpointBuilder.setPort(targetPort); - } - - if (endpointJson.has(HOSTNAME)) { - String targetHostname = endpointJson.get(HOSTNAME).getAsString(); - endpointBuilder.setHostname(targetHostname); - newRouteNeighbours.add(targetHostname); - } - - BasicMeta.Endpoint endpoint = endpointBuilder.build(); - - endpoints.add(endpoint); - if (isIntranet) { - newIntranetEndpoints.add(endpoint); - } - } - } - } - - routeTable = newRouteTable; - routeNeighbours = newRouteNeighbours; - intranetEndpoints = newIntranetEndpoints; - } - - private void initPermission(JsonObject confJson) { - boolean newDefaultAllow = false; - Map> newAllow = new ConcurrentHashMap<>(); - Map> newDeny = new ConcurrentHashMap<>(); - - if (confJson.has("default_allow")) { - newDefaultAllow = confJson.getAsJsonPrimitive("default_allow").getAsBoolean(); - } - - if (confJson.has("allow")) { - initPermissionType(newAllow, confJson.getAsJsonArray("allow")); - } - - if (confJson.has("deny")) { - initPermissionType(newDeny, confJson.getAsJsonArray("deny")); - } - - defaultAllow = newDefaultAllow; - allow = newAllow; - deny = newDeny; - } - - private void initPermissionType(Map> target, JsonArray conf) { - for (JsonElement pairElement : conf) { - JsonObject pair = pairElement.getAsJsonObject(); - - JsonObject from = pair.getAsJsonObject("from"); - Proxy.Topic fromTopic = createTopicFromJson(from); - - JsonObject to = pair.getAsJsonObject("to"); - Proxy.Topic toTopic = createTopicFromJson(to); - - if (!target.containsKey(fromTopic)) { - target.put(fromTopic, new HashSet<>()); - } - Set toTopics = target.get(fromTopic); - toTopics.add(toTopic); - } - } - - private Proxy.Topic createTopicFromJson(JsonObject json) { - Proxy.Topic.Builder topicBuilder = Proxy.Topic.newBuilder(); - if (json.has("coordinator")) { - topicBuilder.setPartyId(json.get("coordinator").getAsString()); - } - - if (json.has("role")) { - topicBuilder.setRole(json.get("role").getAsString()); - } - - return topicBuilder.build(); - } - - private boolean hasRule(Map> target, Proxy.Topic from, Proxy.Topic to) { - boolean result = false; - - if (target == null || target.isEmpty()) { - return result; - } - - Proxy.Topic.Builder fromBuilder = Proxy.Topic.newBuilder(); - Proxy.Topic.Builder toBuilder = Proxy.Topic.newBuilder(); - - Proxy.Topic fromValidator = fromBuilder.setPartyId(from.getPartyId()).setRole(from.getRole()).build(); - Proxy.Topic toValidator = toBuilder.setPartyId(to.getPartyId()).setRole(to.getRole()).build(); - - Set rules = null; - if (target.containsKey(fromValidator)) { - rules = target.get(fromValidator); - } - - int stage = 0; - while (stage < 3 && rules == null) { - switch (stage) { - case 0: - break; - case 1: - fromValidator = fromBuilder.setRole("*").build(); - break; - case 2: - fromValidator = fromBuilder.setPartyId("*").build(); - break; - default: - throw new IllegalStateException("Illegal state when checking from rule"); - } - - if (target.containsKey(fromValidator)) { - rules = target.get(fromValidator); - } - - ++stage; - } - - if (rules == null) { - return result; - } - - - stage = 0; - while (stage < 3 && !result) { - switch (stage) { - case 0: - break; - case 1: - toValidator = toBuilder.setRole("*").build(); - break; - case 2: - toValidator = toBuilder.setPartyId("*").build(); - break; - default: - throw new IllegalStateException("Illegal state when checking to rule"); - } - - if (rules.contains(toValidator)) { - result = true; - } - - ++stage; - } - - return result; - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/service/FdnRouter.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/service/FdnRouter.java deleted file mode 100644 index 3e7e40a5..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/service/FdnRouter.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.service; - - -import com.webank.ai.fate.api.core.BasicMeta; -import com.webank.ai.fate.api.networking.proxy.Proxy; - -public interface FdnRouter { - public BasicMeta.Endpoint route(Proxy.Topic topic); - - public void setRouteTable(String filename); - - public boolean isAllowed(Proxy.Topic from, Proxy.Topic to); - - public boolean isIntranet(BasicMeta.Endpoint endpoint); - - -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java deleted file mode 100644 index b653edbb..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/AuthUtils.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.util; - -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.stream.JsonReader; -import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.networking.proxy.manager.ServerConfManager; -import com.webank.ai.fate.serving.core.bean.Dict; -import com.webank.ai.fate.serving.core.utils.EncryptUtils; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.util.*; - -@Component -public class AuthUtils { - private static final Logger logger = LoggerFactory.getLogger(AuthUtils.class); - private static String confFilePath = System.getProperty(Dict.AUTH_FILE); - private static Map KEY_SECRET_MAP = new HashMap<>(); - private static Map PARTYID_KEY_MAP = new HashMap<>(); - private static int validRequestTimeoutSecond = 10; - private static String applyId = ""; - private static boolean ifUseAuth = false; - private static String selfPartyId = ""; - @Autowired - private ToStringUtils toStringUtils; - - @Autowired - private ServerConfManager serverConfManager; - - @Scheduled(fixedRate = 10000) - public void loadConfig(){ - JsonParser jsonParser = new JsonParser(); - JsonReader jsonReader = null; - JsonObject jsonObject = null; - - if (StringUtils.isEmpty(confFilePath)) { - confFilePath = serverConfManager.getServerConf().getData(Dict.AUTH_FILE, ""); - } - - try { - jsonReader = new JsonReader(new FileReader(confFilePath)); - jsonObject = jsonParser.parse(jsonReader).getAsJsonObject(); - } catch (FileNotFoundException e) { - logger.error("File not found: {}", confFilePath); - throw new RuntimeException(e); - } finally { - if (jsonReader != null) { - try { - jsonReader.close(); - } catch (IOException ignore) { - } - } - } - selfPartyId = jsonObject.get("self_party_id").getAsString(); - ifUseAuth = jsonObject.get("if_use_auth").getAsBoolean(); - validRequestTimeoutSecond = jsonObject.get("request_expire_seconds").getAsInt(); - applyId = jsonObject.get("apply_id").getAsString(); - - JsonArray jsonArray = jsonObject.getAsJsonArray("access_keys"); - Gson gson = new Gson(); - List allowKeys = gson.fromJson(jsonArray, ArrayList.class); - KEY_SECRET_MAP.clear(); - for (Map allowKey : allowKeys) { - KEY_SECRET_MAP.put(allowKey.get("app_key").toString(), allowKey.get("app_secret").toString()); - PARTYID_KEY_MAP.put(allowKey.get("party_id").toString(), allowKey.get("app_key").toString()); - } - logger.debug("refreshed auth cfg using file {}.", confFilePath); - } - - private String getSecret(String appKey) { - return KEY_SECRET_MAP.get(appKey); - } - - private String getAppKey(String partyId) { - return PARTYID_KEY_MAP.get(partyId); - } - - private String calSignature(Proxy.Metadata header, Proxy.Data body, long timestamp, String appKey) throws Exception { - String signature = ""; - String appSecret = getSecret(appKey); - if (StringUtils.isEmpty(appSecret)) { - logger.error("appSecret not found"); - return signature; - } - String encryptText = String.valueOf(timestamp) + "\n" - + toStringUtils.toOneLineString(header) + "\n" - + toStringUtils.toOneLineString(body); - encryptText = new String(encryptText.getBytes(), EncryptUtils.UTF8); - signature = Base64.getEncoder().encodeToString(EncryptUtils.hmacSha1Encrypt(encryptText, appSecret)); - return signature; - } - - public Proxy.Packet addAuthInfo(Proxy.Packet packet) throws Exception { - if(!StringUtils.equals(selfPartyId, packet.getHeader().getDst().getPartyId())) { - Proxy.Packet.Builder packetBuilder = packet.toBuilder(); - Proxy.AuthInfo.Builder authBuilder = packetBuilder.getAuthBuilder(); - - long timestamp = System.currentTimeMillis(); - authBuilder.setTimestamp(timestamp); - authBuilder.setApplyId(applyId); - - if(ifUseAuth) { - String appKey = getAppKey(selfPartyId); - authBuilder.setAppKey(appKey); - String signature = calSignature(packet.getHeader(), packet.getBody(), timestamp, appKey); - authBuilder.setSignature(signature); - } - - packetBuilder.setAuth(authBuilder.build()); - return packetBuilder.build(); - } - return packet; - } - - public boolean checkAuthentication(Proxy.Packet packet) throws Exception { - if(ifUseAuth - && StringUtils.equals(selfPartyId, packet.getHeader().getDst().getPartyId())) { - // check timestamp - long currentTimeMillis = System.currentTimeMillis(); - long requestTimeMillis = packet.getAuth().getTimestamp(); - if (currentTimeMillis >= (requestTimeMillis + validRequestTimeoutSecond * 1000)) { - logger.error("receive an expired request, currentTimeMillis:{}, requestTimeMillis{}.", currentTimeMillis, requestTimeMillis); - return false; - } - // check signature - String reqSignature = packet.getAuth().getSignature(); - String validSignature = calSignature(packet.getHeader(), packet.getBody(), requestTimeMillis, packet.getAuth().getAppKey()); - if (!StringUtils.equals(reqSignature, validSignature)) { - logger.error("invalid signature, request:{}, valid:{}", reqSignature, validSignature); - return false; - } - } - return true; - } - - /*@Override - public void afterPropertiesSet() throws Exception { - - try { - loadConfig(); - } catch (Throwable e) { - logger.error("load authencation keys error", e); - } - - }*/ -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/CommandLineOptions.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/CommandLineOptions.java deleted file mode 100644 index 35f5ca71..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/CommandLineOptions.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.util; - -import org.apache.commons.cli.Options; -import org.springframework.stereotype.Component; - -@Component -public class CommandLineOptions { - public static final String HELP = "h"; - public static final String PORT = "p"; - public static final String ROUTE_TABLE = "t"; - public static final String SERVER_CRT = "c"; - public static final String SERVER_KEY = "k"; - public static final String ROOT_CRT = "r"; - private final Options options; - - public CommandLineOptions() { - options = new Options(); - options.addOption("h", "help", false, "Print this usage information"); - options.addOption("p", "port", true, "Port to listen"); - options.addOption("t", "route-table", true, "File to config route rules. Imply using TLS."); - options.addOption("c", "server-crt", true, "Server certification file. Imply using TLS."); - options.addOption("k", "server-key", true, "Server private key file. Imply using TLS."); - options.addOption("r", "root-crt", true, "Root certification file"); - } - - public Options getOptions() { - return options; - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/ErrorUtils.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/ErrorUtils.java deleted file mode 100644 index bbff5230..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/ErrorUtils.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.util; - -import com.webank.ai.fate.networking.proxy.model.ServerConf; -import io.grpc.Status; -import io.grpc.StatusRuntimeException; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -@Component -@Scope("prototype") -public class ErrorUtils { - @Autowired - private ServerConf serverConf; - - public StatusRuntimeException toGrpcRuntimeException(Throwable throwable) { - StatusRuntimeException result = null; - - if (throwable instanceof StatusRuntimeException) { - result = (StatusRuntimeException) throwable; - } else { - result = Status.INTERNAL - .withCause(throwable) - .withDescription("" + serverConf.getIp() + ":" + serverConf.getPort() + ": " + ExceptionUtils.getStackTrace(throwable)) - .asRuntimeException(); - } - - return result; - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/PipeUtils.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/PipeUtils.java deleted file mode 100644 index 0a8ce986..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/PipeUtils.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.util; - - -import com.webank.ai.fate.api.networking.proxy.Proxy; -import com.webank.ai.fate.networking.proxy.infra.Pipe; -import com.webank.ai.fate.networking.proxy.infra.impl.PacketQueueSingleResultPipe; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -@Component -@Scope("prototype") -public class PipeUtils { - public Proxy.Metadata getResultFromPipe(Pipe pipe) { - if (pipe instanceof PacketQueueSingleResultPipe) { - PacketQueueSingleResultPipe convertedPipe = (PacketQueueSingleResultPipe) pipe; - if (convertedPipe.hasResult()) { - return convertedPipe.getResult(); - } - } - - return null; - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/Timeouts.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/Timeouts.java deleted file mode 100644 index 615d6a24..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/Timeouts.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.util; - -import com.google.protobuf.Descriptors; -import com.webank.ai.fate.api.networking.proxy.Proxy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; - -@Component -public class Timeouts { - public static final long MAX_OVERALL_TIMEOUT = 12L * 3600 * 1000; // 12h - public static final long DEFAULT_OVERALL_TIMEOUT = MAX_OVERALL_TIMEOUT; - - public static final long MAX_COMPLETION_WAIT_TIMEOUT = MAX_OVERALL_TIMEOUT; - public static final long DEFAULT_COMPLETION_WAIT_TIMEOUT = 60L * 60 * 1000; // 60m - - public static final long MAX_PACKET_INTERVAL_TIMEOUT = MAX_OVERALL_TIMEOUT; - public static final long DEFAULT_PACKET_INTERVAL_TIMEOUT = 20L * 1000; // 20s - - private static final Logger logger = LoggerFactory.getLogger(Timeouts.class); - - public boolean isTimeout(long timeout, long startTimestamp) { - return isTimeout(timeout, startTimestamp, System.currentTimeMillis()); - } - - public boolean isTimeout(long timeout, long startTimestamp, long endTimestamp) { - /*logger.info("timeout: {}, start: {}, end: {}, cal: {}, result: {}", - timeout, startTimestamp, endTimestamp, (endTimestamp - startTimestamp), - ((endTimestamp - startTimestamp) > timeout));*/ - return (endTimestamp - startTimestamp) > timeout; - } - - public long getOverallTimeout(Proxy.Metadata metadata) { - long result = DEFAULT_OVERALL_TIMEOUT; - if (metadata != null && metadata.hasConf()) { - Proxy.Conf conf = metadata.getConf(); - long proposedValue = conf.getOverallTimeout(); - - result = getValue(proposedValue, DEFAULT_OVERALL_TIMEOUT, 1, MAX_OVERALL_TIMEOUT); - } - - return result; - } - - public long getCompletionWaitTimeout(Proxy.Metadata metadata) { - long result = DEFAULT_COMPLETION_WAIT_TIMEOUT; - if (metadata != null && metadata.hasConf()) { - Proxy.Conf conf = metadata.getConf(); - long proposedValue = conf.getCompletionWaitTimeout(); - - result = getValue(proposedValue, DEFAULT_COMPLETION_WAIT_TIMEOUT, 1, MAX_COMPLETION_WAIT_TIMEOUT); - } - - return result; - } - - public long getPacketIntervalTimeout(Proxy.Metadata metadata) { - long result = DEFAULT_PACKET_INTERVAL_TIMEOUT; - if (metadata != null && metadata.hasConf()) { - Proxy.Conf conf = metadata.getConf(); - long proposedValue = conf.getPacketIntervalTimeout(); - - result = getValue(proposedValue, DEFAULT_PACKET_INTERVAL_TIMEOUT, 1, MAX_PACKET_INTERVAL_TIMEOUT); - } - - return result; - } - - private long getTimeout(Proxy.Metadata metadata, String timeoutFieldName, long defaultValue, long minValue, long maxValue) { - long result = defaultValue; - if (metadata != null && metadata.hasConf()) { - Proxy.Conf conf = metadata.getConf(); - - Descriptors.Descriptor descriptor = Proxy.Conf.getDescriptor(); - Descriptors.FieldDescriptor fieldDescriptor = descriptor.findFieldByName(timeoutFieldName); - - long proposedValue = (long) conf.getField(fieldDescriptor); - - result = getValue(proposedValue, defaultValue, minValue, maxValue); - } - - return result; - } - - private long getValue(long proposedValue, long defaultValue, long minValue, long maxValue) { - long result = proposedValue; - // logger.info("proposed: {}, default: {}, min: {}, max: {}", proposedValue, defaultValue, minValue, maxValue); - - if (proposedValue < minValue) { - result = defaultValue; - } else if (proposedValue > maxValue) { - result = maxValue; - } - - return result; - } -} diff --git a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/ToStringUtils.java b/router/src/main/java/com/webank/ai/fate/networking/proxy/util/ToStringUtils.java deleted file mode 100644 index bfc1e581..00000000 --- a/router/src/main/java/com/webank/ai/fate/networking/proxy/util/ToStringUtils.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.networking.proxy.util; - -import com.google.protobuf.Message; -import com.google.protobuf.util.JsonFormat; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Scope; -import org.springframework.stereotype.Component; - -@Component -@Scope("prototype") -public class ToStringUtils { - private static final String NULL_STRING = "[null]"; - private static final String LEFT_BRACKET = "["; - private static final String RIGHT_BRACKET = "]"; - private static final String LEFT_BRACE = "{"; - private static final String RIGHT_BRACE = "}"; - private static final String LEFT_PARENTHESIS = "("; - private static final String RIGHT_PARENTHESIS = ")"; - private static final String COLON = ":"; - private static final String SEMICOLON = ";"; - private static final String COMMA = ","; - - private static final Logger logger = LoggerFactory.getLogger(ToStringUtils.class); - - private JsonFormat.Printer protoPrinter = JsonFormat.printer() - .preservingProtoFieldNames() - .omittingInsignificantWhitespace(); - - public String toOneLineString(Message target) { - String result = "[null]"; - - if (target == null) { - logger.info("target is null"); - return result; - } - - try { - result = protoPrinter.print(target); - } catch (Exception e) { - logger.info(ExceptionUtils.getStackTrace(e)); - } - - return result; - } -} diff --git a/router/src/main/java/utils/Constants.java b/router/src/main/java/utils/Constants.java deleted file mode 100644 index d23083b2..00000000 --- a/router/src/main/java/utils/Constants.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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 utils; - -public class Constants { - -} diff --git a/router/src/main/java/utils/Utils.java b/router/src/main/java/utils/Utils.java deleted file mode 100644 index e77e113d..00000000 --- a/router/src/main/java/utils/Utils.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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 utils; - -import com.webank.ai.fate.api.core.BasicMeta; -import com.webank.ai.fate.api.networking.proxy.Proxy; - -import java.util.Arrays; -import java.util.List; - -public class Utils { - public static final String DELIMITER = "-"; - public static final String DST = "dst"; - public static final String SRC = "src"; - public static final Proxy.HeartbeatResponse DEFAULT_HEARTBEAT_RESPONSE; - - static { - DEFAULT_HEARTBEAT_RESPONSE = Proxy.HeartbeatResponse.newBuilder().setOperation(Proxy.Operation.RUN).build(); - } - - public static String genEndpointKey(BasicMeta.Endpoint endpoint) { - List elements - = Arrays.asList(endpoint.getIp(), endpoint.getHostname(), String.valueOf(endpoint.getPort())); - - return String.join(DELIMITER, elements); - } - - public static String genStorageKey(String taskId, BasicMeta.Endpoint dst, BasicMeta.Endpoint src) { - List elements = Arrays.asList(taskId, - DST, genEndpointKey(dst), - SRC, genEndpointKey(src)); - - - return String.join(DELIMITER, elements); - } - -} diff --git a/router/src/main/resources/applicationContext-proxy.xml b/router/src/main/resources/applicationContext-proxy.xml deleted file mode 100644 index 2689a3e2..00000000 --- a/router/src/main/resources/applicationContext-proxy.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/router/src/main/resources/auth_config.json b/router/src/main/resources/auth_config.json deleted file mode 100644 index 27a1426d..00000000 --- a/router/src/main/resources/auth_config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "self_party_id": "9999", - "if_use_auth": false, - "request_expire_seconds": 8, - "apply_id": "20191119163236256", - "access_keys": [{ - "party_id": "9999", - "app_key": "11", - "app_secret": "11111" - }, { - "party_id": "10000", - "app_key": "22", - "app_secret": "22222" - }] -} diff --git a/router/src/main/resources/log4j2.properties b/router/src/main/resources/log4j2.properties deleted file mode 100644 index 960a1003..00000000 --- a/router/src/main/resources/log4j2.properties +++ /dev/null @@ -1,105 +0,0 @@ -# -# Copyright 2019 The FATE Authors. All Rights Reserved. -# -# 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. -# -name=PropertiesConfig -property.auditDir=audit -property.logDir=logs -property.project=fate -property.module=proxy -property.logPattern=[%-5level] %d{yyyy-MM-dd}T%d{HH:mm:ss,SSS} [%t] [%c{1}:%L] - %msg%n -# console -appender.console.type=Console -appender.console.name=STDOUT -appender.console.layout.type=PatternLayout -appender.console.layout.pattern=${logPattern} -# default file -appender.file.type=RollingFile -appender.file.name=LOGFILE -appender.file.fileName=${logDir}/${project}-${module}.log -appender.file.filePattern=${logDir}/%d{yyyy}/%d{MM}/%d{dd}/${project}-${module}.log.%d{yyyy-MM-dd-HH} -appender.file.layout.type=PatternLayout -appender.file.layout.pattern=${logPattern} -appender.file.policies.type=Policies -appender.file.policies.time.type=TimeBasedTriggeringPolicy -appender.file.policies.time.interval=1 -appender.file.policies.time.modulate=true -appender.file.strategy.type=DefaultRolloverStrategy -# debug -appender.debugging.type=RollingFile -appender.debugging.name=LOGDEBUGGING -appender.debugging.fileName=${logDir}/${project}-${module}-debug.log -appender.debugging.filePattern=${logDir}/%d{yyyy}/%d{MM}/%d{dd}/${project}-${module}-debug.log.%d{yyyy-MM-dd-HH-mm} -appender.debugging.layout.type=PatternLayout -appender.debugging.layout.pattern=${logPattern} -appender.debugging.policies.type=Policies -appender.debugging.policies.time.type=TimeBasedTriggeringPolicy -appender.debugging.policies.time.interval=1 -appender.debugging.policies.time.modulate=true -appender.debugging.strategy.type=DefaultRolloverStrategy -# audit -appender.audit.type=RollingFile -appender.audit.name=LOGAUDIT -appender.audit.fileName=${auditDir}/${project}-${module}-audit.log -appender.audit.filePattern=${auditDir}/%d{yyyy}/%d{MM}/%d{dd}/${project}-${module}-audit.log.%d{yyyy-MM-dd-HH} -appender.audit.layout.type=PatternLayout -appender.audit.layout.pattern=[%d{yyyy-MM-dd}T%d{HH:mm:ss,SSS}]%msg%n -appender.audit.policies.type=Policies -appender.audit.policies.time.type=TimeBasedTriggeringPolicy -appender.audit.policies.time.interval=1 -appender.audit.policies.time.modulate=true -appender.audit.strategy.type=DefaultRolloverStrategy -# stat -appender.stat.type=RollingFile -appender.stat.name=LOGSTAT -appender.stat.fileName=${logDir}/${project}-${module}-stat.log -appender.stat.filePattern=${logDir}/%d{yyyy}/%d{MM}/%d{dd}/${project}-${module}-stat.log.%d{yyyy-MM-dd-HH} -appender.stat.layout.type=PatternLayout -appender.stat.layout.pattern=${logPattern} -appender.stat.policies.type=Policies -appender.stat.policies.time.type=TimeBasedTriggeringPolicy -appender.stat.policies.time.interval=1 -appender.stat.policies.time.modulate=true -appender.stat.strategy.type=DefaultRolloverStrategy -# loggers -loggers=file, debugging, audit, stat -# logger - file -logger.file.name=file -logger.file.level=info -logger.file.appenderRefs=file -logger.file.appenderRef.file.ref=LOGFILE -logger.file.additivity=false -# logger - debugging -logger.debugging.name=debugging -logger.debugging.level=info -logger.debugging.appenderRefs=debugging -logger.debugging.appenderRef.debugging.ref=LOGDEBUGGING -logger.debugging.additivity=false -# logger - audit -logger.audit.name=audit -logger.audit.level=info -logger.audit.appenderRefs=audit -logger.audit.appenderRef.file.ref=LOGAUDIT -logger.audit.additivity=false -# logger - stat -logger.stat.name=stat -logger.stat.level=info -logger.stat.appenderRefs=stat -logger.stat.appenderRef.file.ref=LOGSTAT -logger.stat.additivity=false -# logger - root -rootLogger.level=info -rootLogger.appenderRefs=stdout, file -rootLogger.appenderRef.stdout.ref=STDOUT -rootLogger.appenderRef.file.ref=LOGFILE diff --git a/router/src/main/resources/proxy.properties b/router/src/main/resources/proxy.properties deleted file mode 100644 index 57d8d161..00000000 --- a/router/src/main/resources/proxy.properties +++ /dev/null @@ -1,35 +0,0 @@ -# -# Copyright 2019 The FATE Authors. All Rights Reserved. -# -# 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. -# - -# zk router -zk.url=zookeeper://localhost:2181?backup=localhost:2182,localhost:2183 -useRegister=false -useZkRouter=false - -coordinator=webank -ip=127.0.0.1 -port=9370 -route.table=/data/projects/fate/serving-router/conf/route_table.json -#server.crt=/Users/max-webank/Projects/fdn/fdn-proxy/src/main/resources/certs/server.crt -#server.key=/Users/max-webank/Projects/fdn/fdn-proxy/src/main/resources/certs/server-private.pem -root.crt= - -# router auth -authFile=/data/projects/fate/serving-router/conf/auth_config.json - -# zk acl -acl.username= -acl.password= \ No newline at end of file diff --git a/router/src/main/resources/route_tables/route_table.json b/router/src/main/resources/route_tables/route_table.json deleted file mode 100644 index 06a6f7b4..00000000 --- a/router/src/main/resources/route_tables/route_table.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "route_table": { - "default": { - "default": [ - { - "ip": "127.0.0.1", - "port": 9999 - } - ] - }, - "10000": { - "default": [ - { - "ip": "127.0.0.1", - "port": 8889 - } - ] - }, - "9999": { - "default": [ - { - "ip": "127.0.0.1", - "port": 8890 - } - ] - } - }, - "permission": { - "default_allow": true - } -} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java index 715abd6a..f277a464 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/bootstrap/Bootstrap.java @@ -4,7 +4,7 @@ import com.webank.ai.fate.register.url.URL; import com.webank.ai.fate.register.zookeeper.ZookeeperRegistry; import com.webank.ai.fate.serving.core.bean.Dict; -import com.webank.ai.fate.serving.proxy.rpc.core.AbstractServiceAdaptor; +import com.webank.ai.fate.serving.core.rpc.core.AbstractServiceAdaptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/Dict.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/Dict.java deleted file mode 100644 index 78bdad4e..00000000 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/Dict.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.webank.ai.fate.serving.proxy.common; - -/** - * @Description TODO - * @Author - **/ -public class Dict { - public static final String SERVICE_ID = "serviceId"; - public static final String CASE_ID="caseid"; - public static final String CODE ="code"; - public static final String MESSAGE ="message"; - public static final String MODEL_ID = "modelId"; - public static final String MODEL_VERSION = "modelVersion"; - public static final String APP_ID = "appid"; - public static final String PARTY_ID = "partyId"; - public static final String FEATURE_DATA = "featureData"; - public static final String PARTNER_PARTY_NAME = "partnerPartyName"; - public static final String DEFAULT_VERSION = "1.0"; - public static final String SELF_PROJECT_NAME = "proxy"; - public static final String SELF_ENVIRONMENT = "online"; - public static final String HEAD = "head"; - public static final String BODY = "body"; - public static final String SERVICENAME_INFERENCE = "inference"; - public static final String SERVICENAME_START_INFERENCE_JOB = "startInferenceJob"; - public static final String SERVICENAME_GET_INFERENCE_RESULT = "getInferenceResult"; -} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ErrorMessageUtil.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ErrorMessageUtil.java deleted file mode 100644 index f9b8e229..00000000 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/common/ErrorMessageUtil.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.webank.ai.fate.serving.proxy.common; - -import com.alibaba.csp.sentinel.slots.block.BlockException; -import com.webank.ai.fate.serving.core.exceptions.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Map; - -import static com.webank.ai.fate.serving.proxy.common.Dict.CODE; -import static com.webank.ai.fate.serving.proxy.common.Dict.MESSAGE; - -/** - * @Description TODO - * @Author - **/ -public class ErrorMessageUtil { - - static Logger logger = LoggerFactory.getLogger(ErrorMessageUtil.class); - - public static Map handleException(Map result,Throwable e){ - - if (e instanceof IllegalArgumentException) { - result.put(CODE, ErrorCode.PARAM_ERROR); - result.put(MESSAGE,"PARAM_ERROR"); - } - else if(e instanceof NoRouteInfoException){ - result.put(CODE, ErrorCode.ROUTER_ERROR); - result.put(MESSAGE, "ROUTER_ERROR"); - } else if (e instanceof SysException) { - result.put(CODE, ErrorCode.SYSTEM_ERROR); - result.put(MESSAGE, "SYSTEM_ERROR"); - - - } else if (e instanceof BlockException) { - result.put(CODE, ErrorCode.LIMIT_ERROR); - - result.put(MESSAGE, "OVERLOAD"); - - } else if (e instanceof InvalidRoleInfoException) { - result.put(CODE, ErrorCode.ROLE_ERROR); - result.put(MESSAGE, "ROLE_ERROR"); - } else if (e instanceof ShowDownRejectException){ - result.put(CODE, ErrorCode.SHUTDOWN_ERROR); - result.put(MESSAGE, "SHUTDOWN_ERROR"); - - } - else if (e instanceof NoResultException) { - logger.error("NET_ERROR ",e); - result.put(CODE, ErrorCode.NET_ERROR); - result.put(MESSAGE, "NET_ERROR"); - } else { - logger.error("SYSTEM_ERROR ",e); - result.put(CODE, ErrorCode.SYSTEM_ERROR); - result.put(MESSAGE, "SYSTEM_ERROR"); - } - - return result; - - } -} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java index 9bdaf734..29a26c66 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java @@ -20,7 +20,7 @@ import com.webank.ai.fate.register.router.DefaultRouterService; import com.webank.ai.fate.register.router.RouterService; import com.webank.ai.fate.register.zookeeper.ZookeeperRegistry; -import com.webank.ai.fate.serving.proxy.common.Dict; +import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.proxy.rpc.grpc.InterGrpcServer; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java index 84661ce6..529f2d34 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java @@ -4,11 +4,11 @@ import com.alibaba.fastjson.JSONObject; import com.webank.ai.fate.serving.core.bean.BaseContext; import com.webank.ai.fate.serving.core.bean.Context; +import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; import com.webank.ai.fate.serving.core.rpc.core.ServiceAdaptor; import com.webank.ai.fate.serving.metrics.api.IMetricFactory; -import com.webank.ai.fate.serving.proxy.common.Dict; import com.webank.ai.fate.serving.proxy.rpc.core.ProxyServiceRegister; import com.webank.ai.fate.serving.proxy.utils.WebUtil; import org.slf4j.Logger; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/GlobalExceptionHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/GlobalExceptionHandler.java index 35f35648..4b43273c 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/GlobalExceptionHandler.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/exceptions/GlobalExceptionHandler.java @@ -1,7 +1,7 @@ package com.webank.ai.fate.serving.proxy.exceptions; -import com.webank.ai.fate.serving.proxy.common.ErrorMessageUtil; +import com.webank.ai.fate.serving.core.rpc.core.ErrorMessageUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.ControllerAdvice; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyServiceRegister.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyServiceRegister.java index 9851b287..c2c30307 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyServiceRegister.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyServiceRegister.java @@ -1,9 +1,6 @@ package com.webank.ai.fate.serving.proxy.rpc.core; -import com.webank.ai.fate.serving.core.rpc.core.Interceptor; -import com.webank.ai.fate.serving.core.rpc.core.ProxyService; -import com.webank.ai.fate.serving.core.rpc.core.ServiceAdaptor; -import com.webank.ai.fate.serving.core.rpc.core.ServiceRegister; +import com.webank.ai.fate.serving.core.rpc.core.*; import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcConnectionPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java index 95c8a510..09e9b6de 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java @@ -7,11 +7,11 @@ import com.webank.ai.fate.register.annotions.RegisterService; import com.webank.ai.fate.serving.core.bean.BaseContext; import com.webank.ai.fate.serving.core.bean.Context; +import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; import com.webank.ai.fate.serving.core.rpc.core.ServiceAdaptor; import com.webank.ai.fate.serving.metrics.api.IMetricFactory; -import com.webank.ai.fate.serving.proxy.common.Dict; import com.webank.ai.fate.serving.proxy.rpc.core.ProxyServiceRegister; import io.grpc.stub.StreamObserver; import org.apache.commons.lang3.StringUtils; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java index b0768aaa..54fa9937 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java @@ -9,11 +9,11 @@ import com.webank.ai.fate.api.core.BasicMeta; import com.webank.ai.fate.api.networking.proxy.Proxy; import com.webank.ai.fate.serving.core.bean.Context; +import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.router.RouteType; import com.webank.ai.fate.serving.core.rpc.router.RouteTypeConvertor; import com.webank.ai.fate.serving.core.rpc.router.RouterInfo; -import com.webank.ai.fate.serving.proxy.common.Dict; import com.webank.ai.fate.serving.proxy.utils.FileUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java index 122bcea1..efde5d62 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java @@ -4,12 +4,12 @@ import com.webank.ai.fate.register.router.RouterService; import com.webank.ai.fate.register.url.URL; import com.webank.ai.fate.serving.core.bean.Context; +import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.grpc.GrpcType; import com.webank.ai.fate.serving.core.rpc.router.RouteType; import com.webank.ai.fate.serving.core.rpc.router.RouteTypeConvertor; import com.webank.ai.fate.serving.core.rpc.router.RouterInfo; -import com.webank.ai.fate.serving.proxy.common.Dict; import com.webank.ai.fate.serving.proxy.utils.FederatedModelUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java index 0f40349f..cf5f6567 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java @@ -7,15 +7,16 @@ import com.webank.ai.fate.api.serving.InferenceServiceGrpc; import com.webank.ai.fate.api.serving.InferenceServiceProto; import com.webank.ai.fate.serving.core.bean.Context; +import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.core.exceptions.NoResultException; import com.webank.ai.fate.serving.core.exceptions.UnSupportMethodException; +import com.webank.ai.fate.serving.core.rpc.core.AbstractServiceAdaptor; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; import com.webank.ai.fate.serving.core.rpc.core.ProxyService; import com.webank.ai.fate.serving.core.rpc.router.RouterInfo; import com.webank.ai.fate.serving.metrics.api.IMetricFactory; -import com.webank.ai.fate.serving.proxy.common.Dict; -import com.webank.ai.fate.serving.proxy.rpc.core.AbstractServiceAdaptor; + import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcConnectionPool; import io.grpc.ManagedChannel; import org.apache.commons.lang3.StringUtils; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/NotFoundService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/NotFoundService.java index 22af8189..73f10581 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/NotFoundService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/NotFoundService.java @@ -3,12 +3,13 @@ import com.alibaba.fastjson.JSON; import com.google.common.collect.Maps; import com.webank.ai.fate.serving.core.bean.Context; +import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.core.exceptions.ErrorCode; +import com.webank.ai.fate.serving.core.rpc.core.AbstractServiceAdaptor; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; import com.webank.ai.fate.serving.core.rpc.core.ProxyService; -import com.webank.ai.fate.serving.proxy.common.Dict; -import com.webank.ai.fate.serving.proxy.rpc.core.AbstractServiceAdaptor; + import java.util.List; import java.util.Map; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java index 3fe12286..9dc2c7b6 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java @@ -7,14 +7,15 @@ import com.webank.ai.fate.api.networking.proxy.DataTransferServiceGrpc; import com.webank.ai.fate.api.networking.proxy.Proxy; import com.webank.ai.fate.serving.core.bean.Context; +import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.core.exceptions.ErrorCode; +import com.webank.ai.fate.serving.core.rpc.core.AbstractServiceAdaptor; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; import com.webank.ai.fate.serving.core.rpc.core.ProxyService; import com.webank.ai.fate.serving.core.rpc.router.RouterInfo; import com.webank.ai.fate.serving.metrics.api.IMetricFactory; -import com.webank.ai.fate.serving.proxy.common.Dict; -import com.webank.ai.fate.serving.proxy.rpc.core.AbstractServiceAdaptor; + import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcConnectionPool; import com.webank.ai.fate.serving.proxy.security.AuthUtils; import io.grpc.ManagedChannel; diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java index bf132829..ff43ed8b 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/InferenceParamValidator.java @@ -2,10 +2,10 @@ import com.google.common.base.Preconditions; import com.webank.ai.fate.serving.core.bean.Context; +import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; import com.webank.ai.fate.serving.core.rpc.core.Interceptor; import com.webank.ai.fate.serving.core.rpc.core.OutboundPackage; -import com.webank.ai.fate.serving.proxy.common.Dict; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; @@ -19,7 +19,7 @@ public class InferenceParamValidator implements Interceptor{ @Override public void doPreProcess(Context context, InboundPackage inboundPackage, OutboundPackage outboundPackage) throws Exception { Preconditions.checkArgument(StringUtils.isNotEmpty(context.getCaseId()),"case id is null"); - Preconditions.checkArgument(null != inboundPackage.getHead(),Dict.HEAD + " is null"); + Preconditions.checkArgument(null != inboundPackage.getHead(), Dict.HEAD + " is null"); //Preconditions.checkArgument(null != inboundPackage.getBody(),Dict.BODY + " is null"); //Preconditions.checkArgument(StringUtils.isNotEmpty((String) inboundPackage.getHead().get(Dict.SERVICE_ID)), Dict.SERVICE_ID + " is null"); } From 60abcec8342a43f583beb71ca14f1e6ec3be0cf6 Mon Sep 17 00:00:00 2001 From: kaideng Date: Tue, 21 Jan 2020 19:44:38 +0800 Subject: [PATCH 131/190] add model loader Signed-off-by: kaideng --- .../fate/serving/core/bean/BaseContext.java | 4 + .../exceptions/ModelSerializeException.java | 6 + .../core/manager/DefaultCacheManager.java | 4 +- .../webank/ai/fate/serving/ServingServer.java | 26 ++- .../guest/DefaultGuestInferenceProvider.java | 8 +- .../host/DefaultHostInferenceProvider.java | 8 +- .../fate/serving/interfaces/ModelCache.java | 7 +- .../fate/serving/interfaces/ModelManager.java | 10 +- .../serving/manger/AbstractModelLoader.java | 150 ++++++++++++++++ .../manger/DefaultHttpModelLoader.java | 161 ++++++++++++++++++ .../serving/manger/DefaultModelCache.java | 19 ++- .../serving/manger/DefaultModelManager.java | 33 ++-- .../ai/fate/serving/manger/ModelLoader.java | 9 + .../ai/fate/serving/manger/ModelUtil.java | 48 ++++++ .../ai/fate/serving/manger/ModelUtils.java | 151 ---------------- .../ai/fate/serving/service/ModelService.java | 23 +-- 16 files changed, 455 insertions(+), 212 deletions(-) create mode 100644 fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/ModelSerializeException.java create mode 100644 serving-server/src/main/java/com/webank/ai/fate/serving/manger/AbstractModelLoader.java create mode 100644 serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultHttpModelLoader.java create mode 100644 serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelLoader.java create mode 100644 serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtil.java delete mode 100644 serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java index 75589f4b..2eadc5f0 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java @@ -27,10 +27,12 @@ import org.springframework.context.ApplicationContext; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; public class BaseContext implements Context { private static final Logger logger = LoggerFactory.getLogger(LOGGER_NAME); public static ApplicationContext applicationContext; + public static AtomicLong requestInProcess= new AtomicLong(0); long timestamp; LoggerPrinter loggerPrinter; String actionType; @@ -69,6 +71,7 @@ public void setActionType(String actionType) { @Override public void preProcess() { try { + requestInProcess.addAndGet(1); Timer timer = metricRegistry.timer(actionType + "_timer"); Counter counter = metricRegistry.counter(actionType + "_couter"); counter.inc(); @@ -116,6 +119,7 @@ public long getTimeStamp() { @Override public void postProcess(Req req, Resp resp) { try { + requestInProcess.decrementAndGet(); if (timerContext != null) { costTime = timerContext.stop() / 1000000; } else { diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/ModelSerializeException.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/ModelSerializeException.java new file mode 100644 index 00000000..2e5e1c20 --- /dev/null +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/ModelSerializeException.java @@ -0,0 +1,6 @@ +package com.webank.ai.fate.serving.core.exceptions; + + +public class ModelSerializeException extends RuntimeException{ + +} diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java index c2140d2d..79c6cb16 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java @@ -144,7 +144,7 @@ public ReturnResult getInferenceResultCache(String partyId, String caseid) { @Override public void putRemoteModelInferenceResult(FederatedParams guestFederatedParams, ReturnResult returnResult) { - if (!Boolean.parseBoolean(Configuration.getProperty("remoteModelInferenceResultCacheSwitch"))) { + if (!Boolean.parseBoolean(Configuration.getProperty(Dict.PROPERTY_REMOTE_MODEL_INFERENCE_RESULT_CACHE_SWITCH))) { return; } String remoteModelInferenceResultCacheKey = generateRemoteModelInferenceResultCacheKey(guestFederatedParams); @@ -157,7 +157,7 @@ public void putRemoteModelInferenceResult(FederatedParams guestFederatedParams, @Override public ReturnResult getRemoteModelInferenceResult(FederatedParams guestFederatedParams) { - if (!Boolean.parseBoolean(Configuration.getProperty("remoteModelInferenceResultCacheSwitch"))) { + if (!Boolean.parseBoolean(Configuration.getProperty(Dict.PROPERTY_REMOTE_MODEL_INFERENCE_RESULT_CACHE_SWITCH))) { return null; } String remoteModelInferenceResultCacheKey = generateRemoteModelInferenceResultCacheKey(guestFederatedParams); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index 41c88913..da55f947 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -26,6 +26,7 @@ import com.webank.ai.fate.register.url.URL; import com.webank.ai.fate.register.zookeeper.ZookeeperRegistry; import com.webank.ai.fate.serving.core.bean.ApplicationHolder; +import com.webank.ai.fate.serving.core.bean.BaseContext; import com.webank.ai.fate.serving.core.bean.Configuration; import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.federatedml.model.BaseModel; @@ -65,7 +66,6 @@ public ServingServer(String confPath) { this.confPath = new File(confPath).getAbsolutePath(); System.setProperty(Dict.CONFIGPATH, confPath); new Configuration(confPath).load(); - System.setProperty(Dict.ACL_ENABLE, Configuration.getProperty(Dict.ACL_ENABLE, "")); System.setProperty(Dict.ACL_USERNAME, Configuration.getProperty(Dict.ACL_USERNAME, "")); System.setProperty(Dict.ACL_PASSWORD, Configuration.getProperty(Dict.ACL_PASSWORD, "")); @@ -151,7 +151,6 @@ private void start(String[] args) throws Exception { ModelService modelService = applicationContext.getBean(ModelService.class); modelService.restore(); - ConsoleReporter reporter = applicationContext.getBean(ConsoleReporter.class); reporter.start(1, TimeUnit.SECONDS); @@ -182,11 +181,24 @@ private void stop() { }); zookeeperRegistry.destroy(); - try { - Thread.sleep(5000); - } catch (InterruptedException e) { - e.printStackTrace(); - } + int retryCount=0; + long requestInProcess = BaseContext.requestInProcess.get(); + do{ + if(requestInProcess>0&&retryCount<3) { + try { + logger.info("try to stop server,there is {} request in process", requestInProcess); + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + retryCount++; + requestInProcess = BaseContext.requestInProcess.get(); + }else{ + break; + } + + }while(requestInProcess>0&&retryCount<3); + } server.shutdown(); } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java index 265764ce..41aa3035 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java @@ -75,22 +75,22 @@ public ReturnResult runInference(Context context, InferenceRequest inferenceRequ if (StringUtils.isEmpty(modelNamespace) ) { if(StringUtils.isNotEmpty(inferenceRequest.getServiceId())){ - modelNamespace = modelManager.getModelNamespaceByPartyId(inferenceRequest.getServiceId()); + modelNamespace = modelManager.getModelNamespaceByPartyId(context,inferenceRequest.getServiceId()); }else if(inferenceRequest.haveAppId()) { - modelNamespace = modelManager.getModelNamespaceByPartyId(inferenceRequest.getAppid()); + modelNamespace = modelManager.getModelNamespaceByPartyId(context,inferenceRequest.getAppid()); } } if (StringUtils.isEmpty(modelNamespace)) { inferenceResult.setRetcode(InferenceRetCode.LOAD_MODEL_FAILED + 1000); return inferenceResult; } - ModelNamespaceData modelNamespaceData = modelManager.getModelNamespaceData(modelNamespace); + ModelNamespaceData modelNamespaceData = modelManager.getModelNamespaceData(context,modelNamespace); PipelineTask model; if (StringUtils.isEmpty(modelName)) { modelName = modelNamespaceData.getUsedModelName(); model = modelNamespaceData.getUsedModel(); } else { - model = modelManager.getModel(modelName, modelNamespace); + model = modelManager.getModel(context,modelName, modelNamespace); } if (model == null) { inferenceResult.setRetcode(InferenceRetCode.LOAD_MODEL_FAILED + 1000); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/host/DefaultHostInferenceProvider.java b/serving-server/src/main/java/com/webank/ai/fate/serving/host/DefaultHostInferenceProvider.java index a8a4e0bf..33d2531b 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/host/DefaultHostInferenceProvider.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/host/DefaultHostInferenceProvider.java @@ -71,14 +71,14 @@ public ReturnResult federatedInference(Context context, HostFederatedParams fede ModelInfo partnerModelInfo = federatedParams.getPartnerModelInfo(); Map featureIds = federatedParams.getFeatureIdMap(); - ModelInfo modelInfo = modelManager.getModelInfoByPartner(partnerModelInfo.getName(), partnerModelInfo.getNamespace()); + ModelInfo modelInfo = modelManager.getModelInfoByPartner(context,partnerModelInfo.getName(), partnerModelInfo.getNamespace()); if (modelInfo == null) { returnResult.setRetcode(InferenceRetCode.LOAD_MODEL_FAILED); returnResult.setRetmsg("Can not found model."); // logInference(context,federatedParams, party, federatedRoles, returnResult, 0, false, false); return returnResult; } - PipelineTask model = modelManager.getModel(modelInfo.getName(), modelInfo.getNamespace()); + PipelineTask model = modelManager.getModel(context,modelInfo.getName(), modelInfo.getNamespace()); if (model == null) { returnResult.setRetcode(InferenceRetCode.LOAD_MODEL_FAILED); returnResult.setRetmsg("Can not found model."); @@ -129,7 +129,7 @@ public ReturnResult federatedInferenceForTree(Context context, HostFederatedPara ModelInfo partnerModelInfo = federatedParams.getPartnerModelInfo(); Map featureIds = federatedParams.getFeatureIdMap(); - ModelInfo modelInfo = modelManager.getModelInfoByPartner(partnerModelInfo.getName(), partnerModelInfo.getNamespace()); + ModelInfo modelInfo = modelManager.getModelInfoByPartner(context,partnerModelInfo.getName(), partnerModelInfo.getNamespace()); if (modelInfo == null) { returnResult.setRetcode(InferenceRetCode.LOAD_MODEL_FAILED); returnResult.setRetmsg("Can not found model."); @@ -138,7 +138,7 @@ public ReturnResult federatedInferenceForTree(Context context, HostFederatedPara } - PipelineTask model = modelManager.getModel(modelInfo.getName(), modelInfo.getNamespace()); + PipelineTask model = modelManager.getModel(context,modelInfo.getName(), modelInfo.getNamespace()); // Preconditions.checkArgument(federatedParams.getData().get(Dict.TAG)!=null); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/interfaces/ModelCache.java b/serving-server/src/main/java/com/webank/ai/fate/serving/interfaces/ModelCache.java index fa8c4958..02419a63 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/interfaces/ModelCache.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/interfaces/ModelCache.java @@ -16,20 +16,21 @@ package com.webank.ai.fate.serving.interfaces; +import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.federatedml.PipelineTask; import java.util.Set; public interface ModelCache { - public void put(String modelKey, PipelineTask model); + public void put(Context context,String modelKey, PipelineTask model); - public PipelineTask get(String modelKey); + public PipelineTask get(Context context, String modelKey); public long getSize(); public Set getKeys(); - public PipelineTask loadModel(String modelKey); + public PipelineTask loadModel(Context context,String modelKey); } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/interfaces/ModelManager.java b/serving-server/src/main/java/com/webank/ai/fate/serving/interfaces/ModelManager.java index a58f0dfc..68146a88 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/interfaces/ModelManager.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/interfaces/ModelManager.java @@ -29,15 +29,15 @@ public interface ModelManager { public ReturnResult publishOnlineModel(Context context,FederatedParty federatedParty, FederatedRoles federatedRoles, Map> federatedRolesModel); - public PipelineTask getModel(String name, String namespace); + public PipelineTask getModel(Context context, String name, String namespace); - public ModelNamespaceData getModelNamespaceData(String namespace); + public ModelNamespaceData getModelNamespaceData(Context context,String namespace); - public String getModelNamespaceByPartyId(String partyId); + public String getModelNamespaceByPartyId(Context context,String partyId); - public ModelInfo getModelInfoByPartner(String partnerModelName, String partnerModelNamespace); + public ModelInfo getModelInfoByPartner(Context context,String partnerModelName, String partnerModelNamespace); - public PipelineTask pushModelIntoPool(String name, String namespace); + public PipelineTask pushModelIntoPool(Context context,String name, String namespace); } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/AbstractModelLoader.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/AbstractModelLoader.java new file mode 100644 index 00000000..6c8712d6 --- /dev/null +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/AbstractModelLoader.java @@ -0,0 +1,150 @@ +package com.webank.ai.fate.serving.manger; + + +import com.webank.ai.fate.serving.core.bean.Context; +import com.webank.ai.fate.serving.core.bean.Dict; +import com.webank.ai.fate.serving.core.exceptions.ModelSerializeException; +import com.webank.ai.fate.serving.federatedml.PipelineTask; +import com.webank.ai.fate.serving.service.ModelService; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public abstract class AbstractModelLoader implements ModelLoader{ + + Logger logger = LoggerFactory.getLogger(AbstractModelLoader.class); + + @Override + public PipelineTask loadModel(Context context, String name, String namespace) { + + MODELDATA modelData = doLoadModel(context ,name, namespace); + if (modelData == null) { + logger.info("load model error, name {} namespace {} ,try to restore from local cache",name,namespace); + modelData = restore(context,name,namespace); + if(modelData ==null){ + logger.info("load model from local cache error, name {} namespace {}",name,namespace); + } + }else{ + this.store(context,name,namespace,modelData); + } + return this.initPipeLine(context, modelData); + } + + + protected void store(Context context,String name,String namespace,MODELDATA data){ + try { + String cachePath = getCachePath(context, name, namespace); + if (cachePath != null) { + byte[] bytes = this.serialize(context,data); + if(bytes==null){ + throw new ModelSerializeException(); + } + File file = new File(cachePath); + this.doStore(context,bytes,file); + } + }catch(ModelSerializeException e){ + logger.error("serialize mode data error",e); + + }catch(Exception e){ + logger.error("store mode data error",e); + + } + } + + protected abstract byte[] serialize(Context context,MODELDATA data); + protected abstract MODELDATA unserialize(Context context,byte[] data); + + protected MODELDATA restore(Context context,String name,String namespace){ + try { + String cachePath = getCachePath(context, name, namespace); + if (cachePath!=null) { + + byte[] bytes = doRestore(new File(cachePath)); + MODELDATA modelData = this.unserialize(context,bytes); + return modelData; + } + }catch(Throwable e){ + logger.error("restore model data error",e); + } + return null; + } + protected abstract PipelineTask initPipeLine(Context context,MODELDATA modeldata); + private String getCachePath(Context context,String name,String namespace){ + StringBuilder sb = new StringBuilder(); + String locationPre = System.getProperty(Dict.PROPERTY_USER_HOME); + if (StringUtils.isNotEmpty(locationPre)&&StringUtils.isNotEmpty(name)&&StringUtils.isNotEmpty(namespace)) { + String cachFilePath =sb.append( locationPre).append ("/.fate/model_").append(name).append("_").append(namespace).append("_").append("cache").toString(); + return cachFilePath; + } + return null; + } + + protected void doStore(Context context ,byte[] data, File file) { + + if (file == null) { + return; + } + // Save + try { + File lockfile = new File(file.getAbsolutePath() + ".lock"); + if (!lockfile.exists()) { + lockfile.createNewFile(); + } + try (RandomAccessFile raf = new RandomAccessFile(lockfile, "rw"); + FileChannel channel = raf.getChannel()) { + FileLock lock = channel.tryLock(); + if (lock == null) { + throw new IOException("Can not lock the registry cache file " + file.getAbsolutePath() + ", ignore and retry later, maybe multi java process use the file"); + } + try { + if (!file.exists()) { + file.createNewFile(); + } + try (FileOutputStream outputFile = new FileOutputStream(file)) { + outputFile.write(data); + } + } finally { + lock.release(); + } + } + } catch (Throwable e) { + logger.error("Failed to save model cache file, will retry, cause: " + e.getMessage(), e); + } + } + + protected byte[] doRestore(File file) { + + if (file != null && file.exists()) { + + try ( InputStream in = new FileInputStream(file)){ + Long filelength = file.length(); + byte[] filecontent = new byte[filelength.intValue()]; + int readCount = in.read(filecontent); + if(readCount>0){ + return filecontent; + }else { + return null; + } + } + catch (Throwable e) { + logger.error("failed to doRestore file ", e); + } + + } + return null; + } + + + + protected abstract MODELDATA doLoadModel(Context context, String name, String namespace); + + +} diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultHttpModelLoader.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultHttpModelLoader.java new file mode 100644 index 00000000..8f848e95 --- /dev/null +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultHttpModelLoader.java @@ -0,0 +1,161 @@ +/* + * Copyright 2019 The FATE Authors. All Rights Reserved. + * + * 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.webank.ai.fate.serving.manger; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.google.common.collect.Maps; +import com.webank.ai.fate.api.mlmodel.manager.ModelServiceProto; +import com.webank.ai.fate.register.router.RouterService; +import com.webank.ai.fate.register.url.URL; +import com.webank.ai.fate.serving.core.bean.*; +import com.webank.ai.fate.serving.federatedml.PipelineTask; +import com.webank.ai.fate.serving.utils.HttpClientPool; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.*; + +@Component +public class DefaultHttpModelLoader extends AbstractModelLoader>{ + private static final Logger logger = LoggerFactory.getLogger(DefaultHttpModelLoader.class); + + @Autowired + private RouterService routerService; + + + @Override + protected byte[] serialize(Context context, Map data) { + Map result = Maps.newHashMap(); + if(data!=null){ + data.forEach((k,v)->{ + String base64String = new String(Base64.getEncoder().encode(v)); + result.put(k,base64String); + }); + + return JSON.toJSONString(result).getBytes(); + } + return null; + } + + @Override + protected Map unserialize(Context context, byte[] data) { + Map result = Maps.newHashMap(); + if(data !=null) { + String dataString = new String(data); + Map originData = JSON.parseObject(dataString,Map.class); + if(originData!=null){ + originData.forEach((k,v)->{ + result.put(k.toString(),Base64.getDecoder().decode(v.toString())); + }); + return result; + } + } + return null; + } + + @Override + protected PipelineTask initPipeLine(Context context, Map stringMap) { + PipelineTask pipelineTask = new PipelineTask(); + pipelineTask.initModel(stringMap); + return pipelineTask; + } + + @Override + protected Map doLoadModel(Context context,String name, String namespace) { + + + logger.info("read model, name: {} namespace: {}", name, namespace); + try { + String requestUrl = ""; + boolean useRegister = Boolean.valueOf(Configuration.getProperty(Dict.USE_REGISTER)); + if (useRegister) { + URL url = URL.valueOf("flow/online/transfer"); + List urls = routerService.router(url); + if (urls == null || urls.isEmpty()) { + logger.info("url not found, {}", url); + } else { + url = urls.get(0); + requestUrl = url.toFullString(); + } + } + + if (StringUtils.isBlank(requestUrl)) { + requestUrl = Configuration.getProperty(Dict.MODEL_TRANSFER_URL); + } + + if (StringUtils.isBlank(requestUrl)) { + logger.info("roll address not found"); + return null; + } + + Map requestData = new HashMap<>(); + requestData.put("name", name); + requestData.put("namespace", namespace); + + long start = System.currentTimeMillis(); + String responseBody = HttpClientPool.transferPost(requestUrl, requestData); + long end = System.currentTimeMillis(); + + logger.info("{}|{}|{}|{}", requestUrl, start, end, (end - start) + " ms"); + + if (StringUtils.isEmpty(responseBody)) { + logger.info("read model fail, {}, {}", name, namespace); + return null; + } + + JSONObject responseData = JSONObject.parseObject(responseBody); + if (responseData.getInteger("retcode") != 0) { + logger.info("read model fail, {}, {}, {}", name, namespace, responseData.getString("retmsg")); + return null; + } + + Map resultMap = new HashMap<>(); + Map dataMap = responseData.getJSONObject("data"); + if (dataMap == null || dataMap.isEmpty()) { + logger.info("read model fail, {}, {}, {}", name, namespace, dataMap); + return null; + } + + for (Map.Entry entry : dataMap.entrySet()) { + resultMap.put(entry.getKey(), Base64.getDecoder().decode(String.valueOf(entry.getValue()))); + } + + return resultMap; + }catch(Exception e){ + logger.info("get mode info from fateflow error" ,e); + } + return null; + } +// @Override +// public PipelineTask loadModel(Context context, String name, String namespace) { +// Map modelBytes = readModel(name, namespace); +// if (modelBytes == null || modelBytes.size() == 0) { +// logger.info("loadModel error {} {}",name,namespace); +// return null; +// } +// PipelineTask pipelineTask = new PipelineTask(); +// pipelineTask.initModel(modelBytes); +// return pipelineTask; +// } + + +} \ No newline at end of file diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelCache.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelCache.java index 145cb65f..f62177f3 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelCache.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelCache.java @@ -20,11 +20,13 @@ import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.webank.ai.fate.serving.core.bean.Configuration; +import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.federatedml.PipelineTask; import com.webank.ai.fate.serving.interfaces.ModelCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Set; @@ -35,27 +37,30 @@ public class DefaultModelCache implements ModelCache { private static final Logger logger = LoggerFactory.getLogger(DefaultModelCache.class); private LoadingCache modelCache; + @Autowired + private ModelLoader modelLoader; public DefaultModelCache() { modelCache = CacheBuilder.newBuilder() .expireAfterAccess(Configuration.getPropertyInt(Dict.PROPERTY_MODEL_CACHE_ACCESS_TTL), TimeUnit.HOURS) .maximumSize(Configuration.getPropertyInt(Dict.PROPERTY_MODEL_CACHE_MAX_SIZE)) - .build(new CacheLoader() { + .build( + new CacheLoader() { @Override public PipelineTask load(String s) throws Exception { - return loadModel(s); + return loadModel(null,s); } }); } @Override - public PipelineTask loadModel(String modelKey) { - String[] modelKeyFields = ModelUtils.splitModelKey(modelKey); - return ModelUtils.loadModel(modelKeyFields[0], modelKeyFields[1]); + public PipelineTask loadModel(Context context, String modelKey) { + String[] modelKeyFields = ModelUtil.splitModelKey(modelKey); + return modelLoader.loadModel(context,modelKeyFields[0], modelKeyFields[1]); } @Override - public PipelineTask get(String modelKey) { + public PipelineTask get(Context context,String modelKey) { try { return modelCache.get(modelKey); } catch (ExecutionException ex) { @@ -65,7 +70,7 @@ public PipelineTask get(String modelKey) { } @Override - public void put(String modelKey, PipelineTask model) { + public void put(Context context,String modelKey, PipelineTask model) { modelCache.put(modelKey, model); } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java index f7b45c03..6e1a0d50 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java @@ -55,6 +55,8 @@ public class DefaultModelManager implements ModelManager, InitializingBean { private ModelCache modelCache; private ConcurrentHashMap partnerModelData; private File modelFile; + @Autowired + private ModelLoader modelLoader; public DefaultModelManager() { @@ -67,7 +69,6 @@ public DefaultModelManager() { modelFederatedParty = new HashMap<>(); modelFederatedRoles = new HashMap<>(); - String filename = System.getProperty(Dict.PROPERTY_USER_HOME) + "/.fate/fate-model.cache"; File file = null; if (StringUtils.isNotEmpty(filename)) { @@ -106,7 +107,7 @@ public ReturnResult publishLoadModel(Context context,FederatedParty federatedPar returnResult.setRetcode(InferenceRetCode.LOAD_MODEL_FAILED); return returnResult; } - PipelineTask model = pushModelIntoPool(modelInfo.getName(), modelInfo.getNamespace()); + PipelineTask model = pushModelIntoPool(context,modelInfo.getName(), modelInfo.getNamespace()); if (model == null) { returnResult.setRetcode(InferenceRetCode.LOAD_MODEL_FAILED); return returnResult; @@ -114,7 +115,7 @@ public ReturnResult publishLoadModel(Context context,FederatedParty federatedPar federatedRolesModel.forEach((roleName, roleModelInfo) -> { roleModelInfo.forEach((p, m) -> { if (!p.equals(partyId) || (p.equals(partyId) && !role.equals(roleName))) { - String partnerModelKey = ModelUtils.genModelKey(m.getName(), m.getNamespace()); + String partnerModelKey = ModelUtil.genModelKey(m.getName(), m.getNamespace()); partnerModelData.put(partnerModelKey, modelInfo); logger.info("Create model index({}) for partner({}, {})", partnerModelKey, roleName, p); } @@ -127,8 +128,6 @@ public ReturnResult publishLoadModel(Context context,FederatedParty federatedPar zookeeperRegistry.addDynamicEnvironment(serviceId); } partnerModelData.forEach((key,v)->{ - - String keyMd5 = EncryptUtils.encrypt(key,EncryptMethod.MD5); logger.info("transform key {} to md5key {}",key,keyMd5); zookeeperRegistry.addDynamicEnvironment(keyMd5); @@ -136,8 +135,6 @@ public ReturnResult publishLoadModel(Context context,FederatedParty federatedPar }); - - } } @@ -170,8 +167,8 @@ public ReturnResult publishOnlineModel(Context context,FederatedParty federatedP return returnResult; } - String modelKey = ModelUtils.genModelKey(modelInfo.getName(), modelInfo.getNamespace()); - PipelineTask model = modelCache.get(modelKey); + String modelKey = ModelUtil.genModelKey(modelInfo.getName(), modelInfo.getNamespace()); + PipelineTask model = modelCache.get(context,modelKey); if (model == null) { returnResult.setRetcode(InferenceRetCode.LOAD_MODEL_FAILED); returnResult.setRetmsg("Can not found model by these information."); @@ -213,35 +210,35 @@ public ReturnResult publishOnlineModel(Context context,FederatedParty federatedP } @Override - public PipelineTask getModel(String name, String namespace) { - return modelCache.get(ModelUtils.genModelKey(name, namespace)); + public PipelineTask getModel(Context context,String name, String namespace) { + return modelCache.get(context,ModelUtil.genModelKey(name, namespace)); } @Override - public ModelNamespaceData getModelNamespaceData(String namespace) { + public ModelNamespaceData getModelNamespaceData(Context context,String namespace) { return modelNamespaceDataMapPool.get(namespace); } @Override - public String getModelNamespaceByPartyId(String partyId) { + public String getModelNamespaceByPartyId(Context context,String partyId) { return appNamespaceMapPool.get(partyId); } @Override - public ModelInfo getModelInfoByPartner(String partnerModelName, String partnerModelNamespace) { - return partnerModelData.get(ModelUtils.genModelKey(partnerModelName, partnerModelNamespace)); + public ModelInfo getModelInfoByPartner(Context context,String partnerModelName, String partnerModelNamespace) { + return partnerModelData.get(ModelUtil.genModelKey(partnerModelName, partnerModelNamespace)); } @Override - public PipelineTask pushModelIntoPool(String name, String namespace) { - PipelineTask model = ModelUtils.loadModel(name, namespace); + public PipelineTask pushModelIntoPool(Context context ,String name, String namespace) { + PipelineTask model = modelLoader.loadModel(context,name, namespace); if (model == null) { return null; } - modelCache.put(ModelUtils.genModelKey(name, namespace), model); + modelCache.put(context,ModelUtil.genModelKey(name, namespace), model); logger.info("Load model success, name: {}, namespace: {}, model cache size is {}", name, namespace, modelCache.getSize()); return model; } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelLoader.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelLoader.java new file mode 100644 index 00000000..3656dea4 --- /dev/null +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelLoader.java @@ -0,0 +1,9 @@ +package com.webank.ai.fate.serving.manger; + +import com.webank.ai.fate.serving.federatedml.PipelineTask; +import com.webank.ai.fate.serving.core.bean.Context; +public interface ModelLoader { + + public PipelineTask loadModel(Context context,String name, String namespace); + +} diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtil.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtil.java new file mode 100644 index 00000000..551cc8f2 --- /dev/null +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtil.java @@ -0,0 +1,48 @@ +package com.webank.ai.fate.serving.manger; + +import com.webank.ai.fate.api.mlmodel.manager.ModelServiceProto; +import com.webank.ai.fate.serving.core.bean.FederatedRoles; +import com.webank.ai.fate.serving.core.bean.ModelInfo; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + + +public class ModelUtil { + + + private static final String MODEL_KEY_SEPARATOR = "&"; + + + public static String genModelKey(String name, String namespace) { + return StringUtils.join(Arrays.asList(name, namespace), MODEL_KEY_SEPARATOR); + } + + public static String[] splitModelKey(String key) { + return StringUtils.split(key, MODEL_KEY_SEPARATOR); + } + + + public static FederatedRoles getFederatedRoles(Map federatedRolesProto) { + FederatedRoles federatedRoles = new FederatedRoles(); + federatedRolesProto.forEach((roleName, party) -> { + federatedRoles.setRole(roleName, party.getPartyIdList()); + }); + return federatedRoles; + } + + public static Map> getFederatedRolesModel(Map federatedRolesModelProto) { + Map> federatedRolesModel = new HashMap<>(8); + federatedRolesModelProto.forEach((roleName, roleModelInfo) -> { + federatedRolesModel.put(roleName, new HashMap<>(8)); + roleModelInfo.getRoleModelInfoMap().forEach((partyId, modelInfo) -> { + federatedRolesModel.get(roleName).put(partyId, new ModelInfo(modelInfo.getTableName(), modelInfo.getNamespace())); + }); + }); + return federatedRolesModel; + } + + +} diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java deleted file mode 100644 index 856bbf51..00000000 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtils.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.serving.manger; - -import com.alibaba.fastjson.JSONObject; -import com.webank.ai.fate.api.mlmodel.manager.ModelServiceProto; -import com.webank.ai.fate.register.router.RouterService; -import com.webank.ai.fate.register.url.URL; -import com.webank.ai.fate.serving.core.bean.Configuration; -import com.webank.ai.fate.serving.core.bean.Dict; -import com.webank.ai.fate.serving.core.bean.FederatedRoles; -import com.webank.ai.fate.serving.core.bean.ModelInfo; -import com.webank.ai.fate.serving.federatedml.PipelineTask; -import com.webank.ai.fate.serving.utils.HttpClientPool; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import java.util.*; - -@Component -public class ModelUtils { - private static final Logger logger = LoggerFactory.getLogger(ModelUtils.class); - private static final String MODEL_KEY_SEPARATOR = "&"; - - private static ModelUtils modelUtils; - - @Autowired - private RouterService routerService; - - @PostConstruct - private void init() { - modelUtils = this; - } - - public static Map readModel(String name, String namespace) { - logger.info("read model, name: {} namespace: {}", name, namespace); - - String requestUrl = ""; - boolean useRegister = Boolean.valueOf(Configuration.getProperty(Dict.USE_REGISTER)); - if (useRegister) { - URL url = URL.valueOf("flow/online/transfer"); - List urls = modelUtils.routerService.router(url); - if (urls == null || urls.isEmpty()) { - logger.info("url not found, {}", url); - } else { - url = urls.get(0); - requestUrl = url.toFullString(); - } - } - - if (StringUtils.isBlank(requestUrl)) { - requestUrl = Configuration.getProperty(Dict.MODEL_TRANSFER_URL); - } - - if (StringUtils.isBlank(requestUrl)) { - logger.info("roll address not found"); - return null; - } - - Map requestData = new HashMap<>(); - requestData.put("name", name); - requestData.put("namespace", namespace); - - long start = System.currentTimeMillis(); - String responseBody = HttpClientPool.transferPost(requestUrl, requestData); - long end = System.currentTimeMillis(); - - logger.info("{}|{}|{}|{}", requestUrl, start, end, (end - start) + " ms"); - - if (StringUtils.isEmpty(responseBody)) { - logger.info("read model fail, {}, {}", name, namespace); - return null; - } - - JSONObject responseData = JSONObject.parseObject(responseBody); - if (responseData.getInteger("retcode") != 0) { - logger.info("read model fail, {}, {}, {}", name, namespace, responseData.getString("retmsg")); - return null; - } - - Map resultMap = new HashMap<>(); - Map dataMap = responseData.getJSONObject("data"); - if (dataMap == null || dataMap.isEmpty()) { - logger.info("read model fail, {}, {}, {}", name, namespace, dataMap); - return null; - } - - for (Map.Entry entry : dataMap.entrySet()) { - resultMap.put(entry.getKey(), Base64.getDecoder().decode(String.valueOf(entry.getValue()))); - } - - return resultMap; - } - - public static PipelineTask loadModel(String name, String namespace) { - Map modelBytes = readModel(name, namespace); - if (modelBytes == null || modelBytes.size() == 0) { - logger.info("loadModel error {} {}",name,namespace); - return null; - } - PipelineTask pipelineTask = new PipelineTask(); - pipelineTask.initModel(modelBytes); - return pipelineTask; - } - - public static String genModelKey(String name, String namespace) { - return StringUtils.join(Arrays.asList(name, namespace), MODEL_KEY_SEPARATOR); - } - - public static String[] splitModelKey(String key) { - return StringUtils.split(key, MODEL_KEY_SEPARATOR); - } - - - public static FederatedRoles getFederatedRoles(Map federatedRolesProto) { - FederatedRoles federatedRoles = new FederatedRoles(); - federatedRolesProto.forEach((roleName, party) -> { - federatedRoles.setRole(roleName, party.getPartyIdList()); - }); - return federatedRoles; - } - - public static Map> getFederatedRolesModel(Map federatedRolesModelProto) { - Map> federatedRolesModel = new HashMap<>(8); - federatedRolesModelProto.forEach((roleName, roleModelInfo) -> { - federatedRolesModel.put(roleName, new HashMap<>(8)); - roleModelInfo.getRoleModelInfoMap().forEach((partyId, modelInfo) -> { - federatedRolesModel.get(roleName).put(partyId, new ModelInfo(modelInfo.getTableName(), modelInfo.getNamespace())); - }); - }); - return federatedRolesModel; - } -} \ No newline at end of file diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java index 99e6ad79..81647091 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java @@ -29,7 +29,8 @@ import com.webank.ai.fate.serving.core.bean.*; import com.webank.ai.fate.serving.core.utils.ObjectTransform; import com.webank.ai.fate.serving.interfaces.ModelManager; -import com.webank.ai.fate.serving.manger.ModelUtils; +import com.webank.ai.fate.serving.manger.ModelUtil; + import io.grpc.stub.StreamObserver; import org.apache.commons.codec.digest.Md5Crypt; import org.apache.commons.lang3.StringUtils; @@ -123,8 +124,8 @@ public synchronized void publishLoad(PublishRequest req, StreamObserver Date: Mon, 3 Feb 2020 11:51:22 +0800 Subject: [PATCH 132/190] ok Signed-off-by: v_dylanxu <136539068@qq.com> --- fate-serving-core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fate-serving-core/pom.xml b/fate-serving-core/pom.xml index 6dca2ad7..3c0d0eb2 100644 --- a/fate-serving-core/pom.xml +++ b/fate-serving-core/pom.xml @@ -83,7 +83,7 @@ com.alibaba.csp sentinel-core - RELEASE + 1.6.3 From b59446fe861b82d53738db23110edc0dcd81ddbe Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Tue, 4 Feb 2020 09:52:15 +0800 Subject: [PATCH 133/190] shell update route_table --- .../cluster-deploy/scripts/package.sh | 30 +++++++++---------- .../Fate-serving_deployment_guide_build_zh.md | 1 + 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/serving-server/cluster-deploy/scripts/package.sh b/serving-server/cluster-deploy/scripts/package.sh index 0af764eb..a603688d 100644 --- a/serving-server/cluster-deploy/scripts/package.sh +++ b/serving-server/cluster-deploy/scripts/package.sh @@ -10,15 +10,15 @@ cd ${cwd} public_path=$deploy_dir package_path=$cwd/FATE-Serving fate_serving_path=$public_path/fate-serving -sering_path=$fate_serving_path/serving +sering_path=$fate_serving_path/serving-server fate_serving=fate-serving -serving=serving +serving=serving-server fate_serving_zip=fate-serving-server-*-release.zip fate_serving_jar=fate-serving-server-*.jar fate_serving_proxy_zip=fate-serving-proxy-*-release.zip fate_serving_proxy_jar=fate-serving-proxy-*.jar serving_proxy=serving-proxy -serving_proxy_path=$fate_serving_path/serving-proxy +serving_proxy_path=$fate_serving_path/$serving_proxy mkdir -p ./${fate_serving}/${serving} mkdir -p ./${fate_serving}/${serving_proxy} @@ -49,7 +49,7 @@ ln -s $fate_serving_proxy_jar fate-serving-proxy.jar #cp modify_json.py cd ${cwd} -cp modify_json.py ./fate-serving/serving-proxy/conf +cp modify_json.py ./fate-serving/$serving_proxy/conf tar -zcvf fate-serving.tar.gz fate-serving cp_serving() { @@ -84,7 +84,7 @@ update_config () { do #update serving-proxy config path ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/fate-serving/serving-proxy/conf + cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf sed -i "/^route.table=/croute.table=${deploy_dir}/fate-serving/serving-proxy/conf/route_table.json" application.properties sed -i "/^auth.file=/cauth.file=${deploy_dir}/fate-serving/serving-proxy/conf/auth_config.json" application.properties exit @@ -95,7 +95,7 @@ eeooff if [ $temp = 0 ] then ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/fate-serving/serving/conf + cd ${deploy_dir}/${fate_serving}/${serving}/conf sed -i "/^redis.ip=/credis.ip=${host_redis_ip}" serving-server.properties sed -i "/^redis.port=/credis.port=${host_redis_port}" serving-server.properties sed -i "/^redis.password=/credis.password=${host_redis_password}" serving-server.properties @@ -105,7 +105,7 @@ exit eeooff else ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/fate-serving/serving/conf + cd ${deploy_dir}/${fate_serving}/${serving}/conf sed -i "/^redis.ip=/credis.ip=${guest_redis_ip}" serving-server.properties sed -i "/^redis.port=/credis.port=${guest_redis_port}" serving-server.properties sed -i "/^redis.password=/credis.password=${guest_redis_password}" serving-server.properties @@ -126,11 +126,11 @@ update_zk_config () { if [ $temp = 0 ] then ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/fate-serving/serving/conf + cd ${deploy_dir}/${fate_serving}/${serving}/conf sed -i "/^zk.url=/czk.url=${host_zk_url}" serving-server.properties sed -i "/^useRegister=/cuseRegister=true" serving-server.properties sed -i "/^useZkRouter=/cuseZkRouter=true" serving-server.properties - cd ${deploy_dir}/fate-serving/serving-proxy/conf + cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf sed -i "/^zk.url=/czk.url=${host_zk_url}" application.properties sed -i "/^useRegister=/cuseRegister=true" application.properties sed -i "/^useZkRouter=/cuseZkRouter=true" application.properties @@ -138,11 +138,11 @@ exit eeooff else ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/fate-serving/serving/conf + cd ${deploy_dir}/${fate_serving}/${serving}/conf sed -i "/^zk.url=/czk.url=${guest_zk_url}" serving-server.properties sed -i "/^useRegister=/cuseRegister=true" serving-server.properties sed -i "/^useZkRouter=/cuseZkRouter=true" serving-server.properties - cd ${deploy_dir}/fate-serving/serving-proxy/conf + cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf sed -i "/^zk.url=/czk.url=${guest_zk_url}" application.properties sed -i "/^useRegister=/cuseRegister=true" application.properties sed -i "/^useZkRouter=/cuseZkRouter=true" application.properties @@ -159,7 +159,7 @@ update_route_table () { if [ $temp = 0 ] then ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/fate-serving/serving-proxy/conf + cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf sed -i "3c default_default_ip=\"${host_guest[i+1]}\"" modify_json.py sed -i "4c default_default_port=8869" modify_json.py sed -i "5c party_id=${party_list[i]}" modify_json.py @@ -173,7 +173,7 @@ exit eeooff else ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/fate-serving/serving-proxy/conf + cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf sed -i "3c default_default_ip=\"${host_guest[i-1]}\"" modify_json.py sed -i "4c default_default_port=8869" modify_json.py sed -i "5c party_id=${party_list[i]}" modify_json.py @@ -196,10 +196,10 @@ update_nozk_config () { for ((i=0;i<${#host_guest[*]};i++)) do ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/fate-serving/serving/conf + cd ${deploy_dir}/${fate_serving}/${serving}/conf sed -i "/^useRegister=/cuseRegister=false" serving-server.properties sed -i "/^useZkRouter=/cuseZkRouter=false" serving-server.properties - cd ${deploy_dir}/fate-serving/serving-proxy/conf + cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf sed -i "/^useZkRouter=/cuseZkRouter=false" application.properties exit eeooff diff --git a/serving-server/doc/Fate-serving_deployment_guide_build_zh.md b/serving-server/doc/Fate-serving_deployment_guide_build_zh.md index 1b0df06a..7c4493e8 100644 --- a/serving-server/doc/Fate-serving_deployment_guide_build_zh.md +++ b/serving-server/doc/Fate-serving_deployment_guide_build_zh.md @@ -162,6 +162,7 @@ Zookeeper 3.5.5+ 进入执行节点的/data/projects/目录,执行: cd /data/projects/ + git clone https://github.com/FederatedAI/FATE-Serving.git 4.2 配置文件修改和示例 From 0bef0a9831b025097cf5c883075b6dbe80b10bbf Mon Sep 17 00:00:00 2001 From: dylanxu <136539068@qq.com> Date: Tue, 4 Feb 2020 11:35:32 +0800 Subject: [PATCH 134/190] ok --- images/fate-serving-infrastructure.jpg | Bin 0 -> 98176 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/fate-serving-infrastructure.jpg diff --git a/images/fate-serving-infrastructure.jpg b/images/fate-serving-infrastructure.jpg new file mode 100644 index 0000000000000000000000000000000000000000..32d3c35620d0c35992e536a1a991aa91682bbf67 GIT binary patch literal 98176 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 Date: Wed, 5 Feb 2020 11:35:49 +0800 Subject: [PATCH 135/190] add MockAdapter Signed-off-by: v_dylanxu <136539068@qq.com> --- .../adapter/dataaccess/MockAdapter.java | 53 +++++++++++++++++++ .../main/resources/serving-server.properties | 2 +- 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/MockAdapter.java diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/MockAdapter.java b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/MockAdapter.java new file mode 100644 index 00000000..79d0e6b4 --- /dev/null +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess/MockAdapter.java @@ -0,0 +1,53 @@ +/* + * Copyright 2019 The FATE Authors. All Rights Reserved. + * + * 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.webank.ai.fate.serving.adapter.dataaccess; + + +import com.alibaba.fastjson.JSONObject; +import com.webank.ai.fate.serving.core.bean.Context; +import com.webank.ai.fate.serving.core.bean.ReturnResult; +import com.webank.ai.fate.serving.core.constant.InferenceRetCode; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +public class MockAdapter implements FeatureData { + private static final Logger logger = LoggerFactory.getLogger(MockAdapter.class); + + @Override + public ReturnResult getData(Context context, Map featureIds) { + ReturnResult returnResult = new ReturnResult(); + Map data = new HashMap<>(); + try { + String mockData = "x0:1.88669,x1:-1.359293,x2:2.303601,x3:2.001237,x4:1.307686,x5:2.616665,x6:2.109526,x7:2.296076,x8:2.750622,x9:1.937015"; + for (String kv : StringUtils.split(mockData, ",")) { + String[] a = StringUtils.split(kv, ":"); + data.put(a[0], Double.valueOf(a[1])); + } + returnResult.setData(data); + returnResult.setRetcode(InferenceRetCode.OK); + logger.info("MockAdapter result, {}", JSONObject.toJSONString(returnResult)); + } catch (Exception ex) { + logger.error(ex.getMessage()); + returnResult.setRetcode(InferenceRetCode.GET_FEATURE_FAILED); + } + return returnResult; + } +} diff --git a/serving-server/src/main/resources/serving-server.properties b/serving-server/src/main/resources/serving-server.properties index 3702003b..e475312c 100644 --- a/serving-server/src/main/resources/serving-server.properties +++ b/serving-server/src/main/resources/serving-server.properties @@ -47,7 +47,7 @@ external.processCacheDBIndex=0 # federation party.id=9999 # adapter -OnlineDataAccessAdapter=TestFile +OnlineDataAccessAdapter=MockAdapter InferencePostProcessingAdapter=PassPostProcessing InferencePreProcessingAdapter=PassPreProcessing # external subsystem From 06669f5da12e8c71dec7f6efbad85ebdf4d49221 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Wed, 5 Feb 2020 12:22:11 +0800 Subject: [PATCH 136/190] debug Signed-off-by: v_dylanxu <136539068@qq.com> --- serving-proxy/src/main/resources/application.properties | 4 ++-- serving-proxy/src/main/resources/route_table.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/serving-proxy/src/main/resources/application.properties b/serving-proxy/src/main/resources/application.properties index 1999a3b8..ce588aa4 100644 --- a/serving-proxy/src/main/resources/application.properties +++ b/serving-proxy/src/main/resources/application.properties @@ -10,8 +10,8 @@ management.endpoints.web.exposure.include=health,info,metrics #random, consistent routeType=random -route.table=/data/projects/serving-proxy/conf/route_table.json -auth.file=/data/projects/serving-proxy/conf/auth_config.json +route.table=/data/projects/fate-serving/serving-proxy/conf/route_table.json +auth.file=/data/projects/fate-serving/serving-proxy/conf/auth_config.json useZkRouter=false zk.url=zookeeper://localhost:2181 diff --git a/serving-proxy/src/main/resources/route_table.json b/serving-proxy/src/main/resources/route_table.json index 57c58e4a..4c84d2c0 100644 --- a/serving-proxy/src/main/resources/route_table.json +++ b/serving-proxy/src/main/resources/route_table.json @@ -18,7 +18,7 @@ "serving": [ { "ip": "127.0.0.1", - "prot": 8080 + "port": 8080 } ] } From f08c13c9a932c66e79654382503e0dcc0555521d Mon Sep 17 00:00:00 2001 From: kaideng Date: Thu, 6 Feb 2020 09:11:51 +0800 Subject: [PATCH 137/190] change config file Signed-off-by: kaideng --- README.md | 19 ++++++++++++++++-- conf/serving-server.properties | 8 -------- images/fate-serving-infrastructure.jpg | Bin 0 -> 98176 bytes .../serving/proxy/config/RegistryConfig.java | 2 +- .../proxy/rpc/router/ZkServingRouter.java | 2 +- serving-proxy/src/main/resources/log4j2.xml | 4 ---- .../manger/DefaultHttpModelLoader.java | 4 ++-- .../serving/manger/DefaultModelCache.java | 2 +- serving-server/src/main/resources/log4j2.xml | 2 -- .../main/resources/serving-server.properties | 14 +++---------- 10 files changed, 25 insertions(+), 32 deletions(-) create mode 100644 images/fate-serving-infrastructure.jpg diff --git a/README.md b/README.md index 5919e5a4..a34e20e8 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,19 @@ FATE-Serving is a high-performance, industrialized serving system for federated ## Architecture -![fate_serving_arch](./images/fate_serving_arch.png) + +![fate_serving_arch](./images/fate-serving-infrastructure.jpg) + + + +As shown in the figure above,fate-serving includes the following modules: +- serving-server +- serving-proxy + +Now we will explain the architecture and function of each module in turn. +first,the serving-server: + + @@ -140,6 +152,8 @@ For detail, Here are some key steps: + + ### Deploy Serving-Router For detail, Here are some key steps: @@ -177,6 +191,7 @@ Please use FATE-Flow Client which in the fate-flow to operate, refer to **Online - ` http://ip:port/federation/v1/inference ` - where ip:port is the address of guest serving proxy + **request type** - POST - content-application/json @@ -189,7 +204,7 @@ Please use FATE-Flow Client which in the fate-flow to operate, refer to **Online |body |no |json object | the elements and features used by the model| - **head object** - + |name|allow null|type|desc| |:---- |:---|:----- |----- | |serviceId |yes |string | the serviceId used when bind model in fate-flow| diff --git a/conf/serving-server.properties b/conf/serving-server.properties index 0fbde69c..ba7ed993 100644 --- a/conf/serving-server.properties +++ b/conf/serving-server.properties @@ -13,15 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. # -coordinator=webank ip=127.0.0.1 port=8000 -workMode=0 serviceRoleName=serving inferenceWorkerThreadNum=10 -#storage -# maybe python/data/ -standaloneStoragePath= # cache remoteModelInferenceResultCacheSwitch=true # in-process cache @@ -45,7 +40,6 @@ external.inferenceResultCacheDBIndex=0 canCacheRetcode=0,102 external.processCacheDBIndex=0 # federation -party.id=9999 # adapter OnlineDataAccessAdapter=TestFile InferencePostProcessingAdapter=PassPostProcessing @@ -57,5 +51,3 @@ roll=127.0.0.1:8011 zk.url=zookeeper://localhost:2181 useRegister=false useZkRouter=false -useJMX=true -jmx.server.name=JMXServer diff --git a/images/fate-serving-infrastructure.jpg b/images/fate-serving-infrastructure.jpg new file mode 100644 index 0000000000000000000000000000000000000000..32d3c35620d0c35992e536a1a991aa91682bbf67 GIT binary patch literal 98176 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 serving-proxy - - @@ -55,8 +53,6 @@ - - diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultHttpModelLoader.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultHttpModelLoader.java index 8f848e95..f07ae7bb 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultHttpModelLoader.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultHttpModelLoader.java @@ -51,6 +51,7 @@ protected byte[] serialize(Context context, Map data) { result.put(k,base64String); }); + return JSON.toJSONString(result).getBytes(); } return null; @@ -82,11 +83,10 @@ protected PipelineTask initPipeLine(Context context, Map stringM @Override protected Map doLoadModel(Context context,String name, String namespace) { - logger.info("read model, name: {} namespace: {}", name, namespace); try { String requestUrl = ""; - boolean useRegister = Boolean.valueOf(Configuration.getProperty(Dict.USE_REGISTER)); + boolean useRegister = Boolean.valueOf(Configuration.getProperty(Dict.USE_REGISTER,"true")); if (useRegister) { URL url = URL.valueOf("flow/online/transfer"); List urls = routerService.router(url); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelCache.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelCache.java index f62177f3..ea524e63 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelCache.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelCache.java @@ -42,7 +42,7 @@ public class DefaultModelCache implements ModelCache { public DefaultModelCache() { modelCache = CacheBuilder.newBuilder() - .expireAfterAccess(Configuration.getPropertyInt(Dict.PROPERTY_MODEL_CACHE_ACCESS_TTL), TimeUnit.HOURS) + // .expireAfterAccess(Configuration.getPropertyInt(Dict.PROPERTY_MODEL_CACHE_ACCESS_TTL), TimeUnit.HOURS) .maximumSize(Configuration.getPropertyInt(Dict.PROPERTY_MODEL_CACHE_MAX_SIZE)) .build( new CacheLoader() { diff --git a/serving-server/src/main/resources/log4j2.xml b/serving-server/src/main/resources/log4j2.xml index 819aef85..f8205dc6 100644 --- a/serving-server/src/main/resources/log4j2.xml +++ b/serving-server/src/main/resources/log4j2.xml @@ -20,8 +20,6 @@ fate serving-server - - Date: Thu, 6 Feb 2020 14:06:09 +0800 Subject: [PATCH 138/190] change flow log Signed-off-by: kaideng --- .../core/rpc/core/AbstractServiceAdaptor.java | 17 ++++---- .../webank/ai/fate/serving/ServingServer.java | 39 +++++++++---------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/AbstractServiceAdaptor.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/AbstractServiceAdaptor.java index ed10988d..87cb780c 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/AbstractServiceAdaptor.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/AbstractServiceAdaptor.java @@ -144,8 +144,15 @@ public OutboundPackage service(Context context , InboundPackage data requestInHandle.decrementAndGet(); long end = System.currentTimeMillis(); long cost = end - begin; + + if(exceptions.size()!=0){ + try { + outboundPackage = this.serviceFail(context, data, exceptions); + }catch(Throwable e){ + logger.error("handle serviceFail error",e); + } + } try { - logger.info("kaideng test"); flowLogger.info("{}|{}|{}|{}|" + "{}|{}|{}|{}|" + "{}|{}", @@ -156,13 +163,7 @@ public OutboundPackage service(Context context , InboundPackage data logger.error("print flow log error",e); } - if(exceptions.size()!=0){ - try { - outboundPackage = this.serviceFail(context, data, exceptions); - }catch(Throwable e){ - logger.error("handle serviceFail error",e); - } - } + } return outboundPackage; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index da55f947..03d23328 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -58,7 +58,6 @@ public class ServingServer implements InitializingBean { private Server server; private boolean useRegister = false; private String confPath = ""; - public ServingServer() { } @@ -73,8 +72,6 @@ public ServingServer(String confPath) { public static void main(String[] args) { try { - - Options options = new Options(); Option option = Option.builder("c") .longOpt("config") @@ -179,27 +176,27 @@ private void stop() { logger.info("unregister {}", url); zookeeperRegistry.unregister(url); }); - zookeeperRegistry.destroy(); - int retryCount=0; - long requestInProcess = BaseContext.requestInProcess.get(); - do{ - if(requestInProcess>0&&retryCount<3) { - try { - logger.info("try to stop server,there is {} request in process", requestInProcess); - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - retryCount++; - requestInProcess = BaseContext.requestInProcess.get(); - }else{ - break; - } + } + int retryCount=0; + long requestInProcess = BaseContext.requestInProcess.get(); + do{ - }while(requestInProcess>0&&retryCount<3); + logger.info("try to stop server,there is {} request in process,try count {}", requestInProcess,retryCount+1); + if(requestInProcess>0&&retryCount<30) { + try { - } + Thread.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + retryCount++; + requestInProcess = BaseContext.requestInProcess.get(); + }else{ + break; + } + + }while(requestInProcess>0&&retryCount<3); server.shutdown(); } } From dc0d2e9e76b0db5974b71292df5a457d167923f0 Mon Sep 17 00:00:00 2001 From: kaideng Date: Thu, 6 Feb 2020 14:49:55 +0800 Subject: [PATCH 139/190] change thread pool Signed-off-by: kaideng --- .../fate/serving/federatedml/model/BaseModel.java | 15 ++++++++++----- .../com/webank/ai/fate/serving/ServingServer.java | 13 ++++++------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java index cf46f28b..9506b40d 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java @@ -18,6 +18,7 @@ import com.alibaba.fastjson.JSON; import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.ListenableFuture; import com.google.protobuf.ByteString; import com.webank.ai.fate.api.networking.proxy.DataTransferServiceGrpc; import com.webank.ai.fate.api.networking.proxy.Proxy; @@ -34,6 +35,7 @@ import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; public abstract class BaseModel implements Predictor>, FederatedParams, Map> { @@ -193,15 +195,18 @@ protected ReturnResult getFederatedPredictFromRemote(Context context, FederatedP } Preconditions.checkArgument(StringUtils.isNotEmpty(address)); ManagedChannel channel1 = grpcConnectionPool.getManagedChannel(address); + ListenableFuture future= null; try { - - DataTransferServiceGrpc.DataTransferServiceBlockingStub stub1 = DataTransferServiceGrpc.newBlockingStub(channel1); - Proxy.Packet packet = stub1.unaryCall(packetBuilder.build()); - remoteResult = (ReturnResult) ObjectTransform.json2Bean(packet.getBody().getValue().toStringUtf8(), ReturnResult.class); + //DataTransferServiceGrpc.DataTransferServiceBlockingStub stub1 = DataTransferServiceGrpc.newBlockingStub(channel1); + DataTransferServiceGrpc.DataTransferServiceFutureStub stub1 = DataTransferServiceGrpc.newFutureStub(channel1); + future =stub1.unaryCall(packetBuilder.build()); } finally { grpcConnectionPool.returnPool(channel1, address); } - + if(future!=null){ + Proxy.Packet packet = future.get(Configuration.getPropertyInt("rpc.time.out",3000), TimeUnit.MILLISECONDS); + remoteResult = (ReturnResult) ObjectTransform.json2Bean(packet.getBody().getValue().toStringUtf8(), ReturnResult.class); + } return remoteResult; } catch (Exception e) { logger.error("getFederatedPredictFromRemote error", e.getMessage()); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index 03d23328..78aee5f0 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -47,10 +47,7 @@ import java.io.IOException; import java.util.HashMap; import java.util.Set; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.Executor; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; public class ServingServer implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(ServingServer.class); @@ -100,12 +97,14 @@ private void start(String[] args) throws Exception { int port = Integer.parseInt(Configuration.getProperty(Dict.PROPERTY_SERVER_PORT)); //TODO: Server custom configuration - Integer corePoolSize = Configuration.getPropertyInt("serving.core.pool.size",10); - Integer maxPoolSize = Configuration.getPropertyInt("serving.max.pool.size",100); + int processors = Runtime.getRuntime().availableProcessors(); + + Integer corePoolSize = Configuration.getPropertyInt("serving.core.pool.size",processors); + Integer maxPoolSize = Configuration.getPropertyInt("serving.max.pool.size",processors * 2); Integer aliveTime = Configuration.getPropertyInt("serving.pool.alive.time",1000); Integer queueSize = Configuration.getPropertyInt("serving.pool.queue.size",10); Executor executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, aliveTime.longValue(), TimeUnit.MILLISECONDS, - new ArrayBlockingQueue<>(queueSize), new NamedThreadFactory("ServingServer", true)); + new SynchronousQueue(), new NamedThreadFactory("ServingServer", true)); FateServerBuilder serverBuilder = (FateServerBuilder) ServerBuilder.forPort(port); serverBuilder.executor(executor); From c50f3c5c4d67c20261a600158ff43b274cedb839 Mon Sep 17 00:00:00 2001 From: kaideng Date: Thu, 6 Feb 2020 16:00:07 +0800 Subject: [PATCH 140/190] change grpc useage Signed-off-by: kaideng --- .../proxy/rpc/services/InferenceService.java | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java index cf5f6567..5787b292 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java @@ -69,49 +69,60 @@ public Map doService(Context context, InboundPackage data, OutboundPackage< String resultString=null; String callName = context.getCallName(); + ListenableFuture resultFuture; try { try { - logger.info("try to get grpc connection"); + if(logger.isDebugEnabled()) { + logger.debug("try to get grpc connection"); + } managedChannel = this.grpcConnectionPool.getManagedChannel(routerInfo.getHost(), routerInfo.getPort()); } catch (Exception e) { logger.error("get grpc channel error", e); throw new NoResultException(); } - try { - Map reqBodyMap = data.getBody(); - Map reqHeadMap = data.getHead(); - Map inferenceReqMap = Maps.newHashMap(); - inferenceReqMap.put(Dict.CASE_ID, context.getCaseId()); - inferenceReqMap.putAll(reqHeadMap); - inferenceReqMap.put(Dict.FEATURE_DATA,Maps.newHashMap(reqBodyMap)); + Map reqBodyMap = data.getBody(); + Map reqHeadMap = data.getHead(); - logger.info("inference req ============================= {}", JSON.toJSONString(inferenceReqMap)); + Map inferenceReqMap = Maps.newHashMap(); + inferenceReqMap.put(Dict.CASE_ID, context.getCaseId()); + inferenceReqMap.putAll(reqHeadMap); + inferenceReqMap.put(Dict.FEATURE_DATA, Maps.newHashMap(reqBodyMap)); + int timeWait = timeout; + try { + if(logger.isDebugEnabled()) { + logger.debug("inference req : {}", JSON.toJSONString(inferenceReqMap)); + } InferenceServiceProto.InferenceMessage.Builder reqBuilder = InferenceServiceProto.InferenceMessage.newBuilder(); reqBuilder.setBody(ByteString.copyFrom(JSON.toJSONString(inferenceReqMap).getBytes())); InferenceServiceGrpc.InferenceServiceFutureStub futureStub = InferenceServiceGrpc.newFutureStub(managedChannel); - ListenableFuture resultFuture; - int timeWait = timeout; metricFactory.counter("http.inference.service", "in doService", "callName", callName, "direction", "to.self.serving-server", "result", "success").increment(); - if(callName.equals(Dict.SERVICENAME_INFERENCE)) { + if (callName.equals(Dict.SERVICENAME_INFERENCE)) { resultFuture = futureStub.inference(reqBuilder.build()); timeWait = timeout; - } else if(callName.equals(Dict.SERVICENAME_GET_INFERENCE_RESULT)){ + } else if (callName.equals(Dict.SERVICENAME_GET_INFERENCE_RESULT)) { resultFuture = futureStub.getInferenceResult(reqBuilder.build()); timeWait = asyncTimeout; - }else if(callName.equals(Dict.SERVICENAME_START_INFERENCE_JOB)){ + } else if (callName.equals(Dict.SERVICENAME_START_INFERENCE_JOB)) { resultFuture = futureStub.startInferenceJob(reqBuilder.build()); timeWait = asyncTimeout; - }else{ + } else { logger.error("unknown callName {}.", callName); - throw new UnSupportMethodException(); + throw new UnSupportMethodException(); } + }finally { + + if(managedChannel!=null) { + grpcConnectionPool.returnPool(managedChannel, routerInfo.getHost(), routerInfo.getPort()); + } + } + try{ InferenceServiceProto.InferenceMessage result = resultFuture.get(timeWait,TimeUnit.MILLISECONDS); metricFactory.counter("http.inference.service", "in doService", "callName", callName, "direction", "from.self.serving-server", "result", "success").increment(); logger.info("routerinfo {} send {} result {}",routerInfo,inferenceReqMap,result); From 4bd37f11fb615d24386599000bb8215515ef1fa7 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Thu, 6 Feb 2020 18:05:35 +0800 Subject: [PATCH 141/190] change debug log Signed-off-by: v_dylanxu <136539068@qq.com> --- .../metrics/micrometer/MmMetricsRegistry.java | 4 +- .../serving/core/bean/GrpcConnectionPool.java | 4 +- .../core/manager/DefaultCacheManager.java | 17 ++- .../core/rpc/core/AbstractServiceAdaptor.java | 2 +- .../serving/core/utils/ProtobufUtils.java | 8 +- .../register/common/AbstractRegistry.java | 12 +- .../common/CuratorZookeeperClient.java | 21 +++- .../register/common/FailbackRegistry.java | 33 ++++-- .../register/router/DefaultRouterService.java | 4 +- .../fate/register/task/AbstractRetryTask.java | 5 +- .../register/zookeeper/ZookeeperRegistry.java | 34 ++++-- .../serving/proxy/config/RegistryConfig.java | 5 +- .../proxy/controller/ProxyController.java | 5 +- .../proxy/rpc/grpc/GrpcConnectionPool.java | 22 +++- .../proxy/rpc/grpc/ProxyRequestHandler.java | 4 +- .../rpc/grpc/ServiceExceptionHandler.java | 2 +- .../proxy/rpc/router/BaseServingRouter.java | 4 +- .../router/ConfigFileBasedServingRouter.java | 8 +- .../proxy/rpc/router/ZkServingRouter.java | 6 +- .../proxy/rpc/services/InferenceService.java | 4 +- .../proxy/rpc/services/UnaryCallService.java | 1 - .../serving/proxy/security/AuthUtils.java | 5 +- .../webank/ai/fate/serving/ServingServer.java | 8 +- .../webank/ai/fate/serving/SpringConfig.java | 13 --- .../adapter/dataaccess/MockAdapter.java | 5 +- .../guest/DefaultGuestInferenceProvider.java | 25 ++++- .../host/DefaultHostInferenceProvider.java | 9 +- .../serving/manger/AbstractModelLoader.java | 105 ++++++++---------- .../manger/DefaultHttpModelLoader.java | 11 +- .../serving/manger/DefaultModelCache.java | 3 +- .../serving/manger/DefaultModelManager.java | 95 +++++++--------- .../manger/InferenceWorkerManager.java | 8 +- .../ai/fate/serving/service/ModelService.java | 21 ++-- .../ai/fate/serving/service/ProxyService.java | 4 +- .../service/ServiceExceptionHandler.java | 2 +- .../ServiceOverloadProtectionHandle.java | 2 +- 36 files changed, 303 insertions(+), 218 deletions(-) diff --git a/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmMetricsRegistry.java b/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmMetricsRegistry.java index 78b463f6..ea02f9c0 100644 --- a/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmMetricsRegistry.java +++ b/fate-metrics-micrometer/src/main/java/com/webank/ai/fate/serving/metrics/micrometer/MmMetricsRegistry.java @@ -26,7 +26,9 @@ public void destroy() { public void bindTo(MeterRegistry registry) { if (this.registry == null) { this.registry = registry; - logger.info("MeterRegistry is set..."); + if (logger.isDebugEnabled()) { + logger.debug("MeterRegistry is set..."); + } } } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java index ab05ac0e..16a06f33 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java @@ -106,7 +106,9 @@ public ManagedChannelFactory(String ip, int port) { @Override public ManagedChannel create() throws Exception { - logger.info("create ManagedChannel"); + if (logger.isDebugEnabled()) { + logger.debug("create ManagedChannel"); + } NettyChannelBuilder builder = NettyChannelBuilder .forAddress(ip, port) .keepAliveTime(6, TimeUnit.MINUTES) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java index 79c6cb16..5fc631fe 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java @@ -97,8 +97,9 @@ public void afterPropertiesSet() throws Exception { @Override public void store(Context context, String key, Object object) { - - logger.info("store key {} value {}", key, object); + if (logger.isDebugEnabled()) { + logger.debug("store key {} value {}", key, object); + } CacheValueConfig cacheValueConfig = getCacheValueConfig(key, CacheType.PROCESS_DATA); putIntoRedisCache(key, cacheValueConfig, object); @@ -110,7 +111,9 @@ public T restore(Context context, String key, Class dataType) { CacheValueConfig cacheValueConfig = getCacheValueConfig(key, CacheType.PROCESS_DATA); T result = getFromRedisCache(key, cacheValueConfig, dataType); - logger.info("restore key {} value {}", key, result); + if (logger.isDebugEnabled()) { + logger.debug("restore key {} value {}", key, result); + } return result; } @@ -127,7 +130,9 @@ public void putInferenceResultCache(Context context, String partyId, String case } } finally { long end = System.currentTimeMillis(); - logger.info("caseid {} putInferenceResultCache cost {}", context.getCaseId(), end - beginTime); + if (logger.isDebugEnabled()) { + logger.debug("caseid {} putInferenceResultCache cost {}", context.getCaseId(), end - beginTime); + } } } @@ -300,7 +305,9 @@ private String generateRemoteModelInferenceResultCacheKey(FederatedParty remoteP String featureIdString = StringUtils.join(featureIdItemString, "_"); String cacheKey; cacheKey = StringUtils.join(Arrays.asList(remotePartyKey, featureIdString), "#"); - logger.info(cacheKey); + if (logger.isDebugEnabled()) { + logger.debug(cacheKey); + } return cacheKey; } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/AbstractServiceAdaptor.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/AbstractServiceAdaptor.java index 87cb780c..fc3b1a62 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/AbstractServiceAdaptor.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/rpc/core/AbstractServiceAdaptor.java @@ -138,7 +138,7 @@ public OutboundPackage service(Context context , InboundPackage data } catch (Throwable e) { exceptions.add(e); - logger.info(e.getMessage()); + logger.error(e.getMessage()); } finally { requestInHandle.decrementAndGet(); diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/ProtobufUtils.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/ProtobufUtils.java index 32c5631a..339c1da0 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/ProtobufUtils.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/ProtobufUtils.java @@ -27,13 +27,17 @@ public static T parseProtoObject(com.google.protobuf.Parser protoParser, T messageV3; try { messageV3 = protoParser.parseFrom(protoString); - logger.info("parse {} proto object normal", messageV3.getClass().getSimpleName()); + if (logger.isDebugEnabled()) { + logger.debug("parse {} proto object normal", messageV3.getClass().getSimpleName()); + } return messageV3; } catch (Exception ex1) { try { DefaultEmptyFillProto.DefaultEmptyFillMessage defaultEmptyFillMessage = DefaultEmptyFillProto.DefaultEmptyFillMessage.parseFrom(protoString); messageV3 = protoParser.parseFrom(new byte[0]); - logger.info("parse {} proto object with default values", messageV3.getClass().getSimpleName()); + if (logger.isDebugEnabled()) { + logger.debug("parse {} proto object with default values", messageV3.getClass().getSimpleName()); + } return messageV3; } catch (Exception ex2) { throw ex1; diff --git a/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistry.java b/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistry.java index ba2827b6..96802929 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistry.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/AbstractRegistry.java @@ -144,7 +144,9 @@ public AtomicLong getLastCacheChanged() { } public void doSaveProperties(long version) { - logger.info("doSaveProperties {} {}", version, properties); + if (logger.isDebugEnabled()) { + logger.debug("doSaveProperties {} {}", version, properties); + } if (version < lastCacheChanged.get()) { return; @@ -422,7 +424,9 @@ protected void notify(URL url, NotifyListener listener, List urls) { } private void saveProperties(URL url) { - logger.info("saveProperties url {}", url); + if (logger.isDebugEnabled()) { + logger.debug("saveProperties url {}", url); + } if (file == null) { return; @@ -442,7 +446,9 @@ private void saveProperties(URL url) { } } - logger.info("properties set property key {} value {}", url.getServiceKey(), buf.toString()); + if (logger.isDebugEnabled()) { + logger.debug("properties set property key {} value {}", url.getServiceKey(), buf.toString()); + } properties.setProperty(url.getServiceKey(), buf.toString()); long version = lastCacheChanged.incrementAndGet(); if (syncSaveFile) { diff --git a/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java b/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java index f9197dc3..673bcb82 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/CuratorZookeeperClient.java @@ -125,8 +125,9 @@ public void stateChanged(CuratorFramework client, ConnectionState state) { @Override public void createPersistent(String path) { try { - - logger.info("createPersistent {}", path); + if (logger.isDebugEnabled()) { + logger.debug("createPersistent {}", path); + } if (aclEnable) { client.create().withACL(acls).forPath(path); } else { @@ -141,7 +142,9 @@ public void createPersistent(String path) { @Override public void createEphemeral(String path) { try { - logger.info("createEphemeral {}", path); + if (logger.isDebugEnabled()) { + logger.debug("createEphemeral {}", path); + } if (aclEnable) { client.create().withMode(CreateMode.EPHEMERAL).withACL(acls).forPath(path); } else { @@ -157,7 +160,9 @@ public void createEphemeral(String path) { protected void createPersistent(String path, String data) { byte[] dataBytes = data.getBytes(CHARSET); try { - logger.info("createPersistent {} data {}", path, data); + if (logger.isDebugEnabled()) { + logger.debug("createPersistent {} data {}", path, data); + } if (aclEnable) { client.create().withACL(acls).forPath(path, dataBytes); } else { @@ -183,7 +188,9 @@ protected void createPersistent(String path, String data) { protected void createEphemeral(String path, String data) { byte[] dataBytes = data.getBytes(CHARSET); try { - logger.info("createEphemeral {} data {}", path, data); + if (logger.isDebugEnabled()) { + logger.debug("createEphemeral {} data {}", path, data); + } if (aclEnable) { client.create().withMode(CreateMode.EPHEMERAL).withACL(acls).forPath(path, dataBytes); } else { @@ -329,7 +336,9 @@ public void removeTargetChildListener(String path, CuratorWatcherImpl listener) @Override public void clearAcl(String path) { if (aclEnable) { - logger.info("clear acl {}", path); + if (logger.isDebugEnabled()) { + logger.debug("clear acl {}", path); + } try { client.setACL().withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE).forPath(path); } catch (Exception e) { diff --git a/register/src/main/java/com/webank/ai/fate/register/common/FailbackRegistry.java b/register/src/main/java/com/webank/ai/fate/register/common/FailbackRegistry.java index 100eceb4..ca8e8616 100644 --- a/register/src/main/java/com/webank/ai/fate/register/common/FailbackRegistry.java +++ b/register/src/main/java/com/webank/ai/fate/register/common/FailbackRegistry.java @@ -64,8 +64,9 @@ public FailbackRegistry(URL url) { @Override public void subProject(String project) { - - logger.info("try to subProject: {}", project); + if (logger.isDebugEnabled()) { + logger.debug("try to subProject: {}", project); + } super.subProject(project); failedSubProject.remove(project); try { @@ -106,7 +107,9 @@ public void removeFailedNotifiedTask(URL url, NotifyListener listener) { public void addFailedSubscribedProjectTask(String project) { - logger.info("try to add failed subscribed project {}",project); + if (logger.isDebugEnabled()) { + logger.debug("try to add failed subscribed project {}",project); + } FailedSubProjectTask oldOne = failedSubProject.get(project); if (oldOne != null) { @@ -126,7 +129,9 @@ public void addFailedSubscribedProjectTask(String project) { private void addFailedRegistered(URL url) { - logger.info("try to add failed registed url {}",url); + if (logger.isDebugEnabled()) { + logger.debug("try to add failed registed url {}",url); + } FailedRegisteredTask oldOne = failedRegistered.get(url); if (oldOne != null) { return; @@ -314,7 +319,9 @@ public void unregister(URL url) { @Override public void subscribe(URL url, NotifyListener listener) { - logger.info("prepare to subscribe " + url); + if (logger.isDebugEnabled()) { + logger.debug("prepare to subscribe " + url); + } super.subscribe(url, listener); removeFailedSubscribed(url, listener); try { @@ -399,7 +406,9 @@ protected void doNotify(URL url, NotifyListener listener, List urls) { @Override protected void recover() throws Exception { // register - logger.info("prepare to recover registed......{}", getRegistered()); + if (logger.isDebugEnabled()) { + logger.debug("prepare to recover registed......{}", getRegistered()); + } Set recoverRegistered = new HashSet(getRegistered()); if (!recoverRegistered.isEmpty()) { @@ -411,7 +420,9 @@ protected void recover() throws Exception { } } - logger.info("prepare to recover registed.project.....{}", projectSets); + if (logger.isDebugEnabled()) { + logger.debug("prepare to recover registed.project.....{}", projectSets); + } Set subjectSets = new HashSet(this.projectSets); if(!subjectSets.isEmpty()) { @@ -426,7 +437,9 @@ protected void recover() throws Exception { }); } - logger.info("prepare to recover subscribed......{}", getSubscribed()); + if (logger.isDebugEnabled()) { + logger.debug("prepare to recover subscribed......{}", getSubscribed()); + } // subscribe Map> recoverSubscribed = new HashMap>(getSubscribed()); if (!recoverSubscribed.isEmpty()) { @@ -440,7 +453,9 @@ protected void recover() throws Exception { } } } - logger.info("recover over !!!!!!"); + if (logger.isDebugEnabled()) { + logger.debug("recover over !!!!!!"); + } } @Override diff --git a/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java b/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java index f2009c9a..fe062109 100644 --- a/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java +++ b/register/src/main/java/com/webank/ai/fate/register/router/DefaultRouterService.java @@ -39,7 +39,9 @@ public List doRouter(URL url ,LoadBalancer loadBalancer) { urls = filterVersion(urls, version); } List resultUrls = loadBalancer.select(urls); - logger.info("router service return urls {}", resultUrls); + if (logger.isDebugEnabled()) { + logger.debug("router service return urls {}", resultUrls); + } return resultUrls; } diff --git a/register/src/main/java/com/webank/ai/fate/register/task/AbstractRetryTask.java b/register/src/main/java/com/webank/ai/fate/register/task/AbstractRetryTask.java index 62e1ce70..8fab73e8 100644 --- a/register/src/main/java/com/webank/ai/fate/register/task/AbstractRetryTask.java +++ b/register/src/main/java/com/webank/ai/fate/register/task/AbstractRetryTask.java @@ -98,8 +98,9 @@ protected void reput(Timeout timeout, long tick) { @Override public void run(Timeout timeout) throws Exception { - - logger.info("retry task begin"); + if (logger.isDebugEnabled()) { + logger.debug("retry task begin"); + } long begin = System.currentTimeMillis(); if (timeout.isCancelled() || timeout.timer().isStop() || isCancel()) { // other thread cancel this timeout or stop the timer. diff --git a/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java b/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java index 1fad5ed4..050b9fb7 100644 --- a/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java +++ b/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java @@ -126,7 +126,10 @@ public void doSubProject(String project) { subEnvironments(path, project, childrens); } }); - logger.info("environments {}", environments); + + if (logger.isDebugEnabled()) { + logger.debug("environments {}", environments); + } if (environments == null) { logger.info("environment is null,maybe zk is not started"); throw new RuntimeException("environment is null"); @@ -147,7 +150,9 @@ private void subEnvironments(String path, String project, List environme List services = zkClient.addChildListener(tempPath, (parent, childrens) -> { if (StringUtils.isNotEmpty(parent)) { - logger.info("fire services changes {}", childrens); + if (logger.isDebugEnabled()) { + logger.debug("fire services changes {}", childrens); + } subServices(project, environment, childrens); } @@ -166,10 +171,13 @@ private void subServices(String project, String environment, List servic for (String service : services) { String subString = project + Constants.PATH_SEPARATOR + environment + Constants.PATH_SEPARATOR + service; - logger.info("subServices sub {}", subString); + if (logger.isDebugEnabled()) { + logger.debug("subServices sub {}", subString); + } subscribe(URL.valueOf(subString), urls -> { - - logger.info("change services urls =" + urls); + if (logger.isDebugEnabled()) { + logger.debug("change services urls =" + urls); + } }); } } @@ -199,7 +207,9 @@ private String parseRegisterService(RegisterService registerService) { } public synchronized void register(Set sets) { - logger.info("prepare to register {}",sets); + if (logger.isDebugEnabled()) { + logger.debug("prepare to register {}",sets); + } String hostAddress = NetUtils.getLocalIp(); Preconditions.checkArgument(port != 0); Preconditions.checkArgument(StringUtils.isNotEmpty(environment)); @@ -218,17 +228,19 @@ public synchronized void register(Set sets) { this.register(newServiceUrl); this.registedString.add(serviceName); } else { - logger.info("url {} is already registed,will not do anything ", newServiceUrl); + logger.info("url {} is already registed, will not do anything ", newServiceUrl); } }); } } else { if (!registedString.contains(service.serviceName())) { - logger.info("try to register url {}", serviceUrl); + if (logger.isDebugEnabled()) { + logger.debug("try to register url {}", serviceUrl); + } this.register(serviceUrl); this.registedString.add(service.serviceName()); } else { - logger.info("url {} is already registed,will not do anything ", service.serviceName()); + logger.info("url {} is already registed, will not do anything ", service.serviceName()); } } }catch(Exception e){ @@ -263,7 +275,9 @@ public void doRegister(URL url) { try { String urlPath = toUrlPath(url); - logger.info("create urlpath {} ", urlPath); + if (logger.isDebugEnabled()) { + logger.debug("create urlpath {} ", urlPath); + } zkClient.create(urlPath, true); } catch (Throwable e) { throw new RuntimeException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java index 0a317315..fa5e7a43 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java @@ -37,7 +37,6 @@ public class RegistryConfig { private static final Logger logger = LoggerFactory.getLogger(GrpcConfigration.class); - @Value("${proxy.grpc.intra.port:8867}") private Integer port; @@ -58,7 +57,9 @@ public class RegistryConfig { @Bean public ZookeeperRegistry zookeeperRegistry(InterGrpcServer interGrpcServer) { - logger.info("prepare to create zookeeper registry ,use zk {}",useZkRouter); + if (logger.isDebugEnabled()) { + logger.info("prepare to create zookeeper registry ,use zk {}", useZkRouter); + } if ("true".equals(useZkRouter) && StringUtils.isNotEmpty(zkUrl)) { System.setProperty("acl.enable", Optional.ofNullable(aclEnable).orElse("")); System.setProperty("acl.username", Optional.ofNullable(aclUsername).orElse("")); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java index 529f2d34..27156540 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/controller/ProxyController.java @@ -39,7 +39,6 @@ public class ProxyController { @Autowired IMetricFactory metricFactory; - @Value("${coordinator:9999}") private String selfCoordinator; @@ -67,7 +66,9 @@ public Callable federation(@PathVariable String version, return new Callable() { @Override public String call() throws Exception { - logger.info("receive : {} headers {}", data, headers.toSingleValueMap()); + if (logger.isDebugEnabled()) { + logger.debug("receive : {} headers {}", data, headers.toSingleValueMap()); + } final ServiceAdaptor serviceAdaptor = proxyServiceRegister.getServiceAdaptor(Dict.SERVICENAME_INFERENCE); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java index 6ef1ffd7..bdb54a65 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java @@ -30,7 +30,9 @@ public class GrpcConnectionPool { public void returnPool(ManagedChannel channel, String host, int port) { try { - logger.info("return grpc pool {}:{}",host,port); + if (logger.isDebugEnabled()) { + logger.debug("return grpc pool {}:{}", host, port); + } String key = host + ":" + port; poolMap.get(key).returnObject(channel); // logger.info("pool active size {}",poolMap.get(key).()); @@ -47,7 +49,9 @@ public void returnPool(ManagedChannel channel, String host, int port) { public ManagedChannel getManagedChannel(String host, int port) throws Exception { String key = host + ":" + port; - logger.info("try to get grpc channel {}",key); + if (logger.isDebugEnabled()) { + logger.debug("try to get grpc channel {}",key); + } GenericObjectPool pool = poolMap.get(key); if (pool == null) { @@ -86,8 +90,9 @@ public ManagedChannel getManagedChannel(String host, int port) throws Exception } GenericObjectPool objectPool =poolMap.get(key); - - logger.info("grpc pool host {} active num {} idle1 num {}",key,objectPool.getNumActive(),objectPool.getNumIdle()); + if (logger.isDebugEnabled()) { + logger.debug("grpc pool host {} active num {} idle1 num {}",key,objectPool.getNumActive(),objectPool.getNumIdle()); + } return objectPool.borrowObject(); } @@ -105,7 +110,10 @@ public ManagedChannelFactory(String host, int port) { @Override public ManagedChannel create() throws Exception { - logger.info("create managedChannel"); + + if (logger.isDebugEnabled()) { + logger.debug("create managedChannel"); + } ManagedChannelBuilder builder = ManagedChannelBuilder .forAddress(host,port) @@ -139,7 +147,9 @@ public PooledObject wrap(ManagedChannel managedChannel) { @Override public void destroyObject(PooledObject p) throws Exception { - logger.info("destroyObject ================"); + if (logger.isDebugEnabled()) { + logger.debug("destroyObject ================"); + } try { p.getObject().shutdownNow(); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java index 09e9b6de..ddeb2307 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ProxyRequestHandler.java @@ -37,7 +37,9 @@ public void unaryCall(Proxy.Packet req, StreamObserver responseObs metricFactory.counter("grpc.unaryCall.request", "grpc unaryCall request", "src", req.getHeader().getSrc().getPartyId(), "dst", req.getHeader().getDst().getPartyId()).increment(); - logger.info("unaryCall req {}",req); + if (logger.isDebugEnabled()) { + logger.debug("unaryCall req {}", req); + } ServiceAdaptor unaryCallService = getProxyServiceRegister().getServiceAdaptor("unaryCall"); Context context = new BaseContext(); InboundPackage inboundPackage = buildInboundPackage(context, req); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ServiceExceptionHandler.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ServiceExceptionHandler.java index ca3ede76..ff2c31b4 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ServiceExceptionHandler.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/ServiceExceptionHandler.java @@ -17,7 +17,7 @@ public void onHalfClose() { try { super.onHalfClose(); } catch (Exception e) { - logger.info("ServiceException:", e); + logger.error("ServiceException:", e); call.close(Status.INTERNAL .withCause(e) .withDescription(e.getMessage()), new Metadata()); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java index edf950ef..e31142dd 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java @@ -50,7 +50,9 @@ public RouterInfo route(Context context, InboundPackage inboundPackage) { context.setRouterInfo(routerInfo); - logger.info("caseid {} get route info {}:{}", context.getCaseId(),routerInfo.getHost(),routerInfo.getPort()); + if (logger.isDebugEnabled()) { + logger.debug("caseid {} get route info {}:{}", context.getCaseId(),routerInfo.getHost(),routerInfo.getPort()); + } return routerInfo; } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java index 54fa9937..be8dd13c 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java @@ -248,13 +248,17 @@ public void loadRouteTable() { initRouteTable(confJson.getAsJsonObject("route_table")); initPermission(confJson.getAsJsonObject("permission")); - logger.info("refreshed route table at: {}", routeTableFile); + if (logger.isDebugEnabled()) { + logger.debug("refreshed route table at: {}", routeTableFile); + } } @Override public void afterPropertiesSet() throws Exception { - logger.debug("in ConfigFileBasedServingRouter:afterPropertiesSet"); + if (logger.isDebugEnabled()) { + logger.debug("in ConfigFileBasedServingRouter:afterPropertiesSet"); + } routeType = RouteTypeConvertor.string2RouteType(routeTypeString); routeTable = new ConcurrentHashMap<>(); topicEndpointMapping = new WeakHashMap<>(); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java index b843e801..c2e0aa5d 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java @@ -52,8 +52,10 @@ public List getRouterInfoList(Context context, InboundPackage inboun return null; } String environment = getEnvironment(context, inboundPackage); - List list = zkRouterService.router("serving", environment, context.getServiceName()); - logger.info("try to find zk ,{}:{}:{}, result {}", "serving", environment, context.getServiceName(), list); + List list = zkRouterService.router("serving", environment, context.getServiceName()); + if (logger.isDebugEnabled()) { + logger.debug("try to find zk ,{}:{}:{}, result {}", "serving", environment, context.getServiceName(), list); + } if(null == list || list.isEmpty()){ return null; } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java index 5787b292..6ece5f87 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java @@ -125,7 +125,9 @@ public Map doService(Context context, InboundPackage data, OutboundPackage< try{ InferenceServiceProto.InferenceMessage result = resultFuture.get(timeWait,TimeUnit.MILLISECONDS); metricFactory.counter("http.inference.service", "in doService", "callName", callName, "direction", "from.self.serving-server", "result", "success").increment(); - logger.info("routerinfo {} send {} result {}",routerInfo,inferenceReqMap,result); + if (logger.isDebugEnabled()) { + logger.debug("routerinfo {} send {} result {}",routerInfo,inferenceReqMap,result); + } resultString = new String(result.getBody().toByteArray()); } catch (Exception e) { metricFactory.counter("http.inference.service", "in doService", "callName", callName, "direction", "from.self.serving-server", "result", "grpc.error").increment(); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java index 9dc2c7b6..ffa0e310 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java @@ -51,7 +51,6 @@ public class UnaryCallService extends AbstractServiceAdaptor featureIds) { } returnResult.setData(data); returnResult.setRetcode(InferenceRetCode.OK); - logger.info("MockAdapter result, {}", JSONObject.toJSONString(returnResult)); + + if (logger.isDebugEnabled()) { + logger.debug("MockAdapter result, {}", JSONObject.toJSONString(returnResult)); + } } catch (Exception ex) { logger.error(ex.getMessage()); returnResult.setRetcode(InferenceRetCode.GET_FEATURE_FAILED); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java index 41aa3035..d146efdd 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java @@ -97,7 +97,10 @@ public ReturnResult runInference(Context context, InferenceRequest inferenceRequ return inferenceResult; } - logger.info("use model to inference for {}, id: {}, version: {}", inferenceRequest.getAppid(), modelNamespace, modelName); + if (logger.isDebugEnabled()) { + logger.debug("use model to inference for {}, id: {}, version: {}", inferenceRequest.getAppid(), modelNamespace, modelName); + } + Map rawFeatureData = inferenceRequest.getFeatureData(); if (rawFeatureData == null) { @@ -199,7 +202,9 @@ private PreProcessingResult getPreProcessingFeatureData(Context context, Map predictParams = new HashMap<>(8); predictParams.put(Dict.FEDERATED_PARAMS, federatedParams); @@ -112,7 +115,9 @@ public ReturnResult federatedInference(Context context, HostFederatedParams fede long endTime = System.currentTimeMillis(); long federatedInferenceElapsed = endTime - startTime; // InferenceUtils.logInference(context ,federatedParams, party, federatedRoles, returnResult, federatedInferenceElapsed, false, billing); - logger.info(JSONObject.toJSONString(returnResult.getData())); + if (logger.isDebugEnabled()) { + logger.debug(JSONObject.toJSONString(returnResult.getData())); + } return returnResult; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/AbstractModelLoader.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/AbstractModelLoader.java index 6c8712d6..0fa1bfdb 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/AbstractModelLoader.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/AbstractModelLoader.java @@ -18,77 +18,75 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; -public abstract class AbstractModelLoader implements ModelLoader{ +public abstract class AbstractModelLoader implements ModelLoader { Logger logger = LoggerFactory.getLogger(AbstractModelLoader.class); @Override public PipelineTask loadModel(Context context, String name, String namespace) { - - MODELDATA modelData = doLoadModel(context ,name, namespace); + MODELDATA modelData = doLoadModel(context, name, namespace); if (modelData == null) { - logger.info("load model error, name {} namespace {} ,try to restore from local cache",name,namespace); - modelData = restore(context,name,namespace); - if(modelData ==null){ - logger.info("load model from local cache error, name {} namespace {}",name,namespace); + logger.info("load model error, name {} namespace {} ,try to restore from local cache", name, namespace); + modelData = restore(context, name, namespace); + if (modelData == null) { + logger.info("load model from local cache error, name {} namespace {}", name, namespace); } - }else{ - this.store(context,name,namespace,modelData); + } else { + this.store(context, name, namespace, modelData); } return this.initPipeLine(context, modelData); } - - protected void store(Context context,String name,String namespace,MODELDATA data){ + protected void store(Context context, String name, String namespace, MODELDATA data) { try { String cachePath = getCachePath(context, name, namespace); if (cachePath != null) { - byte[] bytes = this.serialize(context,data); - if(bytes==null){ - throw new ModelSerializeException(); - } - File file = new File(cachePath); - this.doStore(context,bytes,file); + byte[] bytes = this.serialize(context, data); + if (bytes == null) { + throw new ModelSerializeException(); + } + File file = new File(cachePath); + this.doStore(context, bytes, file); } - }catch(ModelSerializeException e){ - logger.error("serialize mode data error",e); - - }catch(Exception e){ - logger.error("store mode data error",e); - + } catch (ModelSerializeException e) { + logger.error("serialize mode data error", e); + } catch (Exception e) { + logger.error("store mode data error", e); } } - protected abstract byte[] serialize(Context context,MODELDATA data); - protected abstract MODELDATA unserialize(Context context,byte[] data); + protected abstract byte[] serialize(Context context, MODELDATA data); + + protected abstract MODELDATA unserialize(Context context, byte[] data); - protected MODELDATA restore(Context context,String name,String namespace){ + protected MODELDATA restore(Context context, String name, String namespace) { try { String cachePath = getCachePath(context, name, namespace); - if (cachePath!=null) { + if (cachePath != null) { - byte[] bytes = doRestore(new File(cachePath)); - MODELDATA modelData = this.unserialize(context,bytes); + byte[] bytes = doRestore(new File(cachePath)); + MODELDATA modelData = this.unserialize(context, bytes); return modelData; } - }catch(Throwable e){ - logger.error("restore model data error",e); + } catch (Throwable e) { + logger.error("restore model data error", e); } return null; } - protected abstract PipelineTask initPipeLine(Context context,MODELDATA modeldata); - private String getCachePath(Context context,String name,String namespace){ - StringBuilder sb = new StringBuilder(); + + protected abstract PipelineTask initPipeLine(Context context, MODELDATA modeldata); + + private String getCachePath(Context context, String name, String namespace) { + StringBuilder sb = new StringBuilder(); String locationPre = System.getProperty(Dict.PROPERTY_USER_HOME); - if (StringUtils.isNotEmpty(locationPre)&&StringUtils.isNotEmpty(name)&&StringUtils.isNotEmpty(namespace)) { - String cachFilePath =sb.append( locationPre).append ("/.fate/model_").append(name).append("_").append(namespace).append("_").append("cache").toString(); + if (StringUtils.isNotEmpty(locationPre) && StringUtils.isNotEmpty(name) && StringUtils.isNotEmpty(namespace)) { + String cachFilePath = sb.append(locationPre).append("/.fate/model_").append(name).append("_").append(namespace).append("_").append("cache").toString(); return cachFilePath; } - return null; + return null; } - protected void doStore(Context context ,byte[] data, File file) { - + protected void doStore(Context context, byte[] data, File file) { if (file == null) { return; } @@ -121,30 +119,23 @@ protected void doStore(Context context ,byte[] data, File file) { } protected byte[] doRestore(File file) { - if (file != null && file.exists()) { - - try ( InputStream in = new FileInputStream(file)){ - Long filelength = file.length(); - byte[] filecontent = new byte[filelength.intValue()]; - int readCount = in.read(filecontent); - if(readCount>0){ - return filecontent; - }else { - return null; - } - } - catch (Throwable e) { + try (InputStream in = new FileInputStream(file)) { + Long filelength = file.length(); + byte[] filecontent = new byte[filelength.intValue()]; + int readCount = in.read(filecontent); + if (readCount > 0) { + return filecontent; + } else { + return null; + } + } catch (Throwable e) { logger.error("failed to doRestore file ", e); } - } return null; } - - - protected abstract MODELDATA doLoadModel(Context context, String name, String namespace); - + protected abstract MODELDATA doLoadModel(Context context, String name, String namespace); } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultHttpModelLoader.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultHttpModelLoader.java index f07ae7bb..1a4d0ccf 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultHttpModelLoader.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultHttpModelLoader.java @@ -91,7 +91,7 @@ protected Map doLoadModel(Context context,String name, String URL url = URL.valueOf("flow/online/transfer"); List urls = routerService.router(url); if (urls == null || urls.isEmpty()) { - logger.info("url not found, {}", url); + logger.info("register url not found, {}", url); } else { url = urls.get(0); requestUrl = url.toFullString(); @@ -100,6 +100,7 @@ protected Map doLoadModel(Context context,String name, String if (StringUtils.isBlank(requestUrl)) { requestUrl = Configuration.getProperty(Dict.MODEL_TRANSFER_URL); + logger.info("use profile model.transfer.url, {}", requestUrl); } if (StringUtils.isBlank(requestUrl)) { @@ -115,7 +116,9 @@ protected Map doLoadModel(Context context,String name, String String responseBody = HttpClientPool.transferPost(requestUrl, requestData); long end = System.currentTimeMillis(); - logger.info("{}|{}|{}|{}", requestUrl, start, end, (end - start) + " ms"); + if (logger.isDebugEnabled()) { + logger.debug("{}|{}|{}|{}", requestUrl, start, end, (end - start) + " ms"); + } if (StringUtils.isEmpty(responseBody)) { logger.info("read model fail, {}, {}", name, namespace); @@ -140,8 +143,8 @@ protected Map doLoadModel(Context context,String name, String } return resultMap; - }catch(Exception e){ - logger.info("get mode info from fateflow error" ,e); + } catch (Exception e) { + logger.error("get model info from fateflow error", e); } return null; } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelCache.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelCache.java index ea524e63..f467533d 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelCache.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelCache.java @@ -37,6 +37,7 @@ public class DefaultModelCache implements ModelCache { private static final Logger logger = LoggerFactory.getLogger(DefaultModelCache.class); private LoadingCache modelCache; + @Autowired private ModelLoader modelLoader; @@ -81,8 +82,6 @@ public long getSize() { @Override public Set getKeys() { - return modelCache.asMap().keySet(); - } } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java index 6e1a0d50..5e342100 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java @@ -56,7 +56,7 @@ public class DefaultModelManager implements ModelManager, InitializingBean { private ConcurrentHashMap partnerModelData; private File modelFile; @Autowired - private ModelLoader modelLoader; + private ModelLoader modelLoader; public DefaultModelManager() { @@ -80,18 +80,15 @@ public DefaultModelManager() { } } this.modelFile = file; - } - - @Override - public ReturnResult publishLoadModel(Context context,FederatedParty federatedParty, FederatedRoles federatedRoles, Map> federatedRolesModel) { + public ReturnResult publishLoadModel(Context context, FederatedParty federatedParty, FederatedRoles federatedRoles, Map> federatedRolesModel) { String role = federatedParty.getRole(); String partyId = federatedParty.getPartyId(); String serviceId = null; - if(context.getData(Dict.SERVICE_ID)!=null) { - serviceId = context.getData(Dict.SERVICE_ID).toString(); + if (context.getData(Dict.SERVICE_ID) != null) { + serviceId = context.getData(Dict.SERVICE_ID).toString(); } ReturnResult returnResult = new ReturnResult(); @@ -107,38 +104,43 @@ public ReturnResult publishLoadModel(Context context,FederatedParty federatedPar returnResult.setRetcode(InferenceRetCode.LOAD_MODEL_FAILED); return returnResult; } - PipelineTask model = pushModelIntoPool(context,modelInfo.getName(), modelInfo.getNamespace()); + + PipelineTask model = pushModelIntoPool(context, modelInfo.getName(), modelInfo.getNamespace()); if (model == null) { returnResult.setRetcode(InferenceRetCode.LOAD_MODEL_FAILED); return returnResult; } + federatedRolesModel.forEach((roleName, roleModelInfo) -> { roleModelInfo.forEach((p, m) -> { if (!p.equals(partyId) || (p.equals(partyId) && !role.equals(roleName))) { String partnerModelKey = ModelUtil.genModelKey(m.getName(), m.getNamespace()); partnerModelData.put(partnerModelKey, modelInfo); - logger.info("Create model index({}) for partner({}, {})", partnerModelKey, roleName, p); + if (logger.isDebugEnabled()) { + logger.debug("Create model index({}) for partner({}, {})", partnerModelKey, roleName, p); + } } }); }); - if(Dict.HOST.equals(role)){ + if (Dict.HOST.equals(role)) { if (zookeeperRegistry != null) { - if(StringUtils.isNotEmpty(serviceId)){ + if (StringUtils.isNotEmpty(serviceId)) { zookeeperRegistry.addDynamicEnvironment(serviceId); } - partnerModelData.forEach((key,v)->{ - String keyMd5 = EncryptUtils.encrypt(key,EncryptMethod.MD5); - logger.info("transform key {} to md5key {}",key,keyMd5); + partnerModelData.forEach((key, v) -> { + String keyMd5 = EncryptUtils.encrypt(key, EncryptMethod.MD5); + if (logger.isDebugEnabled()) { + logger.debug("transform key {} to md5key {}", key, keyMd5); + } zookeeperRegistry.addDynamicEnvironment(keyMd5); zookeeperRegistry.register(FateServer.serviceSets); - }); - } - } - logger.info("load the model successfully"); + if (logger.isDebugEnabled()) { + logger.debug("load the model successfully"); + } return returnResult; } catch (Exception ex) { logger.error(ex.getMessage()); @@ -149,14 +151,14 @@ public ReturnResult publishLoadModel(Context context,FederatedParty federatedPar } @Override - public ReturnResult publishOnlineModel(Context context,FederatedParty federatedParty, FederatedRoles federatedRoles, Map> federatedRolesModel) { + public ReturnResult publishOnlineModel(Context context, FederatedParty federatedParty, FederatedRoles federatedRoles, Map> federatedRolesModel) { String role = federatedParty.getRole(); String partyId = federatedParty.getPartyId(); String serviceId = null; - if(context.getData(Dict.SERVICE_ID)!=null&&StringUtils.isNotEmpty(context.getData(Dict.SERVICE_ID).toString().trim())) { - serviceId = context.getData(Dict.SERVICE_ID).toString(); - }else{ + if (context.getData(Dict.SERVICE_ID) != null && StringUtils.isNotEmpty(context.getData(Dict.SERVICE_ID).toString().trim())) { + serviceId = context.getData(Dict.SERVICE_ID).toString(); + } else { logger.info("service id is null"); } ReturnResult returnResult = new ReturnResult(); @@ -168,7 +170,7 @@ public ReturnResult publishOnlineModel(Context context,FederatedParty federatedP } String modelKey = ModelUtil.genModelKey(modelInfo.getName(), modelInfo.getNamespace()); - PipelineTask model = modelCache.get(context,modelKey); + PipelineTask model = modelCache.get(context, modelKey); if (model == null) { returnResult.setRetcode(InferenceRetCode.LOAD_MODEL_FAILED); returnResult.setRetmsg("Can not found model by these information."); @@ -184,16 +186,18 @@ public ReturnResult publishOnlineModel(Context context,FederatedParty federatedP String modelName = modelInfo.getName(); modelNamespaceDataMapPool.put(modelNamespace, new ModelNamespaceData(modelNamespace, federatedParty, federatedRoles, modelName, model)); appNamespaceMapPool.put(partyId, modelNamespace); - if(StringUtils.isNotEmpty(serviceId)){ - logger.info("put serviceId {} input pool",serviceId); + if (StringUtils.isNotEmpty(serviceId)) { + logger.info("put serviceId {} input pool", serviceId); appNamespaceMapPool.put(serviceId, modelNamespace); } - logger.info("Enable model {} for namespace {} success", modelName, modelNamespace); - logger.info("Get model namespace {} for app {}", modelNamespace, partyId); + if (logger.isDebugEnabled()) { + logger.debug("Enable model {} for namespace {} success", modelName, modelNamespace); + logger.debug("Get model namespace {} for app {}", modelNamespace, partyId); + } returnResult.setRetcode(InferenceRetCode.OK); if (zookeeperRegistry != null) { - if(StringUtils.isNotEmpty(serviceId)){ + if (StringUtils.isNotEmpty(serviceId)) { zookeeperRegistry.addDynamicEnvironment(serviceId); } @@ -201,7 +205,6 @@ public ReturnResult publishOnlineModel(Context context,FederatedParty federatedP zookeeperRegistry.register(FateServer.serviceSets); } // zookeeperRegistry.register(); - } catch (Exception ex) { returnResult.setRetcode(InferenceRetCode.SYSTEM_ERROR); returnResult.setRetmsg(ex.getMessage()); @@ -210,71 +213,53 @@ public ReturnResult publishOnlineModel(Context context,FederatedParty federatedP } @Override - public PipelineTask getModel(Context context,String name, String namespace) { - return modelCache.get(context,ModelUtil.genModelKey(name, namespace)); + public PipelineTask getModel(Context context, String name, String namespace) { + return modelCache.get(context, ModelUtil.genModelKey(name, namespace)); } @Override - public ModelNamespaceData getModelNamespaceData(Context context,String namespace) { + public ModelNamespaceData getModelNamespaceData(Context context, String namespace) { return modelNamespaceDataMapPool.get(namespace); } @Override - public String getModelNamespaceByPartyId(Context context,String partyId) { + public String getModelNamespaceByPartyId(Context context, String partyId) { return appNamespaceMapPool.get(partyId); } @Override - public ModelInfo getModelInfoByPartner(Context context,String partnerModelName, String partnerModelNamespace) { + public ModelInfo getModelInfoByPartner(Context context, String partnerModelName, String partnerModelNamespace) { return partnerModelData.get(ModelUtil.genModelKey(partnerModelName, partnerModelNamespace)); } - - - @Override - public PipelineTask pushModelIntoPool(Context context ,String name, String namespace) { - PipelineTask model = modelLoader.loadModel(context,name, namespace); + public PipelineTask pushModelIntoPool(Context context, String name, String namespace) { + PipelineTask model = modelLoader.loadModel(context, name, namespace); if (model == null) { return null; } - modelCache.put(context,ModelUtil.genModelKey(name, namespace), model); + modelCache.put(context, ModelUtil.genModelKey(name, namespace), model); logger.info("Load model success, name: {}, namespace: {}, model cache size is {}", name, namespace, modelCache.getSize()); return model; } private FederatedRoles parseFederatedRoles(Map data) { - return null; - } private FederatedParty parseFederatedParty(Map data) { - return null; - - } private ModelInfo parseModelInfo(Map data) { - return null; } - - private ModelNamespaceData parseModelNamespaceData(Map data) { - - return null; - } @Override public void afterPropertiesSet() throws Exception { - - // test(); - - } } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/InferenceWorkerManager.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/InferenceWorkerManager.java index a9253880..f61493ce 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/InferenceWorkerManager.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manger/InferenceWorkerManager.java @@ -56,7 +56,9 @@ static class InferenceWorkerThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, "inference-worker-thread-" + mThreadNum.getAndIncrement()); - logger.info("{} thead has ben created.", t.getName()); + if (logger.isDebugEnabled()) { + logger.debug("{} thead has ben created.", t.getName()); + } return t; } } @@ -64,7 +66,9 @@ public Thread newThread(Runnable r) { public static class InferenceWorkerThreadRejectedPolicy implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { - logger.info("{} rejected.", r.toString()); + if (logger.isDebugEnabled()) { + logger.debug("{} rejected.", r.toString()); + } } } } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java index 81647091..b66ce143 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java @@ -153,7 +153,9 @@ public synchronized void publishOnline(PublishRequest req, StreamObserver responseObs try { String data = req.getBody().getValue().toStringUtf8(); - logger.info("unaryCall {} head {}", data,req.getHeader().getCommand().getName()); + if (logger.isDebugEnabled()) { + logger.debug("unaryCall {} head {}", data, req.getHeader().getCommand().getName()); + } requestData = JSON.parseObject(data, HostFederatedParams.class); context.setCaseId(requestData.getCaseId() != null ? requestData.getCaseId() : Dict.NONE); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ServiceExceptionHandler.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ServiceExceptionHandler.java index dba5ae64..7f353d61 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ServiceExceptionHandler.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ServiceExceptionHandler.java @@ -33,7 +33,7 @@ public void onHalfClose() { try { super.onHalfClose(); } catch (Exception e) { - logger.info("ServiceException:", e); + logger.error("ServiceException:", e); call.close(Status.INTERNAL .withCause(e) .withDescription(e.getMessage()), new Metadata()); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ServiceOverloadProtectionHandle.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ServiceOverloadProtectionHandle.java index 903fb8cf..40ad0657 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ServiceOverloadProtectionHandle.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ServiceOverloadProtectionHandle.java @@ -41,7 +41,7 @@ public void onHalfClose() { super.onHalfClose(); } catch (Exception e) { - logger.info("ServiceException:", e); + logger.error("ServiceException:", e); serverCall.close(Status.CANCELLED.withCause(e).withDescription(e.getMessage()), metadata); } } From daab7648a049d2b737a450570219144c67b2f475 Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Fri, 7 Feb 2020 14:42:41 +0800 Subject: [PATCH 142/190] shell update pid problem --- bin/common.sh | 21 +++++++------ .../cluster-deploy/scripts/package.sh | 30 +++++++++---------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/bin/common.sh b/bin/common.sh index dc0c3920..628c4780 100644 --- a/bin/common.sh +++ b/bin/common.sh @@ -3,11 +3,9 @@ set -e getpid() { # pid=`ps aux | grep ${main_class} | grep -v grep | awk '{print $2}'` module=$1 - if [ ! -e "./${module}_pid" ];then - touch ./${module}_pid - echo "" >./${module}_pid + if [ -e "./${module}_pid" ]; then + pid=`cat ./${module}_pid` fi - pid=`cat ./${module}_pid` if [[ -n ${pid} ]]; then break 1 else @@ -35,21 +33,22 @@ start() { then java -Dspring.config.location=${configpath}/application.properties -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/application.properties >> logs/console.log 2>>logs/error.log & - else + else echo "" fi - if [[ $? -eq 0 ]]; then - sleep 2 - echo $!>./${module}_pid + sleep 5 + id=`ps -p $!| awk '{print $1}'|sed -n '2p'` + if [[ ${#id} -ne 0 ]]; then + echo $!>./${module}_pid getpid $module echo "service start sucessfully. pid: ${pid}" else echo "service start failed" - fi + fi else echo "service already started. pid: ${pid}" - fi -} + fi +} status() { getpid $1 diff --git a/serving-server/cluster-deploy/scripts/package.sh b/serving-server/cluster-deploy/scripts/package.sh index a603688d..0af764eb 100644 --- a/serving-server/cluster-deploy/scripts/package.sh +++ b/serving-server/cluster-deploy/scripts/package.sh @@ -10,15 +10,15 @@ cd ${cwd} public_path=$deploy_dir package_path=$cwd/FATE-Serving fate_serving_path=$public_path/fate-serving -sering_path=$fate_serving_path/serving-server +sering_path=$fate_serving_path/serving fate_serving=fate-serving -serving=serving-server +serving=serving fate_serving_zip=fate-serving-server-*-release.zip fate_serving_jar=fate-serving-server-*.jar fate_serving_proxy_zip=fate-serving-proxy-*-release.zip fate_serving_proxy_jar=fate-serving-proxy-*.jar serving_proxy=serving-proxy -serving_proxy_path=$fate_serving_path/$serving_proxy +serving_proxy_path=$fate_serving_path/serving-proxy mkdir -p ./${fate_serving}/${serving} mkdir -p ./${fate_serving}/${serving_proxy} @@ -49,7 +49,7 @@ ln -s $fate_serving_proxy_jar fate-serving-proxy.jar #cp modify_json.py cd ${cwd} -cp modify_json.py ./fate-serving/$serving_proxy/conf +cp modify_json.py ./fate-serving/serving-proxy/conf tar -zcvf fate-serving.tar.gz fate-serving cp_serving() { @@ -84,7 +84,7 @@ update_config () { do #update serving-proxy config path ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf + cd ${deploy_dir}/fate-serving/serving-proxy/conf sed -i "/^route.table=/croute.table=${deploy_dir}/fate-serving/serving-proxy/conf/route_table.json" application.properties sed -i "/^auth.file=/cauth.file=${deploy_dir}/fate-serving/serving-proxy/conf/auth_config.json" application.properties exit @@ -95,7 +95,7 @@ eeooff if [ $temp = 0 ] then ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/${fate_serving}/${serving}/conf + cd ${deploy_dir}/fate-serving/serving/conf sed -i "/^redis.ip=/credis.ip=${host_redis_ip}" serving-server.properties sed -i "/^redis.port=/credis.port=${host_redis_port}" serving-server.properties sed -i "/^redis.password=/credis.password=${host_redis_password}" serving-server.properties @@ -105,7 +105,7 @@ exit eeooff else ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/${fate_serving}/${serving}/conf + cd ${deploy_dir}/fate-serving/serving/conf sed -i "/^redis.ip=/credis.ip=${guest_redis_ip}" serving-server.properties sed -i "/^redis.port=/credis.port=${guest_redis_port}" serving-server.properties sed -i "/^redis.password=/credis.password=${guest_redis_password}" serving-server.properties @@ -126,11 +126,11 @@ update_zk_config () { if [ $temp = 0 ] then ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/${fate_serving}/${serving}/conf + cd ${deploy_dir}/fate-serving/serving/conf sed -i "/^zk.url=/czk.url=${host_zk_url}" serving-server.properties sed -i "/^useRegister=/cuseRegister=true" serving-server.properties sed -i "/^useZkRouter=/cuseZkRouter=true" serving-server.properties - cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf + cd ${deploy_dir}/fate-serving/serving-proxy/conf sed -i "/^zk.url=/czk.url=${host_zk_url}" application.properties sed -i "/^useRegister=/cuseRegister=true" application.properties sed -i "/^useZkRouter=/cuseZkRouter=true" application.properties @@ -138,11 +138,11 @@ exit eeooff else ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/${fate_serving}/${serving}/conf + cd ${deploy_dir}/fate-serving/serving/conf sed -i "/^zk.url=/czk.url=${guest_zk_url}" serving-server.properties sed -i "/^useRegister=/cuseRegister=true" serving-server.properties sed -i "/^useZkRouter=/cuseZkRouter=true" serving-server.properties - cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf + cd ${deploy_dir}/fate-serving/serving-proxy/conf sed -i "/^zk.url=/czk.url=${guest_zk_url}" application.properties sed -i "/^useRegister=/cuseRegister=true" application.properties sed -i "/^useZkRouter=/cuseZkRouter=true" application.properties @@ -159,7 +159,7 @@ update_route_table () { if [ $temp = 0 ] then ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf + cd ${deploy_dir}/fate-serving/serving-proxy/conf sed -i "3c default_default_ip=\"${host_guest[i+1]}\"" modify_json.py sed -i "4c default_default_port=8869" modify_json.py sed -i "5c party_id=${party_list[i]}" modify_json.py @@ -173,7 +173,7 @@ exit eeooff else ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf + cd ${deploy_dir}/fate-serving/serving-proxy/conf sed -i "3c default_default_ip=\"${host_guest[i-1]}\"" modify_json.py sed -i "4c default_default_port=8869" modify_json.py sed -i "5c party_id=${party_list[i]}" modify_json.py @@ -196,10 +196,10 @@ update_nozk_config () { for ((i=0;i<${#host_guest[*]};i++)) do ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/${fate_serving}/${serving}/conf + cd ${deploy_dir}/fate-serving/serving/conf sed -i "/^useRegister=/cuseRegister=false" serving-server.properties sed -i "/^useZkRouter=/cuseZkRouter=false" serving-server.properties - cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf + cd ${deploy_dir}/fate-serving/serving-proxy/conf sed -i "/^useZkRouter=/cuseZkRouter=false" application.properties exit eeooff From aaed21ee009e2a5a96b20dfdef05496424c29095 Mon Sep 17 00:00:00 2001 From: kaideng Date: Sat, 8 Feb 2020 10:05:24 +0800 Subject: [PATCH 143/190] change log level Signed-off-by: kaideng --- bin/common.sh | 21 ++++++++++------ .../serving/federatedml/PipelineTask.java | 18 +++++++------- .../serving/federatedml/model/BaseModel.java | 12 +++++++--- .../serving/federatedml/model/DataIO.java | 1 + .../federatedml/model/FeatureSelection.java | 1 - .../model/HeteroFeatureBinning.java | 7 +++--- .../federatedml/model/HeteroLRGuest.java | 10 +++++--- .../federatedml/model/HeteroLRHost.java | 7 +++--- .../model/HeteroSecureBoostingTreeGuest.java | 24 ++++++++++++------- .../model/HeteroSecureBoostingTreeHost.java | 6 ++--- .../serving/federatedml/model/Imputer.java | 3 +-- .../federatedml/model/MinMaxScale.java | 16 +++++++++---- .../federatedml/model/OneHotEncoder.java | 13 ++++------ .../serving/federatedml/model/Outlier.java | 2 -- .../fate/serving/federatedml/model/Scale.java | 2 +- .../federatedml/model/StandardScale.java | 8 +++---- serving-server/bin/service.sh | 2 +- serving-server/src/main/resources/log4j2.xml | 16 ++++++++++--- 18 files changed, 99 insertions(+), 70 deletions(-) diff --git a/bin/common.sh b/bin/common.sh index dc0c3920..44c776bf 100644 --- a/bin/common.sh +++ b/bin/common.sh @@ -22,6 +22,7 @@ mklogsdir() { } start() { + echo "try to start ${module}" getpid ${module} if [[ $? -eq 0 ]]; then mklogsdir @@ -69,14 +70,20 @@ stop() { if [[ -n ${pid} ]]; then echo "killing: `ps -p ${pid}`" - echo "--------------" ${pid} - kill ${pid} - if [[ $? -eq 0 ]]; then - rm -rf ./${module}_pid - echo "killed" + echo "try to kill" ${pid} + pidCount=`ps -p ${pid}|grep ${pid}|wc -l` +# `ps -p ${pid}|grep ${pid}|wc -l` + if [[ $pidCount -ne 0 ]]; then + kill ${pid} + if [[ $? -eq 0 ]]; then + rm -rf ./${module}_pid + echo "killed" + else + echo "kill error" + fi else - echo "kill error" - fi + echo "pid ${pid} is not exist" + fi else echo "service not running" fi diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java index 434ae932..dc82e6e7 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java @@ -75,8 +75,8 @@ public int initModel(Map modelProtoMap) { } } } catch (Exception ex) { - ex.printStackTrace(); - logger.info("initModel error:{}", ex); + // ex.printStackTrace(); + logger.info("PipelineTask initModel error:{}", ex); throw new RuntimeException("initModel error"); } logger.info("Finish init Pipeline"); @@ -85,13 +85,15 @@ public int initModel(Map modelProtoMap) { public Map predict(Context context, Map inputData, FederatedParams predictParams) { - logger.info("Start Pipeline predict use {} model node.", this.pipeLineNode.size()); + //logger.info("Start Pipeline predict use {} model node.", this.pipeLineNode.size()); List> outputData = new ArrayList<>(); for (int i = 0; i < this.pipeLineNode.size(); i++) { - if (this.pipeLineNode.get(i) != null) { - logger.info("component class is {}", this.pipeLineNode.get(i).getClass().getName()); - } else { - logger.info("component class is {}", this.pipeLineNode.get(i)); + if(logger.isDebugEnabled()) { + if (this.pipeLineNode.get(i) != null) { + logger.debug("component class is {}", this.pipeLineNode.get(i).getClass().getName()); + } else { + logger.debug("component class is {}", this.pipeLineNode.get(i)); + } } List> inputs = new ArrayList<>(); HashSet upInputComponents = this.dslParser.getUpInputComponents(i); @@ -120,8 +122,6 @@ public Map predict(Context context, Map inputDat inputData.put(Dict.RET_CODE, federatedResult.getRetcode()); } - logger.info("Finish Pipeline predict"); - if(outputData.size()>0){ return outputData.get(outputData.size() - 1); }else{ diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java index 9506b40d..c1f9fdcb 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java @@ -71,7 +71,9 @@ public Map predict(Context context, List> in long cost = endTime - beginTime; String caseId = context.getCaseId(); String className = this.getClass().getSimpleName(); - logger.info("model {} caseid {} predict cost time {}", className, caseId, cost); + if(logger.isDebugEnabled()) { + logger.debug("model {} caseid {} predict cost time {}", className, caseId, cost); + } } @@ -105,7 +107,9 @@ protected ReturnResult getFederatedPredict(Context context, FederatedParams gues if (useCache) { ReturnResult remoteResultFromCache = CacheManager.getInstance().getRemoteModelInferenceResult(guestFederatedParams); if (remoteResultFromCache != null) { - logger.info("caseid {} get remote party model inference result from cache", context.getCaseId()); + if(logger.isDebugEnabled()) { + logger.debug("caseid {} get remote party model inference result from cache", context.getCaseId()); + } context.putData(Dict.GET_REMOTE_PARTY_RESULT, false); context.hitCache(true); remoteResult = remoteResultFromCache; @@ -125,7 +129,9 @@ protected ReturnResult getFederatedPredict(Context context, FederatedParams gues remoteResult = getFederatedPredictFromRemote(context, srcParty, dstParty, hostFederatedParams, remoteMethodName); if (useCache&& remoteResult!=null&&remoteResult.getRetcode()==0) { CacheManager.getInstance().putRemoteModelInferenceResult(guestFederatedParams, remoteResult); - logger.info("caseid {} get remote party model inference result from federated request.", context.getCaseId()); + if(logger.isDebugEnabled()) { + logger.info("caseid {} get remote party model inference result from federated request.", context.getCaseId()); + } } return remoteResult; } finally { diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/DataIO.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/DataIO.java index ed5d7854..07d20bd4 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/DataIO.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/DataIO.java @@ -58,6 +58,7 @@ public int initModel(byte[] protoMeta, byte[] protoParam) { } } catch (Exception ex) { ex.printStackTrace(); + logger.error("init DataIo error",ex); return StatusCode.ILLEGALDATA; } logger.info("Finish init DataIO class"); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/FeatureSelection.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/FeatureSelection.java index cc10108d..0e2bdfc9 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/FeatureSelection.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/FeatureSelection.java @@ -59,7 +59,6 @@ public int initModel(byte[] protoMeta, byte[] protoParam) { @Override public Map handlePredict(Context context, List> inputData, FederatedParams predictParams) { - logger.info("Start Feature Selection predict"); HashMap outputData = new HashMap<>(8); Map firstData = inputData.get(0); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFeatureBinning.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFeatureBinning.java index fba8f6cc..4fad71ac 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFeatureBinning.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFeatureBinning.java @@ -55,7 +55,6 @@ public int initModel(byte[] protoMeta, byte[] protoParam) { @Override public Map handlePredict(Context context, List> inputData, FederatedParams predictParams) { - logger.info("Start Feature Binning predict"); HashMap outputData = new HashMap<>(8); Map firstData = inputData.get(0); if (!this.needRun) { @@ -86,9 +85,9 @@ public Map handlePredict(Context context, List handlePredict(Context context, List result = new HashMap<>(8); Map forwardRet = forward(inputData); double score = forwardRet.get(Dict.SCORE); - logger.info("caseid {} guest score:{}", context.getCaseId(),score); + if(logger.isDebugEnabled()) { + logger.debug("caseid {} guest score:{}", context.getCaseId(), score); + } try { ReturnResult hostPredictResponse = this.getFederatedPredict(context, predictParams, Dict.FEDERATED_INFERENCE, true); if(hostPredictResponse !=null) { result.put(Dict.RET_CODE,hostPredictResponse.getRetcode()); - logger.info("caseid {} host response is {}",context.getCaseId(),hostPredictResponse.getData()); + if(logger.isDebugEnabled()) { + logger.debug("caseid {} host response is {}", context.getCaseId(), hostPredictResponse.getData()); + } if (hostPredictResponse.getData() != null && hostPredictResponse.getData().get(Dict.SCORE) != null) { double hostScore = ((Number) hostPredictResponse.getData().get(Dict.SCORE)).doubleValue(); - logger.info("caseid {} host score:{}",context.getCaseId(), hostScore); + logger.info("caseid {} host score:{}", context.getCaseId(), hostScore); score += hostScore; } }else{ diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRHost.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRHost.java index 0fe42667..3bf00407 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRHost.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRHost.java @@ -33,13 +33,12 @@ public class HeteroLRHost extends HeteroLR { public Map handlePredict(Context context, List> inputData, FederatedParams predictParams) { - - logger.info("hetero lr host begin to predict"); HashMap result = new HashMap<>(8); Map ret = forward(inputData); result.put(Dict.SCORE, ret.get(Dict.SCORE)); - - logger.info("hetero lr host predict ends, result is {}", result); + if(logger.isDebugEnabled()) { + logger.debug("hetero lr host predict ends, result is {}", result); + } return result; } diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeGuest.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeGuest.java index b33077c9..6bf6d53f 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeGuest.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroSecureBoostingTreeGuest.java @@ -139,8 +139,9 @@ private Map getFinalPredict(double[] weights) { @Override public Map handlePredict(Context context, List> inputData, FederatedParams predictParams) { - - logger.info("HeteroSecureBoostingTreeGuest FederatedParams {}", predictParams); + if(logger.isDebugEnabled()) { + logger.debug("HeteroSecureBoostingTreeGuest FederatedParams {}", predictParams); + } Map input = inputData.get(0); HashMap fidValueMapping = new HashMap(8); @@ -154,7 +155,9 @@ public Map handlePredict(Context context, List handlePredict(Context context, List afterLocation = tempResult.getData(); - - logger.info("after loccation is {}", afterLocation); + if(logger.isDebugEnabled()) { + logger.debug("after loccation is {}", afterLocation); + } for (String location : afterLocation.keySet()) { treeNodeIds[new Integer(location)] = ((Number) afterLocation.get(location)).intValue(); } if (afterLocation == null) { - logger.info("receive predict result of host is null"); + logger.error("receive predict result of host is null"); throw new Exception("Null Data"); } } catch (Exception ex) { ex.printStackTrace(); + logger.error("HeteroSecureBoostingTreeGuest handle error",ex); return null; } } @@ -208,9 +212,11 @@ public Map handlePredict(Context context, List getData(Context context, String tag) { @Override public Map handlePredict(Context context, List> inputData, FederatedParams predictParams) { - - logger.info("HeteroSecureBoostingTreeHost FederatedParams {}", predictParams); - + if(logger.isDebugEnabled()) { + logger.debug("HeteroSecureBoostingTreeHost FederatedParams {}", predictParams); + } Map input = inputData.get(0); String tag = predictParams.getCaseId() + "." + this.componentName + "." + Dict.INPUT_DATA; diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Imputer.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Imputer.java index 8443e096..3deadd44 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Imputer.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Imputer.java @@ -35,7 +35,6 @@ public Imputer(List missingValues, Map missingReplaceVal public Map transform(Map inputData) { if(inputData!=null) { - logger.info("start imputer transform task"); for (String key : inputData.keySet()) { if(inputData.get(key)!=null) { String value = inputData.get(key).toString(); @@ -43,7 +42,7 @@ public Map transform(Map inputData) { try { inputData.put(key, this.missingReplaceValues.get(key)); } catch (Exception ex) { - ex.printStackTrace(); + logger.error("Imputer transform error",ex); inputData.put(key, 0.); } } diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/MinMaxScale.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/MinMaxScale.java index 515d852e..4d1ca731 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/MinMaxScale.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/MinMaxScale.java @@ -26,7 +26,9 @@ public class MinMaxScale { private static final Logger logger = LoggerFactory.getLogger(MinMaxScale.class); public Map transform(Map inputData, Map scales) { - logger.info("Start MinMaxScale transform"); + if(logger.isDebugEnabled()) { + logger.info("Start MinMaxScale transform"); + } for (String key : inputData.keySet()) { try { if (scales.containsKey(key)) { @@ -39,7 +41,9 @@ public Map transform(Map inputData, Map transform(Map inputData, Map handlePredict(Context context, List> inputData, FederatedParams predictParams) { - logger.info("Start OneHot Encoder transform"); - logger.info("First time need run"); + HashMap outputData = new HashMap<>(); Map firstData = inputData.get(0); - logger.info("Need run is ", this.needRun); + if (!this.needRun) { - logger.info("Return firstData :", firstData); return firstData; } for (String colName : firstData.keySet()) { @@ -80,7 +77,6 @@ public Map handlePredict(Context context, List values = colsMap.getValuesList(); List encodedVariables = colsMap.getTransformedHeadersList(); -// Integer inputValue = new Integer(firstData.get(colName).toString()); Integer inputValue = 0; try { @@ -108,7 +104,6 @@ public Map handlePredict(Context context, List outlierValues, Map outlierReplaceVal } public Map transform(Map inputData) { - logger.info("start outlier transform task"); if(inputData!=null) { for (String key : inputData.keySet()) { if(inputData.get(key)!=null) { String value = inputData.get(key).toString(); if (this.outlierValueSet.contains(value.toLowerCase())) { try { - logger.info("value:{}", value); inputData.put(key, outlierReplaceValues.get(key)); } catch (Exception ex) { ex.printStackTrace(); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Scale.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Scale.java index 545fd597..8e0b13d6 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Scale.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/Scale.java @@ -43,7 +43,7 @@ public int initModel(byte[] protoMeta, byte[] protoParam) { this.scaleParam = this.parseModel(ScaleParam.parser(), protoParam); this.needRun = this.scaleMeta.getNeedRun(); } catch (Exception ex) { - ex.printStackTrace(); + logger.error("Scale initModel error",ex); return StatusCode.ILLEGALDATA; } logger.info("Finish init Scale class"); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/StandardScale.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/StandardScale.java index 803c86a9..af17338e 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/StandardScale.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/StandardScale.java @@ -26,7 +26,6 @@ public class StandardScale { private static final Logger logger = LoggerFactory.getLogger(StandardScale.class); public Map transform(Map inputData, Map standardScalesMap) { - logger.info("Start StandardScale transform"); for (String key : inputData.keySet()) { try { if (standardScalesMap.containsKey(key)) { @@ -45,15 +44,16 @@ public Map transform(Map inputData, Mapfate serving-server - - + c + @@ -49,6 +49,15 @@ + + + + + + + + + @@ -56,8 +65,9 @@ - + + \ No newline at end of file From 7c259d154d52e047baa5a1a6b8327809a3a04cff Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Sat, 8 Feb 2020 15:46:15 +0800 Subject: [PATCH 144/190] shell update shell path --- bin/common.sh | 8 ++++---- serving-proxy/bin/service.sh | 2 +- serving-proxy/package.xml | 2 +- serving-server/bin/service.sh | 2 +- .../doc/Fate-serving_deployment_guide_build_zh.md | 6 ++++-- serving-server/package.xml | 2 +- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/bin/common.sh b/bin/common.sh index 0d9b6863..09e01a25 100644 --- a/bin/common.sh +++ b/bin/common.sh @@ -3,8 +3,8 @@ set -e getpid() { # pid=`ps aux | grep ${main_class} | grep -v grep | awk '{print $2}'` module=$1 - if [ -e "./${module}_pid" ]; then - pid=`cat ./${module}_pid` + if [ -e "./bin/${module}.pid" ]; then + pid=`cat ./bin/${module}.pid` fi if [[ -n ${pid} ]]; then break 1 @@ -40,7 +40,7 @@ start() { sleep 5 id=`ps -p $!| awk '{print $1}'|sed -n '2p'` if [[ ${#id} -ne 0 ]]; then - echo $!>./${module}_pid + echo $!>./bin/${module}.pid getpid $module echo "service start sucessfully. pid: ${pid}" else @@ -75,7 +75,7 @@ stop() { if [[ $pidCount -ne 0 ]]; then kill ${pid} if [[ $? -eq 0 ]]; then - rm -rf ./${module}_pid + rm -rf ./bin/${module}.pid echo "killed" else echo "kill error" diff --git a/serving-proxy/bin/service.sh b/serving-proxy/bin/service.sh index 324c2491..82b44381 100644 --- a/serving-proxy/bin/service.sh +++ b/serving-proxy/bin/service.sh @@ -16,7 +16,7 @@ # limitations under the License. # set -e -source ./common.sh +source ./bin/common.sh basepath=$(cd `dirname $0`;pwd) export JAVA_HOME=/data/projects/common/jdk/jdk1.8.0_192 export PATH=$PATH:$JAVA_HOME/bin diff --git a/serving-proxy/package.xml b/serving-proxy/package.xml index e8075ce7..4a66fe61 100644 --- a/serving-proxy/package.xml +++ b/serving-proxy/package.xml @@ -36,7 +36,7 @@ - / + /bin ../bin * diff --git a/serving-server/bin/service.sh b/serving-server/bin/service.sh index e4677e3b..6674b9e2 100644 --- a/serving-server/bin/service.sh +++ b/serving-server/bin/service.sh @@ -19,7 +19,7 @@ #export JAVA_HOME=/data/projects/common/jdk/jdk1.8.0_192 #export PATH=$PATH:$JAVA_HOME/bin set -e -source ./common.sh +source ./bin/common.sh module=serving-server main_class=com.webank.ai.fate.serving.ServingServer module_version=1.2.0 diff --git a/serving-server/doc/Fate-serving_deployment_guide_build_zh.md b/serving-server/doc/Fate-serving_deployment_guide_build_zh.md index 7c4493e8..113ea1db 100644 --- a/serving-server/doc/Fate-serving_deployment_guide_build_zh.md +++ b/serving-server/doc/Fate-serving_deployment_guide_build_zh.md @@ -202,9 +202,11 @@ cd /data/projects/fate-serving sh services.sh all start 如果是两台进各自的分别执行一个 -4)查看所有状态 + +3)查看所有状态 sh services.sh all status -5)关闭所有 + +4)关闭所有 sh services.sh all stop 5.5.配置文件详解 diff --git a/serving-server/package.xml b/serving-server/package.xml index e66aa7f4..ccebe184 100644 --- a/serving-server/package.xml +++ b/serving-server/package.xml @@ -38,7 +38,7 @@ - / + /bin ../bin * From d1cc2650bde95233760c00548567626943944d87 Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Sun, 9 Feb 2020 10:55:32 +0800 Subject: [PATCH 145/190] shell update shell path --- ...\347\275\262\346\226\207\346\241\243.docx" | Bin .../Fate-serving_deployment_guide_build_zh.md | 0 .../allinone_cluster_configurations.sh | 0 .../scripts/modify_json.py | 0 .../scripts/package.sh | 36 +++++++++--------- .../scripts/services.sh | 0 6 files changed, 18 insertions(+), 18 deletions(-) rename "serving-server/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" => "cluster-deploy/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" (100%) rename {serving-server => cluster-deploy}/doc/Fate-serving_deployment_guide_build_zh.md (100%) rename {serving-server/cluster-deploy => cluster-deploy}/scripts/allinone_cluster_configurations.sh (100%) rename {serving-server/cluster-deploy => cluster-deploy}/scripts/modify_json.py (100%) rename {serving-server/cluster-deploy => cluster-deploy}/scripts/package.sh (85%) rename {serving-server/cluster-deploy => cluster-deploy}/scripts/services.sh (100%) diff --git "a/serving-server/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" "b/cluster-deploy/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" similarity index 100% rename from "serving-server/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" rename to "cluster-deploy/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" diff --git a/serving-server/doc/Fate-serving_deployment_guide_build_zh.md b/cluster-deploy/doc/Fate-serving_deployment_guide_build_zh.md similarity index 100% rename from serving-server/doc/Fate-serving_deployment_guide_build_zh.md rename to cluster-deploy/doc/Fate-serving_deployment_guide_build_zh.md diff --git a/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh b/cluster-deploy/scripts/allinone_cluster_configurations.sh similarity index 100% rename from serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh rename to cluster-deploy/scripts/allinone_cluster_configurations.sh diff --git a/serving-server/cluster-deploy/scripts/modify_json.py b/cluster-deploy/scripts/modify_json.py similarity index 100% rename from serving-server/cluster-deploy/scripts/modify_json.py rename to cluster-deploy/scripts/modify_json.py diff --git a/serving-server/cluster-deploy/scripts/package.sh b/cluster-deploy/scripts/package.sh similarity index 85% rename from serving-server/cluster-deploy/scripts/package.sh rename to cluster-deploy/scripts/package.sh index 0af764eb..b3ef6694 100644 --- a/serving-server/cluster-deploy/scripts/package.sh +++ b/cluster-deploy/scripts/package.sh @@ -12,7 +12,7 @@ package_path=$cwd/FATE-Serving fate_serving_path=$public_path/fate-serving sering_path=$fate_serving_path/serving fate_serving=fate-serving -serving=serving +serving=serving-server fate_serving_zip=fate-serving-server-*-release.zip fate_serving_jar=fate-serving-server-*.jar fate_serving_proxy_zip=fate-serving-proxy-*-release.zip @@ -23,7 +23,7 @@ serving_proxy_path=$fate_serving_path/serving-proxy mkdir -p ./${fate_serving}/${serving} mkdir -p ./${fate_serving}/${serving_proxy} cp services.sh ./${fate_serving} -cd ${cwd}/../../../ +cd ${cwd}/../../ echo "[INFO] mvn clean package start" mvn clean package -DskipTests if [ $? -eq 0 ]; then @@ -34,14 +34,14 @@ else fi #serving -cd ${cwd}/../../target +cd ./${serving}/target cp ${fate_serving_zip} ${cwd}/${fate_serving}/${serving} cd ${cwd}/${fate_serving}/${serving} unzip $fate_serving_zip ln -s $fate_serving_jar fate-serving-server.jar #serving-proxy -cd ${cwd}/../../../$serving_proxy/target +cd ${cwd}/../../$serving_proxy/target cp $fate_serving_proxy_zip ${cwd}/${fate_serving}/${serving_proxy} cd ${cwd}/${fate_serving}/${serving_proxy} unzip $fate_serving_proxy_zip @@ -49,8 +49,8 @@ ln -s $fate_serving_proxy_jar fate-serving-proxy.jar #cp modify_json.py cd ${cwd} -cp modify_json.py ./fate-serving/serving-proxy/conf -tar -zcvf fate-serving.tar.gz fate-serving +cp modify_json.py ./${fate_serving}/${serving_proxy}/conf +tar -zcvf fate-serving.tar.gz ${fate_serving} cp_serving() { for node_ip in "${host_guest[@]}"; do @@ -84,9 +84,9 @@ update_config () { do #update serving-proxy config path ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/fate-serving/serving-proxy/conf - sed -i "/^route.table=/croute.table=${deploy_dir}/fate-serving/serving-proxy/conf/route_table.json" application.properties - sed -i "/^auth.file=/cauth.file=${deploy_dir}/fate-serving/serving-proxy/conf/auth_config.json" application.properties + cd ${deploy_dir}/${fate_serving}/${serving_proxy/conf + sed -i "/^route.table=/croute.table=${deploy_dir}/${fate_serving}/${serving_proxy}/conf/route_table.json" application.properties + sed -i "/^auth.file=/cauth.file=${deploy_dir}/${fate_serving}/${serving_proxy}/conf/auth_config.json" application.properties exit eeooff @@ -95,7 +95,7 @@ eeooff if [ $temp = 0 ] then ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/fate-serving/serving/conf + cd ${deploy_dir}/${fate_serving}/${serving}/conf sed -i "/^redis.ip=/credis.ip=${host_redis_ip}" serving-server.properties sed -i "/^redis.port=/credis.port=${host_redis_port}" serving-server.properties sed -i "/^redis.password=/credis.password=${host_redis_password}" serving-server.properties @@ -105,7 +105,7 @@ exit eeooff else ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/fate-serving/serving/conf + cd ${deploy_dir}/${fate_serving}/${serving}/conf sed -i "/^redis.ip=/credis.ip=${guest_redis_ip}" serving-server.properties sed -i "/^redis.port=/credis.port=${guest_redis_port}" serving-server.properties sed -i "/^redis.password=/credis.password=${guest_redis_password}" serving-server.properties @@ -126,11 +126,11 @@ update_zk_config () { if [ $temp = 0 ] then ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/fate-serving/serving/conf + cd ${deploy_dir}/${fate_serving}/${serving}/conf sed -i "/^zk.url=/czk.url=${host_zk_url}" serving-server.properties sed -i "/^useRegister=/cuseRegister=true" serving-server.properties sed -i "/^useZkRouter=/cuseZkRouter=true" serving-server.properties - cd ${deploy_dir}/fate-serving/serving-proxy/conf + cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf sed -i "/^zk.url=/czk.url=${host_zk_url}" application.properties sed -i "/^useRegister=/cuseRegister=true" application.properties sed -i "/^useZkRouter=/cuseZkRouter=true" application.properties @@ -138,11 +138,11 @@ exit eeooff else ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/fate-serving/serving/conf + cd ${deploy_dir}/${fate_serving}/${serving}/conf sed -i "/^zk.url=/czk.url=${guest_zk_url}" serving-server.properties sed -i "/^useRegister=/cuseRegister=true" serving-server.properties sed -i "/^useZkRouter=/cuseZkRouter=true" serving-server.properties - cd ${deploy_dir}/fate-serving/serving-proxy/conf + cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf sed -i "/^zk.url=/czk.url=${guest_zk_url}" application.properties sed -i "/^useRegister=/cuseRegister=true" application.properties sed -i "/^useZkRouter=/cuseZkRouter=true" application.properties @@ -159,7 +159,7 @@ update_route_table () { if [ $temp = 0 ] then ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/fate-serving/serving-proxy/conf + cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf sed -i "3c default_default_ip=\"${host_guest[i+1]}\"" modify_json.py sed -i "4c default_default_port=8869" modify_json.py sed -i "5c party_id=${party_list[i]}" modify_json.py @@ -173,7 +173,7 @@ exit eeooff else ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/fate-serving/serving-proxy/conf + cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf sed -i "3c default_default_ip=\"${host_guest[i-1]}\"" modify_json.py sed -i "4c default_default_port=8869" modify_json.py sed -i "5c party_id=${party_list[i]}" modify_json.py @@ -196,7 +196,7 @@ update_nozk_config () { for ((i=0;i<${#host_guest[*]};i++)) do ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/fate-serving/serving/conf + cd ${deploy_dir}/${fate_serving}/${serving}/conf sed -i "/^useRegister=/cuseRegister=false" serving-server.properties sed -i "/^useZkRouter=/cuseZkRouter=false" serving-server.properties cd ${deploy_dir}/fate-serving/serving-proxy/conf diff --git a/serving-server/cluster-deploy/scripts/services.sh b/cluster-deploy/scripts/services.sh similarity index 100% rename from serving-server/cluster-deploy/scripts/services.sh rename to cluster-deploy/scripts/services.sh From f6d6ae44a1139ae7b9d04e4ff51914c9a2e2ef3e Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Sun, 9 Feb 2020 11:23:30 +0800 Subject: [PATCH 146/190] update shell bug --- cluster-deploy/scripts/package.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cluster-deploy/scripts/package.sh b/cluster-deploy/scripts/package.sh index b3ef6694..dc75dbd7 100644 --- a/cluster-deploy/scripts/package.sh +++ b/cluster-deploy/scripts/package.sh @@ -84,7 +84,7 @@ update_config () { do #update serving-proxy config path ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/${fate_serving}/${serving_proxy/conf + cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf sed -i "/^route.table=/croute.table=${deploy_dir}/${fate_serving}/${serving_proxy}/conf/route_table.json" application.properties sed -i "/^auth.file=/cauth.file=${deploy_dir}/${fate_serving}/${serving_proxy}/conf/auth_config.json" application.properties exit From b4c3c88ffca560cd19b02bd6d1922ca31411d0d3 Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Sun, 9 Feb 2020 12:13:49 +0800 Subject: [PATCH 147/190] update services modules --- cluster-deploy/scripts/services.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cluster-deploy/scripts/services.sh b/cluster-deploy/scripts/services.sh index 0c721773..a1f00b2a 100644 --- a/cluster-deploy/scripts/services.sh +++ b/cluster-deploy/scripts/services.sh @@ -16,7 +16,7 @@ # limitations under the License. # -modules=(serving-proxy serving) +modules=(serving-proxy serving-server) cwd=`pwd` From 4af978bd37ef3c6ece7df1ea9c855c7e29e1f23b Mon Sep 17 00:00:00 2001 From: kaideng Date: Sun, 9 Feb 2020 12:51:31 +0800 Subject: [PATCH 148/190] design a new grpc connection pool Signed-off-by: kaideng --- .../serving/core/bean/GrpcConnectionPool.java | 256 ++++++++++++------ .../serving/federatedml/model/BaseModel.java | 10 +- .../proxy/rpc/core/ProxyServiceRegister.java | 7 +- .../proxy/rpc/grpc/GrpcConnectionPool.java | 186 ------------- .../proxy/rpc/services/InferenceService.java | 153 +++++------ .../proxy/rpc/services/UnaryCallService.java | 10 +- 6 files changed, 258 insertions(+), 364 deletions(-) delete mode 100644 serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java index 16a06f33..6bbeed4b 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java @@ -16,139 +16,233 @@ package com.webank.ai.fate.serving.core.bean; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import io.grpc.Channel; +import io.grpc.ConnectivityState; import io.grpc.ManagedChannel; import io.grpc.netty.shaded.io.grpc.netty.NegotiationType; import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; -import org.apache.commons.pool2.BasePooledObjectFactory; -import org.apache.commons.pool2.PooledObject; -import org.apache.commons.pool2.impl.DefaultPooledObject; -import org.apache.commons.pool2.impl.GenericObjectPool; -import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; public class GrpcConnectionPool { - private static final Logger logger = LoggerFactory.getLogger(GrpcConnectionPool.class); static private GrpcConnectionPool pool = new GrpcConnectionPool(); - ConcurrentHashMap> poolMap = new ConcurrentHashMap>(); - private Integer maxTotal = 64; - private Integer maxIdle = 64; - - private GrpcConnectionPool() { + public ConcurrentHashMap poolMap = new ConcurrentHashMap(); + private int maxTotalPerAddress = Configuration.getPropertyInt("rpc.connections.per.address",Runtime.getRuntime().availableProcessors()); + private long defaultLoadFactor = Configuration.getPropertyInt("rpc.per.channel.loadfactor",10); + private void fireChannelError(String k ,ConnectivityState status) { + logger.error("grpc channel {} status is {}", k,status); } + class ChannelResource{ - static public GrpcConnectionPool getPool() { - return pool; - } + public ChannelResource(String address){ + this.address = address; + } + String address; - public void returnPool(ManagedChannel channel, String address) { - try { + List channels = Lists.newArrayList(); - poolMap.get(address).returnObject(channel); + AtomicLong requestCount = new AtomicLong(0); - } catch (Exception e) { - logger.error("return to pool error", e); + public List getChannels() { + return channels; } - } - - public ManagedChannel getManagedChannel(String key) throws Exception { - GenericObjectPool pool = poolMap.get(key); - if (pool == null) { - - GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); + public void setChannels(List channels) { + this.channels = channels; + } - poolConfig.setMaxTotal(maxTotal); + public AtomicLong getRequestCount() { + return requestCount; + } - poolConfig.setMinIdle(0); + public void setRequestCount(AtomicLong requestCount) { + this.requestCount = requestCount; + } - poolConfig.setMaxIdle(maxIdle); - poolConfig.setMaxWaitMillis(-1); + public long getLatestChecktimestamp() { + return latestChecktimestamp; + } - poolConfig.setLifo(true); + public void setLatestChecktimestamp(long latestChecktimestamp) { + this.latestChecktimestamp = latestChecktimestamp; + } - poolConfig.setMinEvictableIdleTimeMillis(1000L * 60L * 30L); + long latestChecktimestamp = 0; - poolConfig.setBlockWhenExhausted(true); + public long getPreCheckCount() { + return preCheckCount; + } - poolConfig.setTestOnBorrow(true); + public void setPreCheckCount(long preCheckCount) { + this.preCheckCount = preCheckCount; + } - String[] ipPort = key.split(":"); - String ip = ipPort[0]; - int port = Integer.parseInt(ipPort[1]); - poolMap.putIfAbsent(key, new GenericObjectPool - (new ManagedChannelFactory(ip, port), poolConfig)); + long preCheckCount = 0; + } + private boolean needAddChannel(ChannelResource channelResource){ + long requestCount = channelResource.getRequestCount().longValue(); + long preCount = channelResource.getPreCheckCount(); + long latestTimestamp = channelResource.getLatestChecktimestamp(); + + int channelSize = channelResource.getChannels().size(); + long now = System.currentTimeMillis(); + long loadFactor = ((requestCount - preCount) * 1000) / (channelSize * (now - latestTimestamp)); + //System.out.println("channel size : "+channelSize+" load factor : "+loadFactor +" time "+(now-latestTimestamp)+" req "+(requestCount - preCount) ); + channelResource.setLatestChecktimestamp(now); + channelResource.setPreCheckCount(requestCount); + if(channelSize>maxTotalPerAddress){ + return false; } + if(latestTimestamp==0){ + return false; + } + if(channelSize>0) { - return poolMap.get(key).borrowObject(); + if (loadFactor >defaultLoadFactor){ + return true; + } + else{ + return false; + } + } + else{ + return true; + } } + private ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1); + private GrpcConnectionPool() { - ; - + scheduledExecutorService.scheduleAtFixedRate( + new Runnable() { + @Override + public void run() { + + poolMap.forEach((k, v) -> { + try { + if (needAddChannel(v)) { + String[] ipPort = k.split(":"); + String ip = ipPort[0]; + int port = Integer.parseInt(ipPort[1]); + ManagedChannel managedChannel = createManagedChannel(ip, port); + v.getChannels().add(managedChannel); + } + v.getChannels().forEach(e -> { + try { + ConnectivityState state = e.getState(true); + if (state.equals(ConnectivityState.TRANSIENT_FAILURE) || state.equals(ConnectivityState.SHUTDOWN)) { + fireChannelError(k,state); + } + } catch (Exception ex) { + logger.error("channel {} check status error", k); + } + + }); + } catch (Exception e) { + logger.error("channel {} check status error", k); + } + }); + } + }, + 1000, + 10000, + TimeUnit.MILLISECONDS); - private class ManagedChannelFactory extends BasePooledObjectFactory { + } - private String ip; - private int port; - public ManagedChannelFactory(String ip, int port) { - this.ip = ip; - this.port = port; - } + static public GrpcConnectionPool getPool() { + return pool; + } - @Override - public ManagedChannel create() throws Exception { - if (logger.isDebugEnabled()) { - logger.debug("create ManagedChannel"); - } - NettyChannelBuilder builder = NettyChannelBuilder - .forAddress(ip, port) - .keepAliveTime(6, TimeUnit.MINUTES) - .keepAliveTimeout(1, TimeUnit.HOURS) - .keepAliveWithoutCalls(true) - .idleTimeout(1, TimeUnit.HOURS) - .perRpcBufferLimit(128 << 20) - .flowControlWindow(32 << 20) - .maxInboundMessageSize(32 << 20) - .enableRetry() - .retryBufferSize(16 << 20) - .maxRetryAttempts(20); // todo: configurable + public ManagedChannel getManagedChannel(String key) throws Exception { + ChannelResource channelResource = poolMap.get(key); + if (channelResource == null) { + return createInner(key); + } else { + return getRandomManagedChannel(channelResource); + } + } - builder.negotiationType(NegotiationType.PLAINTEXT) - .usePlaintext(); - return builder.build(); + public ManagedChannel getManagedChannel(String ip,int port) throws Exception { + String key = new StringBuilder().append(ip).append(":").append(port).toString(); + return this.getManagedChannel(key); + } + Random r = new Random(); + private ManagedChannel getRandomManagedChannel(ChannelResource channelResource) { + List list = channelResource.getChannels(); + Preconditions.checkArgument(list != null && list.size() > 0); + int index = r.nextInt(list.size()); + ManagedChannel result = list.get(index); + channelResource.getRequestCount().addAndGet(1); + return result; + } + private synchronized ManagedChannel createInner(String key) throws Exception { + ChannelResource channelResource = poolMap.get(key); + if (channelResource == null) { + String[] ipPort = key.split(":"); + String ip = ipPort[0]; + int port = Integer.parseInt(ipPort[1]); + ManagedChannel managedChannel = createManagedChannel(ip, port); + List managedChannelList = new ArrayList(); + managedChannelList.add(managedChannel); + channelResource = new ChannelResource(key); + channelResource.setChannels(managedChannelList); + channelResource.getRequestCount().addAndGet(1); + poolMap.put(key, channelResource); + return managedChannel; + } else { + return getRandomManagedChannel(channelResource); } - @Override - public PooledObject wrap(ManagedChannel managedChannel) { - return new DefaultPooledObject<>(managedChannel); - } + } + ; + - @Override - public void destroyObject(PooledObject p) throws Exception { - p.getObject().shutdown(); - super.destroyObject(p); - } - @Override - public boolean validateObject(PooledObject channel) { + public synchronized ManagedChannel createManagedChannel(String ip, int port) throws Exception { - return !(channel.getObject().isShutdown() || channel.getObject().isTerminated()); + if (logger.isDebugEnabled()) { + logger.debug("create ManagedChannel"); } + NettyChannelBuilder builder = NettyChannelBuilder + .forAddress(ip, port) + .keepAliveTime(60, TimeUnit.SECONDS) + .keepAliveTimeout(60, TimeUnit.SECONDS) + .keepAliveWithoutCalls(true) + .idleTimeout(60, TimeUnit.SECONDS) + .perRpcBufferLimit(128 << 20) + .flowControlWindow(32 << 20) + .maxInboundMessageSize(32 << 20) + .enableRetry() + .retryBufferSize(16 << 20) + .maxRetryAttempts(20); // todo: configurable + builder.negotiationType(NegotiationType.PLAINTEXT) + .usePlaintext(); + + return builder.build(); } + } \ No newline at end of file diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java index c1f9fdcb..d2290c25 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java @@ -202,13 +202,11 @@ protected ReturnResult getFederatedPredictFromRemote(Context context, FederatedP Preconditions.checkArgument(StringUtils.isNotEmpty(address)); ManagedChannel channel1 = grpcConnectionPool.getManagedChannel(address); ListenableFuture future= null; - try { + //DataTransferServiceGrpc.DataTransferServiceBlockingStub stub1 = DataTransferServiceGrpc.newBlockingStub(channel1); - DataTransferServiceGrpc.DataTransferServiceFutureStub stub1 = DataTransferServiceGrpc.newFutureStub(channel1); - future =stub1.unaryCall(packetBuilder.build()); - } finally { - grpcConnectionPool.returnPool(channel1, address); - } + DataTransferServiceGrpc.DataTransferServiceFutureStub stub1 = DataTransferServiceGrpc.newFutureStub(channel1); + future =stub1.unaryCall(packetBuilder.build()); + if(future!=null){ Proxy.Packet packet = future.get(Configuration.getPropertyInt("rpc.time.out",3000), TimeUnit.MILLISECONDS); remoteResult = (ReturnResult) ObjectTransform.json2Bean(packet.getBody().getValue().toStringUtf8(), ReturnResult.class); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyServiceRegister.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyServiceRegister.java index c2c30307..0107fca5 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyServiceRegister.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/core/ProxyServiceRegister.java @@ -1,7 +1,8 @@ package com.webank.ai.fate.serving.proxy.rpc.core; +import com.webank.ai.fate.serving.core.bean.GrpcConnectionPool; import com.webank.ai.fate.serving.core.rpc.core.*; -import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcConnectionPool; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; @@ -47,9 +48,7 @@ public void setApplicationContext(ApplicationContext context) throws BeansExcept } - - @Autowired - GrpcConnectionPool grpcConnectionPool; + GrpcConnectionPool grpcConnectionPool = GrpcConnectionPool.getPool(); @Override diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java deleted file mode 100644 index bdb54a65..00000000 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/grpc/GrpcConnectionPool.java +++ /dev/null @@ -1,186 +0,0 @@ -package com.webank.ai.fate.serving.proxy.rpc.grpc; - -import io.grpc.ManagedChannel; -import io.grpc.ManagedChannelBuilder; -import org.apache.commons.pool2.BasePooledObjectFactory; -import org.apache.commons.pool2.PooledObject; -import org.apache.commons.pool2.impl.DefaultEvictionPolicy; -import org.apache.commons.pool2.impl.DefaultPooledObject; -import org.apache.commons.pool2.impl.GenericObjectPool; -import org.apache.commons.pool2.impl.GenericObjectPoolConfig; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; - - -/** - * @Description grpc 连接池 - * @Author - **/ -@Service -public class GrpcConnectionPool { - - Logger logger = LoggerFactory.getLogger(GrpcConnectionPool.class); - - ConcurrentHashMap> poolMap = new ConcurrentHashMap>(); - - public void returnPool(ManagedChannel channel, String host, int port) { - try { - if (logger.isDebugEnabled()) { - logger.debug("return grpc pool {}:{}", host, port); - } - String key = host + ":" + port; - poolMap.get(key).returnObject(channel); - // logger.info("pool active size {}",poolMap.get(key).()); - } catch (Exception e) { - logger.error("return to pool error", e); - } - - } - - @Value("${proxy.grpc.pool.maxTotal:64}") - private Integer maxTotal; - @Value("${proxy.grpc.pool.maxIdle:1}") - private Integer maxIdle; - - public ManagedChannel getManagedChannel(String host, int port) throws Exception { - String key = host + ":" + port; - if (logger.isDebugEnabled()) { - logger.debug("try to get grpc channel {}",key); - } - GenericObjectPool pool = poolMap.get(key); - if (pool == null) { - - GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); - - poolConfig.setMaxTotal(maxTotal); - - poolConfig.setMinIdle(0); - - poolConfig.setMaxIdle(maxIdle); - - poolConfig.setMaxWaitMillis(-1); - -// poolConfig.setLifo(true); - -// poolConfig.setTestOnBorrow(true); -// -// poolConfig.setTestWhileIdle(true); -// -// poolConfig.setNumTestsPerEvictionRun(1); -// -// poolConfig.setTimeBetweenEvictionRunsMillis(1000); -// -// poolConfig.setEvictionPolicy(new DefaultEvictionPolicy()); -// -// poolConfig.setMinEvictableIdleTimeMillis(3000); -// -// poolConfig.setSoftMinEvictableIdleTimeMillis(3000); -// -// poolConfig.setBlockWhenExhausted(true); - - poolMap.putIfAbsent(key, new GenericObjectPool - (new ManagedChannelFactory(host, port), poolConfig)); - - - } - GenericObjectPool objectPool =poolMap.get(key); - - if (logger.isDebugEnabled()) { - logger.debug("grpc pool host {} active num {} idle1 num {}",key,objectPool.getNumActive(),objectPool.getNumIdle()); - } - - return objectPool.borrowObject(); - } - - - private class ManagedChannelFactory extends BasePooledObjectFactory { - - private String host; - private int port; - - public ManagedChannelFactory(String host, int port) { - this.host = host; - this.port = port; - } - - @Override - public ManagedChannel create() throws Exception { - - if (logger.isDebugEnabled()) { - logger.debug("create managedChannel"); - } - - ManagedChannelBuilder builder = ManagedChannelBuilder - .forAddress(host,port) - .keepAliveTime(6, TimeUnit.SECONDS) - .keepAliveTimeout(6, TimeUnit.SECONDS) - .keepAliveWithoutCalls(true) - .idleTimeout(6, TimeUnit.SECONDS) - .perRpcBufferLimit(128 << 20) - // .flowControlWindow(32 << 20) - .maxInboundMessageSize(32 << 20) - - - .retryBufferSize(16 << 20); - - - - builder - .usePlaintext(); - - ManagedChannel managedChannel = builder - .build(); - - return ManagedChannelBuilder.forAddress(host, port). - usePlaintext(true).build(); - } - - @Override - public PooledObject wrap(ManagedChannel managedChannel) { - return new DefaultPooledObject<>(managedChannel); - } - - @Override - public void destroyObject(PooledObject p) throws Exception { - if (logger.isDebugEnabled()) { - logger.debug("destroyObject ================"); - } - try { - p.getObject().shutdownNow(); - - super.destroyObject(p); - }catch(Exception e){ - - } - } - @Override - public boolean validateObject(PooledObject p) { - - - ManagedChannel managedChannel = p.getObject(); - - - boolean isOk = !managedChannel.isShutdown()&&!managedChannel.isTerminated(); - System.err.println("validateObject ================"+isOk); - - - return isOk; - } - - } - - - public static void main(String[] args){ - - - - - } - - -} diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java index 6ece5f87..09990bc1 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java @@ -8,6 +8,7 @@ import com.webank.ai.fate.api.serving.InferenceServiceProto; import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.bean.Dict; +import com.webank.ai.fate.serving.core.bean.GrpcConnectionPool; import com.webank.ai.fate.serving.core.exceptions.NoResultException; import com.webank.ai.fate.serving.core.exceptions.UnSupportMethodException; import com.webank.ai.fate.serving.core.rpc.core.AbstractServiceAdaptor; @@ -16,8 +17,6 @@ import com.webank.ai.fate.serving.core.rpc.core.ProxyService; import com.webank.ai.fate.serving.core.rpc.router.RouterInfo; import com.webank.ai.fate.serving.metrics.api.IMetricFactory; - -import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcConnectionPool; import io.grpc.ManagedChannel; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -41,114 +40,106 @@ "inferenceParamValidator", "defaultServingRouter"}) -public class InferenceService extends AbstractServiceAdaptor { +public class InferenceService extends AbstractServiceAdaptor { - Logger logger = LoggerFactory.getLogger(InferenceService.class); + Logger logger = LoggerFactory.getLogger(InferenceService.class); @Autowired IMetricFactory metricFactory; - @Autowired - GrpcConnectionPool grpcConnectionPool; - public InferenceService() {} + GrpcConnectionPool grpcConnectionPool = GrpcConnectionPool.getPool(); + + public InferenceService() { + } @Value("${proxy.grpc.inference.timeout:3000}") - private int timeout; + private int timeout; @Value("${proxy.grpc.inference.async.timeout:3000}") - private int asyncTimeout; + private int asyncTimeout; @Override public Map doService(Context context, InboundPackage data, OutboundPackage outboundPackage) { - Map resultMap = Maps.newHashMap(); + Map resultMap = Maps.newHashMap(); RouterInfo routerInfo = data.getRouterInfo(); - ManagedChannel managedChannel = null; + ManagedChannel managedChannel = null; - String resultString=null; + String resultString = null; String callName = context.getCallName(); ListenableFuture resultFuture; + try { - try { - if(logger.isDebugEnabled()) { - logger.debug("try to get grpc connection"); - } - managedChannel = this.grpcConnectionPool.getManagedChannel(routerInfo.getHost(), routerInfo.getPort()); - - } catch (Exception e) { - logger.error("get grpc channel error", e); - throw new NoResultException(); + if (logger.isDebugEnabled()) { + logger.debug("try to get grpc connection"); } + managedChannel = this.grpcConnectionPool.getManagedChannel(routerInfo.getHost(), routerInfo.getPort()); - Map reqBodyMap = data.getBody(); - Map reqHeadMap = data.getHead(); - - Map inferenceReqMap = Maps.newHashMap(); - inferenceReqMap.put(Dict.CASE_ID, context.getCaseId()); - inferenceReqMap.putAll(reqHeadMap); - inferenceReqMap.put(Dict.FEATURE_DATA, Maps.newHashMap(reqBodyMap)); - int timeWait = timeout; - try { - - if(logger.isDebugEnabled()) { - logger.debug("inference req : {}", JSON.toJSONString(inferenceReqMap)); - } - InferenceServiceProto.InferenceMessage.Builder reqBuilder = InferenceServiceProto.InferenceMessage.newBuilder(); - reqBuilder.setBody(ByteString.copyFrom(JSON.toJSONString(inferenceReqMap).getBytes())); - - InferenceServiceGrpc.InferenceServiceFutureStub futureStub = InferenceServiceGrpc.newFutureStub(managedChannel); - - metricFactory.counter("http.inference.service", "in doService", "callName", callName, "direction", "to.self.serving-server", "result", "success").increment(); - - if (callName.equals(Dict.SERVICENAME_INFERENCE)) { - resultFuture = futureStub.inference(reqBuilder.build()); - timeWait = timeout; - } else if (callName.equals(Dict.SERVICENAME_GET_INFERENCE_RESULT)) { - resultFuture = futureStub.getInferenceResult(reqBuilder.build()); - timeWait = asyncTimeout; - } else if (callName.equals(Dict.SERVICENAME_START_INFERENCE_JOB)) { - resultFuture = futureStub.startInferenceJob(reqBuilder.build()); - timeWait = asyncTimeout; - } else { - logger.error("unknown callName {}.", callName); - throw new UnSupportMethodException(); - } - - }finally { - - if(managedChannel!=null) { - grpcConnectionPool.returnPool(managedChannel, routerInfo.getHost(), routerInfo.getPort()); - } - } - try{ - InferenceServiceProto.InferenceMessage result = resultFuture.get(timeWait,TimeUnit.MILLISECONDS); - metricFactory.counter("http.inference.service", "in doService", "callName", callName, "direction", "from.self.serving-server", "result", "success").increment(); - if (logger.isDebugEnabled()) { - logger.debug("routerinfo {} send {} result {}",routerInfo,inferenceReqMap,result); - } - resultString = new String(result.getBody().toByteArray()); - } catch (Exception e) { - metricFactory.counter("http.inference.service", "in doService", "callName", callName, "direction", "from.self.serving-server", "result", "grpc.error").increment(); - logger.error("get grpc result error", e); - throw new NoResultException(); - } + } catch (Exception e) { + logger.error("get grpc channel error", e); + throw new NoResultException(); + } + + Map reqBodyMap = data.getBody(); + Map reqHeadMap = data.getHead(); + + Map inferenceReqMap = Maps.newHashMap(); + inferenceReqMap.put(Dict.CASE_ID, context.getCaseId()); + inferenceReqMap.putAll(reqHeadMap); + inferenceReqMap.put(Dict.FEATURE_DATA, Maps.newHashMap(reqBodyMap)); + int timeWait = timeout; + + + if (logger.isDebugEnabled()) { + logger.debug("inference req : {}", JSON.toJSONString(inferenceReqMap)); } - finally { - if(managedChannel!=null) { - grpcConnectionPool.returnPool(managedChannel, routerInfo.getHost(), routerInfo.getPort()); + InferenceServiceProto.InferenceMessage.Builder reqBuilder = InferenceServiceProto.InferenceMessage.newBuilder(); + reqBuilder.setBody(ByteString.copyFrom(JSON.toJSONString(inferenceReqMap).getBytes())); + + InferenceServiceGrpc.InferenceServiceFutureStub futureStub = InferenceServiceGrpc.newFutureStub(managedChannel); + + metricFactory.counter("http.inference.service", "in doService", "callName", callName, "direction", "to.self.serving-server", "result", "success").increment(); + + if (callName.equals(Dict.SERVICENAME_INFERENCE)) { + resultFuture = futureStub.inference(reqBuilder.build()); + timeWait = timeout; + } else if (callName.equals(Dict.SERVICENAME_GET_INFERENCE_RESULT)) { + resultFuture = futureStub.getInferenceResult(reqBuilder.build()); + timeWait = asyncTimeout; + } else if (callName.equals(Dict.SERVICENAME_START_INFERENCE_JOB)) { + resultFuture = futureStub.startInferenceJob(reqBuilder.build()); + timeWait = asyncTimeout; + } else { + logger.error("unknown callName {}.", callName); + throw new UnSupportMethodException(); + } + + + try { + InferenceServiceProto.InferenceMessage result = resultFuture.get(timeWait, TimeUnit.MILLISECONDS); + metricFactory.counter("http.inference.service", "in doService", "callName", callName, "direction", "from.self.serving-server", "result", "success").increment(); + if (logger.isDebugEnabled()) { + logger.debug("routerinfo {} send {} result {}", routerInfo, inferenceReqMap, result); } + resultString = new String(result.getBody().toByteArray()); + } catch (Exception e) { + metricFactory.counter("http.inference.service", "in doService", "callName", callName, "direction", "from.self.serving-server", "result", "grpc.error").increment(); + logger.error("get grpc result error", e); + throw new NoResultException(); } - if(StringUtils.isNotEmpty(resultString)){ - resultMap = JSON.parseObject(resultString,Map.class); + + + if (StringUtils.isNotEmpty(resultString)) { + resultMap = JSON.parseObject(resultString, Map.class); } - return resultMap; + return resultMap; } @Override - protected Map transformErrorMap(Context context,Map data) { - return data; + protected Map transformErrorMap(Context context, Map data) { + return data; } } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java index ffa0e310..d2975a93 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/UnaryCallService.java @@ -8,6 +8,7 @@ import com.webank.ai.fate.api.networking.proxy.Proxy; import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.bean.Dict; +import com.webank.ai.fate.serving.core.bean.GrpcConnectionPool; import com.webank.ai.fate.serving.core.exceptions.ErrorCode; import com.webank.ai.fate.serving.core.rpc.core.AbstractServiceAdaptor; import com.webank.ai.fate.serving.core.rpc.core.InboundPackage; @@ -16,7 +17,7 @@ import com.webank.ai.fate.serving.core.rpc.router.RouterInfo; import com.webank.ai.fate.serving.metrics.api.IMetricFactory; -import com.webank.ai.fate.serving.proxy.rpc.grpc.GrpcConnectionPool; + import com.webank.ai.fate.serving.proxy.security.AuthUtils; import io.grpc.ManagedChannel; import org.slf4j.Logger; @@ -45,8 +46,8 @@ public class UnaryCallService extends AbstractServiceAdaptor data e.printStackTrace(); logger.error("unaryCall error ",e); }finally { - if(managedChannel!=null){ - grpcConnectionPool.returnPool(managedChannel, routerInfo.getHost(), routerInfo.getPort()); - } long end = System.currentTimeMillis(); context.setDownstreamCost(end - context.getDownstreamBegin()); } From d95b835b57ca2aa6ecfc5e56199605cd2bed7862 Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Sun, 9 Feb 2020 17:03:31 +0800 Subject: [PATCH 149/190] update shell file --- ...\347\275\262\346\226\207\346\241\243.docx" | Bin 29196 -> 28888 bytes .../Fate-serving_deployment_guide_build_zh.md | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git "a/cluster-deploy/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" "b/cluster-deploy/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" index acb7b85a3224635d356980d76952e109fad1a8f9..79598daad0f858a09d379a00334bd5fa5bd40803 100644 GIT binary patch delta 19588 zcmZs?b8shb)IAs*pV+o-+s?$ct%*9eGqLT7?TM|4ZDS@ACzGA;`_}IMwrcxU|JT(| zSKsb)?mg!|aYx{5```@&;7|hRqnWA0;LO0f>?W0+*D*SVJ?9P@?`9N}l!=!gR($~1 zuEd6?r?k17)86OHENY}fUro&!a;--EESeHrjA*PheedjoL}ye5ty{kRM2)mW!sFh2 z53k5nm}zmNi!}k{3WJLpsqI+U#KNfG5UZCNB$v0G%Ngl+e0_J4bgy`192S3MUMk=S zeRZ$%DE|-zHzzp?rWBK6(ZO*UvUEUI40%I95hJF*m)PO@ui!ib&8*%_FT6MYL4H#i zZ&Zoj=}#MnO>SG7fViZBgNozp0AN|?{H6}7kUX{6c`OY2Mob!f0OdMi^6==j+@O%0 z$^(0eQ&WzvBn<6$?tY0q?=CoM?WizzJw1}eX4r-Qp%+hTj6grqp*~%b_S?bzp^#<- zz+gV$>++eyO2sB;&dxCL%}`NCUIm>PTx$2^w1Tz|teF{77#Tj-6%QF21O}#%>IqT= z#`Uwl-rp#k6#RYP^nEy3;=NAaoT)*4$?Nc8bHd)-JU$t!i8bDN$k;y^aT0NGid^>M z=<9kGykY+P{sDZh77*8a9R?=Od9gGMTY0w{^>lS?-rm+Tvv|jLy9!d0G(^!ZzDf`TXpIIHzLOLWLqP)S=TkOk+$xhdI!h8otbUj*KZ$! z$98XjKit^z{|d}y{!I%=?5>dfFB#+fgtv}7r~c_@?fD#vxa*stOjz^&-T z)ZtFXy?Q3|$o?wEN%wN|OW39ibD!>x;*f_02b9UES#O8$hiP+OL=INig<5v(Ety)s zbNeMuqW05a3s@>?g0z9bY>;aJ$Bq%Ob3Nv{>-5+?w1=!3y0>zjxcTug#OX-F-7XRb zIodE<0S=dM)r|V))eSuEU&{#sf!SaN)5XDQqsT|zY_n$fmLr?LKJFSe`U#xAKW)C< zdr9YU_vA&$fc~;9{eA>SZTk(7YA)YQgNyDf7P}57oHDxKmhWC7uUp=UXb7|(Y@I@l z0{^lf=G@Lr{S`5GZW9r5zrC^Q5=G3;WgC>8fh^E^4jyTJVOWr zT4!x{#6qo4u3TCgohyuY2-n$bBWBEcMzw0#fSp9oH!$Y8{y@W>n_Co;?O8=X4wQmX zK}O*lt)Uy{8JH6=f1Tc(h~r?3vEZTA>vq4)qRZ~%w~x6)V(x>xYnQiv)b5X*W2<*l zgTRi>{+%GYn)>n&e&i?Mc69f#pZUo|<$~3jY_4)aJ$WCx_WytC3 zdgkuyTf^tc+iH&fxY)+WGgx%4%Wx_j;jf)|+t$xbx8}`2|4g-4kyzsRoreX7kbxXi zuVqI`Jop7a^p`;kyCisuaWpfpnU>IGUcT4U!sM;<;N*jX4|?k3qZ1T z@>pMZfIj*_O7{s41B4B9Wa-{}^M-pD+>QyO?2&Ix^aEQW8&{+p0j)hicbIKvLfo4Q zPq%*$|DspA-}Ch5_RH8@1C2Fz)KWi%@4dAONF)9>P0Z|`jT%M{t(mqI;nv*LJ^Qy> z-tfLYQdi3;TNKbTqCKRQx*WZSxh}&^Q*6=!ZiYA_8g0>PT~oCYhLnC);`m!!4rW4m z`8)iuY)#iMq=WFE#}>8yMF|tqBjTr+44x)5T)h+0ohGfVoy`HYRg*StiLGDw7T{L6 zc`N1u4+(1782u0}5)@{vG4Ln});UUAwHL~!pAVHhn;L+Cp7thL>eV9LFGvC^n(|ab zyf{ir!t584EO|xhSfSi{9Op{tf8c3=H{|UU`Yt`{FLXEs>`N({UE|4WG+a!YMQ<$^ zDdruP7zWpIB7D*~_^Is%p&024U9@NZvtgT340%L{;=goV*6FAS>T%l@?&amXR8Ym! zm02tGBv1g_VZ98&LoZ};s);`pTDJb??^QEh_;@(ki$7qb+snK5(0AjK7dZa56dI<* z@o&qjL9NRm9Q(&&7K^osD$`5dwsCZ(tHbExZkHk$(aBlJTxdz2g}KAE_R^=!BPWrD zQF4M&=FD>H;Ll6LcBx$b&@4t=Y&mKr6-?vSLxu)?nZKZ)jMK>TM9M9r{gpi}Im`O4 zmCLFqT~^Z!Z(8$G*juRcX4h-_DEgY7YlSUG&V9$vrU&c8k%tG<&_J#rRfz$8C26US z)ff;x{5o~kl03};Zbk;1cb$rlGI@kNy|T!WUk-L`m7ElI9X{NgTF#tT8Ryli0X=9lKd#qyT_CQxPu8My6xHq*x9c%$ssK-88NnG)^o^4iq2GJl) zyrF7BRFl0pB=lFFWVtl)(%Mj(uEL`$oXjhLwyNa!^eDr5bDgfpDs<+ynHl+x;)J@x zW7@PDy+CTzFauWiavh+M3-;8wfjIFc`b;%@e z73C61xJJmPWHqRLz=%hh2r88IGmgJ}D1+6$sp0tlhqei_bR}c}E+zpQ3j0=!5;?u}<2PvF38v0YG+ejYw*=z=i$~;NVeVXP zwP*zc$Qauz3p5tr9Y`{HPWcZ3uMm-?VA*h*D1fK_zJv0_D8|*S`VkO;|`qx0KttrO*~~;DZ%+T3KeoOJ+dX ze_J%zt7%0jnYJoz$NZzFLp1RXC2`OVNkh$kCTl`}MhSL`R?l85Yxz(KV?iIgjv~

    @dSCY?n;meH$w->9-k7ecwDOn;uwUkpS?nqOTBcRQ0!;1OpY5vN_BZm`dema zvRI5kl};|&Bo#z}35bD72&ig7OE%W3qsq!82hDF($4SN>$7_|`?9;92m5?w-VdXFw zR}!Fiw!vLHn6~1Js`{D{L$Vc1XvsPBaGk;9mQw{4&e}wA>xE*@>y!V&#-h`)BrjR< zIBp`>n$`;z=5O|25hwU&fvfW^%6w+73@6#dE6r`T$S`=Q3E&f^@U}E?8OI@vbz&lg zyUvXJ2j&UKLGH;)QdX$7sDql5j>aeBIcHmh#Z^BTQ;<78RnO%h;}GRP!(hmYkG7(P z8j6Y@huJ9z>}B?5Toir`xMHZ-FXa|B^Vf#wi7VyLiV<&NEQLc=UDOhrX`pg9&gRWD zMoD)D@gw8c07#Iu*a<_FjiTtLlW~*Ao!<>!8<& zi%+tbHSX%r-cG`Pw>2Y8M_Xt*4U-qmrJv3}@oj_L0BQrQVvt7!M0Zh$4yIfzbvlGl z+#X{0%${bn9*1EN-kePt#^$9eZgbYQh)WkKyv0Hn2etf&KfEtHN*SLt5#<>*>*nPi zJE@=pitGi$MTJVdtRzp2(e|wjnTtz zUNs6rjNTr&CofhW__sR#QxSWzQA-L)l&$lTT}l%cUZwXpej{!X7qcq-p>87j>Q&13 zXxVygrNM#4<*s2RW^z~?CvBBmWM4@#;^n>5cd^kU1B+xx{0y6>=5Z9q`BL_pCtnPJ z0115oJ*p)sdYdm?%;^l8J%&6BBkP=wT|!ep_hVyoOO=GG%{u{1&`m}haurd&T9QCW z$9i95BDYfluPQ7EPQKwgoPVNp^@L04`Ds?i6ZooH*25U4fww6Z$InItdUHtva53Z5 zFGHuf1Ee*SG|1rb&MO?^4aA&I1}S_1+QamK19=xM+A*BW38ldsQ!V!1sXa={vHMv` z;p?a!ij6S_vLffvXAM(2%6r_753m=eIFU-C!q;I+wDwH3N7BB9c6Fjf_#b?Lf#V1f zlJsSQfLavGL~g?DH!~sZUBi2qRtJ>gc$XtOr~NspFI&CF#xwQb1G8uQPhgQ*R>sCa zmO8tNQ~M*EFNWCkjh8h!=jKLEvv)aYx4xtGWUHt=)_YSKG6PORwOT}|7PCR2Q+mcT zb6ywEe;ckYu2}uK<><6+>pjisW?|oXwQhSC%6M2S~I7(gbe`j32fRFn4 z!rosTJauOrkA3CN=dUfle4*NX_Uh;t=_BlVAvHFvJ&ELY%WoSqZueOLyq^zGLiq=O zf=BdQt?eh8vXNjL7X(g|5CR7^ONkY10mMrx=05da@3VsO>-U)f`0l4{J+%HkT|*M7 zwyj(K!}i(tb6?*-yjnMe6WT>`VT}z13HZ_X?-Gt4tQvhgh{#BN$lLsSJ9iF+4i)z+ zw3Bu}AypM1p%B3SJCYGVsrk0>&5fiYFbLH43(e!)U|?-@sjt+mfE8`De9yIyy6a+Z zFIOu)C`@vgbP!y0YB7@O^k6vQnsW2ppw446FQ;b7(Ryhq+F8E(wLVUCqTn~qV-F9A z6h0;h4*gq#2AUiQEI9OOE8qRC0_f9OM#sS%(tOzXAxX`}m8@#lWTa(JfQZ4;+rgie zBb%A7$)J#Jq=8Khv|+j*6ySVb0xuOaFHOum)$%j)19WyL?^cFJnUy+P0m6CQ$1&<@ z)0$t>khg!X)_tqhUV44)qGvc)+NP~&*2toJ{qA(uWR`vBH>G|ZVxfGu^hIEmbPlFn2Q9;+XjRFe($1`Z#@DL+@F`dS z<`l0Mcad5!W(8hAc{Lo@(o6M=xd-W7`-A4@+}a#`uQlkjPTp>&v%!Vug8YkBHICZL zBq`3~>%@8jV1FyO$!NIyy`UCSfhb%opY4zIc4uyvZ%cFD_dM@eDPDnrG^H8Ot!%tA zD54(VJNYu!soTYg+fd7&ix>;J*{?i2JQ_Kk98{|vq^_>UR{Y4utti>Dn#V6o1YFxm z^7vbuR#w!FSy@UTm@FNr^)HK4`bVwGpMuWw8SX3TKshD%DU0mCJb95Tap!A)d(}NI zrs7s<yp?x|5z* zIN^R#UB;1TVZZ6NN;4~KtS|+dNo-d}JzZ9t>V>JhgzCOZ%%&g5SPbA#DgJ^rX|T4c zg{XsTZj({}~yv zsc92P?HW!AGKL$LIpTS(N+Y7qN3}o54zI8wE`6w?{PaZYB%#`r=afLad3j*!xZ+ZR zr!_OskO2b&T6t)sVN_2?2CmHzwS6yY(HMfNEBkLS* zEela9kW&qR)=|cMI5%nJa44f(J!3pXbfr}5A*d{0ogtCNH1d44#UrvcQ;y5qY0Y`N zO{DMs=y&|bcDG@jbWJH576i*QfE>m{JRWi>w)Sp6FOf5>pKtZIODaw6rFdAA;L0`o zb-w}i;Z>mD7~%-A0pZop*U)ARMbqr7(^p$6iK})m&5%vC;UyfIr7tifyv1bT82>XH9!{7C^@`WY``w`q(8DV`GnVY=t<2Pwnqb~;NfLU36-_ZG7 zAx-gP&$JSjyN9M?+c~+PKbp@%sV>9FxgBGJMELsa^TrO_Lw#Vj`>oqA4Upc>7%#1n zS{2iGV0xG@@wbrp6s!_hkmpYPy|Tq=t}qW|W4Fmn(+;F&xtI@{^edWBS3R7CEs)qU zxrp8###Rx=R`%QqzECEG0@Fe5!&X_$v{@|LSql0Ylx4;7;t}avr`7tpOjfbqP~VbD ziR>TeONhL#`%X}Wj9lC+hv4nI&Oyp86W3;!nBh@=QA|+~2yAC(!jT|SwXS^8ExI)Q zJnO=zAN5D#Vj4Vl;u}*S#4kur&pQ5|9m)A;=I}&kU7-aSQYtt{z?9qR0}7I2!b%mK zS)S$ZE3a)A{!JKER&m~dpP0P$xU(TgDm_@{cQgoL7O&vzrsv;&vJb10Ru79IGehAJ zLAJ%7%YTiSQ2ds=)38Gpfe>poQz|2Q@nsUPm1Gbeml-Lizc}9 z{MBwtU0S1qNd*#`AE3-Gq`R6_IU`z6V6Pb7-GNL)gQW1edlKfqfBJCF?|DJl=rcmn zeqM~i2SFX_6v9AZTYcw}8Hz{?;qftDNHmWNNvRM-Fm%(*B~1KEYzN@&&0}ga3(c2ag8w54GbmcRSNEnaL#1 z4aoDd{H*BIzEpQLrpeM+Rg~xJZuEbge&mppacIn~KlQiV-V~2vFovTF5&!Y&@%+B^ z-3{qOl^Ic108n?KcQXm0R#$@@Pg@AjQv5@ACnJHLEBo?YV_>p9vN8sxbX0%8+`Moa zl=kPR&XYs}xCKkEx4E;o^=EzJBgx00u||6jiQ)&Q?mO-+@sJPCtI*jK9L9KiiFl&L zoMjO1=Ys*n1qA^PMuocWTVDGkaX_Q@Cljz9?Mf7P2@J$s5U2n8@r1kTx?tYZLkPuC z0?Dv2g(qhoGdR{F4WoVYad$qrE8I|T{HVQjcA$uaiXm=`m)9bR18bOD(!r}AZ+E}n zEh6-nvXN}+=u__HR76Fy>|Q>kMU+bk8GqD3wFSzzE+CKdTkx$= zG4`IEKR~} zmOV$ES@_?P0JCGwgG0z)thh!YiXHISOm$@KZr}}`h)If2yE`Xct@g2i*tl`GQwD5v zo8$@b9_#yE^|8$L_6iGI4=irIG^=+fu`@hw*T5DS{yqQoWo{7$gfo@!W}Cq=`ccww z)lpz%B_Tku$Ige$RSEoGkb|Y4;CknPOoX2K0RmESZHm#)%&It z!xFc@3%LK@`}TL|!(K$FFKfK~ih<`^26bIq^&H7O&{)f2%EB+$cw~q5*6#MN#EHpF zsJG>-xn>W0P)`Fm%}xHf=Uf?FPY zCqhSGT2bOUkon=w7?Du;vz7za8}oQ*OxaHS#Mu3`=;J1yITNBa8xD!`705NO5V`sj zDUqM?kX>b;A_rq9L(t5x{IWEr?3N$6v-2|g_YXRP@~scG$ImGoI!Vc8n(SOsDu(cE z_|Qc)m(gel3iqEm0S_6}TcM#nJg2=suoj+73vYSH31Gis^goG<5tw_!Y80_yz0y|A z&tZu62HnJKLzeitUu_X!egkM}6Uk|ThpmmDdRFmJR)2_FiK0r(p?>QOh|#jB84*hv zS^Zwuv|`T4mbaYmlDtIsjr2fi6cOIzlUMkbE|Z)-<nL6n1bNJFYcfC^uUK~Jjsae`Ij?ZT!d* z@#zNqxX?3tg-RMeF@;^&Cwk!`K5`L#k^GHpY~d6HGtdkF9)Haq?yRc5;whn?NkNmMds!txs&qo9Kt^{22{paO%sGysVXOho+k*7Rh=n} zn2o{I-1A$x8D(ATFeMXRFy@y&m^B}IK;A<|aI4dg5tBBs%2R>Hg_~Bh>q{+P5jC{7 znqWTqKtx6sR#?GXa$5mDBu@TWS^-AALGU$*ifAX7VK`+kZ z;km<|regb*w$hKhcEl)*3sOHeD-47&t>uqET_oJ;@o*l*eR$R2FdBH1#Fc6S4+TM4 z8bSCrZ`&;wiSOfpt5l~LCtyjW$nM+cxy)L}@$(KNbp`O5>LXisZbSn2>H4+AeF$cV z98J1F(DFAKO~(s-odr@O{3)2;&_g*y=YJzgwxZ>e@K2E*_gXlA6tusXCm zfffsIB(s+P*T<%hm%`pucnMyYs+31nBH<$7bYZV63Jw5 zz&wqWiShHm<7Pi{ymQ)j9B#H6cDoz_|I>;wZD0#b@lt4CU1F6f%p7HcSBw{CA6NK} z%jV~Ph@Mg@mVz>dLAgGO45#qI^VQfhp}L~8L2hq4_7rIo{P3MZy5w?rHACrzU87EV zqmC>bv_|e`QwFMx*jGJ{a^8Ez0&Ja&f+Q@|22KM9VOh&45op$ttRlpsSSRv-39sO_ z254-_?;YHtSNe~O_?<>H3!jT!(KvGDJhe+>VL5UG6!eQzeKYq7Sx4OX)0EaSK3|W9 z`duc1;Aa_+w$q{Gm32Rr@yI`;-cui01ER>+rp_&+%HVD$?@6Uf|GMt^_~;ylsAkLa zu~kw3CHv@~!Fz2~^XiahB!&g~i6(>tbj6N9>-Ep+~rssCblP zAaGM+qdYSv@yBNM(C=TGQZf<7zrg8|TdX&yDS*N+P?4gR5$Y+XZzadst(Zj2LSYj5 zEclR;KtcTV|C;0w}iomp(oxl$Q($#+kGPyF+{TZj8X0!%3U0q8L% zs*IB}KCsW+AAgZ+3(`Oi$Z*m`>e)_P@iQpK|U~9Ann9k=Tz#eh|poS5{WW zo>>)g*)b_slw;He5q5z%nYq20*9(Bnj%~oF)Ygiqe-E@h8B}Tj&OKxNKHU>VX5j+mLEqT}!#vmnlUwQ4B2dA6anL zUMm97*3E!o=4)44<+-Zccghnh>x$E)&i?{t$bc24&>!>UM-q4h60600Dn#R8Yd{lS zWAgpN;3yj^-n7Sr8#Pnjedm#Sx-!HBC8B~qx~y2;Jn0ga(Bl3EnjH63?=2vLaF+g0)^fWF%vu)+Hh6LH` zO^3lKV1$0TF|q?u0?H9+p%^4=nc-33P_QNlyhw1~^w9h=5koz92JEO5Lq!K*ePwK6 zTkPL(O}N0dFp#{wIx44J-f+wav@pQxhSEn7$-O8XM351-PL4e6xLq6kP$yy5{(=W=d|yyDv??*;!CaygC~(wN zh*=2yr^q-Y34$O7yX;N(A-TA*sY@fuQi*3yxJj%P^|0uUgg)TePkFbJskWQ`*mamA zdjcis7uN6$cltM`Pt$cLT8OzdFx#2%PPp=&!9iIXVYUAcwo9=yJvQ5Y_8tp4?V;pjn@^S zFe&gcyeu?p7gm*9XzUh6fKe7ANrWXS&9aO-G7p#@ru$m4?@6-FzS@NiGfkAm}~(Uo6wb zt^WngoSyaP!^@9O_kN=+8EOrr+*It1c!{?p8`=szdnw%v06*K3`frYUcLU72KukD3 z5|@rF@(K7HOoDvAB*{lDMYB?U7h7YK70NblvUBt>O$d+fBF8@xaD?k}`q+v_Pxiq+5;Y57*f>3Pj8 zxmlMWg=}a6I8gaFf!#bO0m)xvB50OJKJ(?eS%UQrj)S-sKO^>c|;`>y%GUNwe39hD2SqLE(eB7cbuY^2CEP?bQ(`BmI~Gb`X>9LAi0 zVJ^G!u3tm#GmmjW#v~&sk6EGC%dv4p`LW?(^!9g8L>PK7e{%X*U88FHFHL>BZC6;p zan1^g?srtObu4jsurGg~LP@-@+nv68hcvguX+KwAfBVCuB*lP!5&jTV9zNKMeEP@yNrajD^`zQl+Tlm^oGcPq8M>S-;2cz$ zWaO1LMk$?@f6G0ibf6=Ucuo~2yPW2wMfZ9e095F+3`srP>Q>|()~E>PBxKuQ0pG8X zRzrQgJxIP^E8)>>Z08z?!RFoW{(aFrB5QswGkw00W*}UqVbuU7sbd1#)Ev~t<<}3JflGio)*k#WBkPv$0c0J z0Q5FvW{1x5$%`Z^J3Ul+E(1nv7>kf=R2#c>X;Ey%rQX+0ay+oBJ8u@JW$=sWk+7MC zKaNNU=+#_H>_(LCPF>$ehnxGq_C5c|TCxVROekn-QmS=dd$ipx4tWyX1S@Zw1C`2{ z@lwi?bTAw3Y)+MSA9D<>NNxj5#a3HefweYCwQ}@G?f;6e&ezLo7!q5*sJ8SnVSDwe zH2taMGw&wiMqSn<06;o#V|DOIdXlP9CaVe9~$6=lWE0nq4Ciy;f1xqPPY@hN@>q8c>02Fw( z-jitrGEn;bp~WNp-(Qy*X7WV*{9}r;I1&_j6{F_&SzL5y!4JtL;mRQol`Eo3#A!IY zcDwsMW?r%<7Ps@5W}P%ek$aN*GYX@d(TM#Y-l8&t_mhHY`HUoD^MAobapMW$LnM5V z(k{zesCO3l(Z&{gn|zH=Sd#=680f(XbKau{8`*Gr%~|;JQ<+8eo6dhSA|yto6s!5b z=?3h%DbDo|WSNIe*D1Vqlh19X!S9X-sny zSpMfu?1k$6<3j78SXp+tVZ&s_n*RZ5IX%gxIJ5~nK7~oLo(Ww@1IdRncBM0x^Yk+9 zk=xAqSV;}Im#P_{gH??YGb6UfBDAxYl}%z-^afkAeV&1;IIV!z;Xs=^gTfk_gg-2l z%Y@M;hh0Hga;JZOey`agk1RGyO*4KQs#pCeXo+)Xmm=}|GDV7G=&7{3S}{zNZ?h9G zf}+ygOx<*2#l|Fs$0=*uyD)&@Fw0}oIA3Dt8^6-=2L}Q`D|L_Q-Q)bl@P_Zc2vY}J zqocK*t5U(ES+hu6X3?8%^kovE%835|kl4#=NI{qsO!mmX`E|7}vis7QFWvrEVF!^k z5T=ayv-9LJ9;J+EOq8V^sdMY*Dx9RJc4|x;Oc(f8|5R=Ly;NnFwVL;l1B})z zm0Q!Kju7&FZ^&kxrOZfAQE)#}>?Gf2&tbHfIG@OJn|Md~suARzah85;5y%{v=3w4e z=sy{a!sJSz;7%yTGOWGM@YuHPJnr~qrGvdnx$QwUih;HSiEgpgkW$wX`p;*DUq|e zZ^YU`xS|`K3gg+kdXwr^%dARqFX4GM->=8tw3U~Q8Dny>NYaTgBgVBOSwB-ZQB8iX zrmo&x-3DI56OcI39eC)Lx4;;ETiH~`i|fLxi4Ivvk8Sj+snT9 zj6P~M&&@_g|L$hMT;JOp4T-T+pVxZep|hXJj^p2(LBPnDi}cN`l+xw?IVoWW-sR@~ z5b76*aDQu)l=xpaG!JwN6uaZl*^X^>PKlO=!N?+jQ25dq6{+0u&;^2JC(sdFYsGoP zhNXUk*mcX*GX2@-%xFLfSwhkI#-)fC!+wD!g*VZ6@4jel1y^WAF>*w!iu9>#et+;3 zrv!Z7{ND0VuXaOTUh)Gz+(a4B^F%!$!XXJAyNh>>TzMf`;D8(U@o1yRS)x0b|D!cT zx;aQ&$y`%}*b;oZ=eeE}S=;^Se{iL!6>K&NVPhe$&woTZ;^6h6L$N(2x{-C37EyA#dDFI4R>>@&%09gl z59(arvI`ap(dwEBqW`s5{S|MZYVaGl&lV6ISDA#5yUHIxGd_gpM1qhkcuS8s9_5Ht z@#5^48hE--M82Bjw6hOGtd zQmyE>3$9Y_3)*I@q$SpEGi;a)ZoH{HWC|_Di1o6GuB7v6?*WoA-Q4&kf^!EXEP}Xm zwcQWnO7!&jXhm4=Q0Y14;T7qE+U&iBon-T~aa7=I?S!wR;kJ$F80we$EV||t@fx^p z)H|J20%FWVjdw^Q4?;FR^sIbuSvyo{Pt_=1y1}*ky&Zs4#CMY=#6FD^05_rjM?y2WonD3EqGFkLlJjMT9t%>M8Cw+{y$&Z2$DO0Wa}#O<>c&VA>_|+G`shd!DQd9o6DM*1X*T?l2>`bB=qwp!wH z%s>kJXLddc5|+0H!6#_)`;7u}g zW5+pTHdW%VtYom82w+}LTQ+Nx)$Q$dw8SypZk$II+91Q38AJ-~0pS**(7a!jOYol4 zwJVH%zgX&ZIO(d?js!I{{Es7N==1+^ zTb&Y;^1;JxSlgsvjt7+o!{S2r^U@>aY9m>Tc~O(VsT9Y0FW_7tC{&cDf3eYISm^{s zZ>2|^U>oM@OmZk%d8Ot4ln-)m$KxZ;T2PoFq5s&biXOBqpkNn=7twLLkRF+cLwdcZ z6{vcNY{CG3k5&DQlXq*;1HDOX(FzFVRY4cQPzw~LnBNXAgV4Y6Yto8!XV21#sf9oc ze7C0+D=8m1t5bPfJOmwJ7<$hW(F*cShkoz=PHDuiG>|&WPQoctQamsoMuX_^602k; z%7#Ro>=X6VRmbwmT+KXqnBwOS_s#{V7BMf6mtO#Y0xZsu#}p!=TN0^1=!sbu_HF|# z@ViXuBl-iW7r2I(H&4Ne3xAqW-)D|cs;Qz&6Z=uIv5T$y-;VCwpj@tFi<^+g-4q0e zq*LjoGlK%|R+gWkNMV6n3XIHI7bqJ~ynh$CBn-Up9`_UlL_bowFe)9X+oiJPQHY_i zy8*rbZ%es;lyThF{zieDp=k4n#6@m38>8KG-3a11l@eB#FEaM+9>=@-E2L}q-45+> zfiCe31*WK4`b&IucFfDp#Cd4@%|f#&zIP@3Io5W|3O9btdYvNyGzYF#!Z;uKDdsxf ztzABxP>vC@wloD}D32O=L}Clztxbx5p6vw3LMcnZk?ed!e8$}NkQO?NzLlGuXDt+k zI!r|FiUOHTT`p8Uuww)j0P!(ZDIFY0I@fOr`>83_6j}Y+HbMBV52>s zmJs7(M@vthz(DGQO1H|}Sft975Ywt}3s}>91C7S0Aodi2UOSlPia@}%WZmq@m8uQ? zSoqlrwu)b+s6X>Yief2^SGXQ1L)rk}^*niRkz#^B8b2DZEshW#4W9VdQLDgP_bz%S z!E5}+?fXREN9)JvnBNo2_4a3~UZkU8SR-x^XevVT%uG6@KTk9Q#7ks~G>cWB@%?#D zq26mB0VFppNydT+F#;rnvaj&{FuUWn8p%a=W>1tz{ACw=jKo8!n8*Zu=_$cB!(pey zw^^B9SknNf$Y(ig(L=2^(?j&D%PtdpeYYyisTEUD8sDGNDz71af}s)GY)c58#Ir!m zW-s4qz4Sm$2FsJEZFyBCK|@7$J9$bJkLF$Vzp|}WORMoc4JefKoUqJ{##~Unz=^uD ztzSoOzulSN`l?(hvLDSkDg}(9C7|NN#OPVU6ssAe_{@GK+^GC1N4hx78y$&!PlPa$ z5DC+EYb5l1^?UH?>59bYDJh=yaDRbF(IbX@0p7F0s2Rzy1UY$326jx19L`?AZX18h z5A`OI_jobd1JG?M_1K>O&q_lg!(+Qq63qTLwv#ZZ*?_sgv!0u%JLL!E54tN-l_;eD z?$iM>2kQ}sTNDy>jng=QFUb4Ulf=q#GwLHs%uRLu9YYrmhSQ3+1(nq1KYi)>P{`iR zhr@vo&y{dsw9A^5mLvnWH~p3@B(JIx2v%ZkJiiY z9lPLvdwr6~ql6UAGfvUuGWBG~#*fc+Qd8i})c5Z+BX9>)Xl$OsD}LXhD$LBw8j4bU zv9r;_MPvWF&uiM6qD_MeE^GoX6ZskA$kTXjmBGtU{Np&*5PP(W5v|g3L<$;EkzlEhxKXi|b z*Fxcs|JMfTrO3q_@!rdn#Cx9@2_Kbd;!WIpR8kt2B8wquO=9Umiln~M&>_%cF7QbK zNS|sB5Dv4EIwDNIeS%*{I!wDKW(X$85FKq!^7Fkryv=b2z{9~b1Ao}l z!pEQ?e=DRFqev|@k8c{2|C7K)6Lv9+T;8O$fep^vFpYC54+esgD32ZnQ6?dlN+#1gb`>$P8hK!K$78m9P|38bk|BOW+kKyd%;* zO9$n8D?CN(lWP3LBm}xduok@k^iL&1IB`mA?Mna8OTR~fg7sT}67eK*vxE>#GbwREA=C|NneX{cm{Z0!Z+mTQCgYY&;5s5@Tu+);-Gj?75L zP*XV6r7$7KN%iNP)K3cd-W#Cj>mU=pkmEJ?)1yCIcm_y3KFlquCTnVdOlaZj1zL7) zTj+6$?LT(TWS)S_q?irmRk7ezA-vnyiQeV~4dbx3-}W{FmuVmqq~c>*DYY1BYN;`P z27avr?^-&!qsJ}_I(y4waX7O0C_eI&m_bP0sLWJCHOXnY)wt961F4X*ADdKn#aKh7 z1-_h#hWYwNdPUdP-CjV3n~5$y3lg{FPca7xLWY4YP^6hJUwMXkhA$|ZQ+07&YGktz z_P;lw%PtGWbl!-H*kFp8#qANejL&U$zJrdz5L~Dp8czwHv7Pc&qa6`iX}gk-L0v49 zhCP!8WzD5_KARU57Q{TqiFq|#{BH(TNAVoV5xuWGu{Vw!6HovPMX8TUyCU){B@}3U zHgOksQ^SEo+-cnVIJ4ZyG+-tyMF|wI>|yTQQ0K_~&<-&WGlLv1{VhrR%Ccuf^{Jz6 z;ho{aONQ+BOciN9^;>jee`j6^mZtDNXcjW(Ri1$-VP8~Rk=dGEi`egU5IN19Aw4<) zUju3ijgjQqABg5P8ES-ZI1mK^4TOG;&x!{Uaxp0$Eh!$g@vS=1dyVCk$=hqO;a;SV zq*`zp=2~#q(E=S}c5oSp@9mJ3k{w$V{vhTjok-sOB`kTdk+2xsftd57mQCq?(oIiv36C_Fjeu&R2hSGyRaOnkboOn>*C@6q3Ax%dAsu*K6o@0Q@%i4 zc|}otLa2I0yUtr$k4Q#Rd~!bw!(U@Sshg}xtkf7M+Vt|D2Gw?*W2d_d#paCHCEe)I ztX5ADW1EsU$6Za8xj63?rAI-r0k1i$<|;Wu!<*K~HucMHI;4T`WZPtCM)kK7L>d4! z0#22H<%{t;P;w`| z?&L;Sx%0*@(AeI+)Z@Mx_^wRO@p*JY@FwD)pPAk6+_Ss&6<562>^Gmh=C{gZxu1>P zD`r$NW7#Q^5#|XmJ?FtS4YrKj@kB05I>VD_3}ds5g^q3SWNPY_`!B>l1pzNBSJVip z5f6_MO%ZEH+n6M2jyj}!8;rT`he2I!t}}my)0^Le-H?e3`MDlRl>rdmd|3|6Et69= z$KYw#W@<_cfP-ac)5YmGnGA-Bb_5+)CAsQ6K;72SK+e_Zwyz2rF2W>;F#Pp?)jqxk>gS#g)tG^E&41 z(RVc_{lA(x&u})_E{;bUMeSL&ipOK`8ZnyMvu3IA7!@N_?G==i#BLC25u&Zus8OS4 zY1H1kc8t>y59HxaDO@1eZJk-b|3zX%*Vh{ za_`7L)rjQQa?E9BN`2-?WI}=-isSyT;&F>u*(G+&=bJF1%xENgKQEJ+o)jn`ja@FzDYdxp1Z+A3^yC||H*Kq zN4`(9_ZVZ@B^)5ofZ*SD&zK7(wx>qIej+I?INLelHdW%=uD<*?{PH*bU)Sh*hV6AI z=oD&J)x13K=Eft1U)UJF@~5%!YfGjWl)o;196ajeBUp?z_iWQMw4I_~d!T{x-qfaO zacRD^D1Bi;sglL^!aeP1j>hX)lV|Fb$-!OY*xeC_*u(;6@?+f+U5+tAroy@R|Qn(XYnAN{c^Q<+`CcjOB`0w}Fg_x`c9 zhltr&HS({!@}n~7+s!y1a|RrgOM&L+%n$zT+n~jdkR`z6njufG>Mb~&~=p5act6luW}teN$(T#6WV);6-69qxcd~DkA!lRr$$^G z2`Slu`Trom>;W2mf628R#aU!q_kxi_>2&f2MpA(qCBoLNSjPxGr*!h}fD*?EhZuF| zfos|wq2?uiv9#5gkUw^F@4h&@`T-@_F>j5%blvs^9BHurMX;jS!~ugc0Y%X+GV9*1nn=Wx0MoS$VX zvB_4cqlK0qif+N)Sh#Vf{Z@KDSr(;eAMVV17QMY`jFz^~2y9vUd^|EL6z1|xiUPLg z5y$gN^j^CtAm4Q1R?0nQ=Rk(mMsE(GHF4i!mdlaVi$pl`LogNLP9-x;+N$`4Zr*HZ zNMdErQZx}vGjYK%Xz0hjUS1?h4)Z_DO5IE>=oZa9kCJf6kQ8%J-pgd4k^h)~=K?F# zA>|qbIDMDTP3!NKWL_8YQ@OuYnH@psz}g<2NQsBLZr3EXcJZs4e2VIb5T!!;gGWBT zu)69`XmvrJok*pIDc6l}GgN4p3mf?rJl&J;DT1-gP3~Hm^Rl{tv{xn1Yqs|+`Gn)` z8_%>>=P^E3-Iwlm8;n$f{#NZj`!TV)9h!Z8p44>y?sQ0*>efQdV&v(k!CP4OK%0fi ziid+S*Z#ETtVW|LP*tN*gM88DhHtq9R9gxJIYevJ|H_~rUzCIV2}4o|zR%27vpHNm zE)QFOI!die7mVfM3w30*-T55b7tkL_-N~W9UlbFu16dZyFfXT76G)6jexQt!ej%3O zs`#ECq?)n@j(csqIqa{4qcY)hWZW1+cv6&^R$)qa-&Ma!1rk1K5x@!6W~BdmiI5*; zaa5RGrBW0*zLC2fR1BmDg3iBABaF2KOLUJ+C$`;KXS9H5daAp)A;Zvv({Yv@`;A}p z7?cG)9bZYj`Ch4RVBx(**31|Wh-mZGPYmq=sS%yAc2zfc@%XlfD$7?k9?LRN|KvOh zW_IgKdIDV;YbDAsHXm<}6pWfqA(t2hn9J?!V@XlLWqq~@4$ChYx5H5+<>jI9z6}3& z*WNbYGf{ME8jvkHYBBDw&>_b1b$Emf3k>@p`Wd9rfvt^qh2?HR_kPVM2Z7xTXUx6S zt#c=a8(6(!?YTNBOMlCxR?^4TchpaEz9h>KR3Z;D6Q7?(XxB$++GXOAvequzwc;2{ zqsh{B>w+>3lR`^rf@xFo>#+-(qo8yIrXx2d!$vDH%zlAnsL>A!_(kPlTwV$#(mFik z%P$`PJD{R@c+92kM+lASr34H0dz_o06}LK7Y?cF{zGtnAol z4Tv+Hv-hWjIV(9xSl&KFoP$Y3DzT-een$F$uklc^gJ>;xJ&+9iK`EZ&aL53Y!WXlnVoh7DRdhtUObWhmw$t>zf>ku)i zU;S1xytx8deQibOd4J3m1Q?ryWAT|7|DqH5>?#~jrWBh=SShnV7U<%w5r36ZEGTlZ zjDs6LsKQ>5yDgEett+**V>?yF@3?(AneY&U2F z0~K=GQ1AGbATc&EpErc`7-)_~DLQy0gwVS0E|Hz#wUY_FxEa+DgS0|-I(~DwVt@I^ zP!{B@3wb!T;sY|$mtPPdD9glT1(#wrEJ~r4mnmouMR0zk!GBqS(3f`BDJh^0)476D zFPraZatUU!5%k(c(q2{NmAkzp+RaTmNwI<-Y6_P``hGB|-^TEl$koP7XN-Q2X}Fxb zS}Tc00KyZ>G#?wb3A=!TCD$lF&fWx@G9l|*LJ%8!bfy$sagRC-BPvA_wgfae6+iJh z<@<4tloj?~MIxirHj}0OzH>BJ`pWS@|54+z>y^dCukQzP#n|k0nh&wR>eF#Y3B!8g z%dJr~{cA!deiudfd$j0Fab=|i1f|8C%#p!l&MSJNAOHyDLCI=0B+oZL3&qSPAU(cv zog4S}o%<4)wbQGyF#03|Z&J+>VAkVy;_ShZmKd|%_e3%0#vLCc&K90{OBnTaO?i=h zhxV0%UA-WA)}jM5*35=#-ptDe@m{W<=}6oH#`l}z)_;Q6d?NMc@|CM77@7UeiqgYV zL+R5wp@vzLOSAf=3*5EoU_3Q7`>)exf9_6ev`+$;0A z+lYb^2lmkb_B-OjeV+eW{l)9s3pDq@t@$bg!#r>Te$w3k<5xid=HF>A&Y#Syxch!W zJpcQx007IM@W1gK7_P(*0$g>%ef1Oizhzig{V^2jxB`DLMH6npU-Yu4*Y9Wl0q0Mz AasU7T delta 19958 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 diff --git a/cluster-deploy/doc/Fate-serving_deployment_guide_build_zh.md b/cluster-deploy/doc/Fate-serving_deployment_guide_build_zh.md index 113ea1db..e00f76d2 100644 --- a/cluster-deploy/doc/Fate-serving_deployment_guide_build_zh.md +++ b/cluster-deploy/doc/Fate-serving_deployment_guide_build_zh.md @@ -169,7 +169,7 @@ git clone https://github.com/FederatedAI/FATE-Serving.git ------------ **在目标服务器(192.168.0.1)app用户下执行** -进入到FATE-Serving目录下的FATE-Serving/serving-server/cluster-deploy/scripts目录下,修改配置文件allinone_cluster_configurations.sh. +进入到FATE-Serving目录下的FATE-Serving/cluster-deploy/scripts目录下,修改配置文件allinone_cluster_configurations.sh. 配置文件allinone_cluster_configurations.sh说明: @@ -196,7 +196,7 @@ git clone https://github.com/FederatedAI/FATE-Serving.git ------------ 1)打包 按照上述配置含义修改allinone_cluster_configurations.sh文件对应的配置项后,然后在 -FATE-Serving/serving-server/cluster-deploy/scripts目录下执行部署脚本 : sh package.sh +FATE-Serving/cluster-deploy/scripts目录下执行部署脚本 : sh package.sh 2)启动项目 cd /data/projects/fate-serving From 574ea5648c390e28d76d09da52fa5de7d9c6046f Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Mon, 10 Feb 2020 12:06:14 +0800 Subject: [PATCH 150/190] change log Signed-off-by: v_dylanxu <136539068@qq.com> --- .../ai/fate/serving/proxy/rpc/router/BaseServingRouter.java | 5 ++--- .../ai/fate/serving/proxy/rpc/router/ZkServingRouter.java | 6 +++--- .../fate/serving/proxy/rpc/services/InferenceService.java | 4 +--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java index e31142dd..54f99ffe 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/BaseServingRouter.java @@ -50,9 +50,8 @@ public RouterInfo route(Context context, InboundPackage inboundPackage) { context.setRouterInfo(routerInfo); - if (logger.isDebugEnabled()) { - logger.debug("caseid {} get route info {}:{}", context.getCaseId(),routerInfo.getHost(),routerInfo.getPort()); - } + logger.info("caseid {} get route info {}:{}", context.getCaseId(),routerInfo.getHost(),routerInfo.getPort()); + return routerInfo; } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java index c2e0aa5d..cbd0510d 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ZkServingRouter.java @@ -53,9 +53,9 @@ public List getRouterInfoList(Context context, InboundPackage inboun } String environment = getEnvironment(context, inboundPackage); List list = zkRouterService.router("serving", environment, context.getServiceName()); - if (logger.isDebugEnabled()) { - logger.debug("try to find zk ,{}:{}:{}, result {}", "serving", environment, context.getServiceName(), list); - } + + logger.info("try to find zk ,{}:{}:{}, result {}", "serving", environment, context.getServiceName(), list); + if(null == list || list.isEmpty()){ return null; } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java index 09990bc1..1f02c992 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java @@ -120,9 +120,7 @@ public Map doService(Context context, InboundPackage data, OutboundPackage< try { InferenceServiceProto.InferenceMessage result = resultFuture.get(timeWait, TimeUnit.MILLISECONDS); metricFactory.counter("http.inference.service", "in doService", "callName", callName, "direction", "from.self.serving-server", "result", "success").increment(); - if (logger.isDebugEnabled()) { - logger.debug("routerinfo {} send {} result {}", routerInfo, inferenceReqMap, result); - } + logger.info("routerinfo {} send {} result {}", routerInfo, inferenceReqMap, result); resultString = new String(result.getBody().toByteArray()); } catch (Exception e) { metricFactory.counter("http.inference.service", "in doService", "callName", callName, "direction", "from.self.serving-server", "result", "grpc.error").increment(); From 2df7dfdfcaaba883e57e001045c9361e27a13954 Mon Sep 17 00:00:00 2001 From: kaideng Date: Mon, 10 Feb 2020 19:41:07 +0800 Subject: [PATCH 151/190] change log level Signed-off-by: kaideng --- .../serving/federatedml/model/HeteroLR.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLR.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLR.java index c82cd427..04efe713 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLR.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLR.java @@ -57,9 +57,10 @@ Map forward(List> inputDatas) { int inputDataHitCount = 0; int weightNum = this.weight.size(); int inputFeaturesNum = inputData.size(); - logger.info("model weight number:{}", weightNum); - logger.info("input data features number:{}", inputFeaturesNum); - + if(logger.isDebugEnabled()) { + logger.debug("model weight number:{}", weightNum); + logger.debug("input data features number:{}", inputFeaturesNum); + } double score = 0; for (String key : inputData.keySet()) { if (this.weight.containsKey(key)) { @@ -68,7 +69,9 @@ Map forward(List> inputDatas) { score += w * x; modelWeightHitCount += 1; inputDataHitCount += 1; - logger.info("key {} weight is {}, value is {}", key, this.weight.get(key), inputData.get(key)); + if(logger.isDebugEnabled()) { + logger.debug("key {} weight is {}, value is {}", key, this.weight.get(key), inputData.get(key)); + } } } score += this.intercept; @@ -81,9 +84,10 @@ Map forward(List> inputDatas) { } catch (Exception ex) { ex.printStackTrace(); } - - logger.info("model weight hit rate:{}", modelWeightHitRate); - logger.info("input data features hit rate:{}", inputDataHitRate); + if(logger.isDebugEnabled()) { + logger.info("model weight hit rate:{}", modelWeightHitRate); + logger.info("input data features hit rate:{}", inputDataHitRate); + } Map ret = new HashMap<>(8); ret.put(Dict.SCORE, score); From 713f6b6a4a7ea9ea5a937c5161aa99668cb7b89e Mon Sep 17 00:00:00 2001 From: kaideng Date: Mon, 10 Feb 2020 20:05:23 +0800 Subject: [PATCH 152/190] change log in the code Signed-off-by: kaideng --- .../core/manager/DefaultCacheManager.java | 24 ++++++++++++------- .../serving/federatedml/model/HeteroLR.java | 4 ++-- serving-server/src/main/resources/log4j2.xml | 5 +++- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java index 5fc631fe..f98b0389 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java @@ -126,8 +126,10 @@ public void putInferenceResultCache(Context context, String partyId, String case String inferenceResultCacheKey = generateInferenceResultCacheKey(partyId, caseid); boolean putCacheSuccess = putIntoCache(inferenceResultCacheKey, CacheType.INFERENCE_RESULT, returnResult); if (putCacheSuccess) { - logger.info("Put {} inference result into cache", inferenceResultCacheKey); - } + if(logger.isDebugEnabled()) { + logger.info("put {} inference result into cache", inferenceResultCacheKey); + } + } } finally { long end = System.currentTimeMillis(); if (logger.isDebugEnabled()) { @@ -142,8 +144,10 @@ public ReturnResult getInferenceResultCache(String partyId, String caseid) { String inferenceResultCacheKey = generateInferenceResultCacheKey(partyId, caseid); ReturnResult returnResult = getFromCache(inferenceResultCacheKey, CacheType.INFERENCE_RESULT); if (returnResult != null) { - logger.info("Get {} inference result from cache.", inferenceResultCacheKey); - } + if(logger.isDebugEnabled()) { + logger.info("get {} inference result from cache.", inferenceResultCacheKey); + } + } return returnResult; } @@ -155,8 +159,10 @@ public void putRemoteModelInferenceResult(FederatedParams guestFederatedParams, String remoteModelInferenceResultCacheKey = generateRemoteModelInferenceResultCacheKey(guestFederatedParams); boolean putCacheSuccess = putIntoCache(remoteModelInferenceResultCacheKey, CacheType.REMOTE_MODEL_INFERENCE_RESULT, returnResult); if (putCacheSuccess) { - logger.info("put {} remote model inference result into cache", remoteModelInferenceResultCacheKey); - } + if(logger.isDebugEnabled()) { + logger.debug("put {} remote model inference result into cache", remoteModelInferenceResultCacheKey); + } + } } @@ -168,8 +174,10 @@ public ReturnResult getRemoteModelInferenceResult(FederatedParams guestFederated String remoteModelInferenceResultCacheKey = generateRemoteModelInferenceResultCacheKey(guestFederatedParams); ReturnResult returnResult = getFromCache(remoteModelInferenceResultCacheKey, CacheType.REMOTE_MODEL_INFERENCE_RESULT); if (returnResult != null) { - logger.info("get {} remote model inference result from cache", remoteModelInferenceResultCacheKey); - } + if(logger.isDebugEnabled()) { + logger.debug("get {} remote model inference result from cache", remoteModelInferenceResultCacheKey); + } + } return returnResult; } diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLR.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLR.java index 04efe713..a3186e42 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLR.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLR.java @@ -85,8 +85,8 @@ Map forward(List> inputDatas) { ex.printStackTrace(); } if(logger.isDebugEnabled()) { - logger.info("model weight hit rate:{}", modelWeightHitRate); - logger.info("input data features hit rate:{}", inputDataHitRate); + logger.debug("model weight hit rate:{}", modelWeightHitRate); + logger.debug("input data features hit rate:{}", inputDataHitRate); } Map ret = new HashMap<>(8); diff --git a/serving-server/src/main/resources/log4j2.xml b/serving-server/src/main/resources/log4j2.xml index bda4d8d4..394719d2 100644 --- a/serving-server/src/main/resources/log4j2.xml +++ b/serving-server/src/main/resources/log4j2.xml @@ -64,10 +64,13 @@ + + + + - \ No newline at end of file From d3517f6051357fdd52009e559e668bf512d81e0d Mon Sep 17 00:00:00 2001 From: kaideng Date: Mon, 10 Feb 2020 20:47:21 +0800 Subject: [PATCH 153/190] change some log Signed-off-by: kaideng --- .../fate/serving/core/bean/GrpcConnectionPool.java | 3 ++- .../webank/ai/fate/serving/utils/InferenceUtils.java | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java index 6bbeed4b..37dbf2bc 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java @@ -102,7 +102,6 @@ private boolean needAddChannel(ChannelResource channelResource){ int channelSize = channelResource.getChannels().size(); long now = System.currentTimeMillis(); long loadFactor = ((requestCount - preCount) * 1000) / (channelSize * (now - latestTimestamp)); - //System.out.println("channel size : "+channelSize+" load factor : "+loadFactor +" time "+(now-latestTimestamp)+" req "+(requestCount - preCount) ); channelResource.setLatestChecktimestamp(now); channelResource.setPreCheckCount(requestCount); if(channelSize>maxTotalPerAddress){ @@ -134,6 +133,8 @@ public void run() { poolMap.forEach((k, v) -> { try { + logger.info("grpc pool {} channel size {} req count {}",k,v.getChannels().size(),v.getRequestCount().get()-v.getPreCheckCount()); + if (needAddChannel(v)) { String[] ipPort = k.split(":"); String ip = ipPort[0]; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/utils/InferenceUtils.java b/serving-server/src/main/java/com/webank/ai/fate/serving/utils/InferenceUtils.java index fb65ea2c..7df4ef76 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/utils/InferenceUtils.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/utils/InferenceUtils.java @@ -43,12 +43,12 @@ public static String generateSeqno() { } public static void logInference(Context context, Enum inferenceType, FederatedParty federatedParty, FederatedRoles federatedRoles, String caseid, String seqno, int retcode, long elapsed, boolean getRemotePartyResult, boolean billing, Map inferenceRequest, ReturnResult inferenceResult) { - inferenceAuditLogger.info("{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}", GetSystemInfo.localIp, inferenceType, federatedParty.getRole(), federatedParty.getPartyId(), FederatedUtils.federatedRolesIdentificationString(federatedRoles), caseid, seqno, retcode, elapsed, getRemotePartyResult ? 1 : 0, billing ? 1 : 0, context.isHitCache()); - Map inferenceLog = new HashMap<>(8); - inferenceLog.put(Dict.INFERENCE_REQUEST, inferenceRequest); - inferenceLog.put(Dict.INFERENCE_RESULT, ObjectTransform.bean2Json(inferenceResult)); - String inferenceLogBase64String = Base64.getEncoder().encodeToString(ObjectTransform.bean2Json(inferenceLog).getBytes()); - inferenceLogger.info("{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}", GetSystemInfo.localIp, inferenceType, federatedParty.getRole(), federatedParty.getPartyId(), FederatedUtils.federatedRolesIdentificationString(federatedRoles), caseid, seqno, retcode, elapsed, getRemotePartyResult ? 1 : 0, billing ? 1 : 0, context.isHitCache(), inferenceLogBase64String); +// inferenceAuditLogger.info("{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}", GetSystemInfo.localIp, inferenceType, federatedParty.getRole(), federatedParty.getPartyId(), FederatedUtils.federatedRolesIdentificationString(federatedRoles), caseid, seqno, retcode, elapsed, getRemotePartyResult ? 1 : 0, billing ? 1 : 0, context.isHitCache()); +// Map inferenceLog = new HashMap<>(8); +// inferenceLog.put(Dict.INFERENCE_REQUEST, inferenceRequest); +// inferenceLog.put(Dict.INFERENCE_RESULT, ObjectTransform.bean2Json(inferenceResult)); +// String inferenceLogBase64String = Base64.getEncoder().encodeToString(ObjectTransform.bean2Json(inferenceLog).getBytes()); +// inferenceLogger.info("{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}", GetSystemInfo.localIp, inferenceType, federatedParty.getRole(), federatedParty.getPartyId(), FederatedUtils.federatedRolesIdentificationString(federatedRoles), caseid, seqno, retcode, elapsed, getRemotePartyResult ? 1 : 0, billing ? 1 : 0, context.isHitCache(), inferenceLogBase64String); } public static Object getClassByName(String classPath) { From 6395c857a7f73415222067d73249f82a4e2c5d8e Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Tue, 11 Feb 2020 11:45:54 +0800 Subject: [PATCH 154/190] update inference parameters Signed-off-by: v_dylanxu <136539068@qq.com> --- README.md | 15 ++++++++++++--- .../proxy/rpc/services/InferenceService.java | 4 ++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a34e20e8..8b373758 100644 --- a/README.md +++ b/README.md @@ -215,11 +215,20 @@ Please use FATE-Flow Client which in the fate-flow to operate, refer to **Online ``` { "head": { - "serviceId": "111111111" - }, + "serviceId": "111111111" + }, "body": { - "device_id": "aaaaa", + "featureData": { + "x0": 1.88669, + "x1": -1.359293, + "x2": 2.303601, + "x3": 2.00137, + "x4": 1.307686 + }, + "sendToRemoteFeatureData": { + "device_id": "aaaaa", "phone_num": "122222222" + } } } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java index 1f02c992..03d0400f 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/services/InferenceService.java @@ -88,9 +88,9 @@ public Map doService(Context context, InboundPackage data, OutboundPackage< Map inferenceReqMap = Maps.newHashMap(); inferenceReqMap.put(Dict.CASE_ID, context.getCaseId()); inferenceReqMap.putAll(reqHeadMap); - inferenceReqMap.put(Dict.FEATURE_DATA, Maps.newHashMap(reqBodyMap)); - int timeWait = timeout; + inferenceReqMap.putAll(reqBodyMap); + int timeWait = timeout; if (logger.isDebugEnabled()) { logger.debug("inference req : {}", JSON.toJSONString(inferenceReqMap)); From 65d53dbe4677b0d59b18d3d634912ca7b849af79 Mon Sep 17 00:00:00 2001 From: kaideng Date: Tue, 11 Feb 2020 15:01:33 +0800 Subject: [PATCH 155/190] add image Signed-off-by: kaideng --- images/fate-serving-step.jpg | Bin 0 -> 33148 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/fate-serving-step.jpg diff --git a/images/fate-serving-step.jpg b/images/fate-serving-step.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bf0dc6758dfe504a2747719909a04211a3af0865 GIT binary patch literal 33148 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 Date: Wed, 12 Feb 2020 11:37:56 +0800 Subject: [PATCH 156/190] feature: add applyId for fdn3. --- .../webank/ai/fate/serving/core/bean/BaseContext.java | 10 ++++++++++ .../com/webank/ai/fate/serving/core/bean/Context.java | 4 ++++ .../com/webank/ai/fate/serving/core/bean/Dict.java | 1 + .../ai/fate/serving/federatedml/model/BaseModel.java | 3 +++ .../ai/fate/serving/proxy/security/AuthUtils.java | 9 +++++++++ .../webank/ai/fate/serving/bean/InferenceRequest.java | 7 +++++++ .../serving/guest/DefaultGuestInferenceProvider.java | 1 + 7 files changed, 35 insertions(+) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java index 2eadc5f0..33e4d416 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/BaseContext.java @@ -314,4 +314,14 @@ public String getServiceId() { public void setServiceId(String serviceId) { dataMap.put(Dict.SERVICE_ID, serviceId); } + + @Override + public String getApplyId() { + return (String) this.dataMap.getOrDefault(Dict.APPLY_ID, ""); + } + + @Override + public void setApplyId(String applyId) { + dataMap.put(Dict.APPLY_ID, applyId); + } } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Context.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Context.java index 9adbf373..c2a6d369 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Context.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Context.java @@ -117,4 +117,8 @@ public default void postProcess(Req req, Resp resp) { public String getServiceId(); public void setServiceId(String serviceId); + + public String getApplyId(); + + public void setApplyId(String applyId); } diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java index 285f9fb6..2f2467bc 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java @@ -146,6 +146,7 @@ public class Dict { public static final String GUEST_APP_ID = "guestAppId"; public static final String HOST_APP_ID = "hostAppId"; public static final String SERVICE_ID = "serviceId"; + public static final String APPLY_ID = "applyId"; public static final String CONFIGPATH = "configpath"; public static final String ACL_ENABLE = "acl.enable"; public static final String ACL_USERNAME = "acl.username"; diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java index d2290c25..d38e9b5a 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/BaseModel.java @@ -179,6 +179,9 @@ protected ReturnResult getFederatedPredictFromRemote(Context context, FederatedP if(context.getServiceId()!=null) { authBuilder.setServiceId( context.getServiceId()); } + if(context.getApplyId()!=null) { + authBuilder.setApplyId( context.getApplyId()); + } packetBuilder.setAuth(authBuilder.build()); GrpcConnectionPool grpcConnectionPool = GrpcConnectionPool.getPool(); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java index 3a28d9fe..f7e76b96 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java @@ -46,6 +46,7 @@ public class AuthUtils implements InitializingBean{ private static Map PARTYID_KEY_MAP = new HashMap<>(); private static int validRequestTimeoutSecond = 10; private static boolean ifUseAuth = false; + private static String applyId = ""; @Autowired private ToStringUtils toStringUtils; @@ -85,6 +86,7 @@ public void loadConfig(){ } } selfPartyId = jsonObject.get("self_party_id").getAsString(); + applyId = jsonObject.get("apply_id").getAsString(); ifUseAuth = jsonObject.get("if_use_auth").getAsBoolean(); validRequestTimeoutSecond = jsonObject.get("request_expire_seconds").getAsInt(); @@ -136,6 +138,13 @@ public Proxy.Packet addAuthInfo(Proxy.Packet packet) throws Exception { authBuilder.setAppKey(appKey); String signature = calSignature(packet.getHeader(), packet.getBody(), timestamp, appKey); authBuilder.setSignature(signature); + authBuilder.setServiceId(packet.getAuth().getServiceId()); + if(! ("".equals(packet.getAuth().getApplyId()))) { + authBuilder.setApplyId(packet.getAuth().getApplyId()); + } + else{ + authBuilder.setApplyId(applyId); + } packetBuilder.setAuth(authBuilder.build()); return packetBuilder.build(); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java b/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java index 98f92dfb..7ec9da20 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/bean/InferenceRequest.java @@ -33,6 +33,13 @@ public class InferenceRequest implements Request { private String caseid; private String serviceId; private Map featureData; + private String applyId; + public String getApplyId() { + return applyId; + } + public void setApplyId(String applyId) { + this.applyId = applyId; + } public String getServiceId() { return serviceId; } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java index d146efdd..36402ffe 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java @@ -72,6 +72,7 @@ public ReturnResult runInference(Context context, InferenceRequest inferenceRequ String modelNamespace = inferenceRequest.getModelId(); String serviceId = inferenceRequest.getServiceId(); context.setServiceId(serviceId); + context.setApplyId(inferenceRequest.getApplyId()); if (StringUtils.isEmpty(modelNamespace) ) { if(StringUtils.isNotEmpty(inferenceRequest.getServiceId())){ From dd4bd3d7bbab0bae5e61679965466e0f8a1576db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lindazheng=28=E9=83=91=E7=90=B3=E7=90=B3=29?= Date: Wed, 12 Feb 2020 17:43:19 +0800 Subject: [PATCH 157/190] change fm log way --- .../fate/serving/federatedml/model/HeteroFM.java | 16 +++++++++++----- .../serving/federatedml/model/HeteroFMGuest.java | 8 ++++++-- .../serving/federatedml/model/HeteroFMHost.java | 5 +++-- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFM.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFM.java index 98753b3d..16fbcc56 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFM.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFM.java @@ -62,8 +62,10 @@ Map forward(List> inputDatas) { int inputDataHitCount = 0; int weightNum = this.weight.size(); int inputFeaturesNum = inputData.size(); - logger.info("model weight number:{}", weightNum); - logger.info("input data features number:{}", inputFeaturesNum); + if(logger.isDebugEnabled()) { + logger.info("model weight number:{}", weightNum); + logger.info("input data features number:{}", inputFeaturesNum); + } double score = 0; for (String key : inputData.keySet()) { @@ -73,7 +75,9 @@ Map forward(List> inputDatas) { score += w * x; modelWeightHitCount += 1; inputDataHitCount += 1; - logger.info("key {} weight is {}, value is {}", key, this.weight.get(key), inputData.get(key)); + if(logger.isDebugEnabled()) { + logger.info("key {} weight is {}, value is {}", key, this.weight.get(key), inputData.get(key)); + } } } @@ -105,8 +109,10 @@ Map forward(List> inputDatas) { ex.printStackTrace(); } - logger.info("model weight hit rate:{}", modelWeightHitRate); - logger.info("input data features hit rate:{}", inputDataHitRate); + if(logger.isDebugEnabled()) { + logger.info("model weight hit rate:{}", modelWeightHitRate); + logger.info("input data features hit rate:{}", inputDataHitRate); + } Map ret = new HashMap<>(); ret.put(Dict.SCORE, score); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMGuest.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMGuest.java index 3a0b7307..1853b4f6 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMGuest.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMGuest.java @@ -45,12 +45,16 @@ public Map handlePredict(Context context, List forwardRet = forward(inputData); double score = new Double(forwardRet.get(Dict.SCORE).toString()); double[] guestCrosses = (double[]) forwardRet.get(Dict.FM_CROSS); - logger.info("caseid {} guest score:{}, cross data:{}", context.getCaseId(), score, guestCrosses); + if(logger.isDebugEnabled()) { + logger.info("caseid {} guest score:{}, cross data:{}", context.getCaseId(), score, guestCrosses); + } try { ReturnResult hostPredictResponse = this.getFederatedPredict(context, predictParams, Dict.FEDERATED_INFERENCE, true); if(hostPredictResponse !=null) { result.put(Dict.RET_CODE,hostPredictResponse.getRetcode()); - logger.info("caseid {} host response is {}",context.getCaseId(),hostPredictResponse.getData()); + if(logger.isDebugEnabled()) { + logger.info("caseid {} host response is {}",context.getCaseId(),hostPredictResponse.getData()); + } if (hostPredictResponse.getData() != null && hostPredictResponse.getData().get(Dict.SCORE) != null) { double hostScore = ((Number) hostPredictResponse.getData().get(Dict.SCORE)).doubleValue(); List hostCrosses = JSON.parseArray(hostPredictResponse.getData().get(Dict.FM_CROSS).toString(),double.class); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMHost.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMHost.java index 7e36ace0..d428c108 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMHost.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMHost.java @@ -32,13 +32,14 @@ public class HeteroFMHost extends HeteroFM { @Override public Map handlePredict(Context context, List> inputData, FederatedParams predictParams) { - logger.info("hetero fm host begin to predict"); HashMap result = new HashMap<>(); Map ret = forward(inputData); result.put(Dict.SCORE, ret.get(Dict.SCORE)); result.put(Dict.FM_CROSS, ret.get(Dict.FM_CROSS)); - logger.info("hetero fm host predict ends, result is {}", result); + if(logger.isDebugEnabled()) { + logger.info("hetero fm host predict ends, result is {}", result); + } return result; } From 0ef4bb8c7b820fff542ffd15cafa4fb913584fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lindazheng=28=E9=83=91=E7=90=B3=E7=90=B3=29?= Date: Wed, 12 Feb 2020 18:18:22 +0800 Subject: [PATCH 158/190] change fm log to dubug level --- .../ai/fate/serving/federatedml/model/HeteroFM.java | 10 +++++----- .../fate/serving/federatedml/model/HeteroFMGuest.java | 4 ++-- .../fate/serving/federatedml/model/HeteroFMHost.java | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFM.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFM.java index 16fbcc56..86012387 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFM.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFM.java @@ -63,8 +63,8 @@ Map forward(List> inputDatas) { int weightNum = this.weight.size(); int inputFeaturesNum = inputData.size(); if(logger.isDebugEnabled()) { - logger.info("model weight number:{}", weightNum); - logger.info("input data features number:{}", inputFeaturesNum); + logger.debug("model weight number:{}", weightNum); + logger.debug("input data features number:{}", inputFeaturesNum); } double score = 0; @@ -76,7 +76,7 @@ Map forward(List> inputDatas) { modelWeightHitCount += 1; inputDataHitCount += 1; if(logger.isDebugEnabled()) { - logger.info("key {} weight is {}, value is {}", key, this.weight.get(key), inputData.get(key)); + logger.debug("key {} weight is {}, value is {}", key, this.weight.get(key), inputData.get(key)); } } } @@ -110,8 +110,8 @@ Map forward(List> inputDatas) { } if(logger.isDebugEnabled()) { - logger.info("model weight hit rate:{}", modelWeightHitRate); - logger.info("input data features hit rate:{}", inputDataHitRate); + logger.debug("model weight hit rate:{}", modelWeightHitRate); + logger.debug("input data features hit rate:{}", inputDataHitRate); } Map ret = new HashMap<>(); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMGuest.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMGuest.java index 1853b4f6..1f3c3c8e 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMGuest.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMGuest.java @@ -46,14 +46,14 @@ public Map handlePredict(Context context, List handlePredict(Context context, List Date: Thu, 13 Feb 2020 11:59:44 +0800 Subject: [PATCH 159/190] add apply_id Signed-off-by: v_dylanxu <136539068@qq.com> --- serving-proxy/src/main/resources/auth_config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/serving-proxy/src/main/resources/auth_config.json b/serving-proxy/src/main/resources/auth_config.json index 1c116398..c89fe2cf 100644 --- a/serving-proxy/src/main/resources/auth_config.json +++ b/serving-proxy/src/main/resources/auth_config.json @@ -1,5 +1,6 @@ { "self_party_id": "9999", + "apply_id": "", "if_use_auth": false, "request_expire_seconds": 8, "access_keys": [{ From 931c2eea97c1b07c83b6aeada79ca2961b8aa400 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Thu, 13 Feb 2020 12:02:27 +0800 Subject: [PATCH 160/190] Revert "add apply_id" This reverts commit c0e4b34b Signed-off-by: v_dylanxu <136539068@qq.com> --- serving-proxy/src/main/resources/auth_config.json | 1 - 1 file changed, 1 deletion(-) diff --git a/serving-proxy/src/main/resources/auth_config.json b/serving-proxy/src/main/resources/auth_config.json index c89fe2cf..1c116398 100644 --- a/serving-proxy/src/main/resources/auth_config.json +++ b/serving-proxy/src/main/resources/auth_config.json @@ -1,6 +1,5 @@ { "self_party_id": "9999", - "apply_id": "", "if_use_auth": false, "request_expire_seconds": 8, "access_keys": [{ From a66fbe7138b7822845171b49fdceed2f16e6515c Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Thu, 13 Feb 2020 14:28:54 +0800 Subject: [PATCH 161/190] ok Signed-off-by: v_dylanxu <136539068@qq.com> --- .../com/webank/ai/fate/serving/proxy/security/AuthUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java index f7e76b96..28818bec 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java @@ -86,7 +86,7 @@ public void loadConfig(){ } } selfPartyId = jsonObject.get("self_party_id").getAsString(); - applyId = jsonObject.get("apply_id").getAsString(); + applyId = jsonObject.get("apply_id") != null ? jsonObject.get("apply_id").getAsString() : ""; ifUseAuth = jsonObject.get("if_use_auth").getAsBoolean(); validRequestTimeoutSecond = jsonObject.get("request_expire_seconds").getAsInt(); From c1a177136c5b9a50d360d067c28fe6799a6e15e9 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Thu, 13 Feb 2020 14:48:02 +0800 Subject: [PATCH 162/190] ok Signed-off-by: v_dylanxu <136539068@qq.com> --- .../com/webank/ai/fate/serving/proxy/security/AuthUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java index f7e76b96..28818bec 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java @@ -86,7 +86,7 @@ public void loadConfig(){ } } selfPartyId = jsonObject.get("self_party_id").getAsString(); - applyId = jsonObject.get("apply_id").getAsString(); + applyId = jsonObject.get("apply_id") != null ? jsonObject.get("apply_id").getAsString() : ""; ifUseAuth = jsonObject.get("if_use_auth").getAsBoolean(); validRequestTimeoutSecond = jsonObject.get("request_expire_seconds").getAsInt(); From 92e07835529a9fa48d1f435a128e96fcfdd2f66b Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Thu, 13 Feb 2020 17:05:40 +0800 Subject: [PATCH 163/190] log bug --- serving-server/src/main/resources/log4j2.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serving-server/src/main/resources/log4j2.xml b/serving-server/src/main/resources/log4j2.xml index 394719d2..8cb720e5 100644 --- a/serving-server/src/main/resources/log4j2.xml +++ b/serving-server/src/main/resources/log4j2.xml @@ -20,7 +20,7 @@ fate serving-server - c + From 58f7a68540cfecf9e6dfb6a7b3df006251fe0818 Mon Sep 17 00:00:00 2001 From: chengjian <1210786785@qq.com> Date: Fri, 14 Feb 2020 10:57:17 +0800 Subject: [PATCH 164/190] ChengJian Test Push --- .../src/main/java/com/webank/ai/fate/serving/ServingServer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index 09377474..a69c1e9f 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -67,6 +67,7 @@ public ServingServer(String confPath) { System.setProperty(Dict.ACL_PASSWORD, Configuration.getProperty(Dict.ACL_PASSWORD, "")); } + //Server Start public static void main(String[] args) { try { Options options = new Options(); From 141268aa87114b183e22ae3064dfa08e940cee2f Mon Sep 17 00:00:00 2001 From: chengjian <1210786785@qq.com> Date: Fri, 14 Feb 2020 11:00:43 +0800 Subject: [PATCH 165/190] remove annotation --- .../src/main/java/com/webank/ai/fate/serving/ServingServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index a69c1e9f..1990f987 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -67,7 +67,7 @@ public ServingServer(String confPath) { System.setProperty(Dict.ACL_PASSWORD, Configuration.getProperty(Dict.ACL_PASSWORD, "")); } - //Server Start + public static void main(String[] args) { try { Options options = new Options(); From b3781ad708c8b4f200dde6b4978dc5c90d5fc22c Mon Sep 17 00:00:00 2001 From: kaideng Date: Fri, 14 Feb 2020 17:50:10 +0800 Subject: [PATCH 166/190] change config Signed-off-by: kaideng --- .../serving/core/bean/GrpcConnectionPool.java | 1 + .../serving/proxy/config/RegistryConfig.java | 2 +- .../serving/proxy/config/WebConfigration.java | 10 +- .../router/ConfigFileBasedServingRouter.java | 46 ++++++-- .../serving/proxy/security/AuthUtils.java | 109 +++++++++++------- .../src/main/resources/application.properties | 50 ++++---- .../webank/ai/fate/serving/ServingServer.java | 4 +- .../guest/DefaultGuestInferenceProvider.java | 2 +- .../AbstractModelLoader.java | 7 +- .../DefaultHttpModelLoader.java | 4 +- .../DefaultModelCache.java | 9 +- .../DefaultModelManager.java | 2 +- .../InferenceWorkerManager.java | 2 +- .../{manger => manager}/ModelLoader.java | 2 +- .../{manger => manager}/ModelUtil.java | 2 +- .../{manger => manager}/RedisCache.java | 2 +- .../ReentrantReadWriteMapPool.java | 2 +- .../ai/fate/serving/service/ModelService.java | 4 +- 18 files changed, 153 insertions(+), 107 deletions(-) rename serving-server/src/main/java/com/webank/ai/fate/serving/{manger => manager}/AbstractModelLoader.java (95%) rename serving-server/src/main/java/com/webank/ai/fate/serving/{manger => manager}/DefaultHttpModelLoader.java (97%) rename serving-server/src/main/java/com/webank/ai/fate/serving/{manger => manager}/DefaultModelCache.java (90%) rename serving-server/src/main/java/com/webank/ai/fate/serving/{manger => manager}/DefaultModelManager.java (99%) rename serving-server/src/main/java/com/webank/ai/fate/serving/{manger => manager}/InferenceWorkerManager.java (98%) rename serving-server/src/main/java/com/webank/ai/fate/serving/{manger => manager}/ModelLoader.java (84%) rename serving-server/src/main/java/com/webank/ai/fate/serving/{manger => manager}/ModelUtil.java (97%) rename serving-server/src/main/java/com/webank/ai/fate/serving/{manger => manager}/RedisCache.java (95%) rename serving-server/src/main/java/com/webank/ai/fate/serving/{manger => manager}/ReentrantReadWriteMapPool.java (98%) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java index 37dbf2bc..0436154e 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/GrpcConnectionPool.java @@ -149,6 +149,7 @@ public void run() { fireChannelError(k,state); } } catch (Exception ex) { + ex.printStackTrace(); logger.error("channel {} check status error", k); } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java index fa5e7a43..53f48963 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java @@ -46,7 +46,7 @@ public class RegistryConfig { @Value("${useZkRouter:true}") private String useZkRouter; - @Value("${acl.enable}") + @Value("${acl.enable:false}") private String aclEnable; @Value("${acl.username}") diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java index 0c506f70..8daabe23 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java @@ -20,6 +20,7 @@ public class WebConfigration implements WebMvcConfigurer { + int processors = Runtime.getRuntime().availableProcessors(); private final Logger logger = LoggerFactory.getLogger(WebConfigration.class); @@ -29,19 +30,18 @@ public class WebConfigration implements WebMvcConfigurer { @Value("${proxy.async.timeout:5000}") long timeout; - @Value("${proxy.async.coresize:10}") + @Value("${proxy.async.coresize}") int coreSize; - @Value("${proxy.async.maxsize:100}") + @Value("${proxy.async.maxsize}") int maxSize; @Override public void configureAsyncSupport(AsyncSupportConfigurer configurer) { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(coreSize); - executor.setMaxPoolSize(maxSize); + executor.setCorePoolSize(coreSize>0?coreSize:processors); + executor.setMaxPoolSize(maxSize>0?maxSize:2*processors); executor.setThreadNamePrefix("ProxyAsync"); - executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); executor.initialize(); configurer.setTaskExecutor(executor); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java index be8dd13c..21dc7b54 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java @@ -23,6 +23,7 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; @@ -37,9 +38,15 @@ public class ConfigFileBasedServingRouter extends BaseServingRouter implements private RouteType routeType; - @Value("${route.table:conf/route_table.json}") + private String userDir=System.getProperty(Dict.PROPERTY_USER_DIR); + + @Value("${route.table}") private String routeTableFile; + private final String DEFAULT_ROUTER_FILE = "conf/route_table.json"; + + private final String fileSeparator = System.getProperty("file.separator"); + @Value("${coordinator:9999}") private String selfCoordinator; @@ -220,21 +227,33 @@ private boolean hasRule(Map> target, Proxy.Topic f @Scheduled(fixedRate = 10000) public void loadRouteTable() { - logger.debug("start refreshed route table..."); + + + String filePath = ""; + if(StringUtils.isNotEmpty(routeTableFile)) { + filePath = routeTableFile; + }else{ + filePath = userDir+this.fileSeparator+DEFAULT_ROUTER_FILE; + } + logger.info("start refreshed route table...,try to load {}",filePath); + + File file = new File(filePath); + if(!file.exists()){ + logger.error("router table {} is not exist",filePath); + return; + } String fileMd5 = FileUtils.fileMd5(routeTableFile); if(null != fileMd5 && fileMd5.equals(lastFileMd5)){ return; } - lastFileMd5 = fileMd5; - JsonParser jsonParser = new JsonParser(); JsonReader jsonReader = null; JsonObject confJson = null; try { jsonReader = new JsonReader(new FileReader(routeTableFile)); confJson = jsonParser.parse(jsonReader).getAsJsonObject(); - } catch (FileNotFoundException e) { - logger.error("File not found: {}", routeTableFile); + } catch (Exception e) { + logger.error("parse router table error: {}", routeTableFile); throw new RuntimeException(e); } finally { if (jsonReader != null) { @@ -247,10 +266,8 @@ public void loadRouteTable() { } initRouteTable(confJson.getAsJsonObject("route_table")); initPermission(confJson.getAsJsonObject("permission")); - - if (logger.isDebugEnabled()) { - logger.debug("refreshed route table at: {}", routeTableFile); - } + logger.info("refreshed route table at: {}", routeTableFile); + lastFileMd5 = fileMd5; } @@ -385,4 +402,13 @@ private Proxy.Topic createTopicFromJson(JsonObject json) { return topicBuilder.build(); } + + + public static void main(String[] args){ + + System.err.println(System.getProperty("java.class.path")); + System.err.println(System.getenv()); + System.err.println(System.getProperty("user.dir")); + System.err.println(System.getProperty("file.separator")); + } } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java index 28818bec..c7028899 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java @@ -22,6 +22,7 @@ import com.google.gson.JsonParser; import com.google.gson.stream.JsonReader; import com.webank.ai.fate.api.networking.proxy.Proxy; +import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.core.utils.EncryptUtils; import com.webank.ai.fate.serving.proxy.utils.FileUtils; import com.webank.ai.fate.serving.proxy.utils.ToStringUtils; @@ -34,6 +35,7 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; +import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; @@ -48,59 +50,83 @@ public class AuthUtils implements InitializingBean{ private static boolean ifUseAuth = false; private static String applyId = ""; + private final String userDir = System.getProperty(Dict.PROPERTY_USER_DIR); + + private final String DEFAULT_AUTH_FILE="conf/auth_config.json"; + + @Autowired private ToStringUtils toStringUtils; - @Value("${auth.file:conf/auth_config.json}") + @Value("${auth.file}") private String confFilePath; + @Value("${auth.open:false}") + private String openAuth; + + private final String fileSeparator = System.getProperty("file.separator"); - @Value("${coordinator:9999}") + + @Value("${coordinator}") private String selfPartyId; - private String lastFileMd5; + private String lastFileMd5=""; @Scheduled(fixedRate = 10000) public void loadConfig(){ - logger.debug("start refreshed auth config..."); - String fileMd5 = FileUtils.fileMd5(confFilePath); - if(null != fileMd5 && fileMd5.equals(lastFileMd5)){ - return; - } - lastFileMd5 = fileMd5; + if(Boolean.valueOf(openAuth)) { + logger.debug("start refreshed auth config..."); + String filePath = ""; + if (StringUtils.isNotEmpty(confFilePath)) { + filePath = confFilePath; + } else { + filePath = userDir + this.fileSeparator + DEFAULT_AUTH_FILE; + } - JsonParser jsonParser = new JsonParser(); - JsonReader jsonReader = null; - JsonObject jsonObject = null; - try { - jsonReader = new JsonReader(new FileReader(confFilePath)); - jsonObject = jsonParser.parse(jsonReader).getAsJsonObject(); - } catch (FileNotFoundException e) { - logger.error("File not found: {}", confFilePath); - throw new RuntimeException(e); - } finally { - if (jsonReader != null) { - try { - jsonReader.close(); - } catch (IOException ignore) { + String fileMd5 = FileUtils.fileMd5(filePath); + if (null != fileMd5 && fileMd5.equals(lastFileMd5)) { + return; + } + File file = new File(filePath); + if (!file.exists()) { + logger.error("auth table {} is not exist", filePath); + return; + } + + JsonParser jsonParser = new JsonParser(); + JsonReader jsonReader = null; + JsonObject jsonObject = null; + try { + jsonReader = new JsonReader(new FileReader(filePath)); + jsonObject = jsonParser.parse(jsonReader).getAsJsonObject(); + } catch (FileNotFoundException e) { + logger.error("File not found: {}", filePath); + throw new RuntimeException(e); + } finally { + if (jsonReader != null) { + try { + jsonReader.close(); + } catch (IOException ignore) { + } } } - } - selfPartyId = jsonObject.get("self_party_id").getAsString(); - applyId = jsonObject.get("apply_id") != null ? jsonObject.get("apply_id").getAsString() : ""; - ifUseAuth = jsonObject.get("if_use_auth").getAsBoolean(); - validRequestTimeoutSecond = jsonObject.get("request_expire_seconds").getAsInt(); - - JsonArray jsonArray = jsonObject.getAsJsonArray("access_keys"); - Gson gson = new Gson(); - List allowKeys = gson.fromJson(jsonArray, ArrayList.class); - KEY_SECRET_MAP.clear(); - for (Map allowKey : allowKeys) { - KEY_SECRET_MAP.put(allowKey.get("app_key").toString(), allowKey.get("app_secret").toString()); - PARTYID_KEY_MAP.put(allowKey.get("party_id").toString(), allowKey.get("app_key").toString()); - } + selfPartyId = jsonObject.get("self_party_id").getAsString(); + applyId = jsonObject.get("apply_id") != null ? jsonObject.get("apply_id").getAsString() : ""; + ifUseAuth = jsonObject.get("if_use_auth").getAsBoolean(); + validRequestTimeoutSecond = jsonObject.get("request_expire_seconds").getAsInt(); + + JsonArray jsonArray = jsonObject.getAsJsonArray("access_keys"); + Gson gson = new Gson(); + List allowKeys = gson.fromJson(jsonArray, ArrayList.class); + KEY_SECRET_MAP.clear(); + for (Map allowKey : allowKeys) { + KEY_SECRET_MAP.put(allowKey.get("app_key").toString(), allowKey.get("app_secret").toString()); + PARTYID_KEY_MAP.put(allowKey.get("party_id").toString(), allowKey.get("app_key").toString()); + } - if (logger.isDebugEnabled()) { - logger.debug("refreshed auth cfg using file {}.", confFilePath); + if (logger.isDebugEnabled()) { + logger.debug("refreshed auth cfg using file {}.", filePath); + } + lastFileMd5 = fileMd5; } } @@ -128,7 +154,7 @@ private String calSignature(Proxy.Metadata header, Proxy.Data body, long timesta } public Proxy.Packet addAuthInfo(Proxy.Packet packet) throws Exception { - if(ifUseAuth && !StringUtils.equals(selfPartyId, packet.getHeader().getDst().getPartyId())) { + if(Boolean.valueOf(openAuth) && !StringUtils.equals(selfPartyId, packet.getHeader().getDst().getPartyId())) { Proxy.Packet.Builder packetBuilder = packet.toBuilder(); Proxy.AuthInfo.Builder authBuilder = packetBuilder.getAuthBuilder(); @@ -153,7 +179,7 @@ public Proxy.Packet addAuthInfo(Proxy.Packet packet) throws Exception { } public boolean checkAuthentication(Proxy.Packet packet) throws Exception { - if(ifUseAuth) { + if(Boolean.valueOf(openAuth)) { // check timestamp long currentTimeMillis = System.currentTimeMillis(); long requestTimeMillis = packet.getAuth().getTimestamp(); @@ -174,7 +200,6 @@ public boolean checkAuthentication(Proxy.Packet packet) throws Exception { @Override public void afterPropertiesSet() throws Exception { - lastFileMd5 = ""; try { loadConfig(); } catch (Throwable e) { diff --git a/serving-proxy/src/main/resources/application.properties b/serving-proxy/src/main/resources/application.properties index ce588aa4..f60c24f6 100644 --- a/serving-proxy/src/main/resources/application.properties +++ b/serving-proxy/src/main/resources/application.properties @@ -1,46 +1,42 @@ # same as partyid coordinator=9999 - -server.port=8081 - -# actuator -management.server.port=10087 -management.endpoints.web.exposure.include=health,info,metrics +server.port=8059 +#inference.service.name=serving +## actuator +#management.server.port=10087 +#management.endpoints.web.exposure.include=health,info,metrics #random, consistent -routeType=random +#routeType=random -route.table=/data/projects/fate-serving/serving-proxy/conf/route_table.json -auth.file=/data/projects/fate-serving/serving-proxy/conf/auth_config.json +#route.table=/data/projects/fate-serving/serving-proxy/conf/route_table.json +#auth.file=/data/projects/fate-serving/serving-proxy/conf/auth_config.json -useZkRouter=false +#useZkRouter=true zk.url=zookeeper://localhost:2181 # zk acl -acl.enable=false -acl.username= -acl.password= +#acl.enable=false +#acl.username= +#acl.password= # intra-partyid port -proxy.grpc.intra.port=8867 - +proxy.grpc.intra.port=8879 # inter-partyid port proxy.grpc.inter.port=8869 -proxy.grpc.inference.timeout=3000 -proxy.grpc.inference.async.timeout=1000 -proxy.grpc.unaryCall.timeout=3000 +#proxy.grpc.inference.timeout=3000 +#proxy.grpc.inference.async.timeout=1000 +#proxy.grpc.unaryCall.timeout=3000 + -inference.service.name=serving -proxy.grpc.threadpool.coresize=50 -proxy.grpc.threadpool.maxsize=100 -proxy.grpc.threadpool.queuesize=10 +#proxy.grpc.threadpool.coresize=50 +#proxy.grpc.threadpool.maxsize=100 +#proxy.grpc.threadpool.queuesize=10 -proxy.async.timeout=5000 -proxy.async.coresize=10 -proxy.async.maxsize=100 +#proxy.async.timeout=5000 +#proxy.async.coresize=10 +#proxy.async.maxsize=100 -proxy.grpc.pool.maxTotal=64 -proxy.grpc.pool.maxIdle=64 diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index 09377474..618e6d5b 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -30,7 +30,7 @@ import com.webank.ai.fate.serving.core.bean.Configuration; import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.federatedml.model.BaseModel; -import com.webank.ai.fate.serving.manger.InferenceWorkerManager; +import com.webank.ai.fate.serving.manager.InferenceWorkerManager; import com.webank.ai.fate.serving.service.*; import com.webank.ai.fate.serving.utils.HttpClientPool; import io.grpc.Server; @@ -44,7 +44,6 @@ import org.springframework.context.ApplicationContext; import java.io.File; -import java.io.IOException; import java.util.HashMap; import java.util.Set; import java.util.concurrent.*; @@ -107,6 +106,7 @@ private void start(String[] args) throws Exception { new SynchronousQueue(), new NamedThreadFactory("ServingServer", true)); FateServerBuilder serverBuilder = (FateServerBuilder) ServerBuilder.forPort(port); + serverBuilder.keepAliveTime(100,TimeUnit.MILLISECONDS); serverBuilder.executor(executor); //new ServiceOverloadProtectionHandle() serverBuilder.addService(ServerInterceptors.intercept(applicationContext.getBean(InferenceService.class), new ServiceExceptionHandler(), new ServiceOverloadProtectionHandle()), InferenceService.class); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java index 36402ffe..06d62407 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java @@ -29,7 +29,7 @@ import com.webank.ai.fate.serving.core.utils.ObjectTransform; import com.webank.ai.fate.serving.federatedml.PipelineTask; import com.webank.ai.fate.serving.interfaces.ModelManager; -import com.webank.ai.fate.serving.manger.InferenceWorkerManager; +import com.webank.ai.fate.serving.manager.InferenceWorkerManager; import com.webank.ai.fate.serving.utils.InferenceUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/AbstractModelLoader.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/AbstractModelLoader.java similarity index 95% rename from serving-server/src/main/java/com/webank/ai/fate/serving/manger/AbstractModelLoader.java rename to serving-server/src/main/java/com/webank/ai/fate/serving/manager/AbstractModelLoader.java index 0fa1bfdb..e2048fbf 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/AbstractModelLoader.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/AbstractModelLoader.java @@ -1,11 +1,10 @@ -package com.webank.ai.fate.serving.manger; +package com.webank.ai.fate.serving.manager; import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.bean.Dict; import com.webank.ai.fate.serving.core.exceptions.ModelSerializeException; import com.webank.ai.fate.serving.federatedml.PipelineTask; -import com.webank.ai.fate.serving.service.ModelService; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,10 +12,6 @@ import java.io.*; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; public abstract class AbstractModelLoader implements ModelLoader { diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultHttpModelLoader.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultHttpModelLoader.java similarity index 97% rename from serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultHttpModelLoader.java rename to serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultHttpModelLoader.java index 1a4d0ccf..70510cbc 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultHttpModelLoader.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultHttpModelLoader.java @@ -14,12 +14,11 @@ * limitations under the License. */ -package com.webank.ai.fate.serving.manger; +package com.webank.ai.fate.serving.manager; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.google.common.collect.Maps; -import com.webank.ai.fate.api.mlmodel.manager.ModelServiceProto; import com.webank.ai.fate.register.router.RouterService; import com.webank.ai.fate.register.url.URL; import com.webank.ai.fate.serving.core.bean.*; @@ -31,7 +30,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; import java.util.*; @Component diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelCache.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultModelCache.java similarity index 90% rename from serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelCache.java rename to serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultModelCache.java index f467533d..9bd45dd1 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelCache.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultModelCache.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.webank.ai.fate.serving.manger; +package com.webank.ai.fate.serving.manager; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; @@ -31,7 +31,6 @@ import java.util.Set; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; @Service public class DefaultModelCache implements ModelCache { @@ -57,7 +56,11 @@ public PipelineTask load(String s) throws Exception { @Override public PipelineTask loadModel(Context context, String modelKey) { String[] modelKeyFields = ModelUtil.splitModelKey(modelKey); - return modelLoader.loadModel(context,modelKeyFields[0], modelKeyFields[1]); + PipelineTask pipelineTask = modelLoader.loadModel(context,modelKeyFields[0], modelKeyFields[1]); + if(pipelineTask==null){ + logger.error("load model {} error,model loader return null",modelKey); + } + return pipelineTask; } @Override diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultModelManager.java similarity index 99% rename from serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java rename to serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultModelManager.java index 5e342100..884b6b3b 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/DefaultModelManager.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultModelManager.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.webank.ai.fate.serving.manger; +package com.webank.ai.fate.serving.manager; import com.webank.ai.fate.register.provider.FateServer; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/InferenceWorkerManager.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/InferenceWorkerManager.java similarity index 98% rename from serving-server/src/main/java/com/webank/ai/fate/serving/manger/InferenceWorkerManager.java rename to serving-server/src/main/java/com/webank/ai/fate/serving/manager/InferenceWorkerManager.java index f61493ce..0aeef351 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/InferenceWorkerManager.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/InferenceWorkerManager.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.webank.ai.fate.serving.manger; +package com.webank.ai.fate.serving.manager; import com.webank.ai.fate.serving.core.bean.Configuration; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelLoader.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/ModelLoader.java similarity index 84% rename from serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelLoader.java rename to serving-server/src/main/java/com/webank/ai/fate/serving/manager/ModelLoader.java index 3656dea4..4d86dfeb 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelLoader.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/ModelLoader.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.manger; +package com.webank.ai.fate.serving.manager; import com.webank.ai.fate.serving.federatedml.PipelineTask; import com.webank.ai.fate.serving.core.bean.Context; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtil.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/ModelUtil.java similarity index 97% rename from serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtil.java rename to serving-server/src/main/java/com/webank/ai/fate/serving/manager/ModelUtil.java index 551cc8f2..11ad92cd 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ModelUtil.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/ModelUtil.java @@ -1,4 +1,4 @@ -package com.webank.ai.fate.serving.manger; +package com.webank.ai.fate.serving.manager; import com.webank.ai.fate.api.mlmodel.manager.ModelServiceProto; import com.webank.ai.fate.serving.core.bean.FederatedRoles; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/RedisCache.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/RedisCache.java similarity index 95% rename from serving-server/src/main/java/com/webank/ai/fate/serving/manger/RedisCache.java rename to serving-server/src/main/java/com/webank/ai/fate/serving/manager/RedisCache.java index 7295eb50..67e50e46 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/RedisCache.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/RedisCache.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.webank.ai.fate.serving.manger; +package com.webank.ai.fate.serving.manager; import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.interfaces.Cache; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ReentrantReadWriteMapPool.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/ReentrantReadWriteMapPool.java similarity index 98% rename from serving-server/src/main/java/com/webank/ai/fate/serving/manger/ReentrantReadWriteMapPool.java rename to serving-server/src/main/java/com/webank/ai/fate/serving/manager/ReentrantReadWriteMapPool.java index 63855723..ddafc586 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manger/ReentrantReadWriteMapPool.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/ReentrantReadWriteMapPool.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.webank.ai.fate.serving.manger; +package com.webank.ai.fate.serving.manager; import com.webank.ai.fate.serving.core.bean.BaseMapPool; diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java index b66ce143..71219b34 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/service/ModelService.java @@ -29,7 +29,7 @@ import com.webank.ai.fate.serving.core.bean.*; import com.webank.ai.fate.serving.core.utils.ObjectTransform; import com.webank.ai.fate.serving.interfaces.ModelManager; -import com.webank.ai.fate.serving.manger.ModelUtil; +import com.webank.ai.fate.serving.manager.ModelUtil; import io.grpc.stub.StreamObserver; import org.apache.commons.codec.digest.Md5Crypt; @@ -408,4 +408,6 @@ public void afterPropertiesSet() throws Exception { } + + } From 0e54bd31e48c7726591809d43d73635eeea9643f Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Fri, 14 Feb 2020 19:15:48 +0800 Subject: [PATCH 167/190] optimization dependency management Signed-off-by: v_dylanxu <136539068@qq.com> --- fate-metrics-micrometer/pom.xml | 13 +- fate-serving-core/pom.xml | 49 +-- .../serving/core/utils/ProtobufUtils.java | 2 - federatedml/pom.xml | 23 +- pom.xml | 389 ++++++++++++++++-- register/.classpath | 43 -- register/.project | 23 -- register/.settings/org.eclipse.jdt.core.prefs | 5 - register/pom.xml | 98 +---- serving-proxy/pom.xml | 203 +-------- serving-server/pom.xml | 195 ++------- 11 files changed, 413 insertions(+), 630 deletions(-) delete mode 100644 register/.classpath delete mode 100644 register/.project delete mode 100644 register/.settings/org.eclipse.jdt.core.prefs diff --git a/fate-metrics-micrometer/pom.xml b/fate-metrics-micrometer/pom.xml index e7166d2d..3986c50f 100644 --- a/fate-metrics-micrometer/pom.xml +++ b/fate-metrics-micrometer/pom.xml @@ -14,22 +14,11 @@ jar - - org.springframework.boot - spring-boot-starter-actuator - ${spring.boot.version} - org.springframework.boot spring-boot-autoconfigure - ${spring.boot.version} - - - org.springframework.boot - spring-boot-configuration-processor - ${spring.boot.version} - true + com.webank.ai.fate fate-metrics-api diff --git a/fate-serving-core/pom.xml b/fate-serving-core/pom.xml index 3c0d0eb2..087b6cac 100644 --- a/fate-serving-core/pom.xml +++ b/fate-serving-core/pom.xml @@ -26,66 +26,21 @@ fate-serving-core - - 2.10.0 - 2.9.0 - - - - com.alibaba - fastjson - 1.2.25 - - - - - org.json - json - 20151123 - compile - - - - - com.fasterxml.jackson.core - jackson-annotations - ${jackson.version} - - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - - redis.clients jedis - ${jedis.version} - - - commons-codec - commons-codec - 1.12 + io.dropwizard.metrics metrics-core - 4.1.0-rc2 + com.alibaba.csp sentinel-core - 1.6.3 - diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/ProtobufUtils.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/ProtobufUtils.java index 339c1da0..ba9eb8c4 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/ProtobufUtils.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/utils/ProtobufUtils.java @@ -16,7 +16,6 @@ package com.webank.ai.fate.serving.core.utils; -import com.webank.ai.fate.core.mlmodel.buffer.DefaultEmptyFillProto; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,7 +32,6 @@ public static T parseProtoObject(com.google.protobuf.Parser protoParser, return messageV3; } catch (Exception ex1) { try { - DefaultEmptyFillProto.DefaultEmptyFillMessage defaultEmptyFillMessage = DefaultEmptyFillProto.DefaultEmptyFillMessage.parseFrom(protoString); messageV3 = protoParser.parseFrom(new byte[0]); if (logger.isDebugEnabled()) { logger.debug("parse {} proto object with default values", messageV3.getClass().getSimpleName()); diff --git a/federatedml/pom.xml b/federatedml/pom.xml index 1de3cd2c..3cb0032e 100644 --- a/federatedml/pom.xml +++ b/federatedml/pom.xml @@ -22,26 +22,18 @@ com.webank.ai.fate ${fate.version} + 4.0.0 fate-serving-federatedml - + com.webank.ai.fate fate-serving-core ${project.version} - - org.json - json - 20151123 - - - com.alibaba - fastjson - 1.2.58 - + com.webank.ai.fate fate-register @@ -49,5 +41,12 @@ - + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + + + diff --git a/pom.xml b/pom.xml index fe5278f6..66423a81 100644 --- a/pom.xml +++ b/pom.xml @@ -37,12 +37,12 @@ 1.2.0 0.3 - 8 + 1.8 UTF-8 UTF-8 - 27.0.1-jre - 1.16.1 + 27.1-jre + 1.25.0 3.6.1 4.12 2.11.1 @@ -52,10 +52,14 @@ 1.6.1 2.2.0.RELEASE + 2.10.0 + 8.0.13 + 2.9.0 + 1.6.3 + 4.1.2 - - + org.springframework spring-core @@ -66,18 +70,19 @@ spring-beans ${spring.version} + org.springframework spring-context ${spring.version} + org.springframework spring-tx ${spring.version} - javax.annotation javax.annotation-api @@ -95,23 +100,19 @@ log4j-api ${log4j.version} + org.apache.logging.log4j log4j-core ${log4j.version} + org.apache.logging.log4j log4j-slf4j-impl ${log4j.version} - - com.lmax - disruptor - 3.4.2 - - com.google.guava guava @@ -131,9 +132,9 @@ - org.apache.commons - commons-text - 1.4 + commons-codec + commons-codec + 1.12 @@ -142,15 +143,71 @@ 2.6 + + org.apache.commons + commons-text + 1.4 + + com.google.protobuf protobuf-java-util ${protobuf.version} + + org.json + json + 20151123 + compile + + + + com.alibaba + fastjson + 1.2.60 + + + + org.projectlombok + lombok + 1.18.0 + provided + + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + + com.google.flatbuffers + flatbuffers-java + 1.10.0 + + + + com.google.flatbuffers + flatbuffers-java-grpc + 1.10.0 + + io.grpc - grpc-stub + grpc-core + ${grpc.version} + + + + io.grpc + grpc-netty-shaded ${grpc.version} @@ -162,12 +219,229 @@ io.grpc - grpc-netty-shaded + grpc-stub ${grpc.version} + + org.apache.httpcomponents + httpclient + 4.5.7 + + + + org.springframework.boot + spring-boot-configuration-processor + ${spring.boot.version} + true + + + + org.springframework.boot + spring-boot-starter-actuator + ${spring.boot.version} + + + + + + ch.ethz.ganymed + ganymed-ssh2 + 262 + + + + org.springframework.boot + spring-boot-starter + ${spring.boot.version} + + + + org.springframework.boot + spring-boot-loader-tools + ${spring.boot.version} + + + + org.springframework.boot + spring-boot-autoconfigure + ${spring.boot.version} + + + + org.springframework.boot + spring-boot-starter-webflux + ${spring.boot.version} + + + + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} + + + + org.springframework.boot + spring-boot-starter-test + ${spring.boot.version} + test + + + + + + io.dropwizard.metrics + metrics-core + ${dropwizard.metrics.version} + + + + io.dropwizard.metrics + metrics-jmx + ${dropwizard.metrics.version} + + + + redis.clients + jedis + ${jedis.version} + + + + com.alibaba.csp + sentinel-core + ${sentinel.version} + + + + com.alibaba.csp + sentinel-datasource-extension + ${sentinel.version} + + + + com.alibaba.csp + sentinel-transport-simple-http + ${sentinel.version} + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + 2.1.0.RELEASE + + + + org.apache.zookeeper + zookeeper + 3.5.5 + + + + org.apache.commons + commons-collections4 + 4.4 + + + + org.apache.curator + curator-framework + 2.13.0 + + + + org.apache.curator + curator-recipes + 2.13.0 + + + + commons-lang + commons-lang + 2.6 + + + + com.lmax + disruptor + 3.4.2 + + + + com.googlecode.json-simple + json-simple + 1.1 + + + + org.jolokia + jolokia-core + 1.6.1 + + + + org.aspectj + aspectjrt + 1.9.1 + + + + org.aspectj + aspectjweaver + 1.9.1 + + + + cglib + cglib + 2.2.2 + + + + org.apache.commons + commons-pool2 + 2.6.2 + + + + @@ -178,29 +452,64 @@ + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + ${protobuf-maven-plugin.version} + + com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} + + grpc-java + io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} + + ${project.basedir}/../proto + + + + + compile + compile-custom + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.2.1 + + + ${project.basedir}/package.xml + + + + + make-assembly + package + + single + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + true + true + + + + + - - org.xolstice.maven.plugins - protobuf-maven-plugin - ${protobuf-maven-plugin.version} - - com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} - - grpc-java - io.grpc:protoc-gen-grpc-java:1.16.1:exe:${os.detected.classifier} - - ${project.basedir}/../proto - - - - - compile - compile-custom - - - - org.apache.maven.plugins maven-compiler-plugin @@ -242,10 +551,6 @@ - - - - diff --git a/register/.classpath b/register/.classpath deleted file mode 100644 index 71bf931b..00000000 --- a/register/.classpath +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/register/.project b/register/.project deleted file mode 100644 index a53f4cfd..00000000 --- a/register/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - fate-register - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.m2e.core.maven2Nature - - diff --git a/register/.settings/org.eclipse.jdt.core.prefs b/register/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 714351ae..00000000 --- a/register/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,5 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 -org.eclipse.jdt.core.compiler.compliance=1.8 -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=1.8 diff --git a/register/pom.xml b/register/pom.xml index 0a8e29cf..9d07d440 100644 --- a/register/pom.xml +++ b/register/pom.xml @@ -20,7 +20,6 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> fate-register - fate-serving @@ -29,23 +28,13 @@ jar - 4.0.0 - - - - 2.10.0.pr1 - 8.0.13 - 1.3.7 - 0.6.1 - + 4.0.0 - org.apache.zookeeper zookeeper - 3.5.5 slf4j-log4j12 @@ -57,110 +46,27 @@ org.apache.commons commons-collections4 - 4.4 + org.apache.curator curator-framework - 2.13.0 org.apache.curator curator-recipes - 2.13.0 - - - - - - - - - - - - io.grpc - grpc-stub - 1.19.0 - - - - io.grpc - grpc-protobuf - 1.19.0 - - - - io.grpc - grpc-netty-shaded - 1.19.0 - - - - commons-io - commons-io - 2.4 commons-lang commons-lang - 2.6 - - com.alibaba - fastjson - 1.2.25 - - - - - org.apache.logging.log4j - log4j-api - 2.11.1 - - - org.apache.logging.log4j - log4j-core - 2.11.1 - com.lmax disruptor - 3.4.2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/serving-proxy/pom.xml b/serving-proxy/pom.xml index 2facebe5..1b481212 100644 --- a/serving-proxy/pom.xml +++ b/serving-proxy/pom.xml @@ -18,16 +18,34 @@ fate-serving-proxy + + com.webank.ai.fate + fate-serving-core + ${fate.version} + compile + + com.webank.ai.fate fate-register ${fate.version} + + com.webank.ai.fate + fate-metrics-api + ${fate.version} + + + + com.webank.ai.fate + fate-metrics-micrometer + ${fate.version} + + org.apache.zookeeper zookeeper - 3.5.5 slf4j-log4j12 @@ -36,22 +54,14 @@ - - com.googlecode.json-simple json-simple - 1.1 org.springframework.boot spring-boot-starter-webflux - ${spring.boot.version} org.springframework.boot @@ -63,124 +73,46 @@ org.jolokia jolokia-core - 1.6.1 - - - org.apache.commons - commons-lang3 - 3.9 - - - - - - commons-codec - commons-codec - 1.12 - - - - - io.grpc - grpc-core - 1.25.0 - - - io.grpc - grpc-netty-shaded - 1.25.0 - - - - - com.alibaba - fastjson - 1.2.58 org.springframework.boot spring-boot-starter-web - ${spring.boot.version} - - - - - org.springframework.boot - spring-boot-starter-actuator - ${spring.boot.version} - - - - mysql - mysql-connector-java - runtime - 8.0.16 org.springframework.boot spring-boot-starter-test test - ${spring.boot.version} - com.google.guava - guava - 27.1-jre - - - - org.aspectj aspectjrt - 1.9.1 org.aspectj aspectjweaver - 1.9.1 cglib cglib - 2.2.2 - - io.grpc - grpc-stub - 1.25.0 - - - org.apache.curator curator-framework - 2.13.0 org.apache.curator curator-recipes - 2.13.0 - - - - - - - org.apache.httpcomponents - httpclient - 4.5.1 org.apache.commons commons-pool2 - 2.6.2 org.apache.logging.log4j @@ -192,44 +124,22 @@ com.alibaba.csp sentinel-core - 1.6.3 com.alibaba.csp sentinel-datasource-extension - 1.6.3 com.alibaba.csp sentinel-transport-simple-http - 1.6.3 com.alibaba.cloud spring-cloud-starter-alibaba-sentinel - 2.1.0.RELEASE - - - com.webank.ai.fate - fate-serving-core - ${fate.version} - compile - - - com.webank.ai.fate - fate-metrics-api - ${fate.version} - - - com.webank.ai.fate - fate-metrics-micrometer - ${fate.version} - - @@ -256,95 +166,20 @@ - - - kr.motd.maven - os-maven-plugin - 1.5.0.Final - - org.xolstice.maven.plugins protobuf-maven-plugin - ${protobuf-maven-plugin.version} - - com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} - - grpc-java - io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} - - ${basedir}/../../proto - - - - - compile - compile-custom - - - - org.springframework.boot spring-boot-maven-plugin - - true - true - - - - org.apache.maven.plugins - maven-dependency-plugin - 2.10 - - - copy-dependencies - package - - copy-dependencies - - - ${project.build.directory}/lib - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.8 - 1.8 - - - - org.apache.maven.plugins maven-assembly-plugin - 2.2.1 - - - make-assembly - package - - single - - - - package.xml - - - - diff --git a/serving-server/pom.xml b/serving-server/pom.xml index c7fe7b0d..45468e04 100644 --- a/serving-server/pom.xml +++ b/serving-server/pom.xml @@ -22,26 +22,33 @@ com.webank.ai.fate ${fate.version} + 4.0.0 fate-serving-server - - 2.10.0.pr1 - 1.19.0 - 27.1-jre - 2.9.0 - 8.0.13 - 1.3.7 - 0.6.1 - - + + + com.webank.ai.fate + fate-serving-core + ${project.version} + + + com.webank.ai.fate + fate-serving-federatedml + ${project.version} + + + + com.webank.ai.fate + fate-register + ${project.version} + org.springframework.boot spring-boot-starter - 2.0.4.RELEASE org.springframework.boot @@ -50,205 +57,65 @@ - - org.apache.commons - commons-lang3 - 3.8.1 - - - ch.ethz.ganymed ganymed-ssh2 - 262 org.springframework.boot spring-boot-loader-tools - 2.0.4.RELEASE - - - - - - org.springframework.boot - - spring-boot-starter-actuator - 2.0.4.RELEASE - - - - - - - - - - - com.webank.ai.fate - fate-serving-federatedml - ${project.version} - - - com.webank.ai.fate - fate-serving-core - ${project.version} - - - io.grpc - grpc-core - ${grpc.version} - - - io.grpc - grpc-netty-shaded - ${grpc.version} - - - io.grpc - grpc-protobuf - ${grpc.version} - - - io.grpc - grpc-stub - ${grpc.version} - - - com.google.guava - guava - ${guava.version} - - - org.json - json - 20151123 - - - com.alibaba - fastjson - 1.2.60 - - - - org.apache.httpcomponents - httpclient - 4.5.7 - - - com.fasterxml.jackson.core - jackson-annotations - ${jackson.version} - - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - - - - mysql - mysql-connector-java - ${mysql.connector.version} - - - - org.projectlombok - lombok - 1.18.0 - provided - - - - com.google.flatbuffers - flatbuffers-java - 1.10.0 - - - - com.google.flatbuffers - flatbuffers-java-grpc - 1.10.0 - + + io.dropwizard.metrics metrics-core - 4.1.2 + io.dropwizard.metrics metrics-jmx - 4.1.2 - - - - com.webank.ai.fate - fate-register - ${project.version} + + org.xolstice.maven.plugins + protobuf-maven-plugin + + org.apache.maven.plugins maven-assembly-plugin - 2.2.1 - - - package.xml - - - - - make-assembly - package - - single - - - - - - - - From 742aa8c87e73d20e8d43ad0c52314ed3d285aa28 Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Fri, 14 Feb 2020 19:16:50 +0800 Subject: [PATCH 168/190] optimization code Signed-off-by: v_dylanxu <136539068@qq.com> --- .../core/manager/DefaultCacheManager.java | 2 +- .../federatedml/model/FeatureSelection.java | 3 --- ...ry.java => DefaultLoadBalanceFactory.java} | 14 +++++------ .../router/AbstractRouterService.java | 4 ++-- .../webank/ai/fate/serving/ServingServer.java | 24 ++++++++++--------- 5 files changed, 22 insertions(+), 25 deletions(-) rename register/src/main/java/com/webank/ai/fate/register/loadbalance/{DefaultLoadBalancerFactory.java => DefaultLoadBalanceFactory.java} (66%) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java index f98b0389..322445e2 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java @@ -76,7 +76,7 @@ public class DefaultCacheManager implements CacheManager, InitializingBean { jedisPoolConfig.setMaxIdle(Configuration.getPropertyInt("redis.maxIdle",10)); jedisPool = new JedisPool(jedisPoolConfig, Configuration.getProperty("redis.ip"), - Configuration.getPropertyInt("redis.port",3306), + Configuration.getPropertyInt("redis.port",6379), Configuration.getPropertyInt("redis.timeout",2000), Configuration.getProperty("redis.password","")); diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/FeatureSelection.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/FeatureSelection.java index 0e2bdfc9..ca8f779e 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/FeatureSelection.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/FeatureSelection.java @@ -30,9 +30,6 @@ import java.util.List; import java.util.Map; -; - - public class FeatureSelection extends BaseModel { private static final Logger logger = LoggerFactory.getLogger(FeatureSelection.class); private FeatureSelectionParam featureSelectionParam; diff --git a/register/src/main/java/com/webank/ai/fate/register/loadbalance/DefaultLoadBalancerFactory.java b/register/src/main/java/com/webank/ai/fate/register/loadbalance/DefaultLoadBalanceFactory.java similarity index 66% rename from register/src/main/java/com/webank/ai/fate/register/loadbalance/DefaultLoadBalancerFactory.java rename to register/src/main/java/com/webank/ai/fate/register/loadbalance/DefaultLoadBalanceFactory.java index 740509c0..b4d0bfe4 100644 --- a/register/src/main/java/com/webank/ai/fate/register/loadbalance/DefaultLoadBalancerFactory.java +++ b/register/src/main/java/com/webank/ai/fate/register/loadbalance/DefaultLoadBalanceFactory.java @@ -20,20 +20,18 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -public class DefaultLoadBalancerFactory implements LoadBalancerFactory { - - static ConcurrentMap loaderBalancerRegister; +public class DefaultLoadBalanceFactory implements LoadBalancerFactory { + private static ConcurrentMap loaderBalanceRegister; static { - loaderBalancerRegister = new ConcurrentHashMap(); - loaderBalancerRegister.put(LoadBalanceModel.random_with_weight, new RandomLoadBalance()); - loaderBalancerRegister.put(LoadBalanceModel.random, new RandomLoadBalance()); + loaderBalanceRegister = new ConcurrentHashMap(); + loaderBalanceRegister.put(LoadBalanceModel.random_with_weight, new RandomLoadBalance()); + loaderBalanceRegister.put(LoadBalanceModel.random, new RandomLoadBalance()); } @Override public LoadBalancer getLoaderBalancer(LoadBalanceModel model) { - - return loaderBalancerRegister.get(model); + return loaderBalanceRegister.get(model); } } diff --git a/register/src/main/java/com/webank/ai/fate/register/router/AbstractRouterService.java b/register/src/main/java/com/webank/ai/fate/register/router/AbstractRouterService.java index c4d84022..07936435 100644 --- a/register/src/main/java/com/webank/ai/fate/register/router/AbstractRouterService.java +++ b/register/src/main/java/com/webank/ai/fate/register/router/AbstractRouterService.java @@ -22,7 +22,7 @@ import com.webank.ai.fate.register.common.Constants; import com.webank.ai.fate.register.common.RouterModel; import com.webank.ai.fate.register.interfaces.Registry; -import com.webank.ai.fate.register.loadbalance.DefaultLoadBalancerFactory; +import com.webank.ai.fate.register.loadbalance.DefaultLoadBalanceFactory; import com.webank.ai.fate.register.loadbalance.LoadBalanceModel; import com.webank.ai.fate.register.loadbalance.LoadBalancer; import com.webank.ai.fate.register.loadbalance.LoadBalancerFactory; @@ -37,7 +37,7 @@ public abstract class AbstractRouterService implements RouterService { - protected LoadBalancerFactory loadBalancerFactory = new DefaultLoadBalancerFactory(); + protected LoadBalancerFactory loadBalancerFactory = new DefaultLoadBalanceFactory(); protected AbstractRegistry registry; Logger logger = LoggerFactory.getLogger(AbstractRouterService.class); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index 1990f987..339b57b8 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -55,9 +55,11 @@ public class ServingServer implements InitializingBean { private Server server; private boolean useRegister = false; private String confPath = ""; + public ServingServer() { } + public ServingServer(String confPath) { this.confPath = new File(confPath).getAbsolutePath(); System.setProperty(Dict.CONFIGPATH, confPath); @@ -100,10 +102,10 @@ private void start(String[] args) throws Exception { int processors = Runtime.getRuntime().availableProcessors(); - Integer corePoolSize = Configuration.getPropertyInt("serving.core.pool.size",processors); - Integer maxPoolSize = Configuration.getPropertyInt("serving.max.pool.size",processors * 2); - Integer aliveTime = Configuration.getPropertyInt("serving.pool.alive.time",1000); - Integer queueSize = Configuration.getPropertyInt("serving.pool.queue.size",10); + Integer corePoolSize = Configuration.getPropertyInt("serving.core.pool.size", processors); + Integer maxPoolSize = Configuration.getPropertyInt("serving.max.pool.size", processors * 2); + Integer aliveTime = Configuration.getPropertyInt("serving.pool.alive.time", 1000); + Integer queueSize = Configuration.getPropertyInt("serving.pool.queue.size", 10); Executor executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, aliveTime.longValue(), TimeUnit.MILLISECONDS, new SynchronousQueue(), new NamedThreadFactory("ServingServer", true)); @@ -148,7 +150,7 @@ private void start(String[] args) throws Exception { } - ModelService modelService = applicationContext.getBean(ModelService.class); + ModelService modelService = applicationContext.getBean(ModelService.class); modelService.restore(); ConsoleReporter reporter = applicationContext.getBean(ConsoleReporter.class); @@ -180,12 +182,12 @@ private void stop() { }); zookeeperRegistry.destroy(); } - int retryCount=0; + int retryCount = 0; long requestInProcess = BaseContext.requestInProcess.get(); - do{ + do { - logger.info("try to stop server,there is {} request in process,try count {}", requestInProcess,retryCount+1); - if(requestInProcess>0&&retryCount<30) { + logger.info("try to stop server,there is {} request in process,try count {}", requestInProcess, retryCount + 1); + if (requestInProcess > 0 && retryCount < 30) { try { Thread.sleep(100); @@ -194,11 +196,11 @@ private void stop() { } retryCount++; requestInProcess = BaseContext.requestInProcess.get(); - }else{ + } else { break; } - }while(requestInProcess>0&&retryCount<3); + } while (requestInProcess > 0 && retryCount < 3); server.shutdown(); } } From c63cfdea26da9cb57be2813e949efbe84eddb360 Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Sun, 16 Feb 2020 15:41:41 +0800 Subject: [PATCH 169/190] project service.sh bug repair --- serving-proxy/bin/service.sh | 1 + serving-server/bin/service.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/serving-proxy/bin/service.sh b/serving-proxy/bin/service.sh index 82b44381..d9287302 100644 --- a/serving-proxy/bin/service.sh +++ b/serving-proxy/bin/service.sh @@ -29,6 +29,7 @@ module_version=1.2.0 case "$1" in start) + stop $module start $module status $module ;; diff --git a/serving-server/bin/service.sh b/serving-server/bin/service.sh index 6674b9e2..53515512 100644 --- a/serving-server/bin/service.sh +++ b/serving-server/bin/service.sh @@ -27,6 +27,7 @@ module_version=1.2.0 case "$1" in start) + stop $module start $module status $module ;; From fb483ff9cbac596f752cf3ed0b6e4d8ad3b37c16 Mon Sep 17 00:00:00 2001 From: kaideng Date: Mon, 17 Feb 2020 09:49:20 +0800 Subject: [PATCH 170/190] change config Signed-off-by: kaideng --- .../ai/fate/serving/core/bean/Dict.java | 4 +- .../core/manager/DefaultCacheManager.java | 58 ++++++++++++++----- .../serving/proxy/config/RegistryConfig.java | 13 +++-- .../serving/proxy/config/WebConfigration.java | 4 +- .../router/ConfigFileBasedServingRouter.java | 18 +++--- .../serving/proxy/security/AuthUtils.java | 12 ++-- .../webank/ai/fate/serving/ServingServer.java | 13 +++-- .../webank/ai/fate/serving/SpringConfig.java | 2 +- .../serving/manager/DefaultModelCache.java | 2 +- .../manager/InferenceWorkerManager.java | 7 ++- .../main/resources/serving-server.properties | 46 +++++++-------- 11 files changed, 111 insertions(+), 68 deletions(-) diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java index 2f2467bc..c6ae81f7 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/bean/Dict.java @@ -68,11 +68,12 @@ public class Dict { public static final String PROPERTY_EXTERNAL_INFERENCE_RESULT_CACHE_DB_INDEX = "external.inferenceResultCacheDBIndex"; public static final String PROPERTY_EXTERNAL_INFERENCE_RESULT_CACHE_TTL = "external.inferenceResultCacheTTL"; public static final String PROPERTY_EXTERNAL_REMOTE_MODEL_INFERENCE_RESULT_CACHE_DB_INDEX = "external.remoteModelInferenceResultCacheDBIndex"; + public static final String PROPERTY_EXTERNAL_PROCESS_CACHE_DB_INDEX = "external.processCacheDBIndex"; public static final String PROPERTY_EXTERNAL_REMOTE_MODEL_INFERENCE_RESULT_CACHE_TTL = "external.remoteModelInferenceResultCacheTTL"; public static final String PROPERTY_REMOTE_MODEL_INFERENCE_RESULT_CACHE_SWITCH = "remoteModelInferenceResultCacheSwitch"; public static final String PROPERTY_CAN_CACHE_RET_CODE = "canCacheRetcode"; public static final String PROPERTY_SERVICE_ROLE_NAME = "serviceRoleName"; - public static final String PROPERTY_SERVICE_ROLE_NAME_DEFAULT_VALUE = "serving-1.0"; + public static final String PROPERTY_SERVICE_ROLE_NAME_DEFAULT_VALUE = "serving"; public static final String PROPERTY_ONLINE_DATA_ACCESS_ADAPTER = "OnlineDataAccessAdapter"; public static final String PROPERTY_MODEL_CACHE_ACCESS_TTL = "modelCacheAccessTTL"; public static final String PROPERTY_MODEL_CACHE_MAX_SIZE = "modelCacheMaxSize"; @@ -85,6 +86,7 @@ public class Dict { public static final String PROPERTY_SERVER_PORT = "port"; public static final String PROPERTY_USER_DIR = "user.dir"; public static final String PROPERTY_USER_HOME = "user.home"; + public static final String PROPERTY_FILE_SEPARATOR = "file.separator"; public static final String ACTION_TYPE_ASYNC_EXECUTE = "ASYNC_EXECUTE"; diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java index 322445e2..8213287a 100644 --- a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/manager/DefaultCacheManager.java @@ -55,8 +55,8 @@ public class DefaultCacheManager implements CacheManager, InitializingBean { DefaultCacheManager() { remoteModelInferenceResultCache = CacheBuilder.newBuilder() - .expireAfterAccess(Configuration.getPropertyInt("remoteModelInferenceResultCacheTTL",30), TimeUnit.SECONDS) - .maximumSize(Configuration.getPropertyInt("remoteModelInferenceResultCacheMaxSize",1000)) + .expireAfterAccess(Configuration.getPropertyInt(Dict.PROPERTY_REMOTE_MODEL_INFERENCE_RESULT_CACHE_TTL,30), TimeUnit.SECONDS) + .maximumSize(Configuration.getPropertyInt(Dict.PROPERTY_REMOTE_MODEL_INFERENCE_RESULT_CACHE_MAX_SIZE,1000)) .build(); @@ -67,25 +67,32 @@ public class DefaultCacheManager implements CacheManager, InitializingBean { inferenceResultCache = CacheBuilder.newBuilder() - .expireAfterAccess(Configuration.getPropertyInt("inferenceResultCacheTTL",30), TimeUnit.SECONDS) - .maximumSize(Configuration.getPropertyInt("inferenceResultCacheCacheMaxSize",1000)) + .expireAfterAccess(Configuration.getPropertyInt(Dict.PROPERTY_INFERENCE_RESULT_CACHE_TTL,30), TimeUnit.SECONDS) + .maximumSize(Configuration.getPropertyInt(Dict.PROPERTY_INFERENCE_RESULT_CACHE_CACHE_MAX_SIZE,1000)) .build(); JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setMaxTotal(Configuration.getPropertyInt("redis.maxTotal",10)); jedisPoolConfig.setMaxIdle(Configuration.getPropertyInt("redis.maxIdle",10)); + + String password = null; + + String passowrdString = Configuration.getProperty("redis.password",""); + if(StringUtils.isNotEmpty(passowrdString)){ + password = passowrdString; + } jedisPool = new JedisPool(jedisPoolConfig, Configuration.getProperty("redis.ip"), Configuration.getPropertyInt("redis.port",6379), Configuration.getPropertyInt("redis.timeout",2000), - Configuration.getProperty("redis.password","")); - - - inferenceResultCacheDBIndex = Configuration.getPropertyInt("external.inferenceResultCacheDBIndex",0); - externalInferenceResultCacheTTL = Configuration.getPropertyInt("external.inferenceResultCacheTTL"); - remoteModelInferenceResultCacheDBIndex = Configuration.getPropertyInt("external.remoteModelInferenceResultCacheDBIndex",0); - processCacheDBIndex = Configuration.getPropertyInt("external.processCacheDBIndex",0); - externalRemoteModelInferenceResultCacheTTL = Configuration.getPropertyInt("external.remoteModelInferenceResultCacheTTL"); + password + ); + + inferenceResultCacheDBIndex = Configuration.getPropertyInt(Dict.PROPERTY_EXTERNAL_INFERENCE_RESULT_CACHE_DB_INDEX,0); + externalInferenceResultCacheTTL = Configuration.getPropertyInt(Dict.PROPERTY_EXTERNAL_INFERENCE_RESULT_CACHE_TTL,300); + remoteModelInferenceResultCacheDBIndex = Configuration.getPropertyInt(Dict.PROPERTY_EXTERNAL_REMOTE_MODEL_INFERENCE_RESULT_CACHE_DB_INDEX,0); + processCacheDBIndex = Configuration.getPropertyInt(Dict.PROPERTY_EXTERNAL_PROCESS_CACHE_DB_INDEX,0); + externalRemoteModelInferenceResultCacheTTL = Configuration.getPropertyInt(Dict.PROPERTY_EXTERNAL_REMOTE_MODEL_INFERENCE_RESULT_CACHE_TTL,86400); canCacheRetcode = initializeCanCacheRetcode(); } @@ -153,7 +160,7 @@ public ReturnResult getInferenceResultCache(String partyId, String caseid) { @Override public void putRemoteModelInferenceResult(FederatedParams guestFederatedParams, ReturnResult returnResult) { - if (!Boolean.parseBoolean(Configuration.getProperty(Dict.PROPERTY_REMOTE_MODEL_INFERENCE_RESULT_CACHE_SWITCH))) { + if (!Boolean.parseBoolean(Configuration.getProperty(Dict.PROPERTY_REMOTE_MODEL_INFERENCE_RESULT_CACHE_SWITCH,"true"))) { return; } String remoteModelInferenceResultCacheKey = generateRemoteModelInferenceResultCacheKey(guestFederatedParams); @@ -168,7 +175,7 @@ public void putRemoteModelInferenceResult(FederatedParams guestFederatedParams, @Override public ReturnResult getRemoteModelInferenceResult(FederatedParams guestFederatedParams) { - if (!Boolean.parseBoolean(Configuration.getProperty(Dict.PROPERTY_REMOTE_MODEL_INFERENCE_RESULT_CACHE_SWITCH))) { + if (!Boolean.parseBoolean(Configuration.getProperty(Dict.PROPERTY_REMOTE_MODEL_INFERENCE_RESULT_CACHE_SWITCH,"true"))) { return null; } String remoteModelInferenceResultCacheKey = generateRemoteModelInferenceResultCacheKey(guestFederatedParams); @@ -246,7 +253,7 @@ private int[] initializeCacheDBIndex(String config) { private Set initializeCanCacheRetcode() { Set retcodes = new HashSet<>(); - String[] retcodeString = Configuration.getProperty("canCacheRetcode").split(","); + String[] retcodeString = Configuration.getProperty(Dict.PROPERTY_CAN_CACHE_RET_CODE,"0,102").split(","); for (int i = 0; i < retcodeString.length; i++) { retcodes.add(Integer.parseInt(retcodeString[i])); } @@ -336,4 +343,25 @@ private enum CacheType { } + public static void main(String[] args){ + + JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); + System.err.println("oooooooooooo"); + jedisPoolConfig.setMaxTotal(10); + jedisPoolConfig.setMaxIdle(10); + System.err.println("11111111"); + JedisPool jedisPool = new JedisPool(jedisPoolConfig, + "localhost", + 6379, + 2000, + null + ); + + jedisPool.getResource(); + + } + + + + } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java index 53f48963..a252b911 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/RegistryConfig.java @@ -40,7 +40,7 @@ public class RegistryConfig { @Value("${proxy.grpc.intra.port:8867}") private Integer port; - @Value("${zk.url:zookeeper://localhost:2181}") + @Value("${zk.url:}") private String zkUrl; @Value("${useZkRouter:true}") @@ -49,10 +49,10 @@ public class RegistryConfig { @Value("${acl.enable:false}") private String aclEnable; - @Value("${acl.username}") + @Value("${acl.username:}") private String aclUsername; - @Value("${acl.password}") + @Value("${acl.password:}") private String aclPassword; @Bean @@ -60,7 +60,12 @@ public ZookeeperRegistry zookeeperRegistry(InterGrpcServer interGrpcServer) { if (logger.isDebugEnabled()) { logger.info("prepare to create zookeeper registry ,use zk {}", useZkRouter); } - if ("true".equals(useZkRouter) && StringUtils.isNotEmpty(zkUrl)) { + if ("true".equals(useZkRouter)) { + + if(StringUtils.isEmpty(zkUrl)){ + logger.error("useZkRouter is true,but zkUrl is empty,please check zk.url in the config file"); + throw new RuntimeException("wrong zk url"); + } System.setProperty("acl.enable", Optional.ofNullable(aclEnable).orElse("")); System.setProperty("acl.username", Optional.ofNullable(aclUsername).orElse("")); System.setProperty("acl.password", Optional.ofNullable(aclPassword).orElse("")); diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java index 8daabe23..903b44f5 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/config/WebConfigration.java @@ -30,10 +30,10 @@ public class WebConfigration implements WebMvcConfigurer { @Value("${proxy.async.timeout:5000}") long timeout; - @Value("${proxy.async.coresize}") + @Value("${proxy.async.coresize:0}") int coreSize; - @Value("${proxy.async.maxsize}") + @Value("${proxy.async.maxsize:0}") int maxSize; @Override diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java index 21dc7b54..c91925c1 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/rpc/router/ConfigFileBasedServingRouter.java @@ -40,14 +40,14 @@ public class ConfigFileBasedServingRouter extends BaseServingRouter implements private String userDir=System.getProperty(Dict.PROPERTY_USER_DIR); - @Value("${route.table}") + @Value("${route.table:}") private String routeTableFile; - private final String DEFAULT_ROUTER_FILE = "conf/route_table.json"; + private final String DEFAULT_ROUTER_FILE = "conf"+System.getProperty(Dict.PROPERTY_FILE_SEPARATOR)+"route_table.json"; - private final String fileSeparator = System.getProperty("file.separator"); + private final String fileSeparator = System.getProperty(Dict.PROPERTY_FILE_SEPARATOR); - @Value("${coordinator:9999}") + @Value("${coordinator}") private String selfCoordinator; @Value("${inference.service.name:serving}") @@ -242,7 +242,7 @@ public void loadRouteTable() { logger.error("router table {} is not exist",filePath); return; } - String fileMd5 = FileUtils.fileMd5(routeTableFile); + String fileMd5 = FileUtils.fileMd5(filePath); if(null != fileMd5 && fileMd5.equals(lastFileMd5)){ return; } @@ -250,10 +250,12 @@ public void loadRouteTable() { JsonReader jsonReader = null; JsonObject confJson = null; try { - jsonReader = new JsonReader(new FileReader(routeTableFile)); + jsonReader = new JsonReader(new FileReader(filePath)); confJson = jsonParser.parse(jsonReader).getAsJsonObject(); + logger.info("load router table {}",confJson); + } catch (Exception e) { - logger.error("parse router table error: {}", routeTableFile); + logger.error("parse router table error: {}", filePath); throw new RuntimeException(e); } finally { if (jsonReader != null) { @@ -266,7 +268,7 @@ public void loadRouteTable() { } initRouteTable(confJson.getAsJsonObject("route_table")); initPermission(confJson.getAsJsonObject("permission")); - logger.info("refreshed route table at: {}", routeTableFile); + logger.info("refreshed route table at: {}", filePath); lastFileMd5 = fileMd5; } diff --git a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java index c7028899..b0a2bd21 100644 --- a/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java +++ b/serving-proxy/src/main/java/com/webank/ai/fate/serving/proxy/security/AuthUtils.java @@ -52,18 +52,18 @@ public class AuthUtils implements InitializingBean{ private final String userDir = System.getProperty(Dict.PROPERTY_USER_DIR); - private final String DEFAULT_AUTH_FILE="conf/auth_config.json"; + private final String DEFAULT_AUTH_FILE="conf"+System.getProperty(Dict.PROPERTY_FILE_SEPARATOR)+"auth_config.json"; @Autowired private ToStringUtils toStringUtils; - @Value("${auth.file}") + @Value("${auth.file:}") private String confFilePath; @Value("${auth.open:false}") private String openAuth; - private final String fileSeparator = System.getProperty("file.separator"); + private final String fileSeparator = System.getProperty(Dict.PROPERTY_FILE_SEPARATOR); @Value("${coordinator}") @@ -74,14 +74,14 @@ public class AuthUtils implements InitializingBean{ @Scheduled(fixedRate = 10000) public void loadConfig(){ if(Boolean.valueOf(openAuth)) { - logger.debug("start refreshed auth config..."); + String filePath = ""; if (StringUtils.isNotEmpty(confFilePath)) { filePath = confFilePath; } else { filePath = userDir + this.fileSeparator + DEFAULT_AUTH_FILE; } - + logger.info("start refreshed auth config ,file path is {}",filePath); String fileMd5 = FileUtils.fileMd5(filePath); if (null != fileMd5 && fileMd5.equals(lastFileMd5)) { return; @@ -99,7 +99,7 @@ public void loadConfig(){ jsonReader = new JsonReader(new FileReader(filePath)); jsonObject = jsonParser.parse(jsonReader).getAsJsonObject(); } catch (FileNotFoundException e) { - logger.error("File not found: {}", filePath); + logger.error("auth file not found: {}", filePath); throw new RuntimeException(e); } finally { if (jsonReader != null) { diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java index d66bcf2b..5dc0c2a9 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/ServingServer.java @@ -52,7 +52,7 @@ public class ServingServer implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(ServingServer.class); static ApplicationContext applicationContext; private Server server; - private boolean useRegister = false; + private boolean useRegister = true; private String confPath = ""; public ServingServer() { @@ -118,11 +118,14 @@ private void start(String[] args) throws Exception { server = serverBuilder.build(); logger.info("server started listening on port: {}, use configuration: {}", port, this.confPath); server.start(); - - String userRegisterString = Configuration.getProperty(Dict.USE_REGISTER); + String userRegisterString = Configuration.getProperty(Dict.USE_REGISTER,"true"); useRegister = Boolean.valueOf(userRegisterString); - logger.info("serving useRegister {}", useRegister); - + if(useRegister) { + logger.info("serving-server is using register center"); + } + else{ + logger.warn("serving-server not use register center"); + } if (useRegister) { ZookeeperRegistry zookeeperRegistry = applicationContext.getBean(ZookeeperRegistry.class); zookeeperRegistry.subProject(Dict.PROPERTY_PROXY_ADDRESS); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java b/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java index 0d3153fa..d3c7f23b 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/SpringConfig.java @@ -38,7 +38,7 @@ public class SpringConfig { @Bean ZookeeperRegistry getServiceRegistry() { String useRegisterString = com.webank.ai.fate.serving - .core.bean.Configuration.getProperty("useRegister"); + .core.bean.Configuration.getProperty(Dict.USE_REGISTER,"true"); if (Boolean.valueOf(useRegisterString)) { return ZookeeperRegistry.getRegistery(com.webank.ai.fate.serving.core.bean.Configuration.getProperty("zk.url"), "serving", "online", com.webank.ai.fate.serving.core.bean.Configuration.getPropertyInt(Dict.PORT)); diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultModelCache.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultModelCache.java index 9bd45dd1..32d7efaa 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultModelCache.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultModelCache.java @@ -43,7 +43,7 @@ public class DefaultModelCache implements ModelCache { public DefaultModelCache() { modelCache = CacheBuilder.newBuilder() // .expireAfterAccess(Configuration.getPropertyInt(Dict.PROPERTY_MODEL_CACHE_ACCESS_TTL), TimeUnit.HOURS) - .maximumSize(Configuration.getPropertyInt(Dict.PROPERTY_MODEL_CACHE_MAX_SIZE)) + .maximumSize(Configuration.getPropertyInt(Dict.PROPERTY_MODEL_CACHE_MAX_SIZE,100)) .build( new CacheLoader() { @Override diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manager/InferenceWorkerManager.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/InferenceWorkerManager.java index 0aeef351..d8e1eb32 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manager/InferenceWorkerManager.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/InferenceWorkerManager.java @@ -29,11 +29,14 @@ public class InferenceWorkerManager { private static final Logger logger = LoggerFactory.getLogger(InferenceWorkerManager.class); private static ThreadPoolExecutor threadPoolExecutor; + static { + + int coreNum = Runtime.getRuntime().availableProcessors(); LinkedBlockingQueue taskQueue = new LinkedBlockingQueue<>(10); threadPoolExecutor = new ThreadPoolExecutor( - Configuration.getPropertyInt(Dict.PROPERTY_INFERENCE_WORKER_THREAD_NUM), - Configuration.getPropertyInt(Dict.PROPERTY_INFERENCE_WORKER_THREAD_NUM), + Configuration.getPropertyInt(Dict.PROPERTY_INFERENCE_WORKER_THREAD_NUM,coreNum), + Configuration.getPropertyInt(Dict.PROPERTY_INFERENCE_WORKER_THREAD_NUM,2*coreNum), 60, TimeUnit.SECONDS, taskQueue, diff --git a/serving-server/src/main/resources/serving-server.properties b/serving-server/src/main/resources/serving-server.properties index 2cb1a237..f90e3200 100644 --- a/serving-server/src/main/resources/serving-server.properties +++ b/serving-server/src/main/resources/serving-server.properties @@ -15,29 +15,29 @@ # ip=127.0.0.1 port=8000 -serviceRoleName=serving -inferenceWorkerThreadNum=10 +#serviceRoleName=serving +#inferenceWorkerThreadNum=10 # cache -remoteModelInferenceResultCacheSwitch=true +#remoteModelInferenceResultCacheSwitch=true # in-process cache -modelCacheMaxSize=100 -remoteModelInferenceResultCacheTTL=300 -remoteModelInferenceResultCacheMaxSize=10000 -inferenceResultCacheTTL=30 -inferenceResultCacheCacheMaxSize=1000 +#modelCacheMaxSize=100 +#remoteModelInferenceResultCacheTTL=300 +#remoteModelInferenceResultCacheMaxSize=10000 +#inferenceResultCacheTTL=30 +#inferenceResultCacheCacheMaxSize=1000 # external cache redis.ip=127.0.0.1 redis.port=6379 -redis.password=fate_dev -redis.timeout=10 -redis.maxTotal=100 -redis.maxIdle=100 -external.remoteModelInferenceResultCacheTTL=86400 -external.remoteModelInferenceResultCacheDBIndex=0 -external.inferenceResultCacheTTL=300 -external.inferenceResultCacheDBIndex=0 -canCacheRetcode=0,102 -external.processCacheDBIndex=0 +#redis.password=fate_dev +#redis.timeout=10 +#redis.maxTotal=100 +#redis.maxIdle=100 +#external.remoteModelInferenceResultCacheTTL=86400 +#external.remoteModelInferenceResultCacheDBIndex=0 +#external.inferenceResultCacheTTL=300 +#external.inferenceResultCacheDBIndex=0 +#canCacheRetcode=0,102 +#external.processCacheDBIndex=0 # adapter OnlineDataAccessAdapter=MockAdapter @@ -49,9 +49,9 @@ InferencePreProcessingAdapter=PassPreProcessing model.transfer.url=http://127.0.0.1:9380/v1/model/transfer # zk router zk.url=zookeeper://localhost:2181?backup=localhost:2182,localhost:2183 -useRegister=false -useZkRouter=false +#useRegister=false +#useZkRouter=false # zk acl -acl.enable=false -acl.username= -acl.password= \ No newline at end of file +#acl.enable=false +#acl.username= +#acl.password= \ No newline at end of file From 0eb1d261243e18394734da8debe417b37bda1a8c Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Mon, 17 Feb 2020 14:32:28 +0800 Subject: [PATCH 171/190] change log level Signed-off-by: v_dylanxu <136539068@qq.com> --- .../ai/fate/serving/host/DefaultHostInferenceProvider.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/host/DefaultHostInferenceProvider.java b/serving-server/src/main/java/com/webank/ai/fate/serving/host/DefaultHostInferenceProvider.java index 00ad291b..b71c910b 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/host/DefaultHostInferenceProvider.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/host/DefaultHostInferenceProvider.java @@ -85,9 +85,8 @@ public ReturnResult federatedInference(Context context, HostFederatedParams fede return returnResult; } - if (logger.isDebugEnabled()) { - logger.debug("use model to inference on {} {}, id: {}, version: {}", party.getRole(), party.getPartyId(), modelInfo.getNamespace(), modelInfo.getName()); - } + logger.info("use model to inference on {} {}, id: {}, version: {}", party.getRole(), party.getPartyId(), modelInfo.getNamespace(), modelInfo.getName()); + Map predictParams = new HashMap<>(8); predictParams.put(Dict.FEDERATED_PARAMS, federatedParams); From e39fc600b9812b2084f05acaaa46de869d95181e Mon Sep 17 00:00:00 2001 From: kaideng Date: Tue, 18 Feb 2020 09:45:22 +0800 Subject: [PATCH 172/190] change log Signed-off-by: kaideng --- README.md | 268 +----------------- .../register/zookeeper/ZookeeperRegistry.java | 2 +- 2 files changed, 3 insertions(+), 267 deletions(-) diff --git a/README.md b/README.md index 8b373758..364ab383 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ## Introduction -FATE-Serving is a high-performance, industrialized serving system for federated learning models, designed for production environments. +FATE-Serving is a high-performance, industrialized serving system for federated learning models, designed for production environments. for more detail, You can click [WIKI](https://github.com/FederatedAI/FATE-Serving/wiki) for more information for more information ### FATE-Serving now supports @@ -12,277 +12,13 @@ FATE-Serving is a high-performance, industrialized serving system for federated - Federated Learning online inference pipeline. - Dynamic loading federated learning models. - Can serve multiple models, or multiple versions of the same model. -- Support A/B testing experimental models. - Real-time inference using federated learning models. - Support multi-level cache for remote party federated inference result. - Support pre-processing, post-processing and data-access adapters for the production deployment. -- Provide service managerment for grpc interface by using zookeeper as registry (optional) +- Provide service managerment for grpc interface by using zookeeper as registry - Requests for publishing models are persisted to local files,so the loaded model will be loaded automatically when the application is restarted -## Federated Learning Online Inference Pipeline -![fate_serving_online_pipeline](./images/fate_serving_online_pipeline.png) - - -## Architecture - - -![fate_serving_arch](./images/fate-serving-infrastructure.jpg) - - - -As shown in the figure above,fate-serving includes the following modules: -- serving-server -- serving-proxy - -Now we will explain the architecture and function of each module in turn. -first,the serving-server: - - - - - -## Deploy - -The preparations are as follows: - -1. The serving-server rely on Redis,Redis needs to be installed in advance -2. All models is run in the JVM ,Java needs to be installed in advance -3. Verify that the service governance feature is required, you can set it to be enabled in the configuration file, and if it is enabled, you need to install zookeeper in advance - -the ordinary deploy architecture as the graph shows,If you use this pattern, the IP addresses of each module need to be manually configured in the configuration file - -![fate_serving_arch](./images/noZk.png) - - - - - -If you want use the service management,the deploy architecture is show here: - -![fate_serving_arch](./images/useZk.png) - - -- serving-server: Federated Learning online inference service based on GRPC -- serving-router: route requests to serving-server or to another party ,The function of this module is similar to the Proxy module in FATE -- Zookeeper: work as the register center - - - -### The data in the zookeeper - - - - - - - -### serving-server.properties -Key configuration item description: - -| Configuration item | Configuration item meaning | Configuration item value | -| - | - | - | -| ip | listen address for FATE-Serving | default 0.0.0.0 | -| port | listen port for the grpc server of FATE-Serving | default 8000 | -| workMode | the work mode of FATE-Flow | 0 for standalone, 1 for cluster | -| inferenceWorkerThreadNum | inference worker num for async inference | default 10 | -| standaloneStoragePath | the storage path of standalone EggRoll | generally is PYTHONPATH/data | -| remoteModelInferenceResultCacheSwitch | switch of remote model inference result cache storage | default true | -| proxy | the address of proxy | custom configuration | -| roll | the address of roll | custom configuration | -| OnlineDataAccessAdapter | data access adapter class for obtaining host feature data | default TestFile, read host feature data from ``host_data.csv`` on serving-server root directory | -| InferencePostProcessingAdapter| inference post-processing adapter class for dealing result after model inference | default PassPostProcessing | -| InferencePreProcessingAdapter | inference pre-processing adapter class for dealing guest feature data before model inference | default PassPreProcessing | -| useRegister | Register interface to registry or not | default false | -| useZkRouter | route request by the interface info which is registered into zookeeper | default false | -| zk.url | zookeeper url ,eg:zookeeper://localhost:2181?backup=localhost:2182,localhost:2183 | default zookeeper://localhost:2181 | -| coordinator | The party id for serving | default webank | -| serviceRoleName | The federated roles model name | default serving | -| modelCacheAccessTTL | The model cache expire after access | default 12 | -| modelCacheMaxSize | The maximum size of model cache | default 50 | -| remoteModelInferenceResultCacheTTL | The remote model inference result cache expire after access | default 300 | -| remoteModelInferenceResultCacheMaxSize | The maximum size of remote model inference result cache | default 10000 | -| inferenceResultCacheTTL | The inference result cache expire after access | default 30 | -| inferenceResultCacheCacheMaxSize | The maximum size of inference result cache | default 1000 | -| redis.ip | The connection host | default 127.0.0.1 | -| redis.port | Accept redis connections on the specified port| default 6379 | -| redis.password | The connection password | default fate_dev | -| redis.timeout | Close the connection after a client is idle for N seconds | default 10 | -| redis.maxTotal | The maximum number of objects that can be allocated by the pool | default 100 | -| redis.maxIdle | The maximum number of "idle" instances that can be held in the pool or a negative value if there is no limit | default 100 | -| external.remoteModelInferenceResultCacheTTL | The remote model inference result cache expire after access for external cache | default 86400 | -| external.remoteModelInferenceResultCacheDBIndex | The remote model inference result cache DBIndex for external | default 0 | -| external.inferenceResultCacheTTL | The inference result cache expire after access for external cache | default 300 | -| external.inferenceResultCacheDBIndex | The inference result cache DBIndex for external cache | default 0 | -| external.processCacheDBIndex | The process cache DBIndex for external cache | default 0 | -| canCacheRetcode | Caching result by retcode | default 0,102 | -| acl.username | Zookeeper acl authentication user name | | -| acl.password | Zookeeper acl authentication user password | | - -### proxy.properties -Key configuration item description: - -| Configuration item | Configuration item meaning | Configuration item value | -| - | - | - | -| ip | listen address for FATE-Serving-Router | default 0.0.0.0 | -| port | listen port for the FATE-Serving-Router | default 9370 | -| coordinator | The party id for serving | default webank | -| zk.url | zookeeper url, same as serving configuration | default zookeeper://localhost:2181 | -| useRegister | Register interface to registry or not | default false | -| useZkRouter | route request by the interface info which is registered into zookeeper | default false | -| route.table | router table configuration file absolute path | default /data/projects/fate/serving-router/conf/route_table.json | -| acl.username | Zookeeper acl authentication user name | | -| acl.password | Zookeeper acl authentication user password | | - -### Deploy Serving-Server -For detail, Here are some key steps: - - - 1.git clone https://github.com/FederatedAI/FATE-Serving.git - 2.cd FATE-Serving - 3.mvn clean package - 4.copy serving-server/target/fate-serving-server-1.1.2-release.zip to your deploy location and unzip it - 5.modify the configuration file conf/serving-server.properties according to your own requirements - 6.confirm whether Java is installed. You can check through the java -version command. - 7.sh service.sh restart - - - - - - -### Deploy Serving-Router - -For detail, Here are some key steps: - - 1.Same as serving-server deploy steps 1/2/3, if it has been executed, you can skip - 2.copy router/target/fate-serving-router-1.1.2-release.zip to your deploy location and unzip it - 3.modify the configuration file conf/proxy.properties and conf/route_table.json according to your own requirements - 5.confirm whether Java is installed. You can check through the java -version command. - 6.sh service.sh restart - - - - - - - - - - -## Usage -FATE-Serving provide publish model and online inference API. - -### Publish Model - -Please use FATE-Flow Client which in the fate-flow to operate, refer to **Online Inference** guide at [fate_flow_readme](https://github.com/FederatedAI/FATE/blob/master/fate_flow/README.md). - - - -### Inference - -#### Inference using HTTP -##### inference - -**URL** -- ` http://ip:port/federation/v1/inference ` -- where ip:port is the address of guest serving proxy - - -**request type** -- POST -- content-application/json - -**request parameters** - -|name|allow null|type|desc| -|:---- |:---|:----- |----- | -|head |no |json object | | -|body |no |json object | the elements and features used by the model| - - - **head object** - -|name|allow null|type|desc| -|:---- |:---|:----- |----- | -|serviceId |yes |string | the serviceId used when bind model in fate-flow| - - - **example ** - -``` - { - "head": { - "serviceId": "111111111" - }, - "body": { - "featureData": { - "x0": 1.88669, - "x1": -1.359293, - "x2": 2.303601, - "x3": 2.00137, - "x4": 1.307686 - }, - "sendToRemoteFeatureData": { - "device_id": "aaaaa", - "phone_num": "122222222" - } - } - } - -``` - - **response** - -|name|type|desc| -|:----- |:-----|----- | -|retcode |int |0: success, otherwise: error. | -|retmsg |string |error message | -|data | json object | the inference result of model| -|flag |int |the reserved field | - - **a simple example of inference** -```shell -(venv) [***]$ curl -X POST -H 'Content-Type: application/json' -d ' {"head":{"serviceId":"654321"},"body":{"device_id":"123456","phone_num":"1234567899"}}' 'http://localhost:8086/federation/v1/inference' -{"flag":0,"data":{"prob":0.30684422824464636,"guestInputDataHitRate:{}":0.0,"guestModelWeightHitRate:{}":0.0,"retcode":0},"retmsg":"success","retcode":0} -(venv) [***]$ -``` - - -#### Inference using grpc -Serving currently supports three inference-related interfaces, using the grpc protocol. - -- inference: Initiate an inference request and get the result -- startInferenceJob: Initiate an inference request task without getting results -- getInferenceResult: Get the result of the inference by caseid - -please refer to this script for inference. - - - -### Adapter - -Serving supports pre-processing, post-processing and data-access adapters for the actural production. - -- pre-processing: Data pre processing before model calculation -- post-processing: Data post processing after model calculation -- data-access: get feature from party's system - -At the current stage, you need to put the java code to recompile, and later support to dynamically load the jar in the form of a release. - -For now: - -- push your pre-processing and post-processing adapter code into fate-serving/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/processing and modify the InferencePreProcessingAdapter/InferencePostProcessingAdapter configuration parameters. -- push your data-access adapter code into fate-serving/serving-server/src/main/java/com/webank/ai/fate/serving/adapter/dataaccess and modify the OnlineDataAccessAdapter configuration parameters. - -please refer to PassPostProcessing, PassPreProcessing, TestFile adapter. - - - -### Remote party multi-level cache - -For federal learning, one inference needs to be calculated by multiple parties. In the production environment, the parties are deployed in different IDCs, and the network communication between multiple parties is one of the bottleneck. - -So, fate-serving supports caches multi-party model inference results on the initiator, but never caches feature data. you can turn the remoteModelInferenceResultCacheSwitch which in the configuration. diff --git a/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java b/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java index 050b9fb7..3bc8b958 100644 --- a/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java +++ b/register/src/main/java/com/webank/ai/fate/register/zookeeper/ZookeeperRegistry.java @@ -131,7 +131,7 @@ public void doSubProject(String project) { logger.debug("environments {}", environments); } if (environments == null) { - logger.info("environment is null,maybe zk is not started"); + logger.info("path {} is not exist in zk", path); throw new RuntimeException("environment is null"); } From 719303d8b1c8bb34598024f4d8d89f1f3712f6ca Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Tue, 18 Feb 2020 11:27:57 +0800 Subject: [PATCH 173/190] ok Signed-off-by: v_dylanxu <136539068@qq.com> --- bin/common.sh | 127 ++++++++++++++++++++++---------------------------- 1 file changed, 57 insertions(+), 70 deletions(-) diff --git a/bin/common.sh b/bin/common.sh index 09e01a25..7ed01266 100644 --- a/bin/common.sh +++ b/bin/common.sh @@ -1,89 +1,76 @@ #!/bin/bash set -e getpid() { - # pid=`ps aux | grep ${main_class} | grep -v grep | awk '{print $2}'` - module=$1 - if [ -e "./bin/${module}.pid" ]; then - pid=`cat ./bin/${module}.pid` - fi - if [[ -n ${pid} ]]; then - break 1 - else - break 0 - fi + if [ -e "./bin/${module}.pid" ]; then + pid=$(cat ./bin/${module}.pid) + fi } mklogsdir() { - if [[ ! -d "logs" ]]; then - mkdir logs - fi + if [[ ! -d "logs" ]]; then + mkdir logs + fi } start() { - echo "try to start ${module}" - getpid ${module} - if [[ $? -eq 0 ]]; then - mklogsdir - if [[ ! -e "fate-${module}.jar" ]]; then - ln -s fate-${module}-${module_version}.jar fate-${module}.jar - fi - if [ ${module} = "serving-server" ] - then - java -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/${module}.properties >> logs/console.log 2>>logs/error.log & - elif [ ${module} = "serving-proxy" ] - then - java -Dspring.config.location=${configpath}/application.properties -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/application.properties >> logs/console.log 2>>logs/error.log & - - else - echo "" - fi - sleep 5 - id=`ps -p $!| awk '{print $1}'|sed -n '2p'` - if [[ ${#id} -ne 0 ]]; then - echo $!>./bin/${module}.pid - getpid $module - echo "service start sucessfully. pid: ${pid}" - else - echo "service start failed" - fi + echo "try to start ${module}" + getpid + if [[ ! -n ${pid} ]]; then + mklogsdir + if [[ ! -e "fate-${module}.jar" ]]; then + ln -s fate-${module}-${module_version}.jar fate-${module}.jar + fi + if [ ${module} = "serving-server" ]; then + java -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/${module}.properties >>logs/console.log 2>>logs/error.log & + elif [ ${module} = "serving-proxy" ]; then + java -Dspring.config.location=${configpath}/application.properties -cp "conf/:lib/*:fate-${module}.jar" ${main_class} -c conf/application.properties >>logs/console.log 2>>logs/error.log & else - echo "service already started. pid: ${pid}" + echo "usage: ${module} {serving-server|serving-proxy}" fi -} - -status() { - getpid $1 - if [[ -n ${pid} ]]; then - echo "status: - `ps -f -p ${pid} `" - exit 1 + sleep 5 + id=$(ps -p $! | awk '{print $1}' | sed -n '2p') + if [[ ${#id} -ne 0 ]]; then + echo $! >./bin/${module}.pid + getpid + echo "service start sucessfully. pid: ${pid}" else - echo "service not running" - exit 0 + echo "service start failed" fi + else + echo "service already started. pid: ${pid}" + fi } +status() { + getpid + if [[ -n ${pid} ]]; then + echo "status: $(ps -f -p ${pid})" + exit 1 + else + echo "service not running" + exit 0 + fi +} stop() { - getpid $1 - if [[ -n ${pid} ]]; then - echo "killing: - `ps -p ${pid}`" - echo "try to kill" ${pid} - pidCount=`ps -p ${pid}|grep ${pid}|wc -l` -# `ps -p ${pid}|grep ${pid}|wc -l` - if [[ $pidCount -ne 0 ]]; then - kill ${pid} - if [[ $? -eq 0 ]]; then - rm -rf ./bin/${module}.pid - echo "killed" - else - echo "kill error" - fi - else - echo "pid ${pid} is not exist" - fi + getpid + if [[ -n ${pid} ]]; then + echo "killing: $(ps -p ${pid})" + echo "try to kill" ${pid} + pidCount=$(ps -p ${pid} | grep ${pid} | wc -l) + # `ps -p ${pid}|grep ${pid}|wc -l` + if [[ $pidCount -ne 0 ]]; then + kill ${pid} + if [[ $? -eq 0 ]]; then + rm -rf ./bin/${module}.pid + echo "killed" + else + echo "kill error" + fi else - echo "service not running" + echo "pid ${pid} is not exist" fi -} \ No newline at end of file + else + echo "service not running" + fi +} From a36b4a80a9eaa0feac4d5b1e483b5aae66c220ef Mon Sep 17 00:00:00 2001 From: kaideng Date: Tue, 18 Feb 2020 11:31:24 +0800 Subject: [PATCH 174/190] fix pipeline return feature bug Signed-off-by: kaideng --- .../fate/serving/federatedml/PipelineTask.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java index dc82e6e7..95b2e406 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java @@ -18,6 +18,7 @@ import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.webank.ai.fate.core.mlmodel.buffer.PipelineProto; import com.webank.ai.fate.serving.core.bean.*; @@ -86,8 +87,11 @@ public int initModel(Map modelProtoMap) { public Map predict(Context context, Map inputData, FederatedParams predictParams) { //logger.info("Start Pipeline predict use {} model node.", this.pipeLineNode.size()); - List> outputData = new ArrayList<>(); - for (int i = 0; i < this.pipeLineNode.size(); i++) { + List> outputData = Lists.newArrayList(); + + List> result = Lists.newArrayList(); + int pipelineSize = this.pipeLineNode.size(); + for (int i = 0; i < pipelineSize; i++) { if(logger.isDebugEnabled()) { if (this.pipeLineNode.get(i) != null) { logger.debug("component class is {}", this.pipeLineNode.get(i).getClass().getName()); @@ -111,9 +115,13 @@ public Map predict(Context context, Map inputDat inputs.add(inputData); } if (this.pipeLineNode.get(i) != null) { - outputData.add(this.pipeLineNode.get(i).handlePredict(context, inputs, predictParams)); + Map modelResult = this.pipeLineNode.get(i).handlePredict(context, inputs, predictParams); + outputData.add(modelResult); + result.add(modelResult); + } else { outputData.add(inputs.get(0)); + } } @@ -122,8 +130,8 @@ public Map predict(Context context, Map inputDat inputData.put(Dict.RET_CODE, federatedResult.getRetcode()); } - if(outputData.size()>0){ - return outputData.get(outputData.size() - 1); + if(result.size()>0){ + return result.get(result.size() - 1); }else{ return Maps.newHashMap(); } From f082ff933b866b2ce20b43f37eba464809cc6887 Mon Sep 17 00:00:00 2001 From: kaideng Date: Tue, 18 Feb 2020 14:51:32 +0800 Subject: [PATCH 175/190] change return data Signed-off-by: kaideng --- .../com/webank/ai/fate/serving/federatedml/PipelineTask.java | 1 - .../ai/fate/serving/federatedml/model/HeteroLRGuest.java | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java index 95b2e406..39817885 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java @@ -129,7 +129,6 @@ public Map predict(Context context, Map inputDat if (federatedResult != null) { inputData.put(Dict.RET_CODE, federatedResult.getRetcode()); } - if(result.size()>0){ return result.get(result.size() - 1); }else{ diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java index 23c8f732..85dbd846 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java @@ -71,8 +71,8 @@ public Map handlePredict(Context context, List Date: Tue, 18 Feb 2020 16:59:32 +0800 Subject: [PATCH 176/190] feature: add proxy image. --- images/serving-proxy.png | Bin 0 -> 22914 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/serving-proxy.png diff --git a/images/serving-proxy.png b/images/serving-proxy.png new file mode 100644 index 0000000000000000000000000000000000000000..7f003801b631d0505746cdadba5e7f035a038139 GIT binary patch literal 22914 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 Date: Wed, 19 Feb 2020 11:40:12 +0800 Subject: [PATCH 177/190] modify: remove fm. --- ...\347\275\262\346\226\207\346\241\243.docx" | Bin 28888 -> 0 bytes .../Fate-serving_deployment_guide_build_zh.md | 214 ----------------- .../allinone_cluster_configurations.sh | 17 -- cluster-deploy/scripts/modify_json.py | 48 ---- cluster-deploy/scripts/package.sh | 218 ------------------ cluster-deploy/scripts/services.sh | 73 ------ .../ai/fate/serving/core/bean/Dict.java | 1 - .../serving/federatedml/model/HeteroFM.java | 128 ---------- .../federatedml/model/HeteroFMGuest.java | 87 ------- .../federatedml/model/HeteroFMHost.java | 46 ---- proto/fm-model-meta.proto | 36 --- proto/fm-model-param.proto | 54 ----- 12 files changed, 922 deletions(-) delete mode 100644 "cluster-deploy/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" delete mode 100644 cluster-deploy/doc/Fate-serving_deployment_guide_build_zh.md delete mode 100644 cluster-deploy/scripts/allinone_cluster_configurations.sh delete mode 100644 cluster-deploy/scripts/modify_json.py delete mode 100644 cluster-deploy/scripts/package.sh delete mode 100644 cluster-deploy/scripts/services.sh delete mode 100644 federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFM.java delete mode 100644 federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMGuest.java delete mode 100644 federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMHost.java delete mode 100644 proto/fm-model-meta.proto delete mode 100644 proto/fm-model-param.proto diff --git "a/cluster-deploy/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" "b/cluster-deploy/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" deleted file mode 100644 index 79598daad0f858a09d379a00334bd5fa5bd40803..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28888 zcmb5UW0dY(vo_kcZQHhO+qTWswr$(CZELk{uU>7Pex5z{8Smcbd_TU-k)%@hHIkXC z+{LLN4GaPW@Xr<|cOdZ3`M)>FAHu}mSi#BO!I@s+2Zr*)fcO`-0WtHf3kU$<00aPl z@SiY42M0P2TbrB&Mf*Sol#t!DD}IEX0{?^MWUR0U%XOLISBp%i1haWITh_N*brZ#> z&NlB^S^2)LAoR4>``LWhG@^$?hCjRrGO0Q^%zov1*V>m-(n%c}bRFwhqlOZRBwmwkQFJ*F&xJBCP6ZD9=lGIaj6 zZ&D7?alPw%?>q6gowMyxO@0)BpA3jglVZ(QynG{jpILqHc6ACroKL}@6)Rdd#O;eo zOk3lTAZy~mh+AaR7_~$avCNGvvP2<$MJh5xaLcg^$YE()yFn8^xAU@3{Q&r%s^C5! zO_%*qh2f7X5dTvZ#`aF8|LDRuaY1f~044NR)+a3V9qlZ7U1F2e%Iz2l*_veskz+HQ zR@A`F8@0}tbys-9)m6;c$!_oabrwF%rnjo<47OT1W)@KvB2pkqjH+jLLAWEljKV3` zdZJ29IQD68zMF%8D%h~F!ND9Gc$L~gk-%~+cw%AHdx*i!2$G6LDEAN!BP$^stQduO(Z+TKxVT?I5O#xG205~?2hZmEi)Wsid{*nV z2httuAh)rEBfQA_blJjYlg*OcH#$D=pzQeC*KbAR{H6vZpD3x&ek>UDMoZfi_I{By$1Wgz^(a~$6^zMd@P*Hz8+%eDcOT55Hf6l} z`@zE@uS%#NwQ;|f!*><~DWjw@6ZJ&0u8fA1JQ5zD=91k zkNiJm@9OMgZ~N~*A%4axm;podIiy#3oR3V*hP)6?)tpVYdH(#0bTq^Nl`x`J>Fs8Q z;d9;n)sY|WbjRtvmaHD)v}6HQ1{o+|ehWq^kLtzicM&WI-(RmY@*+`C_5JWm)8(#pbd&G0fc<%Sg7!}Ww1*V*xPWF_SkGszr>f=w~0@AcIV@IyL> z(c3Pa9?}2#RR8~t3+Ruy-0huA{+D4f!%}@|fc~*NjsO7g{|#|=@w72@{zsbEy0T82 zlZZQIRPF+fDKsVtNTO0yOLaCAkva{Z$qKXD@Bszk<^V%`W6eGxG|q8b1Z@*t;$B$5 z2t)yd3j*q6Z^ZI$KXJ4E2moq*B7(mepx3g)0N_K6ntSq7{u|Ptvq9uAS zP8v0gC?xKF$&%urZvAaobziIaYY_h9@9SS1j%*m1=uFr~j!rfN@`Bq@D#a-kzhf=h zv|j3&xC88a#_m^k`;@E@CaHvbmQ=vs`GqFObxA<9#f5^1>Y(H7+JwC!f{wc<7>zNJ zJ+9R{86HvrSztAjBD!!u7S034{I7T5P|fILB-Cy(`S7(UE{4hGXKj=le+r9}6V zECwYrv(K8I?Y`%dyJBTp>+|sp_eK&*ngvx+JKq;M^-0<10yT)Ov>=h}yx-R9TsnYl z^r2^mCtV{H5@z&?jzUX@;L(c^fqcwuycqzn4o);C3PHy*bA?*0{n?raI;iFz_H++- zbPtcZK}fnmSh_(-+a|2FBIE*X#rOQ-NsuZ@n!JU%BLZ^p*)SxU-=@cWScV3-+xf_nGpio@>dq@ZvF&f4j4!-RHO7xVb>3FvN+;cX_Uc+@cp#h z1ddpGPkb?=(P04b()xZFoQ@dYHLAf03JTAXhFR9+T#DcN9cLX43S<0&pG%n(Co9}l zDLn4i;5>?@%Be0INFwwHvoc=6F^sSJu<2Ph(M|hzzOYW{4fVlE!oYpFLda>Qtue29 z3ARUV=Nxia%?&J%`u0B;?01i@eM_2GOb2ggWq`u`Cu9n5pMu|349|H)fS+AnS(KfmyG?jUn#T)@A2Um>#+O<-@mPu7W!5%W7B8hs!Nzj1m)+zmgA?3SKq z;|i80S)Y{Wh7#=Z9wCz8NK#V`wBbVK{-9+GlD6)3#BpByiVG^j0qn-Mvzzu>j(9(9 zi8fq5u>Q7mFAEf~EjBMZYoK{xQG?|z!qeOdyG)VzDE@}(VqMb8mN(kwe-j2!X3I3L zRNj300yzl+k^9yi4_%#sIr!d3?|3o(W_A63IWKxZ8}%$ajfc*$u12%%yT{I4z*{09 zSHctaj+wvvy(i8PUWPH6m9RJW$Fl%LCZrNO;kK&~&p-q96uzV}w_f?p(DQ^y$C<~0 z=!KXC85=|S7-Ll|urK2GTAh^FgP7|^(!+0v?TQ`hTa-kb`zjYqJnID)1+{CP*yk7e zJIpulpF^h<=a(#jffnhMNLU((4XaABe@`SvZjLN!g}}%Z&0f@ER$#y!G$?Q$wn}`6 z;-HqKM8BrEM8BSBc0wen0mku!^1gh-?Y$?)!unR!nN6>#3)>hGSDiZMiK(hlsG*k{ zXpUj3T5Rml+GA2Lw`)DsnazD{YtsmK;s1Akf2|=-lRmp;ZaqYWYyDm4P;cu0tBh!f zl?Ev?&pPkMOCkI@zy}G39@QDpD||(Xep5+t2q_aUGGG69J>UNEi%kKDu%izz9_*|Y zr|{oCWQGsesdf)NNqcSA+H9}jk-WztM(`qDeU>!_xAz4J9kwNuRjSN0@`Uv_}sB)t64QI!Q|OC~-%pp0c?=wX!gfMjlvi=#Ql&vkNb{zxAlY6FbFtrz- zDS&7rrM=*a461$7A0h83b<{_P(4eyEABm)!;cD94jB7Gza5w(WZmg=-y{w5Dc01h*jII6%n6uP%kWcY$slD9NbD7E`=)T#tgZcG+hc!tO7^O+&C&AxeyN1 ziehg;HgI0*^-$Kdf!jd0r;3{4mOYR?E(~W+scP|1j`%^Ol>Y~T9I+9>{_p)}uYWL8 zrLd&xs@)YLY@QSml?SpUT)*}TEuO>WHN;~LTud7{E%du9OdCAQB)cngQ0_~eTF~sN z6s_+|k^Yk)QsllbO;i}p`e%Mh%2fxlq>U58k}9b7M2g<`WW8y&lshVDSwT-DS(8Q& za!DS_9TY#4w4&WnDO~*l|0f|*==Q@Y_^-K@l&XznNtq;tC6-g|h!j2@$Ywt+Ds@-T zuz_Fyyx7P|4%yF~6n`GPqV@9jI6`%(Dzl%B6#JweE4A7lqH0oQkrCN{LR8N0ST`Wu`R$<(*9u} z%U>lNB>8V+&nJ5TpR(+dk1*oEFSE}dTYu`9R=sbH8)mG7kHkF(rXi$%ZJiB4|H+CJ z#{=Zv&9Y|VdSzOp2|;Zdh+CPMG556uRA~wbmp`$Wt!QXh@N($~OiPjD8bSuHlw-n% zKx+y>1DYj$>7a>f;0eS{grc4)VS|%h9bDo55N7-vX~xr$|8d7AozgK7oUAdJXy|MP z^wsMO4Yt6B1k@pOolyVhu>nT3=Hh|{T*5{^kl|F!h91TJ77yL zd5d;x1SfU+@h}{V`TKLd{NHTGn~gwP&xG}W^p*1ONT)@uoTTXnq_)P9R9Rkj-JXsd6 z`W9nVv@f|*=*P8{E{-wcC6~;vg_56C*jTReMzOV6Hf``&`7Qkxl#zK~xW+f(5FC?& zJhL7PCaH~H9jDP3d`8@M440~Li%Jg&P5zkyB%JeOb#2#HbFDcu%bi)L7r69L3HXD3 zU0qvgnvRP0>{<5H!nC6l{M$Bl=be;oaTj{AX>*ZH12L0E`lD|WoZG@o@SrPI7!8|r zTbQGFzYzDKMy39o^!?9VHQNyr7WAiHAkFz-x$3_OfU~KKi>00Uzwv#E_PP_!Sc-36 z_$xTr4&_lVfefcpi$zkSP`=n)h*-jzA$|A-7~#dGZzXyl4vsYroBr={?F`R{8|jn0 zua8Zy$Acw~>(tGeD(Kgob`M57w9U=qlcB08{hi0O{euxZejB^66*uPI&KI5=y04E< zzwfm?ygIkTUvYD8^!3AL?k#%Vo$Z^qw{>*%?onNiyjQ)OJ8{?myY^*|^<#NUVN;I` zzCP`K9lvXD!#@9*b!gr2>r)YKjaX(}S4D+cI=gBe9D8-7w{~5>fASn#y?=c=G3I`8 z&!vB*_{K3`y4}~~vA+++rY*Gt!EMgm*m?GG*R_7Rs}CXG&PMsX6$$%j)y?kmO5Ka= z=f=Lb=h*d4zo^e=k#EUwjOdal|Jt(EqjqI-> zpLDG>y#{ZJ)AefZ$PBrdFoPJ38uhe$eHu39gl3@zUnpiq-4ZJH*|%My$EiObHUlIP z#ER+Y%=$a}GVkd5?Ocz!?%F+d4eh}y1nsR}$8CN-4zbwcv$gR@1CQ2^mH|TKnl-_{ zyLI_J?O#js{K^E-nJx@S8HGJ^XPh;9Fdf;v|GcZ;=)<&^)kNBE*& z`t#&BYT2g)Q+4@n7*Oy~w%B<%VVBnRzH;{(cHR7eOOCDfXlWOu_v?%4Fza@1>Wg3B zzLlTX2JJm8DE-?un@zvSLC3+7U1kWVnZr=Sf23JAr!5E7H4w+QW!7>>Fv$Gm%AvWz zzD$1yXPv1!bjGNARIQ59Zzs<69e{4G@0ae*%`F`M_NP zk48^c=y8C_SisQQb(?p3!DZL+`{&#t9^1j)wZnTKeAj2zvDt^A&ad{(z8!zcs=Cro zF4*VW(cPy$x@Y$=VV>*nDLb1XyF9lk!=y6DmzRejE1t>KA-k*VnLE$-`tOtXwJhy% z!Hv%sfQW2|;Uoy0`<)oemhVlcrp;eI>56asQFt*sj|(<|{aJ>v`~0!FGW(m?*B*fL zL(M*=`rj)2r;y;DVOY2b``EUGa`EH$0{NGpr>N-zM5_)8~GKiMDB=pzB;Hz1f_ zaDRJ-=7T#&hqFbu>%k<{< z>)2d9xj9?-QXjF`gSk9{a?EXtpwR;(8JHwu6Gbu9t+AnV<{!nJ;eBnG&gN0Z@a7Tq zA+@BHh&_~baW?WogLXh8=n;VklNR%;ij82H)T<)fKSGjV6LKrHki!yHo%b*YAm-v`2MCfx0lu(1% zw3yFjk^VxG`@O?%CsK84k*QLm=P@lMs&tMgD3Y_%sua98UnCm0n#FCE_70y`OF4yic;s`9tz!SI;>NYV%K4`Nk2$Qb;`pFrOGjsYYD?q3~QzF9J;{@ zkxu+AQ?v9j{-~Jg#KJ_+TpR=wYb)*CL)wi_SYZBY&eu(e=GvA}1X&k{I`)Y|DHLoK zkfRd4ZDsCARRYt**e-_Bqm(oezfco73wDNR>7h!Thm9u)CSd^}$(m)+z?v5W@07n9 zR4IgBY(8os;7MWAf(7-WQ$;!%CzoOmlUzakk~l3o%cxb$W{?ppscM2Wta{Dw$=7(d z>M?v0cuUPTLz5(8yW?Wi0{39f!33+XCz2K|M+UtTF;zls@QoOLn>uSwm}UkvA_UL5 zPQrqlJc6BGU1ZKJ1voZKh!4IF8E#4{rOPRgcJ3Is(Fnyp+CCdC2M)UtE^!W2ZDp#K zcACu_x~lVysHTeijbW!seGZlMf>p_u49&`UnI4kD3QA10RK1anl)M=pCu7)9V#5?A z)3M#(S&1R99Ww4t#$5xRhZ^LvPgoRPxUFTG5tD{H02ZUG5F6fTEd&he$(|sYB2-)* zMBbTybcLRNMNv`oXL^+Sys1W$e+@K!+sKG$M`l9F<|$=bk%~Jhe3%-wYbC+3CebPK z6{)0>xe*~kU5gF&TY|ygb46^PlXFHaVa8HSoUCY)qk?1!KSVijQ=}51tzVB_3>PGb z;X9hEbSRC%x}v(W*Ntr~vk+k{@j;_5#<(H9k#Hz{VI`8amxw8g z6nYTWC?VNe*A?xS0v`khX$TgO<|ys@kh_$GOqEcaU;)AvX#6DOT6IB##5=lh+&qA_ z!3RNS*fqye@@fa8a+A_5iI-cRN^kHlVs(=c+sJ`l$T)U8W5isk=h|<+b@XkVL(5nR z$|6Dx6l_E|rmaX>A}X=V1H>53(lSy)eFHa?QxrBZGH_MPC6Xf7k?LV0AJr)5S*DIq zHOyi8qheW>7=sMK{6SGBP78*@nAcHGXLO|o&TSAxo}n7>>{97K!Med#iPPnEaO7Mo z4Zk(b)zVXiX=<)9?i|_#T!m4L%qa`gkhRoo6gMDOeCg_v)j{sF)rTZ2V?|ek6eQaL zb`4Udu?>TLTTBwOb>ajn0-XY35~Y<^t(5fdg;q%u0vpzL{9%b`yIKtN4AI4T#iJOJ z(|+u8riYjfa!Bl{vlC^o??yna~Z?bQWxlUf`6boNuOL$^Tw zr2vUKIlA=G?r1p*eGxNc)&pyjIhh@$6d~?b`BWfJz#%T`-@jz9uoF@F)%U_xjuB#@ zWRRR2Cl&2ZVJ@{XMV+bD*b5&i?m@LRz`(3kV7s{nYRU^qs(zgpmWxRphr=}ymIOm0w9Ltu8@LPly7I$^exF?TdQ=Mk6x2S3VKP@+c6PQ8)REgLWoG( z#JZ_9dKKDHY)Gd1o~8B(4un^)`hJtQKl&T7RE3@C#cNQDwZ$?)e(-LVfvnIJU`OJu zs*AA;07W*7NJmr1xt+{JpFy=C1!g5E(3%v^IWkKDrH7{i0CBp&Sc9U@1~|-GmFXfe_>V63r75DUlEs(_9W zkhC*bakI&mu44}~GPgEV_d^wPX0BK%7*+8x zQ=16_2y-i_fr>O#E5S>MC-~2AR7Q(L9ml8@-Rx7YY8BzrgrjCr>z89Ab+kfU+ZeWB z2`G3O;Q=!i3ad%lbhDm8Vw95l=g(S%vuOpP%xe?fqoGo2m=YDOx*RtWsZDDI@NqTy ztO{W#n_y@pha1n#m7pgWxTQGF7U%{HHFEM1yPN7bjHBa3+0ha}T&G9>81&dy;ZXlSYLU@*eMqpb+u`htR|VJ2d1Yw^7q z2kF6nM`U^HrR;(xuIi8+A=%tnLA)*Gr4W#ci)uV0Wq7uR*_@e%aIp@5E?BH87+?yt z*ddYz0VKmo2vKR;R)r`KIcpCvrZOoVMZ*=Mt!RLu4ERu28r^Xe1-nVfN&10WDmRkVloSSYR<(BF84Cz&hCcQuIbC&7PO zn_#9ROjPWKi3;XYPv@UGw-KDkbWjTsTtb1m3Ia7yB%{bufq0|$pu1-FR3fyPb^S5t zEJ~0!FBLGFGPe00Itd_67CM>9q(*!oy_n!iIK^;D&d3-xFZY-Tcx>P#E~qb`IO(A# zUqp81D_6s>!lQ{2Bp`4x&=tr8{RPymCB;`!9nbEH24tHT2=x&HbIFtQ9Z84?!?6`& z4cbMUxrr|kIw83C*L%oSzW-!fH~1R7#%m!%5cxO+NJ$!j_L9aOxL4dL2iAhui98$jL4^{(40V|yXPW~>4)Ixk&a0MFjksHW79aC?R+s))w35eHVo1NQh0NG#5QM$pxqfL zQzTIaa>h9&lduZ6=I6%dmI6L$t9vW}kCV6%@EWvKr3f~!hWWnoM0STTW<{_+gj9Vk zgioAU<%C1f`DsS`GvJzH#^V@@j=Lc$^Kt_em9YpmprC$|>drac+b{`YTe{*CLGMDF67C}8Ag7M)2ri7+Wh}R1IQ>L+>};|TFWRo| zgF}lATw#pE5vAS!oT%znkG}p)UG1;T*}fBSSo+nmF$7bMU74xsWkC^JKkzFTzHdN|DY)?{h2wpiLuOU%WN4}a1wUjauwyucqW4xYQxj>kONa=EHYFJDP_ zU)DW2sJ(SrmQ;*;M2L3-c4SyPg|J5{fu`snY z{a>xah1%QUm!GyG2_^sl>i-1(3upYdfv72KkIjzIgMQ|xan;jca!JfY1m4Pbu(SM&^%rM7^&w)u>mIGk7n^^on0!SOyE}_d}AdlrE-nI zhaq|uHSEjmla1L+sIFeS6IbCL-a$ZP`F1Q#x;r!x4@v@=^>zL0xoE1FoJ0ypL^Ck! z3kCZ7Vl@n3M=*7C!TwJL%%i7uqVn{;Ah*EBEJ^UU+@?~RzNJccu z=@wz36#gg5FOR#G-Yt@l@Ry|N)@ z-MRt9WaP32X|F=P{pNM!6B}qnQ7J9UXcVENYR%X==7A0XC6Pc9L0(3QS3sET2(8Gp zH@r!1NV39WflPep$;N^rEo>zw55p;Gy7WyVTF-c*x7+)2ejR1Lb02g%2LFrq*UR0@ z0DT7WlodK@f#u^#82z{BpN!|{rrRB!w~ZKdvah*3e!tf-bvpu^Le3oZU7pX&*CqA1 zK9A2sVsM|Vq5#O-Bn;?VF1|l@4H#&pMMlhHz+ZM~pAd*kM%<8~Qt_E1`+da3(7wKr z6i$0?^|%AvI3mNK-E1A^h&Q4>L=i)WdMMicz@ukyhqDw{6&=wG&*YM4x!LkETeQJ& zy3TD1M#pW;l7hSuQ>N5|Xfs(Tq(~tg1B=RYzcGx*E^d&FMjriPIFW5gdb)-;1&1*c z`7kwBSlBm68IMdah#+2Djhr3EHVv0sF4*{*6Y8lET;p$ztwQdxG9^&DbeHfx(Btm{~zz~igx+P1ZdjV{64}XL66quNF*(Llf^*mwafZe=Q%tDXwXflY~(H)wh z+SfygOITe3R=llrl}CQIv3dt^UTxFlf;i0cGv2!xqVeW<=?1gHln5!KPesoaf zj4hj(g~2xL6y-%fdpolJy&LsV`RVhO$fItEVBv}}9lQBzsIYz>MNW5PWC?=5A{Potqcfh+de4!RhlKe^lsvf5b45*FFs>UmD4pR?U4-ZM zrslyF0k09S%QYOu&gxuNvjpPB#vtTQygw7~(y{w!F5D326FGM-n|@gM2cXBpFbZFq9(TQJr1qnJ|%>)!q91o`=^H&QHnE{A9nTTWlpX3s+ zRNb8Ehv~nS23n{Jf#}$tpwrCT3Oed^@-=kSEy^YvM25*(vO?lw&kTqK0H-SzV)DX= zy3X6_Wy^(SLJ4Do`HFoX`ZTkd*7e%UV43Msg2=b!=WA@03QWSuY@dia+xy1ZLe~(k z<)SRX=mx?QO|X7wrji};wj_l#ds;3`tl<&ETSX*xZ&t6OSQdq>BF@xxK?FJ)PO$MQ zQDSh3cI23GVjdJT&Dg|Li|gtNFEZ5G@-46H1Cxoe1lv6n9Dl%E2JHzc8mxM%fiMsN zZ`~&~S%87+NT3O`)^>%ACr#IU$jhxMu*F$o6m}G480Yr1CZW0Bv=M<1wR_DV$`KR0 z5#fYZ3}*7RwwuqeFvC)GpG}BSIfYqHd6$$>S6H{H%w`y@fuS3Cw3FC1W`tm_7q9Lz zTu8c`7%Uh8TC>POV(e@x4F%rc>*>pGlL7>S~506 z+EIB2IdNffD;|QjjaCNdE!=9C6IS{4()sD@2Y3VB42qDkvL+~4HR`EZ zV?!g;_q20mWJ#o_s!&U3>M5gZGjcYliP zjO$h=<{1cXw_h#M3hJrT#fj5U^WTd|6UkXmc6N5RE-}snb5ygIFrNLAw6Y~ zcEvx_vq0RFUPcpTpuKChh|$R@uhRZ965cKkf4;0V)CyK|2-18Lo=rWDH0j5hlDP*r zs5dvO2C9J+rM7~dRZ?3m2{l`gW-&$O0LcxhuokK5=L^YPZ;jB=7*H`x$x&u=A-!L; zY%GH5JHnB`H;(vH-RJJ)co7{}>iM)>EF*QBnl*V?H{$NuHfLxX>1FmA$k?2kd; zX#Q(AL44vat9If~G5_3?Xi7on#=s6H-BDLkDe01<)JfSgkv5xPzU<|+nKE(Q6g(cN zkoB;7!1-swXp^EvOt7(lCXS+_90OfIOhjUGRXLje`%&EB`M~?nM$YH9{g0t)w)Z(B z#4|K(L{PkjkOhup1E&jp`CXwLTH~S6=Wm6XUmu-1R91R=qIMEnX~=$xE!4EqKXqJo zDa_uI{!5n{85vY=Xt$?b6`Z-W*v5Zp&~+*QaUPPeMvIyDP}C?W=z(8;T8V&$u~Lwm z5;-cu-0#8XK7_e!?u+TZ$=~|4zsTg_Md0LVRU$wcZ1a2x0FQJPsse<8i6cPfC6T}v) z4~TN}FGhudaHj@B?f*g>uGLb@<((YpgK}n|0r#MxYjl5z*`!#Dxag;xmC*JIn$PA{ z5ju8FDWbo7Y%H{#lUyEbItwDb3?^c;jq>N`?5oQeJ8TQ`0Nd^}Z@biic|W7MG>2)C zN!~vP9rHPj1dY=-8!w*)}%FyN``-rFUGZg znlHk2yY4-K=hbs?E+2xl?mS13Yo53^xCg zbLF<}z_kem&mhF%yNtq7hcO#?B;So{d`AuyZ1M)UZg`%&%rvY(P&q6JO9z6E3)~uY zF69|IA(NcFQ@=wPh)zo0a;RX@0!gcZp0CNFpMY+5Q@8*D-R6HMUt6Mc{M42hPxiiR5rZ31Dt{gOzd&@%*XZc{Ar)t{R+3ys|Tb0vKWqq z0P>SFfq|fz)v}5Yg{B0udl)Xn8Ak^umhnL8I;mt6B>A%pR`=zLn1_ZObqAv4VB&SU z47KSFsRHjwq?IQU;yL<~!!f$(;JdPL z^x^n%c{%ovXJhW-pcH0v9s~(|#ryl9qy$V>0EGxFWDwf$JjHH}gP7703ZLr`Kg&#gcAHQ(M8jv>>8!1Ln$_2_o3-Kuqh`Bb2T zmf%)$pmH(@BvVoZ9#2^a$&mR=c_%K6lr8aEtK2`?7FHe!S3Ih{Uuv8`jgazpS>sta z7SM#g$KBZ8-Tb>Q?g{^MKwr778(-!VMe_sW7H`Od{f+nR83K8{tw<=&WX{wd_7$v9$Cl|GpAVu9b7lNsGUPQ#_D0ei=X$4q=9hi z=v(sjlwV$@Tp&7)*gT!`GM#6fteD!tI$~T84IpCJJ5N%J=hiX4h zD&nuY(PJ;#U*NYE!j;QnZCZNK;$qRa(6YLg@`@5G-IFK`K8ecQa@NX38%s!~WxC52 z*SAn6peuv(`CLG~f|jPeFf5)FTAiojh%19cGspEV%XQ5KUzeBNV(V1;JzI@g$Uk9z zbhcHGHi2KL(G9%BJAhH?O0eo(?~u5(qB!bZS*eQEPkDIy4Z9uU0Gr$R&wlP>y?-h` zS6JWQz`^VM7PnrTl)B=XsGqj0!1Hv!&hJ&{7QqnEr()kN)0js;i|Vi1^YqLF1p^yI zU36&=8I`~9?7q_578|{|UD40rX}I00o{KW1R*A}QPf~6_U0DV@goB+GRfw{=9kVl& zaf{~Bk&^*VfjAzQ4lkd+-#>Rg#jlDWzYuIpy?NFe6juQ^WidMKaCJI&KJL(7 zFLtos+w=tMAo6LaPvR6eb*lB@Qpk|`vha!97Am}^od+lk@qzmYrjIwH{syr>A9D>GS^HZgIaUMmELh>-2l5?Ro#& z`LyQe?adf3y`pBn7Ki@{>Ca(|f9b24OqqBG=#T6$+*;j!37;6u1i72O8LMyUqn8^AR(@iCq|_UJ#CyF2I|PoO{gY=ZkQ!!Q;hPtf0kOy;(J1A2@R9nH2dN z3u~PvF^n$V8zk%TW#~(toSZrI7&S5^(VQfPC{N&Y2P}AWnD9jj-)o3ABPc1tRt*<; z497-Cc56zkeEcsLuJ4ofUBBaE2O!*vn7O8n=?_GB7Gh=(pYj;4 zzFy>pxl>N5x0n&_xspzo;&`%}$~x_HXHGr9c<&Hr=PsEdk-QaQi_lT+g~6i`!fqN? z+wlr;Sq>R|9H1XG3Wu!3F}5P{L=lha9A!VEB7umh&q4`LQrniH+Iv$9;@17rKiz3U zWAm4*nZeyrj)%tNtb|VVolgrsZ(``ufm$*lU`XDwjm!A2{)P$Xrafj>SSLz?S&3se zaml?djmbIX{@U4j9sT-?1SNOtLFTeNg-$6VvO=DjO+ZQ=k_j2KsOT^n0YvP)oaOtN zMz$3c)Xjd{Gl;tIY?yz`F^&!HiQM-rB#3S74z66li26oRF+Yba(Bpp-qYhl;?R>R` zjgm}35l2Mf_qesOtYsDhV)hrW1vk9N803#ezaRy@q8^^8p4p#u6*IcDOexd(PLWF_ zuP_(527bOhPATc+RPlt=DVtsl8MFuVREX3uuY8PRM`Lo$D-vlWu~k)Atk4=G==ih6 zNyYubLEgy0lJ@7szD7|T^KcG0C@GZW-{P$tf?d4`)c|HZko%)u=`l13VFx>!!#(Kr zZP}pKiLuOLBx6Koy(W`Z+M|bvi3CMgPbkk^BLlnd<42~@&o_X_`K}SGq+*b9iA;PR z5epYFVT(wMMDK)S3#SNR{XLK$G1p8X_6ka?uELt>{caicYDHAs*7yErBByqL`&$PZ zqX&Y^98kh#<;PrWx^)u9>y5u|MrycdZW0>{R>Re0!e#Df5&d(Gl^(l}+kj84>( zq(K+yn&Kr-O2@Ns{?6!->Z3MJ;9MoipX9rqK-pGwB+{TX1dy@KZ)K;IbgqLHO>}}8 zUlyDNO&3RXy$pe60%BXpm0j`kZ1wqCeqVb(;p=-l{pk5-n~o4~gAwAZUJiXH^m0jL z+h*2y=n-}g9?GdkJ5o?g$1F!46a!*f(W*D8bX7pt+-!pG=o1vge4%oj>{?d=_80~Rsq zb-H#;#zdr@7HdVN%SLGP)EiX`{(w!k)X5+61+8e8$Cq|z^0MtWigItF>JhzQR$%R@ zj9>)hX*C}xvI4#im&bE|w!^D>o6%o4@vKP(kRU)L#i2Ow^OjwbVOSnE7_v1o(cGrE zGE82*t}6^R%*%JkNvj@Hy@c!b4KRQno%c&@hXA^;5d;g^&0h(K8g78=^uWp?&%e4i zKemJPt|7f&t;XGD?qJsKg?g=7I|>UOyIk6Mr@*t3$;NN+45l^JouSsFHk1yy(`7?CX7z!|i+a1^2tUqzW9 zIAf;oaU%1AI1v0ZUMT)xs`wxXM*a57NKb=$ZD@A_F$z*o{CDn`heahCWOi32qkU+T=nJfuyO#SXFs#tv)XoSZno-o zJ8k`ZPRqvBw*X`=dH0os*Jy){;U+i)Il%TY`0iLOmiGg-WQ$Ry<&bqsweiJS_!eHS z#$IrgWbAdads5M+2pS=W@1#>jRzfPNi!ZDiG*TNhBp^VQvo{;l;Kc8AdV+pbMhxh<>zP#$irwN%Uaj6tUWOT)<^FqLTko=!nRi zE$OOW90kst?JKQanB+d1VlfXqv^ zw5PW?XHBBXCfYsyGx4)>s%5x0(AR+22Xu^z=49tRXah7v zBBLtlz=q0cNTH^txl~PcN(NB?8I@=-1H#;Gl^fK&$xkNztuv+kT*0Z9@>dP zp8+gK{z$4;Tu=U^1Ts)G*NE+eq1v@USp2$_S4+?K0s+Mw|1{aio+g2Q)zf zE$xFO!55b?m;Nfl@RncewM9xe3I;R=X6AA7kQCRu?RRYb9*1vy!+gTmRBv$>Ed>J! zLr>zb0cAuPUIGIo&DbU*U6J_J#IwW+So5ADE4egDsmw@M-WWI?`ZL%x%$R9=VzWbY zx5z69QXK*Q z8Op)c|F1n4Wukp*vij~h=C#S*k27^hN;c%p=p@THGBR~GOgEKt{y??Jp;{-t&Y^#|*#o!>vU1oD8bx^WId>805 zFhr1@dgy|wVHNHBys>lRy*;65WIHH`vv{JlMVdl7-)NgPILaIwSss|5YK47ZmAr8z zeL7x)@ZDulxi~oNX6p1xQ;lw z-sL_cruiI?@>GeO*~mYq{I<#)EDAV=nSp5Tz#xAMiqJoC?~ff5aga zfH!|k^}0saewD*5j#Cq56KP0B=D9@);ydC7Zss(sC<1yqqP9di{|g~rG6nL$|0CpQ ztWatPKA;EOLYdFf@PPW|lHJC){4>Djw9LOBUk5v!`}8uz$&_KTlh8I|gx}*WD9W^~ zMK#l~GEK?8vXr{&!PdDWL$F|2H6&n90O!DBrE*0OJT``6Ab_Iog8hWl)FmoGfwEFg z!rHF9?XUbk#&>*og>{F6G)YRnP5O;%WI{4Uc+$fbaI&Zg9waM71SBxJt<>!6-M_US zF4V!>>WGU96_FMxI9G+JIE+l$7?yx}Ehr!mN|U*FbL_Z9?g{x3O^-b0OEoii>TGN& zT84{L7-S$-Q7frbRD%TkO(@?4-eXlhc|{9BYB#VGHaU@y^+zSV$OH$kPws5%Sm zsE=9q#_~;x(=Ucrt0fq?SC#a}?eW)cGq(11qrJ=Sl8y4%X@*fI!01Y@-0ObB_Zmk!z@iXhmqxCVX=Pd1!VPZN=)HgK@$-QW zD=Ak@f05UY1YCMuA>S;0;qmH2jRP4x7m5R2R1cFtG`rUC;y}= zz29%{PhIw#_FD$(dz?H$A2A=eiG7M4U}x=%Mwc)H;*1~uWOS6iztL2+A<~en+9PE_ zeQ+2sEGAv?*%Rxx`5>gGuMQrB<|&`=omQ|ZZ0yLmBzy`Fk_(6clVEz8Ol z*d2!OP`Y?n%K&aKPhhj5-kxq4FVAvFL<`HgdOU!6r@NnTnVtW)x5G@Y7qAfm2JNtd zuf4If00^HXeY(cz{(&)%28j3slw3Djow{CT3 zlSB4cC!X@#reCrpbeM@H@fs)%Ru-qSyH8mC$J+|^Ob)-dPh>#`bF2)9O!)jhy@Tc~;31I+ zL@Ds0Tv>RL5IIZdZdaen%xmVv;&u-0teuJgYhv4ci?B#k&#-ttSf86`AScQiYeo zyY1|>fgX1<3ucH0S(n)7aV?k3b75HK^Jl;OyKBTUcygbq(*?^?=CUG6Fp;h{S&yRa zseWM3?yH^ekV5ypLiM#dYAIbth&%sz;BiQl0rJr|Tr4_%x&T?cF_*yPBWIR-Nf~66 zV$y@>Xf5SwHf+;>nhkqFT0h>;+&@Z!Npe^>L8j_o%PgrSvJ{OtVZ|vuN!UH139KXX zSi&TGMt`1KqCRq)J|88b4Dnhq)6SqsgOV0iWfIiU^Z)eq)nQdG-P?4>CZ)T(*>ra+ zQqtYsDb1!gNQabkN-3eBbR*qeN(o3K-$u_l9{ru~eg5F$g1INwGtZh?vu53kgHvin z{19KWd5oF1Amt^4{kk?^8nqQVxnNi*uQ7{t7MGH$^z!?$vDJF>9Lks|4b8YExc0}J z&nI|>SEv)Jr>K)0LU&}{Gzt)+eCr*7D9S3M!&O6dCG!K+?mHY!x1vEP_9Ohpbz_CL zzH!rU&K|*F6}d&Xukw6lz9Dk^giwW8p`*2wtyaRXS@DVCm3e!n;nM+>GDD_+oteL? zpa9@fv)ZAb2vw;2mMBokx z#e#Vs_hi0xoqZ4umM8D#h>_sabI%g#1QJE0lHAwabN)?zT0=87xK=Do zVh5fJvh147p!U;=4Ykamt2UoIm78U=o5QKlQMP)puNql^yYt0q`wixVMSs#K52+ws zFe^7Y_vugT%|Ln27b(Zdm|CdmYRzl-8D=K`;e6Wrm4$%OcQ-fmO%CS%O32k!Zor+ZI5T#RxI0WZEQv#F z!Lp$X5SAV3kgE@5cs@dUjPNh(5AAS0WjcLU8D>%V=9K?nRBU$T?){C^O8x!iyjns{ zh!jKiN4p{IGmbCTP&FkjYDV5-MYG3 zrF~%(0Of$OAa02uA*9ZIY$IL;{1~=!{5q7S{ZN5B0#mr&FoctxswVdwZIc`5&4^*M zLwYFhBrB%kczC02qpFtvk~VYaSn~PXsYTmhkr1tx;pa?WSId0}JIXp=KzlC+$Cf4% z5zGh%(D!{LbRW*|uOQ}uUHEA|mh4$)jQe8><++~{5f6|XYjdNUkn z_g{qaF2y&3ryT>aCR)8z;ED%~%a#3Bpw-HK0ZW{v3}m`Z2DJmhb%&)Jtf2)sF`kw< zYHTaBEA4}_J@wfFP*fLUL2oV~_nqzIH-Cnhco(o3lJK? zSorp0^&2cps+rMM1kW-+R19@_f9XAf3+cuAxg|-leW}NwyJw^`X1&)UexmhcU5}7Y zWxub3!IHIjFXq#l495B-$qJ-a%wwHoVlv!~C%3Sq?j)Q7*crKA^0t`p9_mp*#?IN( z)#V@_G2aFD5W5r}7@H2%`th~6UbOW~TKn(3&#kk@ZtUoSn3Tp`?J6*;-VZ#YJ>NA+ zm}uBQ*q17-%voXILvTmo+B<42Xe7-UOWjFL)8e8s{c;$_my5M9?888+J~&VhV}Oaz zXo+~wG}PIlb*!Urew2Et_pD)%6!l}W zhooOuBP$G<-Nns3veeZ9MZxxey3fpsn6qiKveq(Ca)-I=-P#JXtWMAD<4;4q+(hLr zEX6L-?nz_DLpij*Qtfcg6gqD)Qy`v=j77mB0xM9wpAS?YDghR0QdQS4Jb09{**S8J z&`GW;se(%_GN-V!ELu=3MzFXZ?XsDua9flz+x7>rO{GkkHp%O@w>wxoGFhq{!xWjP zd^FsN7T5+L$j6|+-7l5`?lCqi^;CbJXt&>PDbX#=32||Q$!~^b73A@ zuL$iHm{kOcMph)G=Ccpx%nXXicoP!T&Mr{1#Q}F|IUB&UCj}2I{nU zNzEmRET-f5S+=`B7VY|!L8$C1@-@y$Z`ow5qHBX5;6`eZK}aO03_c%$PAEUg?5J}J zfc-*HlR=_2bA&-cBLt?S+Kxe@u()HlO6}&;24Ee>z-x?@L0DiYw7RvL#!yhDBYA|2 zoJXv%prbF09@YLTM#WT|6O9b)9X08qW3g|hVHVs)J-N)c{25S*nv(+*6oLyf-$n0L ziiB@Spu1xt;}~B(3NR;XF=2{$A4oUO+cmYY167bWX-s#T-c6&Sjx9^(N6X12G3S5O zvwVp0We#7`m@4-0WpGF;tzIe{AmDg<>Jp9uG3baICw;^j&f0?zQouRB;|jRdmLC8* zr*&pg*?er9%$`Fnfx+e4{>y5d+f3VPYQCW+NK>{xN8_ck9EsL$JE#M2@5qR%D&`yc zw)PS3e;3g;sJ6vAA7@P1#X!g}mwg&no*8|$+`kvvd^lcjLgZD7yoa~cFwI94Jy+#G z49`tq8Q&*BwSzlHcx0Q4B$8!_t}RQ=63VXu9g)!BrAXo|(cMlW_)Em#@wmAKm!OCdi8Z1)wKd4Ye&FaQ%jL zG#_vvT{Y6ZuWn7$8@@Y@ujW@O?$0)#q+CSr8LkINlhr43*#obBqV5+25rGJsVo8v( zkjcJoHVWOeu3)DVU&qZK-S)q`YrN~}^}Ap{Sh`Qvi*zsut0QOw3`R)rnnE}ta>OG5 zKvEO55xkdAsxL>CYCP9a0192wl;jXk9}N&x5pSYDO*oiJJTpcu<^>{#N~AkJhNARd<_`C+*R1 zkq*KgSO5GdYTu@24!!wkd2G=~wNz{k#M2`a)Pog|NrVusXNgdtp`YYE;zKfDI;lYM zsS5~-L_Z}(>5q>@XgV?!x!gbLytvpWH@rxUQZ@3)sAmqMiJd(3GzdpA3Un-AQA>%v?X$IANF{Qlkw3WRO9F(A@Eo-G+h8r6PFUE&S$6T?#51iPl>ir4+Huu z#bw+ozt+{svTbl!Mx=M`lVeP! z;W2pwaq6eG)>;HuT;JD#CXGqj^qA11#>jG!_t6ggbqAJdK<0uETQLUsJ!LFdr4HR< zF!#lGI=!R>l&ih2GF(HBw4}~Qs-Vbv9F+z_#hhN}B{;7Xxh7b{;TYCI)u*x6&3xjc z&`4}3aCD=1eQF{2g@nhF-8n@?Bhc8>G776Zhv??U`@v0(O#z}~k)aV`vi9Fddi6-lB2s5CC(cSue3K!sDK)SU zbRP}8P)Z%F4-gHrmf0i$pI%b#So6oM&E&s2fEsS)1oxXIysyvp8nkAhgcGoQ`;n`g z#mkahhwF2oBB65<6synD(J^U8NJz8=d!YdpT!!2XGzpP zi#{2`LqA^VgbnecFrqn+km|3MK$Mk(YO(;sF85kGD}4JH7mucD@J&~6Cj`uKPDv=; z)%0i(zJ!JoYnOvZim_r_uhfT^;Ip_e#@9DZa2FdXr)12*#A)I^^@)DI#~VjkPC?M& zP?~`s>==-vvCv=SF-TA+=b6R5?o~XC=cNTzW%cJOs&obsqG=lwe&H+ybU24^HxQAN zP(gFeB%3LibEIPF2zHXlw)eYsJhJy#Ka>-`2U! z^(j5JFrnU5GLJ;CD5*R=S}(Mp)WSl<{?%%KzB#eF;S4%+7;3Qk+xMl2)Pqk*$5|(! zOE7@lvTYOV#d_2H`Evsrf@I{xx}=EaKrL?-5+q3)EA3MM`!9ayLNDiz{K+K~sZ8TT zaIxH^hhBw)4hA+)bSq`kOjo+KCLEU&(LoPJ$ z`ZHE$c2nq9k_}`vPG{>!N~azP1*(|?)ku!lbU;f$K-VLV=A-typf4!^W3+-T1{sZL zSvr|sL1sa%b+1ZBh0XI8b4ELhElDKGxF`Xt?dVQe-Kg|r5)J7gg_+o$xOJJ3S05H= zj|=cV7QOW4NifKLW~i5cVAblG=4z}<#E!-%Jt<)?MZ(;%2#7TG6(~+KOY;SQc+@}5 z$#gH|AwI}KmrEXwb*~N+wbleTgU>y1iipp2Zy6hhIXLfeXdDf6+ES9wlV((Sm8DVv z=EoB0^jzum7_-imW0^oe*mJf~9^Cz|0?2hy9pybV2kdvn3GJ~|xJpo=7&TET`=owF zBtmtU#%_|X8joO6m+MyVb}i=9_1Q>@Fajk@+t}XDKjzN9(+<&>Foo?ZI+13$W?wa= zz1Pt;_eygHQlh(F(ngvMo{0CaEsv=n(&wE%AA!xfR%GUnUlZ3>X0zhbBJIO`q%_pqW>^~OH;dH%0JaOANZ{H8o}xr|ShP(?^xkH}g6w;f zL=h4ZX8}I5&Msn)Let>5$bK-kyC9Vf9191ZULHLLl1t^LBMH(vW`-F*emYz@SPr+a zjfjoS)V)$;S*L#m(l*xOY#h7{k6w#Z|EAOs3Fm3kp1ufuxM{S&m%`N%FT^cO@*Gbw zE{D^Pa`ZYUH5so!4j-Ol7+{e|iq1_@mfc^&YFC(*)GjlM?9MB98~8q7(m~=)WyT&P zi|pNSH(DzU$r&9{O*a+2B%|6DLc`{+*)4`>wZJM9YsyRk(-qhKH!)Q@wRnTImdw5I zOc!X^i=s|P{$@eGjA6H3@mmaH8iBHOV!cFjGD2R7o`+4taz@0+ONP%PyYmq;OfoNeJ3{GjYM#B;QK8Q!@$7+pM0 zR=Q7JuuY=A&oBopszIeBFW5fqLJ+Lbr_lv#lBv|iiod>kI9_Tx$g}_>qOM=Z(qciyXHo4)Hx&eT=vm`9@%UJ1T2~r>JFpHzyUPC*&T8 zcbm|NPQpxL1^H0hKq^bX`)ork$<0OMa)Dw+({giSCJjb3l+q|zX%c2#V#E*t_$`y} zzz+D&s-+7H?mF-swuUC}EH~vqh3*fMmf||JG)fM zsUeSUBo@?aBnw!A#ihggCMlZAFOk4FiLpW)oVvV95oi}WyU>n<73Ghq71hYv zrJHJ}vV=!OLLd^|sXfn1Vop>o z4vvNMVpb<*=6$$PnHX)gC|;P8yxeTHnpv*Y!g1%2B{SV;H%!jb3hi-*UZL!_nXS*js=P+OwD^(XAp>~rrhD36D$nIRB&YF`7h{#10LN2W=X zG9u`8&p=TX1(%3RX@iW7&si6bbg@KTL&1G7aRyzUwnCu`vbOVw80cXxa;9sKaU(~n zGO)VM2U!K(8T4Iy@lc0-Dd-Nfcd#pLklQ159N>N{8$IN!$tIkbxhDQikDf{J>6pR% zsbKt~gZ+N0mK;W26ngT^xeO15Ya=n|@sqQm_Q3hpz>>(uz%NP`L<>S)+wAz!gWXTD z*32FM4~9*ksV(-YHND!WdTK4MarQ9fcgZH%4$uSmd*|xN57p{x2CFs7+vbMX8OkzF6Lp( zrL?8;lf`&tK**Kx1m&+vkMFHP%?%r(uS>4Z35(0)98+# zmpc8hIHB!BZ?sa1f^_h!6Z{CrJj;(wT~4x1^`XQEs5v{XA~Z6e_5*`PlF+#16uX%{iH1=d~eit>oHGv+^H-c{Nn1=U7Z%=pf)$3+#qqxySmGC*pu^&}rs@s`M6W3OA z)upGarKd=TO^P0JWf53HndGMN;d0aohtaJ2GaQ;$X;=^>K6q_bAD6^ST^ujjl)M&E z3S-kxb?aE$xE>p)@i9AKg*M%_jvy&yF#gB@RiwHs7H^DY;)c=L;($-H%j8ssjSx`3 z(r)$M12OwaEtV;pW?7z0{(OZesJ3q{xE+W*1&J^amkTdlUukC6hIF*1fu6W%4yY zY5;ZiPzb8526p)|AO^Vr$0qVLAGu_Anti`vP4MAwnmod&5m%>Ebbgg`#?@D1ioG~} zs;N$jV+JR+&2ryxboQ8xE>gecyX5v#oTFwhtX8l5SS=i_;NbU@sqG`LO!B3L*88`2 zBm9VB%MB|5cdNr7TSxo;&Qe#fHg z^r`CAs6v#W@Sykbfoyq10F|GDFNSBi=U9m8x}d6{m0O zs}f+cS0%H7QT1vIKkoo>BqPS<@KuCvMz#=tge!xAcGp=dP0aq(;lbQHHVgW|y@#nG zDq$mRW)b8QHar{g82?+#jw}X0m5pH`bJf>cVR;RQ!%d0QNmsuQPI4fxJ~*Lv6R=(# zH)%%32Y-QelrOOy7)X}{R~}dv1JN&Zwmx;Xvtz!y-XD84t~#@ZNr_dh-yFUW=utVK z8)dLwfO+H_y1>6a;yaM)@|N)RJ7X1Iqt+pg@~aM|!5ZoIaI!9I&r!-zNAE!lwlKHO z7A-nXF`cv9r5Jahg~FV=o$%9~snIt$cH#O&-S8C;&Q49X>!(kIZ?bk`*s}!!&eK3I z@BE~i110p*GB}=^Ni{OXJyx8q*n9e_Qe36@F<4;xZXRW(`x%0;AuN4hg^CrQTR@Vv#+vbL)v zKNFAP;WSC&z|D$sOSPJ4U}A}G7wfpOe70|TN2jI#Wg(ghnx9R1(~^7h>`M#AVlxNW zinX4P2>-6H{&{{K%%0K*%Cig>2LK!$O?JGxv)jAM!BGOHvL+KLchHhm(rFU^XhkYo znpjT$(i?Mt-;T}~YOpR7WY4Ps30ZnExE347#B-&8d;k9rrG8*#Hv7R+t>a+-MPvC!Oldg6em-QXrF%*!f~9xk8|zaw(pdTjJUUu@zJY} zo+#aXPa4n$J(VSS&GHn~xA~omYj{6D<{8^ln)}D87NFE|S;>_rHi~T?m(cv*#V9;v zzSn67oci6N|1O#+Bhu4w2w(gJ5)>3RL^MC*A00g$Of6g?OneUkki%Nf92YnMx9np= zNO0FSO0L-*cm;3@Ik;@m1>#GvW7rlrYp67|v~s&Sz9okFO5=|!2RzJnO2mq34u?iC zypW9y#a!_HSL6zzf_BK>wgY>^X{R1+1^ z0R;?GuuXYoZC0iDVa%NPLmi=8rj3ocNpHqxJ_|}FxHeS~lU{>_S50v+mH<|mytcGf z;+deRd2%#T-&6VFI$pD~8Oq-4d`)U&?b%Ow7L%gTUhd;?` zVEL+d=eCgvr0Mp9YE^pFaS(`#IEc%02Z1T*ii-_N;p>KP5 zNxHe$o;xNGzD0D|4shPN@h=bJ>KV^BZK7Xi5JYDOkB!bTt))lDZ}ZrB4u^xiRf2*h zUu))&$QboD0We9^vWBQ%$UGX}JV>>Sry__wY)03DgBMo9x1;1WJIW6J&lg4A+geOrBesJ3A){#~4zw+_QR&)#U%UD9*(TfqK}A z-14pIf#u8RPaQ))tS5CAhL?q#REZa>VWed@v3jpxtwyx&EcIrazcAZwR@TLJXh`(j zuq+0nFs5$S!M4W)-QA8K_z&ozQHOiH{3r-5U4IBXQ~S6cY;xK^>`%6|bgfTMFij8q zR9mKYb5F!aNPxdII^wk4>qSre6@@6%-?=TliRVTF%T6!cOIm{SVbjBoc zY+AWYER1=IVv`YVuG|1`lt>*VQE>@&NYW&WxAu+G}^A_A3P%+H0Gl z>9R&Y-^c3TA^bjRi}TsN@nE5#mLV(<|4dp-Cr5Wp6I1&inGV{umK^6f2-;sZct!cI z^dJYrY3kw4%eT*U&)d$>5!B)@fJq0uBMemh652Z&n&;Tg@o(&L0u?`q+h!qwvu9uK zC%cV6u0*g4U*wj4QjQiy8R0xB?b}QT*^rBf7Fi*mxGQv}Z$2Iq)q5OQ&9?HsRp*TE z@r(J;E6EG*X}S8>EWYn*>It``Uk-+#cmu)F$=HrllRMG@xsR+eHsKlPqP8mauPJ*- z8<+~?%c$rfTyw-3!vc7(a*miYrDPb-V)0tan}EghlI1S>=CX|uBBzT}8(|=i42pT? zF5Tjy)Z`NqzKvGAt72Sb%-VKHNTm04YZgjb9|PhczC;+Cc9%~~E(J*kVo%w*=verL zhAY+n^AMym8z{3}`DHm^SbgZTH*pZRt}`Cxispc&w2fXhqA8*plGW8~$&@O)-ba-~ zmU!Q#ak)|5+<3A+guW$P6AjT0+4w>ui-CO20Nq8xOcN&a7IxFbs&P7sGmYV=A$@@a?Zw( z+0^l`A#V_Jwi{W(yrF!fDkWkSKG2!Ld9dulPt1z|RUrg!p59;1&{Uy`BA$0C_|?)! z9a2gV@UD^?6*&`(RTlR@3JMAo+K*v#{)+#u)`^ou=a~?ZUOz`%8wKZb}A?s7?My9$u}`e z?{dB3=A*1X6WuEf4%|CsV@V~|gH6$+M4+RPKD{1-%lsoxZCwK>h=UT}yk}hZp zUqeY~BZ^oNg65iO=^;9Nl%^;bi$M4a1BB0+l8wEPw9l_@;=?j}Wx$EpAWUxUf2%3% zZhjImB#hkylym>~Qu;bk;Ehi%=J2B=l7OM@A8Y7;OY$KF&aX|U+_W*oVr)^tmTIW174SvGKm-a&oVj0M5;T>GtC1tM$quUNir z;#3q!^KpQRa;Ep`&{CNd2?ovcNGe}&B?#(zHBx`}FmQ7J?jU|)QZD60L4J)ed6l7f za?ghz>UAk|h|e)Vjy1oe!=V4cf9S=VKE4D7gf?UE=FIX%)E2vMy+KQ;|Nr^+4|~sl z35bqV@dzCvoK}d2CxHm)$Cl)0!G9Ao{G5f!!?qte0AWYr??Pw&ScM#<7{P9g;rcPC z?Mx@vk$Oqs=~sa_Y!N_YxQq1|V>#>diOPnB`+3z{TGTpX1itJ(KYUwEO^lMNO4vQH*y9LD?o$lgN9j7 z28D}7ds-1nn0+MaHSv zbnrgJ>pm(-{Ik>u7?)_AN=g7VJIQp)q-G=kEddY>xSr{mWEShVaJ7Qets$EwSBk6N zBSJnqE@_(e{4O^NdJDz~`=@Y3Lf?%}rPnoI&JR|wr``X-Y?1e6XWttlMpDSf{g9F7 znG|G!gpgVoY5F)@xEVZb?HXSQIemcacP|w0Fu^lSobILN(H&#Gvs~(B6)uh)A>W-L9Fji|)@3RRY(Jp%qhGB6q*;w@W^SF*xl zDc13;{kn^+s9~d=J9FlvB)01@ zKKe}LPM#3jAQg3ZnkQJtPYKpnUq|l_MULxJrrS7+V$?e>T{?|bS-?t|7*|>nV)_>4}&&<`%BmVFUZ5n@$-3r zs9XQGJp9?&KUb0m;7>{U{0@Zl`yaxef6e({U+AASLBDF+ru(Z4|E}ZDj{Iq`{DNnh z{ssTdeEAdqrnHfX xb<^)}FNxx>w*Thv`+c7O$>R5`)z;sEzc~HWmEeDBC|$_xvax#r{XawpOZxx- diff --git a/cluster-deploy/doc/Fate-serving_deployment_guide_build_zh.md b/cluster-deploy/doc/Fate-serving_deployment_guide_build_zh.md deleted file mode 100644 index e00f76d2..00000000 --- a/cluster-deploy/doc/Fate-serving_deployment_guide_build_zh.md +++ /dev/null @@ -1,214 +0,0 @@ -# Serving 部署指南 - -1.服务器配置 -============ - -| 服务器 | | -| :------: | ------------------------------------------------------------ | -| 数量 | 1 or 2 | -| 配置 | 8 core /16GB memory / 500GB硬盘/10M带宽 | -| 操作系统 | CentOS linux 7.2及以上 | -| 依赖包 | yum源: gcc gcc-c++ make openssl-devel supervisor gmp-devel mpfr-devel
    libmpc-devel libaio numactl autoconf automake libtool libffi-devel snappy
    snappy-devel zlib zlib-devel bzip2 bzip2-devel lz4-devel libasan
    (可以使用初始化脚本env.sh安装) | -| 用户 | 用户:app,属主:apps(app用户需可以sudo su root而无需密码) | -| 文件系统 | 1. 500G硬盘挂载在/ data目录下; 2.创建/ data / projects目录,目录属主为:app:apps | - -2.集群规划 -========== - -| party | 主机名 | IP地址 | 操作系统 | -| ------ | ------------- | ----------- | ---------- | -| PartyA | VM_0_1_centos | 192.168.0.1 | CentOS 7.2 | -| PartyB | VM_0_2_centos | 192.168.0.2 | CentOS 7.2 | - -3.基础环境配置 -============== - -3.1 hostname配置(可选) ----------------- - -**1)修改主机名** - -**在192.168.0.1 root用户下执行:** - -hostnamectl set-hostname VM_0_1_centos - -**在192.168.0.2 root用户下执行:** - -hostnamectl set-hostname VM_0_2_centos - -**2)加入主机映射** - -**在目标服务器(192.168.0.1 192.168.0.2)root用户下执行:** - -vim /etc/hosts - -192.168.0.1 VM_0_1_centos - -192.168.0.2 VM_0_2_centos - -3.2 关闭selinux(可选) ---------------- - -**在目标服务器(192.168.0.1 192.168.0.2)root用户下执行:** - -sed -i '/\^SELINUX/s/=.\*/=disabled/' /etc/selinux/config - -setenforce 0 - -3.3 修改Linux最大打开文件数 ---------------------------- - -**在目标服务器(192.168.0.1 192.168.0.2)root用户下执行:** - -vim /etc/security/limits.conf - -\* soft nofile 65536 - -\* hard nofile 65536 - -3.4 关闭防火墙(可选) --------------- - -**在目标服务器(192.168.0.1 192.168.0.2)root用户下执行** - -systemctl disable firewalld.service - -systemctl stop firewalld.service - -systemctl status firewalld.service - -3.5 软件环境初始化 ------------------- - -**1)创建用户** - -**在目标服务器(192.168.0.1 192.168.0.2)root用户下执行** - -``` -groupadd -g 6000 apps -useradd -s /bin/bash -g apps -d /home/app app -passwd app -``` - -**2)配置sudo** - -**在目标服务器(192.168.0.1 192.168.0.2)root用户下执行** - -vim /etc/sudoers.d/app - -app ALL=(ALL) ALL - -app ALL=(ALL) NOPASSWD: ALL - -Defaults !env_reset - -**3)配置ssh无密登录** - -**a. 在目标服务器(192.168.0.1 192.168.0.2)app用户下执行** - -su app - -ssh-keygen -t rsa - -cat \~/.ssh/id_rsa.pub \>\> /home/app/.ssh/authorized_keys - -chmod 600 \~/.ssh/authorized_keys - -**b.合并id_rsa_pub文件** - -拷贝192.168.0.1的authorized_keys 到192.168.0.2 -\~/.ssh目录下,追加到192.168.0.2的id_rsa.pub到authorized_keys,然后再拷贝到192.168.0.1 - -**在192.168.0.1 app用户下执行** - -scp \~/.ssh/authorized_keys app\@192.168.0.2:/home/app/.ssh - -输入密码 - -**在192.168.0.2 app用户下执行** - -cat \~/.ssh/id_rsa.pub \>\> /home/app/.ssh/authorized_keys - -scp \~/.ssh/authorized_keys app\@192.168.0.1:/home/app/.ssh - -覆盖之前的文件 - -**c. 在目标服务器(192.168.0.1 192.168.0.2)app用户下执行ssh 测试** - -ssh app\@192.168.0.1 - -ssh app\@192.168.0.2 - -**4)需要的软件版本** -Git 1.8+ -Maven 3.5+ -Redis 4.0+ -Jdk 1.8+ -Zookeeper 3.5.5+ - - -4.项目部署 -========== - -注:此指导安装目录默认为/data/projects/,执行用户为app,安装时根据具体实际情况修改。 - -4.1 代码获取和打包 ------------- - -**在目标服务器(192.168.0.1 具备外网环境)app用户下执行**: - -**注意:服务器需已安装好git和maven 3.5+** - -进入执行节点的/data/projects/目录,执行: - -cd /data/projects/ - -git clone https://github.com/FederatedAI/FATE-Serving.git - -4.2 配置文件修改和示例 ------------- -**在目标服务器(192.168.0.1)app用户下执行** - -进入到FATE-Serving目录下的FATE-Serving/cluster-deploy/scripts目录下,修改配置文件allinone_cluster_configurations.sh. - -配置文件allinone_cluster_configurations.sh说明: - -| 配置项 | 配置项意义 | 配置项值 | 说明 | -| -------------------| -------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -| user | 操作用户 | 默认为app | 使用默认值 | -| host_guest | Host和guest的服务器ip | (192.168.0.1 192.168.0.2) | 根据对应部署的服务器填写ip | -| deploy_dir | Serving安装路径 | 默认为 /data/projects | 使用默认值 | -| party_list | 模型的partyid | 每个数组元素代表一个partyid,只支持数字,比如10000,9999 | 只部署一个party,只填写一个partyid,部署两个party,填写两个partyid。 | -| apply_zk | 是否使用zk | true使用,false不使用 | 默认true -| host_redis_ip | host连接的redis的ip | 127.0.0.1 | 根据host连接的redis配置ip | -| host_redis_port | host连接的redis的端口号 | 6379 | 根据host连接的redis配置端口| -|host_redis_password | host连接的redis的密码 |fate_dev | 根据host连接的redis配置密码| -|guest_redis_ip | guest连接的redis的ip | 127.0.0.1 | 根据guest连接的redis配置ip| -|guest_redis_port | guest连接的redis的端口号 | 6379 | 根据guest连接的redis配置端口| -|guest_redis_password| guest连接的redis的密码 | fate_dev | 根据guest连接的redis配置密码| -|host_zk_url | host连接的zk地址 | zookeeper://localhost:2181 | 根据host连接的zk配置,若是集群就配置集群地址| -|guest_zk_url | guest连接的zk地址 | zookeeper://localhost:2181 | 根据guest连接的zk配置,若是集群就配置集群地址| -|workMode | fate_flow工作模式 | 1:集群 0:单机 | 使用默认值 | -|host_model_transfer | host的fate_flow的地址和端口 | http://127.0.0.1:9380 | 根据连接的 fate_flow配置ip和端口号| -|guest_model_transfer| guest的fate_flow的地址和端口 | http://127.0.0.1:9380 | 根据连接的 fate_flow配置ip和端口号| - -4.3 部署 ------------- -1)打包 -按照上述配置含义修改allinone_cluster_configurations.sh文件对应的配置项后,然后在 -FATE-Serving/cluster-deploy/scripts目录下执行部署脚本 : sh package.sh -2)启动项目 -cd /data/projects/fate-serving - -sh services.sh all start -如果是两台进各自的分别执行一个 - -3)查看所有状态 -sh services.sh all status - -4)关闭所有 -sh services.sh all stop - -5.5.配置文件详解 -详情查看: -https://github.com/FederatedAI/FATE-Serving/blob/master/README.md \ No newline at end of file diff --git a/cluster-deploy/scripts/allinone_cluster_configurations.sh b/cluster-deploy/scripts/allinone_cluster_configurations.sh deleted file mode 100644 index fd4de146..00000000 --- a/cluster-deploy/scripts/allinone_cluster_configurations.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -user=app -host_guest=(192.168.0.1 192.168.0.2) -deploy_dir=/data/projects -party_list=(10000 9999) -host_redis_ip=127.0.0.1 -host_redis_port=6379 -host_redis_password=fate_dev -guest_redis_ip=127.0.0.1 -guest_redis_port=6379 -guest_redis_password=fate_dev -apply_zk=true -host_zk_url=zookeeper://localhost:2181 -guest_zk_url=zookeeper://localhost:2181 -workMode=1 -host_fate_flow_url=http://127.0.0.1:9380 -guest_fate_flow_url=http://127.0.0.1:9380 diff --git a/cluster-deploy/scripts/modify_json.py b/cluster-deploy/scripts/modify_json.py deleted file mode 100644 index dbaa8f70..00000000 --- a/cluster-deploy/scripts/modify_json.py +++ /dev/null @@ -1,48 +0,0 @@ -import sys -import json -default_default_ip = "" -default_default_port = None -party_id = "10000" -role_default_ip = "" -role_default_port = None -role_serving_ip = "" -role_serving_port = None - - -def get_new_json(filepath): - with open(filepath, 'rb') as f: - global party_id - json_data = json.load(f) - data = json_data - if default_default_ip: - data['route_table']['default']['default'][0]['ip'] = default_default_ip - if default_default_port: - data['route_table']['default']['default'][0]['port'] = default_default_port - role_default_conf = data['route_table']['10000']['default'] - if role_default_ip: - role_default_conf[0]['ip'] = role_default_ip - if role_default_port: - role_default_conf[0]['port'] = role_default_port - if party_id != "10000": - del data['route_table']['10000'] - data['route_table'][party_id] = {} - data['route_table'][party_id]['default'] = role_default_conf - if role_serving_ip and role_serving_port: - data['route_table'][party_id]["serving"] = [{ - 'ip': role_serving_ip, - 'port': role_serving_port - }] - f.close() - return json_data - - -def rewrite_json_file(filepath, json_data): - with open(filepath, 'w') as f: - json.dump(json_data, f, indent=4, separators=(',', ': ')) - f.close() - - -if __name__ == '__main__': - json_path = sys.argv[1] - m_json_data = get_new_json(json_path) - rewrite_json_file(json_path, m_json_data) \ No newline at end of file diff --git a/cluster-deploy/scripts/package.sh b/cluster-deploy/scripts/package.sh deleted file mode 100644 index dc75dbd7..00000000 --- a/cluster-deploy/scripts/package.sh +++ /dev/null @@ -1,218 +0,0 @@ -#!/bean package -DskipTestsin/bash -# -#Copyright 2019 The serving Authors. All Rights Reserved. -# - -set -e -source ./allinone_cluster_configurations.sh -cwd=$(cd `dirname $0`; pwd) -cd ${cwd} -public_path=$deploy_dir -package_path=$cwd/FATE-Serving -fate_serving_path=$public_path/fate-serving -sering_path=$fate_serving_path/serving -fate_serving=fate-serving -serving=serving-server -fate_serving_zip=fate-serving-server-*-release.zip -fate_serving_jar=fate-serving-server-*.jar -fate_serving_proxy_zip=fate-serving-proxy-*-release.zip -fate_serving_proxy_jar=fate-serving-proxy-*.jar -serving_proxy=serving-proxy -serving_proxy_path=$fate_serving_path/serving-proxy - -mkdir -p ./${fate_serving}/${serving} -mkdir -p ./${fate_serving}/${serving_proxy} -cp services.sh ./${fate_serving} -cd ${cwd}/../../ -echo "[INFO] mvn clean package start" -mvn clean package -DskipTests -if [ $? -eq 0 ]; then - echo "[INFO] mvn clean package success" -else - echo "[INFO] mvn clan package filed" - exit -fi - -#serving -cd ./${serving}/target -cp ${fate_serving_zip} ${cwd}/${fate_serving}/${serving} -cd ${cwd}/${fate_serving}/${serving} -unzip $fate_serving_zip -ln -s $fate_serving_jar fate-serving-server.jar - -#serving-proxy -cd ${cwd}/../../$serving_proxy/target -cp $fate_serving_proxy_zip ${cwd}/${fate_serving}/${serving_proxy} -cd ${cwd}/${fate_serving}/${serving_proxy} -unzip $fate_serving_proxy_zip -ln -s $fate_serving_proxy_jar fate-serving-proxy.jar - -#cp modify_json.py -cd ${cwd} -cp modify_json.py ./${fate_serving}/${serving_proxy}/conf -tar -zcvf fate-serving.tar.gz ${fate_serving} - -cp_serving() { - for node_ip in "${host_guest[@]}"; do - echo "[INFO] install on ${node_ip}" - ssh -tt ${user}@${node_ip} << eeooff -if [ ! -d ${deploy_dir} ]; then - mkdir -p ${deploy_dir} -fi -exit -eeooff -scp fate-serving.tar.gz ${user}@${node_ip}:${deploy_dir} - ssh -tt ${user}@${node_ip} << eeooff - cd ${deploy_dir} - tar -zxvf fate-serving.tar.gz - rm -rf fate-serving.tar.gz - exit -eeooff - done -} -cp_serving -if [ $? -eq 0 ]; then - echo "[INFO] cp fate-serving.tar.gz success" -else - echo "[INFO] cp fate-serving.tar.gz filed" - exit -fi - - -update_config () { - for ((i=0;i<${#host_guest[*]};i++)) - do - #update serving-proxy config path - ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf - sed -i "/^route.table=/croute.table=${deploy_dir}/${fate_serving}/${serving_proxy}/conf/route_table.json" application.properties - sed -i "/^auth.file=/cauth.file=${deploy_dir}/${fate_serving}/${serving_proxy}/conf/auth_config.json" application.properties -exit -eeooff - - - temp=$(( $i % 2 )) - if [ $temp = 0 ] - then - ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/${fate_serving}/${serving}/conf - sed -i "/^redis.ip=/credis.ip=${host_redis_ip}" serving-server.properties - sed -i "/^redis.port=/credis.port=${host_redis_port}" serving-server.properties - sed -i "/^redis.password=/credis.password=${host_redis_password}" serving-server.properties - sed -i "/^workMode=/cworkMode=${workMode}" serving-server.properties - sed -i "/^model.transfer.url=/cmodel.transfer.url=${host_fate_flow_url}/v1/model/transfer" serving-server.properties -exit -eeooff - else - ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/${fate_serving}/${serving}/conf - sed -i "/^redis.ip=/credis.ip=${guest_redis_ip}" serving-server.properties - sed -i "/^redis.port=/credis.port=${guest_redis_port}" serving-server.properties - sed -i "/^redis.password=/credis.password=${guest_redis_password}" serving-server.properties - sed -i "/^workMode=/cworkMode=${workMode}" serving-server.properties - sed -i "/^model.transfer.url=/cmodel.transfer.url=${guest_fate_flow_url}/v1/model/transfer" serving-server.properties -exit -eeooff - fi - done -} -update_config - -#apply zookeepre the config -update_zk_config () { - for ((i=0;i<${#host_guest[*]};i++)) - do - temp=$(( $i % 2 )) - if [ $temp = 0 ] - then - ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/${fate_serving}/${serving}/conf - sed -i "/^zk.url=/czk.url=${host_zk_url}" serving-server.properties - sed -i "/^useRegister=/cuseRegister=true" serving-server.properties - sed -i "/^useZkRouter=/cuseZkRouter=true" serving-server.properties - cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf - sed -i "/^zk.url=/czk.url=${host_zk_url}" application.properties - sed -i "/^useRegister=/cuseRegister=true" application.properties - sed -i "/^useZkRouter=/cuseZkRouter=true" application.properties -exit -eeooff - else - ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/${fate_serving}/${serving}/conf - sed -i "/^zk.url=/czk.url=${guest_zk_url}" serving-server.properties - sed -i "/^useRegister=/cuseRegister=true" serving-server.properties - sed -i "/^useZkRouter=/cuseZkRouter=true" serving-server.properties - cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf - sed -i "/^zk.url=/czk.url=${guest_zk_url}" application.properties - sed -i "/^useRegister=/cuseRegister=true" application.properties - sed -i "/^useZkRouter=/cuseZkRouter=true" application.properties -exit -eeooff - fi - done -} - -update_route_table () { - for ((i=0;i<${#host_guest[*]};i++)) - do - temp=$(( $i % 2 )) - if [ $temp = 0 ] - then - ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf - sed -i "3c default_default_ip=\"${host_guest[i+1]}\"" modify_json.py - sed -i "4c default_default_port=8869" modify_json.py - sed -i "5c party_id=${party_list[i]}" modify_json.py - sed -i "6c role_default_ip=\"${host_guest[i]}\"" modify_json.py - sed -i "7c role_default_port=8867" modify_json.py - sed -i "8c role_serving_ip=\"${host_guest[i]}\"" modify_json.py - sed -i "9c role_serving_port=8000" modify_json.py - python modify_json.py route_table.json - rm -rf modify_json.py -exit -eeooff - else - ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/${fate_serving}/${serving_proxy}/conf - sed -i "3c default_default_ip=\"${host_guest[i-1]}\"" modify_json.py - sed -i "4c default_default_port=8869" modify_json.py - sed -i "5c party_id=${party_list[i]}" modify_json.py - sed -i "6c role_default_ip=\"${host_guest[i]}\"" modify_json.py - sed -i "7c role_default_port=8867" modify_json.py - sed -i "8c role_serving_ip=\"${host_guest[i]}\"" modify_json.py - sed -i "9c role_serving_port=8000" modify_json.py - python modify_json.py route_table.json - rm -rf modify_json.py -exit -eeooff - fi - done - -} -update_route_table - -#no zookeepre the config -update_nozk_config () { - for ((i=0;i<${#host_guest[*]};i++)) - do - ssh -tt ${user}@${host_guest[i]} << eeooff - cd ${deploy_dir}/${fate_serving}/${serving}/conf - sed -i "/^useRegister=/cuseRegister=false" serving-server.properties - sed -i "/^useZkRouter=/cuseZkRouter=false" serving-server.properties - cd ${deploy_dir}/fate-serving/serving-proxy/conf - sed -i "/^useZkRouter=/cuseZkRouter=false" application.properties -exit -eeooff - done -} - -if [ ${apply_zk} = "true" ] -then - update_zk_config -elif [ ${apply_zk} = "false" ] -then - update_nozk_config -else - echo "" -fi -rm -rf fate-serving* diff --git a/cluster-deploy/scripts/services.sh b/cluster-deploy/scripts/services.sh deleted file mode 100644 index a1f00b2a..00000000 --- a/cluster-deploy/scripts/services.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/bin/bash - -# -# Copyright 2019 The FATE Authors. All Rights Reserved. -# -# 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. -# - -modules=(serving-proxy serving-server) - -cwd=`pwd` - -main() { - module=$1 - action=$2 - echo "--------------" - echo "[INFO] processing: ${module}" - cd ${module} - bash service.sh ${action} - echo "--------------" - echo -} - -all() { - echo "[INFO] processing all modules" - echo "==================" - echo - for module in "${modules[@]}"; do - cd ${cwd} - main ${module} $2 - done - echo "==================" -} - -usage() { - echo "usage: $0 {all|[module1, ...]} {start|stop|status|restart}" -} - -multiple() { - total=$# - action=${!total} - for (( i=1; i weight; - private Double intercept; - private Map embedding; - private int embedSize; - - @Override - public int initModel(byte[] protoMeta, byte[] protoParam) { - logger.info("start init HeteroFM class"); - try { - FMModelParam fmModelParam = this.parseModel(FMModelParam.parser(), protoParam); - - this.weight = fmModelParam.getWeightMap(); - this.intercept = fmModelParam.getIntercept(); - this.embedding = fmModelParam.getEmbeddingMap(); - this.embedSize = fmModelParam.getEmbedSize(); - } catch (Exception ex) { - ex.printStackTrace(); - return StatusCode.ILLEGALDATA; - } - logger.info("Finish init HeteroFM class, model weight is {}, model embedding is {}", this.weight, this.embedding); - return StatusCode.OK; - } - - Map forward(List> inputDatas) { - Map inputData = inputDatas.get(0); - - int modelWeightHitCount = 0; - int inputDataHitCount = 0; - int weightNum = this.weight.size(); - int inputFeaturesNum = inputData.size(); - if(logger.isDebugEnabled()) { - logger.debug("model weight number:{}", weightNum); - logger.debug("input data features number:{}", inputFeaturesNum); - } - - double score = 0; - for (String key : inputData.keySet()) { - if (this.weight.containsKey(key)) { - Double x = new Double(inputData.get(key).toString()); - Double w = new Double(this.weight.get(key).toString()); - score += w * x; - modelWeightHitCount += 1; - inputDataHitCount += 1; - if(logger.isDebugEnabled()) { - logger.debug("key {} weight is {}, value is {}", key, this.weight.get(key), inputData.get(key)); - } - } - } - - double[] multiplies = new double[this.embedSize]; - double[] squares = new double[this.embedSize]; - for (String key: this.embedding.keySet()) { - if (inputData.containsKey(key)) { - Double x = new Double(inputData.get(key).toString()); - List wList = this.embedding.get(key).getWeightList(); - for (int i = 0; i < this.embedSize; i++) { - multiplies[i] = multiplies[i] + wList.get(i) * x; - squares[i] = squares[i] + Math.pow(wList.get(i) * x, 2); - } - } - } - double cross = 0.0; - for (int i =0; i < this.embedSize; i++) { - cross += (Math.pow(multiplies[i],2) - squares[i]); - } - score += cross * 0.5; - score += this.intercept; - - double modelWeightHitRate = -1.0; - double inputDataHitRate = -1.0; - try { - modelWeightHitRate = (double) modelWeightHitCount / weightNum; - inputDataHitRate = (double) inputDataHitCount / inputFeaturesNum; - } catch (Exception ex) { - ex.printStackTrace(); - } - - if(logger.isDebugEnabled()) { - logger.debug("model weight hit rate:{}", modelWeightHitRate); - logger.debug("input data features hit rate:{}", inputDataHitRate); - } - - Map ret = new HashMap<>(); - ret.put(Dict.SCORE, score); - ret.put(Dict.MODEL_WRIGHT_HIT_RATE, modelWeightHitRate); - ret.put(Dict.INPUT_DATA_HIT_RATE, inputDataHitRate); - ret.put(Dict.FM_CROSS, multiplies); - - return ret; - } - - @Override - public abstract Map handlePredict(Context context, List> inputData, FederatedParams predictParams); -} diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMGuest.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMGuest.java deleted file mode 100644 index 1f3c3c8e..00000000 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMGuest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.serving.federatedml.model; - - -import com.alibaba.fastjson.JSON; -import com.webank.ai.fate.serving.core.bean.Context; -import com.webank.ai.fate.serving.core.bean.Dict; -import com.webank.ai.fate.serving.core.bean.FederatedParams; -import com.webank.ai.fate.serving.core.bean.ReturnResult; -import com.webank.ai.fate.serving.core.constant.InferenceRetCode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static java.lang.Math.exp; - -public class HeteroFMGuest extends HeteroFM { - private static final Logger logger = LoggerFactory.getLogger(HeteroFMGuest.class); - - private double sigmod(double x) { - return 1. / (1. + exp(-x)); - } - - @Override - public Map handlePredict(Context context, List> inputData, FederatedParams predictParams) { - Map result = new HashMap<>(); - Map forwardRet = forward(inputData); - double score = new Double(forwardRet.get(Dict.SCORE).toString()); - double[] guestCrosses = (double[]) forwardRet.get(Dict.FM_CROSS); - if(logger.isDebugEnabled()) { - logger.debug("caseid {} guest score:{}, cross data:{}", context.getCaseId(), score, guestCrosses); - } - try { - ReturnResult hostPredictResponse = this.getFederatedPredict(context, predictParams, Dict.FEDERATED_INFERENCE, true); - if(hostPredictResponse !=null) { - result.put(Dict.RET_CODE,hostPredictResponse.getRetcode()); - if(logger.isDebugEnabled()) { - logger.debug("caseid {} host response is {}",context.getCaseId(),hostPredictResponse.getData()); - } - if (hostPredictResponse.getData() != null && hostPredictResponse.getData().get(Dict.SCORE) != null) { - double hostScore = ((Number) hostPredictResponse.getData().get(Dict.SCORE)).doubleValue(); - List hostCrosses = JSON.parseArray(hostPredictResponse.getData().get(Dict.FM_CROSS).toString(),double.class); - logger.info("caseid {} host score:{}, cross data: {}",context.getCaseId(), hostScore, hostCrosses); - score += hostScore; - if (hostCrosses == null || hostCrosses.size() != guestCrosses.length) { - throw new RuntimeException("the length of the cross part is not match"); - } - for (int i = 0; i < guestCrosses.length; i++) { - score += hostCrosses.get(i) * guestCrosses[i]; - } - } - }else{ - logger.info("caseid {} host response is null",context.getCaseId()); - } - } catch (io.grpc.StatusRuntimeException ex) { - logger.error("merge host predict failed:", ex); - result.put(Dict.RET_CODE, InferenceRetCode.NETWORK_ERROR); - } - catch(Exception ex){ - logger.error("merge host predict failed:", ex); - result.put(Dict.RET_CODE, InferenceRetCode.SYSTEM_ERROR); - } - double prob = sigmod(score); - result.put(Dict.PROB, prob); - result.put(Dict.GUEST_MODEL_WEIGHT_HIT_RATE + ":{}", forwardRet.get(Dict.MODEL_WRIGHT_HIT_RATE)); - result.put(Dict.GUEST_INPUT_DATA_HIT_RATE + ":{}", forwardRet.get(Dict.INPUT_DATA_HIT_RATE)); - return result; - } -} diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMHost.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMHost.java deleted file mode 100644 index 2173472c..00000000 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroFMHost.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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.webank.ai.fate.serving.federatedml.model; - -import com.webank.ai.fate.serving.core.bean.Context; -import com.webank.ai.fate.serving.core.bean.Dict; -import com.webank.ai.fate.serving.core.bean.FederatedParams; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class HeteroFMHost extends HeteroFM { - private static final Logger logger = LoggerFactory.getLogger(HeteroFMHost.class); - - @Override - public Map handlePredict(Context context, List> inputData, FederatedParams predictParams) { - - HashMap result = new HashMap<>(); - Map ret = forward(inputData); - result.put(Dict.SCORE, ret.get(Dict.SCORE)); - result.put(Dict.FM_CROSS, ret.get(Dict.FM_CROSS)); - - if(logger.isDebugEnabled()) { - logger.debug("hetero fm host predict ends, result is {}", result); - } - - return result; - } -} diff --git a/proto/fm-model-meta.proto b/proto/fm-model-meta.proto deleted file mode 100644 index abca6416..00000000 --- a/proto/fm-model-meta.proto +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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. - */ - -syntax = "proto3"; - -package com.webank.ai.fate.core.mlmodel.buffer.fm; -option java_outer_classname = "FMModelMetaProto"; - -message FMModelMeta { - string penalty = 1; - double eps = 2; - double alpha = 3; - string optimizer = 4; - double party_weight = 5; - int64 batch_size = 6; - double learning_rate = 7; - int64 max_iter = 8; - string converge_func = 9; - int64 re_encrypt_batches = 10; - bool need_run=11; - bool need_one_vs_rest = 12; -} - diff --git a/proto/fm-model-param.proto b/proto/fm-model-param.proto deleted file mode 100644 index 3dc2f72a..00000000 --- a/proto/fm-model-param.proto +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2019 The FATE Authors. All Rights Reserved. - * - * 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. - */ - -syntax = "proto3"; - -package com.webank.ai.fate.core.mlmodel.buffer.fm; -option java_outer_classname = "FMModelParamProto"; - -message Embedding { - repeated double weight = 1; -} - -message FMModelParam { - int32 iters = 1; - repeated double loss_history = 2; - bool is_converged = 3; - map weight = 4; - map embedding = 5; - int32 embed_size = 6; - double intercept = 7; - repeated string header = 8; - OneVsRestResult one_vs_rest_result = 9; - bool need_one_vs_rest = 10; -} - -message SingleModel { - int32 iters = 1; - repeated double loss_history = 2; - bool is_converged = 3; - map weight = 4; - map embedding = 5; - int32 embed_size = 6; - double intercept = 7; - repeated string header = 8; -} - -message OneVsRestResult { - repeated SingleModel completed_models = 1; - repeated string one_vs_rest_classes = 2; -} - From dfe9d3d44a42cb5f58cee3b288d1f7bca81c5cbc Mon Sep 17 00:00:00 2001 From: kaideng Date: Wed, 19 Feb 2020 16:26:49 +0800 Subject: [PATCH 178/190] add image Signed-off-by: kaideng --- images/serving-server.jpg | Bin 0 -> 55938 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/serving-server.jpg diff --git a/images/serving-server.jpg b/images/serving-server.jpg new file mode 100644 index 0000000000000000000000000000000000000000..48e07c26be671591b9f74c7d3871938b86582a99 GIT binary patch literal 55938 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 KP_c-w1OEZP87cDs literal 0 HcmV?d00001 From eaddc226d8f5c2d7d23508ff5ef8a2058e5bd258 Mon Sep 17 00:00:00 2001 From: tanmc123 Date: Wed, 19 Feb 2020 17:12:18 +0800 Subject: [PATCH 179/190] Add federatedml process image Signed-off-by: tanmc123 --- images/federatedml_process.png | Bin 0 -> 63686 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/federatedml_process.png diff --git a/images/federatedml_process.png b/images/federatedml_process.png new file mode 100644 index 0000000000000000000000000000000000000000..c3857a14ba93177166afa7c50fa5d64303f33537 GIT binary patch literal 63686 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 a+? Date: Wed, 19 Feb 2020 17:19:02 +0800 Subject: [PATCH 180/190] change image Signed-off-by: kaideng --- images/serving-server.jpg | Bin 55938 -> 56011 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/images/serving-server.jpg b/images/serving-server.jpg index 48e07c26be671591b9f74c7d3871938b86582a99..519207c71165f5d464c88f641b9288d435775339 100644 GIT binary patch delta 3157 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 EH&n=*yZ`_I From e7d65fbba23126dcefbdc6fb77265cc0372371dc Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Thu, 20 Feb 2020 09:20:29 +0800 Subject: [PATCH 181/190] shell bug repair --- bin/common.sh | 4 +++- serving-proxy/bin/service.sh | 2 -- serving-server/bin/service.sh | 2 -- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/bin/common.sh b/bin/common.sh index 7ed01266..a6e77718 100644 --- a/bin/common.sh +++ b/bin/common.sh @@ -15,7 +15,8 @@ mklogsdir() { start() { echo "try to start ${module}" getpid - if [[ ! -n ${pid} ]]; then + count=`ps -ef | grep $pid | grep -v "grep" | wc -l` + if [[ ! (($count == '1')) ]]; then mklogsdir if [[ ! -e "fate-${module}.jar" ]]; then ln -s fate-${module}-${module_version}.jar fate-${module}.jar @@ -62,6 +63,7 @@ stop() { if [[ $pidCount -ne 0 ]]; then kill ${pid} if [[ $? -eq 0 ]]; then + pid=0 rm -rf ./bin/${module}.pid echo "killed" else diff --git a/serving-proxy/bin/service.sh b/serving-proxy/bin/service.sh index d9287302..7b3ae448 100644 --- a/serving-proxy/bin/service.sh +++ b/serving-proxy/bin/service.sh @@ -29,7 +29,6 @@ module_version=1.2.0 case "$1" in start) - stop $module start $module status $module ;; @@ -43,7 +42,6 @@ case "$1" in restart) stop $module - sleep 5 start $module status $module ;; diff --git a/serving-server/bin/service.sh b/serving-server/bin/service.sh index 53515512..cb183955 100644 --- a/serving-server/bin/service.sh +++ b/serving-server/bin/service.sh @@ -27,7 +27,6 @@ module_version=1.2.0 case "$1" in start) - stop $module start $module status $module ;; @@ -41,7 +40,6 @@ case "$1" in restart) stop $module - sleep 1 start $module status $module ;; From 9ade36bdbc26681f9d4da5ca85c5e6fbb4022348 Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Thu, 20 Feb 2020 10:16:22 +0800 Subject: [PATCH 182/190] project service.sh bug repair --- serving-proxy/bin/service.sh | 1 + serving-server/bin/service.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/serving-proxy/bin/service.sh b/serving-proxy/bin/service.sh index 7b3ae448..82b44381 100644 --- a/serving-proxy/bin/service.sh +++ b/serving-proxy/bin/service.sh @@ -42,6 +42,7 @@ case "$1" in restart) stop $module + sleep 5 start $module status $module ;; diff --git a/serving-server/bin/service.sh b/serving-server/bin/service.sh index cb183955..fd91bf69 100644 --- a/serving-server/bin/service.sh +++ b/serving-server/bin/service.sh @@ -40,6 +40,7 @@ case "$1" in restart) stop $module + sleep 5 start $module status $module ;; From 76195ac7329be78a648b4d3e2b2d699c8b854896 Mon Sep 17 00:00:00 2001 From: kaideng Date: Thu, 20 Feb 2020 10:49:38 +0800 Subject: [PATCH 183/190] change image Signed-off-by: kaideng --- images/fate-serving-infrastructure.jpg | Bin 98176 -> 97378 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/images/fate-serving-infrastructure.jpg b/images/fate-serving-infrastructure.jpg index 32d3c35620d0c35992e536a1a991aa91682bbf67..c836bb2641c5c001c8759d08eb857069df3defc2 100644 GIT binary patch literal 97378 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

    Iv}QY=hB^ZYb?tz~i0UI*ms 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 literal 98176 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 Date: Thu, 20 Feb 2020 11:17:36 +0800 Subject: [PATCH 184/190] repair Signed-off-by: v_dylanxu <136539068@qq.com> --- bin/common.sh | 31 ++++++++++++++----------------- serving-proxy/bin/service.sh | 2 +- serving-server/bin/service.sh | 2 +- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/bin/common.sh b/bin/common.sh index a6e77718..0d134337 100644 --- a/bin/common.sh +++ b/bin/common.sh @@ -4,6 +4,13 @@ getpid() { if [ -e "./bin/${module}.pid" ]; then pid=$(cat ./bin/${module}.pid) fi + if [[ -n ${pid} ]]; then + count=$(ps -ef | grep $pid | grep -v "grep" | wc -l) + if [[ ${count} -eq 0 ]]; then + rm ./bin/${module}.pid + unset pid + fi + fi } mklogsdir() { @@ -15,8 +22,7 @@ mklogsdir() { start() { echo "try to start ${module}" getpid - count=`ps -ef | grep $pid | grep -v "grep" | wc -l` - if [[ ! (($count == '1')) ]]; then + if [[ ! -n ${pid} ]]; then mklogsdir if [[ ! -e "fate-${module}.jar" ]]; then ln -s fate-${module}-${module_version}.jar fate-${module}.jar @@ -46,10 +52,10 @@ status() { getpid if [[ -n ${pid} ]]; then echo "status: $(ps -f -p ${pid})" - exit 1 + exit 0 else echo "service not running" - exit 0 + exit 1 fi } @@ -57,20 +63,11 @@ stop() { getpid if [[ -n ${pid} ]]; then echo "killing: $(ps -p ${pid})" - echo "try to kill" ${pid} - pidCount=$(ps -p ${pid} | grep ${pid} | wc -l) - # `ps -p ${pid}|grep ${pid}|wc -l` - if [[ $pidCount -ne 0 ]]; then - kill ${pid} - if [[ $? -eq 0 ]]; then - pid=0 - rm -rf ./bin/${module}.pid - echo "killed" - else - echo "kill error" - fi + kill ${pid} + if [[ $? -eq 0 ]]; then + echo "killed" else - echo "pid ${pid} is not exist" + echo "kill error" fi else echo "service not running" diff --git a/serving-proxy/bin/service.sh b/serving-proxy/bin/service.sh index 82b44381..a687a35a 100644 --- a/serving-proxy/bin/service.sh +++ b/serving-proxy/bin/service.sh @@ -42,7 +42,7 @@ case "$1" in restart) stop $module - sleep 5 + sleep 0.5 start $module status $module ;; diff --git a/serving-server/bin/service.sh b/serving-server/bin/service.sh index fd91bf69..eae2d3d3 100644 --- a/serving-server/bin/service.sh +++ b/serving-server/bin/service.sh @@ -40,7 +40,7 @@ case "$1" in restart) stop $module - sleep 5 + sleep 0.5 start $module status $module ;; From dfd3b55f6d7d09dbfd151bdd162d1a8efb97f14c Mon Sep 17 00:00:00 2001 From: v_dylanxu <136539068@qq.com> Date: Thu, 20 Feb 2020 11:17:58 +0800 Subject: [PATCH 185/190] add log output Signed-off-by: v_dylanxu <136539068@qq.com> --- .../ai/fate/serving/federatedml/model/HeteroLRGuest.java | 6 +++--- .../ai/fate/serving/federatedml/model/HeteroLRHost.java | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java index 85dbd846..071b4c1b 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRGuest.java @@ -43,9 +43,9 @@ public Map handlePredict(Context context, List result = new HashMap<>(8); Map forwardRet = forward(inputData); double score = forwardRet.get(Dict.SCORE); - if(logger.isDebugEnabled()) { - logger.debug("caseid {} guest score:{}", context.getCaseId(), score); - } + + logger.info("caseid {} guest score:{}", context.getCaseId(), score); + try { ReturnResult hostPredictResponse = this.getFederatedPredict(context, predictParams, Dict.FEDERATED_INFERENCE, true); if(hostPredictResponse !=null) { diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRHost.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRHost.java index 3bf00407..d97d8b0c 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRHost.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/model/HeteroLRHost.java @@ -36,9 +36,8 @@ public Map handlePredict(Context context, List result = new HashMap<>(8); Map ret = forward(inputData); result.put(Dict.SCORE, ret.get(Dict.SCORE)); - if(logger.isDebugEnabled()) { - logger.debug("hetero lr host predict ends, result is {}", result); - } + + logger.info("hetero lr host predict ends, result is {}", result); return result; } From e1e330481b7944145d4d941b510ca10b4ba6d7b5 Mon Sep 17 00:00:00 2001 From: kaideng Date: Thu, 20 Feb 2020 11:21:41 +0800 Subject: [PATCH 186/190] change image Signed-off-by: kaideng --- images/fate-serving-infrastructure.jpg | Bin 97378 -> 109013 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/images/fate-serving-infrastructure.jpg b/images/fate-serving-infrastructure.jpg index c836bb2641c5c001c8759d08eb857069df3defc2..ead5392046f3977fa5b6e11a124505292a4fc5e4 100644 GIT binary patch literal 109013 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 EG5gXs|EgjR~ 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

    +zWnCcT(IYu%}a`1 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 literal 97378 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

    Iv}QY=hB^ZYb?tz~i0UI*ms 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 From 3bc9b4a44eb66245ca936352a923e92d0362d92c Mon Sep 17 00:00:00 2001 From: kaideng Date: Thu, 20 Feb 2020 11:32:38 +0800 Subject: [PATCH 187/190] change image Signed-off-by: kaideng --- images/fate-serving-infrastructure.jpg | Bin 109013 -> 109699 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/images/fate-serving-infrastructure.jpg b/images/fate-serving-infrastructure.jpg index ead5392046f3977fa5b6e11a124505292a4fc5e4..bfc1a92ec6647c0bacc006ff3f330163dcfc3e48 100644 GIT binary patch delta 28822 zcmce8cUY6(l7FxvMNkAms#2s0D1vk%BGRNO5IRbcZlrf!1qDQk5CNr1Z$hMZqEzXf zNN>_xLJg4ed%b(_-f!8xd-u0{_mAXxPM+jFXU@#|%*j^wL*zPCsqe z)|BH{Qxz=EoNBVoaRfFKZbJ_=Z1%!{u7>UUcnu{o=6D??vP=K<8v~GC62fAfrCLma z)Y$$YK_p(I;u!ke@SDoQ!%4?^yE7Mlg6Wi%#b#WQ z&fK}~sQd2g4f627TR+fS3<1n|ZW1J>V5ax@oZa-%BesAASgW%#_Uf{)H>~MOdYIrs zlrCR-gCrK-w)I9@qIo)$`O-%q!!8vQ>UXQWblBbh?!qFYkhM@+bqtkP--d7e7}3xv z_tUxYx0F3udThPM{Otwv){@%gb48mQSwjI%)!cELOFwpDOTA6NILjj}d3YUfrdO0U zCX0|0@i8s9SFtw1Nm;eZE4;7Gi<{>fIsE+=^C!czGm+(D@G<3m#E>6w0kbp5;$_n$ z@&wDiFT~!_rR5QS-gKgkKX}zH!0poE>*P5-yAzMeuWQ~g_#uZvt}YGeVYWnbcqzkf zj(hX??${!U6yA2eqXop!e)IDt0sYe#@w`rR)8x3ydjsxT>~_*q5os%yn%*0`Ywdf> zuPVBEldR&z-za{Rk{W*rMAaLl(g*P4efoB3S@1Woe$ot;p~8OrxobX83$n$3dUQM4 zHkhu6ZSF+rR}H&=EPA9B*`mi)sgc+W44OVr71_y;h@1$7 z9KrX!aqS$fTV%a+%IbOz1AA130=qvR*p$S~4dv}PWj>}-6p&W=c$w^r%YD1S=2orP zXRc4{*diLAObIyG%Z#lJXA)SnFb%83HKl+6dyaCO7B-e9q9~SF_-VOf>Jp}>8kp_W zPnfeU5`hmKtdDF1R-2J5UVXvIKVsAxS|F6|WrWISyZ5l{@rAv!z0CF%304wgCJl_$ z@#}*VTAmCsqdmOB*s3_MaW8H$-$*hRd-+rt;lzy!Yqj8uy7Ssz009vs{0E@3%g9rnlfpcWa2b-+~LdH;(VU zopW{$&Gk0?ec{v01W#9WY>r56*U?BAejF>Z(SJKOcP)jpxE@o5nGV^Lax}hQRr}s6 zY=3I(2{jtfhTP*vRL{S8AE$8e@(Ozq#~5OcCJU?n2vd~QaJei*6V0gcR$-{>zDHa(hy>OSI?Rn0CUR2>~aXxJ{Ck@&S_pXGTxI2HJ!H8 zS_?V?(NWDE+HkAeVIFP7m5g7oGeodR-4Uq^_$%HZK+O5|V!cTJTvYd^hJKKT#>pJv zCfoF|z846xBQ|*QzoVvj)CfNbB6{4p@3Q`zCl3Av!n}N?JXn5%Y_|X0r-MH3`4K9e zvz`~<8h=U;6KIde-Y27vkE9}_ARCE)5=N$iNKyO&j1uYaLICTed`=$XQk3nuIsBYh z5V^$>HEZz^8JcUul%q+W1B0h>%CN(XalQ}@m0)$d-nMADsVe>w)20!mhM{=_hkT70 z<>jruMSBB5g80Kc%|mUx%OB|3NkQEm>!Oy6E`PV3!Svb7AFEcP&HMP$!?01=lzizg zDEZ#WG7an(y1Pgq^Q96saJj>&5!n~>j^W8rL| z!Bz&9bDUf&S+03W2a#wI2&b^*4L(`<;KGj}bYVa&tb4jFCGk`W`@o*d&FhTUApZKg zLO~>mpR>7M<7l~>L1_OoETSC6HG`YKngOG_{=m|%&G^6+c;@GKh5L9%Y5H}(8%=m9$Rkjhv;QBbx9gT1?Tx_*}iHt~vB znMY_5js0Zo?SX-g#V+NA4_7P8e3eg_Zq$7*QjvkdQAYbfzJT?q1DP8ij)tV_!wb7& z0DgWEEg$}sABRz2Q72`6XS5tFCq4Esz&ydPa4Nv_nV(ZD zjf;F-dMRMwC7g=hjnYRlSY{t44XkBa#BT8Ta>tLyPya03p$#WNp4P&d*5t*pC%wX) zWVEq7`)~5?D$g4uKW3RP>v%s`lc+XqVvT#4w;fa6!=y;QzBeaw!JeHH%h&>3roG@` z)Elyim$2RU@mblgxCF~M*nIafTjW;7fN9e6+b@BzqnNse>A7>QIarUi*`nyDrAEsM zCZ0!y^EbP0O{wVjOfoWEUtukg;(bj;*u19uIHkZ9NzrVWv{LmbW&SM{oWem`IkGSv8}iz%T}%-W4lgk@Td|H#4|cfa8tWA%cs=h1 z_htt$`$cYkwlYYZ_5{x1n9BeyEUTQ`>nu0iz0r8+yO7+;5O!XziH`cZb6#WaUVG6| zSd~t=Y5(FpE`%{GXT3mG+!udS%VWb0Q9fQW>SiS9H!QI}n=|DnisfjkopdpmFhp<3 zD%kUn@gn~;cCnvY;?X0~YoSXB$;3oRL9^H=m0 z)hbG2C`J}#iXjP4czP3AZG3M)PJ}@$&0#vKvol9*Vz|m1LC?g+7Z*|2pJip|jjis( z$`Qqw1$0wt_-$yJNF(&T*Ky0ac^UQ7Xra#073kygGT0Zv#R>Yj$JWUexjfMdEP#%; zRCzkF!%TdztoIB~dS^~~O!n%`IhRg@f%W_E-#BxQns{Tsxw|f<-VlgWvcC82>Zmc9 z!*VJ)UZD!@=Q__3ax|RqW7X_(+?XxpO{#QnpJyH{Q40l=|UD;U2H954r+LR`EjV8#+jjUQ>5@brK}_8CE~pJ%2fX9=o>O zKsDmd?=IzNF*tavHZk#lw<-{=W2l!N1{3Oi-}5q5f6H)6j_^_T3HFpdJ0rTzuX2id zNc?7=Q2pYEO6l^L!%nG+(jhZo^8VSeaEt4q9zVm5FNNj24$`k&^xm!Nz4_|#utB%0 zaNWE5-2-GN)(#6_Dv*VLo2J@}BFF1sQzM1y?o=}KhMNiIxmA>&Fc5hX%;GFC`6j^X zW;5Vn!CK+z9JKyk$M`;lZ4~+C$b!(AWSB>cF;C`0b*Eib)77Kbh&1o36mCyQkT)E< z&xa5_LM(tI4p12K)A(#`oA8jly2hyh@dEO*SpJ1os(mXm2l}+|HbuFHjsBC5P8MN2 zyC*DEq2@7lNjvPAABMw`1bLg%rcsMlOa9q~(&>Kt-e(w_Mu5`m?uf{)E(!%+RX++U9Fc++puJ2Htmi~Jc9{|ds_82+$d36QFET?h>0%lsSSpEyrTp3vFQzrLl7 z50sJ-4R_sIGb%HL1(+sS+;G=(6jHt{oR_HGteI4uS9_~{voNbHxp0xmI)87(E1!o> z;l}D$fUNwC&+F4cb@*N|66g3$XDFQLk_m_e6;>;m23QPXq89gE9| zVIo@W$b1HVNaqJ7K^`A#5$MW?&_aLwU}sqCP3ka{)~sTGfPxaeE*s*eh92TZ-zmV1 z>{uE29$`a*JUNTn)h*xJo8c()#D|Um`{&=1AWG_H*{Z}tb-enHEIS_w673IQO6TnP z(ot^E#Jgd9pHBSdMSB;gN`mmfzNPLn52ALnr3oF#Ci7RR+j@SN!RHsskRWQ7$ccPh z4GQ~51+%@HTR58p`UEUvuHXcst(2*0k4VvU9p|dYA zni-gaCV)lObDm9I4fi&8EiB&irs+hd6iWI{$T8naCl{Au<@pcy0D*v=sCsj_7j#yl zY5KHjQDwT9>w_|M=F6+qLxt;U!K?F>CriH<)NOGI#YxGz*bn##;H){`YDFO$eIDXs zKU5FBe$NSDs+okAoTjTHC2S<;B@6W#wsQ8I{IxD#X!Y{uS8&}usGqXqUOZ&m{pr{4 z0KE3CD2a--Yc>l&OaB~^ZOg`qXa(*@rntAJIrG${-p!5S$UqOcx8ZEQ9`U|*ho4`o z0TPAN=H7nu^_A^n8;#)(<{O%E3ghb&5P7D>9plSRxtencQH{OaAj*(KYg1;)L})k1 z*4KRoGkjZb1@M?CSw4s}n2pH(*zEl2dRH5SY$(QuOj94G-Jx*G>OxnPYIvN8u>-X$?<9pZ}*@ASm1FUM(yMeM;V6>}~G#sXrSmf(?e9pQ>jbl`|OB5kCCr||2TaapH?p70=J6Z&$%cDH> zGOS47W+TmE5_BNiqh=>;6?i)|<2;qCVd)EJ?skR)+&ZYqr*}UQDCj4c zIhjJ$Z3se?WqLz{?w7d?GsWNaue7>Q4ido~9N{`6B=5%Csd|pS| zeA&mL7o!)7S+o=fvlJMzhGxTW)V)+8w?_o}oQyJ(G8FhM6T4i{o^Ah-@xs`HR<4j{ zM~~I$y1>2J1$@Qz!}-zWHSJx*|+_WGW%a3tv9ZFUxA0 zd_N;#qFa#2+e(iJHsDk@Hzh$XNT=d)0DwQafSNn4Pl9~PB0*lDc4Q;N_Ih#pDU_h~ z`6BU5W&m~-I&0*`OZ4AFVV;0ayDgUl2~E}_`aw}d2!J5Knt)EnXPcizoVGqc^>s+J zqf^O>2$OyGioyCxAk&3+%2&|4r_IASFom)VV^eD+$Qi~|yiYy(2Js3Bl7?(f#c8ji z@Udo>;B6P0J47P{jeCh?(?J-cLhiw4B>prB^6_X=DL{pQYUbD#$AK9uB^wN+^Ky@Y zQ0TG)mX`?7OCx`Iz-VGAFoGrQF+`69`I#?2Jx6=Qj0e5$YnlQQq#e)&K2Ps%VZjaL zz>=>5eV{}A3e2Py4#9p#jpG$a5JmGhP)yn|a3~N}i3=hIqv(KH#e5QETaRN+f*^wj zX27ugD0TNd3d4cpU2|tRLdW9Ld0+Fi=R zKT~v5xmw`)9WJiiZc}0Yg_!u#anSHYF+Ld@{!f4zC`K#E7IDFZi>9HC$mtF}#1Kgk z9C-x7(;yh}z(f|H#B$)@j^z-1dcniPyxYr$yY8iLC_YIeyBtF|_%(wl`(w|IW2YI^ zBN*xWE4L@ti=|TG&xtuvkHibdocLS_cqxzvp56JPfK+iPT-B>452VfVD{^Yp6SU-? zzp)nGV(dF@T(&<*d7#-hLpfyaxjI*zk_$TR#yJd8pU|f5QQCmqmC@J2w39Q{esacFtL#tt zG$bhY^yqcWQ#kTpBW8s_#4m7Q8Sf&*OsVeg{dirx_cc3q|JoxRm}7yG7F*#^DT{5I z=LaiCN_FRrkrYSdDH6o`hRU_I(kGK`l}1tq^_d>k`eEfl5$iW87As>U6PcdgrC-Y7 z4Xb^USw7^d{=D}DH=PEHw_~FsyS(=O8&W@a>4#W6INfJ5x~77a7yutRk%zIKN_Mv* z(n`22Zl{FgX!!(B4Qy_N88z;R?w!pF#pk({)PEL7Thud%jT~IUFL54hSmb)`uZv+NkMN zu!NahRr6dnzJ(_nmJSmIN(RR2IZ0!g!h_TiFI62Q>)i8q4a&LqK|<>2<3OCOqnBc*V2gc8z-``rtEX;A!Da zWy*qfxmzY)*!Y)i_-19o;d>HfJ$8EUePkPVrRbS=q4%l+AC%T-hc&}PKC%v&8gc7@7gu=(`zkM z^DQgBU}1WmlW}q&xlldR%V%&Sp>FNO#nl9U>!pL;oD&8>3Z+xIcbysF~B;yByAoNn1vNrp8o;a2j>sYNPA3ZzB)XpWc@m^Eu&v zUuJMX;o{1PMwz9M_Nd1d{QIsc4NrZKmN1KTc6$*<#z2Xx_btV6N70)mK9+L)Use;6 z%g&5VZ5pJ>>6iu*@1Oy+-oCs#!6a3vIzGE%446WtI;2G8+9T{zIO^k7;iZQ)@886> z>N>Sk-dENcFE=cSp{iAfUhYwpPj=#V2^!AUii5UP+H50vB`$uD@3Mk(-zI#N@wB|$ z_F%qVVA3S3)`+|3i&W=*rdQmaf_(|SYd&j)TNQ?M3nd<&!+UlG;s*h|h0CIKIgLAN zo`4LY{zJ;o7>y@(K3OA$X=};`A9EtuM~RlnX_#4&~gx!e2Bk)yryI9OsgFAxk#es;CSp?<(R7V&gF$r;U1NgFO!`>^u% z2%6%$!>gd4Yoo?Y-_1}<=!Bg%kLa=c3m;+vvI{O4x_M4C33TT3OX#?aD%oXi_^(eM z$XV(E^w*is_RCK5uJ@531wDh;&UnsO+(bu~!?hcv4Y5j7AHH$xS6nvh!QVXE1_0)n zJ&uc0Jr!?qG{o~2oLFfXI)bfwjDx1dd#Dm$vPM&wa5poWwoxlz`LY$|C*dDgnPc1DaF_N!?K6T!8pI}Nv`goK{5H96)?+OhTYyZtHOL($v&8f^{| zd2AF1S$pNx8Q-&PSTf6D>ZV4X0rq3wYd?LuTbkGAEOX3nNShUsM?ZT+Ft{s!w_yg0 zJF=)2p^sXxm7zT+cw!{iI+2aK#Px_KrL|<&-)L%Tj&T^jmg{t*?>=(7An-P=;#c;n z&)xTrOx2Ej4~h77gQy3)953dw3*F|9OEBJ6X+{z_iw2E?OoHRyPO(}TU(v1Qz+bQj zGixsRrXNj z97gg({toTXSvmO83G)CC6h0sdr=>+86G6$C6QRocw(9p+7kyk1}R`hlmxlr zy$Ple3cBF-SV0dtQ3$mCRhlByx+9!$5)6kR@rO)^5c?4z$^@?zIWT2h5W#%N`TFu? zspIo}QO#3_E2goVNV^=Pc3)qbYtR&2d?kD#>gKUU?4wJPI~|)`KR8t2+^Oj zbK97xP%m?h7SzYWDpMysCgtF&u3{A_%XKY3$q z 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⪼8FQ2MiKc4Yil90#I@~evBk_J z_7BH4UC|lc38);*74X6UsPfDt@LABt1J?|!ZqK;XMXAb!ym+r6*J5^FL4lXK@jA>e z{rrT>Oh9UBP*nym$%$?Zs6`Kkke#JgB+u6Cd)Qa#Vbn{V<9frtMvIUP;?=(*!?Ofh1X?Vd8ER<0eQWXs$m2_}wh_A29weu$V%D zUomD%9MCbB;s<~Zv3GS_38R?ib>!kl2sME2Gj1IA=X)Um>D`XYg*+;7bJt+j9K3me z7hSVKh5cO|LI2sGVT8UpPnq08t|~$PT;Gjv5rTZQ6QGU#Zm~aEEATr#z6(5F-Kk)J zZD~}?CSEF0;`HJg-!p3gxV}Ya&mSmLc2R!nA?F%)rcKO>V)!?t_zMusKYv%?4?2M_ z3pPORh=5W0$`U?GVDPedH4r@u|3=HmZy1^*b~{`D_Z$KK4M~22+h1Y(Z{^E(&;b92 zB+{8BBEp_>`X>Yje@>Rn@6zGBq~Dl~c%3=?QdAdf0*Q)lzVqUKxyJPi%nDArOxZIo zm-duessQiqCg$@S#GiHzHPn`q>KVvPvq^Eyb(uU^cEar}`tLG+3T1%%9)7_;_LJ{Z zNJadK`=@@9DV3QtGNOGZFJp}H#tA`@QqDy$YBXoF%PbCF{R%S3CI^j}$p BW*7hf From b92f50480b57f0753391b8e71a1d53fd08e7f9d9 Mon Sep 17 00:00:00 2001 From: mgqa34 Date: Thu, 20 Feb 2020 14:04:13 +0800 Subject: [PATCH 188/190] add hetero_secureboost flow diagram Signed-off-by: mgqa34 --- images/hetero_secureboost_process.png | Bin 0 -> 123166 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/hetero_secureboost_process.png diff --git a/images/hetero_secureboost_process.png b/images/hetero_secureboost_process.png new file mode 100644 index 0000000000000000000000000000000000000000..190eb1a5338b4dfc13f61b12894f46dd1fd20d9b GIT binary patch literal 123166 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

    +DMN5L=D*En5I z6D@&@&7toCLWBZ618>xfzOSka-{$@lcG99$e#YQoed~X7d7R)?VQ%vVJBbCGOXNG@d#|J)S3R>y$y`J zvNJXP^*GF95h+`LP@|68K~^~YKu%c!sIK)dUc=|$y){xsb480KdLma2!wN%xg5dX; zASF*f06SB<0!pNE2-0_@AZ&p~4U85{I>%bsQd3KD)ACSpFKd6f7Pvo+68_AjCztwJ2@balFF-^{qZ z?d1p+N7LQ=yjw0sBke^avF#q;8}XHNwe$O9?EJF^FW;EGE`T5Y{U@jZSR60F6x6zq z%mL@D_`*%`a{#W-2#bDr1YEY5WR4G!MYnxlhr;a1ZYlwfNWoBaRN;DGhve|mP={&~kBeup! z(lA+rL}|Q2Eajpdt37Gd|lAVtWIDF=rk!HKdcxb ztK<1haND2mrr{9FKbhWH)7_oL zOEx&Yo(4_!AtdH}8(*C_*8393k;b3$`2OE+8h-K2TcR=>U_{I-#a_%N3vlT8 z)0NA`erxvX7$|x+7Ywogj}3+~QWa>5gSCkbI8-~B6Q<(NgN;Mk)As~eKP-;PHWc{b zx_mq)KiNraS9aL-SCa_-(MW&g{a=56gw2bp6dA`tab^S0GdXZ~K7N#L?+f((*n4cQ z=KB<|?SBxrv?9da_55+d7DSD9|LQ-NnD8s(_*eaZ{_Hs4!GOkI04T0lagEK2K!At3 zG3&a|vg{0G=j4NP6^yS0PQnm$?)`B-QJ^0iL=vH%NFpi2&I2*%z)ozbbJzoldu6Rs zNObRqj}=0TXJn%2?hYl*bjwz`3$q~Wm=1cIRRIckjd})z;5sBp)WZ!gU=e~IxV2l2 z&vDj970aM@b*QB1dyBXCnqN@>Ux<4Hp-_;wBi=Kwkt&a0S$9*=FfKJ9^xtr^fo;Vd zu!r|DIP=GeWR-y6P656ID@r5EG2@!8FF|1pz_}HPL5s?U#2-ek?Vf&KQa3SP#XDB? z24ni>0k$Ie2wgFmJsJ{V9MO7Lk6}SN{D%4#f@(9dMF9fk+X1sV9)LCr)UNLtFj*wb zPtbv{9$DF(%*#N10S-s(MEnG;^uq^Cw7@YC+z=FJ0X`Ii3joC__$Me;8PTqbXuXU0 z_KfI4CI0ECd;j;S|91xdU1vb0pP1T-z*$0K(qPTp)i|?w9D023IM)X6IcxOi7i3<` zlg$kotmkK$yhZMWuc96lWV3}r?m;KL+aVQR@d%_lI)V%^v^X@bz8+I~%;5gcTd@RN zOQu>r5SVRtvp9M;`!sUy(SL@p{>i`W=g9wLaJfhQ)mX_Go;#cdKnP848p!V+F}edT zii?Z(Uo_@%e~i|Ac{kiJx}i6T@q{+nSn~Pl_>aiv&8Oh=qQdc=oMcH#D)|y1uUu4+ z8FcdmXlUuQ8O=^BKS77tg~sYPW>&29(VxJq)D8rD^ZCukS?oVSUOW6?ejDrFwJ9RR zz`q)|o!Olre9FAekAW`fSl9RyvtWq{l&5#(JXj?f1%kI}vS~`Oe+t>@svmu5MTB?O zbFn`V#Y|kF6<88K#g(?L$qjFza+l#X5WFb2OHY@U^g6|2U%am#`evQX9x@I$YL+f>kk*R>w3Gnaca{k#ziG&Gv!P&O=Q!7WSDY5 z4?ubUkDTtWE02HG@4sP0_h*l!KcEn4lwQim%HkXdK7&Ua@D3dh)?<}kdN^h^qZ%Y) zwC}(1UkjOJ%#Hn$lSa%Txe!JvRhm#19Uqc9>}36V;DZLTX`ywk4)4L}}+Z+3DmmMCmwEP4OoVRgqZX(ium1jH&h+M zqEq`ZFAc-RqiSl*GCLkzsTQDbxx9&bqRw-&*gx>R|JpUh_VYLB&i~pn_rRYJLMekE zgMU@*&L{SndKz}}1?r|oR8^DgN*?X{@LO zeL=ddC?^qvJ!3Q{G3q&ho3lyK2)nBJ;H#(FAo`wUp=+S4Upb^syh1i`dFP^anj!?KC1EFU-+|km19v9YE&JQJY>kkij2YwYQLGk?!`SOx zW678Oe*c=3@ZZ&;`7_cp!_R*JOeTXyCWA7~;KWNZl(>e=T2KhL?nLpz3_LKKY_G4J^c1yhG>Yhwtz+)O8P_Dn&G7BL58ccU zhQciv9<#hTLT*##S@3~>g;W7jXW!F67R!F5Pz#(cF|>0*fyKCbHU0wg$GMN5YtKG$ z*KIc&`e=y^bi_~38;z2UeQ@anecb500Qo6OGHEiSp7*UmOVajFkhtniL|v;;@^{m^ zVlXqjT@%m}Bhf&cre5RjJiDKTu#pQU@Mxen%X-Pw6~prUyJwl%8$NLg=&9_-w&ftFU+$;1=6>l%i&g46?pIQ@|3ASc%Ux^1zETsb7mT(Bp zgl)Q87t9Ycz?(tCG+D-bgA|NM@;=pAdXBv?H?($>aDGvCV7GX0XFpiBDuHdjP!=&|Xa+;P3y>XPqTfCiaAMOlDyBXK#L&`! z2{CIktEpOw3oSQAAWL4>lm=EIsl?65+#7P|k;O#3jJg z^W`^M!eJCy`xj&#K05#75qe+^X!P#}fpMEV=yYmax+QrJOf?2XMi0Sv3<^V_FucwQ&_`SpU%1>Ig`0~2qz zi=M=xLif^FLM`yg{r#n>p5q2{!{gSyJ+ujlog6dW!JXzs$AbZ42b7L{m(m1SZzH)- zZQj9Xm4PluWD;j|o~uhxUJ-3oHTD||w|m46cD5=;-(dr!xjeQPE75*vJ6?;N*Z#0R zZc0bVwq0=7&OkWKRHRqTe2p&(YYB5r_+p? zkmTe~czXTwGAFx;aJ7o@MrE2#?&Dbkjcy5rI)>cnKc{H zlSI3omKuqr%EQO0Qt3;O_*Y(W>MPUl+?F%C)D4d_PH9sQpF;vao2{aB?nTLn>x*d% z`veyjo-ND);@m*=2D%_X6`BPCTa(OETC@R0aZY3d)l+8i|$znu-Kh?tKZO~(lYQNONuRhz` z{!$Aw)MfMmwQz&*BYt7?0dR%F)(%OF`ZdiJ2aSWvS`$gF<9MFR>bj9U!)aB1ri59Q zzC=dwZvR-@0^Q^}sP})%;pn%{%76F0-wZ+61riH@ecj27T*Tx={!h@l)CxQv25?=H zE-XMupmoHdp$Pl|fEhRgkByP4c#`0cpCF24BXVdbWPVpqgW)IWu80B<1m%#;cKvtO zO#r4uJ@yiKF4>dJ0C#V|_VdRI0$MXh1pbY#7kKWJ0rbJGi@?SF5CvpB{3bH^&lKr@ z#Akn_*F*cSoZD;u`a`*iA%!6Z(jc1Am)9-d1?aZ*d=PI44PnA=zWveN1jCZd+!35r z10iK_8%feGXbWMEet}+iJ@PO%-QWVf7Q|k@v;^t7 zR9~4a4d|*RV?!lqXE<7UF2PNJoLg3(cy?mEG2Si<)1>On)SWl8 zX@b^$OyCk3j@6(ft6eK!p26rd)NFt#jqg!hjYio44&7kEy#Y~TNPFj;AWk9lera5# ztA@c7kB#OsxQEPr|cx|lvlmn6GBr!)+4*%94r78rxJ<=fz; zDDx5E&WT&@UG2uAS9sS0tTw;&9o&c2yj4nN$W#Nt!zB}Y8~W}}vyOzm<(MRmYyvh= zPXU+vOXo0pSj&$Fc%gPa)pB=Dg_P|}r{@fVF0b8=zZMgnek^Idu^=(|=y2&m*Fn&f zL=!$9C;^WJK< z`hiRXOnvu|p=h{F<>-!6y^;Yf)ZpBiBQm1xqiI0B z&vj+Eie|JYThS)AMf7=G-FX!$m;}c5+UIFP2OfyyWv;HNjK?4%)*9+kj8Ac@G>r-x zh+dD)K4Wk{hSe9UzCo25hBH&YLV0$8*^RqNENVqNQNthU7b{@>SPa{9ZjOq_tqyr_ zpG;DVGj-#Zy1oOxK^U9ZJ2%6Qpd$jqJV)xv|0r&t({GrzJ~Xz z#xq~dS~5jN?)B-*ZEsVD7W~vWrgl^}Pe(q^l4}4>``=A`>jTw+L0>N3C?}nUF%LJN z@VJ=yW)aCA5Yc+*h4RgxQ%PQGT6*ElH&E#Ws#)#Tsnv)Bob=6g;it*>8Yqb{dbWs& zQ0jU2rz^AjFMa?KZNX$+)Z@ysPw9OPpX_-SPAw~Zv1~S_o`6~b38h(ENMF3*$W5Xy zKGg)@jLPj)=^2TS^-?K_k%C1eSzoP7v^DSNm#Z^7{S;`NZzpYx3%svQb2M=2i4_QT zIj!_Kw6|q5Dig(`d7fCg2$-#)p+@-92F#8SpgV3;eL6x^9l4xpSTQ=xqB1^WF}C9{ zhVmD_eb6fma!ON~0c{Kty%)PXy!m10>wx4>CxxzP6sDAz72k!DcICFvFFX%T;j$HjB7~?9p1OXmFp|yx)`k} z`&{VGaaM}QM8W^6HT&=PWdEgWwloE)A&a-}gdW}NLxfS)qFb3g>=CN@vg_sUO14vZ zm6g_AQce~zuhEXn1(MGaWWS&5nlImZUX7g0!LY?vp%`t58`Qap%VX7~hJM?T+o3v1 zGNs-NBQd+3UYfcb-2$=|8qfRgPHe7qWrd<4-C%muGZl)j)!_Y;uQY~T{de*QG~m`< zgV(+Z!OoQWcU={ty(HU7HX^aYIPfLg(QGk!6DnE9iGju=Rhw)q#znI%Ogtlej!Kjy zRju8MFit*abM!Hr6girM36d09iV}%%Ge)a-WU3KO7B+(*t#gdS%DE#y4z{I3c0KJZ z(Hqv^8PI~J)(<{{yzBZKc8&|~L(o>!i!X=~q{{(9ZCnBNRVX2%`0)O%)Y}cC?;3`G ze3LJ|pI3j%!@-AZIrS7T{sf~w9artvjsP6UqBzoMTNq&6p_&OcX4-@^ri@jU=(R5c z{Q_%rvu@TKknMPBdXDRh7Cp}}{_WE4Yeb=iS+!0SJJ~~{_GwBE{*0`Lry=&CL^n%b zUW0X~$NhJFeJ>dN#kQqpBPH*G*!pd6V;nZXRSYk@u#Ggh=|vcdTTV{H;GMyuUcDN0 zDh;0D;-r=Y=(_wBJ(cV`JkIPE3%TSEfy>uIs3L&fpvmZcvO^y6A*q#mV!S%zmgniO z7r+MP!Sl+SuDID&+AdE^kPQCJaCY!f*>9I<3HNc3h-~7Gl(g`C`R>-~C-;9uM>@ag z_wiK8pPZ9zE0;-RqQ5T|zovVMhT)t!A8r#W3|Dm>R%}k6Y^m;yeVc-KJM@ zlpI|jwwJKta!T6N){-0cbA9P7CVlnbQUoUd1>e$sJpAOg-w;fsbmp6=+)$&=gRhHg z`Zyj+UPD%e>TJSHFWrD}Ln}?3NjWqJ!G&^;4~Oz<0Jd8ZcH-8X5QYG!#p(BMwMmv1 zcGIokRGyM$pSyi@0cLk*>YRj^#J*<%sF0&V7?{U-IQLWCxQ4uFVs3M&u&D5;)9~Gw z*B-J>u0$P|&{BLRs=5@ydK9GTPbv3nkYxdz5LY~u)#i96GzB*Sh4WgJ<9va8rg++y z^>G5;vG>Fq)3-dAk9?HoSxiHQZa2#W&@M?$v=tRJT0BC93nJRrJ>`KS*ERozvRi4& z>g?96ndwS+w}i*;%`yRGGXj&W$>I)(8yhd-&2L%vdwJ8Ntu4M?bn4$6$aUkiM5ajB zRFR?|+!1rWH|eN%vsBI8!3Rf+sURCoG%KzT+I(x4!JmvntLFLH`$-kf=ydfi>-U&_ zdd^b*_396s;|pg%8nP_}F)BJ!Zp4qD=NZur)8ibaC7@3VI;<$WK<7vINCl^d72pe{ zo(3#5%(wP&9}F$U@bs$@XUZN0ECp=IYpCVh+RdJO5kBVY$E`|^--|6H&zNtpwJA)+ zw?LUo_W(eCT~h+j7v<;8GpTK_SyZxl&fPrZD_HS;+#IHCs6eUmNPfse#!_3zJ>#ja z_EtM~obpB6t}GvnN}}?t-Dj$&n$?pJx4a|+`&2^=>>e_IX6rFNp+ySFhizqCg1d*uNJ_42NVny(?{&=4kmAg=tydnLt6Dwn*u ztLW?R_2z;3)Gy7T@+Mr(N6oxacdLlds(z^9$c{p_b#zgck=v`8>t2#4p;h9gJ$YvI z?8$4)RkW+~(S&&*o&`PzVF6gJ=X6G@h2rVll>$(m6TF17z=6P;6Pg4JcwUl?heC?i`vE$U4k z!_9)!FJNXsm~eJnlaI*a{iBDhBRr3``r;o~zq0aW!*qKEWn(~yDR**0vu7pV@iEh6 zq4v+w5T%PfGEWZfRL3GZM8)+_G>)lIt_9sXD8T~8VU;%wECwst8E!q1qzBRNzFhi{ z2;92QQPqxu;0|xJd6%Ye<47Z)Phh@#?L?vge4=2EKkUP1(4_=x4p7n+nJ?FhPSMLQ zWiWR^8B54)_~!2&2*JJCbBp0QIZw>_1kFBr`n(Hhb8wLf<3Lo(Z?n57_6nz4`p3_u zICt-9n(zaR{%zT1R5YZ5EgY^)45yO1sF(0DnL=YyA%;li>5cN%ch}3rWn$l1BhFu4 zp{+mGqL9L$eU#d1j0xNc5%g*R#`^-@-BF@;a~GhQt2{9?uS(%}h_cq#3OQ2V^*O*v zT5=^e$j3yRPIWM^sh)8$_HtoCizzJ-pG|N0OSm8Ide(tD8@iU-j{FJAmp!D^AUm>% zCymJ&5m-ReXFmZPHkksS{@3!Ve`jmt_in;}3;Fw(78if9*_Nbv*}`*x!Lt!k@oL@k z9JXXyO6(c<{8Q^5JVTtx@ia&Tb`p-WzJ9jW{7XKEq+@}!db7DAHDX*f3}E^a?b=DQ z?H8R&6P;EK)Dm5otUFh~tQvqF?ah5Z$#n}zCOrXMM~xO(ByLfXwszoR@XwDgrgi|5 z?s7Yu^=1X!fg9Aia{N&~Nq+TXeq`GLR^lprhzVm+SY;8jst`o%2SNuBoWSA_7J%7M z()L*RI-fogbk0so9QAUpRDBQ>WtMCu71ML(RaM}Lmwl{4;#y4Qo2Yxx18FMI2dD^L z0#nqDEVF8i+l+2^gBg)z9!#{Mt#q4UOf4D}?O5Ge-QQtmmSlUSko^pyu0D$XO|N{J zLxKSa3|PAWG$8`ui+jljvlE5Dg%G3LGC@_N%0aT1Yimchs?*hNQw-!P=PfIxmi=ja z>#M`kKOY?Pe!aBr9R=7EeLMqKC$kHtJ}1i&Qm9g65+kJ&<7Wh|!vt-YAND@Taxx7| zng0^>!jbZgWBGVB?A#)7;|jv&nc>HXw-?}v;K)W;8vG^xvZuJ71y;G?lKS;vJ4t)K zr-=Kuxrl}y^Edu1sB>9WjkMWrH(^(ZvUs7^r*G0*!CYk9k3^*2h$ydt@`q8fioevB zdS8O+wto+Ysi{CZeeDC9!*_pm`2wD7WG-pyjdnuGPf*z=(9iiyMR^wedc)-}vflq_ z_~f5GKFx1Hs(;*#1az=y`!#vVa=86ABe2fSYnYSHA_llI3yIp$Q&v8UIZc#NhiuX7zs9S)}r9h=t z7xaX!4f!mKh01~!4>)(7V}tF(dYbKcj;iSH?!102?|5@yfvJ>(FxK>z!TC}}h%=N0 z(yb1OtB;5x8y48CU)38?7K+ui)*5k48@iuqXs8l--LXR`{%q;m<1S;c{*cx$EW2Bv#IShNn|Bl#B(hC43WY3p>pqY z3sZ1U}L01O=nOoCsKwAbNoGeWM znOy~dGF^UxKHGi#3FEW-A)LdGP%%PSW9o$!}R`xNuF{0S+>{?*6(4ielnqzPTP*xwBHZ-xni&ksRg{Pket|7w_8 z;MyPNiwu7=*qpze(3=cc3J(#`2K>sj{_2F%kPyE%(sn#s7nCRq!7}Id<4Elg<`YCW z&yVqyv+f^`DIY9G-4IC$T%UZ8)p&i+O%EqBna!F?J>~;!%)HTtgb#StEYhs%k}dR% z{XX<7YE<(2)0L}1b$dL3pivy&IbRVTMv{a{8q-_Po-A4vx#eB6X%%(KwC%kZ$7ccX#=`H6F996Je7G?L#FsvzIW+vMy5aYhxBru$`z;*n@5~G8nDY{qsc7j9 z;5kQhb7KwC=p*@e!jsHbC)yp>fQh+DJ<}IY+{0Z>WJSyehz3IpT>oGq`2WWs)Zd$! z(7HNc8BC=PDgmr(WQ4{AHp;#?^TfIo+>;FIA0Tr(5OV5;OPV5{+cS(jP8X2)ejAAB zef4jKtN$LHAMwvpaFPAUDStI+tv}W~D1Pr2@ftu&osIzj8^>bf>7^yWu55~{LzIWO zf;)KGp?PF1m)bW`rM_UrSo(^uHuKmk0Z+7R$BRzmR|4!+_Xh@6@_~hOA-@c!2&AC5 zFU~)QaZaSIbp~dLddLQDJ|oy_xQ&0mN`3NzA!{vl#dLR{)$r7TKEocLE{sA_Qs@#w zJ-5<}Y}S~IB)ctYQ}X;hgbnEg@B4%J@4t_IB6>a0KSgf!@_{oyGW%bq`uo+Z=69mI z|IBx3|2dv4ag9(zscz;ZRO|8wC7j^`%_Du6!KvZ|zp>*VuA)^9v)xshAraz1GW?gK*@aGD4NSH+pw<7o} zlFU^^&>7b}MIB95MX^76U(eQ-{?eyJM}|dczEwi1KV8leLESI$BDL8Ge+n_dP>%Lq0sQ&emy^z);Z|QhK@vj_S=6qNEV^xbc{y6V3zrmdNt_8YrA;zuL3SWw5Y;3sCtO9i{a$x>2Xn;s8oT4}LdcMHXXb%FztSb&8<&tXhQ zd`<>YQsodB4PxcTYxirdx(P*|s=f(*)eoiXZWtKv`6OJ=UvcgCo|2^=o22sNWhmJ| zwjwj&&}6K6dP9l1m1Q)`y?W%s`O{yzWiNHy1?5xjSR4_1T%7zPbt{&{K|V!EYE+Ls za-xLTb|{t>*W}C3$GJxT{@Fv=3$^20w+!#{xrlKPJV88Ph<&Y@+{6e>hE8kd`Qehi zCN9&Mc3-zgA^N#G17bhK7K)DZh_P#fCX4{51ZLRfUlgRSP~yq?7!|hI`HH5_?&xOD z8B|sY(Q75sx%zp~l6d@^M80iXvG3x-*92(KQGuoFC#V_)9`>aX6Lg_VM@rv*`YI+C@ZYeq*I%>YOhSdeA8oPMjXN!1}0kwlKzcq_p zdBWq(k3e$^d2fT2{xa{`t2;vrKFilw8Fexiz-6z@SD{u34z;Q=FsiA(rR3}Oqr z;E@F;-x(VLF5=JH?CrC5#xcdbLyiKH-ZzfKzlkc4(+s4PL%epvAr@i$meLXE+l228btK_zgS>#-@?N}0 z{KXB~FI3mkf6$~p5V~n;Ukiia-tf}98=o@p|yftB3kF@<*f6f)IASA)V&TC42(4Hmn?af;?*KU zqeVMZRU!*SUG>@WU8#&P9@)C5?}^1O0r#g&*Qovw$Q0=Jfa&GY^WLWI}T^r=rh{%b@1mo#Lyh1#*a-wYjZP@ai$hMf7 zR7=SBwYKSg-dOQE=2*#xR+*)kIMayF8-0mdqV4$`f^ek0XW_cJqTQ6;)tA~DtQER) zw?IALHm+5aiG>`adw6;U0TFbGgt%o9rTC)7aQiX05Ru?YYW_nr^oE$ul)y1b-MU0i z2%cZ0@y#ch&nr_1FDfU@p6H7oXjO%>!}&IEZa!7{)DP|G$)3h~K0{N3FrE7k7>au(hW}M3Y?#t-sZuN##h}*xu0QRutR*GM5kNP!m9s z&$L8%;Fqam2%O0!D`Q@^scypAa!oa2;z#}QBzLnHwW>8rkDNj;#JM>LC`f?C*_Bqx zS~Vbx{#Zql7NYe^qYDDZMbNIU!_Bw5T7MoV8$J-7&9A*5{#_V0*e>?9?-Auk`wrIV z8!8`Ck-z1OOC=cLkOcFP5=51TPM~0EUa8G{N(H`7>gu`WHpYW{I?sYF8dhE~^drjW z$y`VSqEQjDYaY$?BjEeu_&bGZ(ZE!XqM(cZ>d{w+9S3|fukY*|b3BX19!CEInAoS3@`|5M_$(E=+W{D4;{SsqDtlf{)(! z`HdnRYerr8X|C1KH}&C3mAm@-ir_a6AaK#|smlMSKl2-mTr!bT0n~J&G?dpEZ#v>V z9(+UxY41+eP`1?Y)as&ilOnnAZAck%$FZkt?Ct{XHxyckkW4foDFR@XAo=DPec8*0 zQx@$JO{h0lVGRKlYJNg*6Cx8VgT+yzjTdm=2`RwV>tjIaL^q1pxsK?M0~^;VxuHEj zo!GgYq^2dzrg`1TYFl@AFzJNO_kum>)Ea@27>}Rm!c#|}wVgBdW5F$Zus#GM~KEiTgrU~M3pUgxfIc5fssBJ(gN#Obpe92x##j#w z<#5?S2cI0HtBie$KI;-MAj{K+Uu%7t5uW+dI|MV%nIcBCr{dh7+U0N# zc=~yY_Z=TP|GClYdC@5mMwJ$r15p+?u{0lYh6;w=8hSsjoQ{0s5398PLc^@mn6nyX z+S`6=J^8{dZSu{!5kO)HEZPiFnIr=>FaSxpG~HNJy4uZYAz)o}m&3#7W$eNeE_|Kb zb2<<;_#0A$2*$5r5Lm7S4{tD#XUTPYWTnlBqs?ZjKc%R41W*_10${@V$TY zuF~k=%2z%Zh+m17E9PM5{Bi?C`%#ims@FZw4kTFAwpm5QHH&TX9`c6eb+6jk+SH7i zOlhrTOyUZ%D>;vZ=<^VLmyBbP>x1Ngme7l<$kjH5{q^kGm_n^@c*#)o>5JO>$+%Y zg7n@cC?FspiWCJTBGN=eR6uG}K$;L~5+x)^?^Qrih=>YG3B5z;ReJ9vC?GXK48aud zbe%oc-e=u?_Br?5amTnn&Oe;a%*@R4eb4v4Pw9hszS~Ke(?M=W3=ao#Z&=t~QIS#tvY+>`?YIqNUO~A52_q#f4{ZCUW*5T=~dKc=(o?E@A%ap@^EYr4h%4 zdO%!#+jNB;lo{ux{Uz%mOo2Aa)C1KoCTj3kee%$`Iez&16!T#F# zLT|ZIvu&@{iF*0CEX=7;_Pj41&mYx)T6CYA{Gde=0XkN~hkV6lgM#5+A7WaIl$gTU zq?4+VKtziU8AlPIKA@#h{e3BB$hM$6CB@IutqEC!+D)L{fT&ac{yxw2O!WfXUfp}k zOhJ)!X^lux`jq&^F}xBE3%=KFPLK4JA9@<{qeOIdD1JJWco4|srL|Cfc+~K^K+Xbe3EN6@vqM6OWth0dF|c$^H5~|C^TicBAF^^ za~mc2mul}ao6JHrDhsHIzEfvaTR9>5qU?t8E$fou#S_C>!?e-X0j|HmBrf6@G7 zUIPOSb`A@;zkgmC$HQW>=sx*Ri`5rT%@jeQz0LVok@qHONA_&ZYWKd>4uNYRR6$k1 zgp5IzryUhhDfw2KwO8r_0k&gBd9smC zA47{%b&62!jpH9;;P!2H7hTb}vv&=n778DW?hIV?dUGULn^o7IcfK6W?sJ-!O%)(n z>_B3igteDrVe0l<$vmD?;x!{@b@koH4AnIgKl5CAnD{`UMA7Q#3HLoF8Y9Azz_Q*6 zVdvrYXi=Hc<-YB{rZvY|))rJDjS6gWe{?Gm{fcFn>8%TRZB11SenO9Nu`M4tlKt{` zZf(idbgnERspxabhoH<03l1Qa4UFs8outm>)gt^u^hIicV>WS-@3#? z1VV#V;rGcc_|WM*!PIc8ryey0xfI*|I9P@8~t6GfO2WK4i-C$Z|y z+EF&^8LKX0Q?vIB*XLHF5A&Ajy8jTW@f!a)Rs;mP6GxA$vN-OpePY0KYAV&Nc){L& zZqXAai1KMlnlwq?5%?07?sG2`&XNQIc(~U0sBAzI+_01*)sSGEFy4u-4oUo=CrwmI z9L+t!V>7D3>Mmj&QO0K|czUL!>NU6Ql`(IlJKz~G8^jC3jd(H+G`WtGeOk$G_`~R( z(_UCsa%Aq%=G?AEJ1o#h&91TnNtb)bH~Cty%q|}{d<20>qGt6*AP3(IZR)8uqq?^d z5_rcToY9*CY^Q0!=i-CL#*cT;?DZ=RCN9}w_ZS7L%EWqZF&yy_m{zqqRf4R^CAejN z_<%Y2p~i6Rp{Bp*l9cZkNBhk~g-jA@%pllXmg9k+%B*vfU*xA-VH$lL(?I3|AU!t2 zqh{W8r#bcHlc)95I-KGT9VT`Mql%}de&;M!JDh4_FnH+`^@Wk?{m*_SCb`+@3b3b;RNntCM%^%k&y$igf)S3JB*4xTR$*t$8 z?cvq=B2s*ZlM>l@g=T|Wk3Z>udNX}5lnf#W%BIFEE&@M6fkr)>?8(yt^z=r?WO7>bLFP)6P#mABU>U zpSpdQij~}a0)NvAW=E=0MQf9Lv?1Z@=*X;EO>v9FW3Mohu}{m6GV{$O*qLz&5%mvE zx3WPL4$^b-vjPuvb$2s3tQzp-aD>BJ^@Pf5Cr?iNYTUWG|*8aEhr2k zxJWW`KO0!|Z(ru33Q?|-=SiOA0`Fy7v(Bz&&vc_gVEXCDufvYj_L86~lJawYj@E{?=3a`D-_ zOAl{&=8Mi*#39Gh$dFDbSl5?w8F5-zrW2_{sEuyY2=e~eCA@X9RKx3a;PC<==jDF6 z_xh9my@a>N1R450=AiMvR0Tb-!nu2^*IMMYO~$hy_d(AZ2VT@h^(x^flQ?h9mt;z`lr zBx|za$}q138MThY9F zG6)@D5LbQw<2O2rZ`=T6sNbZ}Vc<*g8#6&5|@Tk~-pzNi;Y`!RfsRhvYa{O=a z+E7)IwxEWk*j0DkV4SzSO|<2NhFNFhsHz6+%-rtS3Dku82{Rm65RM<{Msn6QrnlAnRO!?<%>9tgRH(c^c+MmyXQYyu2?^4vU|6Iwk3J*< zc6T0o+=GGs&cdHpkE>({D`qdP@#)3hk1~05L*4JJU(zLOP*TPqc@?;wu7u^b-#y6) zH)1;eDLT(xbIi$U0oCsK+3b4<@4{fk<)NMv08ROw&=UPCbpI~I9ZQk2`2)J~JH#8g z0Nr@B-+Q!{FJR(3|Z%}*Uu?HGaZ}2GIr6o@%IrmCdaRmsft9IPAT?U0;J;+@x;fLWcBlHzR2`t zkdO7f56T~|hDNwNM)BgY22`!#=zt2;`VCd8nQ69zpEuG2}bqsn<` z^We}!Z#XBvcE#ZnKa1y-EF5e{4)6&j6#|AZJv~4PEzrx}qT0;cRqORBeX(mEY(4B^ z6Coy;w|_vMJ2KY+asd-jW8U_Pk3@&|Gq>|+_f2d2_u_t+Tv9#y6j4e@P$6VOuyB?R?)R6 zvTKzxsE>GJ;8)(=688nPt%QRCow~~Pm=DNv+VJ?^QeLViXFzfv!vdCOBz4m%MD z5v)>E>QNps_f@yfd@xeFP}%=_h^a_^21emT2;T$VO;0#B6!1ZasI>)zl9(?9)meLz zE)b2xGrQc1l$+E53mOC!di2T0d*Qw9=0IKZy9Pr2?|EErDKKq7<_^2XwK^8%J93qY zHr<0}D@(~LUo)j#quv~QS6i!=_gdIObL>a{OiEu>=odeI*&U%HLjo3$?SQc%&P*as zhESXxHhw44rmD4PXE#QHEZONp0h6@GzJkg;G(63{GgRgRi!{jVaIdX}Zw$eK^VVt? zS6VQ4{@C^X>U0*r`K}i?2KZdrgus^M&>QI(B1TzTdeB^?a8w?E9jj9!j>Vyv8N|^SrkAiRT2`KVbf;S ziR!U(FWyHflr9Y&-}TPV)NB{C_ba_uq}FUtcBPmhP(WEE2;SeZBEJ*j?TfdF8n4aB zBj+w$?$GzCV{%SyaL*TA_5my>BSHp$d@n+V(Fb$SMdOEiF!4ZO=fMdHAn>zm5gn}_ zqWAN58QLO^WX9Z>iOWy=&Tk$wedg_p-X9~=6DR?Iw#~6B5)Pv>5S0wW9Y?B#I%|HG zKIzbLN$eKCBy!2-s+fgz9B6@njz?Tr#cj$XUB!VL)opNg4ni_ z^e&8zna`06$p}xzt?wuTJrNAiYC0LoKj=#oSkcV$UE9=lZ98qo+sQTln6)om)T$v7 zQ7e5s1rdk3k8;DzSpdR8t(6!ck*V>nh{79dQukxIortd&zB?GER}r3;$$^>k&m9ZB zy!k)nnrQoZzHLZCv;N*H1e_tBO`tAZ5m6{sL|EcpL8d)PzJEZ?zM^z*+cD}Df+TpW z$@EiC@}ARG$eUZNj)qV8sWxPL*57-g8^EC7Txid;F6U+jBq}uldxqf1e?Uu<-mE{< zLN8y9BmO|S!$QxYO3ANr0Ki_FdK~>Tv+^UY0Q;y8GL5h$yhQDJO)(t}UXl84h@hzM zcuF5!zqC)~s*G^DL7a zah3GlJOR{(>{I}@HhMb<*jAkfKJ*DfZAg!>PF30T0MvyxeTcIHaG;3^Xvdp74&WVF4PH|)>C|S{MTOT-9{$tc)*#y8V*RRpYQKbqo zkwl`S$=L<8MHw65FkeSlm(^|7tFVN-dozz9^+aw{$l_=Q~L` zA&X-gJhW`SE8zUQHftZS1Z(t6DWo26Ze(#7@CHMB&Ef_qnppyAiFfgr@7TTs2AGcn zG?RBc;|)qbndnlh6nQK)*Z^;q{CoXFI`z9Cssmu5Jp#BX$uMqs|0=zHqu>8%fgMDH zyvLq{;I^<#X!_+AFq!`Mzn-AipDFG2j3X#RD`_J$^KyT*opTmcsx*sQTq zfh*8)8z8`CwJyh|qZYW&Q%l8@$%XB zNLHPV#=9(`iSjQ|vYzwtq~GLte|`<@b}dmSSV7kGgRUp{B~ZowWrE&IhTo=!NEq-% zPpD7c&~;I+7AI;@>lLz)p$q!7DqtndEERyQ2#UYP^x3}G&Z7N1w>c1zYzfV^15*Wq zaF5W%wg64LHoVD-@aXqZQ{5w?UY4vde;-hTHeYz&cff@e9d)y7;Ef&+q!cev~5ZjIGn`ru7p~?ZD0;>nuy4WFx?u zWU}C6V|3d0${Du<%`z_3yHp-bcWlZ!0$xMjbS&P|%Zrc(U*x9S1 z-Ls2wAk@|B)RGP2mYoJY;q2*g`Z9A_$fwaxn{!8BY`#Vkp7z!+l!S*dltOS8UD#-p zM3ay!krfwJ&0YT<%Bfr~sp|*gFjV)Bj}GYzZ;-c{7Z_Cf`y>+|(nR43*U=Hkmso4Hvr1c+bBTfLe|M6zPvy?ZZ6x z5Dt^b3mv`zfz43ks!_Sf9KHUUsXK$d5~rBCWmR;J$@Qlk6O>H<0XVEmapBOjwK!;0 zlUp#6`N!UutwZtMJiJ8k8+?UEx6TZj};eB`2!m=(;z zv$}HSM%3S+G+ALa|NJVYfoRMkIBC(UX+`REy=A2v4A3JG` z$PsqP+SOeY4qI0N^;Et0J~?u|AjJH6Ds!6G&KU*9j3~6nr+K-O_sn;#99De1-zg{? z-+3|Baxa`DL(7#R1W`|2aKAICFj-nXT12aW2V!?5`ozAWDc6rBeQS{z=<|PR#Z{Mp zE>U>Llhm_qyAsH9iX2Ylzt}5%iacP=eqlp*bNc`?i^dIQsfOp(PyT9tzS=5Y(p`N& z(Az1*$kIRXF)4otARzRKm%AHFNV?wSh}zLRNA&~R6H2CI)##X78*74B&_IE0NlwNf zb=*-Mb}sQjb&~CIMG48B9-$*&JUa2Yd>qOhD+Ww4waHC^ge`+fb#Lw=#em^sj*A*p zk~iFyzho&#ryxzvvCfLCY2UY%^@n`B1#nt!ZC)^bNcg*^^m{}^h545)t!ckS&uR11 zPotVrjzW#NW!SHTAnW)o=ba{R;MS3Y$C`wzh0C{ww6v2|f3~%es9M z?JDnTs&Zm8vc6i2wLf@biaAA&gaAE8^$mdds;2<2U9}pH{{@&6ME|`WTuwho=k6^5 zfUlSTUcj$7h4t8-Hsue(DwY0cO20{iEy#PMQ)C{ZMW-Da@WeMPA?F0zqpO+|*P1_b z>7DMqUvAf>>-+wg*zpAJ1sg{gMTsg77-NQ3qobEOt3K;V%zI%UQLGLlKWbY8o=h$& zrr_T$27{2!0^dv4fpi(D;pi19Rf5HeO+*W``&@ShE}mv{58WvqzWF?oULA5eFT~%E z*u(upKAjnVan!hMT|OI!)TH;(xyiqZ218a=g~>Go=8e@!LwiPJzxuWh-V|ECj>#VN zOK@3?*5wCn7~ddM)`Zs*{ssv07s-!?4kq1P{`@O=7KHKv@)y@CK6rY6!rMmF(@y5jif>n7itPMjiOXKUK~#4 ze$LMUxppV&48tDk1X2^BNaiB^20#v0307CbslQB0)!Q zkVEKpU-j|dTPx^oH)11cn%GslBS-Ofa+upVdo9}{N+hK6a=A{1Zt-qawJxIm=Njef zKnHf$C-U7!uE=O{lMb$tO}$z5X6*f%>YBudQKBgiEcrlIx3odPY^E^@St(quMdpO~ zfAW-4Pio&EGmdhH8jh!#d`QR$a1DNFpvcVo>{v@4Eem-OfLnL5UXFII1l8pHF~p@u z^Qsz+I}&C|-SSMQ75Zl?2Q_&XuK1=nypx`OhibFMK74}pq2bpkN?VcU z`gf5WdXU+4`g4t7)mDqyBA}ycPSCAwEK9ztC` zGi#0|o=!%x3bScQkEg^dQb0w4a|?i5a%j39%2Pt{52zF=Q9(~B0TTw$gj)bBdUc&t za{Ld-fvV$0W1j|wC^|dK3YKj8fc$`BMt**{l9kNUtMUtT#IpEyXW6ST^puNyzTU)k z=^FNmbgslJ=lYsd*C;FQqzJW&vR>&VHklNFeDx53J}2o5Ky$HYc*p-a`qyv=(7`v$ z$ft`|lDf0rpyxU4*9(*Nuh9$-4>04u6_^0*C+bfsOb5=>Yfn&%=0Dzq{r((y1fsZT z?_B`&g`QjQ1~gm_ldE)_M_7xEcRTT;F`3_56HYbU+6>J^(2w+I#CS_}kl&BdWomCgXw zI(LkJ#{T<9?BU0tWEy$!AD=*9h2-SlMs<8{;f7EV)SKi)fW+mfF~hT8DfIgV@Y|ul zZ!bW(0qV?Tv?v_EN^kJ8M1Oy*0DOxnasUuzfVB(ab|G7zN_qApY&HSP!jU09nJmY% z{rVjoc4ykV3M>Fm+p?%lgBPp(vLwY9B%1OWHl2d`x9_@&>81cR5HNBGTjADj!L~&H zi8tO}BuW^Ls4uGyH#ST?W>n0WQWrI&eYKqd2@32?VMFM0jtHYlg7rsVLS~2gM3c1; z%S-X5{rQ&b;#57vF8>YP)Vu;5gp&6M;`$URnmzT7LgK$ z1bTXj{mjOk3jS;Sso9y2?R~y#VVjTYSmK`ap3epaML}G^v$ardhtGgpwPD!!-E%^@ zd)85MahHg(auz#$r&w5(KL&9dEfArP0U0#&7h1G4h0c^~aaf&b1^}dbp^l~6h;)23 zRerggwHw0)C69L=p4ze{m``E3NEw*28w=)=zJ1D5S!Vp1YF9s+ToJn(wfH+xET#L? zeW6~7u$)$@75-z!Ev#dqdW`XaQi8%^JdG>;xj8pfiX#0K$_HwmswBziUBtNvU{&|o z)B6u7&rzJx5(gqjsgh3v!?qdE0@>aLwba-Gqp`0{B|r%lsxu8hc6^Z;WbbkZYU6e` zMKca`#I8CcLAnSW>GUW!q*<>|?nw^+ZX{?%gK zh4|xGBZnz-R*k3)6E7pxSq{=2;PWR4z+e}AwH)|5U<&&S-$b@Clye>Q0vu~NFh2)% z{D#wSStmd%gSPirf1@K?gp$FFqsqqnQOy7|e?O*$3BeUk0}}1HjgxnO2sGFl!sV;A&YiZT?>L zzTFQX+N-YWvM|pt?T{waZfE!r`$&?)d(l`SR*Ez`kh=J<48OnsUek45!2k)sgvGsZ zs{aRjuZFYlqjciU>N4-W1(WRaPFO{;=jyX&Q@_!&PEBH6(Q~jaaJlQw!8HBYRMi^M zCQNG0KsGZ){o~mK+F;t%x~G=8sa4TyFQ+$!kTB#Zr!r(E4^q)gE0hZPUK{WI@h~=k zT53`LR0arxF>(Lj&?o#W%evIRYl)YDO>WXEghRTs9b2m3d!~x|l$_1y?>Hm%E?2Q`2M7CNsFzcY8iu0_mi_M6UcFaLWNLu( zy1D#_&AT1M-?)gq2_Tl`c>|mlzzs>Rf!|!ED0K;2vQAVfWgXj`A#ilV1={LXuIJZr z7xs9Egg}$A)mc=5bjx%=97Y(0l$YQLHl5y@Mol4_WBOj+!3sdDO;=XnVlRCDP;H%4 zK-qzb#2!a2pe3PJ@*9FW7hUyOkz>N)t;<4>uE$a+wANBOi?#8aP&)C3z zxhcy)C;#ualXDbS%i>-k3#rxLNq!>c*zZ%oAD`b#eYrYFtbu zQ9sz3gm2liAD0*UrVZ)A%nfu3OR-J{Ku;jf)m4yF{e9iu4F!Ptjk2^fKg$eoMww1; z?=uL7%Fc_uz8-4Q6|(q=DnYP*Lkzbie=A>bAQ#z=dsl_ab#~t?dAyVJw#PG({^9yJ z^}L%?u3VvA@y`~(73hw57|RbNOVrOhW+w!?qPsQKlt-0GbK%g-M{$~b_wDymR~2i0 z6Ju2CcHeR9kW?mHMsYln8`?va9$(;%W<+Gw?*r?nwVLPWotAF;uzwIixA2{N-1)66 z@Y{Lm)ii>9coQGdo-_gsd`w*pJD9j%sK}y^KeD+~SO(;yBNuCPe05|;3>oU{QzT6V zzFOWr7ASot#NZP`oZys5jJ74?K7Q=N#55Uvvj6cstVhw_?A4BB(*-j&(PtxI6_$T&$intIF>&ZZxgmGor#INZj^Q)UnOBV~8;CBWROJxnY6t5lKt(WCy6=cBv zr}CuxJtneW5d8H07>$_hUnX;)Wk*b&2P~%Gx)gkvxh5FV0B~Y)6RFp=a_j#5CIH(kZORO;!{3OMHgzS%e=lugJVlGUfU7*MzyC47palvps zlM)T}l2U4pIh~Jj=w|s!JDw!8-W&d;H_>#6;ptOQa``_59gMm81v8$%LxwWG%=bKV z4HjtA6;ObGk25^8u{q0G{2`2o+4pYKB^*tr9Vsh_Yhb==X6b$eWsf{zh|^snP+_>1~Ytw zbwSVZkleD(VI)Q3Dir=qw3p6IbVv*=I5%|N){$R|F__UtKI>(Y(!oA}H2?y<7l6D# z1i9rHJW$Rxa<@SWOpZ+5#gjBNpt#Q!Uge_tI0QD?kB9P^}`x z>SaP0+e(}q1Jl`UZ!#~m1Rx8`~T2aKh zs?I@3&KbW@GmSmQ_XwSjU0{sx3ZuHp`L{32o8+AMQITfS!>T~)Gtc;@(uzeC1VD!! zgEpPz)N??MC*KJnL=HPwU@=WMQ;O0(R*gORFjbT`Mq!fT#oAHum-z46KD@uNB6Q!6 z?J}F*OVICsEiF-9$QGXv&k4z4c_Vj73S(5KGRMuc8X;zOqXF{+liB+o!nWQp?`*u9 z0Z6yq^cLGn9PJG-p2{H<-yxV6pS4MhEqtzPWds&QO6MWIBcW{1gxjxsg=E)+;S!c{k@kj9i(RLw{XeGU@%#^@C`>r$Jeb4{WM|mA4}Q5(gd3Kc1!C?T!p_;Y z(`I12&O`Ad>xhtBtA4d@XeJ=kCJ|hYO}bA5gvLJeIibvl-Fw=M($ z?}|C-LZ@Ga5;y}AsOdA!Bd(Bbi%)<4G#HtA zVc%iS{2vU*DnHe{l?1iS+oGY*Y5!s{@E^?v{`oiDVuyud4Cq{5Q~6Yr^b|eckDXqq zd20D!54L8BJ~Jm7){|Nv%l4XO)>z|b*3N4RimFY^EJn9KKT{B|C+8$BoUN{G>z%Dn zsfXEKGQ7m{5Zsh5!*F*GER7sqE(_{{%o%qRk>ils=5L3GHukRLO{!-f9%s{eZ}uvS zafxNf@XLc&Z@=mrW-~a9cS<+P;ifgQt)&ejG{)LfL4rZCvS(W!pJvt!3@d>F8nNF;@TDv z*4h86A*Zlg6MmhfBjL;tg|FzYYNZ$V=TcI6K&(CzI1|o2On~^HBBZbf$bhQxAlcH;oFka z+4QeV?4ww3$o%-KYS&)EdDU2TXp~mC#c06R2OjrS+9$lk2||rk=Ea?FDS6E5Lk>Tu z50ez~qtv4W`FcdzzvMGqSpl(L0rGZ}>0lWg^tTKWSYNdOD;oUYm2fZFh90vVi`}aO z=E*o38khz|A-H`#As}y03lF30Lg=TG$%y1g)J7ut4V{tdqfjR*({Qn&8f8p9; zCFm+}EDqC&oqG>UxJA{SRtLa{mtBH(Y0>*Vxi}6&S`D+X?}m%&yPBK&!S=@c*HBE?j@v|o^GN16FuQfAJrk^$=9gc`0-yU5Gpy7Z3YDro$#Ai4XTb&zIBYd+(ia7bw z9#f?h%a9UD8MnLCgXSp(1#E0GSi|bJ_Tx#w(mBl67IAF~y^7f$eyYnww6VzAnfG?l z%$!SXN6dG8(i;xw`A`($PcLKa)(@x1q1ud9RhxjCA?HuZgIP2)!gSolULD(*U}q1# zaZc#0AWk}`o4t(owZ31lUkQY49{nFeP(M8#;$ApX41VVpu_yUga0tiMv3#|K#f6sM zkpNK8Bj@(_VP8AXgqa93OzC`ZA2VsQ(w9vS1#OxmHGudqf+de2yrUaT7(|r~z9Bbu z&!smP|6cr}JVlzpb-jiy*55Q+!0tLTN!J9)Cs41De^LMiYTJz_*AN1f^a#*?!P+HE zb#vSt>$wCR+L4EIC%CUR0Bf$w#9EU}FC%)BfBBQ3v@|3?z26B~i$w(EXjyX_fH&)o z%ERw>mzFQCuRSXDKVT9S>m%Ej!iNjs->so3~3l|*h zfBftXx)eI_)9jM$ZoO|K<%J$l0sd6=-@7mU6A=9e&?E&VLRT>Dm?XUuFm|d9VGtdw z{%6jRGCuJoWxAb}PC&&xY?(m}5JVL)wK@^iwB~pN_>FQq-=mzaZJy z9vA%56S4Tc5`gt(Yb{&Me5t(}-%-LXtW&`U+autik;uxUIlb%>0$uOoFriWJKZ> zaG|GV%S*dt6&fJDqIoyOl4#tacZnDjaaP?T+Ub0H z>+^|<_P#G&&alEJ-~6alW|2!VM{(@m!fP){POqJ<#H)(JdvHPRTzBxo`QG@PV2VGU zC2{BbW{w)i#Q4F^qd}*YAE`$vpTmSWM^2_N?0+w0TEd`w0Oo6hW;6b>!Af+@!?7kM zr23-W_dpeK*YXAx{#W1c!A|VA_z5%IWMYJ`HD4foq+wdbk%Lmgli<6P0Y_9fu7sc| zaiPHueU0tg=HFef=M(o^xOtSw)A+28X07($7EyG?+6Y;~;6cOpgoc&?ntj)Wg}EPE zBAE*19l|AUEG6x(n&vU%^4ZMpiY!fYVo-KUD+Kv(t2Li1$9uls>g*20B zKE1zcnLTX;U;6fO^JWdvCn%OrTvIpw0QCDhh15z8E`&dt?n1FuIl@+1Ue?~WbpW&4 z8zLVUB+0&vPuBK&@*<;qK_J}`+B!TZZ$ke58IHMDOGu7ENPHn{+nd0On+&~JKDKI= zi@jG!8P>b|Zpie6(S<<-w1E1Ae27nE#U&-LSj5s<8YU3I_+#U;6NpQnXf5LwNh+u8 z4{PgRU}OjTt31!iB}D3$gs2dhTD1W_T^vB4NXHv@+VhA3#e(h;kL>2I>Q8TIFCUaw z7?;OcMGvF>${jzx5S2;&$qxDrXyyJnSup-*e>Y{V?om@PG0P;CE%0w+mFJVsRC|_> zPD1&x+xAnCgCsi;l?DvpT0oXZz9VX8pzsf96dhGcqd4Q8_c zpJnO)T7L^PI+Gz@(3v6}1SrE|D#`D8wv&ZsI{Q9t)%0?GJz_r_x*wd-o< zPq%acsFbmVpFRlzqgK#*8s)TH?Bh2|iT>`$_bifKY@@l}1Q<`h+gaue02K!y6^0RWvg{FhKy;3u^|_R!y} zDN+i43puEFuE<~f==O>ChOC|-o{iga1nBG@DGiWfnv+jK0MF}5!!c>A=JGduOEWh* z^sG8<|Gx{X-IMx+)3@k`Bp3rhyh!TIA;bVote23y< zk4sqI)asuxT$T5=f}}~YlQk$Nz^ru(0d*s~k;3Zh2&!$tiikxSqZ&E!O})^0rQ}x? zgu}GYL)Y6I{RCzJgJrTjHc?Lw-isXj1F8-Ama9N~^sdj>3rG3H!_ttPeI6c9X*!EE zGii-wUb(3z;G$cMO996H`fq>{*$9krk9 zGVkkSU!?ye(ls{OFX6(-H(w|hI22mFy#1bKdAkw6N4AqG4+|LoI&4#z$g@&5p=a5Ov#J3&g}FXJHbA%`pa6oB z2xJ{1#1ZJr2d`e-rHdl2MMm5q2LWfJ6t1{Yv)?OxDI8pu{D6T)bUYKz-D z?$kVa7#7I%akG$M#v8S*5E3_bCR)NoCne+X9gp$*rX^Us6H6_*97ecqzb>WPFb1_x zn(obdP-fZ`l|D58ol$nxXJ?TZcL20LN&%SCy~I04wfqE^v79PVY0@t-R-e*Fq@P-J z=<&Ir)WxUsRvM7LpV%t+w;vFN$8(s-HZ-I@h!Q%x`|Zs8!oElHp`dT+P-dsU2n^zC zb_wP)es^+D_i}*)Ep64QdKBh1CiCXxQ`$!voq8FTySu$gDf;htuEnbJu+o_ppKyKZEJlXI>X%*x%~`he3q&#Akju@umIjS{4gbvF|X$nG62N zm8=ZEF{a`?p?+k$x4V*R%c0Cs)lhcQHuhGrS1GLo$%>o+=o<7h2ty)k``vJh@q^@J zs*x(nf$Em5G2Xx0lp9iO>V8^1$XtlI_UH;j>M{K1oaGN#jus`rL}PSiKs5%;sZkfW za}0=6)QJgFw1GBDrq~X(=y0g-on$%pw7mCW5Hbn?uR<1<6Uyp|dh6DcXJx7?Dq`1j zJ?_c+8j5EIJ=JBgVorlIHr3jaiG%{fafC>TIi@410AVmua*)Hl`Bh^P?R;;j3$%WE@7>kitA)y)3c4WE z4Hg6w;1zB|iw5Ss=RlRDO%K3zy$1}x(R_wCsc zkIek!T=`nQGgA|MNAPd(7V$`C0=m=UI6`R}Hpjyc$HAbcwY>dX+CN5PeJ-5(d~Gg* zuQ}>DXEx~I%ilhLk`i!5p`7luOESb02ilJ^J{iajPEK|d-)iH$Zu>2D2FN2jF8h|j zVzfF(zSGVy6Z>w-u+XF7<@tqqhgQ7FCxAkdCR1vlq{z(ICdly7$&7eNH?ir2QG|ab z(_}K6J^vLjfRF&6xlIB{VHz#PZo5vP4$P~Or8eKeR6Z=-Tzre(?P&Z5bZy>d^ZP&L z#Q%lK!M`Vsf5{>VYnLQ;zy4xx{LC##qC;Ik8lcstcn1XB2Z*Tp%=eSH>;YuJ4KS1} zW>oRX+pDY3P#&0>Q~RqHja7?h^Kdc#((a~OZ(Rw~Cu0oSzqKaHWmy(K%iXr%QjY;j8}xdN?|E%~t0Ae=B?* zoC-bhx7~Ql(b4|iym@f2n*IEIzNu{5xog+-GaosJf|z7;L8V}PP?stQ=y`e{Ce!7% zZn@h@y{%7sHoWAVQ{El^*EQjcv~tfoOb41DVL$_(-IXs$V zb{+HOmD}9%3dZQO=LS3^p1GT-)gOt1o?~_dCR1={l@JED*0BuznlI(r`Hpi{ z-V?;9cD1l?82kUqV4XDq;YQ1C0r<7gd8jhWz&z}Qxf*v(pyAc-PV62_r>kpnu8B1W zfY&l{?=Q{5^11*) z0x&#*wn67BSBASno*?vyT5~NDz(ky>NY%pM8%$h&i;7}R_cTvGymHD&^60+JTm~L0 zj&*~C0YOM-V`UBCFjQ@trl=pq;O2x!Qo@yf@d-h|DS9^f4q^s{+&XZ440;s~|juJ3~ z7f&GJ5YNeZ3b3MymC+P=58J)!(tg8&@Fn%6>Ew8jp{eI2)R~4@Y=!_wp2k7r{W}iGg?pLzA>@G~L?6 zTMwN?a_7Hhz_RgcKZljS$m~-wsxm>@qFRL2>?5~!Ks1rO(?iI@H4ks&fN^?>SBfpp zcIN;iuoT}*)i3x_o2Hxgg8BTBia%d!Qvo-tK}PR>t8NI4E=~2Pl>!}BaDwL2rN$9w zVr}H^uYmD)RWI=4k%RIqA0JVC z3CQx5KOl+C+UGlO;BOauLh_e4Il0;~v-ni`$fw~_t8Zy5Pl7_>FVWSo9WXJA6hcEI z3<*V!RR9MeNjC`Jn9S`Nq~xA9dtGHLAUK_K!^Au_KCGx}m&F*Y@_Y*5{8g3a5&hf1 z<+<_1hr3E8GFRSo=_wPlJ*J48S|jGq3G;UL*XWNmsl>+gYW+U;UV0nrHkK$nZ9?XH z@R>Z=eA zjN~(^9@AuB@B@aUL9b6!zn4u^eR?(Y!Zq|lm$DzfKF4ssY5U6*rkHLl6!EOFqQefhV$tLT=n#`>2-@zU5lYAedE{Up@fl`k5a`HnU1Xw3zu88DK zzT`oGuDWe+w&)PR(Fe=hpEXd&oebc$&*8}ro{q;=mD-yAwC%i-Gjx|Jn>5Uc>;?1E zPeT#bL_qFnT;j3b1rbmezy*6jd5WW^4aOMUI5?*noIJ$MD{6jqf7i|ME=t5fm{DzJ z$P;AodEDt2MO4VNih9gc$4%ntcd*rFd(rORY_EH(=K-T2PzbJd$>q{Rh7~<6pi3o* zP^4uekCDM7J0g^-gkeWqYizDHN7+aI;>bDHKJ;)p@13*av(Eh^6Qq0S(22Xn@fbo7 zsT-(MT9BfWaf4aisl@TthV|%QDyDMTZ7S6&XDOz&!C46doacIE{ksHKcuk~vvc$5c z&!X-xi;=-Np7wc*2&uDG>3CFo2v9V$t(njWeo7yj*wuJAh+^rq^ntIY@WyP_Puhqv z??P<}tnI3E&>k?qY7>lN>NR&W1NUL<>_I$)9c~v~Q(i~$i^$%jn@&MA4IPS|A8@?= zd|Lv}I=I9*iuFlOL@`Oz3Un2T532rvM7DNlCGW{b5<05ekZr;@Ls#W=g_Ne4<7!o} z#TZ&1G=(@5lJS7#o0mR;64YhE&sR#l&U2R%ZVtb}csi+npRPDKV`>>xcQ$-ahg;8q z33|Z;Rmro{hl54})EL}_p!_<-udioPSC|i zM=d1ZSUm{Bmi7+fsv6}aM!3F(A||K5vN&jH+tHHp>OI8?Rl&z+SFmdT8-bWi2m>VD<5 z>7LR7-;wu^&mS!B*bsQS{(wZQmRZRd+|z2WJfKL0>;9sVC~SUJGs_~UU-WxT!(EmL zzfy+4+XG3gmq1`4#BtgeANA$A^foLjRfpJzyhJwWnaWCSbiDVmnx0&EZ~paUXITe- zpHS=7JbSq(7R){f@T|eA{0a{Ze?u{1z8J2Xa%Oax;kQ)Em>0 zerBKRe#@F_7d1#09pGqibyf`h0sv72=z8#IL&9Ek(E0)d#Buo&5Mr7DS%oIHaMQo)T^eZ;de|V}y(dDw8FFgNw4q|Z_<+l%lRq+| zOUl92C*`W$DL*ZO)u`VekR^EvHm6E5BD%F>0EbO{*IpM7C%MOIoT`fXF;|jP%yKqR ztzBncTzSl?#evjhpTQ^RgUvLj~bcJzYJW9I%T1X`691 zl~ZvGr~zgp_@U5{sOHyG9|N3M+;-fE(>=iA28inB*kku_;vokX>X(LucX$Ws&b5?# zirV)nIlO0kkdQOa@OYn1CnF!Uju6CFW$nO9fOPQn=$13fR29N-bajqFM~f&RR8X?b z^a$wLU;2GiVB}$KdT~D^3&hK|9u!M1AT^PXf+?zS1L$<12lYbmCvyMK_7+{Db>yeB z@20HhoYk5aG`HXITI}zp-)C>#zG8d3C9Iq*LV1W_CR!vP?9CQf5^~E|(2|=rc8kSM zvT6Gg%@Vh3KtupVeQmW|3<0DSa&b59cDd=b=38}GT} z3(F^GdfP+1?0a`AI*n7m30rrMAgy&mSrf<>ow?d%454xr9NlsPIi`u`MTludxnReu zMWR@UH?y>h#7ca&=h*zcefcYnN@Y?_+gTnoROqq50j@NlFHR;|)|3-7y7OSGSkWI( z(|c6@FV@~X9Lfi7`&JY}_I(+mRJO8Kn31w2X|rS-LbjN)4viU0_7H_qOcJt(89S3b zNwPE6nJE&(j5MyynBRN-{&?@>dG7muj{7;@yDV3Pd%{R6nM`2$7aHQrh^+O=2V4#CL5vBYdDDIwC5XX-jooq1DkRP1i zZECQVa|`T$@Mhz@$s3CMS40mO_-4U~#{qUULvYKO`A3jHx_Ei(NfrL`Gn?F9@BS3; zKy`oKTsb@`*Zx!Q!wt@-!90h#6G92ZAx11vM+YOapn3~POa#DGj%Uc|Co0&9awQ~G15$6UPF&zggd!V0>Hz!0gbJKeYUAiiw$r66^=c! zT(!1F2wH0|xL5>nq|~USOLF>}MtBa*S7G1453q%h&qxs|Y>j}Z{mGJE1!KC`9yN8& z=n37~I*?0A_22p~)aGf)wA9~*r!Xjpfe>l~xM>X}uBg>sW63Uz7|MWR9JJMQAz|LZ z;ez;l{$%fOe8L7U{{33^OM9(xFx7!7Ru+>P@-CD9NhgSog9Q2G= zRg2wCPGRZQ%Z1$w)&oXQO{KHsvnbx)U&-%;T7p&?pyuvb@_sI6BVYoy^LQG}!Ri18 zLD9%bObi08hHUDADovpbNge(H1!~jwI+Eb%dcfSbb zOj{+dg8MG-Oj%SU{GcCOinb$zO-9|F67DRkhFdG$#wSWxCs&()xe}6XAs71_9gDgS z7?R<#sAxcse(OprF6{~saiVqa@hzjYhbJdC5^zw{s{85qe)ywO?yH4rpP@RaL%_Hv z)5MWI4r!>~ObMxR^<5|JBh2X1`G#thCx7S+=!(u2w}(_N3z?4{j<$<|o})pif5|W| zlperpQqZjj+M!a;YrAf;bY47Dy3^H_m0Z-GE+Ot2sG}JC!jI#=97W+)2-6oCPfAQ! zu1(p)P%j43FS#}o8lWn)*ZCK^{PapFg6GxmXcXs4;%`21&3h<)8a6>#%Ei`W`ypxY zBj5)V!FWY)*ICDkQsSi4bCeSAQ1bo$LtFJ2j#q@aesoN2FFjGDAwO2){u!k9?**Jr4Q zz)b?*V!xnxbb_Q4ehN*jEn@>K6M|}=iLVV77X~iDkthQ|nOw`w1`BP;PQ70d;WvL4 z!f@XLHW;VqFw`z4Py zHsvk67IAzVcD%2RjkoQ-6(qx(I>lp5z zjrtPxPG*5+Uuo}JQwq;3+VB=I;%%9ZN*jPQCiM}-QCxsDo!e~+T+boly}PZ| zc^O8wMQcOvjh_>}5~sM*70$OmZ+|X1FIJz0kKnNDnC@8u*SFMany5M57R>%za;BxP z#^BTD+Nwo%oeduTauV?u(S3GVIraRp*XEA6tGz$i_oNKDZ%J-c(?^ygV$h<#OAV@N z8=Eyo2gIqJP4CvnT;|rGO0G7Kn?yh7vi+>GU8%h8bDW;x2kvk{?@#~Pf}pPK=YZZU zB!=-A)VMEGRV|{euTE%C8ZYe53cRC6l=@HgOR?UU61jp&-sghPK+X2K_ET^gAtw7d zY#HbEMMZ4y&H0~BVPCXI#2(-~+8Lub`wC^|;6YX@HP>CcH~(*KH>9q%Pf!>(0Fy?6 zf`rEb-Qc7TPnt!Y_bUZFPO!h?)8B?kIv|cI<&z5XLQY42vxMnS?lBqeG@m|55{$E5 zgT^GUuZgU8%HT3>EJkG_@Qo(t#eHx)Ghe)YU zpFR**&%O>wQZlKM;4>PcCuDXl5w9&$`j-xGe&5y&M9(@|;F6<9`Obb7NAJH*Z{hf& ze*J*zR>4EIF^xNpeG+MVoo3K0udoDSE-b%9NaU%t$b~D?KL3hzxGC*;_$JTc82M#e z1ppMGC4kka9Fo)FU$VUYSq=O`!&dr$sbHA5k@gw>sv{q`gyB&1B&N}z8$N5$O&}KP zwX(m_1vc&zgOcvAGlm+!A3i_wj7NDTr~fd|^{oV0Zl}?H0W6>vP%TTv3revEBqQ8( zfk?8S(aTV)H-VGk`LFQVbexOYTp#>^J%S>P$B6YyJ*!*ME z9al-~*_qm{<6eH@a>TE1Cw0OA}WSNa+*3#+zI$ieuJ}QGmhU4 z$9b8b11KT1b;kUDcAY`DKnPoMwdWQ5Z{DA^%TOg;0w!^aDSMAt%yH%%D}5Iv@F0$&G$wR&19kfJC7%%k&R$hX(WX4wtztd0*Mn=e6hvI@#0OQKcnt zFykQSC;xx9`mp+l*iK`V>6C7C2x;pvR0z;qvdh`}W=jGJD#F>_khaZnFeu+N=DQ){`vtoGkOR)4RuCAKzL^0y%l*x#E;0~ z@VeVmvTyflv&G_Qg~aUkcb^sUb^PBU@JKiaRF<-IC!joT{t}Y;IGKniqP>z+M3jx* zXTN;Cs_L*%VY`eW%y|{mvN)H7%O(fJ4do6LJfd~ab>DJav#Nxpv|E+@R8NOxeJ?Fi zGJh?f+1`Jc`Z{Q>+2ICVweLb+Eb9OnBK4DaeOe3WEWNq>!GO5ii{lq@s`P3bs zZLYQsAvWC}UDWM8ZmKEE0)eOK8FOso8sf+C}4e`?_AI?|o;VynX3Pky1a}9engQ z`W*Tz2J8dx!30tE6DV8SI9WfQ3=u(kI@Ny|3#|_3J5@~A{YJc~zU^0M=^0>kZ-UoZ z?eU9>SX{Zwvn>p-k#FB*(j_gUo9e1lg{1G<$G*n> zx^*8ktaMyCtjraI{(y1@R?ARl>8d?g!FSRMi%yrHekD`Sgwa zn@#Qr6VwOxWT)O_yB&GyC<_W`fFF<(5@wGzZ<$cv3T<|O^g>w0JuNIu?8WRc{cuVl zo}NS{RieM>urNR%iN{D7LD@<@RXeL+_x6sXWM{`%2KK_mpx>6|LAM^6F!`~kYqk$Q znH~XqmLoL;2vvZ7aDa3Oo1!>|F9-|_BLG)}LL)=F%4T{?&+UAIg#-u6TgiU<%`<~M z7z6WQ3!_J6ntCxQGOOJq;wa-u)S;T6`K~opqy7;RA%7dEL#+7kCw}$I+q(RBkjt|q zG6T=VppGT=B>&?nls`(gC}l$co+Cx$f6qx#y+!- zfm_qe9{Ix*GB|{FwoS=jY%8+mQsWp+e0BmK9>%wxr$2Vhaqi3eLYE5J;MajieK~g! zq$TPflC20**?b!1O=I@AN;EwRs&}2eNGiC=ka}l~s!XObKBezwB|>j%ANW*TQw?$? zPS_s#xp;O;?)axyoLKthufey=&%he88OFclgF@2x|rB6$mLJDB_kg&>QP8G}+$KS=0p|*C=??n#vt1s%OXjOneZ^`ExdU!+={p zSgmOE9}dJw0#(Hc@&u(t#tH(J8&8);ic3=>%0~{B%q#`!SbNBgisfoo9jPPs_RoZQ zLqI_xUfoRyA1+VX=rEi8JeI?9W(HfyQzC(kP162(l*H%bFh-Wicqx3Eci~(%S$9PCJtq|_$o>X9 z%DO{??lR(O6HN|m(fYaxtw9N@z))YQ^yOb4Mt(eH7Ck6R{Z4s$z5*i7*2i)noj(?y z9>q;gP}>8@0)iHNx``5r0{2Yf?jO33bB}L`!L+OOw;M0>KxNro;Y<9iu=0VTC4aPZ zp#I)ANhRypWV=bJfwuaUue0r~U5a7n*WeoHQMC9zuKoNnAt6dIg=MkqTqz5Y2Asya zsJaX0=d-s@bvq5hUOxClN8DJcs1SL#aoR-ez+M7|ZV1X*`fMPiL%k7@xv0gQM^(

    ifkzfwJU7Nj)7$yJ~C4`(zM_e^g(#zo33C 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 literal 0 HcmV?d00001 From 786db7482ee0887f08e4a61513d84e6af5727c0a Mon Sep 17 00:00:00 2001 From: kaideng Date: Thu, 20 Feb 2020 17:27:25 +0800 Subject: [PATCH 189/190] fix model overwrite by the same serviceId Signed-off-by: kaideng --- .../core/exceptions/LoadModelException.java | 13 +++ .../serving/federatedml/PipelineTask.java | 81 ++++++++++--------- .../guest/DefaultGuestInferenceProvider.java | 49 ++++++----- .../manager/DefaultHttpModelLoader.java | 11 ++- .../serving/manager/DefaultModelCache.java | 2 + .../serving/manager/DefaultModelManager.java | 12 ++- 6 files changed, 102 insertions(+), 66 deletions(-) create mode 100644 fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/LoadModelException.java diff --git a/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/LoadModelException.java b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/LoadModelException.java new file mode 100644 index 00000000..97d2fae0 --- /dev/null +++ b/fate-serving-core/src/main/java/com/webank/ai/fate/serving/core/exceptions/LoadModelException.java @@ -0,0 +1,13 @@ +package com.webank.ai.fate.serving.core.exceptions; + +/** + * @Description TODO + * @Author + **/ +public class LoadModelException extends RuntimeException { + + public LoadModelException(){ + + super("load model error"); + } +} diff --git a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java index 39817885..0a857af1 100644 --- a/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java +++ b/federatedml/src/main/java/com/webank/ai/fate/serving/federatedml/PipelineTask.java @@ -40,48 +40,53 @@ public BaseModel getModelByComponentName(String name) { return this.modelMap.get(name); } public int initModel(Map modelProtoMap) { - logger.info("start init pipeline,model components {}",modelProtoMap.keySet()); - try { - Map newModelProtoMap = changeModelProto(modelProtoMap); - logger.info("after parse pipeline {}",newModelProtoMap.keySet()); - Preconditions.checkArgument(newModelProtoMap.get(PIPLELINE_IN_MODEL)!=null); - PipelineProto.Pipeline pipeLineProto = PipelineProto.Pipeline.parseFrom(newModelProtoMap.get(PIPLELINE_IN_MODEL)); - //inference_dsl - String dsl = pipeLineProto.getInferenceDsl().toStringUtf8(); - dslParser.parseDagFromDSL(dsl); - ArrayList components = dslParser.getAllComponent(); - HashMap componentModuleMap = dslParser.getComponentModuleMap(); - - for (int i = 0; i < components.size(); ++i) { - String componentName = components.get(i); - String className = componentModuleMap.get(componentName); - logger.info("try to get class:{}", className); - try { - Class modelClass = Class.forName(this.modelPackage + "." + className); - BaseModel mlNode = (BaseModel) modelClass.getConstructor().newInstance(); - mlNode.setComponentName(componentName); - byte[] protoMeta = newModelProtoMap.get(componentName + ".Meta"); - byte[] protoParam = newModelProtoMap.get(componentName + ".Param"); - int returnCode = mlNode.initModel(protoMeta, protoParam); - if (returnCode == StatusCode.OK) { - modelMap.put(componentName, mlNode); - pipeLineNode.add(mlNode); - logger.info(" Add class {} to pipeline task list", className); - } else { - throw new RuntimeException("initModel error"); + if(modelProtoMap!=null) { + logger.info("start init pipeline,model components {}", modelProtoMap.keySet()); + try { + Map newModelProtoMap = changeModelProto(modelProtoMap); + logger.info("after parse pipeline {}", newModelProtoMap.keySet()); + Preconditions.checkArgument(newModelProtoMap.get(PIPLELINE_IN_MODEL) != null); + PipelineProto.Pipeline pipeLineProto = PipelineProto.Pipeline.parseFrom(newModelProtoMap.get(PIPLELINE_IN_MODEL)); + //inference_dsl + String dsl = pipeLineProto.getInferenceDsl().toStringUtf8(); + dslParser.parseDagFromDSL(dsl); + ArrayList components = dslParser.getAllComponent(); + HashMap componentModuleMap = dslParser.getComponentModuleMap(); + + for (int i = 0; i < components.size(); ++i) { + String componentName = components.get(i); + String className = componentModuleMap.get(componentName); + logger.info("try to get class:{}", className); + try { + Class modelClass = Class.forName(this.modelPackage + "." + className); + BaseModel mlNode = (BaseModel) modelClass.getConstructor().newInstance(); + mlNode.setComponentName(componentName); + byte[] protoMeta = newModelProtoMap.get(componentName + ".Meta"); + byte[] protoParam = newModelProtoMap.get(componentName + ".Param"); + int returnCode = mlNode.initModel(protoMeta, protoParam); + if (returnCode == StatusCode.OK) { + modelMap.put(componentName, mlNode); + pipeLineNode.add(mlNode); + logger.info(" Add class {} to pipeline task list", className); + } else { + throw new RuntimeException("initModel error"); + } + } catch (Exception ex) { + pipeLineNode.add(null); + logger.warn("Can not instance {} class", className); } - } catch (Exception ex) { - pipeLineNode.add(null); - logger.warn("Can not instance {} class", className); } + } catch (Exception ex) { + // ex.printStackTrace(); + logger.info("PipelineTask initModel error:{}", ex); + throw new RuntimeException("initModel error"); } - } catch (Exception ex) { - // ex.printStackTrace(); - logger.info("PipelineTask initModel error:{}", ex); - throw new RuntimeException("initModel error"); + logger.info("Finish init Pipeline"); + return StatusCode.OK; + }else{ + logger.error("model content is null "); + throw new RuntimeException("model content is null"); } - logger.info("Finish init Pipeline"); - return StatusCode.OK; } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java index 06d62407..c75b14f1 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/guest/DefaultGuestInferenceProvider.java @@ -17,6 +17,7 @@ package com.webank.ai.fate.serving.guest; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Preconditions; import com.google.common.collect.Maps; import com.webank.ai.fate.serving.adapter.processing.PostProcessing; import com.webank.ai.fate.serving.adapter.processing.PreProcessing; @@ -73,26 +74,41 @@ public ReturnResult runInference(Context context, InferenceRequest inferenceRequ String serviceId = inferenceRequest.getServiceId(); context.setServiceId(serviceId); context.setApplyId(inferenceRequest.getApplyId()); - - if (StringUtils.isEmpty(modelNamespace) ) { + String modelKey = ""; + if (StringUtils.isEmpty(modelNamespace)&& StringUtils.isEmpty(modelName) ) { if(StringUtils.isNotEmpty(inferenceRequest.getServiceId())){ - modelNamespace = modelManager.getModelNamespaceByPartyId(context,inferenceRequest.getServiceId()); - }else if(inferenceRequest.haveAppId()) { - modelNamespace = modelManager.getModelNamespaceByPartyId(context,inferenceRequest.getAppid()); + modelKey = modelManager.getModelNamespaceByPartyId(context,inferenceRequest.getServiceId()); } + + if (StringUtils.isEmpty(modelKey)) { + inferenceResult.setRetcode(InferenceRetCode.LOAD_MODEL_FAILED + 1000); + return inferenceResult; + } + String[] modelKeyElement = modelKey.split(":"); + Preconditions.checkArgument(modelKeyElement!=null&&modelKeyElement.length==2); + modelName = modelKeyElement[1]; + modelNamespace = modelKeyElement[0]; + +// else if(inferenceRequest.haveAppId()) { +// modelKey = modelManager.getModelNamespaceByPartyId(context,inferenceRequest.getAppid()); +// } } - if (StringUtils.isEmpty(modelNamespace)) { - inferenceResult.setRetcode(InferenceRetCode.LOAD_MODEL_FAILED + 1000); - return inferenceResult; - } + + + ModelNamespaceData modelNamespaceData = modelManager.getModelNamespaceData(context,modelNamespace); PipelineTask model; - if (StringUtils.isEmpty(modelName)) { - modelName = modelNamespaceData.getUsedModelName(); - model = modelNamespaceData.getUsedModel(); - } else { - model = modelManager.getModel(context,modelName, modelNamespace); - } +// if (StringUtils.isEmpty(modelName)) { +// modelName = modelNamespaceData.getUsedModelName(); +// model = modelNamespaceData.getUsedModel(); +// } else { +// model = modelManager.getModel(context,modelName, modelNamespace); +// } + Preconditions.checkArgument(StringUtils.isNotEmpty(modelName)); + Preconditions.checkArgument(StringUtils.isNotEmpty(modelNamespace)); + Preconditions.checkArgument(modelNamespaceData!=null); + model = modelManager.getModel(context,modelName, modelNamespace); + if (model == null) { inferenceResult.setRetcode(InferenceRetCode.LOAD_MODEL_FAILED + 1000); return inferenceResult; @@ -107,7 +123,6 @@ public ReturnResult runInference(Context context, InferenceRequest inferenceRequ if (rawFeatureData == null) { inferenceResult.setRetcode(InferenceRetCode.EMPTY_DATA + 1000); inferenceResult.setRetmsg("Can not parse data json."); - logInference(context, inferenceRequest, modelNamespaceData, inferenceResult, 0, false, false); return inferenceResult; } @@ -126,7 +141,6 @@ public ReturnResult runInference(Context context, InferenceRequest inferenceRequ if (featureData == null) { inferenceResult.setRetcode(InferenceRetCode.NUMERICAL_ERROR + 1000); inferenceResult.setRetmsg("Can not preprocessing data"); - logInference(context, inferenceRequest, modelNamespaceData, inferenceResult, 0, false, false); return inferenceResult; } Map predictParams = new HashMap<>(8); @@ -157,7 +171,6 @@ public ReturnResult runInference(Context context, InferenceRequest inferenceRequ } inferenceResult = handleResult(context, inferenceRequest, modelNamespaceData, inferenceResult); - return inferenceResult; } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultHttpModelLoader.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultHttpModelLoader.java index 70510cbc..f7145d8d 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultHttpModelLoader.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultHttpModelLoader.java @@ -73,9 +73,14 @@ protected Map unserialize(Context context, byte[] data) { @Override protected PipelineTask initPipeLine(Context context, Map stringMap) { - PipelineTask pipelineTask = new PipelineTask(); - pipelineTask.initModel(stringMap); - return pipelineTask; + if(stringMap!=null) { + PipelineTask pipelineTask = new PipelineTask(); + pipelineTask.initModel(stringMap); + return pipelineTask; + } + else{ + return null; + } } @Override diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultModelCache.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultModelCache.java index 32d7efaa..f9fdff61 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultModelCache.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultModelCache.java @@ -22,6 +22,7 @@ import com.webank.ai.fate.serving.core.bean.Configuration; import com.webank.ai.fate.serving.core.bean.Context; import com.webank.ai.fate.serving.core.bean.Dict; +import com.webank.ai.fate.serving.core.exceptions.LoadModelException; import com.webank.ai.fate.serving.federatedml.PipelineTask; import com.webank.ai.fate.serving.interfaces.ModelCache; import org.slf4j.Logger; @@ -59,6 +60,7 @@ public PipelineTask loadModel(Context context, String modelKey) { PipelineTask pipelineTask = modelLoader.loadModel(context,modelKeyFields[0], modelKeyFields[1]); if(pipelineTask==null){ logger.error("load model {} error,model loader return null",modelKey); + throw new LoadModelException(); } return pipelineTask; } diff --git a/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultModelManager.java b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultModelManager.java index 884b6b3b..194c3b6b 100644 --- a/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultModelManager.java +++ b/serving-server/src/main/java/com/webank/ai/fate/serving/manager/DefaultModelManager.java @@ -185,10 +185,10 @@ public ReturnResult publishOnlineModel(Context context, FederatedParty federated String modelNamespace = modelInfo.getNamespace(); String modelName = modelInfo.getName(); modelNamespaceDataMapPool.put(modelNamespace, new ModelNamespaceData(modelNamespace, federatedParty, federatedRoles, modelName, model)); - appNamespaceMapPool.put(partyId, modelNamespace); + //appNamespaceMapPool.put(partyId, modelNamespace); if (StringUtils.isNotEmpty(serviceId)) { logger.info("put serviceId {} input pool", serviceId); - appNamespaceMapPool.put(serviceId, modelNamespace); + appNamespaceMapPool.put(serviceId, modelNamespace+":"+modelName); } if (logger.isDebugEnabled()) { @@ -200,7 +200,6 @@ public ReturnResult publishOnlineModel(Context context, FederatedParty federated if (StringUtils.isNotEmpty(serviceId)) { zookeeperRegistry.addDynamicEnvironment(serviceId); } - zookeeperRegistry.addDynamicEnvironment(partyId); zookeeperRegistry.register(FateServer.serviceSets); } @@ -235,11 +234,10 @@ public ModelInfo getModelInfoByPartner(Context context, String partnerModelName, @Override public PipelineTask pushModelIntoPool(Context context, String name, String namespace) { PipelineTask model = modelLoader.loadModel(context, name, namespace); - if (model == null) { - return null; + if (model != null) { + modelCache.put(context, ModelUtil.genModelKey(name, namespace), model); + logger.info("load model success, name: {}, namespace: {}, model cache size is {}", name, namespace, modelCache.getSize()); } - modelCache.put(context, ModelUtil.genModelKey(name, namespace), model); - logger.info("Load model success, name: {}, namespace: {}, model cache size is {}", name, namespace, modelCache.getSize()); return model; } From 4df8c4c7936e21c9763822c8c68058f0b65efbd1 Mon Sep 17 00:00:00 2001 From: utu Date: Fri, 21 Feb 2020 10:03:33 +0800 Subject: [PATCH 190/190] feature: add release node of 1.2.0 --- RELEASE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE.md b/RELEASE.md index 23e50d39..1041a134 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,7 +1,7 @@ # Release 1.2.0 ## Major Features and Improvements +* Replace serving-router with a brand new service called serving-proxy, which supports authentication and inference request with HTTP or gRPC * Decouple FATE-Serving and Eggroll, model is read directly from FATE-Flow -* Serving router supports authentication * Fixed a bug that got the remote inference result cache # Release 1.1.2

    ku>F%-oIQlkEVps6*KXZP?b~fjXY73^UT(JIEFHrf(;Te6OiH72+9YNHJ1 z5&=$VjS6%QX*Fp~cb+e3S&SJWtp+DN$&Po873uEDXZd-U`tdB8RbtwP^3r;geW_Ua z%+g)Qatr#)52`(Nx3UUcD`u()@InYjjQ6IJNtq_qTq1m+-}{2&hN?LiAT7$eb^1nB zozVd;Sr6Ja=hi0S-$ry!N_KC>$aTraM-Yht>^i!1zlGW(`xzGFLJgjdcb4fBkcKtO zg2$}MDy}fv-wQB$`7;|GqIVLt1Ds?@I5HK-<^(3BvaV?e)iIgXDrg`(kKv zuE_+m%2yKv#*`}LWYaG`Di3a>jXV`&BaK@oc)4(Q@QVl(>0&m+ZamtMx7af9ZL>wt z2zs3CSk~^Gn{p(R9M&eTT(?WV|21km?PFLTIy$3(LL*q(CJjoc&$3L(DzAj?%)Pe; znKxO{O$Zm<`Wb2(yJPqW0{@r}XN{Ho@E9r%%bH2wLa|jXj6+ze$J}Mf!^Q${pw6-f zd&#WLM^OQ+x`~1WpJ$F$>1O2-knM?vgloek3}g-rAQP%E>g79#=0B7GynxL9vN4G#vY+sux@Blh=He;!4oB z@yKZzUl<;XY9`)Pl7FI=s$=7EQ6{w>qyEhHL(Ha|eQrUyF;X0c@5|tblT&ZJV$d+! zF&pNC)jcHPd0^4zHoTh(A^xMnLD@i4aGp9+l0zfqeMLS0>WZUZPDtFC!2A_ZFPMkS zOUhg~h2kOFfh=0A5}f!Rp!+~OIqSBZ9jwMcp+LeBvIH%p9|ff@5p;q?#a!n&g;ELi zih%Oa_6_f`NToXgMEYKL2#=*rEMFhQGf~vA^99`F5@vt12xC7~ec+?`mkMpM1%wKM z{E0&kga_=rAQ2m%qu($wfUmm*JR>WUJBTJBg#3fZKJXo8@Ix>mi}Aq~L9M+PV~DJf zObvf7g*aLR=d{)&n$A{Y;m&w2z2w+3horw-kXZNx2QK(CK0G))T%=Z~f6X5@(60Ys z&my zk(`cQm2sH{)9JSmS4j#b2AqtF*Svyax$qLz4m}i1m%5S|B68W(LpFIT<~*DPCPZS+ zg8nn^1Y)ujuIHj%1ARl|S3PJU@+}OcC%G{i;d#Bk*hy!;(9$hYo-c`3?j7!5v&e|A zoGPVcO+fGk{l5F!r!)WeIrR(sZRJR>_@y`nur^Wo$IU-K(}`-&o^yu;pTn^~h7_PV zpqlXm8xwy2E~ZW}l@K-SWn>-#1=Gm6P#sW3MbTo4lOArYY^%ELTv@cJ?@uVYKKB0Wvvu|SOz5!b_1drmdft=HXimkFi zN9Mqo8QEUVp}aC`Ma*E8?Mx*@lJs#VWYx%Prjzrm>SQt4STTn?|IeG+xcxs~I1oWSl3wiUa{ z#O{o^5@DpC+=|(OY9>nhPV*ES@SWD}m*6;Sh9i*KN-G}KV@{*(&jc=7D{J0Kp{cYC zrwd<|iS^Jln5eZ?n6JFxuG`4%cmShK>!I+bxMtGDNftn>GU%iJLK*jPYiPHT0T^Y; zizPKwp5Oqu%Z}n(GK`v8uyL4v%&k%898$$UT=ypU>$)10$arZ_^~-kKYv#Gd55byh|xEGZVcx6xmYUI=NdwA#BizX>Q3< z8udu9%4TYhZK+YQuXnyI$Fd)uCwKbBp0op?{)vIJ$(BAQ7AUz071?WY@kfOQJNhbU z+9(y3?dRDDcGj@;=q-GRJL513qu*;1=ivEyfcuQHDX)&E3FCBo zk`&$Klf3j2--yZ9nD^p0VBc{p>AlTjOi@A5k|5J6Fn2^fpY+@XW+um~bC$4%v2EWt zN~kc@TgRF65tbFT_0*+Osu*F-NVmbDO}_p|-z}sH56}k#DNnBV(i15Ld@x^k)N_nk zctDq@bl1#Hp=V0v4XjJ~niivLkjJrPm;I?Cd~4@1=&-Ux%^rom*78&(K0G-H$gj`u zKaJjAzKi-(YT2%7HEDYJaoDH>>CQKMRO6E>;;Ws$q)&a$*eMn_>^xLRW-LS5>Hz>ZaAVJSWE{e}{vs;rqj;O6fm?8#<) z>O5LvKCPS3K=ut5hVQ@@`kT&x^}50h(p%l2u1Y=cmBqUK`At}S%ZX}MDkr7`eQD|rX&d7HK=zN>rYI(`4NmG0rL!F5b>Dw(oF3B@$ z5H9$jZa&+1(s$dnzEa+FkdCx-g`><0hOuv`&sM)tEXMlpM%KqR8Bkzc^Qf zLx7E?LO?ij_P4Q=aquEhX(}gz=rrLtwUG2;q(|AalE4~na;==KFa8#L1}@50b$7Re zbna=?;?P;uHHHE1sv)c~vt@eV=&bpTnn!l6x1&{;poi@va{eiz0hE~W5AhC47RkD+ zV@%k|;g#pR4sm8(#+j%4Y_rsQpUx7I&Y=`EIEI1z4UrnFemC;w(biR--7wbVbbhnt zSD%-|tv=_BhEP=b%LgN8tdn1G0rye>>%xUJI*TT;0wXczwO!F`d3=)@ITL4JU2xI$)dQJ(*MMSypXGD|%t>)$u%AQ@~ zP81iKP;&^j7~VnN`M|ZPBjV<>vvmhYKb2=swmC|Q@FlgcAZ-NP9P<$dkAxBC5-iW_ zS0}IJ4z)qDCm`maJh}h2$AZ1Zl9z6f;NI(!1R8eysNfLPbrWLmXUm(w5h^s_5y&2L z18NvcxlWC5u&j{sL}Ux0TL7yI;3H^tQP^x*6^+*YN^^pcy!qFK&Ppu z<%n(7HSs3Py&tNVH_Q%a?Jx&gvdv3jpCkUaErPIWEe$V>Nh&K!3+t?1EO~vHuP|@7 z8Nq5wojz74!Nbg&{xb~>_U=aAChnx9lZAU>Ipx&itIs5%`X+sHb4^+ zr-N+uuiAZ9FSm#3kRSW=63OnQb?bH7CrhY2LJ7=XQtv7K=UtT{Vf$MG8da|T_P zSd=R+U8~)qX4bbbdJjdze*7CCol95CIh3q}?6P_+2B9w%xmt^;r*m@psP&iC)}% z{s-6lE6HR@P0l>7+O`UcK0~)4#?lOQwfX>RQknX%z@5X-PPV?|8p-rbUYu5bMX_E+ z9-hRboHO)15iWMw44`{ew75t!ZOI84v42GPT1TAjl>aE9eCwE9$qnB4OJ%k7GgmTK zKEU&rbN^uow)6d5%wZQFL)G^cVom$oNUzUXvFgVGm*8`O*oyk5i%&~A22iKBHrE@s4cOh=x?e**IU zQZv6Gzz3IV_3D(5f@w_nSiJS(hJ>1VaU9o6{`D~(T$HibuHyKg|yR#KN2y>f0;tleK0D>3K;t6LY=QP`8k-QYoeund@kwflRSR+x%oMLLc=J=57GDNl9Hqu#>uN=S1}(;8398m^eLzbskv zOu@71q0-vg(pIXhVfxXOZN!ENhKm1o!nOVJbJKIH?O{X-i3k3VYWc0f&&zv5Qa;k1 zIzA5jqM)hg?cP5@ZC<~x-8bLBkX)fc4^qAl+0&z~ACXqRlEX`2oBE+3iN3pmP?GT+ z_XzQLF9(V}9V-xa_TKCL-L^#Syc9y{>>-M|x#mEUbGw$=^oa|xb)O#JD&lcljWN5X zQ}Rk5aF^|MCG$!~>N*l)g>=7rMfKShg62=SChdG2;(Plxb$6}Q`E15lJ~ZY11mQE% zSEc@2@AVq5y}Qr$`CfxRdfS!fElW=Ceh29(FNpsJM`NEH)YbB}P$p^(;;_lW!&Y2^ zDwdBGea^!(??(4y@e-+FR&wDCf(PZtW}OV<%Qvu!*FJP+M=?(=&m|vL!<6AmpgE64IR;f_ z#;8hz5RqBlq6~1vHfnI8ab6&~RIk)&K$)!xQ^AlMopUCDca1jdw3+;Z-HoUa5(^S<6MNub>T3%D5cPyoC{0pfS|KAA zLcRO%BKP$Mn9oDUMVhHL{z6#9)LK+$&73G+;N;Wcc~pGT8%SkkA0&5`(Ie!oiertA zi4qJ`Ld@2@hwS3eWun9_A(Mahf~k>g z=GIlt2Xv0bIEl@~?-&kENr=QZu2gs+Zf^b-K4NrAbi>@BM##8jeWyQVtzrbyQ>Uik zWxV~NN9aULFisKEL;}XQkWO|a_BOY>!gl*aFJ#JhRoa}AS5X4Mp9iWK=F$B}#`k2Rn;osKKj z4q8o>kkkpvf+W3v`D%3nv9VR(1yfpeSbNW;fq3mWdjEYgbb@UB;nPzUlgq9x(?_%~ zG`iIvmMj)0@){M^m-u4HBg&)d(_XQ>+d*K+bTGp1C~rAUOlror})pd zv+}!;8Y!(2`6^;lsWnt1&Dc?3j%+kHx6P&rkJkXsrFnY**Q325I>7k(wnU~QxzJ5!B@Te> zN`n3JpAm)%OfCsJQOh9oQn|{QCL~l5E9bZq`ROU7hmyPa8X2^QBe~k+iS}@tq+!ps z>O`2Mxq@_Y1BUilbTokOkQ)>U<&j*?dAq-<85gELI_W=wV3cDULqSt6zOAK9ZQ2eX z2{PvGI`Y)}gTnSPDMb((Z6?VmJ1#-u9f~h!3n|kngqI7@m zoXPu!i<3WBMy2IU=3JL05oHlvF+%djnusVFA$w$_3b-P4ZIAq* zTuPO?thJ9wl06AifOXNlmLjLjf=j%IR`bR%+dwlPA(vbe3Y-~iUWKGKyNPCbV*?tOg017U~m-1p@T@~?-s=iR&d3FG`y1i(;UgqP!y_h>LXp7tQGts zEmtMdQu(0eyQ}5>ZPe;CbfW<7y^_fFo30w6v(Z%dvcfDIWy6~2;+#0Z={L;GL_V;i zN$HU1<x<1Orisx%o7C5f@h17e^oV`Ov=|{#T+f1%-3bPW$KUg z6HX06Pt#s^I80}qr&FLPqoiU3k=xaQQUH}NA}`Z9$T*Hv&qSZ>xuj7R8RFr(GuY&$ z!G3*~Us>G?9|v$}Q_~Sl@hHx~4Y*x_6L1CkOG+GT?rl9Y`o)>y)6qc++2|nK&m{_kC;=rX7&M7&2D z8Q_pZmb3ghD?=EX<}~JJQtR@Kz8(Fx@2mV{34KI}()Pqnf9|pi zDTLSR{ZGjF_lNLmdhsfG!W8}>2+pxSAF)jQdnALM4JTg$sh9q_rt&|w$XM<7B;I8K z9|8l&m;^a$#qm%I0%rqoaSYzW@?5y=45GOGg7_zUwIM`G0?q}PwZ8!183BKxr zbNqf9UA4`pKa?3*_mFo$d6i^eu!T%Y`8k|%Jjj-@=bZrk=mAyz8iffTfWD7IzU*1ge6aQZRwg5 zIj9=GQK2qyqb_2vE~TI@_c1GI>zUxCpRoC%;PGxo6_-US=auDk1996Izf7vVEY+kV zIP5tRK`4TKtMND$2oB!NWp%H7~wylIl}?f9S6zKjAp zKhkVdC_qx+iXlBBN)}wmkk@*?#~sIlitvEF6BbxewQ#Q%nf4D^$7YFU(O#{IPg~~C zbg{#u3)IR2@K#>EES(QnRrx2Kvn^pE=J(7(BA5*SH3#`DXwwN!nOygx15h^c=1Udy zPQf&F_j`}pMFh7HjMRkGV3-s~eA<{a-KnZeHdoI%{426(JbNWB{_=bRzQ0N~#e{+z zd3J;Za)_8lUC&>BfWY(#&p`4UvRk|OhKk_zq)J-1WDVE5pOyr zAjp0Lv3qDF&&sTSBbt)63gCvBU9l64L?CSk0>)kH{(y)qkgDHtXzxbO`FTH4r5AJ$d8^=F?cQ4^D#z>! z{u;>zQdmxmK>tjO?7$sR?xKiP43OK7zyG_f)XW>z%$iw-6{Ww?LB-?$H=&4qJtA?h zuJ%Ubdb!ysSsvRDH8mYo1C%^<x^t|9Gwhooc?p+@zrts@B9B) zdK9RfDb5L?e>9&{yz=gq`XX`)Ct;RI-h)>A0ynoO$A3ya$ zU8{IO4aL{%53QMJj-MZ{IZnEE@_N=jzx~~#1c6%~-36&MV~oNB&>7p~u;=ZlvaGAx zQAV}ltoIp0!X}~9G2^dOshziKENfO_E;<7NG{kWWo=CVf4o8D= zF4$qL4&Nt*xFT*r!mnf`j`?ogW4G+YQ?sxF2<<*ZP!r~UNMEnG=?=5AWu(sLB<}lB zcXDN3B^>BU&vI#=y8!FOj*1d0E}@M+aq1SAe~v*Nd0!tC>F#XXcEO#cEdReqd*|p# z!#3TwJGO1xcG9tJ+qToOZQHhOcWgW9pyH&1lm2FAfAh`RGkfiG)~c#M-hZlAt?Rj- z_r0I{zJB`R*Mwk8H#~?aI(Q*Ol1c2QzKM~o*|t3@cwEymgfI7au%wbL?>|qIL`NN3 zMe_8M`z+BJXg;`4oaxir#LSUN0ZDG!8v6;UIEFaWvu_nb^HMUcYFF07*B~%l=NspSM8F`aD@@+{A6$R_2iFS_hwe`Tm-J z{aZ0VPBb$=>+_}UBUXd(c+_E`8fLNSo#>#(q>XdTl+yP$b_EDvmsF`5O!(P)$RUM( z@Vz4x#S#@2yx%_T7XlnI-+ZVT#EHE#ek>B+#o#Ce{`DX^gwoyI9}w9`Kv8^=+bfni zj|?@|bkyW_jd1Z9;6600Z*4UxI?`MTlSpitA1NK}iVa<~H*qB$Q_>Rl_mzL{fMXbcfx31-*H- zmqPw5%-MWyuf(t1uW^T0hMoGq?~4!8JKfYyw!LPys9$E$d*9)9eZ0*C2D5%_gLiWT zT5<8Qn7@nT>7|&Wr!(0Mv2>Yvhj+4d!Rl3>&{@#=DH{^7B|j5f^}AQH*eyaA%u zFc#NJgvV$)vX9&ckJKetzF3L~V@SUFllrc$R@`|bQ3O@*iCQ=&?lGduAVf)xKa-w zL{-BOl@s2n@cKvzgNS=f!@H!4vkaOl- zd&;Ppl!YsFikdaLb--sZ8AUKX99h188cC z#U$HW=1sh^#jXt77Hr>%-5-NlI*o8~LAg=KN+iEg+-S;xXu&XNL-d*oj#f3V@aDi0 zuN<>pS+i?lQtxiYUual(K1l$vw(g)xv_EV!N^53wFIW^YOSqr5Qri&N$M;NQ`LtQ2 z93m`urnpjO31xU^I5gPJj}v7xYEHlLsegPc*NguUAsiipL%<4JFkcCEctyNLh`npe zyNxvr_{Eee5IbOYr!<>yBa5GBhF|)aKN)0^{EL8sLO%>5hFT7kB|+kvXj>7qA;J~P zu2pg((aG77;t=VmK&?*U)lSpPoR(M2)Cs?P_&)lL83X2mdG^=n4YBDsBj(3Xwpy8^ z*Cz&sN1=#I0|s{Ck22SvG}Y}m>7o4HZ&sG@rG6zr83oy;hIt8?ROY)-s4e6v5+*B{&_ro1d8-=OAg6r zb)WssA?I$89F<*9y5Ik4lYg7s{M4mX-@mHP%U>yH{Qt8-m>L*;nWV?xQEgWf$G-<8 z7S0wXPNM&I`QNLe${UJb$z{C2{xlA#Q55XK_K76gswfH|$p~}>-z$Xcy|b*zGX1A* zv$g~PYOhE%)wjS^x2ZH<33+_#K}_YBIoX_BKCeuF-cM5PzM<7&56X}fiVyz^De`xQ zSx94Kz#hb*0WQ?sSuoU%{G-XkP?z^0M2iN?!^B{_TmM*xGMq?}g2|;;L_TN+b2+~F zU5MlY9Yn}Y*3+<6a|Ze$ojloOw^q}XfibNc{xb>#DYb9CM=c3%aBNsTKvZ&|nqlbiy`o{bWU(FYo!h@% z$ae}8OjoU2K4sV+h9sHnz{7VeGri~n5D<}&qomwLBkS#A44N-L053;9F!6d6aSchY z&E^2@12c$DKG^r0LRFe)m@1wYsh5I@Y`M;osB)N#P1@RUatPX5{tl~LO(k1V5Ib#k zxi8gSWLpws?tUQ1*1Uiw;3qf8xIw7FS#Rd#ufj^B_D!d%?zjB9awRvqsc6ECTFF)d zl>d+m;{||0T9+9F`hmcqFRgu7j(K5Su3oG z*5B75!Wmj@NBQHXXljf zjnMF3m+*g()rH_C1Ts)qvx8n0*ZH8hLlBc7W0Xc+8Nygd>*FP=7qyZ~&oxWVu}T_2 zGrQcLf%TJl0An`DA-uDkDKyv-Lnn4c=VyN)KRhQMRw@Kg&d;8O&w0l1t@Dd3k%RD$Xe${LX9E%;18ZwV6DRwxCjgUwh3zeA zf_vi}C4c(OOBU)xH&aiKje%)_K@-$lIV&5LVxZL7Gd(fx#6Y66iS zjf)cyC?O+*Y@qBX1EmW>O0;j!2*gPRg4FJfD?IE@pEGaDNH%jdxpI4bzkg8cecFEN zd3j8h%k}x*Ck?p#79oO(-v1LK+>wroBwt(*R==|hX4f0>e&Jr6PIfm~g-4V?#1N%b z^lm6zJ8Q_u$bDShR>jE*r&acNKj~|Z6Od50H&dRF*0)f~7@|o>=7@M)5ECh(EqzR? z)TS~kpwyl=VzkiC8W@?rnLS*+wwWBtZj{r%SVM=+nDh8tR;?6|Cu`|h5c89!xuGxiBDD@1lm7m7UDVt$XNm2cykEQWh&*QHECo`G55EC9Q|Y>_65@EZw(4=GPL=i9TXjM|>fkwl*}I4qwHk zqC`i84=iXgV~12NEp%k0w6vO)sSKv1h{)s_-L>@<(BM=p55wb|mRllJ#kiWHnJvKr zk%drY!k5LZjYu$$z9W~@Z(*9a*@g`nUNkAI5#t~s&cl4uW^f=jxMqr~sP|PvkH;v6ZxtK6z&a>*CfTmg zaJq9Ol{$>jOk*mazA=~E^ZZ7|)ibW@Y+WRwnp%@8oP|Lnk$y}AIj<{kw~l*?b@JZw zM-FT%pd^xbgZQCdysOYxW^!1Q+QxUgg5J`M2xx)FNvLjvxZXCJ*8;%^h1U6xx`Do& zCpw$o91Ps@P%}vOKIA%Zx5W-Hy0u9kOy~%5)O_>^vjud}SlP0L4inl@18lZ|#QzgnD(;$CNWssS@QL}d6(mM?x3MnX*A%qAu#mp_uY zfRcK^(&n|@KBmuRCS$L#v6^vvDce?-ym_nzG^TQ9HGO%X5^DLPDaM>Cdvggz1|GaJ zK-;^7czA?E_OKaGxPPDJ0ifV0QdspEBQSKc6W+zvt%YBjlphaLj2Li*K5qC)Q< zH|VO_(X5mfc~>k8nanou6T`vW%TBZW@oAMQ-i}uqs2MniIs0@qHl&2rcok_kY>u?E z$XGoD3~B2Nhx@nwMK<^q-@~Bw>{xiw?z_9&qW&VeNyFPfHC6cT1XY^ZVQg@0o~?BH zKUt^))Yb*du(XyV=YUaN8AY9phs-kv_%zJ78GH02raa{Utivz7wKP&Er+tT9Udh?c z8?>uzF?23E#dq3KcChO%nZm5KoYOT&Kq=4dE}@;PG%dQ9{!YBfN7822sceg);4D49 z$?_{@S1yz5fg~MW>d;ZcGg3lB8xS|B>5;IizJl)U#J;3MauoC&5phH{Q_Xh=7uKA( zdhqLUbCJv_4AgzBO^O3FI=oU1hw4=!#jp!whwio?BQpD<^hB$*I}L3T-WvK1rI}$g zJ%Uk&^mp#mzQjiOzVrutZ?Yr6&(%?(9#!D64gmkC6OFv<15ix)S zw8kclZ9IVPYv5it$ksAUi?rY zkik>!InbS>?>F?c^hpH)^5jvR8PK6Tw{|S{(yl(?mC>%B^Vd}u+==11A#hQ1<^??t z(MPQb?omh>@Mau)@e9<*=cl)Hbizq4 z%Sj6?Os-^|nVH%&XymcxqN_|FtICyZy4N-P!9-i*d@?rHvk0$_(tfT&f z;+ALmR~7wPsL-Cw4SI<#MA4KwqIm1mA_Gh4sfV|@rB{^<6dBf`CJ5Z|9VCv~Em8Lu z4s#@~&_JUT2k(hV_X4c+m#G3hZc_+p`%-c)$=G;BlW{SoMFRVM1g*^A2-=IWgei_g z@~{2E4!R^C<0LSE$eUspnpS{yOV2`jVI%uq&P}@;w=sIO9<4A=AVE}-#Sv-g?SwmM z09YBisGR0l!353VdPLUghzRA}N(#PTO+i^<6Z-6DOv3jGbEF9%@d-hYvTYHghI*XT zT_&F`TW#9{O%dK4r2%jSBs96fS&^Tf#c~=HVg+HUG{JHn(7voOOeUCANnqHP!y&Jd z$PcFHsPfez7jS}jSWl8Dh*#hC;Hb<56|h`3iDBK5*;{J{mV&x}@}s9IqbW_}h=(#5 z;zMCNK~<^N3~(J;X2M*^U;=SWk_l0I;~T+@%d0Ag z3VG2wW!PGF1x=k55mU@8zCN&3r>bj~YV|_;kuhqH9S*o8!Wl*Zhh)0I97L(4WP;d} zU(MHp*|T5GMSJ33H3x+5rTDr|*29V0wMzVMjeC_T3gDOL$ggk}aFIh()*EGaR)*jG zfvm!8(;l0QS;K8{`yjWk7j~r?fa)tP4Zgc zdx{YTR#}A?qR1bLi6z`03iPs^eOQ>ilv!uEvGVG^D1=82QOY@Wl%*2|YQbVRWfmGF zKg7wO(r5($x9x{sN4ykJ_yl@Yb7znHi@MUQ!Veavy)Ed-?7#GVIC?W#HnvZ&xCQMY zXKM0VHT?~mQvb9oan5*1KBUgLBsFv{l%~yohAB-6Lh1Bym>LH7YTURz1>E@*dUvNy z9Bih!y89wd|A~H45DAqS5ER51z*xL^KoR5wQ+imB^M*o+Z(!L>y|pvDtb{QGZnr0Ctx|w}k8oygvWa2(d$q zi+awL@omR!=%?Jk0+J^w&Z1FvkdPHA&mCmF;F1`~T@{`OXinkh39U zF5V3ZK3C*}#TE@)YpyGpQj!6;9&GZJ2%PGFx zDw0ypuD<#Wi7ac3sPg?EOr!fm=8lZyfrz|n3St6^lL*Rz809GkC@Rg2@$zgdw8vf;g$7H7xI zUG-ha7@43OxH2))BdJ{QM#`XVS`=*>Q5B;GFlQ#8sV~GCX_(7KD+AS6BSpmO+W1v3 zEV4Ti5(=4Z!~<1@Um$fE6^4I{ejrW2B^K z*Cd@?&`amMha^uX(fC@-2cxrq6rneWbbwLuu*k1N!(yYiQ$kVm=vRp0*ii~!o^)2< zHy6>j=i*{GuQv!x0I~iEyrl0e$$E4*hg5HF5=Yr6p)U?e4V=Q8d&uG)&D#21Q3g7N zFHZh5V6X~MC@1m`IIzPx5F3la|E~3h;cUjVKWa5Jc7ejbO&}UG&(|#hIH)!nitQcV zF$u@l`*k3uWcucwxj)Js130(_z<<%cjE7?5@c316|M7Rf@j(K>Bk=L#^8Liu-*zzo z2l(NYdYmJ%akRcwcnJK0AxIVyCFOq7i~L_d0ds=de?ro~J{c54?~L2^?U`KiWP5~L z7V;ybpVO(BUn=0!>Wz6i_3oktxuO}aEI=q9iuR~g5qU`&S%uL{N5>RP&kng^wNL1m z9k)>KCETmQ+elE3sV*@|$JYeXmi%r4=*KtT$R6r^hiS?^D_Q!C&;a`S-Rr^YtTesr zm=~YtZsdC~w_mNMh(3kfb5Fax$X(mcr9@1-GD6?cHTt}8{nh@*ml9x9$>y??Sotv2z0DUcj}1`^`+MWe2&QRq{T& zw<&*0@Kt=W;4g81?^J%r6Km0-x&{-QIB5rUZNNzF;{_UQvJpJ&vGEe|9}iaB<--#; zfLNuCkj6Xif8N#;#&qsiiZPE8u7-zg7Oszb_9D%vWSpZZWepBN#|^R{G4i&fZMi{- zG<=T^)$$z}!?<5mPJ+^?6(bOQFe(#9jqijgtiw<-FMV}BcSHHZm~kFiz9Xy#g{LR- zZo{%oN;iUD-mBxR556SA;!C+SOafl}2Ps-lf#!fs(~bL~SMp({j4yX&!^gLae%|xl z&X9W@a_2K5+w(oWEod}YE;SDXJuD6a<O-v2G&bvfxogF>I?W~Reqo=xBS?4QI6T=tI z`hts!04RWnKo&O)1>tR7IEb+yhQ+$y-eVcpvc753f_%@+A03Il>vp17Kk5VNV8hhF zoC<%A?9yvy)1+3I+}&!9n7Bx`C!RgVd$7$n#-Yg?9n^$95sdYkGW(P2 z8CspM_Ej6L6ILm9ZOVI|Cr%L!cA(R)QfFrD9tR2(nn5kRS7Uf79|e|WMu z=PC8Kg>pnqlVc@tLCX{JKGiMd4YF6Ok3dW(hg^M5PikYZRuZ-XI!VU2Lyr zj>;7a&5yf+Y)_O#hE>c`7!4=lSM9PCZ_(@Nckbb-jODTEz-|Z{P~;XBjM4M0Y<1)eUOB< z4~14Lq({FXOgB#pdi~4>5-foOJ-iUPSVP$RH}I-2#k|Ola9@<)Fe!m@33SJt97{O0 z*hJ$HY)+0)6HZ<;wd>F4??|EY+r;hop72r4g~|7RdAuLETL8PmealnxZ>ixi1R;2u zN8QG{#Y*^`X?@5MRk^OOC|A>!z5nHG_bQy6UEoXk1z*bl?}rBdHwEUuxR3vZ`cU4m zMP@|cow1QZ@;tz^Ee=X33MLI}RZd>7NG1X;GcO-1Y;$NU72JM?r3A??jQjP0-#4zp@oOn??oE@?&KCv@Mo-4lrlF(YFvBJP&iD|ys1|s7 zYb}^w@}DiHANX}jaqFS?>d!WycTf+L13fuO*Q;&EApO)HK8(h$dTYLLiZ>W4Ie=`5 zD>`tEZR6tg>mhqaR_OuUbpxy4E80l`TTHb~-SR;)h^ggRM@n<9ET%Nd*`&B~sE#26 zEdlRm-uV@UETb2{;x_d;>+eq?n4NAu8Ryg_E zj)?^^H8;5B?2%HxG0x=7D0)axoXG14gYQfoDD!D|nQG)mRBoA3+S$4#L2KZl7b)rI zw6wHeBc?QN`5!!lZk32K>`0!GY{?$cY{|VSGsrEH!nlGv+r-(wanEoMwK8TZ{t}g*TV?+ z96E4Q`u_rGBA;#FcwdRa$v(dMB>`qy2qndvgxS~H-LA8|uF~7t|GbZWoh9ZN=Ktim za!;cq2=oFyWVqPt3FQoVYfzrA`D#d_=7w2g-9m&pkSQOEEZRUjX0;PTNw+@#DaO4i z{=^Gmdf5{-UNzargn}&z*@y<&3UgU}j+F@QlcWpS~uFeiQCrvR2xn71OKUnomir!TOTOnL!#tX(cla9ARR9odZf(PI2_=ATSt! z|7!myz9auW)xBN|ys+mY^ahjiCT@2W2Y=_*2J8Ve@KkZ+lk!4-}Q1@FE0<_7O|s8@F8j?CM0x(oB?@oX{+(z7D7*dD$ZW3Fuu zFs_n2GEw}>aOVb|%m7mxv<4w(H`{!Fw*fKVyFBZGf7jTw-G@I-k=4zj4&#}Bpf#>%|?!Vq83_8Ry%X|rQ>PwhB|7T(T*TXmm7ZWGve_H1M z(q;=Ph3^5n;jm&DbqGcx5)wfiOSxoUT-r0!7Tn~nbLq?sQ4a>bH(=*9k|g6Fh%Rm^ zAqc{HwvO?sZl{w?X0wy+sad@p-*4&y)jxS`J<@0h0=d8Bd9+s(3MJ&LAUaRS2%}=w zg4s~nL5m@p$%HhUAS;!I-c?M&w7vLUZDUis^$N7nRc!=jSfysM#62-=l_?2Jfn=&; z-+{*bPJ;Ot?q{_*;I#Q4?rC1^y`Aiv7k#7VP7_!wNzU2?I z@GfCU5xD?_VfraZDP(X-fS@CVT_5TNv+t)pinnvpv#)TW0#Bx$`^E!%<-tJt+WkTh2OmTl$rWIbJ^Fk44PQ5s!f*MUBBWKV!fD)G+$@ zX12SHC`af^rdeMy{qLiC|2JE@q@CeE<*D`8IzLL9bZC+@(NXNe!nzUAqU=aX1PIE@ z#e?FWIXeH{IYqn|{EFxk%=h7M#>xF+08=f5C4}`d&WY(x$5U=Schfmv@UDk1c$Zlj zO5CUqyc4MLP2EqNV_u%yy1qEQM9FoRka)$HIKs~2%m9M|!sOMwTEDXfO$66;Y~tEzMzz0x9fuXLrf z{4)iWEMBb}E>5=GWhG8+d6XX%UzOz?jDZH8IKV*q3eBt>DnBN_$_Fj7F)zBm#~ApG z-@6x->&Smhb*l$PPSm9QG|>}<(uylI&D)k_O-ulWx6Zpl)yTHltL-MXv4V>nfqO>x z>v{O2K}iVRPAj-xwkU$ay(;ssch-&7u}G7qs!|yuDZxw*ZV83F^yX¬flya2?fK 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

  • _kVa#`h=afs5OjNe;N7MIsEM9TQK5f1cXmRk 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 sf^G;|aoo211?B$$?g;M1& delta 18770 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 VA@V#ZkCFr=0arKxP>+50_#1cV3VZ+n From e3424c485e435b032cbe0cdaf84da274d6b2d363 Mon Sep 17 00:00:00 2001 From: qianduoduo Date: Thu, 2 Jan 2020 12:05:30 +0800 Subject: [PATCH 078/190] update file --- .../allinone_cluster_configurations.sh | 14 +++++++------- .../cluster-deploy/scripts/package.sh | 9 --------- ...\347\275\262\346\226\207\346\241\243.docx" | Bin 191880 -> 191663 bytes 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh b/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh index 620919d6..b2908587 100644 --- a/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh +++ b/serving-server/cluster-deploy/scripts/allinone_cluster_configurations.sh @@ -3,13 +3,13 @@ user=app host_guest=(172.16.153.9 172.16.153.113) roll_hostAndguest=(172.16.153.9 172.16.153.113) deploy_dir=/data/projects -host_redis_ip=127.0.0.2 +host_redis_ip=127.0.0.1 host_redis_port=63792 -host_redis_password=fate_dev_host -guest_redis_ip=127.0.0.3 -guest_redis_port=63793 -guest_redis_password=fate_dev_guest +host_redis_password=fate_dev +guest_redis_ip=127.0.0.1 +guest_redis_port=6379 +guest_redis_password=fate_dev apply_zk=true -host_zk_url=zookeeper://localhost:2182 -guest_zk_url=zookeeper://localhost:2183 +host_zk_url=zookeeper://localhost:2181 +guest_zk_url=zookeeper://localhost:2181 workMode=1 diff --git a/serving-server/cluster-deploy/scripts/package.sh b/serving-server/cluster-deploy/scripts/package.sh index f7ae2d0c..7c0c7d19 100644 --- a/serving-server/cluster-deploy/scripts/package.sh +++ b/serving-server/cluster-deploy/scripts/package.sh @@ -74,15 +74,6 @@ elif [ ${apply_zk} = "true" ] then echo "---------apply _zk true" cd $router_path/conf - sleep 6 - echo "----------------" ${host_zk_url} - echo "----------------" ${host_zk_url} - echo "----------------" ${host_zk_url} - echo "----------------" ${host_zk_url} - echo "----------------" ${host_zk_url} - echo "----------------" ${host_zk_url} - echo "----------------" pwd - echo "----------------" $pwd sed -i "/^zk.url=/czk.url=${host_zk_url}" proxy.properties cd $sering_path/conf sed -i 's#zk.url=zookeeper://localhost:2181#'zk.url=${host_zk_url}'#' serving-server.properties diff --git "a/serving-server/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" "b/serving-server/doc/FATE-serving\351\203\250\347\275\262\346\226\207\346\241\243.docx" index a8ee794d9cf800413cf8e79a0569c6f6026e5e69..c0b50d49dc9a7e6232f24a6f91adc9cc03ebf824 100644 GIT binary patch delta 23909 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