[CELEBORN-1797] Support to adjust the logger level with RESTful API during runtime
### What changes were proposed in this pull request? Support to adjust the logger level during runtime without restarting the server. ### Why are the changes needed? It is useful for debug, likes hadoop daemonlog command: https://hadoop.apache.org/docs/r3.4.1/hadoop-project-dist/hadoop-common/CommandsManual.html#daemonlog ### Does this PR introduce _any_ user-facing change? Yes, new RESTful api. ### How was this patch tested? GA. <img width="1430" alt="image" src="https://github.com/user-attachments/assets/9d974fd9-21f3-429a-a35f-e15662aa75ac" /> <img width="1428" alt="image" src="https://github.com/user-attachments/assets/ca32b596-12a1-4038-9e1b-4fdc6a377b54" /> <img width="1255" alt="image" src="https://github.com/user-attachments/assets/5c399a73-9f53-43a8-b337-5a79621abea4" /> <img width="1244" alt="image" src="https://github.com/user-attachments/assets/16dc9ede-01bb-4e38-80fe-acb044ae6cc7" /> Closes #3022 from turboFei/log_level. Lead-authored-by: Wang, Fei <fwang12@ebay.com> Co-authored-by: Fei Wang <cn.feiwang@gmail.com> Signed-off-by: mingji <fengmingxiao.fmx@alibaba-inc.com>
This commit is contained in:
parent
03656b5b1c
commit
27e34ecad0
@ -0,0 +1,233 @@
|
||||
/*
|
||||
* 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.celeborn.rest.v1.master;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
import org.apache.celeborn.rest.v1.master.invoker.ApiException;
|
||||
import org.apache.celeborn.rest.v1.master.invoker.ApiClient;
|
||||
import org.apache.celeborn.rest.v1.master.invoker.BaseApi;
|
||||
import org.apache.celeborn.rest.v1.master.invoker.Configuration;
|
||||
import org.apache.celeborn.rest.v1.master.invoker.Pair;
|
||||
|
||||
import org.apache.celeborn.rest.v1.model.HandleResponse;
|
||||
import org.apache.celeborn.rest.v1.model.LoggerInfo;
|
||||
import org.apache.celeborn.rest.v1.model.LoggerInfos;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", comments = "Generator version: 7.8.0")
|
||||
public class LoggerApi extends BaseApi {
|
||||
|
||||
public LoggerApi() {
|
||||
super(Configuration.getDefaultApiClient());
|
||||
}
|
||||
|
||||
public LoggerApi(ApiClient apiClient) {
|
||||
super(apiClient);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Get the logger level, return all loggers if no name specified.
|
||||
* @param name The logger name. (optional)
|
||||
* @param all Return all logger instances if true, otherwise return all configured loggers. (optional, default to false)
|
||||
* @return LoggerInfos
|
||||
* @throws ApiException if fails to make API call
|
||||
*/
|
||||
public LoggerInfos getLogger(String name, Boolean all) throws ApiException {
|
||||
return this.getLogger(name, all, Collections.emptyMap());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Get the logger level, return all loggers if no name specified.
|
||||
* @param name The logger name. (optional)
|
||||
* @param all Return all logger instances if true, otherwise return all configured loggers. (optional, default to false)
|
||||
* @param additionalHeaders additionalHeaders for this call
|
||||
* @return LoggerInfos
|
||||
* @throws ApiException if fails to make API call
|
||||
*/
|
||||
public LoggerInfos getLogger(String name, Boolean all, Map<String, String> additionalHeaders) throws ApiException {
|
||||
Object localVarPostBody = null;
|
||||
|
||||
// create path and map variables
|
||||
String localVarPath = "/api/v1/loggers";
|
||||
|
||||
StringJoiner localVarQueryStringJoiner = new StringJoiner("&");
|
||||
String localVarQueryParameterBaseName;
|
||||
List<Pair> localVarQueryParams = new ArrayList<Pair>();
|
||||
List<Pair> localVarCollectionQueryParams = new ArrayList<Pair>();
|
||||
Map<String, String> localVarHeaderParams = new HashMap<String, String>();
|
||||
Map<String, String> localVarCookieParams = new HashMap<String, String>();
|
||||
Map<String, Object> localVarFormParams = new HashMap<String, Object>();
|
||||
|
||||
localVarQueryParams.addAll(apiClient.parameterToPair("name", name));
|
||||
localVarQueryParams.addAll(apiClient.parameterToPair("all", all));
|
||||
|
||||
localVarHeaderParams.putAll(additionalHeaders);
|
||||
|
||||
|
||||
|
||||
final String[] localVarAccepts = {
|
||||
"application/json"
|
||||
};
|
||||
final String localVarAccept = apiClient.selectHeaderAccept(localVarAccepts);
|
||||
|
||||
final String[] localVarContentTypes = {
|
||||
|
||||
};
|
||||
final String localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes);
|
||||
|
||||
String[] localVarAuthNames = new String[] { "basic" };
|
||||
|
||||
TypeReference<LoggerInfos> localVarReturnType = new TypeReference<LoggerInfos>() {};
|
||||
return apiClient.invokeAPI(
|
||||
localVarPath,
|
||||
"GET",
|
||||
localVarQueryParams,
|
||||
localVarCollectionQueryParams,
|
||||
localVarQueryStringJoiner.toString(),
|
||||
localVarPostBody,
|
||||
localVarHeaderParams,
|
||||
localVarCookieParams,
|
||||
localVarFormParams,
|
||||
localVarAccept,
|
||||
localVarContentType,
|
||||
localVarAuthNames,
|
||||
localVarReturnType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Set the logger level.
|
||||
* @param loggerInfo (optional)
|
||||
* @return HandleResponse
|
||||
* @throws ApiException if fails to make API call
|
||||
*/
|
||||
public HandleResponse setLogger(LoggerInfo loggerInfo) throws ApiException {
|
||||
return this.setLogger(loggerInfo, Collections.emptyMap());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Set the logger level.
|
||||
* @param loggerInfo (optional)
|
||||
* @param additionalHeaders additionalHeaders for this call
|
||||
* @return HandleResponse
|
||||
* @throws ApiException if fails to make API call
|
||||
*/
|
||||
public HandleResponse setLogger(LoggerInfo loggerInfo, Map<String, String> additionalHeaders) throws ApiException {
|
||||
Object localVarPostBody = loggerInfo;
|
||||
|
||||
// create path and map variables
|
||||
String localVarPath = "/api/v1/loggers";
|
||||
|
||||
StringJoiner localVarQueryStringJoiner = new StringJoiner("&");
|
||||
String localVarQueryParameterBaseName;
|
||||
List<Pair> localVarQueryParams = new ArrayList<Pair>();
|
||||
List<Pair> localVarCollectionQueryParams = new ArrayList<Pair>();
|
||||
Map<String, String> localVarHeaderParams = new HashMap<String, String>();
|
||||
Map<String, String> localVarCookieParams = new HashMap<String, String>();
|
||||
Map<String, Object> localVarFormParams = new HashMap<String, Object>();
|
||||
|
||||
|
||||
localVarHeaderParams.putAll(additionalHeaders);
|
||||
|
||||
|
||||
|
||||
final String[] localVarAccepts = {
|
||||
"application/json"
|
||||
};
|
||||
final String localVarAccept = apiClient.selectHeaderAccept(localVarAccepts);
|
||||
|
||||
final String[] localVarContentTypes = {
|
||||
"application/json"
|
||||
};
|
||||
final String localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes);
|
||||
|
||||
String[] localVarAuthNames = new String[] { "basic" };
|
||||
|
||||
TypeReference<HandleResponse> localVarReturnType = new TypeReference<HandleResponse>() {};
|
||||
return apiClient.invokeAPI(
|
||||
localVarPath,
|
||||
"POST",
|
||||
localVarQueryParams,
|
||||
localVarCollectionQueryParams,
|
||||
localVarQueryStringJoiner.toString(),
|
||||
localVarPostBody,
|
||||
localVarHeaderParams,
|
||||
localVarCookieParams,
|
||||
localVarFormParams,
|
||||
localVarAccept,
|
||||
localVarContentType,
|
||||
localVarAuthNames,
|
||||
localVarReturnType
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T invokeAPI(String url, String method, Object request, TypeReference<T> returnType, Map<String, String> additionalHeaders) throws ApiException {
|
||||
String localVarPath = url.replace(apiClient.getBaseURL(), "");
|
||||
StringJoiner localVarQueryStringJoiner = new StringJoiner("&");
|
||||
List<Pair> localVarQueryParams = new ArrayList<Pair>();
|
||||
List<Pair> localVarCollectionQueryParams = new ArrayList<Pair>();
|
||||
Map<String, String> localVarHeaderParams = new HashMap<String, String>();
|
||||
Map<String, String> localVarCookieParams = new HashMap<String, String>();
|
||||
Map<String, Object> localVarFormParams = new HashMap<String, Object>();
|
||||
|
||||
localVarHeaderParams.putAll(additionalHeaders);
|
||||
|
||||
final String[] localVarAccepts = {
|
||||
"application/json"
|
||||
};
|
||||
final String localVarAccept = apiClient.selectHeaderAccept(localVarAccepts);
|
||||
|
||||
final String[] localVarContentTypes = {
|
||||
"application/json"
|
||||
};
|
||||
final String localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes);
|
||||
|
||||
String[] localVarAuthNames = new String[] { "basic" };
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
localVarPath,
|
||||
method,
|
||||
localVarQueryParams,
|
||||
localVarCollectionQueryParams,
|
||||
localVarQueryStringJoiner.toString(),
|
||||
request,
|
||||
localVarHeaderParams,
|
||||
localVarCookieParams,
|
||||
localVarFormParams,
|
||||
localVarAccept,
|
||||
localVarContentType,
|
||||
localVarAuthNames,
|
||||
returnType
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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.celeborn.rest.v1.model;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Arrays;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
|
||||
/**
|
||||
* LoggerInfo
|
||||
*/
|
||||
@JsonPropertyOrder({
|
||||
LoggerInfo.JSON_PROPERTY_NAME,
|
||||
LoggerInfo.JSON_PROPERTY_LEVEL
|
||||
})
|
||||
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", comments = "Generator version: 7.8.0")
|
||||
public class LoggerInfo {
|
||||
public static final String JSON_PROPERTY_NAME = "name";
|
||||
private String name;
|
||||
|
||||
public static final String JSON_PROPERTY_LEVEL = "level";
|
||||
private String level;
|
||||
|
||||
public LoggerInfo() {
|
||||
}
|
||||
|
||||
public LoggerInfo name(String name) {
|
||||
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The logger name.
|
||||
* @return name
|
||||
*/
|
||||
@javax.annotation.Nonnull
|
||||
@JsonProperty(JSON_PROPERTY_NAME)
|
||||
@JsonInclude(value = JsonInclude.Include.ALWAYS)
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
@JsonProperty(JSON_PROPERTY_NAME)
|
||||
@JsonInclude(value = JsonInclude.Include.ALWAYS)
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public LoggerInfo level(String level) {
|
||||
|
||||
this.level = level;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The logger level.
|
||||
* @return level
|
||||
*/
|
||||
@javax.annotation.Nonnull
|
||||
@JsonProperty(JSON_PROPERTY_LEVEL)
|
||||
@JsonInclude(value = JsonInclude.Include.ALWAYS)
|
||||
|
||||
public String getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
|
||||
@JsonProperty(JSON_PROPERTY_LEVEL)
|
||||
@JsonInclude(value = JsonInclude.Include.ALWAYS)
|
||||
public void setLevel(String level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
LoggerInfo loggerInfo = (LoggerInfo) o;
|
||||
return Objects.equals(this.name, loggerInfo.name) &&
|
||||
Objects.equals(this.level, loggerInfo.level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("class LoggerInfo {\n");
|
||||
sb.append(" name: ").append(toIndentedString(name)).append("\n");
|
||||
sb.append(" level: ").append(toIndentedString(level)).append("\n");
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given object to string with each line indented by 4 spaces
|
||||
* (except the first line).
|
||||
*/
|
||||
private String toIndentedString(Object o) {
|
||||
if (o == null) {
|
||||
return "null";
|
||||
}
|
||||
return o.toString().replace("\n", "\n ");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.celeborn.rest.v1.model;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Arrays;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import org.apache.celeborn.rest.v1.model.LoggerInfo;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
|
||||
/**
|
||||
* LoggerInfos
|
||||
*/
|
||||
@JsonPropertyOrder({
|
||||
LoggerInfos.JSON_PROPERTY_LOGGERS
|
||||
})
|
||||
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", comments = "Generator version: 7.8.0")
|
||||
public class LoggerInfos {
|
||||
public static final String JSON_PROPERTY_LOGGERS = "loggers";
|
||||
private List<LoggerInfo> loggers = new ArrayList<>();
|
||||
|
||||
public LoggerInfos() {
|
||||
}
|
||||
|
||||
public LoggerInfos loggers(List<LoggerInfo> loggers) {
|
||||
|
||||
this.loggers = loggers;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoggerInfos addLoggersItem(LoggerInfo loggersItem) {
|
||||
if (this.loggers == null) {
|
||||
this.loggers = new ArrayList<>();
|
||||
}
|
||||
this.loggers.add(loggersItem);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The logger infos.
|
||||
* @return loggers
|
||||
*/
|
||||
@javax.annotation.Nullable
|
||||
@JsonProperty(JSON_PROPERTY_LOGGERS)
|
||||
@JsonInclude(value = JsonInclude.Include.USE_DEFAULTS)
|
||||
|
||||
public List<LoggerInfo> getLoggers() {
|
||||
return loggers;
|
||||
}
|
||||
|
||||
|
||||
@JsonProperty(JSON_PROPERTY_LOGGERS)
|
||||
@JsonInclude(value = JsonInclude.Include.USE_DEFAULTS)
|
||||
public void setLoggers(List<LoggerInfo> loggers) {
|
||||
this.loggers = loggers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
LoggerInfos loggerInfos = (LoggerInfos) o;
|
||||
return Objects.equals(this.loggers, loggerInfos.loggers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(loggers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("class LoggerInfos {\n");
|
||||
sb.append(" loggers: ").append(toIndentedString(loggers)).append("\n");
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given object to string with each line indented by 4 spaces
|
||||
* (except the first line).
|
||||
*/
|
||||
private String toIndentedString(Object o) {
|
||||
if (o == null) {
|
||||
return "null";
|
||||
}
|
||||
return o.toString().replace("\n", "\n ");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,233 @@
|
||||
/*
|
||||
* 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.celeborn.rest.v1.worker;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
import org.apache.celeborn.rest.v1.worker.invoker.ApiException;
|
||||
import org.apache.celeborn.rest.v1.worker.invoker.ApiClient;
|
||||
import org.apache.celeborn.rest.v1.worker.invoker.BaseApi;
|
||||
import org.apache.celeborn.rest.v1.worker.invoker.Configuration;
|
||||
import org.apache.celeborn.rest.v1.worker.invoker.Pair;
|
||||
|
||||
import org.apache.celeborn.rest.v1.model.HandleResponse;
|
||||
import org.apache.celeborn.rest.v1.model.LoggerInfo;
|
||||
import org.apache.celeborn.rest.v1.model.LoggerInfos;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", comments = "Generator version: 7.8.0")
|
||||
public class LoggerApi extends BaseApi {
|
||||
|
||||
public LoggerApi() {
|
||||
super(Configuration.getDefaultApiClient());
|
||||
}
|
||||
|
||||
public LoggerApi(ApiClient apiClient) {
|
||||
super(apiClient);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Get the logger level, return all loggers if no name specified.
|
||||
* @param name The logger name. (optional)
|
||||
* @param all Return all logger instances if true, otherwise return all configured loggers. (optional, default to false)
|
||||
* @return LoggerInfos
|
||||
* @throws ApiException if fails to make API call
|
||||
*/
|
||||
public LoggerInfos getLogger(String name, Boolean all) throws ApiException {
|
||||
return this.getLogger(name, all, Collections.emptyMap());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Get the logger level, return all loggers if no name specified.
|
||||
* @param name The logger name. (optional)
|
||||
* @param all Return all logger instances if true, otherwise return all configured loggers. (optional, default to false)
|
||||
* @param additionalHeaders additionalHeaders for this call
|
||||
* @return LoggerInfos
|
||||
* @throws ApiException if fails to make API call
|
||||
*/
|
||||
public LoggerInfos getLogger(String name, Boolean all, Map<String, String> additionalHeaders) throws ApiException {
|
||||
Object localVarPostBody = null;
|
||||
|
||||
// create path and map variables
|
||||
String localVarPath = "/api/v1/loggers";
|
||||
|
||||
StringJoiner localVarQueryStringJoiner = new StringJoiner("&");
|
||||
String localVarQueryParameterBaseName;
|
||||
List<Pair> localVarQueryParams = new ArrayList<Pair>();
|
||||
List<Pair> localVarCollectionQueryParams = new ArrayList<Pair>();
|
||||
Map<String, String> localVarHeaderParams = new HashMap<String, String>();
|
||||
Map<String, String> localVarCookieParams = new HashMap<String, String>();
|
||||
Map<String, Object> localVarFormParams = new HashMap<String, Object>();
|
||||
|
||||
localVarQueryParams.addAll(apiClient.parameterToPair("name", name));
|
||||
localVarQueryParams.addAll(apiClient.parameterToPair("all", all));
|
||||
|
||||
localVarHeaderParams.putAll(additionalHeaders);
|
||||
|
||||
|
||||
|
||||
final String[] localVarAccepts = {
|
||||
"application/json"
|
||||
};
|
||||
final String localVarAccept = apiClient.selectHeaderAccept(localVarAccepts);
|
||||
|
||||
final String[] localVarContentTypes = {
|
||||
|
||||
};
|
||||
final String localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes);
|
||||
|
||||
String[] localVarAuthNames = new String[] { "basic" };
|
||||
|
||||
TypeReference<LoggerInfos> localVarReturnType = new TypeReference<LoggerInfos>() {};
|
||||
return apiClient.invokeAPI(
|
||||
localVarPath,
|
||||
"GET",
|
||||
localVarQueryParams,
|
||||
localVarCollectionQueryParams,
|
||||
localVarQueryStringJoiner.toString(),
|
||||
localVarPostBody,
|
||||
localVarHeaderParams,
|
||||
localVarCookieParams,
|
||||
localVarFormParams,
|
||||
localVarAccept,
|
||||
localVarContentType,
|
||||
localVarAuthNames,
|
||||
localVarReturnType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Set the logger level.
|
||||
* @param loggerInfo (optional)
|
||||
* @return HandleResponse
|
||||
* @throws ApiException if fails to make API call
|
||||
*/
|
||||
public HandleResponse setLogger(LoggerInfo loggerInfo) throws ApiException {
|
||||
return this.setLogger(loggerInfo, Collections.emptyMap());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Set the logger level.
|
||||
* @param loggerInfo (optional)
|
||||
* @param additionalHeaders additionalHeaders for this call
|
||||
* @return HandleResponse
|
||||
* @throws ApiException if fails to make API call
|
||||
*/
|
||||
public HandleResponse setLogger(LoggerInfo loggerInfo, Map<String, String> additionalHeaders) throws ApiException {
|
||||
Object localVarPostBody = loggerInfo;
|
||||
|
||||
// create path and map variables
|
||||
String localVarPath = "/api/v1/loggers";
|
||||
|
||||
StringJoiner localVarQueryStringJoiner = new StringJoiner("&");
|
||||
String localVarQueryParameterBaseName;
|
||||
List<Pair> localVarQueryParams = new ArrayList<Pair>();
|
||||
List<Pair> localVarCollectionQueryParams = new ArrayList<Pair>();
|
||||
Map<String, String> localVarHeaderParams = new HashMap<String, String>();
|
||||
Map<String, String> localVarCookieParams = new HashMap<String, String>();
|
||||
Map<String, Object> localVarFormParams = new HashMap<String, Object>();
|
||||
|
||||
|
||||
localVarHeaderParams.putAll(additionalHeaders);
|
||||
|
||||
|
||||
|
||||
final String[] localVarAccepts = {
|
||||
"application/json"
|
||||
};
|
||||
final String localVarAccept = apiClient.selectHeaderAccept(localVarAccepts);
|
||||
|
||||
final String[] localVarContentTypes = {
|
||||
"application/json"
|
||||
};
|
||||
final String localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes);
|
||||
|
||||
String[] localVarAuthNames = new String[] { "basic" };
|
||||
|
||||
TypeReference<HandleResponse> localVarReturnType = new TypeReference<HandleResponse>() {};
|
||||
return apiClient.invokeAPI(
|
||||
localVarPath,
|
||||
"POST",
|
||||
localVarQueryParams,
|
||||
localVarCollectionQueryParams,
|
||||
localVarQueryStringJoiner.toString(),
|
||||
localVarPostBody,
|
||||
localVarHeaderParams,
|
||||
localVarCookieParams,
|
||||
localVarFormParams,
|
||||
localVarAccept,
|
||||
localVarContentType,
|
||||
localVarAuthNames,
|
||||
localVarReturnType
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T invokeAPI(String url, String method, Object request, TypeReference<T> returnType, Map<String, String> additionalHeaders) throws ApiException {
|
||||
String localVarPath = url.replace(apiClient.getBaseURL(), "");
|
||||
StringJoiner localVarQueryStringJoiner = new StringJoiner("&");
|
||||
List<Pair> localVarQueryParams = new ArrayList<Pair>();
|
||||
List<Pair> localVarCollectionQueryParams = new ArrayList<Pair>();
|
||||
Map<String, String> localVarHeaderParams = new HashMap<String, String>();
|
||||
Map<String, String> localVarCookieParams = new HashMap<String, String>();
|
||||
Map<String, Object> localVarFormParams = new HashMap<String, Object>();
|
||||
|
||||
localVarHeaderParams.putAll(additionalHeaders);
|
||||
|
||||
final String[] localVarAccepts = {
|
||||
"application/json"
|
||||
};
|
||||
final String localVarAccept = apiClient.selectHeaderAccept(localVarAccepts);
|
||||
|
||||
final String[] localVarContentTypes = {
|
||||
"application/json"
|
||||
};
|
||||
final String localVarContentType = apiClient.selectHeaderContentType(localVarContentTypes);
|
||||
|
||||
String[] localVarAuthNames = new String[] { "basic" };
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
localVarPath,
|
||||
method,
|
||||
localVarQueryParams,
|
||||
localVarCollectionQueryParams,
|
||||
localVarQueryStringJoiner.toString(),
|
||||
request,
|
||||
localVarHeaderParams,
|
||||
localVarCookieParams,
|
||||
localVarFormParams,
|
||||
localVarAccept,
|
||||
localVarContentType,
|
||||
localVarAuthNames,
|
||||
returnType
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -456,6 +456,51 @@ paths:
|
||||
type: string
|
||||
format: binary
|
||||
|
||||
/api/v1/loggers:
|
||||
get:
|
||||
tags:
|
||||
- Logger
|
||||
operationId: getLogger
|
||||
description: Get the logger level, return all loggers if no name specified.
|
||||
parameters:
|
||||
- name: name
|
||||
in: query
|
||||
description: The logger name.
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- name: all
|
||||
in: query
|
||||
description: Return all logger instances if true, otherwise return all configured loggers.
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
responses:
|
||||
"200":
|
||||
description: The request was successful.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/LoggerInfos'
|
||||
post:
|
||||
tags:
|
||||
- Logger
|
||||
operationId: setLogger
|
||||
description: Set the logger level.
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/LoggerInfo'
|
||||
responses:
|
||||
"200":
|
||||
description: The request was successful.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HandleResponse'
|
||||
|
||||
components:
|
||||
schemas:
|
||||
ConfigData:
|
||||
@ -1067,6 +1112,28 @@ components:
|
||||
type: integer
|
||||
format: int32
|
||||
|
||||
LoggerInfo:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: The logger name.
|
||||
level:
|
||||
type: string
|
||||
description: The logger level.
|
||||
required:
|
||||
- name
|
||||
- level
|
||||
|
||||
LoggerInfos:
|
||||
type: object
|
||||
properties:
|
||||
loggers:
|
||||
type: array
|
||||
description: The logger infos.
|
||||
items:
|
||||
$ref: '#/components/schemas/LoggerInfo'
|
||||
|
||||
HandleResponse:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@ -202,6 +202,51 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApplicationsResponse'
|
||||
|
||||
/api/v1/loggers:
|
||||
get:
|
||||
tags:
|
||||
- Logger
|
||||
operationId: getLogger
|
||||
description: Get the logger level, return all loggers if no name specified.
|
||||
parameters:
|
||||
- name: name
|
||||
in: query
|
||||
description: The logger name.
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- name: all
|
||||
in: query
|
||||
description: Return all logger instances if true, otherwise return all configured loggers.
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
default: false
|
||||
responses:
|
||||
"200":
|
||||
description: The request was successful.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/LoggerInfos'
|
||||
post:
|
||||
tags:
|
||||
- Logger
|
||||
operationId: setLogger
|
||||
description: Set the logger level.
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/LoggerInfo'
|
||||
responses:
|
||||
"200":
|
||||
description: The request was successful.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HandleResponse'
|
||||
|
||||
components:
|
||||
schemas:
|
||||
ConfigData:
|
||||
@ -651,6 +696,28 @@ components:
|
||||
- IMMEDIATELY
|
||||
- NONE
|
||||
|
||||
LoggerInfo:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: The logger name.
|
||||
level:
|
||||
type: string
|
||||
description: The logger level.
|
||||
required:
|
||||
- name
|
||||
- level
|
||||
|
||||
LoggerInfos:
|
||||
type: object
|
||||
properties:
|
||||
loggers:
|
||||
type: array
|
||||
description: The logger infos.
|
||||
items:
|
||||
$ref: '#/components/schemas/LoggerInfo'
|
||||
|
||||
HandleResponse:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@ -135,6 +135,8 @@ object Dependencies {
|
||||
"org.fusesource.leveldbjni"
|
||||
}
|
||||
val leveldbJniAll = leveldbJniGroup % "leveldbjni-all" % leveldbJniVersion
|
||||
val log4jApi = "org.apache.logging.log4j" % "log4j-api" % log4j2Version
|
||||
val log4jCore = "org.apache.logging.log4j" % "log4j-core" % log4j2Version
|
||||
val log4j12Api = "org.apache.logging.log4j" % "log4j-1.2-api" % log4j2Version
|
||||
val log4jSlf4jImpl = "org.apache.logging.log4j" % "log4j-slf4j-impl" % log4j2Version
|
||||
val lz4Java = "org.lz4" % "lz4-java" % lz4JavaVersion
|
||||
@ -629,6 +631,8 @@ object CelebornService {
|
||||
Dependencies.jettyServer,
|
||||
Dependencies.jettyServlet,
|
||||
Dependencies.jettyProxy,
|
||||
Dependencies.log4jApi,
|
||||
Dependencies.log4jCore,
|
||||
Dependencies.log4jSlf4jImpl % "test",
|
||||
Dependencies.log4j12Api % "test",
|
||||
Dependencies.h2 % "test",
|
||||
|
||||
@ -180,6 +180,16 @@
|
||||
<artifactId>jackson-databind-nullable</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
|
||||
@ -35,6 +35,9 @@ class ApiV1BaseResource extends ApiRequestContext {
|
||||
@Path("conf")
|
||||
def conf: Class[ConfResource] = classOf[ConfResource]
|
||||
|
||||
@Path("loggers")
|
||||
def logger: Class[LoggerResource] = classOf[LoggerResource]
|
||||
|
||||
@Path("/thread_dump")
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
|
||||
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.celeborn.server.common.http.api.v1
|
||||
|
||||
import javax.ws.rs.{Consumes, DefaultValue, GET, POST, Produces, QueryParam}
|
||||
import javax.ws.rs.core.MediaType
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
import io.swagger.v3.oas.annotations.Parameter
|
||||
import io.swagger.v3.oas.annotations.media.{Content, Schema}
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
||||
import io.swagger.v3.oas.annotations.tags.Tag
|
||||
import org.apache.logging.log4j.{Level, LogManager}
|
||||
import org.apache.logging.log4j.core.LoggerContext
|
||||
import org.apache.logging.log4j.core.config.Configurator
|
||||
|
||||
import org.apache.celeborn.rest.v1.model.{HandleResponse, LoggerInfo, LoggerInfos}
|
||||
import org.apache.celeborn.server.common.http.api.ApiRequestContext
|
||||
|
||||
@Tag(name = "Logger")
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
class LoggerResource extends ApiRequestContext {
|
||||
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
content = Array(new Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = new Schema(implementation = classOf[LoggerInfo]))),
|
||||
description = "Get the logger level, return all loggers if no name specified.")
|
||||
@GET
|
||||
def getLoggerLevel(
|
||||
@QueryParam("name") name: String,
|
||||
@QueryParam("all") @DefaultValue("false") @Parameter(description =
|
||||
"Return all logger instances if true, otherwise return all configured loggers.") all: Boolean)
|
||||
: LoggerInfos = {
|
||||
if (null != name) {
|
||||
new LoggerInfos().addLoggersItem(
|
||||
new LoggerInfo().name(name).level(LogManager.getLogger(name).getLevel.toString))
|
||||
} else {
|
||||
val loggerContext = LogManager.getContext(false).asInstanceOf[LoggerContext]
|
||||
val loggers =
|
||||
if (all) {
|
||||
loggerContext.getLoggers.asScala.map { logger =>
|
||||
new LoggerInfo().name(logger.getName).level(logger.getLevel.toString)
|
||||
}.toSeq
|
||||
} else {
|
||||
loggerContext.getConfiguration.getLoggers.values().asScala.map { loggerConfig =>
|
||||
new LoggerInfo().name(loggerConfig.getName).level(loggerConfig.getLevel.toString)
|
||||
}.toSeq
|
||||
}
|
||||
new LoggerInfos().loggers(loggers.sortBy(_.getName).asJava)
|
||||
}
|
||||
}
|
||||
|
||||
@ApiResponse(
|
||||
responseCode = "200",
|
||||
content = Array(new Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = new Schema(implementation = classOf[HandleResponse]))),
|
||||
description = "Set the logger level.")
|
||||
@POST
|
||||
def setLoggerLevel(request: LoggerInfo): HandleResponse = {
|
||||
val loggerName = request.getName
|
||||
val logger = LogManager.getLogger(loggerName)
|
||||
val originalLevel = logger.getLevel
|
||||
val newLevel = Level.toLevel(request.getLevel)
|
||||
Configurator.setLevel(loggerName, newLevel)
|
||||
new HandleResponse().success(true).message(
|
||||
s"Set logger `$loggerName` level from `$originalLevel` to `$newLevel`.`")
|
||||
}
|
||||
}
|
||||
@ -19,11 +19,12 @@ package org.apache.celeborn.server.common.http.api.v1
|
||||
|
||||
import java.net.URI
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
import javax.ws.rs.client.Entity
|
||||
import javax.ws.rs.core.{MediaType, UriBuilder}
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
import org.apache.celeborn.rest.v1.model.{ConfResponse, ThreadStackResponse}
|
||||
import org.apache.celeborn.rest.v1.model.{ConfResponse, LoggerInfo, LoggerInfos, ThreadStackResponse}
|
||||
import org.apache.celeborn.server.common.http.HttpTestHelper
|
||||
|
||||
abstract class ApiV1BaseResourceSuite extends HttpTestHelper {
|
||||
@ -40,6 +41,85 @@ abstract class ApiV1BaseResourceSuite extends HttpTestHelper {
|
||||
assert(response.readEntity(classOf[String]).contains("Dynamic configuration is disabled."))
|
||||
}
|
||||
|
||||
test("logger resource") {
|
||||
val loggerName = this.getClass.getName
|
||||
|
||||
// set logger level to INFO as initial state
|
||||
val response = webTarget.path("loggers").request(MediaType.APPLICATION_JSON).post(Entity.entity(
|
||||
new LoggerInfo().name(loggerName).level("INFO"),
|
||||
MediaType.APPLICATION_JSON))
|
||||
assert(HttpServletResponse.SC_OK == response.getStatus)
|
||||
|
||||
// check logger level is INFO
|
||||
val response1 = webTarget.path("loggers")
|
||||
.queryParam("name", loggerName)
|
||||
.request(MediaType.APPLICATION_JSON).get()
|
||||
assert(HttpServletResponse.SC_OK == response.getStatus)
|
||||
val loggerInfo = response1.readEntity(classOf[LoggerInfos]).getLoggers.get(0)
|
||||
assert(loggerName == loggerInfo.getName)
|
||||
assert(loggerInfo.getLevel == "INFO")
|
||||
assert(log.isInfoEnabled)
|
||||
assert(!log.isDebugEnabled)
|
||||
|
||||
// set logger level to DEBUG
|
||||
val response2 =
|
||||
webTarget.path("loggers").request(MediaType.APPLICATION_JSON).post(Entity.entity(
|
||||
new LoggerInfo().name(loggerName).level("DEBUG"),
|
||||
MediaType.APPLICATION_JSON))
|
||||
assert(HttpServletResponse.SC_OK == response2.getStatus)
|
||||
|
||||
// check logger level is DEBUG
|
||||
val response3 = webTarget.path("loggers")
|
||||
.queryParam("name", loggerName)
|
||||
.request(MediaType.APPLICATION_JSON).get()
|
||||
assert(HttpServletResponse.SC_OK == response.getStatus)
|
||||
val loggerInfo2 = response3.readEntity(classOf[LoggerInfos]).getLoggers.get(0)
|
||||
assert(loggerName == loggerInfo2.getName)
|
||||
assert(loggerInfo2.getLevel == "DEBUG")
|
||||
assert(log.isInfoEnabled)
|
||||
assert(log.isDebugEnabled)
|
||||
|
||||
// check all configured loggers
|
||||
val response4 =
|
||||
webTarget.path("loggers").queryParam("all", "false").request(MediaType.APPLICATION_JSON).get()
|
||||
assert(HttpServletResponse.SC_OK == response4.getStatus)
|
||||
val configuredLoggers = response4.readEntity(classOf[LoggerInfos]).getLoggers.asScala
|
||||
assert(configuredLoggers.exists(l => l.getName == loggerName && l.getLevel == "DEBUG"))
|
||||
// root logger
|
||||
assert(configuredLoggers.exists(l => l.getName == "" && l.getLevel == "INFO"))
|
||||
|
||||
// check all loggers
|
||||
val response5 =
|
||||
webTarget.path("loggers").queryParam("all", "true").request(MediaType.APPLICATION_JSON).get()
|
||||
assert(HttpServletResponse.SC_OK == response5.getStatus)
|
||||
val allLoggers = response5.readEntity(classOf[LoggerInfos]).getLoggers.asScala
|
||||
assert(configuredLoggers.exists(l => l.getName == loggerName && l.getLevel == "DEBUG"))
|
||||
assert(allLoggers.size > configuredLoggers.size)
|
||||
|
||||
// update root logger level
|
||||
val response6 =
|
||||
webTarget.path("loggers").request(MediaType.APPLICATION_JSON).post(Entity.entity(
|
||||
new LoggerInfo().name("").level("DEBUG"),
|
||||
MediaType.APPLICATION_JSON))
|
||||
assert(HttpServletResponse.SC_OK == response6.getStatus)
|
||||
|
||||
// check root logger level is DEBUG
|
||||
val response7 = webTarget.path("loggers")
|
||||
.queryParam("name", "")
|
||||
.request(MediaType.APPLICATION_JSON).get()
|
||||
assert(HttpServletResponse.SC_OK == response7.getStatus)
|
||||
val loggerInfo3 = response7.readEntity(classOf[LoggerInfos]).getLoggers.get(0)
|
||||
assert("" == loggerInfo3.getName)
|
||||
assert(loggerInfo3.getLevel == "DEBUG")
|
||||
|
||||
// reset root logger level to INFO
|
||||
val response8 =
|
||||
webTarget.path("loggers").request(MediaType.APPLICATION_JSON).post(Entity.entity(
|
||||
new LoggerInfo().name("").level("INFO"),
|
||||
MediaType.APPLICATION_JSON))
|
||||
assert(HttpServletResponse.SC_OK == response8.getStatus)
|
||||
}
|
||||
|
||||
test("thread_dump") {
|
||||
val response = webTarget.path("thread_dump").request(MediaType.APPLICATION_JSON).get()
|
||||
assert(HttpServletResponse.SC_OK == response.getStatus)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user