/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.api.service.impl.resource;
import static com.emc.storageos.api.mapper.ComputeMapper.map;
import static com.emc.storageos.api.mapper.TaskMapper.toTask;
import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import javax.ws.rs.Consumes;
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.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import com.emc.storageos.api.mapper.ComputeMapper;
import com.emc.storageos.api.mapper.DbObjectMapper;
import com.emc.storageos.api.service.impl.response.BulkList;
import com.emc.storageos.db.client.URIUtil;
import com.emc.storageos.db.client.constraint.ContainmentConstraint;
import com.emc.storageos.db.client.constraint.URIQueryResultList;
import com.emc.storageos.db.client.model.ComputeImage;
import com.emc.storageos.db.client.model.ComputeImage.ComputeImageStatus;
import com.emc.storageos.db.client.model.ComputeImageJob;
import com.emc.storageos.db.client.model.ComputeImageServer;
import com.emc.storageos.db.client.model.ComputeSystem;
import com.emc.storageos.db.client.model.Operation;
import com.emc.storageos.db.client.model.StringSet;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.imageservercontroller.ImageServerController;
import com.emc.storageos.model.BulkIdParam;
import com.emc.storageos.model.ResourceOperationTypeEnum;
import com.emc.storageos.model.ResourceTypeEnum;
import com.emc.storageos.model.TaskResourceRep;
import com.emc.storageos.model.compute.ComputeImageServerBulkRep;
import com.emc.storageos.model.compute.ComputeImageServerCreate;
import com.emc.storageos.model.compute.ComputeImageServerList;
import com.emc.storageos.model.compute.ComputeImageServerRestRep;
import com.emc.storageos.model.compute.ComputeImageServerUpdate;
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.volumecontroller.AsyncTask;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
/**
* Service class responsible for serving rest requests of ComputeImageServer
*
*
*/
@Path("/compute/imageservers")
@DefaultPermissions(readRoles = { Role.SYSTEM_ADMIN, Role.SYSTEM_MONITOR }, writeRoles = {
Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN })
public class ComputeImageServerService extends TaskResourceService {
private static final Logger log = LoggerFactory
.getLogger(ComputeImageServerService.class);
private static final String EVENT_SERVICE_TYPE = "ComputeImageServer";
private static final String IMAGESERVER_IP = "imageServerIp";
private static final String TFTPBOOTDIR = "tftpBootDir";
private static final String IMAGESERVER_SECONDARY_IP = "imageServerSecondIp";
private static final String IMAGESERVER_PASSWORD = "imageServerPassword";
private static final String IMAGESERVER_USER = "imageServerUser";
private static final String OS_INSTALL_TIMEOUT_MS = "osInstallTimeoutMs";
private static final String IMAGE_SERVER_IMAGEDIR = "image_server_image_directory";
@Override
protected ComputeImageServer queryResource(URI id) {
return queryObject(ComputeImageServer.class, id, false);
}
@Override
protected URI getTenantOwner(URI id) {
return null;
}
@Override
protected ResourceTypeEnum getResourceType() {
return ResourceTypeEnum.COMPUTE_IMAGESERVER;
}
@Override
public String getServiceType() {
return EVENT_SERVICE_TYPE;
}
/**
* Delete the Compute image server
*
* @param id
* the URN of compute image server
*
* @return {@link Response} instance
*/
@POST
@Path("/{id}/deactivate")
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN })
public Response deleteComputeImageServer(@PathParam("id") URI id) {
// Validate the imageServer
log.info("Delete computeImageServer id {} ", id);
ArgValidator.checkFieldUriType(id, ComputeImageServer.class, "id");
ComputeImageServer imageServer = _dbClient.queryObject(
ComputeImageServer.class, id);
ArgValidator.checkEntityNotNull(imageServer, id, isIdEmbeddedInURL(id));
// make sure there are no active jobs associated with this imageserver
checkActiveJobsForImageServer(id);
// Remove the association with the ComputeSystem and then delete the
// imageServer
List<URI> imageServerURIList = _dbClient.queryByType(
ComputeImageServer.class, true);
ArrayList<URI> tempList = Lists.newArrayList(imageServerURIList
.iterator());
if (tempList.size() > 1) {
removeImageServerFromComputeSystem(id);
} else if (tempList.size() == 1) {
// If the imageServer being deleted is the last one,
// then check if there are any valid AVAILABLE images, if so
// throw exception because user cannot delete all imageServers when
// there are valid images available.
boolean hasValidImages = false;
List<URI> imageURIList = _dbClient.queryByType(ComputeImage.class,
true);
Iterator<ComputeImage> imageItr = _dbClient.queryIterativeObjects(
ComputeImage.class, imageURIList);
while (imageItr.hasNext()) {
ComputeImage computeImage = (ComputeImage) imageItr.next();
if (ComputeImageStatus.AVAILABLE.name().equals(
computeImage.getComputeImageStatus())) {
hasValidImages = true;
break;
}
}
if (hasValidImages) {
throw APIException.badRequests.cannotDeleteImageServer();
} else {
removeImageServerFromComputeSystem(id);
}
}
// Set to inactive.
_dbClient.markForDeletion(imageServer);
auditOp(OperationTypeEnum.DELETE_COMPUTE_IMAGESERVER, true, null,
imageServer.getId().toString(), imageServer.getImageServerIp(),
imageServer.getImageServerUser());
return Response.ok().build();
}
/**
* Create the Compute image server
*
* @param createParams
* {@link ComputeImageServerCreate} containing the details
*
* @return {@link TaskResourceRep} instance
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN })
public TaskResourceRep createComputeImageServer(
ComputeImageServerCreate createParams) {
log.info("Create computeImageServer");
String imageServerName = createParams.getName();
String imageServerAddress = createParams.getImageServerIp();
ArgValidator.checkFieldNotEmpty(imageServerName, "imageServerName");
ArgValidator.checkIpIsNotNumeric(imageServerAddress, IMAGESERVER_IP);
checkDuplicateImageServer(null, imageServerAddress, imageServerName);
String bootDir = createParams.getTftpBootDir();
String osInstallAddress = createParams.getImageServerSecondIp();
String username = createParams.getImageServerUser();
String password = createParams.getImageServerPassword();
Integer installTimeout = createParams.getOsInstallTimeout();
Integer sshTimeout = createParams.getSshTimeout();
Integer imageImportTimeout = createParams.getImageImportTimeout();
ArgValidator.checkFieldNotEmpty(bootDir, TFTPBOOTDIR);
ArgValidator.checkIpIsNotNumeric(osInstallAddress,
IMAGESERVER_SECONDARY_IP);
ArgValidator.checkFieldNotEmpty(username, IMAGESERVER_USER);
ArgValidator.checkFieldNotEmpty(password, IMAGESERVER_PASSWORD);
ArgValidator.checkFieldNotNull(installTimeout, OS_INSTALL_TIMEOUT_MS);
ArgValidator.checkFieldRange(installTimeout, 0, 2147483, "seconds", "osInstallTimeout");
ArgValidator.checkFieldNotNull(sshTimeout, OS_INSTALL_TIMEOUT_MS);
ArgValidator.checkFieldRange(sshTimeout, 0, 2147483, "seconds", "sshTimeout");
ArgValidator.checkFieldNotNull(imageImportTimeout, OS_INSTALL_TIMEOUT_MS);
ArgValidator.checkFieldRange(installTimeout, 0, 2147483, "seconds", "imageImportTimeout");
ComputeImageServer imageServer = new ComputeImageServer();
imageServer.setId(URIUtil.createId(ComputeImageServer.class));
imageServer.setLabel(imageServerName);
imageServer.setImageServerIp(imageServerAddress);
imageServer.setTftpBootDir(bootDir);
imageServer.setImageServerUser(username);
imageServer.setImageServerPassword(password);
imageServer.setOsInstallTimeoutMs(new Long(
TimeUnit.SECONDS.toMillis(installTimeout)).intValue());
imageServer.setImageServerSecondIp(osInstallAddress);
imageServer.setImageDir(_coordinator.getPropertyInfo().getProperty(IMAGE_SERVER_IMAGEDIR));
imageServer.setSshTimeoutMs(new Long(
TimeUnit.SECONDS.toMillis(sshTimeout)).intValue());
imageServer.setImageImportTimeoutMs(new Long(
TimeUnit.SECONDS.toMillis(imageImportTimeout)).intValue());
auditOp(OperationTypeEnum.IMAGESERVER_VERIFY_IMPORT_IMAGES, true, null,
imageServer.getId().toString(), imageServer.getImageServerIp());
_dbClient.createObject(imageServer);
ArrayList<AsyncTask> tasks = new ArrayList<AsyncTask>(1);
String taskId = UUID.randomUUID().toString();
AsyncTask task = new AsyncTask(ComputeImageServer.class,
imageServer.getId(), taskId);
tasks.add(task);
Operation op = new Operation();
op.setResourceType(ResourceOperationTypeEnum.CREATE_VERIFY_COMPUTE_IMAGE_SERVER);
_dbClient.createTaskOpStatus(ComputeImageServer.class,
imageServer.getId(), taskId, op);
ImageServerController controller = getController(
ImageServerController.class, null);
controller.verifyImageServerAndImportExistingImages(task, op.getName());
return toTask(imageServer, taskId, op);
}
/**
* Show compute image server attributes.
*
* @param id
* the URN of compute image server
* @brief Show compute image server
* @return Compute image server details
*/
@GET
@Path("/{id}")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public ComputeImageServerRestRep getComputeImageServer(
@PathParam("id") URI id) {
ArgValidator.checkFieldUriType(id, ComputeImageServer.class, "id");
ComputeImageServer imageServer = queryResource(id);
return map(_dbClient, imageServer);
}
/**
* Returns a list of all compute image servers.
*
* @brief Show compute image servers
* @return List of all compute image servers.
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.SYSTEM_MONITOR })
public ComputeImageServerList getComputeImageServers() {
List<URI> ids = _dbClient.queryByType(ComputeImageServer.class, true);
ComputeImageServerList imageServerList = new ComputeImageServerList();
Iterator<ComputeImageServer> iter = _dbClient.queryIterativeObjects(
ComputeImageServer.class, ids);
while (iter.hasNext()) {
ComputeImageServer imageServer = iter.next();
imageServerList.getComputeImageServers().add(
DbObjectMapper.toNamedRelatedResource(imageServer));
}
return imageServerList;
}
/**
* Update the Compute image server details
*
* @param id
* the URN of a ViPR compute image server
*
* @return Updated compute image server information.
*/
@PUT
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}")
@CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN })
public ComputeImageServerRestRep updateComputeImageServer(
@PathParam("id") URI id, ComputeImageServerUpdate param) {
log.info("Update computeImageServer id {} ",id);
ComputeImageServer imageServer = _dbClient.queryObject(
ComputeImageServer.class, id);
if (null == imageServer || imageServer.getInactive()) {
throw APIException.notFound.unableToFindEntityInURL(id);
} else {
StringSet availImages = imageServer.getComputeImages();
// make sure there are no active jobs associated with this imageserver
checkActiveJobsForImageServer(id);
String imageServerName = param.getName();
String imageServerAddress = param.getImageServerIp();
String bootDir = param.getTftpBootDir();
String osInstallAddress = param.getImageServerSecondIp();
String username = param.getImageServerUser();
String password = param.getImageServerPassword();
Integer installTimeout = param.getOsInstallTimeout();
Integer sshTimeout = param.getSshTimeout();
Integer imageImportTimeout = param.getImageImportTimeout();
if (StringUtils.isNotBlank(imageServerName)
&& !imageServerName
.equalsIgnoreCase(imageServer.getLabel())) {
checkDuplicateLabel(ComputeImageServer.class, imageServerName);
imageServer.setLabel(param.getName());
}
if (StringUtils.isNotBlank(imageServerAddress)
&& !imageServerAddress.equalsIgnoreCase(imageServer
.getImageServerIp())) {
checkDuplicateImageServer(id, imageServerAddress, null);
disassociateComputeImages(imageServer);
imageServer.setImageServerIp(imageServerAddress);
}
if(StringUtils.isNotBlank(osInstallAddress)){
imageServer.setImageServerSecondIp(osInstallAddress);
}
if(StringUtils.isNotBlank(username)){
imageServer.setImageServerUser(username);
}
if(null != installTimeout){
ArgValidator.checkFieldRange(installTimeout, 0, 2147483, "seconds", "osInstallTimeout");
imageServer.setOsInstallTimeoutMs(new Long(
TimeUnit.SECONDS.toMillis(installTimeout)).intValue());
}
if(null != sshTimeout){
ArgValidator.checkFieldRange(sshTimeout, 0, 2147483, "seconds", "sshTimeout");
imageServer.setSshTimeoutMs(new Long(
TimeUnit.SECONDS.toMillis(sshTimeout)).intValue());
}
if(null != imageImportTimeout){
ArgValidator.checkFieldRange(imageImportTimeout, 0, 2147483, "seconds", "imageImportTimeout");
imageServer.setImageImportTimeoutMs(new Long(
TimeUnit.SECONDS.toMillis(imageImportTimeout)).intValue());
}
if (StringUtils.isNotBlank(bootDir)) {
if (!CollectionUtils.isEmpty(availImages)
&& !imageServer.getTftpBootDir().equals(bootDir)) {
log.info("Cannot update TFTPBOOT directory, while "
+ "an image server has associated successful import images.");
throw APIException.badRequests
.cannotUpdateTFTPBOOTDirectory();
} else {
imageServer.setTftpBootDir(bootDir);
}
}
if(StringUtils.isNotBlank(password)){
imageServer.setImageServerPassword(password);
}
auditOp(OperationTypeEnum.IMAGESERVER_VERIFY_IMPORT_IMAGES, true,
null, imageServer.getId().toString(),
imageServer.getImageServerIp());
_dbClient.updateObject(imageServer);
ArrayList<AsyncTask> tasks = new ArrayList<AsyncTask>(1);
String taskId = UUID.randomUUID().toString();
AsyncTask task = new AsyncTask(ComputeImageServer.class,
imageServer.getId(), taskId);
tasks.add(task);
Operation op = new Operation();
op.setResourceType(ResourceOperationTypeEnum.UPDATE_VERIFY_COMPUTE_IMAGE_SERVER);
_dbClient.createTaskOpStatus(ComputeImageServer.class,
imageServer.getId(), taskId, op);
ImageServerController controller = getController(
ImageServerController.class, null);
controller.verifyImageServerAndImportExistingImages(task,
op.getName());
}
return map(_dbClient, imageServer);
}
/**
* List data of compute image servers based on input ids.
*
* @param param
* POST data containing the id list.
* @prereq none
* @brief List data of compute image servers
* @return List of representations.
*/
@POST
@Path("/bulk")
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Override
public ComputeImageServerBulkRep getBulkResources(BulkIdParam param) {
return (ComputeImageServerBulkRep) super.getBulkResources(param);
}
@Override
public ComputeImageServerBulkRep queryBulkResourceReps(List<URI> ids) {
Iterator<ComputeImageServer> _dbIterator = _dbClient
.queryIterativeObjects(getResourceClass(), ids);
return new ComputeImageServerBulkRep(BulkList.wrapping(_dbIterator,
COMPUTE_IMAGESERVER_MAPPER));
}
private final ComputeImageServerMapper COMPUTE_IMAGESERVER_MAPPER = new ComputeImageServerMapper();
private class ComputeImageServerMapper implements
Function<ComputeImageServer, ComputeImageServerRestRep> {
@Override
public ComputeImageServerRestRep apply(
final ComputeImageServer imageserver) {
return ComputeMapper.map(_dbClient, imageserver);
}
}
@Override
protected ComputeImageServerBulkRep queryFilteredBulkResourceReps(
List<URI> ids) {
return queryBulkResourceReps(ids);
}
@SuppressWarnings("unchecked")
@Override
public Class<ComputeImageServer> getResourceClass() {
return ComputeImageServer.class;
}
/**
* Check if the given imageServer has any active computeImageJob
* if so throws an appropriate exception
* @param imageServerURI
*/
private void checkActiveJobsForImageServer(URI imageServerURI) {
log.info(
"Check if any active ComputeImageJobs are present for imageServer id {} ",
imageServerURI);
// make sure there are no active jobs associated with this imageserver
URIQueryResultList computeImageJobsUriList = new URIQueryResultList();
_dbClient
.queryByConstraint(
ContainmentConstraint.Factory
.getComputeImageJobsByComputeImageServerConstraint(imageServerURI),
computeImageJobsUriList);
Iterator<URI> iterator = computeImageJobsUriList.iterator();
while (iterator.hasNext()) {
ComputeImageJob job = _dbClient.queryObject(ComputeImageJob.class,
iterator.next());
if (job.getJobStatus().equals(
ComputeImageJob.JobStatus.CREATED.name())) {
throw APIException.badRequests
.cannotDeleteOrUpdateImageServerWhileInUse();
}
}
}
/**
* Removes the given imageServerId from each ComputeSystem present,
* if the computeSystem has the given imageServerId as it association or relation.
* Disassociate's the imageServer from the computeSystem.
* @param imageServerID {@link URI} computeImageServer id
*/
private void removeImageServerFromComputeSystem(URI imageServerID) {
// Remove the association with the ComputeSystem and then delete
// the imageServer
List<URI> computeSystemURIList = _dbClient.queryByType(
ComputeSystem.class, true);
if (computeSystemURIList != null
&& computeSystemURIList.iterator().hasNext()) {
List<ComputeSystem> computeSystems = _dbClient.queryObject(
ComputeSystem.class, computeSystemURIList);
if (!CollectionUtils.isEmpty(computeSystems)) {
for (ComputeSystem computeSystem : computeSystems) {
if (computeSystem.getComputeImageServer() != null
&& computeSystem.getComputeImageServer()
.equals(imageServerID)) {
computeSystem
.setComputeImageServer(NullColumnValueGetter
.getNullURI());
_dbClient.updateObject(computeSystem);
log.info(
"Disassociating imageServer {} from ComputeSystem id {} ",
imageServerID, computeSystem.getId());
}
}
}
}
}
/**
* Check if the imageServer already exists, this method checks by both
* name/label and IP.
* @param id {@link URI} imageServer URI
* @param imageServerAddress {@link String} imageServer IP/FQDN
* @param imageServerName {@link String} label/user given name for imageServer
*/
private void checkDuplicateImageServer(URI id, String imageServerAddress,
String imageServerName) {
if (StringUtils.isNotBlank(imageServerName)) {
checkDuplicateLabel(ComputeImageServer.class, imageServerName);
}
List<URI> existingImageServers = _dbClient.queryByType(
ComputeImageServer.class, false);
for (URI uri : existingImageServers) {
ComputeImageServer existing = _dbClient.queryObject(
ComputeImageServer.class, uri);
if (existing == null || existing.getInactive()
|| existing.getId().equals(id)) {
continue;
}
if (existing.getImageServerIp() != null
&& imageServerAddress != null
&& existing.getImageServerIp().equalsIgnoreCase(
imageServerAddress)) {
throw APIException.badRequests
.resourceExistsWithSameName(imageServerAddress);
}
}
}
/**
* Remove computeImage associations (both success image and failed images
* associations)for a given imageServer
*
* @param imageServer {@link ComputeImageServer} instance
*/
private void disassociateComputeImages(ComputeImageServer imageServer) {
StringSet successImages = imageServer.getComputeImages();
if (!CollectionUtils.isEmpty(successImages)) {
Iterator<String> itr = successImages.iterator();
while (itr.hasNext()) {
itr.next();
itr.remove();
}
}
StringSet failedImages = imageServer.getFailedComputeImages();
if (!CollectionUtils.isEmpty(failedImages)) {
Iterator<String> itr = failedImages.iterator();
while (itr.hasNext()) {
itr.next();
itr.remove();
}
}
}
}