diff --git a/sdk/src/main/java/org/zstack/sdk/RegisterLicenseClientAction.java b/sdk/src/main/java/org/zstack/sdk/RegisterLicenseClientAction.java new file mode 100644 index 0000000000..83fedbad41 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/RegisterLicenseClientAction.java @@ -0,0 +1,113 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class RegisterLicenseClientAction extends AbstractAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.RegisterLicenseClientResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + return this; + } + } + + @Param(required = true, nonempty = true, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String serverUrl; + + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String siteName; + + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String productLine; + + @Param(required = true, nonempty = true, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String bundle; + + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String bundleName; + + @Param(required = false) + public java.util.List systemTags; + + @Param(required = false) + public java.util.List userTags; + + @Param(required = false) + public String sessionId; + + @Param(required = false) + public String accessKeyId; + + @Param(required = false) + public String accessKeySecret; + + @Param(required = false) + public String requestIp; + + @NonAPIParam + public long timeout = -1; + + @NonAPIParam + public long pollingInterval = -1; + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.RegisterLicenseClientResult value = res.getResult(org.zstack.sdk.RegisterLicenseClientResult.class); + ret.value = value == null ? new org.zstack.sdk.RegisterLicenseClientResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "POST"; + info.path = "/licenses/client"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "params"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/RegisterLicenseClientResult.java b/sdk/src/main/java/org/zstack/sdk/RegisterLicenseClientResult.java new file mode 100644 index 0000000000..f731129c21 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/RegisterLicenseClientResult.java @@ -0,0 +1,38 @@ +package org.zstack.sdk; + + + +public class RegisterLicenseClientResult { + public java.lang.String siteUuid; + public void setSiteUuid(java.lang.String siteUuid) { + this.siteUuid = siteUuid; + } + public java.lang.String getSiteUuid() { + return this.siteUuid; + } + + public java.lang.String state; + public void setState(java.lang.String state) { + this.state = state; + } + public java.lang.String getState() { + return this.state; + } + + public long licenseCount; + public void setLicenseCount(long licenseCount) { + this.licenseCount = licenseCount; + } + public long getLicenseCount() { + return this.licenseCount; + } + + public java.lang.String config; + public void setConfig(java.lang.String config) { + this.config = config; + } + public java.lang.String getConfig() { + return this.config; + } + +} diff --git a/testlib/src/main/java/org/zstack/testlib/EnvSpec.groovy b/testlib/src/main/java/org/zstack/testlib/EnvSpec.groovy index 5491d321f1..d5efa889e0 100755 --- a/testlib/src/main/java/org/zstack/testlib/EnvSpec.groovy +++ b/testlib/src/main/java/org/zstack/testlib/EnvSpec.groovy @@ -4,6 +4,7 @@ import groovy.transform.AutoClone import org.codehaus.groovy.runtime.InvokerHelper import org.springframework.http.* import org.springframework.http.client.HttpComponentsClientHttpRequestFactory +import org.springframework.web.multipart.MultipartHttpServletRequest import org.springframework.web.client.RestTemplate import org.zstack.compute.vm.VmGlobalConfig import org.zstack.configuration.SqlForeignKeyGenerator @@ -987,21 +988,78 @@ class EnvSpec extends ApiHelper implements Node { } HttpEntity getEntityFromRequest(HttpServletRequest req) { - StringBuilder sb = new StringBuilder() - String line - while ((line = req.getReader().readLine()) != null) { - sb.append(line) - } - req.getReader().close() - HttpHeaders header = new HttpHeaders() for (Enumeration e = req.getHeaderNames() ; e.hasMoreElements() ;) { String name = e.nextElement().toString() header.add(name, req.getHeader(name)) } + + StringBuilder sb = new StringBuilder() + if (req.getContentType()?.toLowerCase()?.startsWith("multipart/")) { + sb.append(readMultipartBody(req)) + } else { + def reader = req.getReader() + try { + String line + while ((line = reader.readLine()) != null) { + sb.append(line) + } + } finally { + reader.close() + } + } + return new HttpEntity(sb.toString(), header) } + protected String readMultipartBody(HttpServletRequest req) { + byte[] raw = req.inputStream.bytes + if (raw.length > 0) { + return new String(raw, "UTF-8") + } + + if (req instanceof MultipartHttpServletRequest) { + return readSpringMultipartBody(req as MultipartHttpServletRequest) + } + + StringBuilder sb = new StringBuilder() + try { + req.getParts().each { part -> + appendMultipartPart(sb, part.name, part.submittedFileName, part.contentType, part.inputStream.bytes) + } + } catch (Throwable t) { + logger.debug("failed to read multipart parts for ${req.requestURI}", t) + } + return sb.toString() + } + + protected String readSpringMultipartBody(MultipartHttpServletRequest req) { + StringBuilder sb = new StringBuilder() + req.parameterMap.each { String name, String[] values -> + values.each { value -> + appendMultipartPart(sb, name, null, "text/plain", value == null ? new byte[0] : value.getBytes("UTF-8")) + } + } + req.fileMap.each { String name, file -> + appendMultipartPart(sb, name, file.originalFilename, file.contentType, file.bytes) + } + return sb.toString() + } + + protected void appendMultipartPart(StringBuilder sb, String name, String filename, String contentType, byte[] content) { + sb.append("Content-Disposition: form-data; name=\"").append(name).append("\"") + if (filename != null) { + sb.append("; filename=\"").append(filename).append("\"") + } + sb.append("\n") + if (contentType != null) { + sb.append("Content-Type: ").append(contentType).append("\n") + } + sb.append("\n") + sb.append(new String(content == null ? new byte[0] : content, "UTF-8")) + sb.append("\n") + } + void handleConditionSimulatorHttpRequests(HttpServletRequest req, HttpEntity entity, HttpServletResponse rsp) { def url = req.getRequestURI() if (httpConditionHandlers[url] == null || httpConditionHandlers[url].isEmpty()) { @@ -1222,4 +1280,4 @@ class EnvSpec extends ApiHelper implements Node { void resetAllMessageSize() { messageHandlerCounters.clear() } -} \ No newline at end of file +} diff --git a/testlib/src/main/java/org/zstack/testlib/TestLibController.java b/testlib/src/main/java/org/zstack/testlib/TestLibController.java index 286eae91b0..545acb8b27 100755 --- a/testlib/src/main/java/org/zstack/testlib/TestLibController.java +++ b/testlib/src/main/java/org/zstack/testlib/TestLibController.java @@ -27,7 +27,7 @@ public class TestLibController { private static final ExecutorService pool = Executors.newFixedThreadPool(32); @RequestMapping( - value = "/**", + value = {"/**", "/v1/sites/**", "/v1/quota/**"}, method = { RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE, RequestMethod.GET, RequestMethod.HEAD, RequestMethod.OPTIONS, RequestMethod.PATCH, RequestMethod.TRACE @@ -39,6 +39,11 @@ public void handle(HttpServletRequest request, HttpServletResponse response) thr return; } + if (isMultipartRequest(request)) { + Test.handleHttp(request, response); + return; + } + final AsyncContext asyncContext = request.startAsync(); asyncContext.setTimeout(TestConfigUtils.getMessageTimeoutMillisConfig()); @@ -59,6 +64,11 @@ public void handle(HttpServletRequest request, HttpServletResponse response) thr }); } + private boolean isMultipartRequest(HttpServletRequest request) { + String contentType = request.getContentType(); + return contentType != null && contentType.toLowerCase().startsWith("multipart/"); + } + @PreDestroy public void shutdownPool() { logger.info("Shutting down TestLibController pool");