/*
* Copyright (c) 2016 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.systemservices.impl.resource;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.emc.storageos.coordinator.client.model.Site;
import com.emc.storageos.coordinator.client.model.SiteState;
import com.emc.storageos.coordinator.client.model.StorageDriverMetaData;
import com.emc.storageos.coordinator.client.model.StorageDriversInfo;
import com.emc.storageos.coordinator.client.service.CoordinatorClient;
import com.emc.storageos.coordinator.client.service.DrUtil;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.model.StorageProvider;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.StorageSystemType;
import com.emc.storageos.model.storagedriver.StorageDriverList;
import com.emc.storageos.model.storagedriver.StorageDriverRestRep;
import com.emc.storageos.security.audit.AuditLogManager;
import com.emc.storageos.security.authorization.CheckPermission;
import com.emc.storageos.security.authorization.DefaultPermissions;
import com.emc.storageos.security.authorization.Role;
import com.emc.storageos.services.OperationTypeEnum;
import com.emc.storageos.svcs.errorhandling.resources.APIException;
import com.emc.storageos.systemservices.impl.storagedriver.StorageDriverManager;
import com.emc.storageos.systemservices.impl.upgrade.CoordinatorClientExt;
import com.emc.storageos.systemservices.impl.upgrade.LocalRepository;
import com.emc.storageos.systemservices.mapper.StorageDriverMapper;
import com.emc.vipr.model.sys.ClusterInfo;
import com.google.common.io.Files;
import com.sun.jersey.core.header.FormDataContentDisposition;
import com.sun.jersey.multipart.FormDataParam;
/**
* APIs implementation to storage driver lifecycle management such as install,
* uninstall and upgrade.
*/
@Path("/storagedriver")
@DefaultPermissions(readRoles = { Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN }, writeRoles = { Role.SYSTEM_ADMIN,
Role.RESTRICTED_SYSTEM_ADMIN })
public class StorageDriverService {
private static final Logger log = LoggerFactory.getLogger(StorageDriverService.class);
// meta data fields related constants
private static final String META_DEF_FILE_NAME = "metadata.properties";
private static final String DRIVER_NAME = "driver_name";
private static final String DRIVER_VERSION = "driver_version";
private static final Pattern DRIVER_VERSION_PATTERN = Pattern.compile("^\\d+\\.\\d+\\.\\d+\\.\\d+$");
private static final String STORAGE_NAME = "storage_name";
private static final String STORAGE_DISPLAY_NAME = "storage_display_name";
private static final String PROVIDER_NAME = "provider_name";
private static final String PROVIDER_DISPLAY_NAME = "provider_display_name";
private static final String STORAGE_META_TYPE = "meta_type";
private static final String ENABLE_SSL = "enable_ssl";
private static final String NON_SSL_PORT = "non_ssl_port";
private static final String SSL_PORT = "ssl_port";
private static final String DRIVER_CLASS_NAME = "driver_class_name";
private static final String SUPPORT_AUTO_TIER_POLICY = "support_auto_tier_policy";
private static final int DRIVER_VERSION_NUM_SIZE = 4;
private static final Set<String> VALID_META_TYPES = new HashSet<String>(
Arrays.asList(new String[] { "block", "file", "block_and_file", "object" }));
private static final String READY = "READY";
private static final String IN_USE = "IN_USE";
private static final String EVENT_SERVICE_TYPE = "StorageDriver";
// TODO we may need to make these 2 values configurable by overwrite it with
// value from ZK
private static final int MAX_DRIVER_NUMBER = 25;
private static final int MAX_DRIVER_SIZE = 20 * 1024 * 1024; // 20MB
private static final int MAX_DISPLAY_STRING_LENGTH = 50;
private static final String STORAGE_DRIVER_OPERATION_lOCK = "storagedriveroperation";
private static final int LOCK_WAIT_TIME_SEC = 5; // 5 seconds
@Autowired
private AuditLogManager auditMgr;
private CoordinatorClient coordinator;
private CoordinatorClientExt coordinatorExt;
private DbClient dbClient;
private LocalRepository localRepo = LocalRepository.getInstance();
public void setLocalRepo(LocalRepository localRepo) {
this.localRepo = localRepo;
}
public DbClient getDbClient() {
return dbClient;
}
public void setDbClient(DbClient dbClient) {
this.dbClient = dbClient;
}
public void setCoordinatorExt(CoordinatorClientExt coordinatorExt) {
this.coordinatorExt = coordinatorExt;
this.coordinator = coordinatorExt.getCoordinatorClient();
}
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN })
@Path("/{driverName}")
public StorageDriverRestRep getSingleStorageDriver(@PathParam("driverName") String name) {
List<StorageDriverRestRep> drivers = queryStorageDriver(name);
if (drivers.isEmpty()) {
throw APIException.badRequests.driverNameNotFound(name);
}
return drivers.get(0);
}
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN })
public StorageDriverList getStorageDrivers() {
StorageDriverList driverList = new StorageDriverList();
driverList.setDrivers(queryStorageDriver(null));
return driverList;
}
/**
*
* @return driver specified by the name; return all drivers if name is null
*/
private List<StorageDriverRestRep> queryStorageDriver(String name) {
Set<String> usedProviderTypes = getUsedStorageProviderTypes();
Set<String> usedSystemTypes = getUsedStorageSystemTypes();
Map<String, StorageDriverRestRep> driverMap = new HashMap<String, StorageDriverRestRep>();
List<URI> ids = dbClient.queryByType(StorageSystemType.class, true);
Iterator<StorageSystemType> it = dbClient.queryIterativeObjects(StorageSystemType.class, ids);
while (it.hasNext()) {
StorageSystemType type = it.next();
String driverName = type.getDriverName();
if (name != null && !name.equals(driverName)) {
continue;
}
if (type.getIsNative() == null || type.getIsNative()) {
// bypass native storage types
continue;
}
setTypeDriverStatus(type, usedProviderTypes, usedSystemTypes);
setDriverIntoMap(driverName, type, driverMap);
}
List<StorageDriverRestRep> drivers = new ArrayList<StorageDriverRestRep>();
drivers.addAll(driverMap.values());
return drivers;
}
private void setDriverIntoMap(String driverName, StorageSystemType type, Map<String, StorageDriverRestRep> driverMap) {
if (driverMap.containsKey(driverName)) {
StorageDriverRestRep driverRestRep = driverMap.get(driverName);
driverRestRep.getSupportedTypes().add(type.getStorageTypeDispName());
if (!(IN_USE.equals(driverRestRep.getDriverStatus()) && READY.equals(type.getDriverStatus()))) {
driverRestRep.setDriverStatus(type.getDriverStatus());
}
} else {
driverMap.put(type.getDriverName(), StorageDriverMapper.map(type));
}
}
private void setTypeDriverStatus(StorageSystemType type, Set<String> usedProviderTypes,
Set<String> usedSystemTypes) {
if (!StringUtils.equals(type.getDriverStatus(), StorageSystemType.STATUS.ACTIVE.toString())) {
return;
}
type.setDriverStatus(READY);
if (usedProviderTypes.contains(type.getStorageTypeName())
|| usedSystemTypes.contains(type.getStorageTypeName())) {
type.setDriverStatus(IN_USE);
}
}
protected Set<String> getUsedStorageProviderTypes() {
List<URI> ids = dbClient.queryByType(StorageProvider.class, true);
Set<String> types = new HashSet<String>();
Iterator<StorageProvider> it = dbClient.queryIterativeObjects(StorageProvider.class, ids);
while (it.hasNext()) {
types.add(it.next().getInterfaceType());
}
log.info("These storage provider types are being refered: {}", Arrays.toString(types.toArray()));
return types;
}
protected Set<String> getUsedStorageSystemTypes() {
List<URI> ids = dbClient.queryByType(StorageSystem.class, true);
Set<String> types = new HashSet<String>();
Iterator<StorageSystem> it = dbClient.queryIterativeObjects(StorageSystem.class, ids);
while (it.hasNext()) {
types.add(it.next().getSystemType());
}
log.info("These storage system types are being refered: {}", Arrays.toString(types.toArray()));
return types;
}
@GET
@Path("/internal/download")
@Produces({ MediaType.APPLICATION_OCTET_STREAM })
public Response getDriver(@QueryParam("name") String name) throws FileNotFoundException {
log.info("download driver {} ...", name);
InputStream in = new FileInputStream(StorageDriverManager.DRIVER_DIR + name);
return Response.ok(in).type(MediaType.APPLICATION_OCTET_STREAM).build();
}
// Extract this method for the convenience of UT mocking
protected void moveDriverToDataDir(File f) throws IOException {
Files.move(f, new File(StorageDriverManager.DRIVER_DIR + f.getName()));
}
@POST
@CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN })
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response install(@FormDataParam("driver") InputStream uploadedInputStream,
@FormDataParam("driver") FormDataContentDisposition details) {
String fileName = details.getFileName();
precheckForDriverFileName(fileName);
long fileSize = details.getSize();
log.info("Received driver jar file: {}, size: {}", fileName, fileSize);
if (fileSize >= MAX_DRIVER_SIZE) {
throw APIException.badRequests.fileSizeExceedsLimit(MAX_DRIVER_SIZE);
}
precheckForEnv();
File driverFile = saveToTmpDir(fileName, uploadedInputStream);
StorageDriverMetaData metaData = parseDriverMetaData(driverFile);
precheckForMetaData(metaData);
InterProcessLock lock = getStorageDriverOperationLock();
try {
// move file from /tmp to /data/drivers
moveDriverToDataDir(driverFile);
// insert meta data int db
List<StorageSystemType> types = StorageDriverMapper.map(metaData);
for (StorageSystemType type : types) {
type.setDriverStatus(StorageSystemType.STATUS.INSTALLING.toString());
type.setIsNative(false);
dbClient.createObject(type);
log.info("Added storage system type {}, set status to INSTALLING", type.getStorageTypeName());
}
// update local list in ZK
Set<String> localDrivers = localRepo.getLocalDrivers();
StorageDriversInfo info = new StorageDriversInfo();
info.setInstalledDrivers(localDrivers);
coordinatorExt.setNodeSessionScopeInfo(info);
log.info("Updated local driver list to syssvc service beacon: {}", Arrays.toString(localDrivers.toArray()));
// update target list in ZK
info = coordinator.getTargetInfo(StorageDriversInfo.class);
if (info == null) {
info = new StorageDriversInfo();
}
info.getInstalledDrivers().add(metaData.getDriverFileName());
coordinator.setTargetInfo(info);
log.info("Successfully triggered install operation for driver", metaData.getDriverName());
auditOperation(OperationTypeEnum.INSTALL_STORAGE_DRIVER, AuditLogManager.AUDITLOG_SUCCESS,
AuditLogManager.AUDITOP_BEGIN, metaData.getDriverName());
return Response.ok().build();
} catch (Exception e) {
log.error("Error happened when installing driver file", e);
auditOperation(OperationTypeEnum.INSTALL_STORAGE_DRIVER, AuditLogManager.AUDITLOG_FAILURE, null,
metaData.getDriverName());
throw APIException.internalServerErrors.installDriverFailed(e.getMessage());
} finally {
try {
lock.release();
} catch (Exception ignore) {
log.error(String.format("Lock release failed when installing driver %s", metaData.getDriverName()));
}
}
}
@DELETE
@CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN })
@Path("/{driverName}")
public Response uninstall(@PathParam("driverName") String driverName) {
log.info("Start to uninstall driver {} ...", driverName);
Set<String> driverNames = getAllDriverNames();
if (!driverNames.contains(driverName)) {
throw APIException.badRequests.driverNameNotFound(driverName);
}
precheckForEnv();
List<StorageSystemType> toUninstallTypes = filterTypesByDriver(driverName);
precheckForDriverStatus(toUninstallTypes, driverName);
InterProcessLock lock = getStorageDriverOperationLock();
try {
StorageDriversInfo info = coordinator.getTargetInfo(StorageDriversInfo.class);
if (info == null) {
info = new StorageDriversInfo();
}
for (StorageSystemType type : toUninstallTypes) {
type.setDriverStatus(StorageSystemType.STATUS.UNISNTALLING.toString());
dbClient.updateObject(type);
info.getInstalledDrivers().remove(type.getDriverFileName());
}
// update target list in ZK
coordinator.setTargetInfo(info);
log.info("Successfully triggered uninstall operation for driver {}", driverName);
auditOperation(OperationTypeEnum.UNINSTALL_STORAGE_DRIVER, AuditLogManager.AUDITLOG_SUCCESS,
AuditLogManager.AUDITOP_BEGIN, driverName);
return Response.ok().build();
} catch (Exception e) {
log.error("Error happened when installing driver file", e);
auditOperation(OperationTypeEnum.UNINSTALL_STORAGE_DRIVER, AuditLogManager.AUDITLOG_FAILURE, null,
driverName);
throw APIException.internalServerErrors.uninstallDriverFailed(e.getMessage());
} finally {
try {
lock.release();
} catch (Exception ignore) {
log.error(String.format("Lock release failed when uninstalling driver %s", driverName));
}
}
}
private List<StorageSystemType> filterTypesByDriver(String driverName) {
List<StorageSystemType> types = listStorageSystemTypes();
List<StorageSystemType> toUninstallTypes = new ArrayList<StorageSystemType>();
for (StorageSystemType type : types) {
if (!StringUtils.equals(driverName, type.getDriverName())) {
continue;
}
toUninstallTypes.add(type);
}
return toUninstallTypes;
}
private void precheckForDriverStatus(List<StorageSystemType> toUninstallTypes, String driverName) {
Set<String> usedProviderTypes = getUsedStorageProviderTypes();
Set<String> usedSystemTypes = getUsedStorageSystemTypes();
for (StorageSystemType type : toUninstallTypes) {
if (usedProviderTypes.contains(type.getStorageTypeName())
|| usedSystemTypes.contains(type.getStorageTypeName())) {
throw APIException.badRequests.cantUninstallDriverInUse(driverName);
}
}
}
protected File saveToTmpDir(String fileName, InputStream uploadedInputStream) {
File driverFile = new File(StorageDriverManager.TMP_DIR + fileName);
OutputStream os = null;
try {
os = new FileOutputStream(driverFile);
long copiedSize = IOUtils.copyLarge(uploadedInputStream, os, 0, MAX_DRIVER_SIZE);
if (copiedSize >= MAX_DRIVER_SIZE) {
throw APIException.badRequests.fileSizeExceedsLimit(MAX_DRIVER_SIZE);
}
uploadedInputStream.close();
} catch (IOException e) {
log.error("Error happened when uploading driver file", e);
throw APIException.internalServerErrors.installDriverUploadFailed(e.getMessage());
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
log.error("Error happened when closing output stream of driver file {}", fileName, e);
}
}
}
log.info("Finished saving driver file to {}", driverFile.getAbsolutePath());
return driverFile;
}
@POST
@CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN })
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Path("/{driverName}")
public Response upgrade(@PathParam("driverName") String driverName,
@FormDataParam("driver") InputStream uploadedInputStream,
@FormDataParam("driver") FormDataContentDisposition details,
@FormDataParam("force") @DefaultValue("false") Boolean force) {
log.info("Start to upgrade driver for {} ...", driverName);
String fileName = details.getFileName();
precheckForDriverFileName(fileName);
long fileSize = details.getSize();
log.info("Received driver jar file: {}, size: {}", fileName, fileSize);
if (fileSize >= MAX_DRIVER_SIZE) {
throw APIException.badRequests.fileSizeExceedsLimit(MAX_DRIVER_SIZE);
}
precheckForEnv();
File driverFile = saveToTmpDir(fileName, uploadedInputStream);
StorageDriverMetaData metaData = parseDriverMetaData(driverFile);
if (!StringUtils.equals(driverName, metaData.getDriverName())) {
throw APIException.internalServerErrors.upgradeDriverPrecheckFailed(
String.format("Driver name specified in jar file is not %s", driverName));
}
precheckForMetaData(metaData, true, force);
InterProcessLock lock = getStorageDriverOperationLock();
try {
moveDriverToDataDir(driverFile);
// save new meta data to ZK
coordinator.persistServiceConfiguration(metaData.toConfiguration());
// update status to UPGRADING in db
StorageDriversInfo targetInfo = coordinator.getTargetInfo(StorageDriversInfo.class);
if (targetInfo == null) {
targetInfo = new StorageDriversInfo();
}
List<StorageSystemType> types = filterTypesByDriver(driverName);
for (StorageSystemType type : types) {
type.setDriverStatus(StorageSystemType.STATUS.UPGRADING.toString());
dbClient.updateObject(type);
// remove old driver file name from target list
targetInfo.getInstalledDrivers().remove(type.getDriverFileName());
}
coordinator.setTargetInfo(targetInfo);
log.info("Successfully triggered upgrade operation for driver", metaData.getDriverName());
auditOperation(OperationTypeEnum.UPGRADE_STORAGE_DRIVER, AuditLogManager.AUDITLOG_SUCCESS,
AuditLogManager.AUDITOP_BEGIN, driverName);
return Response.ok().build();
} catch (Exception e) {
log.error("Error happened when upgrading driver file", e);
auditOperation(OperationTypeEnum.UPGRADE_STORAGE_DRIVER, AuditLogManager.AUDITLOG_FAILURE, null,
driverName);
throw APIException.internalServerErrors.upgradeDriverFailed(e.getMessage());
} finally {
try {
lock.release();
} catch (Exception ignore) {
log.error(String.format("Lock release failed when upgrading driver %s", metaData.getDriverName()));
}
}
}
private Set<String> getAllDriverNames() {
List<StorageSystemType> types = listStorageSystemTypes();
Set<String> drivers = new HashSet<String>();
for (StorageSystemType type : types) {
drivers.add(type.getDriverName());
}
return drivers;
}
private void precheckForMetaData(StorageDriverMetaData metaData) {
precheckForMetaData(metaData, false, false);
}
private void compareVersion(String oldVersionStr, String newVersionStr) {
String[] oldVersionSegs = oldVersionStr.split("\\.");
String[] newVersionSegs = newVersionStr.split("\\.");
if (oldVersionSegs.length != DRIVER_VERSION_NUM_SIZE || newVersionSegs.length != DRIVER_VERSION_NUM_SIZE) {
throw APIException.internalServerErrors.upgradeDriverPrecheckFailed(
String.format("Invalid driver version format (four numbers separated by dot), old: %s, new: %s",
oldVersionStr, newVersionStr));
}
for (int i = 0; i < DRIVER_VERSION_NUM_SIZE; i ++) {
int oldVersion = Integer.valueOf(oldVersionSegs[i]);
int newVersion = Integer.valueOf(newVersionSegs[i]);
if (newVersion > oldVersion) {
return;
} else if (newVersion < oldVersion) {
throw APIException.internalServerErrors.upgradeDriverPrecheckFailed(String.format(
"new version (%s) should be later than the old one (%s)", newVersionStr, oldVersionStr));
}
}
throw APIException.internalServerErrors.upgradeDriverPrecheckFailed(String.format(
"new version (%s) should be later than the old one (%s)", newVersionStr, oldVersionStr));
}
private void precheckForDupField(String existingValue, String newValue, String fieldName) {
if (StringUtils.equals(existingValue, newValue)) {
throw APIException.internalServerErrors.installDriverPrecheckFailed(
String.format("duplicate %s: %s", fieldName, newValue));
}
}
private void precheckForMetaData(StorageDriverMetaData metaData, boolean upgrade, boolean force) {
List<StorageSystemType> types = listStorageSystemTypes();
Set<String> drivers = new HashSet<String>();
boolean driverNameExists = false;
for (StorageSystemType type : types) {
if (upgrade && StringUtils.equals(type.getDriverName(), metaData.getDriverName())) {
driverNameExists = true;
if (!force) {
String oldVersion = type.getDriverVersion();
String newVersion = metaData.getDriverVersion();
compareVersion(oldVersion, newVersion);
}
} else {
precheckForDupField(type.getDriverName(), metaData.getDriverName(), "driver name");
precheckForDupField(type.getStorageTypeName(), metaData.getStorageName(), "storage name");
precheckForDupField(type.getStorageTypeDispName(), metaData.getStorageDisplayName(), "display name");
precheckForDupField(type.getStorageTypeName(), metaData.getProviderName(), "provider name");
precheckForDupField(type.getStorageTypeDispName(), metaData.getProviderDisplayName(), "provider display name");
precheckForDupField(type.getDriverClassName(), metaData.getDriverClassName(), "driver class name");
}
precheckForDupField(type.getDriverFileName(), metaData.getDriverFileName(), "driver file name");
drivers.add(type.getDriverName());
}
if (upgrade && !driverNameExists) {
throw APIException.internalServerErrors.upgradeDriverPrecheckFailed(
String.format("Can't find specified driver name: %s", metaData.getDriverName()));
}
if (!upgrade && drivers.size() >= MAX_DRIVER_NUMBER) {
throw APIException.internalServerErrors.installDriverPrecheckFailed(String
.format("Can't install more drivers as max driver number %s has been reached", MAX_DRIVER_NUMBER));
}
}
private void precheckForNotEmptyField(String fieldName, String value) {
if (StringUtils.isEmpty(value)) {
throw APIException.internalServerErrors
.installDriverPrecheckFailed(String.format("%s field value is not provided", fieldName));
}
}
private void precheckForDriverFileName (String fileName) {
precheckForNotEmptyField("driver file name", fileName);
if (!fileName.endsWith(".jar") && !fileName.endsWith(".JAR")) {
throw APIException.internalServerErrors
.installDriverPrecheckFailed("driver file name should end with .jar or .JAR as suffix");
}
if (hasForbiddenChar(fileName.substring(0, fileName.length() - ".jar".length()))) {
throw APIException.internalServerErrors.installDriverPrecheckFailed(
"driver file name (not include .jar suffix part) should only contain letter, digit, dash or underline");
}
}
private void precheckForDriverName(String name) {
precheckForNotEmptyField("driver_name", name);
if (name.length() > MAX_DISPLAY_STRING_LENGTH) {
throw APIException.internalServerErrors.installDriverPrecheckFailed(
String.format("driver name is longer than %s", MAX_DISPLAY_STRING_LENGTH));
}
if (hasForbiddenChar(name)) {
throw APIException.internalServerErrors
.installDriverPrecheckFailed("driver name should only contain letter, digit, dash or underline");
}
}
private boolean hasForbiddenChar(String name) {
for (int i = 0; i < name.length(); i ++) {
char c = name.charAt(i);
if (c != '_' && c != '-' && !Character.isLetter(c) && !Character.isDigit(c)) {
return true;
}
}
return false;
}
private void precheckForDriverVersion(String driverVersion) {
precheckForNotEmptyField("driver_version", driverVersion);
if (!DRIVER_VERSION_PATTERN.matcher(driverVersion).find()) {
throw APIException.internalServerErrors.installDriverPrecheckFailed(
"driver_version field value should be four numbers concatenated by dot");
}
}
private void precheckForProviderName(String providerName, String providerDisplayName,
StorageDriverMetaData metaData) {
if (StringUtils.isNotEmpty(providerName) && StringUtils.isNotEmpty(providerDisplayName)) {
metaData.setProviderName(providerName);
metaData.setProviderDisplayName(providerDisplayName);
} else if (StringUtils.isEmpty(providerName) && StringUtils.isEmpty(providerDisplayName)) {
// This driver doesn't support provider, which is allowed, so do
// nothing
} else {
// This is ambiguous input, which should cause exception
throw APIException.internalServerErrors.installDriverPrecheckFailed(
"provider_name and provider_display_name fields values should be both providerd or not");
}
}
private Properties extractPropsFromFile(String driverFilePath) {
Properties props = new Properties();
try {
ZipFile zipFile = new ZipFile(driverFilePath);
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (!META_DEF_FILE_NAME.equals(entry.getName())) {
continue;
}
props.load(zipFile.getInputStream(entry));
}
zipFile.close();
} catch (Exception e) {
log.error("Error happened when parsing meta data from JAR file", e);
// use precheck error for now
throw APIException.internalServerErrors.installDriverPrecheckFailed(e.getMessage());
}
if (props.isEmpty()) {
log.error("Didn't find metadata.properties file or file is empty");
throw APIException.internalServerErrors
.installDriverPrecheckFailed("Didn't find metadata.properties file or file is empty");
}
log.info("Have successfully load meta data properties from JAR file");
return props;
}
protected StorageDriverMetaData parseDriverMetaData(File driverFile) {
String driverFilePath = driverFile.getAbsolutePath();
Properties props = extractPropsFromFile(driverFilePath);
StorageDriverMetaData metaData = new StorageDriverMetaData();
// check driver name
String driverName = props.getProperty(DRIVER_NAME);
precheckForDriverName(driverName);
metaData.setDriverName(driverName);
// check driver version and format
String driverVersion = props.getProperty(DRIVER_VERSION);
precheckForDriverVersion(driverVersion);
metaData.setDriverVersion(driverVersion);
// check storage name
String storageName = props.getProperty(STORAGE_NAME);
precheckForNotEmptyField("storage_name", storageName);
metaData.setStorageName(storageName);
// check storage display name
String storageDisplayName = props.getProperty(STORAGE_DISPLAY_NAME);
precheckForNotEmptyField("storage_display_name", storageDisplayName);
metaData.setStorageDisplayName(storageDisplayName);
// check provider name and provider display name
String providerName = props.getProperty(PROVIDER_NAME);
String providerDisplayName = props.getProperty(PROVIDER_DISPLAY_NAME);
precheckForProviderName(providerName, providerDisplayName, metaData);
// check meta type
String metaType = props.getProperty(STORAGE_META_TYPE);
precheckForMetaType(metaType);
metaData.setMetaType(metaType.toUpperCase());
// check enable_ssl
String enableSslStr = props.getProperty(ENABLE_SSL);
if (StringUtils.isNotEmpty(enableSslStr)) {
boolean enableSsl = Boolean.valueOf(enableSslStr);
metaData.setEnableSsl(enableSsl);
} else {
// default to false
metaData.setEnableSsl(false);
}
// check ssl port
try {
String sslPortStr = props.getProperty(SSL_PORT);
if (StringUtils.isNotEmpty(sslPortStr)) {
long sslPort = 0L;
sslPort = Long.valueOf(sslPortStr);
metaData.setSslPort(sslPort);
}
} catch (NumberFormatException e) {
throw APIException.internalServerErrors.installDriverPrecheckFailed("SSL port format is not valid");
}
// check non ssl port
try {
String nonSslPortStr = props.getProperty(NON_SSL_PORT);
if (StringUtils.isNotEmpty(nonSslPortStr)) {
long nonSslPort = 0L;
nonSslPort = Long.valueOf(nonSslPortStr);
metaData.setNonSslPort(nonSslPort);
}
} catch (NumberFormatException e) {
throw APIException.internalServerErrors.installDriverPrecheckFailed("SSL port format is not valid");
}
// check driver class name
String driverClassName = props.getProperty(DRIVER_CLASS_NAME);
precheckForNotEmptyField("driver_class_name", driverClassName);
metaData.setDriverClassName(driverClassName);
// check if support auto-tier policy
String supportAutoTierStr = props.getProperty(SUPPORT_AUTO_TIER_POLICY);
if (StringUtils.isNotEmpty(supportAutoTierStr)) {
boolean supportAutoTierPolicy = Boolean.valueOf(supportAutoTierStr);
metaData.setSupportAutoTierPolicy(supportAutoTierPolicy);
} else {
// default to false
metaData.setSupportAutoTierPolicy(false);
}
metaData.setDriverFileName(driverFile.getName());
log.info("Parsed result from jar file: {}", metaData.toString());
return metaData;
}
protected void precheckForEnv() {
DrUtil drUtil = new DrUtil(coordinator);
if (!drUtil.isActiveSite()) {
throw APIException.internalServerErrors
.installDriverPrecheckFailed("This operation is not allowed on standby site");
}
for (Site site : drUtil.listSites()) {
SiteState siteState = site.getState();
if (!siteState.equals(SiteState.ACTIVE) && !siteState.equals(SiteState.STANDBY_SYNCED)) {
throw APIException.internalServerErrors.installDriverPrecheckFailed(
String.format("Site %s is in %s state,not active or synced", site.getName(), siteState));
}
ClusterInfo.ClusterState state = coordinator.getControlNodesState(site.getUuid());
if (state != ClusterInfo.ClusterState.STABLE) {
throw APIException.internalServerErrors
.installDriverPrecheckFailed(String.format("Currently site %s is not stable", site.getName()));
}
}
}
protected InterProcessLock getStorageDriverOperationLock() {
// Try to acquire lock, succeed or throw Exception
InterProcessLock lock = coordinator.getSiteLocalLock(STORAGE_DRIVER_OPERATION_lOCK);
boolean acquired;
try {
acquired = lock.acquire(LOCK_WAIT_TIME_SEC, TimeUnit.SECONDS);
} catch (Exception e) {
try {
lock.release();
} catch (Exception ex) {
log.error("Fail to release storage driver operation lock", ex);
}
throw APIException.internalServerErrors.installDriverPrecheckFailed(
"Acquiring lock failed, there's another trigger operation holding lock");
}
if (!acquired) {
throw APIException.internalServerErrors.installDriverPrecheckFailed(
"Acquiring lock failed, there's another trigger operation holding lock");
}
// Check if there's ongoing storage operation, if there is, release lock
// and throw exception
StorageSystemType opOngoingStorageType = null;
List<StorageSystemType> types = listStorageSystemTypes();
for (StorageSystemType type : types) {
String statusStr = type.getDriverStatus();
if (statusStr == null) {
log.info("Bypass type {} as it has no status field value", type.getStorageTypeName());
continue;
}
StorageSystemType.STATUS status = Enum.valueOf(StorageSystemType.STATUS.class, type.getDriverStatus());
if (status.isStorageOperationOngoing()) {
opOngoingStorageType = type;
break;
}
}
if (opOngoingStorageType != null) {
try {
lock.release();
} catch (Exception e) {
log.error("Fail to release storage driver operation lock", e);
}
throw APIException.internalServerErrors.installDriverPrecheckFailed(String.format("Driver %s is in % state",
opOngoingStorageType.getDriverName(), opOngoingStorageType.getDriverStatus()));
}
return lock;
}
private void precheckForMetaType(String metaType) {
if (!isValidMetaType(metaType)) {
throw APIException.internalServerErrors.installDriverPrecheckFailed("meta_type field value is not valid");
}
}
private boolean isValidMetaType(String metaType) {
if (StringUtils.isEmpty(metaType)) {
return false;
}
return VALID_META_TYPES.contains(metaType);
}
private List<StorageSystemType> listStorageSystemTypes() {
List<StorageSystemType> result = new ArrayList<StorageSystemType>();
List<URI> ids = dbClient.queryByType(StorageSystemType.class, true);
Iterator<StorageSystemType> it = dbClient.queryIterativeObjects(StorageSystemType.class, ids);
while (it.hasNext()) {
result.add(it.next());
}
return result;
}
protected void auditOperation(OperationTypeEnum type, String status, String stage, Object... descparams) {
auditMgr.recordAuditLog(null, null, EVENT_SERVICE_TYPE, type, System.currentTimeMillis(), status, stage,
descparams);
}
}