package org.zstack.portal.apimediator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import org.zstack.core.Platform; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.CloudBusCallBack; import org.zstack.core.cloudbus.MessageSafe; import org.zstack.core.db.DatabaseFacade; import org.zstack.core.errorcode.ErrorFacade; import org.zstack.core.thread.SyncTask; import org.zstack.core.thread.ThreadFacade; import org.zstack.header.AbstractService; import org.zstack.header.apimediator.*; import org.zstack.header.errorcode.SysErrors; import org.zstack.header.managementnode.*; import org.zstack.header.message.*; import org.zstack.utils.StringDSL; import org.zstack.utils.Utils; import org.zstack.utils.gson.JSONObjectUtil; import org.zstack.utils.logging.CLogger; import static org.zstack.core.Platform.argerr; import javax.persistence.Query; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.zstack.utils.CollectionDSL.e; import static org.zstack.utils.CollectionDSL.map; public class ApiMediatorImpl extends AbstractService implements ApiMediator, GlobalApiMessageInterceptor { private static final CLogger logger = Utils.getLogger(ApiMediator.class); @Autowired private CloudBus bus; @Autowired private ThreadFacade thdf; @Autowired private ErrorFacade errf; @Autowired private DatabaseFacade dbf; private ApiMessageProcessor processor; private List<String> serviceConfigFolders; private int apiWorkerNum = 5; private void dispatchMessage(APIMessage msg) { ApiMessageDescriptor desc = processor.getApiMessageDescriptor(msg); if (desc == null) { Map message = map(e(msg.getClass().getName(), msg)); String err = String.format("no service configuration file declares message: %s", JSONObjectUtil.toJsonString(message)); logger.warn(err); bus.replyErrorByMessageType(msg, errf.instantiateErrorCode(PortalErrors.NO_SERVICE_FOR_MESSAGE, err)); return; } try { msg.setServiceId(null); msg = processor.process(msg); } catch (ApiMessageInterceptionException ie) { logger.debug(ie.getError().toString(), ie); bus.replyErrorByMessageType(msg, ie.getError()); return; } catch (StopRoutingException e) { return; } if (msg.getServiceId() == null && desc.getServiceId() != null) { bus.makeLocalServiceId(msg, desc.getServiceId()); } if (msg.getServiceId() == null) { String err = String.format("No service id found for API message[%s], message dump: %s", msg.getMessageName(), JSONObjectUtil.toJsonString(msg)); logger.warn(err); bus.replyErrorByMessageType(msg, errf.stringToInternalError(err)); return; } if (!msg.hasSystemTag(PortalSystemTags.VALIDATION_ONLY.getTagFormat())) { bus.route(msg); return; } // this call is only for validate the API parameters if (msg instanceof APISyncCallMessage) { APIReply reply = new APIReply(); bus.reply(msg, reply); } else { APIEvent evt = new APIEvent(msg.getId()); bus.publish(evt); } } @Override public void handleMessage(final Message msg) { thdf.syncSubmit(new SyncTask<Object>() { @Override public String getSyncSignature() { return "api.worker"; } @Override public int getSyncLevel() { return apiWorkerNum; } @Override public String getName() { return "api.worker"; } @MessageSafe public void handleMessage(Message msg) { if (msg instanceof APIIsReadyToGoMsg) { handle((APIIsReadyToGoMsg) msg); } else if (msg instanceof APIGetVersionMsg) { handle((APIGetVersionMsg) msg); } else if (msg instanceof APIGetCurrentTimeMsg) { handle((APIGetCurrentTimeMsg) msg); } else if (msg instanceof APIMessage) { dispatchMessage((APIMessage) msg); } else { logger.debug("Not an APIMessage.Message ID is " + msg.getId()); } } @Override public Object call() throws Exception { handleMessage(msg); return null; } }); } @Transactional(readOnly = true) private void handle(APIGetVersionMsg msg) { String sql = "select v.version from schema_version v order by version_rank desc"; Query q = dbf.getEntityManager().createNativeQuery(sql); q.setMaxResults(1); String version = (String) q.getSingleResult(); APIGetVersionReply reply = new APIGetVersionReply(); reply.setVersion(version); bus.reply(msg, reply); } private void handle(APIGetCurrentTimeMsg msg) { Map<String, Long> ret = new HashMap<String, Long>(); long currentTimeMillis = System.currentTimeMillis(); long currentTimeSeconds = System.currentTimeMillis()/1000; ret.put("MillionSeconds", currentTimeMillis); ret.put("Seconds", currentTimeSeconds); APIGetCurrentTimeReply reply = new APIGetCurrentTimeReply(); reply.setCurrentTime(ret); bus.reply(msg, reply); } private void handle(final APIIsReadyToGoMsg msg) { final APIIsReadyToGoReply areply = new APIIsReadyToGoReply(); IsManagementNodeReadyMsg imsg = new IsManagementNodeReadyMsg(); String nodeId = msg.getManagementNodeId(); if (nodeId == null) { bus.makeLocalServiceId(imsg, ManagementNodeConstant.SERVICE_ID); nodeId = Platform.getManagementServerId(); } else { bus.makeServiceIdByManagementNodeId(imsg, ManagementNodeConstant.SERVICE_ID, msg.getManagementNodeId()); } final String fnodeId = nodeId; areply.setManagementNodeId(nodeId); bus.send(imsg, new CloudBusCallBack(msg) { @Override public void run(MessageReply reply) { if (!reply.isSuccess()) { areply.setError(reply.getError()); } else { IsManagementNodeReadyReply r = (IsManagementNodeReadyReply) reply; if (!r.isReady()) { areply.setError(errf.instantiateErrorCode(SysErrors.NOT_READY_ERROR, String.format("management node[uuid:%s] is not ready yet", fnodeId))); } } bus.reply(msg, areply); } }); } @Override public String getId() { return ApiMediatorConstant.SERVICE_ID; } @Override public boolean start() { Map<String, Object> config = new HashMap<String, Object>(); config.put("serviceConfigFolders", serviceConfigFolders); processor = new ApiMessageProcessorImpl(config); bus.registerService(this); return true; } @Override public boolean stop() { bus.unregisterService(this); return true; } public void setServiceConfigFolders(List<String> serviceConfigFolders) { this.serviceConfigFolders = serviceConfigFolders; } public void setApiWorkerNum(int apiWorkerNum) { this.apiWorkerNum = apiWorkerNum; } @Override public List<Class> getMessageClassToIntercept() { List<Class> lst = new ArrayList<Class>(); lst.add(APICreateMessage.class); return lst; } @Override public InterceptorPosition getPosition() { return InterceptorPosition.FRONT; } @Override public APIMessage intercept(APIMessage msg) throws ApiMessageInterceptionException { if (msg instanceof APICreateMessage) { APICreateMessage cmsg = (APICreateMessage) msg; if (cmsg.getResourceUuid() != null) { if (!StringDSL.isZstackUuid(cmsg.getResourceUuid())) { throw new ApiMessageInterceptionException(argerr("resourceUuid[%s] is not a valid uuid. A valid uuid is a UUID(v4 recommended) with '-' stripped. " + "see http://en.wikipedia.org/wiki/Universally_unique_identifier for format of UUID, the regular expression ZStack uses" + " to validate a UUID is '[0-9a-f]{8}[0-9a-f]{4}[1-5][0-9a-f]{3}[89ab][0-9a-f]{3}[0-9a-f]{12}'", cmsg.getResourceUuid())); } } } return msg; } }