blob: a7ff6268b5a8b3423254f10cef43a4f4b97de38f [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.hypervisor.kvm.storage.StorPoolStorageAdaptor;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.script.OutputInterpreter;
import com.cloud.utils.script.Script;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.snapshot.StorPoolConfigurationManager;
import org.apache.cloudstack.storage.to.VolumeObjectTO;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.log4j.Logger;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class StorPoolUtil {
private static final Logger log = Logger.getLogger(StorPoolUtil.class);
private static final File spLogFile = new File("/var/log/cloudstack/management/storpool-plugin.log");
private static PrintWriter spLogPrinterWriter = spLogFileInitialize();
private static PrintWriter spLogFileInitialize() {
try {
log.info("INITIALIZE SP-LOG_FILE");
if (spLogFile.exists()) {
final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
final Timestamp timestamp = new Timestamp(System.currentTimeMillis());
final File spLogFileRename = new File(spLogFile + "-" + sdf.format(timestamp));
final boolean ret = spLogFile.renameTo(spLogFileRename);
if (!ret) {
log.warn("Unable to rename" + spLogFile + " to " + spLogFileRename);
} else {
log.debug("Renamed " + spLogFile + " to " + spLogFileRename);
}
} else {
spLogFile.getParentFile().mkdirs();
}
return new PrintWriter(spLogFile);
} catch (Exception e) {
log.info("INITIALIZE SP-LOG_FILE: " + e.getMessage());
throw new RuntimeException(e);
}
}
public static void spLog(String fmt, Object... args) {
String timeStamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,ms").format(Calendar.getInstance().getTime());
spLogPrinterWriter.println(String.format(timeStamp + " " + fmt, args));
spLogPrinterWriter.flush();
if (spLogFile.length() > 107374182400L) {
spLogPrinterWriter.close();
spLogPrinterWriter = spLogFileInitialize();
}
}
public static final String SP_PROVIDER_NAME = "StorPool";
public static final String SP_DEV_PATH = "/dev/storpool-byid/";
public static final String SP_OLD_PATH = "/dev/storpool/";
public static final String SP_VC_POLICY = "vc-policy";
public static final String GLOBAL_ID = "snapshotGlobalId";
public static final String UPDATED_DETAIL = "renamed";
public static final String SP_STORAGE_POOL_ID = "spStoragePoolId";
public static final String SP_HOST_PORT = "SP_API_HTTP_HOST";
public static final String SP_TEMPLATE = "SP_TEMPLATE";
public static final String SP_AUTH_TOKEN = "SP_AUTH_TOKEN";
public static final String SP_VOLUME_ON_CLUSTER = "SP_VOLUME_ON_CLUSTER";
public static enum StorpoolRights {
RO("ro"), RW("rw"), DETACH("detach");
private final String name;
private StorpoolRights(String name) {
this.name = name;
}
public String toString() {
return name;
}
}
public static final class SpApiError {
private String name;
private String descr;
public SpApiError() {
}
public String getName() {
return this.name;
}
public String getDescr() {
return this.descr;
}
public void setName(String name) {
this.name = name;
}
public void setDescr(String descr) {
this.descr = descr;
}
public String toString() {
return String.format("%s: %s", name, descr);
}
}
public static class SpConnectionDesc {
private String hostPort;
private String authToken;
private String templateName;
public SpConnectionDesc(String url) {
String[] urlSplit = url.split(";");
if (urlSplit.length == 1 && !urlSplit[0].contains("=")) {
this.templateName = url;
Script sc = new Script("storpool_confget", 0, log);
OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
final String err = sc.execute(parser);
if (err != null) {
final String errMsg = String.format("Could not execute storpool_confget. Error: %s", err);
log.warn(errMsg);
throw new CloudRuntimeException(errMsg);
}
String SP_API_HOST = null;
String SP_API_PORT = null;
for (String line : parser.getLines().split("\n")) {
String[] toks = line.split("=");
if (toks.length != 2) {
continue;
}
switch (toks[0]) {
case "SP_API_HTTP_HOST":
SP_API_HOST = toks[1];
break;
case "SP_API_HTTP_PORT":
SP_API_PORT = toks[1];
break;
case "SP_AUTH_TOKEN":
this.authToken = toks[1];
break;
}
}
if (SP_API_HOST == null)
throw new CloudRuntimeException("Invalid StorPool config. Missing SP_API_HTTP_HOST");
if (SP_API_PORT == null)
throw new CloudRuntimeException("Invalid StorPool config. Missing SP_API_HTTP_PORT");
if (this.authToken == null)
throw new CloudRuntimeException("Invalid StorPool config. Missing SP_AUTH_TOKEN");
this.hostPort = SP_API_HOST + ":" + SP_API_PORT;
} else {
for (String kv : urlSplit) {
String[] toks = kv.split("=");
if (toks.length != 2)
continue;
switch (toks[0]) {
case "SP_API_HTTP":
this.hostPort = toks[1];
break;
case "SP_AUTH_TOKEN":
this.authToken = toks[1];
break;
case "SP_TEMPLATE":
this.templateName = toks[1];
break;
}
}
}
}
public SpConnectionDesc(String host, String authToken2, String templateName2) {
this.hostPort = host;
this.authToken = authToken2;
this.templateName = templateName2;
}
public String getHostPort() {
return this.hostPort;
}
public String getAuthToken() {
return this.authToken;
}
public String getTemplateName() {
return this.templateName;
}
}
public static SpConnectionDesc getSpConnection(String url, long poolId, StoragePoolDetailsDao poolDetails,
PrimaryDataStoreDao storagePool) {
boolean isAlternateEndpointEnabled = StorPoolConfigurationManager.AlternativeEndPointEnabled.valueIn(poolId);
if (isAlternateEndpointEnabled) {
String alternateEndpoint = StorPoolConfigurationManager.AlternativeEndpoint.valueIn(poolId);
if (StringUtils.isNotEmpty(alternateEndpoint)) {
return new SpConnectionDesc(alternateEndpoint);
} else {
throw new CloudRuntimeException(String.format("Using an alternative endpoint of StorPool primary storage with id [%s] is enabled but no endpoint URL is provided", poolId));
}
}
List<StoragePoolDetailVO> details = poolDetails.listDetails(poolId);
String host = null;
String authToken = null;
String templateName = null;
for (StoragePoolDetailVO storagePoolDetailVO : details) {
switch (storagePoolDetailVO.getName()) {
case SP_HOST_PORT:
host = storagePoolDetailVO.getValue();
break;
case SP_AUTH_TOKEN:
authToken = storagePoolDetailVO.getValue();
break;
case SP_TEMPLATE:
templateName = storagePoolDetailVO.getValue();
break;
}
}
if (host != null && authToken != null && templateName != null) {
return new SpConnectionDesc(host, authToken, templateName);
} else {
return updateStorageAndStorageDetails(url, poolId, poolDetails, storagePool);
}
}
private static SpConnectionDesc updateStorageAndStorageDetails(String url, long poolId,
StoragePoolDetailsDao poolDetails, PrimaryDataStoreDao storagePool) {
SpConnectionDesc conn = new SpConnectionDesc(url);
poolDetails.persist(new StoragePoolDetailVO(poolId, SP_HOST_PORT, conn.getHostPort(), false));
poolDetails.persist(new StoragePoolDetailVO(poolId, SP_AUTH_TOKEN, conn.getAuthToken(), false));
poolDetails.persist(new StoragePoolDetailVO(poolId, SP_TEMPLATE, conn.getTemplateName(), false));
StoragePoolVO pool = storagePool.findById(poolId);
pool.setUuid(conn.getTemplateName() + ";" + UUID.randomUUID().toString());
storagePool.update(poolId, pool);
StorPoolUtil.spLog(
"Storage pool with id=%s and template's name=%s was updated and its connection details are hidden from UI.",
pool.getId(), conn.getTemplateName());
return conn;
}
public static class SpApiResponse {
private SpApiError error;
public JsonElement fullJson;
public SpApiResponse() {
}
public SpApiError getError() {
return this.error;
}
public void setError(SpApiError error) {
this.error = error;
}
}
public static String devPath(final String name) {
return String.format("%s%s", SP_DEV_PATH, name);
}
private static SpApiResponse spApiRequest(HttpRequestBase req, String query, SpConnectionDesc conn) {
if (conn == null)
conn = new SpConnectionDesc("");
if (conn.getHostPort() == null) {
throw new CloudRuntimeException("Invalid StorPool config. Missing SP_API_HTTP_HOST");
}
if (conn.getAuthToken() == null) {
throw new CloudRuntimeException("Invalid StorPool config. Missing SP_AUTH_TOKEN");
}
try (CloseableHttpClient httpclient = HttpClientBuilder.create().build()) {
final String qry = String.format("http://%s/ctrl/1.0/%s", conn.getHostPort(), query);
final URI uri = new URI(qry);
req.setURI(uri);
req.addHeader("Authorization", String.format("Storpool v1:%s", conn.getAuthToken()));
final HttpResponse resp = httpclient.execute(req);
Gson gson = new Gson();
BufferedReader br = new BufferedReader(new InputStreamReader(resp.getEntity().getContent()));
JsonElement el = new JsonParser().parse(br);
SpApiResponse apiResp = gson.fromJson(el, SpApiResponse.class);
apiResp.fullJson = el;
return apiResp;
} catch (UnsupportedEncodingException ex) {
throw new CloudRuntimeException(ex.getMessage());
} catch (ClientProtocolException ex) {
throw new CloudRuntimeException(ex.getMessage());
} catch (IOException ex) {
throw new CloudRuntimeException(ex.getMessage());
} catch (URISyntaxException ex) {
throw new CloudRuntimeException(ex.getMessage());
}
}
private static SpApiResponse GET(String query, SpConnectionDesc conn) {
return spApiRequest(new HttpGet(), query, conn);
}
private static SpApiResponse POST(String query, Object json, SpConnectionDesc conn) {
HttpPost req = new HttpPost();
if (json != null) {
Gson gson = new Gson();
String js = gson.toJson(json);
StringEntity input = new StringEntity(js, ContentType.APPLICATION_JSON);
log.info("Request:" + js);
req.setEntity(input);
}
return spApiRequest(req, query, conn);
}
public static boolean templateExists(SpConnectionDesc conn) {
SpApiResponse resp = GET("VolumeTemplateDescribe/" + conn.getTemplateName(), conn);
return resp.getError() == null ? true : objectExists(resp.getError());
}
public static boolean snapshotExists(final String name, SpConnectionDesc conn) {
SpApiResponse resp = GET("MultiCluster/Snapshot/" + name, conn);
return resp.getError() == null ? true : objectExists(resp.getError());
}
public static JsonArray snapshotsList(SpConnectionDesc conn) {
SpApiResponse resp = GET("MultiCluster/SnapshotsList", conn);
JsonObject obj = resp.fullJson.getAsJsonObject();
JsonArray data = obj.getAsJsonArray("data");
return data;
}
public static JsonArray volumesList(SpConnectionDesc conn) {
SpApiResponse resp = GET("MultiCluster/VolumesList", conn);
JsonObject obj = resp.fullJson.getAsJsonObject();
JsonArray data = obj.getAsJsonArray("data");
return data;
}
public static JsonArray volumesSpace(SpConnectionDesc conn) {
SpApiResponse resp = GET("MultiCluster/AllClusters/VolumesSpace", conn);
JsonObject obj = resp.fullJson.getAsJsonObject();
return obj.getAsJsonObject("data").getAsJsonArray("clusters");
}
public static JsonArray templatesStats(SpConnectionDesc conn) {
SpApiResponse resp = GET("MultiCluster/AllClusters/VolumeTemplatesStatus", conn);
JsonObject obj = resp.fullJson.getAsJsonObject();
return obj.getAsJsonObject("data").getAsJsonArray("clusters");
}
private static boolean objectExists(SpApiError err) {
if (!err.getName().equals("objectDoesNotExist")) {
throw new CloudRuntimeException(err.getDescr());
}
return false;
}
public static Long snapshotSize(final String name, SpConnectionDesc conn) {
SpApiResponse resp = GET("MultiCluster/Snapshot/" + name, conn);
JsonObject obj = resp.fullJson.getAsJsonObject();
if (resp.getError() != null && !objectExists(resp.getError())) {
return null;
}
JsonObject data = obj.getAsJsonArray("data").get(0).getAsJsonObject();
return data.getAsJsonPrimitive("size").getAsLong();
}
public static String getSnapshotClusterID(String name, SpConnectionDesc conn) {
SpApiResponse resp = GET("MultiCluster/Snapshot/" + name, conn);
JsonObject obj = resp.fullJson.getAsJsonObject();
JsonObject data = obj.getAsJsonArray("data").get(0).getAsJsonObject();
JsonPrimitive clusterId = data.getAsJsonPrimitive("clusterId");
return clusterId != null ? clusterId.getAsString() : null;
}
public static String getVolumeClusterID(String name, SpConnectionDesc conn) {
SpApiResponse resp = GET("MultiCluster/Volume/" + name, conn);
JsonObject obj = resp.fullJson.getAsJsonObject();
JsonObject data = obj.getAsJsonArray("data").get(0).getAsJsonObject();
JsonPrimitive clusterId = data.getAsJsonPrimitive("clusterId");
return clusterId != null ? clusterId.getAsString() : null;
}
public static SpApiResponse volumeCreate(final String name, final String parentName, final Long size, String vmUuid,
String vcPolicy, String csTag, Long iops, SpConnectionDesc conn) {
Map<String, Object> json = new LinkedHashMap<>();
json.put("name", "");
json.put("iops", iops);
json.put("parent", parentName);
json.put("size", size);
json.put("template", conn.getTemplateName());
Map<String, String> tags = StorPoolHelper.addStorPoolTags(name, vmUuid, csTag, vcPolicy);
json.put("tags", tags);
return POST("MultiCluster/VolumeCreate", json, conn);
}
public static SpApiResponse volumeCreate(SpConnectionDesc conn) {
Map<String, Object> json = new LinkedHashMap<>();
json.put("name", "");
json.put("size", 512);
json.put("template", conn.getTemplateName());
Map<String, String> tags = new HashMap<>();
tags.put("cs", "check-volume-is-on-host");
json.put("tags", tags);
return POST("MultiCluster/VolumeCreate", json, conn);
}
public static SpApiResponse volumeCopy(final String name, final String baseOn, String csTag, Long iops, String cvmTag, String vcPolicyTag,
SpConnectionDesc conn) {
Map<String, Object> json = new HashMap<>();
json.put("baseOn", baseOn);
if (iops != null) {
json.put("iops", iops);
}
json.put("template", conn.getTemplateName());
Map<String, String> tags = StorPoolHelper.addStorPoolTags(name, cvmTag, csTag, vcPolicyTag);
json.put("tags", tags);
return POST("MultiCluster/VolumeCreate", json, conn);
}
public static SpApiResponse volumeUpdateRename(final String name, String newName, String uuid,
SpConnectionDesc conn) {
Map<String, Object> json = new HashMap<>();
json.put("rename", newName);
Map<String, String> tags = new HashMap<>();
tags.put("uuid", uuid);
json.put("tags", tags);
return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
}
public static SpApiResponse volumeUpdate(final String name, final Long newSize, final Boolean shrinkOk, Long iops,
SpConnectionDesc conn) {
Map<String, Object> json = new HashMap<>();
json.put("iops", iops);
json.put("size", newSize);
json.put("shrinkOk", shrinkOk);
return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
}
public static SpApiResponse volumeUpdateTags(final String name, final String uuid, Long iops,
SpConnectionDesc conn, String vcPolicy) {
Map<String, Object> json = new HashMap<>();
Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, uuid, null, vcPolicy);
json.put("iops", iops);
json.put("tags", tags);
return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
}
public static SpApiResponse volumeUpdateCvmTags(final String name, final String uuid, SpConnectionDesc conn) {
Map<String, Object> json = new HashMap<>();
Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, uuid, null, null);
json.put("tags", tags);
return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
}
public static SpApiResponse volumeUpdateVCTags(final String name, SpConnectionDesc conn, String vcPolicy) {
Map<String, Object> json = new HashMap<>();
Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, null, null, vcPolicy);
json.put("tags", tags);
return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
}
public static SpApiResponse volumeUpdateTemplate(final String name, SpConnectionDesc conn) {
Map<String, Object> json = new HashMap<>();
json.put("template", conn.getTemplateName());
return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
}
public static SpApiResponse volumeSnapshot(final String volumeName, final String snapshotName, String vmUuid,
String csTag, String vcPolicy, SpConnectionDesc conn) {
Map<String, Object> json = new HashMap<>();
Map<String, String> tags = StorPoolHelper.addStorPoolTags(snapshotName, vmUuid, csTag, vcPolicy);
json.put("name", "");
json.put("tags", tags);
return POST("MultiCluster/VolumeSnapshot/" + volumeName, json, conn);
}
public static SpApiResponse volumesGroupSnapshot(final List<VolumeObjectTO> volumeTOs, final String vmUuid,
final String snapshotName, String csTag, SpConnectionDesc conn) {
Map<String, Object> json = new LinkedHashMap<>();
Map<String, String> tags = StorPoolHelper.addStorPoolTags(snapshotName, vmUuid, csTag, null);
List<Map<String, Object>> volumes = new ArrayList<>();
for (VolumeObjectTO volumeTO : volumeTOs) {
Map<String, Object> vol = new LinkedHashMap<>();
String name = StorPoolStorageAdaptor.getVolumeNameFromPath(volumeTO.getPath(), true);
vol.put("name", "");
vol.put("volume", name);
volumes.add(vol);
}
json.put("tags", tags);
json.put("volumes", volumes);
log.info("json:" + json);
return POST("MultiCluster/VolumesGroupSnapshot", json, conn);
}
public static SpApiResponse volumeRevert(final String name, final String snapshotName, SpConnectionDesc conn) {
Map<String, Object> json = new HashMap<>();
json.put("toSnapshot", snapshotName);
return POST("MultiCluster/VolumeRevert/" + name, json, conn);
}
public static SpApiResponse volumeFreeze(final String volumeName, SpConnectionDesc conn) {
return POST("MultiCluster/VolumeFreeze/" + volumeName, null, conn);
}
public static SpApiResponse volumeAcquire(final String volumeName, SpConnectionDesc conn) {
Map<String, Object> json = new HashMap<>();
json.put("onRemoteAttached", "detachForce");
return POST("MultiCluster/VolumeAcquire/" + volumeName, json, conn);
}
public static SpApiResponse volumeDelete(final String name, SpConnectionDesc conn) {
Map<String, Object> json = new HashMap<>();
json.put("onAttached", "detachForce");
return POST("MultiCluster/VolumeDelete/" + name, json, conn);
}
public static SpApiResponse snapshotDelete(final String name, SpConnectionDesc conn) {
SpApiResponse resp = detachAllForced(name, true, conn);
return resp.getError() == null ? POST("MultiCluster/SnapshotDelete/" + name, null, conn) : resp;
}
public static SpApiResponse detachAllForced(final String name, final boolean snapshot, SpConnectionDesc conn) {
final String type = snapshot ? "snapshot" : "volume";
List<Map<String, Object>> json = new ArrayList<>();
Map<String, Object> reassignDesc = new HashMap<>();
reassignDesc.put(type, name);
reassignDesc.put("detach", "all");
reassignDesc.put("force", true);
json.add(reassignDesc);
return POST("MultiCluster/VolumesReassign", json, conn);
}
public static String getSnapshotNameFromResponse(SpApiResponse resp, boolean tildeNeeded, String globalIdOrRemote) {
JsonObject obj = resp.fullJson.getAsJsonObject();
JsonPrimitive data = obj.getAsJsonObject("data").getAsJsonPrimitive(globalIdOrRemote);
String name = data != null ? data.getAsString() : null;
name = name != null ? !tildeNeeded ? name : "~" + name : name;
return name;
}
public static String getNameFromResponse(SpApiResponse resp, boolean tildeNeeded) {
JsonObject obj = resp.fullJson.getAsJsonObject();
JsonPrimitive data = obj.getAsJsonObject("data").getAsJsonPrimitive("name");
String name = data != null ? data.getAsString() : null;
name = name != null ? name.startsWith("~") && !tildeNeeded ? name.split("~")[1] : name : name;
return name;
}
}