| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.servicecomb.serviceregistry.task; |
| |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| |
| import javax.ws.rs.core.Response.Status; |
| |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.servicecomb.config.BootStrapProperties; |
| import org.apache.servicecomb.foundation.common.base.ServiceCombConstants; |
| import org.apache.servicecomb.registry.api.registry.Microservice; |
| import org.apache.servicecomb.serviceregistry.RegistryUtils; |
| import org.apache.servicecomb.serviceregistry.adapter.EnvAdapterManager; |
| import org.apache.servicecomb.serviceregistry.api.response.GetSchemaResponse; |
| import org.apache.servicecomb.serviceregistry.client.ServiceRegistryClient; |
| import org.apache.servicecomb.serviceregistry.client.http.Holder; |
| import org.apache.servicecomb.serviceregistry.config.ServiceRegistryConfig; |
| import org.apache.servicecomb.swagger.SwaggerUtils; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.common.eventbus.EventBus; |
| import com.google.common.eventbus.Subscribe; |
| |
| import io.swagger.models.Swagger; |
| |
| public class MicroserviceRegisterTask extends AbstractRegisterTask { |
| private static final Logger LOGGER = LoggerFactory.getLogger(MicroserviceRegisterTask.class); |
| |
| private boolean schemaIdSetMatch; |
| |
| public MicroserviceRegisterTask(EventBus eventBus, ServiceRegistryClient srClient, Microservice microservice) { |
| super(eventBus, srClient, microservice); |
| this.taskStatus = TaskStatus.READY; |
| } |
| |
| public boolean isSchemaIdSetMatch() { |
| return schemaIdSetMatch; |
| } |
| |
| @Subscribe |
| public void onMicroserviceInstanceHeartbeatTask(MicroserviceInstanceHeartbeatTask task) { |
| if (task.getHeartbeatResult() != HeartbeatResult.SUCCESS && isSameMicroservice(task.getMicroservice())) { |
| LOGGER.info("read MicroserviceInstanceHeartbeatTask status is {}", task.taskStatus); |
| this.taskStatus = TaskStatus.READY; |
| this.registered = false; |
| } |
| } |
| |
| @Subscribe |
| public void onInstanceRegistryFailed(MicroserviceInstanceRegisterTask task) { |
| if (task.taskStatus != TaskStatus.FINISHED) { |
| LOGGER.info("read MicroserviceInstanceRegisterTask status is {}", task.taskStatus); |
| this.taskStatus = TaskStatus.READY; |
| this.registered = false; |
| } |
| } |
| |
| @Override |
| protected boolean doRegister() { |
| LOGGER.info("running microservice register task."); |
| String serviceId = srClient.getMicroserviceId(microservice.getAppId(), |
| microservice.getServiceName(), |
| microservice.getVersion(), |
| microservice.getEnvironment()); |
| if (!StringUtils.isEmpty(serviceId)) { |
| // This microservice has been registered, so we just use the serviceId gotten from service center |
| microservice.setServiceId(serviceId); |
| |
| // Need to update microservice properties if we have modified or added properties of microservices. |
| Microservice microserviceTemp = new Microservice(); |
| EnvAdapterManager.INSTANCE.processMicroserviceWithAdapters(microserviceTemp); |
| if (!microserviceTemp.getProperties().isEmpty()) { |
| Map<String, String> propertiesTemp = microserviceTemp.getProperties(); |
| Microservice srClientMicroservice = srClient.getMicroservice(serviceId); |
| if (srClientMicroservice != null) { |
| microserviceTemp.setProperties(srClientMicroservice.getProperties()); |
| microserviceTemp.getProperties().putAll(propertiesTemp); |
| } |
| if (srClient.updateMicroserviceProperties(serviceId, microserviceTemp.getProperties())) { |
| LOGGER.info("microservice is already registered. Update microservice properties successfully. properties=[{}]", |
| microserviceTemp.getProperties()); |
| } else { |
| LOGGER.error("microservice is already registered. Update microservice properties failed. properties=[{}]", |
| microserviceTemp.getProperties()); |
| } |
| } |
| LOGGER.info( |
| "Microservice exists in service center, no need to register. id=[{}] appId=[{}], name=[{}], version=[{}], env=[{}]", |
| serviceId, |
| microservice.getAppId(), |
| microservice.getServiceName(), |
| microservice.getVersion(), |
| microservice.getEnvironment()); |
| |
| if (!checkSchemaIdSet()) { |
| return false; |
| } |
| } else { |
| EnvAdapterManager.INSTANCE.processMicroserviceWithAdapters(microservice); |
| serviceId = srClient.registerMicroservice(microservice); |
| if (StringUtils.isEmpty(serviceId)) { |
| LOGGER.error( |
| "Registry microservice failed. appId=[{}], name=[{}], version=[{}], env=[{}]", |
| microservice.getAppId(), |
| microservice.getServiceName(), |
| microservice.getVersion(), |
| microservice.getEnvironment()); |
| return false; |
| } |
| |
| LOGGER.info( |
| "Registry Microservice successfully. id=[{}] appId=[{}], name=[{}], version=[{}], schemaIds={}, env=[{}]", |
| serviceId, |
| microservice.getAppId(), |
| microservice.getServiceName(), |
| microservice.getVersion(), |
| microservice.getSchemas(), |
| microservice.getEnvironment()); |
| } |
| |
| microservice.setServiceId(serviceId); |
| microservice.getInstance().setServiceId(microservice.getServiceId()); |
| |
| return registerSchemas(); |
| } |
| |
| private boolean checkSchemaIdSet() { |
| Microservice existMicroservice = srClient.getMicroservice(microservice.getServiceId()); |
| if (existMicroservice == null) { |
| LOGGER.error("Error to get microservice from service center when check schema set"); |
| return false; |
| } |
| Set<String> existSchemas = new HashSet<>(existMicroservice.getSchemas()); |
| Set<String> localSchemas = new HashSet<>(microservice.getSchemas()); |
| schemaIdSetMatch = existSchemas.equals(localSchemas); |
| |
| if (!schemaIdSetMatch) { |
| LOGGER.warn( |
| "SchemaIds is different between local and service center. " |
| + "serviceId=[{}] appId=[{}], name=[{}], version=[{}], env=[{}], local schemaIds={}, service center schemaIds={}", |
| microservice.getServiceId(), |
| microservice.getAppId(), |
| microservice.getServiceName(), |
| microservice.getVersion(), |
| microservice.getEnvironment(), |
| localSchemas, |
| existSchemas); |
| // return true and print log only. |
| // because even schema ids not same, will also check content to see if should fail. |
| return true; |
| } |
| |
| LOGGER.info( |
| "SchemaIds are equals to service center. serviceId=[{}], appId=[{}], name=[{}], version=[{}], env=[{}], schemaIds={}", |
| microservice.getServiceId(), |
| microservice.getAppId(), |
| microservice.getServiceName(), |
| microservice.getVersion(), |
| microservice.getEnvironment(), |
| localSchemas); |
| return true; |
| } |
| |
| private boolean registerSchemas() { |
| Holder<List<GetSchemaResponse>> scSchemaHolder = srClient.getSchemas(microservice.getServiceId()); |
| if (Status.OK.getStatusCode() != scSchemaHolder.getStatusCode()) { |
| LOGGER.error("failed to get schemas from service center, statusCode = [{}]", scSchemaHolder.getStatusCode()); |
| return false; |
| } |
| |
| Map<String, GetSchemaResponse> scSchemaMap = convertScSchemaMap(scSchemaHolder); |
| // CHECK: local > sc, local != sc |
| for (Entry<String, String> localSchemaEntry : microservice.getSchemaMap().entrySet()) { |
| if (!registerSchema(scSchemaMap, localSchemaEntry)) { |
| return false; |
| } |
| } |
| |
| // CHECK: local < sc |
| checkRemainingSchema(scSchemaMap); |
| |
| schemaIdSetMatch = true; |
| return true; |
| } |
| |
| /** |
| * Check whether a local schema is equal to a sc schema. |
| * @return true if the local schema is equal to a sc schema, or be registered to sc successfully; |
| * false if schema is registered to sc but failed. |
| * @throws IllegalStateException The environment is not modifiable, and the local schema is different from sc schema |
| * or not exist in sc. |
| */ |
| private boolean registerSchema(Map<String, GetSchemaResponse> scSchemaMap, |
| Entry<String, String> localSchemaEntry) { |
| GetSchemaResponse scSchema = scSchemaMap.get(localSchemaEntry.getKey()); |
| |
| boolean onlineSchemaExists = scSchema != null; |
| LOGGER.info("schemaId [{}] exists [{}], summary exists [{}]", |
| localSchemaEntry.getKey(), |
| onlineSchemaExists, |
| scSchema != null && scSchema.getSummary() != null); |
| if (!onlineSchemaExists) { |
| // local > sc |
| return registerNewSchema(localSchemaEntry); |
| } |
| |
| scSchemaMap.remove(localSchemaEntry.getKey()); |
| |
| // local != sc |
| return compareAndReRegisterSchema(localSchemaEntry, scSchema); |
| } |
| |
| /** |
| * Try to register a new schema to service center, or throw exception if cannot register. |
| * @param localSchemaEntry local schema to be registered. |
| * @return whether local schema is registered successfully. |
| * @throws IllegalStateException The environment is unmodifiable. |
| */ |
| private boolean registerNewSchema(Entry<String, String> localSchemaEntry) { |
| // The ids of schemas are contained by microservice registry request, which means once a microservice |
| // is registered in the service center, the schemas that it contains are determined. |
| // If we get a microservice but cannot find the given schemaId in it's schemaId list, this means that |
| // the schemas of this microservice has been changed, and we should decide whether to register this new |
| // schema according to it's environment configuration. |
| if (onlineSchemaIsModifiable()) { |
| return registerSingleSchema(localSchemaEntry.getKey(), localSchemaEntry.getValue()); |
| } |
| |
| throw new IllegalStateException( |
| "There is a schema only existing in local microservice: [" + localSchemaEntry.getKey() |
| + "], which means there are interfaces changed. " |
| + "You need to increment microservice version before deploying, " |
| + "or you can configure " + BootStrapProperties.CONFIG_SERVICE_ENVIRONMENT + "=" |
| + ServiceCombConstants.DEVELOPMENT_SERVICECOMB_ENV |
| + " to work in development environment and ignore this error"); |
| } |
| |
| /** |
| * Compare schema summary and determine whether to re-register schema or throw exception. |
| * @param localSchemaEntry local schema |
| * @param scSchema schema in service center |
| * @return true if the two copies of schema are the same, or local schema is re-registered successfully, |
| * false if the local schema is re-registered to service center but failed. |
| * @throws IllegalStateException The two copies of schema are different and the environment is not modifiable. |
| */ |
| private boolean compareAndReRegisterSchema(Entry<String, String> localSchemaEntry, GetSchemaResponse scSchema) { |
| String scSchemaSummary = getScSchemaSummary(scSchema); |
| |
| if (null == scSchemaSummary) { |
| // cannot get scSchemaSummary, which means there is no schema content in sc, register schema directly |
| return registerSingleSchema(localSchemaEntry.getKey(), localSchemaEntry.getValue()); |
| } |
| |
| String localSchemaSummary = RegistryUtils.calcSchemaSummary(localSchemaEntry.getValue()); |
| if (!localSchemaSummary.equals(scSchemaSummary)) { |
| String scSchemaContent = srClient.getSchema(microservice.getServiceId(), scSchema.getSchemaId()); |
| String localSchemaContent = localSchemaEntry.getValue(); |
| |
| //if local schema and service center schema is different then print the |
| // both schemas and print difference in local schema. |
| LOGGER.warn( |
| "service center schema and local schema both are different:" |
| + "\n service center schema:\n[{}\n local schema:\n[{}]", |
| scSchemaContent, |
| localSchemaContent); |
| String diffStringLocal = StringUtils.difference(scSchemaContent, localSchemaContent); |
| if (diffStringLocal.equals("")) { |
| LOGGER.warn("Some APIs are deleted in local schema which are present in service center schema \n"); |
| } else { |
| LOGGER.warn("The difference in local schema:\n[{}]", diffStringLocal); |
| } |
| |
| if (!StringUtils.isEmpty(scSchemaContent) && !StringUtils.isEmpty(localSchemaContent)) { |
| Swagger scSwagger = SwaggerUtils.parseSwagger(scSchemaContent); |
| Swagger localSwagger = SwaggerUtils.parseSwagger(localSchemaContent); |
| if (scSwagger.equals(localSwagger)) { |
| LOGGER.info("Service center schema and local schema content different, but with same swagger syntax."); |
| return true; |
| } |
| } |
| |
| // 规避开关。这段代码可以删除,主要是为了避免未知bug的情况下(由于每次重启生成的契约不同,服务重启失败),能够提供一个规避手段。 |
| // 目前没有发现这个场景。 |
| if (ServiceRegistryConfig.INSTANCE.isIgnoreSwaggerDifference()) { |
| LOGGER.warn( |
| "service center schema and local schema both are different:\n service center " |
| + "schema:\n[{}]\n local schema:\n[{}]\nYou have configured to ignore difference " |
| + "check. It's recommended to increment microservice version before deploying when " |
| + "schema change.", |
| scSchemaContent, |
| localSchemaContent); |
| return true; |
| } |
| |
| if (onlineSchemaIsModifiable()) { |
| LOGGER.warn( |
| "schema[{}]'s content is changed and the current environment is [{}], so re-register it. It's recommended " |
| + " to change servicecomb_description.version after schema change, or restart consumer to" |
| + " make changes get notified.", |
| localSchemaEntry.getKey(), |
| microservice.getEnvironment()); |
| return registerSingleSchema(localSchemaEntry.getKey(), localSchemaEntry.getValue()); |
| } |
| |
| // env is not development, throw an exception and break the init procedure |
| throw new IllegalStateException( |
| "The schema(id=[" + localSchemaEntry.getKey() |
| + "]) content held by this instance and the service center is different. " |
| + "You need to increment microservice version before deploying. " |
| + "Or you can configure " + BootStrapProperties.CONFIG_SERVICE_ENVIRONMENT + "=" |
| + ServiceCombConstants.DEVELOPMENT_SERVICECOMB_ENV |
| + " to work in development environment and ignore this error"); |
| } |
| |
| // summaries are the same |
| return true; |
| } |
| |
| /** |
| * Try to get or calculate scSchema summary. |
| * @return summary of scSchema, |
| * or null if there is no schema content in service center |
| */ |
| private String getScSchemaSummary(GetSchemaResponse scSchema) { |
| String scSchemaSummary = scSchema.getSummary(); |
| if (null != scSchemaSummary) { |
| return scSchemaSummary; |
| } |
| |
| // if there is no online summery, query online schema content directly and calculate summary |
| String onlineSchemaContent = srClient.getSchema(microservice.getServiceId(), scSchema.getSchemaId()); |
| if (null != onlineSchemaContent) { |
| scSchemaSummary = RegistryUtils.calcSchemaSummary(onlineSchemaContent); |
| } |
| |
| return scSchemaSummary; |
| } |
| |
| /** |
| * Check whether there are schemas remaining in service center but not exist in local microservice. |
| * @throws IllegalStateException There are schemas only existing in service center, and the environment is unmodifiable. |
| */ |
| private void checkRemainingSchema(Map<String, GetSchemaResponse> scSchemaMap) { |
| if (!scSchemaMap.isEmpty()) { |
| // there are some schemas only exist in service center |
| if (!onlineSchemaIsModifiable()) { |
| // env is not development, throw an exception and break the init procedure |
| throw new IllegalStateException("There are schemas only existing in service center: " + scSchemaMap.keySet() |
| + ", which means there are interfaces changed. " |
| + "You need to increment microservice version before deploying, " |
| + "or if " + BootStrapProperties.CONFIG_SERVICE_ENVIRONMENT + "=" |
| + ServiceCombConstants.DEVELOPMENT_SERVICECOMB_ENV |
| + ", you can delete microservice information in service center and restart this instance."); |
| } |
| |
| // Currently nothing to do but print a warning |
| LOGGER.warn("There are schemas only existing in service center: {}, which means there are interfaces changed. " |
| + "It's recommended to increment microservice version before deploying.", |
| scSchemaMap.keySet()); |
| LOGGER.warn("ATTENTION: The schemas in new version are less than the old version, " |
| + "which may cause compatibility problems."); |
| } |
| } |
| |
| // 配置项供开发测试使用,减少接口频繁变更情况下,需要修改版本号的工作量。 |
| // 生产环境不能打开这个配置项,否则会导致网关、consumer等在provider之前启动的场景下,访问不到新增、更新的接口。 |
| private boolean onlineSchemaIsModifiable() { |
| return ServiceCombConstants.DEVELOPMENT_SERVICECOMB_ENV.equalsIgnoreCase(microservice.getEnvironment()) |
| || ServiceRegistryConfig.INSTANCE.isAlwaysOverrideSchema(); |
| } |
| |
| /** |
| * Register a schema directly. |
| * @return true if register success, otherwise false |
| */ |
| private boolean registerSingleSchema(String schemaId, String content) { |
| EnvAdapterManager.INSTANCE.processSchemaWithAdapters(schemaId, content); |
| return srClient.registerSchema(microservice.getServiceId(), schemaId, content); |
| } |
| |
| private Map<String, GetSchemaResponse> convertScSchemaMap(Holder<List<GetSchemaResponse>> scSchemaHolder) { |
| Map<String, GetSchemaResponse> scSchemaMap = new HashMap<>(); |
| List<GetSchemaResponse> scSchemaList = scSchemaHolder.getValue(); |
| if (null == scSchemaList) { |
| return scSchemaMap; |
| } |
| |
| for (GetSchemaResponse scSchema : scSchemaList) { |
| scSchemaMap.put(scSchema.getSchemaId(), scSchema); |
| } |
| |
| return scSchemaMap; |
| } |
| } |