/*
* Copyright (c) 2008-2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.api.service.impl.resource;
import static com.emc.storageos.api.mapper.DbObjectMapper.map;
import static com.emc.storageos.api.mapper.HostMapper.map;
import static com.emc.storageos.api.mapper.TaskMapper.toTask;
import java.net.URI;
import java.util.UUID;
import java.util.Collection;
import java.util.Set;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
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 com.emc.storageos.db.client.URIUtil;
import com.emc.storageos.db.client.constraint.AlternateIdConstraint;
import com.emc.storageos.db.client.constraint.NamedElementQueryResultList;
import com.emc.storageos.db.client.model.*;
import com.emc.storageos.db.client.util.CustomQueryUtility;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.model.compute.VcenterClusterParam;
import com.emc.storageos.vcentercontroller.VcenterController;
import com.emc.storageos.volumecontroller.AsyncTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.emc.storageos.api.mapper.HostMapper;
import com.emc.storageos.api.mapper.functions.MapVcenterDataCenter;
import com.emc.storageos.api.service.authorization.PermissionsHelper;
import com.emc.storageos.api.service.impl.response.BulkList;
import com.emc.storageos.api.service.impl.response.ResRepFilter;
import com.emc.storageos.computesystemcontroller.ComputeSystemController;
import com.emc.storageos.computesystemcontroller.impl.ComputeSystemHelper;
import com.emc.storageos.db.exceptions.DatabaseException;
import com.emc.storageos.model.BulkIdParam;
import com.emc.storageos.model.RelatedResourceRep;
import com.emc.storageos.model.ResourceOperationTypeEnum;
import com.emc.storageos.model.ResourceTypeEnum;
import com.emc.storageos.model.TaskResourceRep;
import com.emc.storageos.model.host.HostList;
import com.emc.storageos.model.host.cluster.ClusterList;
import com.emc.storageos.model.host.vcenter.VcenterDataCenterBulkRep;
import com.emc.storageos.model.host.vcenter.VcenterDataCenterRestRep;
import com.emc.storageos.model.host.vcenter.VcenterDataCenterUpdate;
import com.emc.storageos.security.authentication.StorageOSUser;
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 org.springframework.util.CollectionUtils;
/**
* Service providing APIs for vcenterdatacenter.
*/
@Path("/compute/vcenter-data-centers")
@DefaultPermissions(readRoles = { Role.TENANT_ADMIN, Role.SYSTEM_MONITOR, Role.SYSTEM_ADMIN, Role.SECURITY_ADMIN },
writeRoles = { Role.TENANT_ADMIN, Role.SYSTEM_ADMIN, Role.SECURITY_ADMIN })
public class VcenterDataCenterService extends TaskResourceService {
private static Logger _log = LoggerFactory.getLogger(VcenterDataCenter.class);
private static final String EVENT_SERVICE_TYPE = "vcenterdatacenter";
public String getServiceType() {
return EVENT_SERVICE_TYPE;
}
@Autowired
private HostService _hostService;
@Override
protected URI getTenantOwner(URI id) {
VcenterDataCenter vcenterDataCenter = queryObject(VcenterDataCenter.class, id, false);
return vcenterDataCenter.getTenant();
}
@Override
protected VcenterDataCenter queryResource(URI id) {
return queryObject(VcenterDataCenter.class, id, false);
}
/**
* Shows the data for a vCenter data center
*
* @param id the URN of a ViPR vCenter data center
* @prereq none
* @brief Show vCenter data center
* @return A VcenterDataCenterRestRep reference specifying the data for the
* vCenter data center with the passed id.
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}")
public VcenterDataCenterRestRep getVcenterDataCenter(@PathParam("id") URI id) {
VcenterDataCenter vcenterdatacenter = queryResource(id);
// check the user permissions
verifyAuthorizedSystemOrTenantOrgUser(vcenterdatacenter.getTenant());
return HostMapper.map(vcenterdatacenter);
}
/**
* Update a vCenter data center.
*
* @param id the URN of a ViPR vCenter data center
* @param updateParam the details of the vCenter data center
* @prereq none
* @brief Update vCenter data center
* @return the details of the vCenter data center, including its id and link,
* when update completes successfully.
* @throws DatabaseException when a database error occurs.
*/
@PUT
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.SECURITY_ADMIN, Role.TENANT_ADMIN })
@Path("/{id}")
public VcenterDataCenterRestRep updateVcenterDataCenter(@PathParam("id") URI id,
VcenterDataCenterUpdate updateParam) throws DatabaseException {
VcenterDataCenter dataCenter = queryResource(id);
ArgValidator.checkEntity(dataCenter, id, isIdEmbeddedInURL(id));
if (updateParam.getName() != null && !dataCenter.getLabel().equals(updateParam.getName())) {
checkDuplicateChildName(dataCenter.getVcenter(), VcenterDataCenter.class, "label",
"vcenter", updateParam.getName(), _dbClient);
dataCenter.setLabel(updateParam.getName());
}
checkUserPrivileges(updateParam, dataCenter);
validateTenant(updateParam, dataCenter);
ComputeSystemHelper.updateVcenterDataCenterTenant(_dbClient, dataCenter, updateParam.getTenant());
_dbClient.persistObject(dataCenter);
auditOp(OperationTypeEnum.UPDATE_VCENTER_DATACENTER, true, null,
dataCenter.auditParameters());
return map(dataCenter);
}
/**
* Deactivates the vCenter data center, all its clusters and hosts
*
* @param id the URN of a ViPR vCenter data center to be deactivated
* @prereq none
* @brief Delete vCenter data center
* @return the task used for tracking the deactivation
* @throws DatabaseException when a DB error occurs
*/
@POST
@Path("/{id}/deactivate")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.TENANT_ADMIN })
public TaskResourceRep deactivateVcenterDataCenter(@PathParam("id") URI id,
@DefaultValue("false") @QueryParam("detach-storage") boolean detachStorage) throws DatabaseException {
if (ComputeSystemHelper.isDataCenterInUse(_dbClient, id) && !detachStorage) {
throw APIException.badRequests.resourceHasActiveReferences(VcenterDataCenter.class.getSimpleName(), id);
} else {
VcenterDataCenter dataCenter = queryResource(id);
ArgValidator.checkEntity(dataCenter, id, isIdEmbeddedInURL(id));
String taskId = UUID.randomUUID().toString();
Operation op = _dbClient.createTaskOpStatus(VcenterDataCenter.class, dataCenter.getId(), taskId,
ResourceOperationTypeEnum.DELETE_VCENTER_DATACENTER_STORAGE);
ComputeSystemController controller = getController(ComputeSystemController.class, null);
controller.detachDataCenterStorage(dataCenter.getId(), true, taskId);
auditOp(OperationTypeEnum.DELETE_VCENTER_DATACENTER, true, null,
dataCenter.auditParameters());
return toTask(dataCenter, taskId, op);
}
}
/**
* Detaches storage from the datacenter.
*
* @param id the URN of a ViPR datacenter
* @brief Detach storage from datacenter
* @return the task used for tracking the detach-storage
* @throws DatabaseException when a DB error occurs
*/
@POST
@Path("/{id}/detach-storage")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.TENANT_ADMIN })
public TaskResourceRep detachStorage(@PathParam("id") URI id) throws DatabaseException {
VcenterDataCenter dataCenter = queryObject(VcenterDataCenter.class, id, true);
ArgValidator.checkEntity(dataCenter, id, true);
String taskId = UUID.randomUUID().toString();
Operation op = _dbClient.createTaskOpStatus(Vcenter.class, dataCenter.getId(), taskId,
ResourceOperationTypeEnum.DETACH_VCENTER_DATACENTER_STORAGE);
ComputeSystemController controller = getController(ComputeSystemController.class, null);
controller.detachDataCenterStorage(dataCenter.getId(), false, taskId);
return toTask(dataCenter, taskId, op);
}
/**
* List the clusters in a vCenter data center.
*
* @param id the URN of a ViPR vCenter data center
* @prereq none
* @brief List vCenter data center clusters
* @return The list of clusters of the vCenter data center.
* @throws DatabaseException when a DB error occurs.
*/
@GET
@Path("/{id}/clusters")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public ClusterList getVcenterDataCenterClusters(@PathParam("id") URI id) throws DatabaseException {
VcenterDataCenter dataCenter = queryResource(id);
// check the user permissions
verifyAuthorizedInTenantOrg(dataCenter.getTenant(), getUserFromContext());
ClusterList list = new ClusterList();
list.setClusters(map(ResourceTypeEnum.CLUSTER, listChildren(id, Cluster.class, "label", "vcenterDataCenter")));
return list;
}
/**
* List the hosts in a vCenter data center.
*
* @param id the URN of a ViPR vCenter data center
* @prereq none
* @brief List vCenter data center hosts
* @return The list of hosts of the vCenter data center.
* @throws DatabaseException when a DB error occurs.
*/
@GET
@Path("/{id}/hosts")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public HostList getVcenterDataCenterHosts(@PathParam("id") URI id) throws DatabaseException {
VcenterDataCenter dataCenter = queryResource(id);
// check the user permissions
verifyAuthorizedInTenantOrg(dataCenter.getTenant(), getUserFromContext());
// add all hosts in the data center
HostList list = new HostList();
list.setHosts(map(ResourceTypeEnum.HOST, listChildren(dataCenter.getId(), Host.class, "label", "vcenterDataCenter")));
return list;
}
/**
* List data of specified vCenter data centers.
*
* @param param POST data containing the id list.
* @prereq none
* @brief List data of specified vCenter data centers
* @return list of representations.
*/
@POST
@Path("/bulk")
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Override
public VcenterDataCenterBulkRep getBulkResources(BulkIdParam param) {
return (VcenterDataCenterBulkRep) super.getBulkResources(param);
}
@SuppressWarnings("unchecked")
@Override
public Class<VcenterDataCenter> getResourceClass() {
return VcenterDataCenter.class;
}
@Override
public VcenterDataCenterBulkRep queryBulkResourceReps(List<URI> ids) {
Iterator<VcenterDataCenter> _dbIterator =
_dbClient.queryIterativeObjects(getResourceClass(), ids);
return new VcenterDataCenterBulkRep(BulkList.wrapping(_dbIterator, MapVcenterDataCenter.getInstance()));
}
@Override
public VcenterDataCenterBulkRep queryFilteredBulkResourceReps(List<URI> ids) {
Iterator<VcenterDataCenter> _dbIterator =
_dbClient.queryIterativeObjects(getResourceClass(), ids);
BulkList.ResourceFilter filter = new BulkList.VcenterDataCenterFilter(getUserFromContext(), _permissionsHelper);
return new VcenterDataCenterBulkRep(BulkList.wrapping(_dbIterator, MapVcenterDataCenter.getInstance(), filter));
}
@Override
protected ResourceTypeEnum getResourceType() {
return ResourceTypeEnum.VCENTERDATACENTER;
}
@Override
protected boolean isZoneLevelResource() {
return false;
}
@Override
protected boolean isSysAdminReadableResource() {
return true;
}
public static class VcenterDataCenterResRepFilter<E extends RelatedResourceRep>
extends ResRepFilter<E> {
public VcenterDataCenterResRepFilter(StorageOSUser user,
PermissionsHelper permissionsHelper) {
super(user, permissionsHelper);
}
@Override
public boolean isAccessible(E resrep) {
boolean ret = false;
URI id = resrep.getId();
VcenterDataCenter obj = _permissionsHelper.getObjectById(id, VcenterDataCenter.class);
if (obj == null) {
return false;
}
if (obj.getTenant().toString().equals(_user.getTenantId()) ||
isSecurityAdmin() || isSystemAdmin()) {
return true;
}
ret = isTenantAccessible(obj.getTenant());
return ret;
}
}
/**
* Get object specific permissions filter
*/
@Override
public ResRepFilter<? extends RelatedResourceRep> getPermissionFilter(StorageOSUser user,
PermissionsHelper permissionsHelper)
{
return new VcenterDataCenterResRepFilter(user, permissionsHelper);
}
private boolean isHostCompatibleForVcenterCluster(Host host) {
if (host.getType().equalsIgnoreCase(Host.HostType.Esx.name())) {
_log.info("Host " + host.getLabel() + " is compatible for vCenter cluster operation due to type " + host.getType()
+ " and OS version " + host.getOsVersion());
return true;
} else {
_log.info("Host " + host.getLabel() + " is not compatible for vCenter cluster operation due to type " + host.getType());
return false;
}
}
/*
* @param create - Whether to create a new cluster vs update an existing
*/
private TaskResourceRep createOrUpdateVcenterCluster(boolean createCluster, URI id, URI clusterId, List<URI> addHosts,
List<URI> removeHosts) {
_log.info("createOrUpdateVcenterCluster " + createCluster + " " + id + " " + clusterId);
ArgValidator.checkFieldUriType(id, VcenterDataCenter.class, "id");
VcenterDataCenter vcenterDataCenter = queryObject(VcenterDataCenter.class, id, false);
ArgValidator.checkEntity(vcenterDataCenter, id, isIdEmbeddedInURL(id));
ArgValidator.checkFieldUriType(clusterId, Cluster.class, "clusterId");
Cluster cluster = queryObject(Cluster.class, clusterId, false);
ArgValidator.checkEntity(cluster, clusterId, isIdEmbeddedInURL(clusterId));
verifyAuthorizedInTenantOrg(cluster.getTenant(), getUserFromContext());
/*
* Check if explicit add host or remove hosts are specified
* If one or both are, only execute whats specified
* If nothing is specified, do automatic host selection and import all hosts in cluster
*/
Collection<URI> addHostUris = new ArrayList<URI>();
Collection<URI> removeHostUris = new ArrayList<URI>();
boolean manualHostSpecification = false;
if (addHosts != null && !addHosts.isEmpty()) {
_log.info("Request to explicitly add hosts " + addHosts);
manualHostSpecification = true;
}
if (removeHosts != null && !removeHosts.isEmpty()) {
_log.info("Request to explicitly remove hosts " + removeHosts);
manualHostSpecification = true;
}
if (manualHostSpecification) {
for (URI uri : addHosts) {
Host host = queryObject(Host.class, uri, false);
if (isHostCompatibleForVcenterCluster(host)) {
addHostUris.add(uri);
}
}
for (URI uri : removeHosts) {
Host host = queryObject(Host.class, uri, false);
if (isHostCompatibleForVcenterCluster(host)) {
removeHostUris.add(uri);
}
}
} else {
// If no hosts specified by default add all hosts within cluster
Collection<URI> hostUris = new ArrayList<URI>();
List<NamedElementQueryResultList.NamedElement> hostNamedElements = listChildren(clusterId, Host.class, "label", "cluster");
for (NamedElementQueryResultList.NamedElement hostNamedElement : hostNamedElements) {
Host host = queryObject(Host.class, hostNamedElement.getId(), false);
if (isHostCompatibleForVcenterCluster(host)) {
addHostUris.add(host.getId());
}
}
if (addHostUris.isEmpty()) { // Require at least a single compatible host for automatic mode, do not create empty cluster
_log.error("Cluster " + cluster.getLabel()
+ " does not contain any ESX/ESXi hosts and is thus incompatible for vCenter operations");
throw APIException.badRequests.clusterContainsNoCompatibleHostsForVcenter();
}
}
// Find all shared volumes in the cluster
List<URI> volumeUris = new ArrayList<URI>();
try {
List<ExportGroup> exportGroups = CustomQueryUtility.queryActiveResourcesByConstraint(_dbClient, ExportGroup.class,
AlternateIdConstraint.Factory.getConstraint(ExportGroup.class, "clusters", cluster.getId().toString()));
_log.info("Found " + exportGroups.size() + " export groups for cluster " + cluster.getLabel());
for (ExportGroup exportGroup : exportGroups) {
_log.info("Cluster " + cluster.getLabel() + " has export group " + exportGroup.getLabel() + " of type "
+ exportGroup.getType());
if (exportGroup.forCluster()) {
_log.info("Export group " + exportGroup.getLabel() + " is cluster/shared type");
StringMap volumes = exportGroup.getVolumes();
_log.info("Export group " + exportGroup.getLabel() + " has " + volumes.size() + " volumes");
for (String volumeUriString : volumes.keySet()) {
_log.info("Volume URI " + volumeUriString + " found in export group " + exportGroup.getLabel());
URI uri = URI.create(volumeUriString);
volumeUris.add(uri);
}
}
}
} catch (Exception e) {
_log.error("Exception navigating cluster export groups for shared volumes " + e);
// for time being just ignore
}
Collection<Volume> volumes = _dbClient.queryObject(Volume.class, volumeUris);
for (Volume volume : volumes) {
_log.info("Volume " + volume.getLabel() + " " + volume.getWWN() + " is shared and compatible for VMFS datastore");
}
String taskId = UUID.randomUUID().toString();
Operation op = new Operation();
op.setResourceType(createCluster ? ResourceOperationTypeEnum.CREATE_VCENTER_CLUSTER
: ResourceOperationTypeEnum.UPDATE_VCENTER_CLUSTER);
_dbClient.createTaskOpStatus(VcenterDataCenter.class, vcenterDataCenter.getId(), taskId, op);
AsyncTask task = new AsyncTask(VcenterDataCenter.class, vcenterDataCenter.getId(), taskId);
VcenterController vcenterController = getController(VcenterController.class, null);
if (createCluster) {
vcenterController.createVcenterCluster(task, clusterId, addHostUris.toArray(new URI[0]), volumeUris.toArray(new URI[0]));
} else {
vcenterController.updateVcenterCluster(task, clusterId, addHostUris.toArray(new URI[0]), removeHostUris.toArray(new URI[0]),
volumeUris.toArray(new URI[0]));
}
auditOp(OperationTypeEnum.CREATE_UPDATE_VCENTER_CLUSTER, true, null, vcenterDataCenter.auditParameters());
return toTask(vcenterDataCenter, taskId, op);
}
/**
* Create a new vCenter cluster with all hosts and datastores
*
* @param id the URN of a discovered vCenter datacenter
* @param vcenterClusterParam the URN of the ViPR cluster
* @brief Create a new vCenter cluster with all hosts and datastores
* @return task representation
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/create-vcenter-cluster")
@CheckPermission(roles = { Role.TENANT_ADMIN })
public TaskResourceRep createVcenterCluster(@PathParam("id") URI id, VcenterClusterParam vcenterClusterParam) {
return createOrUpdateVcenterCluster(true, id, vcenterClusterParam.getId(), null, null);
}
/**
* Updates an existing vCenter cluster with new hosts and datastores
*
* @param id the URN of a discovered vCenter datacenter
* @param vcenterClusterParam the URN of the ViPR cluster
* @brief Updates an existing vCenter cluster with new hosts and datastores
* @return task representation
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/update-vcenter-cluster")
@CheckPermission(roles = { Role.TENANT_ADMIN })
public TaskResourceRep updateVcenterCluster(@PathParam("id") URI id, VcenterClusterParam vcenterClusterParam) {
List<URI> addHosts = vcenterClusterParam.getAddHosts();
List<URI> removeHosts = vcenterClusterParam.getRemoveHosts();
return createOrUpdateVcenterCluster(false, id, vcenterClusterParam.getId(), addHosts, removeHosts);
}
/**
* Checks if the user is authorized to view resources in a tenant organization.
* The user can see resources if:
*
* The user is in the tenant organization.
* The user has SysAdmin, SysMonitor, SecAdmin role.
* The user has TenantAdmin role to this tenant organization even
* if the user is in another tenant org
*
* @param tenantId the tenant organization URI
*/
protected void verifyAuthorizedSystemOrTenantOrgUser(URI tenantId) {
if (isSystemAdmin() || isSecurityAdmin()) {
return;
}
StorageOSUser user = getUserFromContext();
verifyAuthorizedInTenantOrg(tenantId, user);
}
/**
* Validates if the tenant in the update param is sharing the
* vCenter or not.
*
* @param updateParam input to update the vCenterDataCenter.
* @param dataCenter the vCenterDataCenter resource to be updated.
*/
private void validateTenant(VcenterDataCenterUpdate updateParam, VcenterDataCenter dataCenter) {
Vcenter vcenter = _dbClient.queryObject(Vcenter.class, dataCenter.getVcenter());
ArgValidator.checkEntity(vcenter, dataCenter.getVcenter(), isIdEmbeddedInURL(dataCenter.getVcenter()));
//Set the current tenant of the datacenter to the updateParam if
//updateParam does not contain the tenant information.
//To set the null tenant for the datacenter, use the string "null".
if (updateParam.getTenant() == null) {
updateParam.setTenant(dataCenter.getTenant());
}
if (NullColumnValueGetter.isNullURI(updateParam.getTenant()) &&
vcenter.getCascadeTenancy()) {
throw APIException.badRequests.cannotRemoveDatacenterTenant(dataCenter.getLabel(), vcenter.getLabel());
}
Set<URI> vcenterTenants = _permissionsHelper.getUsageURIsFromAcls(vcenter.getAcls());
if (!NullColumnValueGetter.isNullURI(updateParam.getTenant()) &&
(CollectionUtils.isEmpty(vcenterTenants) || !vcenterTenants.contains(updateParam.getTenant()))) {
//Since, the given tenant in the update param is not a null URI
//and it is not sharing the vCenter, return the error.
TenantOrg tenant = _dbClient.queryObject(TenantOrg.class, updateParam.getTenant());
throw APIException.badRequests.tenantDoesNotShareTheVcenter(tenant.getLabel(), vcenter.getLabel());
}
}
/**
* Check if the user has a privilege to update the
* vCenterDataCenter's tenant or not.
*
* @param updateParam
* @param dataCenter
*/
private void checkUserPrivileges(VcenterDataCenterUpdate updateParam, VcenterDataCenter dataCenter) {
if(!(isSecurityAdmin() || isSystemAdmin())) {
if(updateParam.getTenant() != null &&
dataCenter.getTenant() != null &&
!URIUtil.identical(updateParam.getTenant(), dataCenter.getTenant())) {
throw APIException.forbidden.insufficientPermissionsForUser(getUserFromContext().getName());
} else if(updateParam.getTenant() == null &&
dataCenter.getTenant() != null) {
throw APIException.forbidden.insufficientPermissionsForUser(getUserFromContext().getName());
} else if(updateParam.getTenant() != null &&
dataCenter.getTenant() == null) {
throw APIException.forbidden.insufficientPermissionsForUser(getUserFromContext().getName());
}
}
}
}