blob: a1084bf9a40d23e5249be8092563a836b3e3d3f8 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.storage.datastore.util;
import com.cloud.host.Host;
import com.cloud.host.HostVO;
import com.cloud.utils.exception.CloudRuntimeException;
import com.google.common.base.Preconditions;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.StringTokenizer;
import java.util.UUID;
public class DateraUtil {
private static final Logger s_logger = Logger.getLogger(DateraUtil.class);
private static final String API_VERSION = "v2";
public static final String PROVIDER_NAME = "Datera";
public static final String DRIVER_VERSION = "4.13.0-v2.0.0";
private static final String HEADER_AUTH_TOKEN = "auth-token";
private static final String HEADER_CONTENT_TYPE = "content-type";
private static final String HEADER_VALUE_JSON = "application/json";
public static final String MANAGEMENT_VIP = "mVip";
public static final String STORAGE_VIP = "sVip";
public static final String MANAGEMENT_PORT = "mPort";
public static final String STORAGE_PORT = "sPort";
public static final String DEFAULT_IP_POOL = "default";
private static final int DEFAULT_MANAGEMENT_PORT = 7717;
private static final int DEFAULT_STORAGE_PORT = 3260;
private static final int DEFAULT_NUM_REPLICAS = 3;
private static final long ONEGIB_BYTES = 1073741824;
private static final String DEFAULT_VOL_PLACEMENT = "hybrid";
public static final String CLUSTER_ADMIN_USERNAME = "clusterAdminUsername";
public static final String CLUSTER_ADMIN_PASSWORD = "clusterAdminPassword";
public static final String CLUSTER_DEFAULT_MIN_IOPS = "clusterDefaultMinIops";
public static final String CLUSTER_DEFAULT_MAX_IOPS = "clusterDefaultMaxIops";
public static final String NUM_REPLICAS = "numReplicas";
public static final String VOL_PLACEMENT = "volPlacement";
public static final String STORAGE_POOL_ID = "DateraStoragePoolId";
public static final String VOLUME_SIZE = "DateraVolumeSize";
public static final String VOLUME_ID = "DateraVolumeId";
public static final String SNAPSHOT_ID = "DateraSnapshotId";
public static final String TEMP_VOLUME_ID = "tempVolumeId";
public static final String IP_POOL = "ipPool";
public static final int MAX_IOPS = 10000; // max IOPS that can be assigned to a volume
public static final String INITIATOR_GROUP_PREFIX = "CS-InitiatorGroup";
public static final String INITIATOR_PREFIX = "CS-Initiator";
public static final String APPINSTANCE_PREFIX = "CS";
public static final int APPINSTANCE_MAX_LENTH = 64;
public static final int MIN_NUM_REPLICAS = 1;
public static final int MAX_NUM_REPLICAS = 5;
public static final int POLL_TIMEOUT_MS = 3000;
public static final String STATE_AVAILABLE = "available";
public static final int DEFAULT_RETRIES = 10;
private static Gson gson = new GsonBuilder().create();
private int managementPort;
private String managementIp;
private String username;
private String password;
private static final String SCHEME_HTTP = "http";
private static final int UUID_LENGTH = 8;
public DateraUtil(String managementIp, int managementPort, String username, String password) {
this.managementPort = managementPort;
this.managementIp = managementIp;
this.username = username;
this.password = password;
}
public static String login(DateraObject.DateraConnection conn)
throws UnsupportedEncodingException, DateraObject.DateraError {
DateraObject.DateraLogin loginParams = new DateraObject.DateraLogin(conn.getUsername(), conn.getPassword());
HttpPut loginReq = new HttpPut(generateApiUrl("login"));
StringEntity jsonParams = new StringEntity(gson.toJson(loginParams));
loginReq.setEntity(jsonParams);
String response = executeHttp(conn, loginReq);
DateraObject.DateraLoginResponse loginResponse = gson.fromJson(response,
DateraObject.DateraLoginResponse.class);
return loginResponse.getKey();
}
public static Map<String, DateraObject.AppInstance> getAppInstances(DateraObject.DateraConnection conn)
throws DateraObject.DateraError {
HttpGet getAppInstancesReq = new HttpGet(generateApiUrl("app_instances"));
String response = null;
response = executeApiRequest(conn, getAppInstancesReq);
Type responseType = new TypeToken<Map<String, DateraObject.AppInstance>>() {
}.getType();
return gson.fromJson(response, responseType);
}
public static DateraObject.AppInstance getAppInstance(DateraObject.DateraConnection conn, String name)
throws DateraObject.DateraError {
HttpGet url = new HttpGet(generateApiUrl("app_instances", name));
String response = null;
try {
response = executeApiRequest(conn, url);
return gson.fromJson(response, DateraObject.AppInstance.class);
} catch (DateraObject.DateraError dateraError) {
if (DateraObject.DateraErrorTypes.NotFoundError.equals(dateraError)) {
return null;
} else {
throw dateraError;
}
}
}
public static DateraObject.PerformancePolicy getAppInstancePerformancePolicy(DateraObject.DateraConnection conn,
String appInstanceName) throws DateraObject.DateraError {
HttpGet url = new HttpGet(generateApiUrl("app_instances", appInstanceName, "storage_instances",
DateraObject.DEFAULT_STORAGE_NAME, "volumes", DateraObject.DEFAULT_VOLUME_NAME, "performance_policy"));
try {
String response = executeApiRequest(conn, url);
return gson.fromJson(response, DateraObject.PerformancePolicy.class);
} catch (DateraObject.DateraError dateraError) {
if (DateraObject.DateraErrorTypes.NotFoundError.equals(dateraError)) {
return null;
} else {
throw dateraError;
}
}
}
public static DateraObject.PerformancePolicy createAppInstancePerformancePolicy(DateraObject.DateraConnection conn,
String appInstanceName, int totalIops) throws UnsupportedEncodingException, DateraObject.DateraError {
HttpPost url = new HttpPost(generateApiUrl("app_instances", appInstanceName, "storage_instances",
DateraObject.DEFAULT_STORAGE_NAME, "volumes", DateraObject.DEFAULT_VOLUME_NAME, "performance_policy"));
DateraObject.PerformancePolicy performancePolicy = new DateraObject.PerformancePolicy(totalIops);
url.setEntity(new StringEntity(gson.toJson(performancePolicy)));
String response = executeApiRequest(conn, url);
return gson.fromJson(response, DateraObject.PerformancePolicy.class);
}
public static void updateAppInstanceIops(DateraObject.DateraConnection conn, String appInstance, int totalIops)
throws UnsupportedEncodingException, DateraObject.DateraError {
if (getAppInstancePerformancePolicy(conn, appInstance) == null) {
createAppInstancePerformancePolicy(conn, appInstance, totalIops);
} else {
HttpPut url = new HttpPut(
generateApiUrl("app_instances", appInstance, "storage_instances", DateraObject.DEFAULT_STORAGE_NAME,
"volumes", DateraObject.DEFAULT_VOLUME_NAME, "performance_policy"));
DateraObject.PerformancePolicy performancePolicy = new DateraObject.PerformancePolicy(totalIops);
url.setEntity(new StringEntity(gson.toJson(performancePolicy)));
executeApiRequest(conn, url);
}
}
public static void updateAppInstanceSize(DateraObject.DateraConnection conn, String appInstanceName, int newSize)
throws UnsupportedEncodingException, DateraObject.DateraError {
HttpPut url = new HttpPut(generateApiUrl("app_instances", appInstanceName, "storage_instances",
DateraObject.DEFAULT_STORAGE_NAME, "volumes", DateraObject.DEFAULT_VOLUME_NAME));
DateraObject.Volume volume = new DateraObject.Volume(newSize);
url.setEntity(new StringEntity(gson.toJson(volume)));
executeApiRequest(conn, url);
}
public static void updateAppInstancePlacement(DateraObject.DateraConnection conn, String appInstanceName,
String newPlacementMode) throws UnsupportedEncodingException, DateraObject.DateraError {
HttpPut url = new HttpPut(generateApiUrl("app_instances", appInstanceName, "storage_instances",
DateraObject.DEFAULT_STORAGE_NAME, "volumes", DateraObject.DEFAULT_VOLUME_NAME));
DateraObject.Volume volume = new DateraObject.Volume(newPlacementMode);
url.setEntity(new StringEntity(gson.toJson(volume)));
executeApiRequest(conn, url);
}
private static DateraObject.AppInstance createAppInstance(DateraObject.DateraConnection conn, String name,
StringEntity appInstanceEntity) throws DateraObject.DateraError {
HttpPost createAppInstance = new HttpPost(generateApiUrl("app_instances"));
HttpGet getAppInstance = new HttpGet(generateApiUrl("app_instances", name));
createAppInstance.setEntity(appInstanceEntity);
String response = null;
executeApiRequest(conn, createAppInstance);
// create is async, do a get to fetch the IQN
executeApiRequest(conn, getAppInstance);
return pollAppInstanceAvailable(conn, name);
}
public static DateraObject.AppInstance createAppInstance(DateraObject.DateraConnection conn, String name, int size,
int totalIops, int replicaCount) throws UnsupportedEncodingException, DateraObject.DateraError {
DateraObject.AppInstance appInstance = new DateraObject.AppInstance(name, size, totalIops, replicaCount,
DEFAULT_VOL_PLACEMENT, DEFAULT_IP_POOL);
StringEntity appInstanceEntity = new StringEntity(gson.toJson(appInstance));
return createAppInstance(conn, name, appInstanceEntity);
}
public static DateraObject.AppInstance createAppInstance(DateraObject.DateraConnection conn, String name, int size,
int totalIops, int replicaCount, String placementMode, String ipPool)
throws UnsupportedEncodingException, DateraObject.DateraError {
DateraObject.AppInstance appInstance = new DateraObject.AppInstance(name, size, totalIops, replicaCount,
placementMode, ipPool);
StringEntity appInstanceEntity = new StringEntity(gson.toJson(appInstance));
return createAppInstance(conn, name, appInstanceEntity);
}
public static DateraObject.AppInstance cloneAppInstanceFromVolume(DateraObject.DateraConnection conn, String name,
String srcCloneName) throws UnsupportedEncodingException, DateraObject.DateraError {
return null;
}
public static DateraObject.AppInstance cloneAppInstanceFromVolume(DateraObject.DateraConnection conn, String name,
String srcCloneName, String ipPool) throws UnsupportedEncodingException, DateraObject.DateraError {
s_logger.debug("cloneAppInstanceFromVolume() called");
DateraObject.AppInstance srcAppInstance = getAppInstance(conn, srcCloneName);
if (srcAppInstance == null) {
throw new DateraObject.DateraError("NotFoundError", 404, null,
"Unable to find the base app instance to clone from");
}
String srcClonePath = srcAppInstance.getVolumePath();
DateraObject.AppInstance appInstanceObj = new DateraObject.AppInstance(name, srcClonePath);
StringEntity appInstanceEntity = new StringEntity(gson.toJson(appInstanceObj));
DateraObject.AppInstance appInstance = createAppInstance(conn, name, appInstanceEntity);
// Update ipPool
updateAppInstanceIpPool(conn, name, ipPool);
// bring it online
updateAppInstanceAdminState(conn, name, DateraObject.AppState.ONLINE);
return getAppInstance(conn, name);
}
public static DateraObject.AppInstance pollAppInstanceAvailable(DateraObject.DateraConnection conn,
String appInstanceName) throws DateraObject.DateraError {
int retries = DateraUtil.DEFAULT_RETRIES;
DateraObject.AppInstance appInstance = null;
do {
appInstance = getAppInstance(conn, appInstanceName);
try {
Thread.sleep(DateraUtil.POLL_TIMEOUT_MS);
} catch (InterruptedException e) {
return null;
}
retries--;
} while ((appInstance != null && !Objects.equals(appInstance.getVolumeOpState(), DateraUtil.STATE_AVAILABLE))
&& retries > 0);
return appInstance;
}
public static DateraObject.Initiator createInitiator(DateraObject.DateraConnection conn, String name, String iqn)
throws DateraObject.DateraError, UnsupportedEncodingException {
HttpPost req = new HttpPost(generateApiUrl("initiators"));
DateraObject.Initiator initiator = new DateraObject.Initiator(name, iqn);
StringEntity httpEntity = new StringEntity(gson.toJson(initiator));
req.setEntity(httpEntity);
return gson.fromJson(executeApiRequest(conn, req), DateraObject.Initiator.class);
}
public static DateraObject.Initiator getInitiator(DateraObject.DateraConnection conn, String iqn)
throws DateraObject.DateraError {
try {
HttpGet getReq = new HttpGet(generateApiUrl("initiators", iqn));
String response = executeApiRequest(conn, getReq);
return gson.fromJson(response, DateraObject.Initiator.class);
} catch (DateraObject.DateraError dateraError) {
if (DateraObject.DateraErrorTypes.NotFoundError.equals(dateraError)) {
return null;
} else {
throw dateraError;
}
}
}
public static void deleteInitiator(DateraObject.DateraConnection conn, String iqn) throws DateraObject.DateraError {
HttpDelete req = new HttpDelete(generateApiUrl("initiators", iqn));
executeApiRequest(conn, req);
}
public static DateraObject.InitiatorGroup createInitiatorGroup(DateraObject.DateraConnection conn, String name)
throws UnsupportedEncodingException, DateraObject.DateraError {
HttpPost createReq = new HttpPost(generateApiUrl("initiator_groups"));
DateraObject.InitiatorGroup group = new DateraObject.InitiatorGroup(name, Collections.<String>emptyList());
StringEntity httpEntity = new StringEntity(gson.toJson(group));
createReq.setEntity(httpEntity);
String response = executeApiRequest(conn, createReq);
return gson.fromJson(response, DateraObject.InitiatorGroup.class);
}
public static void deleteInitatorGroup(DateraObject.DateraConnection conn, String name)
throws DateraObject.DateraError {
HttpDelete delReq = new HttpDelete(generateApiUrl("initiator_groups", name));
executeApiRequest(conn, delReq);
}
public static DateraObject.InitiatorGroup getInitiatorGroup(DateraObject.DateraConnection conn, String name)
throws DateraObject.DateraError {
try {
HttpGet getReq = new HttpGet(generateApiUrl("initiator_groups", name));
String response = executeApiRequest(conn, getReq);
return gson.fromJson(response, DateraObject.InitiatorGroup.class);
} catch (DateraObject.DateraError dateraError) {
if (DateraObject.DateraErrorTypes.NotFoundError.equals(dateraError)) {
return null;
} else {
throw dateraError;
}
}
}
public static void updateInitiatorGroup(DateraObject.DateraConnection conn, String initiatorPath, String groupName,
DateraObject.DateraOperation op) throws DateraObject.DateraError, UnsupportedEncodingException {
DateraObject.InitiatorGroup initiatorGroup = getInitiatorGroup(conn, groupName);
if (initiatorGroup == null) {
throw new CloudRuntimeException("Unable to find initiator group by name " + groupName);
}
HttpPut addReq = new HttpPut(generateApiUrl("initiator_groups", groupName, "members"));
DateraObject.Initiator initiator = new DateraObject.Initiator(initiatorPath, op);
addReq.setEntity(new StringEntity(gson.toJson(initiator)));
executeApiRequest(conn, addReq);
}
public static void addInitiatorToGroup(DateraObject.DateraConnection conn, String initiatorPath, String groupName)
throws UnsupportedEncodingException, DateraObject.DateraError {
updateInitiatorGroup(conn, initiatorPath, groupName, DateraObject.DateraOperation.ADD);
}
public static void removeInitiatorFromGroup(DateraObject.DateraConnection conn, String initiatorPath,
String groupName) throws DateraObject.DateraError, UnsupportedEncodingException {
updateInitiatorGroup(conn, initiatorPath, groupName, DateraObject.DateraOperation.REMOVE);
}
public static Map<String, DateraObject.InitiatorGroup> getAppInstanceInitiatorGroups(
DateraObject.DateraConnection conn, String appInstance) throws DateraObject.DateraError {
HttpGet req = new HttpGet(generateApiUrl("app_instances", appInstance, "storage_instances",
DateraObject.DEFAULT_STORAGE_NAME, "acl_policy", "initiator_groups"));
String response = executeApiRequest(conn, req);
if (response == null) {
return null;
}
Type responseType = new TypeToken<Map<String, DateraObject.InitiatorGroup>>() {
}.getType();
return gson.fromJson(response, responseType);
}
public static void assignGroupToAppInstance(DateraObject.DateraConnection conn, String group, String appInstance)
throws DateraObject.DateraError, UnsupportedEncodingException {
DateraObject.InitiatorGroup initiatorGroup = getInitiatorGroup(conn, group);
if (initiatorGroup == null) {
throw new CloudRuntimeException("Initator group " + group + " not found ");
}
Map<String, DateraObject.InitiatorGroup> initiatorGroups = getAppInstanceInitiatorGroups(conn, appInstance);
if (initiatorGroups == null) {
throw new CloudRuntimeException("Initator group not found for appInstnace " + appInstance);
}
for (DateraObject.InitiatorGroup ig : initiatorGroups.values()) {
if (ig.getName().equals(group)) {
// already assigned
return;
}
}
HttpPut url = new HttpPut(generateApiUrl("app_instances", appInstance, "storage_instances",
DateraObject.DEFAULT_STORAGE_NAME, "acl_policy", "initiator_groups"));
url.setEntity(new StringEntity(gson
.toJson(new DateraObject.InitiatorGroup(initiatorGroup.getPath(), DateraObject.DateraOperation.ADD))));
executeApiRequest(conn, url);
}
public static void removeGroupFromAppInstance(DateraObject.DateraConnection conn, String group, String appInstance)
throws DateraObject.DateraError, UnsupportedEncodingException {
DateraObject.InitiatorGroup initiatorGroup = getInitiatorGroup(conn, group);
if (initiatorGroup == null) {
throw new CloudRuntimeException("Initator groups not found for appInstnace " + appInstance);
}
Map<String, DateraObject.InitiatorGroup> initiatorGroups = getAppInstanceInitiatorGroups(conn, appInstance);
if (initiatorGroups == null) {
throw new CloudRuntimeException("Initator group not found for appInstnace " + appInstance);
}
boolean groupAssigned = false;
for (DateraObject.InitiatorGroup ig : initiatorGroups.values()) {
if (ig.getName().equals(group)) {
groupAssigned = true;
break;
}
}
if (!groupAssigned) {
return; // already removed
}
HttpPut url = new HttpPut(generateApiUrl("app_instances", appInstance, "storage_instances",
DateraObject.DEFAULT_STORAGE_NAME, "acl_policy", "initiator_groups"));
url.setEntity(new StringEntity(gson.toJson(
new DateraObject.InitiatorGroup(initiatorGroup.getPath(), DateraObject.DateraOperation.REMOVE))));
executeApiRequest(conn, url);
}
public static void updateAppInstanceAdminState(DateraObject.DateraConnection conn, String appInstanceName,
DateraObject.AppState appState) throws UnsupportedEncodingException, DateraObject.DateraError {
DateraObject.AppInstance appInstance = new DateraObject.AppInstance(appState);
HttpPut updateAppInstanceReq = new HttpPut(generateApiUrl("app_instances", appInstanceName));
updateAppInstanceReq.setEntity(new StringEntity(gson.toJson(appInstance)));
executeApiRequest(conn, updateAppInstanceReq);
}
public static void updateAppInstanceIpPool(DateraObject.DateraConnection conn, String appInstanceName,
String ipPool) throws UnsupportedEncodingException, DateraObject.DateraError {
HttpPut url = new HttpPut(generateApiUrl("app_instances", appInstanceName, "storage_instances",
DateraObject.DEFAULT_STORAGE_NAME));
url.setEntity(new StringEntity(gson.toJson(new DateraObject.AccessNetworkIpPool(ipPool))));
executeApiRequest(conn, url);
}
public static void deleteAppInstance(DateraObject.DateraConnection conn, String name)
throws UnsupportedEncodingException, DateraObject.DateraError {
HttpDelete deleteAppInstanceReq = new HttpDelete(generateApiUrl("app_instances", name));
updateAppInstanceAdminState(conn, name, DateraObject.AppState.OFFLINE);
executeApiRequest(conn, deleteAppInstanceReq);
}
public static DateraObject.AppInstance cloneAppInstanceFromSnapshot(DateraObject.DateraConnection conn,
String newAppInstanceName, String snapshotName)
throws DateraObject.DateraError, UnsupportedEncodingException {
return cloneAppInstanceFromSnapshot(conn, newAppInstanceName, snapshotName, DEFAULT_IP_POOL);
}
public static DateraObject.AppInstance cloneAppInstanceFromSnapshot(DateraObject.DateraConnection conn,
String newAppInstanceName, String snapshotName, String ipPool)
throws DateraObject.DateraError, UnsupportedEncodingException {
// split the snapshot name to appInstanceName and the snapshot timestamp
String[] tokens = snapshotName.split(":");
Preconditions.checkArgument(tokens.length == 2);
// A snapshot is stored in Cloudstack as <AppInstanceName>:<SnapshotTime>
String appInstanceName = tokens[0];
String snapshotTime = tokens[1];
// get the snapshot from Datera
HttpGet getSnasphotReq = new HttpGet(
generateApiUrl("app_instances", appInstanceName, "storage_instances", DateraObject.DEFAULT_STORAGE_NAME,
"volumes", DateraObject.DEFAULT_VOLUME_NAME, "snapshots", snapshotTime));
String resp = executeApiRequest(conn, getSnasphotReq);
DateraObject.VolumeSnapshot snapshot = gson.fromJson(resp, DateraObject.VolumeSnapshot.class);
String snapshotPath = snapshot.getPath();
DateraObject.AppInstance appInstanceObj = new DateraObject.AppInstance(newAppInstanceName, snapshotPath);
StringEntity appInstanceEntity = new StringEntity(gson.toJson(appInstanceObj));
DateraObject.AppInstance appInstance = createAppInstance(conn, newAppInstanceName, appInstanceEntity);
// Update ipPool
updateAppInstanceIpPool(conn, newAppInstanceName, ipPool);
// bring it online
updateAppInstanceAdminState(conn, newAppInstanceName, DateraObject.AppState.ONLINE);
return getAppInstance(conn, newAppInstanceName);
}
public static void deleteVolumeSnapshot(DateraObject.DateraConnection conn, String snapshotName)
throws DateraObject.DateraError {
// split the snapshot name to appInstanceName and the snapshot timestamp
String[] tokens = snapshotName.split(":");
Preconditions.checkArgument(tokens.length == 2);
// A snapshot is stored in Cloudstack as <AppInstanceName>:<SnapshotTime>
String appInstanceName = tokens[0];
String snapshotTime = tokens[1];
HttpDelete deleteSnapshotReq = new HttpDelete(
generateApiUrl("app_instances", appInstanceName, "storage_instances", DateraObject.DEFAULT_STORAGE_NAME,
"volumes", DateraObject.DEFAULT_VOLUME_NAME, "snapshots", snapshotTime));
executeApiRequest(conn, deleteSnapshotReq);
}
public static DateraObject.VolumeSnapshot getVolumeSnapshot(DateraObject.DateraConnection conn,
String appInstanceName, String snapshotTime) throws DateraObject.DateraError {
HttpGet getSnapshotReq = new HttpGet(
generateApiUrl("app_instances", appInstanceName, "storage_instances", DateraObject.DEFAULT_STORAGE_NAME,
"volumes", DateraObject.DEFAULT_VOLUME_NAME, "snapshots", snapshotTime));
String resp = executeApiRequest(conn, getSnapshotReq);
return gson.fromJson(resp, DateraObject.VolumeSnapshot.class);
}
public static DateraObject.VolumeSnapshot takeVolumeSnapshot(DateraObject.DateraConnection conn,
String baseAppInstanceName) throws UnsupportedEncodingException, DateraObject.DateraError {
HttpPost takeSnasphotReq = new HttpPost(
generateApiUrl("app_instances", baseAppInstanceName, "storage_instances",
DateraObject.DEFAULT_STORAGE_NAME, "volumes", DateraObject.DEFAULT_VOLUME_NAME, "snapshots"));
String snapshotUuid = UUID.randomUUID().toString();
DateraObject.VolumeSnapshot volumeSnapshot = new DateraObject.VolumeSnapshot(snapshotUuid);
takeSnasphotReq.setEntity(new StringEntity(gson.toJson(volumeSnapshot)));
String snapshotResponse = executeApiRequest(conn, takeSnasphotReq);
volumeSnapshot = gson.fromJson(snapshotResponse, DateraObject.VolumeSnapshot.class);
String snapshotTime = volumeSnapshot.getTimestamp();
int retries = DateraUtil.DEFAULT_RETRIES;
do {
try {
Thread.sleep(DateraUtil.POLL_TIMEOUT_MS);
} catch (InterruptedException e) {
return null;
}
volumeSnapshot = getVolumeSnapshot(conn, baseAppInstanceName, snapshotTime);
} while ((!Objects.equals(volumeSnapshot.getOpState(), DateraUtil.STATE_AVAILABLE)) && --retries > 0);
return volumeSnapshot;
}
public static DateraObject.AppInstance restoreVolumeSnapshot(DateraObject.DateraConnection conn,
String snapshotName) throws DateraObject.DateraError {
// split the snapshot name to appInstanceName and the snapshot timestamp
String[] tokens = snapshotName.split(":");
Preconditions.checkArgument(tokens.length == 2);
// A snapshot is stored in Cloudstack as <AppInstanceName>:<SnapshotTime>
String appInstanceName = tokens[0];
String snapshotTime = tokens[1];
HttpPut restoreSnapshotReq = new HttpPut(generateApiUrl("app_instances", appInstanceName, "storage_instances",
DateraObject.DEFAULT_STORAGE_NAME, "volumes", DateraObject.DEFAULT_VOLUME_NAME));
try {
// bring appInstance offline
updateAppInstanceAdminState(conn, appInstanceName, DateraObject.AppState.OFFLINE);
DateraObject.VolumeSnapshotRestore volumeSnapshotRestore = new DateraObject.VolumeSnapshotRestore(
snapshotTime);
StringEntity jsonParams = new StringEntity(gson.toJson(volumeSnapshotRestore));
restoreSnapshotReq.setEntity(jsonParams);
executeApiRequest(conn, restoreSnapshotReq);
// bring appInstance online
updateAppInstanceAdminState(conn, appInstanceName, DateraObject.AppState.ONLINE);
} catch (UnsupportedEncodingException e) {
throw new CloudRuntimeException("Failed to restore volume snapshot" + e.getMessage());
}
return getAppInstance(conn, appInstanceName);
}
private static String executeApiRequest(DateraObject.DateraConnection conn, HttpRequest apiReq)
throws DateraObject.DateraError {
// Get the token first
String authToken = null;
try {
authToken = login(conn);
} catch (UnsupportedEncodingException e) {
throw new CloudRuntimeException("Unable to login to Datera " + e.getMessage());
}
if (authToken == null) {
throw new CloudRuntimeException("Unable to login to Datera: error getting auth token ");
}
apiReq.addHeader(HEADER_AUTH_TOKEN, authToken);
return executeHttp(conn, apiReq);
}
private static String executeHttp(DateraObject.DateraConnection conn, HttpRequest request)
throws DateraObject.DateraError {
CloseableHttpClient httpclient = HttpClientBuilder.create().build();
String response = null;
if (null == httpclient) {
throw new CloudRuntimeException("Unable to create httpClient for request");
}
try {
request.setHeader(HEADER_CONTENT_TYPE, HEADER_VALUE_JSON);
HttpHost target = new HttpHost(conn.getManagementIp(), conn.getManagementPort(), SCHEME_HTTP);
HttpResponse httpResponse = httpclient.execute(target, request);
HttpEntity entity = httpResponse.getEntity();
StatusLine status = httpResponse.getStatusLine();
response = EntityUtils.toString(entity);
assert response != null;
if (status.getStatusCode() != HttpStatus.SC_OK) {
// check if this is an error
DateraObject.DateraError error = gson.fromJson(response, DateraObject.DateraError.class);
if (error != null && error.isError()) {
throw error;
} else {
throw new CloudRuntimeException("Error while trying to get HTTP object from Datera");
}
}
} catch (IOException e) {
throw new CloudRuntimeException("Error while sending request to Datera. Error " + e.getMessage());
}
return response;
}
protected static String generateApiUrl(String... args) {
ArrayList<String> urlList = new ArrayList<String>(Arrays.asList(args));
urlList.add(0, API_VERSION);
urlList.add(0, "");
return StringUtils.join(urlList, "/");
}
public static String getManagementVip(String url) {
return getVip(DateraUtil.MANAGEMENT_VIP, url);
}
public static String getStorageVip(String url) {
return getVip(DateraUtil.STORAGE_VIP, url);
}
public static int getManagementPort(String url) {
return getPort(DateraUtil.MANAGEMENT_VIP, url, DEFAULT_MANAGEMENT_PORT);
}
public static int getStoragePort(String url) {
return getPort(DateraUtil.STORAGE_VIP, url, DEFAULT_STORAGE_PORT);
}
public static int getNumReplicas(String url) {
try {
String value = getValue(DateraUtil.NUM_REPLICAS, url, false);
return Integer.parseInt(value);
} catch (NumberFormatException ex) {
return DEFAULT_NUM_REPLICAS;
}
}
public static String getVolPlacement(String url) {
String volPlacement = getValue(DateraUtil.VOL_PLACEMENT, url, false);
if (volPlacement == null) {
return DEFAULT_VOL_PLACEMENT;
} else {
return volPlacement;
}
}
public static String getIpPool(String url) {
String ipPool = getValue(DateraUtil.IP_POOL, url, false);
if (ipPool == null) {
return DEFAULT_IP_POOL;
} else {
return ipPool;
}
}
private static String getVip(String keyToMatch, String url) {
String delimiter = ":";
String storageVip = getValue(keyToMatch, url);
int index = storageVip.indexOf(delimiter);
if (index != -1) {
return storageVip.substring(0, index);
}
return storageVip;
}
private static int getPort(String keyToMatch, String url, int defaultPortNumber) {
String delimiter = ":";
String storageVip = getValue(keyToMatch, url);
int index = storageVip.indexOf(delimiter);
int portNumber = defaultPortNumber;
if (index != -1) {
String port = storageVip.substring(index + delimiter.length());
try {
portNumber = Integer.parseInt(port);
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("Invalid URL format (port is not an integer)");
}
}
return portNumber;
}
public static String getValue(String keyToMatch, String url) {
return getValue(keyToMatch, url, true);
}
public static String getValue(String keyToMatch, String url, boolean throwExceptionIfNotFound) {
String delimiter1 = ";";
String delimiter2 = "=";
StringTokenizer st = new StringTokenizer(url, delimiter1);
while (st.hasMoreElements()) {
String token = st.nextElement().toString();
int index = token.indexOf(delimiter2);
if (index == -1) {
throw new CloudRuntimeException("Invalid URL format");
}
String key = token.substring(0, index);
if (key.equalsIgnoreCase(keyToMatch)) {
return token.substring(index + delimiter2.length());
}
}
if (throwExceptionIfNotFound) {
throw new CloudRuntimeException("Key not found in URL");
}
return null;
}
public static String getModifiedUrl(String originalUrl) {
StringBuilder sb = new StringBuilder();
String delimiter = ";";
StringTokenizer st = new StringTokenizer(originalUrl, delimiter);
while (st.hasMoreElements()) {
String token = st.nextElement().toString().toUpperCase();
if (token.startsWith(DateraUtil.MANAGEMENT_VIP.toUpperCase())
|| token.startsWith(DateraUtil.STORAGE_VIP.toUpperCase())) {
sb.append(token).append(delimiter);
}
}
String modifiedUrl = sb.toString();
int lastIndexOf = modifiedUrl.lastIndexOf(delimiter);
if (lastIndexOf == (modifiedUrl.length() - delimiter.length())) {
return modifiedUrl.substring(0, lastIndexOf);
}
return modifiedUrl;
}
public static DateraObject.DateraConnection getDateraConnection(long storagePoolId,
StoragePoolDetailsDao storagePoolDetailsDao) {
StoragePoolDetailVO storagePoolDetail = storagePoolDetailsDao.findDetail(storagePoolId,
DateraUtil.MANAGEMENT_VIP);
String mVip = storagePoolDetail.getValue();
storagePoolDetail = storagePoolDetailsDao.findDetail(storagePoolId, DateraUtil.MANAGEMENT_PORT);
int mPort = Integer.parseInt(storagePoolDetail.getValue());
storagePoolDetail = storagePoolDetailsDao.findDetail(storagePoolId, DateraUtil.CLUSTER_ADMIN_USERNAME);
String clusterAdminUsername = storagePoolDetail.getValue();
storagePoolDetail = storagePoolDetailsDao.findDetail(storagePoolId, DateraUtil.CLUSTER_ADMIN_PASSWORD);
String clusterAdminPassword = storagePoolDetail.getValue();
return new DateraObject.DateraConnection(mVip, mPort, clusterAdminUsername, clusterAdminPassword);
}
public static boolean hostsSupport_iScsi(List<HostVO> hosts) {
if (hosts == null || hosts.size() == 0) {
return false;
}
for (Host host : hosts) {
if (!hostSupport_iScsi(host)) {
return false;
}
}
return true;
}
public static boolean hostSupport_iScsi(Host host) {
if (host == null || host.getStorageUrl() == null || host.getStorageUrl().trim().length() == 0
|| !host.getStorageUrl().startsWith("iqn")) {
return false;
}
return true;
}
public static String getInitiatorGroupKey(long storagePoolId) {
return "DateraInitiatorGroup-" + storagePoolId;
}
/**
* Checks whether a host initiator is present in an initiator group
*
* @param initiator Host initiator to check
* @param initiatorGroup the initiator group
* @return true if host initiator is in the group, false otherwise
*/
public static boolean isInitiatorPresentInGroup(DateraObject.Initiator initiator,
DateraObject.InitiatorGroup initiatorGroup) {
for (String memberPath : initiatorGroup.getMembers()) {
if (memberPath.equals(initiator.getPath())) {
return true;
}
}
return false;
}
public static int bytesToGib(long volumeSizeBytes) {
return (int) Math.ceil(volumeSizeBytes / (double) ONEGIB_BYTES);
}
public static long gibToBytes(int volumeSizeGb) {
return volumeSizeGb * ONEGIB_BYTES;
}
/**
* IQN path is stored in the DB by cloudstack it is of the form /<IQN>/0
*
* @param iqn: IQN of the LUN
* @return IQN path as defined above
*/
public static String generateIqnPath(String iqn) {
if (iqn != null) {
return "/" + iqn + "/0";
}
return null;
}
/**
* Does the opposite of generateIqnPath
*
* @param iqnPath
* @return timmed IQN path
*/
public static String extractIqn(String iqnPath) {
if (iqnPath == null) {
return null;
}
if (iqnPath.endsWith("/")) {
iqnPath = iqnPath.substring(0, iqnPath.length() - 1);
}
final String tokens[] = iqnPath.split("/");
if (tokens.length != 3) {
final String msg = "Wrong iscsi path " + iqnPath + " it should be /targetIQN/LUN";
s_logger.warn(msg);
return null;
}
return tokens[1].trim();
}
/**
* Generate random uuid
*
* @param seed
* @param length ( default to 8 )
* @return String uuid
*/
public static String generateUUID(String seed) {
int length = UUID_LENGTH;
// creating UUID
UUID uid = UUID.fromString(seed);
String uuid = String.valueOf(uid.randomUUID()).substring(0, length);
return uuid;
}
}