Skip to content

Commit 18e6b9b

Browse files
Add NSX specific RestClient implementation
- Add -noverify JVM arg to surefire plugin, to allow Powermockito to de-encapsulate private methods - Add dependency on cloud-utils test-jar to use custom HttpRequest matchers
1 parent de63b94 commit 18e6b9b

8 files changed

Lines changed: 583 additions & 1 deletion

File tree

plugins/network-elements/nicira-nvp/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@
3030
<relativePath>../../pom.xml</relativePath>
3131
</parent>
3232

33+
<dependencies>
34+
<dependency>
35+
<groupId>org.apache.cloudstack</groupId>
36+
<artifactId>cloud-utils</artifactId>
37+
<version>4.6.0-SNAPSHOT</version>
38+
<type>test-jar</type>
39+
<scope>test</scope>
40+
</dependency>
41+
</dependencies>
42+
3343
<build>
3444
<sourceDirectory>src/main/java</sourceDirectory>
3545
<testSourceDirectory>src/test/java</testSourceDirectory>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
//
19+
20+
package com.cloud.network.nicira;
21+
22+
import java.util.concurrent.atomic.AtomicInteger;
23+
24+
public class ExecutionCounter {
25+
26+
private final int executionLimit;
27+
private final AtomicInteger executionCount = new AtomicInteger(0);
28+
29+
public ExecutionCounter(final int executionLimit) {
30+
this.executionLimit = executionLimit;
31+
}
32+
33+
public ExecutionCounter resetExecutionCounter() {
34+
executionCount.set(0);
35+
return this;
36+
}
37+
38+
public boolean hasReachedExecutionLimit() {
39+
return executionCount.get() >= executionLimit;
40+
}
41+
42+
public ExecutionCounter incrementExecutionCounter() {
43+
executionCount.incrementAndGet();
44+
return this;
45+
}
46+
47+
public int getValue() {
48+
return executionCount.get();
49+
}
50+
}
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
//
19+
20+
package com.cloud.network.nicira;
21+
22+
import java.io.IOException;
23+
import java.util.HashMap;
24+
import java.util.Map;
25+
26+
import org.apache.http.HttpEntity;
27+
import org.apache.http.StatusLine;
28+
import org.apache.http.client.methods.CloseableHttpResponse;
29+
import org.apache.http.client.methods.HttpUriRequest;
30+
import org.apache.http.client.protocol.HttpClientContext;
31+
import org.apache.http.impl.client.CloseableHttpClient;
32+
import org.apache.http.util.EntityUtils;
33+
import org.apache.log4j.Logger;
34+
35+
import com.cloud.utils.rest.BasicRestClient;
36+
import com.cloud.utils.rest.CloudstackRESTException;
37+
import com.cloud.utils.rest.HttpConstants;
38+
import com.cloud.utils.rest.HttpMethods;
39+
import com.cloud.utils.rest.HttpStatusCodeHelper;
40+
import com.cloud.utils.rest.HttpUriRequestBuilder;
41+
42+
public class NiciraRestClient extends BasicRestClient {
43+
44+
private static final Logger s_logger = Logger.getLogger(NiciraRestClient.class);
45+
46+
private static final String CONTENT_TYPE = HttpConstants.CONTENT_TYPE;
47+
private static final String TEXT_HTML_CONTENT_TYPE = HttpConstants.TEXT_HTML_CONTENT_TYPE;
48+
49+
private static final int DEFAULT_BODY_RESP_MAX_LEN = 1024;
50+
private static final int DEFAULT_EXECUTION_LIMIT = 5;
51+
52+
private final ExecutionCounter counter;
53+
private final int maxResponseErrorMesageLength;
54+
private final int executionLimit;
55+
56+
private final String username;
57+
private final String password;
58+
private final String loginUrl;
59+
60+
private NiciraRestClient(final Builder builder) {
61+
super(builder.client, builder.clientContext, builder.hostname);
62+
executionLimit = builder.executionLimit;
63+
counter = new ExecutionCounter(executionLimit);
64+
maxResponseErrorMesageLength = builder.maxResponseErrorMesageLength;
65+
username = builder.username;
66+
password = builder.password;
67+
loginUrl = builder.loginUrl;
68+
}
69+
70+
public static Builder create() {
71+
return new Builder();
72+
}
73+
74+
@Override
75+
public CloseableHttpResponse execute(final HttpUriRequest request) throws CloudstackRESTException {
76+
return execute(request, 0);
77+
}
78+
79+
private CloseableHttpResponse execute(final HttpUriRequest request, final int previousStatusCode) throws CloudstackRESTException {
80+
if (counter.hasReachedExecutionLimit()) {
81+
throw new CloudstackRESTException("Reached max executions limit of " + executionLimit);
82+
}
83+
counter.incrementExecutionCounter();
84+
s_logger.debug("Executing " + request.getMethod() + " request [execution count = " + counter.getValue() + "]");
85+
final CloseableHttpResponse response = super.execute(request);
86+
87+
final StatusLine statusLine = response.getStatusLine();
88+
final int statusCode = statusLine.getStatusCode();
89+
s_logger.debug("Status of last request: " + statusLine.toString());
90+
if (HttpStatusCodeHelper.isUnauthorized(statusCode)) {
91+
return handleUnauthorizedResponse(request, previousStatusCode, response, statusCode);
92+
} else if (HttpStatusCodeHelper.isSuccess(statusCode)) {
93+
return handleSuccessResponse(response);
94+
} else {
95+
throw new CloudstackRESTException("Unexpecetd status code: " + statusCode);
96+
}
97+
}
98+
99+
private CloseableHttpResponse handleUnauthorizedResponse(final HttpUriRequest request, final int previousStatusCode, final CloseableHttpResponse response, final int statusCode)
100+
throws CloudstackRESTException {
101+
super.closeResponse(response);
102+
if (HttpStatusCodeHelper.isUnauthorized(previousStatusCode)) {
103+
s_logger.error(responseToErrorMessage(response));
104+
throw new CloudstackRESTException("Two consecutive failed attempts to authenticate against REST server");
105+
}
106+
final HttpUriRequest authenticateRequest = createAuthenticationRequest();
107+
final CloseableHttpResponse loginResponse = execute(authenticateRequest, statusCode);
108+
final int loginStatusCode = loginResponse.getStatusLine().getStatusCode();
109+
super.closeResponse(loginResponse);
110+
return execute(request, loginStatusCode);
111+
}
112+
113+
private CloseableHttpResponse handleSuccessResponse(final CloseableHttpResponse response) {
114+
counter.resetExecutionCounter();
115+
return response;
116+
}
117+
118+
private HttpUriRequest createAuthenticationRequest() {
119+
final Map<String, String> parameters = new HashMap<>();
120+
parameters.put("username", username);
121+
parameters.put("password", password);
122+
return HttpUriRequestBuilder.create()
123+
.method(HttpMethods.POST)
124+
.methodParameters(parameters)
125+
.path(loginUrl)
126+
.build();
127+
}
128+
129+
private String responseToErrorMessage(final CloseableHttpResponse response) {
130+
String errorMessage = response.getStatusLine().toString();
131+
if (response.containsHeader(CONTENT_TYPE) && TEXT_HTML_CONTENT_TYPE.equals(response.getFirstHeader(CONTENT_TYPE).getValue())) {
132+
try {
133+
final HttpEntity entity = response.getEntity();
134+
final String respobnseBody = EntityUtils.toString(entity);
135+
errorMessage = respobnseBody.subSequence(0, maxResponseErrorMesageLength).toString();
136+
} catch (final IOException e) {
137+
s_logger.debug("Could not read repsonse body. Response: " + response, e);
138+
}
139+
}
140+
141+
return errorMessage;
142+
}
143+
144+
protected static class Builder extends BasicRestClient.Builder<Builder> {
145+
private CloseableHttpClient client;
146+
private HttpClientContext clientContext;
147+
private String hostname;
148+
private String username;
149+
private String password;
150+
private String loginUrl;
151+
private int executionLimit = DEFAULT_EXECUTION_LIMIT;
152+
private int maxResponseErrorMesageLength = DEFAULT_BODY_RESP_MAX_LEN;
153+
154+
public Builder hostname(final String hostname) {
155+
this.hostname = hostname;
156+
return this;
157+
}
158+
159+
public Builder username(final String username) {
160+
this.username = username;
161+
return this;
162+
}
163+
164+
public Builder password(final String password) {
165+
this.password = password;
166+
return this;
167+
}
168+
169+
public Builder loginUrl(final String loginUrl) {
170+
this.loginUrl = loginUrl;
171+
return this;
172+
}
173+
174+
@Override
175+
public Builder client(final CloseableHttpClient client) {
176+
this.client = client;
177+
return this;
178+
}
179+
180+
@Override
181+
public Builder clientContext(final HttpClientContext clientContext) {
182+
this.clientContext = clientContext;
183+
return this;
184+
}
185+
186+
public Builder executionLimit(final int executionLimit) {
187+
this.executionLimit = executionLimit;
188+
return this;
189+
}
190+
191+
public Builder maxResponseErrorMesageLength(final int maxResponseErrorMesageLength) {
192+
this.maxResponseErrorMesageLength = maxResponseErrorMesageLength;
193+
return this;
194+
}
195+
196+
@Override
197+
public NiciraRestClient build() {
198+
return new NiciraRestClient(this);
199+
}
200+
201+
}
202+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
//
19+
20+
package com.cloud.network.nicira;
21+
22+
import static org.hamcrest.MatcherAssert.assertThat;
23+
import static org.hamcrest.Matchers.equalTo;
24+
25+
import org.junit.Test;
26+
27+
public class ExecutionCounterTest {
28+
29+
@Test
30+
public void testIncrementCounter() throws Exception {
31+
final ExecutionCounter executionCounter = new ExecutionCounter(-1);
32+
33+
executionCounter.incrementExecutionCounter().incrementExecutionCounter();
34+
35+
assertThat(executionCounter.getValue(), equalTo(2));
36+
}
37+
38+
@Test
39+
public void testHasNotYetReachedTheExecutuionLimit() throws Exception {
40+
final ExecutionCounter executionCounter = new ExecutionCounter(2);
41+
42+
executionCounter.incrementExecutionCounter();
43+
44+
assertThat(executionCounter.hasReachedExecutionLimit(), equalTo(false));
45+
}
46+
47+
@Test
48+
public void testHasAlreadyReachedTheExecutuionLimit() throws Exception {
49+
final ExecutionCounter executionCounter = new ExecutionCounter(2);
50+
51+
executionCounter.incrementExecutionCounter().incrementExecutionCounter();
52+
53+
assertThat(executionCounter.hasReachedExecutionLimit(), equalTo(true));
54+
}
55+
}

0 commit comments

Comments
 (0)