/*
* Copyright (c) 2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.api.service.impl.resource;
import static com.emc.storageos.api.mapper.FileMapper.map;
import static com.emc.storageos.api.mapper.TaskMapper.toTask;
import java.net.URI;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.emc.storageos.db.client.model.DataObject;
import com.emc.storageos.db.client.model.DataObject.Flag;
import com.emc.storageos.db.client.model.FSExportMap;
import com.emc.storageos.db.client.model.FileShare;
import com.emc.storageos.db.client.model.NamedURI;
import com.emc.storageos.db.client.model.Operation;
import com.emc.storageos.db.client.model.Project;
import com.emc.storageos.db.client.model.SMBShareMap;
import com.emc.storageos.db.client.model.TenantOrg;
import com.emc.storageos.db.client.model.VirtualPool;
import com.emc.storageos.db.exceptions.DatabaseException;
import com.emc.storageos.model.TaskList;
import com.emc.storageos.model.TaskResourceRep;
import com.emc.storageos.model.file.*;
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.svcs.errorhandling.resources.InternalException;
/*
* Internal api for object provisioning
*/
@Path("/internal/file/filesystems")
public class InternalFileResource extends ResourceService {
private final static Logger _log = LoggerFactory.getLogger(InternalFileResource.class);
private static final String EVENT_SERVICE_TYPE = "file";
private static final Project _internalProject = createInternalProject();
private static final DataObject.Flag[] INTERNAL_FILESHARE_FLAGS = new DataObject.Flag[] {
Flag.INTERNAL_OBJECT, Flag.NO_PUBLIC_ACCESS, Flag.NO_METERING };
@Autowired
private FileService _fileService;
/*
* check if the fileshare is accessible via internal api
*/
private void checkFileShareInternal(FileShare fs) {
if (!fs.checkInternalFlags(Flag.INTERNAL_OBJECT)) {
throw APIException.forbidden.internalAPICannotBeUsed();
}
}
/*
* POST to create
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public TaskResourceRep createFileSystemInternal(FileSystemParam param) {
TenantOrg tenant = _permissionsHelper.getRootTenant();
TaskResourceRep rep = null;
if (!_permissionsHelper.userHasGivenRole(getUserFromContext(), tenant.getId(),
Role.SYSTEM_ADMIN, Role.TENANT_ADMIN)) {
rep = new TaskResourceRep();
_log.error("Unable to process the request as Only [system_admin, tenant_admin] can provision file systems for object");
rep.setMessage("Only [system_admin, tenant_admin] can provision file systems for object");
rep.setState(Operation.Status.error.name());
return rep;
}
try {
rep = _fileService.createFSInternal(param, _internalProject, tenant, INTERNAL_FILESHARE_FLAGS);
} catch (Exception ex) {
rep = new TaskResourceRep();
_log.error("Exception occurred while creating file system due to:", ex);
rep.setMessage(ex.getMessage());
rep.setState(Operation.Status.error.name());
}
return rep;
}
/*
* GET filesystem by id
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}")
public FileShareRestRep getFileSystemInternal(@PathParam("id") URI id) {
ArgValidator.checkFieldUriType(id, FileShare.class, "id");
FileShare fs = _fileService.queryResource(id);
checkFileShareInternal(fs);
return map(fs);
}
/*
* GET task status
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/tasks/{op_id}/")
public TaskResourceRep getTaskInternal(@PathParam("id") URI id,
@PathParam("op_id") URI op_id) throws DatabaseException {
ArgValidator.checkFieldUriType(id, FileShare.class, "id");
FileShare fs = _fileService.queryResource(id);
checkFileShareInternal(fs);
return toTask(fs, op_id.toString());
}
/*
* GET list of file system exports
*
* @param id the URN of a ViPR File system
*
* @return File system exports list.
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/exports")
public FileSystemExportList getFileSystemExportListInternal(@PathParam("id") URI id) {
ArgValidator.checkFieldUriType(id, FileShare.class, "id");
FileShare fs = _fileService.queryResource(id);
checkFileShareInternal(fs);
return _fileService.getFileSystemExportList(id);
}
/*
* POST to create a new export
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/exports")
public TaskResourceRep exportInternal(@PathParam("id") URI id, FileSystemExportParam param)
throws InternalException {
ArgValidator.checkFieldUriType(id, FileShare.class, "id");
FileShare fs = _fileService.queryResource(id);
checkFileShareInternal(fs);
return _fileService.export(id, param);
}
/**
* Modifies existing export
*
* @param id the URN of a ViPR file share
* @param protocol protocol to be used for export
* @param securityType security type for export
* @param permissions export permissions
* @param rootUserMapping user mapping for export
* @param updateParam parameter indicating the information to be updated for this export, which contains the list of
* endpoints
* @return returns a task corresponding to this operation
* @throws InternalException
*/
@PUT
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/exports/{protocol},{secType},{perm},{root_mapping}")
public TaskResourceRep modifyExportInternal(@PathParam("id") URI id,
@PathParam("protocol") String protocol, @PathParam("secType") String securityType,
@PathParam("perm") String permissions,
@PathParam("root_mapping") String rootUserMapping,
FileExportUpdateParam updateParam) throws InternalException {
ArgValidator.checkFieldUriType(id, FileShare.class, "id");
FileShare fs = _fileService.queryResource(id);
checkFileShareInternal(fs);
return _fileService.updateExport(id, protocol, securityType, permissions, rootUserMapping, updateParam);
}
/*
* DELETE filesystem export
*/
@DELETE
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/exports/{protocol},{secType},{perm},{root_mapping}")
public TaskResourceRep unexportInternal(@PathParam("id") URI id,
@PathParam("protocol") String protocol, @PathParam("secType") String securityType,
@PathParam("perm") String permissions,
@PathParam("root_mapping") String rootUserMapping,
@QueryParam("subDirectory") String subDirectory) throws InternalException {
ArgValidator.checkFieldUriType(id, FileShare.class, "id");
FileShare fs = _fileService.queryResource(id);
checkFileShareInternal(fs);
return _fileService.unexport(id, protocol, securityType, permissions, rootUserMapping, subDirectory);
}
/*
* POST to deactivate filesystem
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/deactivate")
public TaskResourceRep deactivateFileSystemInternal(@PathParam("id") URI id, FileSystemDeleteParam param)
throws InternalException {
ArgValidator.checkFieldUriType(id, FileShare.class, "id");
FileShare fs = _fileService.queryResource(id);
checkFileShareInternal(fs);
TenantOrg tenant = _permissionsHelper.getRootTenant();
if (!_permissionsHelper.userHasGivenRole(getUserFromContext(), tenant.getId(),
Role.SYSTEM_ADMIN, Role.TENANT_ADMIN)) {
throw APIException.forbidden.onlyAdminsCanDeactivateFileSystems(
Role.SYSTEM_ADMIN.toString(), Role.TENANT_ADMIN.toString());
}
return _fileService.deactivateFileSystem(id, param);
}
/**
* Release a file system from its current tenant & project for internal object usage
*
* @param id the URN of a ViPR file system to be released
* @return the updated file system
* @throws InternalException
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/release")
public FileShareRestRep releaseFileSystemInternal(@PathParam("id") URI id)
throws InternalException {
ArgValidator.checkFieldUriType(id, FileShare.class, "id");
FileShare fs = _fileService.queryResource(id);
// if the FS is already marked as internal, we can skip all this logic
// and just return success down at the bottom
if (!fs.checkInternalFlags(Flag.INTERNAL_OBJECT)) {
URI tenantURI = fs.getTenant().getURI();
if (!_permissionsHelper.userHasGivenRole(getUserFromContext(), tenantURI,
Role.TENANT_ADMIN)) {
throw APIException.forbidden.onlyAdminsCanReleaseFileSystems(
Role.TENANT_ADMIN.toString());
}
// we can't release a fs that has exports
FSExportMap exports = fs.getFsExports();
if ((exports != null) && (!exports.isEmpty())) {
throw APIException.badRequests.cannotReleaseFileSystemExportExists(exports.keySet().toString());
}
// we can't release a fs that has shares
SMBShareMap shares = fs.getSMBFileShares();
if ((shares != null) && (!shares.isEmpty())) {
throw APIException.badRequests.cannotReleaseFileSystemSharesExists(shares.keySet().toString());
}
// files systems with pending operations can't be released
if (fs.getOpStatus() != null) {
for (String opId : fs.getOpStatus().keySet()) {
Operation op = fs.getOpStatus().get(opId);
if (Operation.Status.pending.name().equals(op.getStatus())) {
throw APIException.badRequests.cannotReleaseFileSystemWithTasksPending();
}
}
}
// file systems with snapshots can't be released
Integer snapCount = _fileService.getNumSnapshots(fs);
if (snapCount > 0) {
throw APIException.badRequests.cannotReleaseFileSystemSnapshotExists(snapCount);
}
TenantOrg rootTenant = _permissionsHelper.getRootTenant();
// we can't release the file system to the root tenant if the root tenant has no access
// to the filesystem's virtual pool
ArgValidator.checkFieldNotNull(fs.getVirtualPool(), "virtualPool");
VirtualPool virtualPool = _permissionsHelper.getObjectById(fs.getVirtualPool(), VirtualPool.class);
ArgValidator.checkEntity(virtualPool, fs.getVirtualPool(), false);
if (!_permissionsHelper.tenantHasUsageACL(rootTenant.getId(), virtualPool)) {
throw APIException.badRequests.cannotReleaseFileSystemRootTenantLacksVPoolACL(virtualPool.getId().toString());
}
fs.setOriginalProject(fs.getProject().getURI());
fs.setTenant(new NamedURI(rootTenant.getId(), fs.getLabel()));
fs.setProject(new NamedURI(_internalProject.getId(), fs.getLabel()));
fs.addInternalFlags(INTERNAL_FILESHARE_FLAGS);
_dbClient.updateAndReindexObject(fs);
// audit against the source project, not the new dummy internal project
auditOp(OperationTypeEnum.RELEASE_FILE_SYSTEM, true, null, fs.getId().toString(), fs.getOriginalProject().toString());
}
return map(fs);
}
/**
* Undo the release of a file system
*
* @param id the URN of a ViPR file system to undo
* @return the updated file system
* @throws InternalException
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/release/undo")
public FileShareRestRep undoReleaseFileSystemInternal(@PathParam("id") URI id)
throws InternalException {
ArgValidator.checkFieldUriType(id, FileShare.class, "id");
FileShare fs = _fileService.queryResource(id);
checkFileShareInternal(fs);
URI releasedProject = fs.getOriginalProject();
if (releasedProject == null) {
throw APIException.forbidden.onlyPreviouslyReleasedFileSystemsCanBeUndone();
}
Project project = _permissionsHelper.getObjectById(releasedProject, Project.class);
ArgValidator.checkEntity(project, releasedProject, false);
ArgValidator.checkFieldNotNull(project.getTenantOrg(), "tenantOrg");
ArgValidator.checkFieldNotNull(project.getTenantOrg().getURI(), "tenantOrg");
fs.setTenant(new NamedURI(project.getTenantOrg().getURI(), fs.getLabel()));
fs.setProject(new NamedURI(releasedProject, fs.getLabel()));
fs.setOriginalProject(null);
fs.clearInternalFlags(INTERNAL_FILESHARE_FLAGS);
_dbClient.updateAndReindexObject(fs);
// audit against the new project, not the old dummy internal project
auditOp(OperationTypeEnum.UNDO_RELEASE_FILE_SYSTEM, true, null, fs.getId().toString(), project.getId().toString());
return map(fs);
}
@Override
public String getServiceType() {
return EVENT_SERVICE_TYPE;
}
/**
* Create a non-persisted project object to be used with internal object resources
*
* @return the dummy project
*/
private static Project createInternalProject() {
Project project = new Project();
project.setId(FileShare.INTERNAL_OBJECT_PROJECT_URN);
return project;
}
}