Skip to content

Commit 5d409d0

Browse files
shwstpprDaanHooglandharikrishna-patnala
authored andcommitted
api,server: apis return their http request type (apache#11382)
* api,server: apis return their http request type Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com> * fix and unit test Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com> * more test Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com> * address copilot Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com> * Update plugins/api/discovery/src/main/java/org/apache/cloudstack/api/response/ApiDiscoveryResponse.java --------- Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com> Co-authored-by: dahn <daan@onecht.net> Co-authored-by: Harikrishna <harikrishna.patnala@gmail.com>
1 parent 41a2dab commit 5d409d0

File tree

16 files changed

+319
-56
lines changed

16 files changed

+319
-56
lines changed

api/src/main/java/org/apache/cloudstack/api/APICommand.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,6 @@
5050
RoleType[] authorized() default {};
5151

5252
Class<?>[] entityType() default {};
53+
54+
String httpMethod() default "";
5355
}

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ public class ApiConstants {
282282
public static final String HOST = "host";
283283
public static final String HOST_CONTROL_STATE = "hostcontrolstate";
284284
public static final String HOSTS_MAP = "hostsmap";
285+
public static final String HTTP_REQUEST_TYPE = "httprequesttype";
285286
public static final String HYPERVISOR = "hypervisor";
286287
public static final String INLINE = "inline";
287288
public static final String INSTANCE = "instance";

api/src/main/java/org/apache/cloudstack/api/command/admin/offering/IsAccountAllowedToCreateOfferingsWithTagsCmd.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
import org.apache.cloudstack.api.response.IsAccountAllowedToCreateOfferingsWithTagsResponse;
2727

2828
@APICommand(name = "isAccountAllowedToCreateOfferingsWithTags", description = "Return true if the specified account is allowed to create offerings with tags.",
29-
responseObject = IsAccountAllowedToCreateOfferingsWithTagsResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
29+
responseObject = IsAccountAllowedToCreateOfferingsWithTagsResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
30+
httpMethod = "GET")
3031
public class IsAccountAllowedToCreateOfferingsWithTagsCmd extends BaseCmd {
3132

3233
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "Account UUID", required = true)

plugins/api/discovery/src/main/java/org/apache/cloudstack/api/response/ApiDiscoveryResponse.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ public class ApiDiscoveryResponse extends BaseResponse {
5959
@Param(description = "Response field type")
6060
private String type;
6161

62+
@SerializedName(ApiConstants.HTTP_REQUEST_TYPE)
63+
@Param(description = "Preferred HTTP request type for the API", since = "4.23.0")
64+
private String httpRequestType;
65+
6266
public ApiDiscoveryResponse() {
6367
params = new HashSet<ApiParameterResponse>();
6468
apiResponse = new HashSet<ApiResponseResponse>();
@@ -74,6 +78,7 @@ public ApiDiscoveryResponse(ApiDiscoveryResponse another) {
7478
this.params = new HashSet<>(another.getParams());
7579
this.apiResponse = new HashSet<>(another.getApiResponse());
7680
this.type = another.getType();
81+
this.httpRequestType = another.getHttpRequestType();
7782
this.setObjectName(another.getObjectName());
7883
}
7984

@@ -140,4 +145,12 @@ public Set<ApiResponseResponse> getApiResponse() {
140145
public String getType() {
141146
return type;
142147
}
148+
149+
public String getHttpRequestType() {
150+
return httpRequestType;
151+
}
152+
153+
public void setHttpRequestType(String httpRequestType) {
154+
this.httpRequestType = httpRequestType;
155+
}
143156
}

plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import org.reflections.ReflectionUtils;
5151
import org.springframework.stereotype.Component;
5252

53+
import com.cloud.api.ApiServlet;
5354
import com.cloud.exception.PermissionDeniedException;
5455
import com.cloud.serializer.Param;
5556
import com.cloud.user.Account;
@@ -189,14 +190,20 @@ private ApiResponseResponse getFieldResponseMap(Field responseField) {
189190
return responseResponse;
190191
}
191192

192-
private ApiDiscoveryResponse getCmdRequestMap(Class<?> cmdClass, APICommand apiCmdAnnotation) {
193+
protected ApiDiscoveryResponse getCmdRequestMap(Class<?> cmdClass, APICommand apiCmdAnnotation) {
193194
String apiName = apiCmdAnnotation.name();
194195
ApiDiscoveryResponse response = new ApiDiscoveryResponse();
195196
response.setName(apiName);
196197
response.setDescription(apiCmdAnnotation.description());
197198
if (!apiCmdAnnotation.since().isEmpty()) {
198199
response.setSince(apiCmdAnnotation.since());
199200
}
201+
String httpRequestType = apiCmdAnnotation.httpMethod();
202+
if (StringUtils.isBlank(httpRequestType)) {
203+
httpRequestType = ApiServlet.GET_REQUEST_COMMANDS.matcher(apiName.toLowerCase()).matches() ?
204+
"GET" : "POST";
205+
}
206+
response.setHttpRequestType(httpRequestType);
200207

201208
Set<Field> fields = ReflectUtil.getAllFieldsForClass(cmdClass, new Class<?>[] {BaseCmd.class, BaseAsyncCmd.class, BaseAsyncCreateCmd.class});
202209

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package org.apache.cloudstack.discovery;
18+
19+
import static org.mockito.ArgumentMatchers.any;
20+
21+
import java.lang.reflect.Field;
22+
import java.util.Set;
23+
24+
import org.apache.cloudstack.api.APICommand;
25+
import org.apache.cloudstack.api.BaseAsyncCmd;
26+
import org.apache.cloudstack.api.BaseCmd;
27+
import org.apache.cloudstack.api.Parameter;
28+
import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd;
29+
import org.apache.cloudstack.api.command.admin.user.GetUserCmd;
30+
import org.apache.cloudstack.api.command.user.discovery.ListApisCmd;
31+
import org.apache.cloudstack.api.response.ApiDiscoveryResponse;
32+
import org.apache.cloudstack.api.response.ApiParameterResponse;
33+
import org.junit.Assert;
34+
import org.junit.Before;
35+
import org.junit.Test;
36+
import org.junit.runner.RunWith;
37+
import org.mockito.InjectMocks;
38+
import org.mockito.Mock;
39+
import org.mockito.MockedStatic;
40+
import org.mockito.Mockito;
41+
import org.mockito.Spy;
42+
import org.mockito.junit.MockitoJUnitRunner;
43+
import org.springframework.test.util.ReflectionTestUtils;
44+
45+
import com.cloud.utils.ReflectUtil;
46+
47+
@RunWith(MockitoJUnitRunner.class)
48+
public class ApiDiscoveryServiceImplTest {
49+
50+
@Mock
51+
APICommand apiCommandMock;
52+
53+
@Spy
54+
@InjectMocks
55+
ApiDiscoveryServiceImpl discoveryServiceSpy;
56+
57+
@Before
58+
public void setUp() {
59+
Mockito.when(apiCommandMock.name()).thenReturn("listApis");
60+
Mockito.when(apiCommandMock.since()).thenReturn("");
61+
}
62+
63+
@Test
64+
public void getCmdRequestMapReturnsResponseWithCorrectApiNameAndDescription() {
65+
Mockito.when(apiCommandMock.description()).thenReturn("Lists all APIs");
66+
ApiDiscoveryResponse response = discoveryServiceSpy.getCmdRequestMap(ListApisCmd.class, apiCommandMock);
67+
Assert.assertEquals("listApis", response.getName());
68+
Assert.assertEquals("Lists all APIs", response.getDescription());
69+
}
70+
71+
@Test
72+
public void getCmdRequestMapSetsHttpRequestTypeToGetWhenApiNameMatchesGetPattern() {
73+
Mockito.when(apiCommandMock.name()).thenReturn("getUser");
74+
Mockito.when(apiCommandMock.httpMethod()).thenReturn("");
75+
ApiDiscoveryResponse response = discoveryServiceSpy.getCmdRequestMap(GetUserCmd.class, apiCommandMock);
76+
Assert.assertEquals("GET", response.getHttpRequestType());
77+
}
78+
79+
@Test
80+
public void getCmdRequestMapSetsHttpRequestTypeToPostWhenApiNameDoesNotMatchGetPattern() {
81+
Mockito.when(apiCommandMock.name()).thenReturn("createAccount");
82+
Mockito.when(apiCommandMock.httpMethod()).thenReturn("");
83+
ApiDiscoveryResponse response = discoveryServiceSpy.getCmdRequestMap(CreateAccountCmd.class, apiCommandMock);
84+
Assert.assertEquals("POST", response.getHttpRequestType());
85+
}
86+
87+
@Test
88+
public void getCmdRequestMapSetsAsyncToTrueForAsyncCommand() {
89+
Mockito.when(apiCommandMock.name()).thenReturn("asyncApi");
90+
ApiDiscoveryResponse response = discoveryServiceSpy.getCmdRequestMap(BaseAsyncCmd.class, apiCommandMock);
91+
Assert.assertTrue(response.getAsync());
92+
}
93+
94+
@Test
95+
public void getCmdRequestMapDoesNotAddParamsWithoutParameterAnnotation() {
96+
ApiDiscoveryResponse response = discoveryServiceSpy.getCmdRequestMap(BaseCmd.class, apiCommandMock);
97+
Assert.assertFalse(response.getParams().isEmpty());
98+
Assert.assertEquals(1, response.getParams().size());
99+
}
100+
101+
@Test
102+
public void getCmdRequestMapAddsParamsWithExposedAndIncludedInApiDocAnnotations() {
103+
Field fieldMock = Mockito.mock(Field.class);
104+
Parameter parameterMock = Mockito.mock(Parameter.class);
105+
Mockito.when(parameterMock.expose()).thenReturn(true);
106+
Mockito.when(parameterMock.includeInApiDoc()).thenReturn(true);
107+
Mockito.when(parameterMock.name()).thenReturn("paramName");
108+
Mockito.when(parameterMock.since()).thenReturn("");
109+
Mockito.when(parameterMock.entityType()).thenReturn(new Class[]{Object.class});
110+
Mockito.when(parameterMock.description()).thenReturn("paramDescription");
111+
Mockito.when(parameterMock.type()).thenReturn(BaseCmd.CommandType.STRING);
112+
Mockito.when(fieldMock.getAnnotation(Parameter.class)).thenReturn(parameterMock);
113+
try (MockedStatic<ReflectUtil> reflectUtilMockedStatic = Mockito.mockStatic(ReflectUtil.class)) {
114+
reflectUtilMockedStatic.when(() -> ReflectUtil.getAllFieldsForClass(any(Class.class), any(Class[].class)))
115+
.thenReturn(Set.of(fieldMock));
116+
ApiDiscoveryResponse response = discoveryServiceSpy.getCmdRequestMap(ListApisCmd.class, apiCommandMock);
117+
Set<ApiParameterResponse> params = response.getParams();
118+
Assert.assertEquals(1, params.size());
119+
ApiParameterResponse paramResponse = params.iterator().next();
120+
Assert.assertEquals("paramName", ReflectionTestUtils.getField(paramResponse, "name"));
121+
}
122+
}
123+
}

plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaBalanceCmd.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
import org.apache.cloudstack.quota.vo.QuotaBalanceVO;
3636
import org.apache.cloudstack.api.response.QuotaStatementItemResponse;
3737

38-
@APICommand(name = "quotaBalance", responseObject = QuotaStatementItemResponse.class, description = "Create a quota balance statement", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
38+
@APICommand(name = "quotaBalance", responseObject = QuotaStatementItemResponse.class, description = "Create a quota balance statement", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
39+
httpMethod = "GET")
3940
public class QuotaBalanceCmd extends BaseCmd {
4041

4142

plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaEnabledCmd.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626

2727
import javax.inject.Inject;
2828

29-
@APICommand(name = "quotaIsEnabled", responseObject = QuotaEnabledResponse.class, description = "Return true if the plugin is enabled", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
29+
@APICommand(name = "quotaIsEnabled", responseObject = QuotaEnabledResponse.class, description = "Return true if the plugin is enabled", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
30+
httpMethod = "GET")
3031
public class QuotaEnabledCmd extends BaseCmd {
3132

3233

plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaStatementCmd.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535

3636
import com.cloud.user.Account;
3737

38-
@APICommand(name = "quotaStatement", responseObject = QuotaStatementItemResponse.class, description = "Create a quota statement", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
38+
@APICommand(name = "quotaStatement", responseObject = QuotaStatementItemResponse.class, description = "Create a quota statement", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
39+
httpMethod = "GET")
3940
public class QuotaStatementCmd extends BaseCmd {
4041

4142

plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaSummaryCmd.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333

3434
import javax.inject.Inject;
3535

36-
@APICommand(name = "quotaSummary", responseObject = QuotaSummaryResponse.class, description = "Lists balance and quota usage for all Accounts", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
36+
@APICommand(name = "quotaSummary", responseObject = QuotaSummaryResponse.class, description = "Lists balance and quota usage for all Accounts", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
37+
httpMethod = "GET")
3738
public class QuotaSummaryCmd extends BaseListCmd {
3839

3940
@Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, required = false, description = "Optional, Account Id for which statement needs to be generated")

0 commit comments

Comments
 (0)