/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package com.emc.storageos.api.service.impl.resource.utils; import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.URIUtil; import com.emc.storageos.db.client.constraint.AlternateIdConstraint; import com.emc.storageos.db.client.constraint.ContainmentConstraint; import com.emc.storageos.db.client.constraint.URIQueryResultList; import com.emc.storageos.db.client.model.DiscoveredDataObject; import com.emc.storageos.db.client.model.FSExportMap; import com.emc.storageos.db.client.model.FileExport; import com.emc.storageos.db.client.model.FileExportRule; import com.emc.storageos.db.client.model.FileObject; import com.emc.storageos.db.client.model.FileShare; import com.emc.storageos.db.client.model.Snapshot; import com.emc.storageos.db.client.model.StorageSystem; import com.emc.storageos.db.client.model.StringSet; import com.emc.storageos.db.client.util.CustomQueryUtility; import com.emc.storageos.model.file.ExportRule; import com.emc.storageos.model.file.ExportRules; import com.emc.storageos.model.file.FileExportUpdateParams; import com.emc.storageos.model.file.FileExportUpdateParams.ExportOperationErrorType; import com.emc.storageos.model.file.FileExportUpdateParams.ExportOperationType; import com.emc.storageos.model.file.FileExportUpdateParams.ExportSecurityType; import com.emc.storageos.security.authentication.StorageOSUser; import com.emc.storageos.svcs.errorhandling.resources.APIException; public class ExportVerificationUtility { private static final Logger _log = LoggerFactory .getLogger(ExportVerificationUtility.class); private DbClient _dbClient; private FileShare fs; private Snapshot snapshot; private FileExportUpdateParams param; private StorageOSUser user; public static final String SEC_TYPE = "secType"; public static final String ANON_TYPE = "anon"; public static final String NO_HOSTS_FOUND = "hosts"; private List<String> secFlavorsFound = new ArrayList<>(); private String invalidXMLElementErrorToReport; private List<URI> allSavedURIs = new ArrayList<>(); public ExportVerificationUtility(DbClient dbClient, StorageOSUser user) { _dbClient = dbClient; invalidXMLElementErrorToReport = null; this.user = user; } public void saveAndRetrieveExportURIs(List<ExportRule> listExportRule, ExportOperationType type) { // FileExportRule if (listExportRule == null) { return; } for (ExportRule exportRule : listExportRule) { if (exportRule.isToProceed()) { FileExportRule rule = new FileExportRule(); copyPropertiesToSave(rule, exportRule); rule.setOpType(type.name()); URI uri = URIUtil.createId(FileExportRule.class); rule.setId(uri); _log.debug("Saving Object {}", rule.toString()); _dbClient.createObject(rule); allSavedURIs.add(uri); } continue; } } private void copyPropertiesToSave(FileExportRule dest, ExportRule orig) { if (snapshot != null) { dest.setSnapshotId(snapshot.getId()); dest.setExportPath(snapshot.getPath()); } else { dest.setFileSystemId(fs.getId()); dest.setExportPath(fs.getPath()); } dest.setSecFlavor(orig.getSecFlavor()); dest.setAnon(orig.getAnon()); dest.setReadOnlyHosts(new StringSet(dest.getReadOnlyHosts())); dest.setReadWriteHosts(new StringSet(dest.getReadWriteHosts())); dest.setRootHosts(new StringSet(dest.getRootHosts())); } /** * Since, Modifying an export is not allowed This method verifies the * existing export params with the new one issued to modify. * * @param fs * @param param */ public void verifyExports(FileShare fileShare, Snapshot snapshot, FileExportUpdateParams fsParam) throws Exception { fs = fileShare; param = fsParam; this.snapshot = snapshot; // Add Payload ExportRules exportRules = param.getExportRulesToAdd(); validateExportRules(exportRules, ExportOperationType.ADD); reportErrors(fsParam, ExportOperationType.ADD); // Modify Payload ExportRules exportModifyRules = param.getExportRulesToModify(); validateExportRules(exportModifyRules, ExportOperationType.MODIFY); reportErrors(fsParam, ExportOperationType.MODIFY); // Delete Payload ExportRules exportDeleteRules = param.getExportRulesToDelete(); validateExportRules(exportDeleteRules, ExportOperationType.DELETE); reportErrors(fsParam, ExportOperationType.DELETE); // letThisObjEligibleForGC(); } /** * Verify the list of export rules that can be added * * @param fs * @param listExportRules */ private void validateExportRules(ExportRules listExportRules, ExportOperationType type) throws Exception { if (listExportRules == null) { _log.info("Missing Export Rules - Ignoring the operation type {} ", type.name()); invalidXMLElementErrorToReport = "Missing Export Rules - Ignoring the operation type " + type.name(); return; } switch (type) { case ADD: { verifyAddExportRule(listExportRules.getExportRules()); break; } case MODIFY: { verifyModifyExportRule(listExportRules.getExportRules()); break; } case DELETE: { verifyDeleteExportRule(listExportRules.getExportRules()); break; } } } /** * Verify each export rule that can be added * * @param listExportRule */ private void verifyAddExportRule(List<ExportRule> listExportRule) throws Exception { if (listExportRule == null) { return; } _log.info("Checking if file system is exported before adding export rule"); if (!isFileSystemExported()) { String msg = "File system is not exported. To add export rule, file system must be exported first."; _log.error(msg); String urn = fs != null ? fs.getId().toString() : snapshot.getId().toString(); throw APIException.badRequests.fileSystemNotExported( ExportOperationType.ADD.name(), urn); } _log.info("Number of Export Rule(s) Requested to Add {} - Iterating ..", listExportRule.size()); for (ExportRule exportRule : listExportRule) { exportRule.setIsToProceed(true, ExportOperationErrorType.NO_ERROR); _log.info("Verifying Export Rule {}", exportRule.toString()); // is same security flavor found in several exports - report the error scanForDuplicateSecFlavor(exportRule); if (exportRule.getErrorTypeIfNotToProceed() != null && !(exportRule.getErrorTypeIfNotToProceed().name().equals(ExportOperationErrorType.NO_ERROR.name()))) { _log.info("Same Security Flavor found across the exports {}", exportRule.toString()); break; } // Now validate hosts FileExportRule rule = validateHosts(exportRule); // If same export already found -- Don't allow to add again. if (rule != null) { _log.info("Duplicate Export to Add {} Requested : {}", rule, exportRule); exportRule.setIsToProceed(false, ExportOperationErrorType.EXPORT_EXISTS); break; } // If not found proceed for further verifications. else { if (exportRule.isToProceed()) { _log.info("No Existing Export found in DB {}", exportRule); verifyExportAnon(exportRule); } } } } private void scanForDuplicateSecFlavor(ExportRule rule) { if (rule == null) { return; } String secRuleToValidate = rule.getSecFlavor(); // MULTIPLE_EXPORTS_WITH_SAME_SEC_FLAVOR if (!secFlavorsFound.contains(secRuleToValidate)) { secFlavorsFound.add(rule.getSecFlavor()); } else { rule.setIsToProceed(false, ExportOperationErrorType.MULTIPLE_EXPORTS_WITH_SAME_SEC_FLAVOR); } } /** * Verify each export rule that can be added * * @param fs * @param listExportRule */ private void verifyModifyExportRule(List<ExportRule> listExportRule) throws Exception { if (listExportRule == null) { return; } _log.info("{} Export Rule(s) Requested to Modify {} - Iterating ..", listExportRule.size()); for (ExportRule exportRule : listExportRule) { exportRule.setIsToProceed(true, ExportOperationErrorType.NO_ERROR); _log.info("Verifying Export Rule {}", exportRule.toString()); // is same security flavor found in several exports - report the error scanForDuplicateSecFlavor(exportRule); if (!exportRule.isToProceed()) { _log.info("Same Security Flavor found across the exports {}", exportRule.toString()); break; } FileExportRule rule = validateHosts(exportRule); // If found -- allow to modify. if (rule != null) { verifyExportAnon(exportRule); } // If not, stop proceed further. else { if (exportRule.isToProceed()) { _log.info("Export not found to modify"); exportRule.setIsToProceed(false, ExportOperationErrorType.EXPORT_NOT_FOUND); } } } } /** * Verify each export rule that can be added * * @param fs * @param listExportRule */ private void verifyDeleteExportRule(List<ExportRule> listExportRule) throws Exception { if (listExportRule == null) { return; } _log.info("{} Export Rule(s) Requested to Delete {} - Iterating ..", listExportRule.size()); for (ExportRule exportRule : listExportRule) { exportRule.setIsToProceed(true, ExportOperationErrorType.NO_ERROR); _log.info("Verifying Export Rule {}", exportRule.toString()); // is same security flavor found in several exports - report the error scanForDuplicateSecFlavor(exportRule); if (!exportRule.isToProceed()) { _log.info("Same Security Flavor found across the exports {}", exportRule.toString()); break; } FileExportRule rule = validateInputAndQueryDB(exportRule); // If found -- allow to delete. if (rule != null) { exportRule.setIsToProceed(true, ExportOperationErrorType.NO_ERROR); } // If not, stop proceed further. else { _log.info("Export not found to delete"); exportRule.setIsToProceed(false, ExportOperationErrorType.EXPORT_NOT_FOUND); } } } /** * Verifying the validity of secflavor. If any new in future, verify them at * here. * * @param exportRule */ private void verifyExportAnon(ExportRule exportRule) { if (!exportRule.isToProceed()) { return; } String anon = exportRule.getAnon(); if (anon != null) { anon = anon.toLowerCase(); if (!"nobody".equals(anon) && !anon.equals(user.getName())) { exportRule .setIsToProceed(false, ExportOperationErrorType.INVALID_ANON); } else { exportRule.setIsToProceed(true, ExportOperationErrorType.NO_ERROR); } } else { _log.error("No root user mapping provided."); exportRule .setIsToProceed(false, ExportOperationErrorType.INVALID_ANON); } } /** * Copy the properties and set Fs Id. * * @param dest * @param orig * @param fs * @throws IllegalAccessException * @throws InvocationTargetException */ private void copyProperties(FileExportRule dest, ExportRule orig) { String subDirPath = ""; if (param.getSubDir() != null && param.getSubDir().length() > 0) { subDirPath = "/" + param.getSubDir(); } _log.info("Sub Dir Path : {}", subDirPath); dest.setSecFlavor(orig.getSecFlavor()); if (snapshot != null) { dest.setSnapshotId(snapshot.getId()); dest.setExportPath(snapshot.getPath() + subDirPath); } else { dest.setFileSystemId(fs.getId()); dest.setExportPath(fs.getPath() + subDirPath); } // BeanUtils.copyProperties(dest, orig); _log.info("After copying properties to DB Model Export Rule {}", dest.toString()); } private List<FileExportRule> queryExports(FileShare fs, Snapshot snapshot, boolean isFile) { try { ContainmentConstraint containmentConstraint; if (isFile) { _log.info("Querying all ExportRules Using FsId {}", fs.getId()); containmentConstraint = ContainmentConstraint.Factory.getFileExportRulesConstraint(fs.getId()); } else { URI snapshotId = snapshot.getId(); _log.info("Querying all ExportRules Using Snapshot Id {}", snapshotId); containmentConstraint = ContainmentConstraint.Factory.getSnapshotExportRulesConstraint(snapshotId); } List<FileExportRule> fileExportRules = CustomQueryUtility.queryActiveResourcesByConstraint(_dbClient, FileExportRule.class, containmentConstraint); return fileExportRules; } catch (Exception e) { _log.error("Error while querying {}", e); } return null; } /** * Verify is the rule available in the database * * @param compositeKey * @throws URISyntaxException */ private FileExportRule getAvailableExportRule(FileExportRule exportRule) throws URISyntaxException { String exportIndex = exportRule.getFsExportIndex(); if (snapshot != null) { exportIndex = exportRule.getSnapshotExportIndex(); } _log.info("Retriving DB Model using its index {}", exportIndex); FileExportRule rule = null; URIQueryResultList result = new URIQueryResultList(); if (snapshot != null) { _dbClient.queryByConstraint(AlternateIdConstraint.Factory .getSnapshotExportRuleConstraint(exportIndex), result); } else { _dbClient.queryByConstraint(AlternateIdConstraint.Factory .getFileExportRuleConstraint(exportIndex), result); } Iterator<URI> it = result.iterator(); while (it.hasNext()) { if (result.iterator().hasNext()) { rule = _dbClient.queryObject(FileExportRule.class, it.next()); if (rule != null && !rule.getInactive()) { _log.info("Existing DB Model found {}", rule); break; } else { rule = null; } } } return rule; } /** * Fail the request and report errors accordingly * * @param param * @throws Exception */ private void reportErrors(FileExportUpdateParams param, ExportOperationType type) throws Exception { _log.info("Working on reporting errors found if any"); // if (invalidXMLElementErrorToReport != null) { // throw APIException.badRequests // .invalidFileExportXML(invalidXMLElementErrorToReport); // } switch (type) { case ADD: { reportAddErrors(param); break; } case MODIFY: { reportModifyErrors(param); break; } case DELETE: { reportDeleteErrors(param); break; } } } private void reportAddErrors(FileExportUpdateParams param) throws Exception { String opName = ExportOperationType.ADD.name(); // Report Add Export Errors ExportRules listExportRules = param.getExportRulesToAdd(); if (listExportRules == null || listExportRules.getExportRules().isEmpty()) { return; } List<ExportRule> listExportRule = listExportRules.getExportRules(); for (ExportRule exportRule : listExportRule) { if (!exportRule.isToProceed()) { ExportOperationErrorType error = exportRule .getErrorTypeIfNotToProceed(); switch (error) { case SNAPSHOT_EXPORT_SHOULD_BE_READ_ONLY: { throw APIException.badRequests.snapshotExportPermissionReadOnly(); } case INVALID_SECURITY_TYPE: { if (exportRule.getSecFlavor() != null) { throw APIException.badRequests .invalidSecurityType(exportRule .getSecFlavor()); } else { throw APIException.badRequests .missingInputTypeFound(SEC_TYPE, opName); } } case MULTIPLE_EXPORTS_WITH_SAME_SEC_FLAVOR: { if (exportRule.getSecFlavor() != null) { throw APIException.badRequests .sameSecurityFlavorInMultipleExportsFound(exportRule .getSecFlavor(), opName); } else { throw APIException.badRequests .missingInputTypeFound(SEC_TYPE, opName); } } case INVALID_ANON: { if (exportRule.getAnon() != null) { throw APIException.badRequests .invalidAnon(exportRule.getAnon()); } else { throw APIException.badRequests .missingInputTypeFound(ANON_TYPE, opName); } } case NO_HOSTS_FOUND: { throw APIException.badRequests.missingInputTypeFound( NO_HOSTS_FOUND, opName); } case EXPORT_EXISTS: { throw APIException.badRequests.exportExists(opName, exportRule.toString()); } case STORAGE_SYSTEM_NOT_SUPPORT_MUL_SECS: { StorageSystem system = null; String systemName = ""; if (fs != null) { system = _dbClient.queryObject(StorageSystem.class, fs.getStorageDevice()); } else if (snapshot != null) { FileShare fileSystem = _dbClient.queryObject(FileShare.class, snapshot.getParent()); system = _dbClient.queryObject(StorageSystem.class, fileSystem.getStorageDevice()); } if (system != null) { systemName = system.getSystemType(); } throw APIException.badRequests.storageDoesNotSupportMulSecRule(opName, systemName, exportRule.toString()); } case EXPORT_NOT_FOUND: case NO_ERROR: default: break; } } } } private void reportModifyErrors(FileExportUpdateParams param) throws Exception { String opName = ExportOperationType.MODIFY.name(); // Report Modify Export Errors ExportRules listExportRules = param.getExportRulesToModify(); if (listExportRules == null || listExportRules.getExportRules().isEmpty()) { return; } List<ExportRule> listExportRule = listExportRules.getExportRules(); for (ExportRule exportRule : listExportRule) { if (!exportRule.isToProceed()) { ExportOperationErrorType error = exportRule .getErrorTypeIfNotToProceed(); switch (error) { case SNAPSHOT_EXPORT_SHOULD_BE_READ_ONLY: { throw APIException.badRequests.snapshotExportPermissionReadOnly(); } case EXPORT_NOT_FOUND: { throw APIException.badRequests.exportNotFound(opName, exportRule.toString()); } case INVALID_SECURITY_TYPE: { if (exportRule.getSecFlavor() != null) { throw APIException.badRequests .invalidSecurityType(exportRule .getSecFlavor()); } else { throw APIException.badRequests .missingInputTypeFound(SEC_TYPE, opName); } } case MULTIPLE_EXPORTS_WITH_SAME_SEC_FLAVOR: { if (exportRule.getSecFlavor() != null) { throw APIException.badRequests .sameSecurityFlavorInMultipleExportsFound(exportRule .getSecFlavor(), opName); } else { throw APIException.badRequests .missingInputTypeFound(SEC_TYPE, opName); } } case INVALID_ANON: { if (exportRule.getAnon() != null) { throw APIException.badRequests .invalidAnon(exportRule.getAnon()); } else { throw APIException.badRequests .missingInputTypeFound(ANON_TYPE, opName); } } case NO_HOSTS_FOUND: { throw APIException.badRequests.missingInputTypeFound( NO_HOSTS_FOUND, opName); } case EXPORT_EXISTS: case NO_ERROR: default: break; } } } } private void reportDeleteErrors(FileExportUpdateParams param) throws Exception { String opName = ExportOperationType.DELETE.name(); // Report Delete Export Errors ExportRules listExportRules = param.getExportRulesToDelete(); if (listExportRules == null || listExportRules.getExportRules().isEmpty()) { return; } List<ExportRule> listExportRule = listExportRules.getExportRules(); for (ExportRule exportRule : listExportRule) { if (!exportRule.isToProceed()) { ExportOperationErrorType error = exportRule .getErrorTypeIfNotToProceed(); switch (error) { case INVALID_SECURITY_TYPE: { if (exportRule.getSecFlavor() != null) { throw APIException.badRequests .invalidSecurityType(exportRule .getSecFlavor()); } else { throw APIException.badRequests .missingInputTypeFound(SEC_TYPE, ExportOperationType.DELETE.name()); } } case MULTIPLE_EXPORTS_WITH_SAME_SEC_FLAVOR: { if (exportRule.getSecFlavor() != null) { throw APIException.badRequests .sameSecurityFlavorInMultipleExportsFound(exportRule .getSecFlavor(), opName); } else { throw APIException.badRequests .missingInputTypeFound(SEC_TYPE, opName); } } case EXPORT_NOT_FOUND: { throw APIException.badRequests.exportNotFound( ExportOperationType.DELETE.name(), exportRule.toString()); } case EXPORT_EXISTS: case INVALID_ANON: case NO_ERROR: default: break; } } } } // Disable and handle at device layer private FileExportRule validateHosts(ExportRule exportRule) throws Exception { _log.info("Validating Export hosts"); if (snapshot != null) { // snapshot specific validation - ro permission - can't export - per old code. So adding this validation here. if ((exportRule.getReadWriteHosts() != null && !exportRule.getReadWriteHosts().isEmpty()) || (exportRule.getRootHosts() != null && !exportRule.getRootHosts().isEmpty())) { exportRule.setIsToProceed(false, ExportOperationErrorType.SNAPSHOT_EXPORT_SHOULD_BE_READ_ONLY); _log.info("Snapshot export permission should be read only"); return null; } } return validateInputAndQueryDB(exportRule); } private FileExportRule validateInputAndQueryDB(ExportRule exportRule) throws Exception { FileExportRule rule = null; verifyExportSecurity(exportRule); if (exportRule.isToProceed()) { rule = new FileExportRule(); copyProperties(rule, exportRule); rule = getAvailableExportRule(rule); } return rule; } /** * Verifying the validity of secflavor. If any new in future, verify them at * here. * * @param exportRule */ private void verifyExportSecurity(ExportRule exportRule) { _log.info("Validating Export Security"); try { List<String> secTypes = new ArrayList<String>(); exportRule.setIsToProceed(true, ExportOperationErrorType.NO_ERROR); for (String securityType : exportRule.getSecFlavor().split(",")) { if (!securityType.trim().isEmpty()) { secTypes.add(securityType.trim()); ExportSecurityType secType = ExportSecurityType.valueOf(securityType.trim().toUpperCase()); if (secType == null) { exportRule.setIsToProceed(false, ExportOperationErrorType.INVALID_SECURITY_TYPE); } } } // Multiple security types in a single rule allowed for Isilon storage only!!! if (secTypes.size() > 1) { StorageSystem system = null; if (fs != null) { system = _dbClient.queryObject(StorageSystem.class, fs.getStorageDevice()); } else if (snapshot != null) { FileShare fileSystem = _dbClient.queryObject(FileShare.class, snapshot.getParent()); system = _dbClient.queryObject(StorageSystem.class, fileSystem.getStorageDevice()); } if (!DiscoveredDataObject.Type.isilon.name().equals(system.getSystemType())) { exportRule.setIsToProceed(false, ExportOperationErrorType.STORAGE_SYSTEM_NOT_SUPPORT_MUL_SECS); } } } catch (Exception e) { _log.info("Invalid Security Type found in Request {}", exportRule.getSecFlavor()); exportRule.setIsToProceed(false, ExportOperationErrorType.INVALID_SECURITY_TYPE); } } private void letThisObjEligibleForGC() { _dbClient = null; fs = null; param = null; } private boolean isFileSystemExported() { FileObject fileObject = null; if (fs != null) { fileObject = fs; } else { fileObject = snapshot; } String path = fileObject.getPath(); String subDirectory = param.getSubDir(); if (subDirectory != null && !subDirectory.equalsIgnoreCase("null") && subDirectory.length() > 0) { // Add subdirectory to the path as this is a subdirectory export path += "/" + subDirectory; } FSExportMap exportMap = fileObject.getFsExports(); if (exportMap != null) { Iterator<String> it = fileObject.getFsExports().keySet().iterator(); while (it.hasNext()) { String fsExpKey = it.next(); FileExport fileExport = fileObject.getFsExports().get(fsExpKey); if (fileExport.getPath().equalsIgnoreCase(path)) { _log.info("File system path: {} is exported", path); return true; } } } _log.info("File system path: {} is not exported", path); return false; } }