diff --git a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java index db41a2f099a..96cf333cb19 100644 --- a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java +++ b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java @@ -124,7 +124,16 @@ public interface StorageManager extends StorageService { "Storage", "60", "Timeout (in secs) for the storage pool client connection timeout (for managed pools). Currently only supported for PowerFlex.", - true, + false, + ConfigKey.Scope.StoragePool, + null); + + ConfigKey STORAGE_POOL_CLIENT_MAX_CONNECTIONS = new ConfigKey<>(Integer.class, + "storage.pool.client.max.connections", + "Storage", + "100", + "Maximum connections for the storage pool client (for managed pools). Currently only supported for PowerFlex.", + false, ConfigKey.Scope.StoragePool, null); diff --git a/plugins/storage/volume/scaleio/pom.xml b/plugins/storage/volume/scaleio/pom.xml index e95087e7257..36a385c0d09 100644 --- a/plugins/storage/volume/scaleio/pom.xml +++ b/plugins/storage/volume/scaleio/pom.xml @@ -33,6 +33,12 @@ cloud-engine-storage-volume ${project.version} + + com.github.tomakehurst + wiremock-standalone + ${cs.wiremock.version} + test + diff --git a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClient.java b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClient.java index f6b10f88832..f497b10127d 100644 --- a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClient.java +++ b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClient.java @@ -40,8 +40,8 @@ public interface ScaleIOGatewayClient { String STORAGE_POOL_SYSTEM_ID = "powerflex.storagepool.system.id"; static ScaleIOGatewayClient getClient(final String url, final String username, final String password, - final boolean validateCertificate, final int timeout) throws NoSuchAlgorithmException, KeyManagementException, URISyntaxException { - return new ScaleIOGatewayClientImpl(url, username, password, validateCertificate, timeout); + final boolean validateCertificate, final int timeout, final int maxConnections) throws NoSuchAlgorithmException, KeyManagementException, URISyntaxException { + return new ScaleIOGatewayClientImpl(url, username, password, validateCertificate, timeout, maxConnections); } // Volume APIs diff --git a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientConnectionPool.java b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientConnectionPool.java index 2daf8e4635c..e557e088132 100644 --- a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientConnectionPool.java +++ b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientConnectionPool.java @@ -62,8 +62,9 @@ public class ScaleIOGatewayClientConnectionPool { final String encryptedPassword = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.GATEWAY_API_PASSWORD).getValue(); final String password = DBEncryptionUtil.decrypt(encryptedPassword); final int clientTimeout = StorageManager.STORAGE_POOL_CLIENT_TIMEOUT.valueIn(storagePoolId); + final int clientMaxConnections = StorageManager.STORAGE_POOL_CLIENT_MAX_CONNECTIONS.valueIn(storagePoolId); - client = new ScaleIOGatewayClientImpl(url, username, password, false, clientTimeout); + client = new ScaleIOGatewayClientImpl(url, username, password, false, clientTimeout, clientMaxConnections); gatewayClients.put(storagePoolId, client); LOGGER.debug("Added gateway client for the storage pool: " + storagePoolId); } diff --git a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientImpl.java b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientImpl.java index 5e8568dede1..fa195414b67 100644 --- a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientImpl.java +++ b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientImpl.java @@ -56,11 +56,16 @@ import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.ConnectTimeoutException; +import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.pool.PoolStats; import org.apache.http.util.EntityUtils; import org.apache.log4j.Logger; @@ -80,25 +85,27 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { private final URI apiURI; private final HttpClient httpClient; - private static final String SESSION_HEADER = "X-RestSvcSessionId"; + private final PoolingHttpClientConnectionManager connectionManager; private static final String MDM_CONNECTED_STATE = "Connected"; - private String host; private String username; private String password; private String sessionKey = null; // The session token is valid for 8 hours from the time it was created, unless there has been no activity for 10 minutes // Reference: https://cpsdocs.dellemc.com/bundle/PF_REST_API_RG/page/GUID-92430F19-9F44-42B6-B898-87D5307AE59B.html - private static final long MAX_VALID_SESSION_TIME_IN_MILLISECS = 8 * 60 * 60 * 1000; // 8 hrs - private static final long MAX_IDLE_TIME_IN_MILLISECS = 10 * 60 * 1000; // 10 mins + private static final long MAX_VALID_SESSION_TIME_IN_HRS = 8; + private static final long MAX_VALID_SESSION_TIME_IN_MILLISECS = MAX_VALID_SESSION_TIME_IN_HRS * 60 * 60 * 1000; + private static final long MAX_IDLE_TIME_IN_MINS = 10; + private static final long MAX_IDLE_TIME_IN_MILLISECS = MAX_IDLE_TIME_IN_MINS * 60 * 1000; private static final long BUFFER_TIME_IN_MILLISECS = 30 * 1000; // keep 30 secs buffer before the expiration (to avoid any last-minute operations) + private boolean authenticating = false; private long createTime = 0; private long lastUsedTime = 0; public ScaleIOGatewayClientImpl(final String url, final String username, final String password, - final boolean validateCertificate, final int timeout) + final boolean validateCertificate, final int timeout, final int maxConnections) throws NoSuchAlgorithmException, KeyManagementException, URISyntaxException { Preconditions.checkArgument(!Strings.isNullOrEmpty(url), "Gateway client url cannot be null"); Preconditions.checkArgument(!Strings.isNullOrEmpty(username) && !Strings.isNullOrEmpty(password), "Gateway client credentials cannot be null"); @@ -109,83 +116,141 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { .setSocketTimeout(timeout * 1000) .build(); + SSLConnectionSocketFactory factory = SSLConnectionSocketFactory.getSocketFactory(); if (!validateCertificate) { final SSLContext sslcontext = SSLUtils.getSSLContext(); sslcontext.init(null, new X509TrustManager[]{new TrustAllManager()}, new SecureRandom()); - final SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslcontext, NoopHostnameVerifier.INSTANCE); - this.httpClient = HttpClientBuilder.create() - .setDefaultRequestConfig(config) - .setSSLSocketFactory(factory) - .build(); - } else { - this.httpClient = HttpClientBuilder.create() - .setDefaultRequestConfig(config) - .build(); + factory = new SSLConnectionSocketFactory(sslcontext, NoopHostnameVerifier.INSTANCE); } + final Registry socketFactoryRegistry = RegistryBuilder. create() + .register("https", factory) + .build(); + connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); + connectionManager.setMaxTotal(maxConnections); + connectionManager.setDefaultMaxPerRoute(maxConnections); + + this.httpClient = HttpClientBuilder.create() + .setConnectionManager(connectionManager) + .setDefaultRequestConfig(config) + .setSSLSocketFactory(factory) + .build(); + this.apiURI = new URI(url); - this.host = apiURI.getHost(); this.username = username; this.password = password; authenticate(); + LOG.debug("API client for the PowerFlex gateway " + apiURI.getHost() + " is created successfully, with max connections: " + + maxConnections + " and timeout: " + timeout + " secs"); } ///////////////////////////////////////////////////////////// //////////////// Private Helper Methods ///////////////////// ///////////////////////////////////////////////////////////// - private void authenticate() { + private synchronized void authenticate() { final HttpGet request = new HttpGet(apiURI.toString() + "/login"); request.setHeader(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes())); + HttpResponse response = null; try { - final HttpResponse response = httpClient.execute(request); - checkAuthFailure(response); - this.sessionKey = EntityUtils.toString(response.getEntity()); - if (Strings.isNullOrEmpty(this.sessionKey)) { - throw new CloudRuntimeException("Failed to create a valid PowerFlex Gateway Session to perform API requests"); + authenticating = true; + LOG.debug("Authenticating gateway " + apiURI.getHost() + " with the request: " + request.toString()); + response = httpClient.execute(request); + if (isNullResponse(response)) { + LOG.warn("Invalid response received while authenticating, for the request: " + request.toString()); + throw new CloudRuntimeException("Failed to authenticate PowerFlex API Gateway due to invalid response from the Gateway " + apiURI.getHost()); } - this.sessionKey = this.sessionKey.replace("\"", ""); + + LOG.debug("Received response: " + response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase() + + ", for the authenticate request: " + request.toString()); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { - throw new CloudRuntimeException("PowerFlex Gateway login failed, please check the provided settings"); + throw new CloudRuntimeException("PowerFlex Gateway " + apiURI.getHost() + " login failed, please check the provided settings"); } + + String sessionKeyInResponse = EntityUtils.toString(response.getEntity()); + if (Strings.isNullOrEmpty(sessionKeyInResponse)) { + throw new CloudRuntimeException("Failed to create a valid session for PowerFlex Gateway " + apiURI.getHost() + " to perform API requests"); + } + + LOG.info("PowerFlex API Gateway " + apiURI.getHost() + " authenticated successfully"); + this.sessionKey = sessionKeyInResponse.replace("\"", ""); + + long now = System.currentTimeMillis(); + createTime = lastUsedTime = now; } catch (final IOException e) { - throw new CloudRuntimeException("Failed to authenticate PowerFlex API Gateway due to:" + e.getMessage()); + LOG.error("Failed to authenticate PowerFlex API Gateway " + apiURI.getHost() + " due to: " + e.getMessage() + getConnectionManagerStats()); + throw new CloudRuntimeException("Failed to authenticate PowerFlex API Gateway " + apiURI.getHost() + " due to: " + e.getMessage()); + } finally { + authenticating = false; + if (response != null) { + EntityUtils.consumeQuietly(response.getEntity()); + } } - long now = System.currentTimeMillis(); - createTime = lastUsedTime = now; } private synchronized void renewClientSessionOnExpiry() { if (isSessionExpired()) { - LOG.debug("Session expired, renewing"); + LOG.debug("Session expired for the PowerFlex API Gateway " + apiURI.getHost() + ", renewing"); authenticate(); } } private boolean isSessionExpired() { long now = System.currentTimeMillis() + BUFFER_TIME_IN_MILLISECS; - if ((now - createTime) > MAX_VALID_SESSION_TIME_IN_MILLISECS || - (now - lastUsedTime) > MAX_IDLE_TIME_IN_MILLISECS) { + if ((now - createTime) > MAX_VALID_SESSION_TIME_IN_MILLISECS) { + LOG.debug("Session expired for the Gateway " + apiURI.getHost() + ", token is invalid after " + MAX_VALID_SESSION_TIME_IN_HRS + + " hours from the time it was created"); + return true; + } + + if ((now - lastUsedTime) > MAX_IDLE_TIME_IN_MILLISECS) { + LOG.debug("Session expired for the Gateway " + apiURI.getHost() + ", as there has been no activity for " + MAX_IDLE_TIME_IN_MINS + " mins"); + return true; + } + + return false; + } + + private boolean isNullResponse(final HttpResponse response) { + if (response == null) { + LOG.warn("Nil response"); + return true; + } + + if (response.getStatusLine() == null) { + LOG.warn("No status line in the response"); + return true; + } + + return false; + } + + private boolean checkAuthFailure(final HttpResponse response, final boolean renewAndRetryOnAuthFailure) { + if (!isNullResponse(response) && response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { + if (!renewAndRetryOnAuthFailure) { + throw new ServerApiException(ApiErrorCode.UNAUTHORIZED, "PowerFlex Gateway API call unauthorized, please check the provided settings"); + } + LOG.debug("PowerFlex Gateway API call unauthorized. Current token might be invalid, renew the session." + getConnectionManagerStats()); return true; } return false; } - private void checkAuthFailure(final HttpResponse response) { - if (response != null && response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { - throw new ServerApiException(ApiErrorCode.UNAUTHORIZED, "PowerFlex Gateway API call unauthorized, please check the provided settings"); - } - } - private void checkResponseOK(final HttpResponse response) { - if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NO_CONTENT) { - LOG.debug("Requested resource does not exist"); + if (isNullResponse(response)) { return; } + + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NO_CONTENT) { + LOG.warn("Requested resource does not exist"); + return; + } + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_BAD_REQUEST) { throw new ServerApiException(ApiErrorCode.MALFORMED_PARAMETER_ERROR, "Bad API request"); } + if (!(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK || response.getStatusLine().getStatusCode() == HttpStatus.SC_ACCEPTED)) { String responseBody = response.toString(); @@ -193,54 +258,118 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { responseBody = EntityUtils.toString(response.getEntity()); } catch (IOException ignored) { } - LOG.debug("HTTP request failed, status code is " + response.getStatusLine().getStatusCode() + ", response is: " + responseBody); + LOG.debug("HTTP request failed, status code: " + response.getStatusLine().getStatusCode() + ", response: " + + responseBody + getConnectionManagerStats()); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "API failed due to: " + responseBody); } } private void checkResponseTimeOut(final Exception e) { if (e instanceof ConnectTimeoutException || e instanceof SocketTimeoutException) { - throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, "API operation timed out, please try again."); + throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, "Gateway API operation timed out, please try again."); } } - private HttpResponse get(final String path) throws IOException { - renewClientSessionOnExpiry(); - final HttpGet request = new HttpGet(apiURI.toString() + path); - request.setHeader(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString((this.username + ":" + this.sessionKey).getBytes())); - final HttpResponse response = httpClient.execute(request); - synchronized (this) { - lastUsedTime = System.currentTimeMillis(); - } - String responseStatus = (response != null) ? (response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase()) : "nil"; - LOG.debug("GET request path: " + path + ", response: " + responseStatus); - checkAuthFailure(response); - return response; + private T get(final String path, final Class type) { + return get(path, type, true); } - private HttpResponse post(final String path, final Object obj) throws IOException { + private T get(final String path, final Class type, final boolean renewAndRetryOnAuthFailure) { renewClientSessionOnExpiry(); - final HttpPost request = new HttpPost(apiURI.toString() + path); - request.setHeader(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString((this.username + ":" + this.sessionKey).getBytes())); - request.setHeader("Content-type", "application/json"); - if (obj != null) { - if (obj instanceof String) { - request.setEntity(new StringEntity((String) obj)); - } else { - JsonMapper mapper = new JsonMapper(); - mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - String json = mapper.writer().writeValueAsString(obj); - request.setEntity(new StringEntity(json)); + HttpResponse response = null; + boolean responseConsumed = false; + try { + while (authenticating); // wait for authentication request (if any) to complete (and to pick the new session key) + final HttpGet request = new HttpGet(apiURI.toString() + path); + request.setHeader(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString((this.username + ":" + this.sessionKey).getBytes())); + LOG.debug("Sending GET request: " + request.toString()); + response = httpClient.execute(request); + String responseStatus = (!isNullResponse(response)) ? (response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase()) : "nil"; + LOG.debug("Received response: " + responseStatus + ", for the sent GET request: " + request.toString()); + if (checkAuthFailure(response, renewAndRetryOnAuthFailure)) { + EntityUtils.consumeQuietly(response.getEntity()); + responseConsumed = true; + + authenticate(); + return get(path, type, false); + } + return processResponse(response, type); + } catch (final IOException e) { + LOG.error("Failed in GET method due to: " + e.getMessage() + getConnectionManagerStats(), e); + checkResponseTimeOut(e); + } finally { + if (!responseConsumed && response != null) { + EntityUtils.consumeQuietly(response.getEntity()); } } - final HttpResponse response = httpClient.execute(request); + return null; + } + + private T post(final String path, final Object obj, final Class type) { + return post(path, obj, type, true); + } + + private T post(final String path, final Object obj, final Class type, final boolean renewAndRetryOnAuthFailure) { + renewClientSessionOnExpiry(); + HttpResponse response = null; + boolean responseConsumed = false; + try { + while (authenticating); // wait for authentication request (if any) to complete (and to pick the new session key) + final HttpPost request = new HttpPost(apiURI.toString() + path); + request.setHeader(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString((this.username + ":" + this.sessionKey).getBytes())); + request.setHeader("Content-type", "application/json"); + if (obj != null) { + if (obj instanceof String) { + request.setEntity(new StringEntity((String) obj)); + } else { + JsonMapper mapper = new JsonMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + String json = mapper.writer().writeValueAsString(obj); + request.setEntity(new StringEntity(json)); + } + } + LOG.debug("Sending POST request: " + request.toString()); + response = httpClient.execute(request); + String responseStatus = (!isNullResponse(response)) ? (response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase()) : "nil"; + LOG.debug("Received response: " + responseStatus + ", for the sent POST request: " + request.toString()); + if (checkAuthFailure(response, renewAndRetryOnAuthFailure)) { + EntityUtils.consumeQuietly(response.getEntity()); + responseConsumed = true; + + authenticate(); + return post(path, obj, type, false); + } + return processResponse(response, type); + } catch (final IOException e) { + LOG.error("Failed in POST method due to: " + e.getMessage() + getConnectionManagerStats(), e); + checkResponseTimeOut(e); + } finally { + if (!responseConsumed && response != null) { + EntityUtils.consumeQuietly(response.getEntity()); + } + } + return null; + } + + private T processResponse(HttpResponse response, final Class type) throws IOException { + if (isNullResponse(response)) { + return null; + } + + checkResponseOK(response); synchronized (this) { lastUsedTime = System.currentTimeMillis(); } - String responseStatus = (response != null) ? (response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase()) : "nil"; - LOG.debug("POST request path: " + path + ", response: " + responseStatus); - checkAuthFailure(response); - return response; + if (type == Boolean.class) { + return (T) Boolean.TRUE; + } else if (type == String.class) { + return (T) EntityUtils.toString(response.getEntity()); + } else if (type == JsonNode.class) { + return (T) new ObjectMapper().readTree(response.getEntity().getContent()); + } else { + ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return mapper.readValue(response.getEntity().getContent(), type); + } } ////////////////////////////////////////////////// @@ -254,50 +383,25 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { Preconditions.checkArgument(!Strings.isNullOrEmpty(storagePoolId), "Storage pool id cannot be null"); Preconditions.checkArgument(sizeInGb != null && sizeInGb > 0, "Size(GB) must be greater than 0"); - HttpResponse response = null; - try { - Volume newVolume = new Volume(); - newVolume.setName(name); - newVolume.setStoragePoolId(storagePoolId); - newVolume.setVolumeSizeInGb(sizeInGb); - if (Storage.ProvisioningType.FAT.equals(volumeType)) { - newVolume.setVolumeType(Volume.VolumeType.ThickProvisioned); - } else { - newVolume.setVolumeType(Volume.VolumeType.ThinProvisioned); - } - // The basic allocation granularity is 8GB. The volume size will be rounded up. - response = post("/types/Volume/instances", newVolume); - checkResponseOK(response); - ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - Volume newVolumeObject = mapper.readValue(response.getEntity().getContent(), Volume.class); - return getVolume(newVolumeObject.getId()); - } catch (final IOException e) { - LOG.error("Failed to create PowerFlex volume due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } + Volume newVolume = new Volume(); + newVolume.setName(name); + newVolume.setStoragePoolId(storagePoolId); + newVolume.setVolumeSizeInGb(sizeInGb); + if (Storage.ProvisioningType.FAT.equals(volumeType)) { + newVolume.setVolumeType(Volume.VolumeType.ThickProvisioned); + } else { + newVolume.setVolumeType(Volume.VolumeType.ThinProvisioned); } - return null; + // The basic allocation granularity is 8GB. The volume size will be rounded up. + Volume newVolumeObject = post("/types/Volume/instances", newVolume, Volume.class); + return getVolume(newVolumeObject.getId()); } @Override public List listVolumes() { - HttpResponse response = null; - try { - response = get("/types/Volume/instances"); - checkResponseOK(response); - ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - Volume[] volumes = mapper.readValue(response.getEntity().getContent(), Volume[].class); + Volume[] volumes = get("/types/Volume/instances", Volume[].class); + if (volumes != null) { return Arrays.asList(volumes); - } catch (final IOException e) { - LOG.error("Failed to list PowerFlex volumes due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } } return new ArrayList<>(); } @@ -313,52 +417,24 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { } } } - return snapshotVolumes; } @Override public Volume getVolume(String volumeId) { Preconditions.checkArgument(!Strings.isNullOrEmpty(volumeId), "Volume id cannot be null"); - - HttpResponse response = null; - try { - response = get("/instances/Volume::" + volumeId); - checkResponseOK(response); - ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - return mapper.readValue(response.getEntity().getContent(), Volume.class); - } catch (final IOException e) { - LOG.error("Failed to get volume due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } - } - return null; + return get("/instances/Volume::" + volumeId, Volume.class); } @Override public Volume getVolumeByName(String name) { Preconditions.checkArgument(!Strings.isNullOrEmpty(name), "Volume name cannot be null"); - HttpResponse response = null; - try { - Volume searchVolume = new Volume(); - searchVolume.setName(name); - response = post("/types/Volume/instances/action/queryIdByKey", searchVolume); - checkResponseOK(response); - String volumeId = EntityUtils.toString(response.getEntity()); - if (!Strings.isNullOrEmpty(volumeId)) { - return getVolume(volumeId.replace("\"", "")); - } - } catch (final IOException e) { - LOG.error("Failed to get volume due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } + Volume searchVolume = new Volume(); + searchVolume.setName(name); + String volumeId = post("/types/Volume/instances/action/queryIdByKey", searchVolume, String.class); + if (!Strings.isNullOrEmpty(volumeId)) { + return getVolume(volumeId.replace("\"", "")); } return null; } @@ -368,20 +444,11 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { Preconditions.checkArgument(!Strings.isNullOrEmpty(volumeId), "Volume id cannot be null"); Preconditions.checkArgument(!Strings.isNullOrEmpty(newName), "New name for volume cannot be null"); - HttpResponse response = null; - try { - response = post( - "/instances/Volume::" + volumeId + "/action/setVolumeName", - String.format("{\"newName\":\"%s\"}", newName)); - checkResponseOK(response); - return true; - } catch (final IOException e) { - LOG.error("Failed to rename PowerFlex volume due to: ", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } + Boolean renameVolumeStatus = post( + "/instances/Volume::" + volumeId + "/action/setVolumeName", + String.format("{\"newName\":\"%s\"}", newName), Boolean.class); + if (renameVolumeStatus != null) { + return renameVolumeStatus; } return false; } @@ -392,21 +459,12 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { Preconditions.checkArgument(sizeInGB != null && (sizeInGB > 0 && sizeInGB % 8 == 0), "Size(GB) must be greater than 0 and in granularity of 8"); - HttpResponse response = null; - try { - // Volume capacity can only be increased. sizeInGB must be a positive number in granularity of 8 GB. - response = post( - "/instances/Volume::" + volumeId + "/action/setVolumeSize", - String.format("{\"sizeInGB\":\"%s\"}", sizeInGB.toString())); - checkResponseOK(response); + // Volume capacity can only be increased. sizeInGB must be a positive number in granularity of 8 GB. + Boolean resizeVolumeStatus = post( + "/instances/Volume::" + volumeId + "/action/setVolumeSize", + String.format("{\"sizeInGB\":\"%s\"}", sizeInGB.toString()), Boolean.class); + if (resizeVolumeStatus != null && resizeVolumeStatus.booleanValue()) { return getVolume(volumeId); - } catch (final IOException e) { - LOG.error("Failed to resize PowerFlex volume due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } } return null; } @@ -426,33 +484,19 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { public SnapshotGroup takeSnapshot(final Map srcVolumeDestSnapshotMap) { Preconditions.checkArgument(srcVolumeDestSnapshotMap != null && !srcVolumeDestSnapshotMap.isEmpty(), "srcVolumeDestSnapshotMap cannot be null"); - HttpResponse response = null; - try { - final List defs = new ArrayList<>(); - for (final String volumeId : srcVolumeDestSnapshotMap.keySet()) { - final SnapshotDef snapshotDef = new SnapshotDef(); - snapshotDef.setVolumeId(volumeId); - String snapshotName = srcVolumeDestSnapshotMap.get(volumeId); - if (!Strings.isNullOrEmpty(snapshotName)) { - snapshotDef.setSnapshotName(srcVolumeDestSnapshotMap.get(volumeId)); - } - defs.add(snapshotDef); - } - final SnapshotDefs snapshotDefs = new SnapshotDefs(); - snapshotDefs.setSnapshotDefs(defs.toArray(new SnapshotDef[0])); - response = post("/instances/System/action/snapshotVolumes", snapshotDefs); - checkResponseOK(response); - ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - return mapper.readValue(response.getEntity().getContent(), SnapshotGroup.class); - } catch (final IOException e) { - LOG.error("Failed to take snapshot due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); + final List defs = new ArrayList<>(); + for (final String volumeId : srcVolumeDestSnapshotMap.keySet()) { + final SnapshotDef snapshotDef = new SnapshotDef(); + snapshotDef.setVolumeId(volumeId); + String snapshotName = srcVolumeDestSnapshotMap.get(volumeId); + if (!Strings.isNullOrEmpty(snapshotName)) { + snapshotDef.setSnapshotName(srcVolumeDestSnapshotMap.get(volumeId)); } + defs.add(snapshotDef); } - return null; + final SnapshotDefs snapshotDefs = new SnapshotDefs(); + snapshotDefs.setSnapshotDefs(defs.toArray(new SnapshotDef[0])); + return post("/instances/System/action/snapshotVolumes", snapshotDefs, SnapshotGroup.class); } @Override @@ -514,22 +558,12 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { Preconditions.checkArgument(!Strings.isNullOrEmpty(systemId), "System id cannot be null"); Preconditions.checkArgument(!Strings.isNullOrEmpty(snapshotGroupId), "Snapshot group id cannot be null"); - HttpResponse response = null; - try { - response = post( - "/instances/System::" + systemId + "/action/removeConsistencyGroupSnapshots", - String.format("{\"snapGroupId\":\"%s\"}", snapshotGroupId)); - checkResponseOK(response); - JsonNode node = new ObjectMapper().readTree(response.getEntity().getContent()); + JsonNode node = post( + "/instances/System::" + systemId + "/action/removeConsistencyGroupSnapshots", + String.format("{\"snapGroupId\":\"%s\"}", snapshotGroupId), JsonNode.class); + if (node != null) { JsonNode noOfVolumesNode = node.get("numberOfVolumes"); return noOfVolumesNode.asInt(); - } catch (final IOException e) { - LOG.error("Failed to delete PowerFlex snapshot group due to: " + e.getMessage(), e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } } return -1; } @@ -539,31 +573,18 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { Preconditions.checkArgument(!Strings.isNullOrEmpty(volumeId), "Volume id cannot be null"); Preconditions.checkArgument(!Strings.isNullOrEmpty(snapshotVolumeName), "Snapshot name cannot be null"); - HttpResponse response = null; - try { - final SnapshotDef[] snapshotDef = new SnapshotDef[1]; - snapshotDef[0] = new SnapshotDef(); - snapshotDef[0].setVolumeId(volumeId); - snapshotDef[0].setSnapshotName(snapshotVolumeName); - final SnapshotDefs snapshotDefs = new SnapshotDefs(); - snapshotDefs.setSnapshotDefs(snapshotDef); + final SnapshotDef[] snapshotDef = new SnapshotDef[1]; + snapshotDef[0] = new SnapshotDef(); + snapshotDef[0].setVolumeId(volumeId); + snapshotDef[0].setSnapshotName(snapshotVolumeName); + final SnapshotDefs snapshotDefs = new SnapshotDefs(); + snapshotDefs.setSnapshotDefs(snapshotDef); - response = post("/instances/System/action/snapshotVolumes", snapshotDefs); - checkResponseOK(response); - ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - SnapshotGroup snapshotGroup = mapper.readValue(response.getEntity().getContent(), SnapshotGroup.class); - if (snapshotGroup != null) { - List volumeIds = snapshotGroup.getVolumeIds(); - if (volumeIds != null && !volumeIds.isEmpty()) { - return getVolume(volumeIds.get(0)); - } - } - } catch (final IOException e) { - LOG.error("Failed to take snapshot due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); + SnapshotGroup snapshotGroup = post("/instances/System/action/snapshotVolumes", snapshotDefs, SnapshotGroup.class); + if (snapshotGroup != null) { + List volumeIds = snapshotGroup.getVolumeIds(); + if (volumeIds != null && !volumeIds.isEmpty()) { + return getVolume(volumeIds.get(0)); } } return null; @@ -574,34 +595,25 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { Preconditions.checkArgument(!Strings.isNullOrEmpty(sourceSnapshotVolumeId), "Source snapshot volume id cannot be null"); Preconditions.checkArgument(!Strings.isNullOrEmpty(destVolumeId), "Destination volume id cannot be null"); - HttpResponse response = null; - try { - Volume sourceSnapshotVolume = getVolume(sourceSnapshotVolumeId); - if (sourceSnapshotVolume == null) { - throw new CloudRuntimeException("Source snapshot volume: " + sourceSnapshotVolumeId + " doesn't exists"); - } + Volume sourceSnapshotVolume = getVolume(sourceSnapshotVolumeId); + if (sourceSnapshotVolume == null) { + throw new CloudRuntimeException("Source snapshot volume: " + sourceSnapshotVolumeId + " doesn't exists"); + } - Volume destVolume = getVolume(destVolumeId); - if (sourceSnapshotVolume == null) { - throw new CloudRuntimeException("Destination volume: " + destVolumeId + " doesn't exists"); - } + Volume destVolume = getVolume(destVolumeId); + if (sourceSnapshotVolume == null) { + throw new CloudRuntimeException("Destination volume: " + destVolumeId + " doesn't exists"); + } - if (!sourceSnapshotVolume.getVtreeId().equals(destVolume.getVtreeId())) { - throw new CloudRuntimeException("Unable to revert, source snapshot volume and destination volume doesn't belong to same volume tree"); - } + if (!sourceSnapshotVolume.getVtreeId().equals(destVolume.getVtreeId())) { + throw new CloudRuntimeException("Unable to revert, source snapshot volume and destination volume doesn't belong to same volume tree"); + } - response = post( - "/instances/Volume::" + destVolumeId + "/action/overwriteVolumeContent", - String.format("{\"srcVolumeId\":\"%s\",\"allowOnExtManagedVol\":\"TRUE\"}", sourceSnapshotVolumeId)); - checkResponseOK(response); - return true; - } catch (final IOException e) { - LOG.error("Failed to map PowerFlex volume due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } + Boolean overwriteVolumeContentStatus = post( + "/instances/Volume::" + destVolumeId + "/action/overwriteVolumeContent", + String.format("{\"srcVolumeId\":\"%s\",\"allowOnExtManagedVol\":\"TRUE\"}", sourceSnapshotVolumeId), Boolean.class); + if (overwriteVolumeContentStatus != null) { + return overwriteVolumeContentStatus; } return false; } @@ -611,24 +623,15 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { Preconditions.checkArgument(!Strings.isNullOrEmpty(volumeId), "Volume id cannot be null"); Preconditions.checkArgument(!Strings.isNullOrEmpty(sdcId), "Sdc Id cannot be null"); - HttpResponse response = null; - try { - if (isVolumeMappedToSdc(volumeId, sdcId)) { - return true; - } - - response = post( - "/instances/Volume::" + volumeId + "/action/addMappedSdc", - String.format("{\"sdcId\":\"%s\",\"allowMultipleMappings\":\"TRUE\"}", sdcId)); - checkResponseOK(response); + if (isVolumeMappedToSdc(volumeId, sdcId)) { return true; - } catch (final IOException e) { - LOG.error("Failed to map PowerFlex volume due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } + } + + Boolean mapVolumeToSdcStatus = post( + "/instances/Volume::" + volumeId + "/action/addMappedSdc", + String.format("{\"sdcId\":\"%s\",\"allowMultipleMappings\":\"TRUE\"}", sdcId), Boolean.class); + if (mapVolumeToSdcStatus != null) { + return mapVolumeToSdcStatus; } return false; } @@ -642,31 +645,22 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { Preconditions.checkArgument(bandwidthLimitInKbps != null && (bandwidthLimitInKbps == 0 || (bandwidthLimitInKbps > 0 && bandwidthLimitInKbps % 1024 == 0)), "Bandwidth limit(Kbps) must be 0 (unlimited) or in granularity of 1024"); - HttpResponse response = null; - try { - if (mapVolumeToSdc(volumeId, sdcId)) { - long iopsLimitVal = 0; - if (iopsLimit != null && iopsLimit.longValue() > 0) { - iopsLimitVal = iopsLimit.longValue(); - } - - long bandwidthLimitInKbpsVal = 0; - if (bandwidthLimitInKbps != null && bandwidthLimitInKbps.longValue() > 0) { - bandwidthLimitInKbpsVal = bandwidthLimitInKbps.longValue(); - } - - response = post( - "/instances/Volume::" + volumeId + "/action/setMappedSdcLimits", - String.format("{\"sdcId\":\"%s\",\"bandwidthLimitInKbps\":\"%d\",\"iopsLimit\":\"%d\"}", sdcId, bandwidthLimitInKbpsVal, iopsLimitVal)); - checkResponseOK(response); - return true; + if (mapVolumeToSdc(volumeId, sdcId)) { + long iopsLimitVal = 0; + if (iopsLimit != null && iopsLimit.longValue() > 0) { + iopsLimitVal = iopsLimit.longValue(); } - } catch (final IOException e) { - LOG.error("Failed to map PowerFlex volume with limits due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); + + long bandwidthLimitInKbpsVal = 0; + if (bandwidthLimitInKbps != null && bandwidthLimitInKbps.longValue() > 0) { + bandwidthLimitInKbpsVal = bandwidthLimitInKbps.longValue(); + } + + Boolean setVolumeSdcLimitsStatus = post( + "/instances/Volume::" + volumeId + "/action/setMappedSdcLimits", + String.format("{\"sdcId\":\"%s\",\"bandwidthLimitInKbps\":\"%d\",\"iopsLimit\":\"%d\"}", sdcId, bandwidthLimitInKbpsVal, iopsLimitVal), Boolean.class); + if (setVolumeSdcLimitsStatus != null) { + return setVolumeSdcLimitsStatus; } } return false; @@ -677,21 +671,12 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { Preconditions.checkArgument(!Strings.isNullOrEmpty(volumeId), "Volume id cannot be null"); Preconditions.checkArgument(!Strings.isNullOrEmpty(sdcId), "Sdc Id cannot be null"); - HttpResponse response = null; - try { - if (isVolumeMappedToSdc(volumeId, sdcId)) { - response = post( - "/instances/Volume::" + volumeId + "/action/removeMappedSdc", - String.format("{\"sdcId\":\"%s\",\"skipApplianceValidation\":\"TRUE\"}", sdcId)); - checkResponseOK(response); - return true; - } - } catch (final IOException e) { - LOG.error("Failed to unmap PowerFlex volume due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); + if (isVolumeMappedToSdc(volumeId, sdcId)) { + Boolean unmapVolumeFromSdcStatus = post( + "/instances/Volume::" + volumeId + "/action/removeMappedSdc", + String.format("{\"sdcId\":\"%s\",\"skipApplianceValidation\":\"TRUE\"}", sdcId), Boolean.class); + if (unmapVolumeFromSdcStatus != null) { + return unmapVolumeFromSdcStatus; } } return false; @@ -701,30 +686,21 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { public boolean unmapVolumeFromAllSdcs(final String volumeId) { Preconditions.checkArgument(!Strings.isNullOrEmpty(volumeId), "Volume id cannot be null"); - HttpResponse response = null; - try { - Volume volume = getVolume(volumeId); - if (volume == null) { - return false; - } + Volume volume = getVolume(volumeId); + if (volume == null) { + return false; + } - List mappedSdcList = volume.getMappedSdcList(); - if (mappedSdcList == null || mappedSdcList.isEmpty()) { - return true; - } - - response = post( - "/instances/Volume::" + volumeId + "/action/removeMappedSdc", - "{\"allSdcs\": \"\"}"); - checkResponseOK(response); + List mappedSdcList = volume.getMappedSdcList(); + if (mappedSdcList == null || mappedSdcList.isEmpty()) { return true; - } catch (final IOException e) { - LOG.error("Failed to unmap PowerFlex volume from all SDCs due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } + } + + Boolean unmapVolumeFromAllSdcsStatus = post( + "/instances/Volume::" + volumeId + "/action/removeMappedSdc", + "{\"allSdcs\": \"\"}", Boolean.class); + if (unmapVolumeFromAllSdcsStatus != null) { + return unmapVolumeFromAllSdcsStatus; } return false; } @@ -759,23 +735,14 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { public boolean deleteVolume(final String volumeId) { Preconditions.checkArgument(!Strings.isNullOrEmpty(volumeId), "Volume id cannot be null"); - HttpResponse response = null; try { - try { - unmapVolumeFromAllSdcs(volumeId); - } catch (Exception ignored) {} - response = post( - "/instances/Volume::" + volumeId + "/action/removeVolume", - "{\"removeMode\":\"ONLY_ME\"}"); - checkResponseOK(response); - return true; - } catch (final IOException e) { - LOG.error("Failed to delete PowerFlex volume due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } + unmapVolumeFromAllSdcs(volumeId); + } catch (Exception ignored) {} + Boolean removeVolumeStatus = post( + "/instances/Volume::" + volumeId + "/action/removeVolume", + "{\"removeMode\":\"ONLY_ME\"}", Boolean.class); + if (removeVolumeStatus != null) { + return removeVolumeStatus; } return false; } @@ -797,21 +764,8 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { LOG.debug("Migrating the volume: " + srcVolumeId + " on the src pool: " + srcPoolId + " to the dest pool: " + destPoolId + " in the same PowerFlex cluster"); - HttpResponse response = null; - try { - response = post( - "/instances/Volume::" + srcVolumeId + "/action/migrateVTree", - String.format("{\"destSPId\":\"%s\"}", destPoolId)); - checkResponseOK(response); - } catch (final IOException e) { - LOG.error("Unable to migrate PowerFlex volume due to: ", e); - checkResponseTimeOut(e); - throw e; - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } - } + post("/instances/Volume::" + srcVolumeId + "/action/migrateVTree", + String.format("{\"destSPId\":\"%s\"}", destPoolId), Boolean.class); LOG.debug("Wait until the migration is complete for the volume: " + srcVolumeId); long migrationStartTime = System.currentTimeMillis(); @@ -899,22 +853,9 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { return null; } - HttpResponse response = null; - try { - response = get("/instances/VTree::" + volumeTreeId); - checkResponseOK(response); - ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - VTree volumeTree = mapper.readValue(response.getEntity().getContent(), VTree.class); - if (volumeTree != null && volumeTree.getVTreeMigrationInfo() != null) { - return volumeTree.getVTreeMigrationInfo().getMigrationStatus(); - } - } catch (final IOException e) { - LOG.error("Failed to migrate PowerFlex volume due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } + VTree volumeTree = get("/instances/VTree::" + volumeTreeId, VTree.class); + if (volumeTree != null && volumeTree.getVTreeMigrationInfo() != null) { + return volumeTree.getVTreeMigrationInfo().getMigrationStatus(); } return null; } @@ -922,53 +863,49 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { private boolean rollbackVolumeMigration(final String srcVolumeId) { Preconditions.checkArgument(!Strings.isNullOrEmpty(srcVolumeId), "src volume id cannot be null"); - HttpResponse response = null; - try { - Volume volume = getVolume(srcVolumeId); - VTreeMigrationInfo.MigrationStatus migrationStatus = getVolumeTreeMigrationStatus(volume.getVtreeId()); - if (migrationStatus != null && migrationStatus == VTreeMigrationInfo.MigrationStatus.NotInMigration) { - LOG.debug("Volume: " + srcVolumeId + " is not migrating, no need to rollback"); - return true; - } + Volume volume = getVolume(srcVolumeId); + if (volume == null) { + LOG.warn("Unable to rollback volume migration, couldn't get details for the volume: " + srcVolumeId); + return false; + } - pauseVolumeMigration(srcVolumeId, true); // Pause forcefully - // Wait few secs for volume migration to change to Paused state - boolean paused = false; - int retryCount = 3; - while (retryCount > 0) { - try { - Thread.sleep(3000); // Try after few secs - migrationStatus = getVolumeTreeMigrationStatus(volume.getVtreeId()); // Get updated migration status - if (migrationStatus != null && migrationStatus == VTreeMigrationInfo.MigrationStatus.Paused) { - LOG.debug("Migration for the volume: " + srcVolumeId + " paused"); - paused = true; - break; - } - } catch (Exception ex) { - LOG.warn("Exception while checking for migration pause status of the volume: " + srcVolumeId + " - " + ex.getLocalizedMessage()); - // don't do anything - } finally { - retryCount--; + VTreeMigrationInfo.MigrationStatus migrationStatus = getVolumeTreeMigrationStatus(volume.getVtreeId()); + if (migrationStatus != null && migrationStatus == VTreeMigrationInfo.MigrationStatus.NotInMigration) { + LOG.debug("Volume: " + srcVolumeId + " is not migrating, no need to rollback"); + return true; + } + + pauseVolumeMigration(srcVolumeId, true); // Pause forcefully + // Wait few secs for volume migration to change to Paused state + boolean paused = false; + int retryCount = 3; + while (retryCount > 0) { + try { + Thread.sleep(3000); // Try after few secs + migrationStatus = getVolumeTreeMigrationStatus(volume.getVtreeId()); // Get updated migration status + if (migrationStatus != null && migrationStatus == VTreeMigrationInfo.MigrationStatus.Paused) { + LOG.debug("Migration for the volume: " + srcVolumeId + " paused"); + paused = true; + break; } + } catch (Exception ex) { + LOG.warn("Exception while checking for migration pause status of the volume: " + srcVolumeId + " - " + ex.getLocalizedMessage()); + // don't do anything + } finally { + retryCount--; } + } - if (paused) { - // Rollback migration to the src pool (should be quick) - response = post( - "/instances/Volume::" + srcVolumeId + "/action/migrateVTree", - String.format("{\"destSPId\":\"%s\"}", volume.getStoragePoolId())); - checkResponseOK(response); - return true; - } else { - LOG.warn("Migration for the volume: " + srcVolumeId + " didn't pause, couldn't rollback"); - } - } catch (final IOException e) { - LOG.error("Failed to rollback volume migration due to: ", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); + if (paused) { + // Rollback migration to the src pool (should be quick) + Boolean migrateVTreeStatus = post( + "/instances/Volume::" + srcVolumeId + "/action/migrateVTree", + String.format("{\"destSPId\":\"%s\"}", volume.getStoragePoolId()), Boolean.class); + if (migrateVTreeStatus != null) { + return migrateVTreeStatus; } + } else { + LOG.warn("Migration for the volume: " + srcVolumeId + " didn't pause, couldn't rollback"); } return false; } @@ -979,23 +916,14 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { return false; } - HttpResponse response = null; - try { - // When paused gracefully, all data currently being moved is allowed to complete the migration. - // When paused forcefully, migration of unfinished data is aborted and data is left at the source, if possible. - // Pausing forcefully carries a potential risk to data. - response = post( - "/instances/Volume::" + volumeId + "/action/pauseVTreeMigration", - String.format("{\"pauseType\":\"%s\"}", forced ? "Forcefully" : "Gracefully")); - checkResponseOK(response); - return true; - } catch (final IOException e) { - LOG.error("Failed to pause migration of the volume due to: ", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } + // When paused gracefully, all data currently being moved is allowed to complete the migration. + // When paused forcefully, migration of unfinished data is aborted and data is left at the source, if possible. + // Pausing forcefully carries a potential risk to data. + Boolean pauseVTreeMigrationStatus = post( + "/instances/Volume::" + volumeId + "/action/pauseVTreeMigration", + String.format("{\"pauseType\":\"%s\"}", forced ? "Forcefully" : "Gracefully"), Boolean.class); + if (pauseVTreeMigrationStatus != null) { + return pauseVTreeMigrationStatus; } return false; } @@ -1006,20 +934,9 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { @Override public List listStoragePools() { - HttpResponse response = null; - try { - response = get("/types/StoragePool/instances"); - checkResponseOK(response); - ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - StoragePool[] pools = mapper.readValue(response.getEntity().getContent(), StoragePool[].class); + StoragePool[] pools = get("/types/StoragePool/instances", StoragePool[].class); + if (pools != null) { return Arrays.asList(pools); - } catch (final IOException e) { - LOG.error("Failed to list PowerFlex storage pools due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } } return new ArrayList<>(); } @@ -1027,72 +944,29 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { @Override public StoragePool getStoragePool(String poolId) { Preconditions.checkArgument(!Strings.isNullOrEmpty(poolId), "Storage pool id cannot be null"); - - HttpResponse response = null; - try { - response = get("/instances/StoragePool::" + poolId); - checkResponseOK(response); - ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - return mapper.readValue(response.getEntity().getContent(), StoragePool.class); - } catch (final IOException e) { - LOG.error("Failed to get storage pool due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } - } - return null; + return get("/instances/StoragePool::" + poolId, StoragePool.class); } @Override public StoragePoolStatistics getStoragePoolStatistics(String poolId) { Preconditions.checkArgument(!Strings.isNullOrEmpty(poolId), "Storage pool id cannot be null"); - - HttpResponse response = null; - try { - response = get("/instances/StoragePool::" + poolId + "/relationships/Statistics"); - checkResponseOK(response); - ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - return mapper.readValue(response.getEntity().getContent(), StoragePoolStatistics.class); - } catch (final IOException e) { - LOG.error("Failed to get storage pool due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } - } - return null; + return get("/instances/StoragePool::" + poolId + "/relationships/Statistics", StoragePoolStatistics.class); } @Override public VolumeStatistics getVolumeStatistics(String volumeId) { Preconditions.checkArgument(!Strings.isNullOrEmpty(volumeId), "Volume id cannot be null"); - HttpResponse response = null; - try { - Volume volume = getVolume(volumeId); - if (volume != null) { - String volumeTreeId = volume.getVtreeId(); - if (!Strings.isNullOrEmpty(volumeTreeId)) { - response = get("/instances/VTree::" + volumeTreeId + "/relationships/Statistics"); - checkResponseOK(response); - ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - VolumeStatistics volumeStatistics = mapper.readValue(response.getEntity().getContent(), VolumeStatistics.class); - if (volumeStatistics != null) { - volumeStatistics.setAllocatedSizeInKb(volume.getSizeInKb()); - return volumeStatistics; - } + Volume volume = getVolume(volumeId); + if (volume != null) { + String volumeTreeId = volume.getVtreeId(); + if (!Strings.isNullOrEmpty(volumeTreeId)) { + VolumeStatistics volumeStatistics = get("/instances/VTree::" + volumeTreeId + "/relationships/Statistics", VolumeStatistics.class); + if (volumeStatistics != null) { + volumeStatistics.setAllocatedSizeInKb(volume.getSizeInKb()); + return volumeStatistics; } } - } catch (final IOException e) { - LOG.error("Failed to get volume stats due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } } return null; @@ -1102,22 +976,9 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { public String getSystemId(String protectionDomainId) { Preconditions.checkArgument(!Strings.isNullOrEmpty(protectionDomainId), "Protection domain id cannot be null"); - HttpResponse response = null; - try { - response = get("/instances/ProtectionDomain::" + protectionDomainId); - checkResponseOK(response); - ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - ProtectionDomain protectionDomain = mapper.readValue(response.getEntity().getContent(), ProtectionDomain.class); - if (protectionDomain != null) { - return protectionDomain.getSystemId(); - } - } catch (final IOException e) { - LOG.error("Failed to get protection domain details due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } + ProtectionDomain protectionDomain = get("/instances/ProtectionDomain::" + protectionDomainId, ProtectionDomain.class); + if (protectionDomain != null) { + return protectionDomain.getSystemId(); } return null; } @@ -1126,20 +987,9 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { public List listVolumesInStoragePool(String poolId) { Preconditions.checkArgument(!Strings.isNullOrEmpty(poolId), "Storage pool id cannot be null"); - HttpResponse response = null; - try { - response = get("/instances/StoragePool::" + poolId + "/relationships/Volume"); - checkResponseOK(response); - ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - Volume[] volumes = mapper.readValue(response.getEntity().getContent(), Volume[].class); + Volume[] volumes = get("/instances/StoragePool::" + poolId + "/relationships/Volume", Volume[].class); + if (volumes != null) { return Arrays.asList(volumes); - } catch (final IOException e) { - LOG.error("Failed to list volumes in storage pool due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } } return new ArrayList<>(); } @@ -1150,20 +1000,9 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { @Override public List listSdcs() { - HttpResponse response = null; - try { - response = get("/types/Sdc/instances"); - checkResponseOK(response); - ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - Sdc[] sdcs = mapper.readValue(response.getEntity().getContent(), Sdc[].class); + Sdc[] sdcs = get("/types/Sdc/instances", Sdc[].class); + if (sdcs != null) { return Arrays.asList(sdcs); - } catch (final IOException e) { - LOG.error("Failed to list SDCs due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } } return new ArrayList<>(); } @@ -1171,43 +1010,16 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { @Override public Sdc getSdc(String sdcId) { Preconditions.checkArgument(!Strings.isNullOrEmpty(sdcId), "Sdc id cannot be null"); - - HttpResponse response = null; - try { - response = get("/instances/Sdc::" + sdcId); - checkResponseOK(response); - ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - return mapper.readValue(response.getEntity().getContent(), Sdc.class); - } catch (final IOException e) { - LOG.error("Failed to get volume due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } - } - return null; + return get("/instances/Sdc::" + sdcId, Sdc.class); } @Override public Sdc getSdcByIp(String ipAddress) { Preconditions.checkArgument(!Strings.isNullOrEmpty(ipAddress), "IP address cannot be null"); - HttpResponse response = null; - try { - response = post("/types/Sdc/instances/action/queryIdByKey", String.format("{\"ip\":\"%s\"}", ipAddress)); - checkResponseOK(response); - String sdcId = EntityUtils.toString(response.getEntity()); - if (!Strings.isNullOrEmpty(sdcId)) { - return getSdc(sdcId.replace("\"", "")); - } - } catch (final IOException e) { - LOG.error("Failed to get volume due to:", e); - checkResponseTimeOut(e); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } + String sdcId = post("/types/Sdc/instances/action/queryIdByKey", String.format("{\"ip\":\"%s\"}", ipAddress), String.class); + if (!Strings.isNullOrEmpty(sdcId)) { + return getSdc(sdcId.replace("\"", "")); } return null; } @@ -1252,4 +1064,27 @@ public class ScaleIOGatewayClientImpl implements ScaleIOGatewayClient { return false; } + + private String getConnectionManagerStats() { + StringBuilder sb = new StringBuilder(); + sb.append("\n").append("Client Connection Manager Stats => "); + if (connectionManager != null) { + sb.append("MaxTotal: ").append(connectionManager.getMaxTotal()).append(", "); + sb.append("DefaultMaxPerRoute: ").append(connectionManager.getDefaultMaxPerRoute()); + + PoolStats poolStats = connectionManager.getTotalStats(); + if (poolStats != null) { + sb.append(", "); + sb.append("Available: ").append(poolStats.getAvailable()).append(", "); + sb.append("Leased: ").append(poolStats.getLeased()).append(", "); + sb.append("Max: ").append(poolStats.getMax()).append(", "); + sb.append("Pending: ").append(poolStats.getPending()); + } + } else { + sb.append("NULL"); + } + + sb.append("\n"); + return sb.toString(); + } } diff --git a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java index 5c9ddea4752..edebdac7929 100644 --- a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java +++ b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java @@ -106,7 +106,8 @@ public class ScaleIOPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCyc private org.apache.cloudstack.storage.datastore.api.StoragePool findStoragePool(String url, String username, String password, String storagePoolName) { try { final int clientTimeout = StorageManager.STORAGE_POOL_CLIENT_TIMEOUT.value(); - ScaleIOGatewayClient client = ScaleIOGatewayClient.getClient(url, username, password, false, clientTimeout); + final int clientMaxConnections = StorageManager.STORAGE_POOL_CLIENT_MAX_CONNECTIONS.value(); + ScaleIOGatewayClient client = ScaleIOGatewayClient.getClient(url, username, password, false, clientTimeout, clientMaxConnections); List storagePools = client.listStoragePools(); for (org.apache.cloudstack.storage.datastore.api.StoragePool pool : storagePools) { if (pool.getName().equals(storagePoolName)) { @@ -121,9 +122,9 @@ public class ScaleIOPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCyc } } catch (NoSuchAlgorithmException | KeyManagementException | URISyntaxException e) { LOGGER.error("Failed to add storage pool", e); - throw new CloudRuntimeException("Failed to establish connection with PowerFlex Gateway to validate storage pool"); + throw new CloudRuntimeException("Failed to establish connection with PowerFlex Gateway to find and validate storage pool: " + storagePoolName); } - throw new CloudRuntimeException("Failed to find the provided storage pool name in discovered PowerFlex storage pools"); + throw new CloudRuntimeException("Failed to find the provided storage pool name: " + storagePoolName + " in the discovered PowerFlex storage pools"); } @SuppressWarnings("unchecked") diff --git a/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientImplTest.java b/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientImplTest.java index 10823102cf8..80a78c80909 100644 --- a/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientImplTest.java +++ b/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/client/ScaleIOGatewayClientImplTest.java @@ -19,30 +19,179 @@ package org.apache.cloudstack.storage.datastore.client; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.containing; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.unauthorized; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; + +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.storage.datastore.api.Volume; import org.junit.After; +import org.junit.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; +import com.cloud.storage.Storage; import com.cloud.utils.exception.CloudRuntimeException; +import com.github.tomakehurst.wiremock.client.BasicCredentials; +import com.github.tomakehurst.wiremock.junit.WireMockRule; @RunWith(MockitoJUnitRunner.class) public class ScaleIOGatewayClientImplTest { + private final int port = 443; + private final int timeout = 30; + private final int maxConnections = 50; + private final String username = "admin"; + private final String password = "P@ssword123"; + private final String sessionKey = "YWRtaW46MTYyMzM0OTc4NDk0MTo2MWQ2NGQzZWJhMTVmYTVkNDIwNjZmOWMwZDg0ZGZmOQ"; + private ScaleIOGatewayClient client = null; - ScaleIOGatewayClientImpl client; + @Rule + public WireMockRule wireMockRule = new WireMockRule(wireMockConfig() + .httpsPort(port) + .needClientAuth(false) + .basicAdminAuthenticator(username, password) + .bindAddress("localhost")); @Before public void setUp() throws Exception { + wireMockRule.stubFor(get("/api/login") + .willReturn(ok() + .withHeader("Content-Type", "application/json;charset=UTF-8") + .withBody(sessionKey))); + + client = new ScaleIOGatewayClientImpl("https://localhost/api", username, password, false, timeout, maxConnections); + + wireMockRule.stubFor(post("/api/types/Volume/instances") + .willReturn(aResponse() + .withHeader("Content-Type", "application/json;charset=UTF-8") + .withStatus(200) + .withBody("{\"id\":\"c948d0b10000000a\"}"))); + + wireMockRule.stubFor(get("/api/instances/Volume::c948d0b10000000a") + .willReturn(aResponse() + .withHeader("Content-Type", "application/json;charset=UTF-8") + .withStatus(200) + .withBody("{\"storagePoolId\":\"4daaa55e00000000\",\"dataLayout\":\"MediumGranularity\",\"vtreeId\":\"657e289500000009\"," + + "\"sizeInKb\":8388608,\"snplIdOfAutoSnapshot\":null,\"volumeType\":\"ThinProvisioned\",\"consistencyGroupId\":null," + + "\"ancestorVolumeId\":null,\"notGenuineSnapshot\":false,\"accessModeLimit\":\"ReadWrite\",\"secureSnapshotExpTime\":0," + + "\"useRmcache\":false,\"managedBy\":\"ScaleIO\",\"lockedAutoSnapshot\":false,\"lockedAutoSnapshotMarkedForRemoval\":false," + + "\"autoSnapshotGroupId\":null,\"compressionMethod\":\"Invalid\",\"pairIds\":null,\"timeStampIsAccurate\":false,\"mappedSdcInfo\":null," + + "\"retentionLevels\":[],\"snplIdOfSourceVolume\":null,\"volumeReplicationState\":\"UnmarkedForReplication\",\"replicationJournalVolume\":false," + + "\"replicationTimeStamp\":0,\"originalExpiryTime\":0,\"creationTime\":1623335880,\"name\":\"testvolume\",\"id\":\"c948d0b10000000a\"}"))); } @After public void tearDown() throws Exception { } + @Test + public void testClientAuthSuccess() { + Assert.assertNotNull(client); + wireMockRule.verify(getRequestedFor(urlEqualTo("/api/login")) + .withBasicAuth(new BasicCredentials(username, password))); + + wireMockRule.stubFor(get("/api/types/StoragePool/instances") + .willReturn(aResponse() + .withHeader("Content-Type", "application/json;charset=UTF-8") + .withStatus(200) + .withBody(""))); + + client.listStoragePools(); + + wireMockRule.verify(getRequestedFor(urlEqualTo("/api/types/StoragePool/instances")) + .withBasicAuth(new BasicCredentials(username, sessionKey))); + } + @Test(expected = CloudRuntimeException.class) - public void testClient() throws Exception { - client = (ScaleIOGatewayClientImpl) ScaleIOGatewayClient.getClient("https://10.2.3.149/api", - "admin", "P@ssword123", false, 60); + public void testClientAuthFailure() throws Exception { + wireMockRule.stubFor(get("/api/login") + .willReturn(unauthorized() + .withHeader("Content-Type", "application/json;charset=UTF-8") + .withBody(""))); + + new ScaleIOGatewayClientImpl("https://localhost/api", username, password, false, timeout, maxConnections); + } + + @Test(expected = ServerApiException.class) + public void testRequestTimeout() { + Assert.assertNotNull(client); + wireMockRule.verify(getRequestedFor(urlEqualTo("/api/login")) + .withBasicAuth(new BasicCredentials(username, password))); + + wireMockRule.stubFor(get("/api/types/StoragePool/instances") + .willReturn(aResponse() + .withHeader("Content-Type", "application/json;charset=UTF-8") + .withStatus(200) + .withFixedDelay(2 * timeout * 1000) + .withBody(""))); + + client.listStoragePools(); + } + + @Test + public void testCreateSingleVolume() { + Assert.assertNotNull(client); + wireMockRule.verify(getRequestedFor(urlEqualTo("/api/login")) + .withBasicAuth(new BasicCredentials(username, password))); + + final String volumeName = "testvolume"; + final String scaleIOStoragePoolId = "4daaa55e00000000"; + final int sizeInGb = 8; + Volume scaleIOVolume = client.createVolume(volumeName, scaleIOStoragePoolId, sizeInGb, Storage.ProvisioningType.THIN); + + wireMockRule.verify(postRequestedFor(urlEqualTo("/api/types/Volume/instances")) + .withBasicAuth(new BasicCredentials(username, sessionKey)) + .withRequestBody(containing("\"name\":\"" + volumeName + "\"")) + .withHeader("Content-Type", equalTo("application/json"))); + wireMockRule.verify(getRequestedFor(urlEqualTo("/api/instances/Volume::c948d0b10000000a")) + .withBasicAuth(new BasicCredentials(username, sessionKey))); + + Assert.assertNotNull(scaleIOVolume); + Assert.assertEquals(scaleIOVolume.getId(), "c948d0b10000000a"); + Assert.assertEquals(scaleIOVolume.getName(), volumeName); + Assert.assertEquals(scaleIOVolume.getStoragePoolId(), scaleIOStoragePoolId); + Assert.assertEquals(scaleIOVolume.getSizeInKb(), Long.valueOf(sizeInGb * 1024 * 1024)); + Assert.assertEquals(scaleIOVolume.getVolumeType(), Volume.VolumeType.ThinProvisioned); + } + + @Test + public void testCreateMultipleVolumes() { + Assert.assertNotNull(client); + wireMockRule.verify(getRequestedFor(urlEqualTo("/api/login")) + .withBasicAuth(new BasicCredentials(username, password))); + + final String volumeNamePrefix = "testvolume_"; + final String scaleIOStoragePoolId = "4daaa55e00000000"; + final int sizeInGb = 8; + final int volumesCount = 1000; + + for (int i = 1; i <= volumesCount; i++) { + String volumeName = volumeNamePrefix + i; + Volume scaleIOVolume = client.createVolume(volumeName, scaleIOStoragePoolId, sizeInGb, Storage.ProvisioningType.THIN); + + Assert.assertNotNull(scaleIOVolume); + Assert.assertEquals(scaleIOVolume.getId(), "c948d0b10000000a"); + Assert.assertEquals(scaleIOVolume.getStoragePoolId(), scaleIOStoragePoolId); + Assert.assertEquals(scaleIOVolume.getSizeInKb(), Long.valueOf(sizeInGb * 1024 * 1024)); + Assert.assertEquals(scaleIOVolume.getVolumeType(), Volume.VolumeType.ThinProvisioned); + } + + wireMockRule.verify(volumesCount, postRequestedFor(urlEqualTo("/api/types/Volume/instances")) + .withBasicAuth(new BasicCredentials(username, sessionKey)) + .withRequestBody(containing("\"name\":\"" + volumeNamePrefix)) + .withHeader("Content-Type", equalTo("application/json"))); + wireMockRule.verify(volumesCount, getRequestedFor(urlEqualTo("/api/instances/Volume::c948d0b10000000a")) + .withBasicAuth(new BasicCredentials(username, sessionKey))); } } \ No newline at end of file diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index cb7e4b5185d..f431945c632 100755 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -474,6 +474,9 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati configValuesForValidation.add("externaldhcp.vmip.max.retry"); configValuesForValidation.add("externaldhcp.vmipFetch.threadPool.max"); configValuesForValidation.add("remote.access.vpn.psk.length"); + configValuesForValidation.add(StorageManager.STORAGE_POOL_DISK_WAIT.key()); + configValuesForValidation.add(StorageManager.STORAGE_POOL_CLIENT_TIMEOUT.key()); + configValuesForValidation.add(StorageManager.STORAGE_POOL_CLIENT_MAX_CONNECTIONS.key()); } private void weightBasedParametersForValidation() { diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java index 5e7be308323..9e6cb3fc040 100644 --- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java @@ -3130,6 +3130,7 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C MaxNumberOfManagedClusteredFileSystems, STORAGE_POOL_DISK_WAIT, STORAGE_POOL_CLIENT_TIMEOUT, + STORAGE_POOL_CLIENT_MAX_CONNECTIONS, PRIMARY_STORAGE_DOWNLOAD_WAIT, SecStorageMaxMigrateSessions, MaxDataMigrationWaitTime diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index a2b2a4978c0..bdad40fc90f 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -1485,6 +1485,13 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } } + private void removeVolume(long volumeId) { + final VolumeVO volume = _volsDao.findById(volumeId); + if (volume != null) { + _volsDao.remove(volumeId); + } + } + protected boolean stateTransitTo(Volume vol, Volume.Event event) throws NoTransitionException { return _volStateMachine.transitTo(vol, event, null, _volsDao); } @@ -1526,6 +1533,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } } + removeVolume(volume.getId()); return volume; } @@ -1621,6 +1629,9 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic if (destPrimaryStorage != null && (volumeToAttach.getState() == Volume.State.Allocated || volumeOnSecondary)) { try { + if (volumeOnSecondary && destPrimaryStorage.getPoolType() == Storage.StoragePoolType.PowerFlex) { + throw new InvalidParameterValueException("Cannot attach uploaded volume, this operation is unsupported on storage pool type " + destPrimaryStorage.getPoolType()); + } newVolumeOnPrimaryStorage = _volumeMgr.createVolumeOnPrimaryStorage(vm, volumeToAttach, rootDiskHyperType, destPrimaryStorage); } catch (NoTransitionException e) { s_logger.debug("Failed to create volume on primary storage", e);