/*
* Copyright (c) 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.DbObjectMapper.toNamedRelatedResource;
import static com.emc.storageos.api.mapper.DbObjectMapper.toRelatedResource;
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.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
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 org.apache.commons.codec.digest.Md5Crypt;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.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.api.mapper.TaskMapper;
import com.emc.storageos.api.service.authorization.PermissionsHelper;
import com.emc.storageos.api.service.impl.resource.utils.AsyncTaskExecutorIntf;
import com.emc.storageos.api.service.impl.resource.utils.DiscoveredObjectTaskScheduler;
import com.emc.storageos.api.service.impl.resource.utils.HostConnectionValidator;
import com.emc.storageos.api.service.impl.resource.utils.VolumeIngestionUtil;
import com.emc.storageos.api.service.impl.response.BulkList;
import com.emc.storageos.api.service.impl.response.ResRepFilter;
import com.emc.storageos.blockorchestrationcontroller.VolumeDescriptor;
import com.emc.storageos.computecontroller.ComputeController;
import com.emc.storageos.computesystemcontroller.ComputeSystemController;
import com.emc.storageos.computesystemcontroller.impl.ComputeSystemHelper;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.URIUtil;
import com.emc.storageos.db.client.constraint.ContainmentConstraint;
import com.emc.storageos.db.client.constraint.NamedElementQueryResultList;
import com.emc.storageos.db.client.constraint.URIQueryResultList;
import com.emc.storageos.db.client.model.Cluster;
import com.emc.storageos.db.client.model.ComputeElement;
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.ComputeVirtualPool;
import com.emc.storageos.db.client.model.DataObject;
import com.emc.storageos.db.client.model.DiscoveredDataObject;
import com.emc.storageos.db.client.model.DiscoveredDataObject.DataCollectionJobStatus;
import com.emc.storageos.db.client.model.DiscoveredDataObject.RegistrationStatus;
import com.emc.storageos.db.client.model.DiscoveredDataObject.Type;
import com.emc.storageos.db.client.model.Host;
import com.emc.storageos.db.client.model.Host.ProvisioningJobStatus;
import com.emc.storageos.db.client.model.HostInterface;
import com.emc.storageos.db.client.model.Initiator;
import com.emc.storageos.db.client.model.IpInterface;
import com.emc.storageos.db.client.model.Operation;
import com.emc.storageos.db.client.model.Project;
import com.emc.storageos.db.client.model.StoragePool;
import com.emc.storageos.db.client.model.StorageProvider;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.StringSet;
import com.emc.storageos.db.client.model.Task;
import com.emc.storageos.db.client.model.TenantOrg;
import com.emc.storageos.db.client.model.UCSServiceProfile;
import com.emc.storageos.db.client.model.UCSServiceProfileTemplate;
import com.emc.storageos.db.client.model.VcenterDataCenter;
import com.emc.storageos.db.client.model.VirtualArray;
import com.emc.storageos.db.client.model.Volume;
import com.emc.storageos.db.client.model.UnManagedDiscoveredObjects.UnManagedExportMask;
import com.emc.storageos.db.client.model.UnManagedDiscoveredObjects.UnManagedVolume;
import com.emc.storageos.db.client.model.util.TaskUtils;
import com.emc.storageos.db.client.util.EndpointUtility;
import com.emc.storageos.db.client.util.FileOperationUtils;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.db.client.util.WWNUtility;
import com.emc.storageos.db.client.util.iSCSIUtility;
import com.emc.storageos.db.exceptions.DatabaseException;
import com.emc.storageos.imageservercontroller.ImageServerController;
import com.emc.storageos.imageservercontroller.impl.ImageServerUtils;
import com.emc.storageos.model.BulkIdParam;
import com.emc.storageos.model.NamedRelatedResourceRep;
import com.emc.storageos.model.RelatedResourceRep;
import com.emc.storageos.model.ResourceOperationTypeEnum;
import com.emc.storageos.model.ResourceTypeEnum;
import com.emc.storageos.model.TaskList;
import com.emc.storageos.model.TaskResourceRep;
import com.emc.storageos.model.block.UnManagedExportMaskList;
import com.emc.storageos.model.block.UnManagedVolumeList;
import com.emc.storageos.model.compute.ComputeElementListRestRep;
import com.emc.storageos.model.compute.ComputeElementRestRep;
import com.emc.storageos.model.compute.ComputeSystemBulkRep;
import com.emc.storageos.model.compute.ComputeSystemRestRep;
import com.emc.storageos.model.compute.OsInstallParam;
import com.emc.storageos.model.file.MountInfoList;
import com.emc.storageos.model.host.ArrayAffinityHostParam;
import com.emc.storageos.model.host.BaseInitiatorParam;
import com.emc.storageos.model.host.HostBulkRep;
import com.emc.storageos.model.host.HostCreateParam;
import com.emc.storageos.model.host.HostList;
import com.emc.storageos.model.host.HostParam;
import com.emc.storageos.model.host.HostRestRep;
import com.emc.storageos.model.host.HostUpdateParam;
import com.emc.storageos.model.host.InitiatorCreateParam;
import com.emc.storageos.model.host.InitiatorList;
import com.emc.storageos.model.host.IpInterfaceCreateParam;
import com.emc.storageos.model.host.IpInterfaceList;
import com.emc.storageos.model.host.IpInterfaceParam;
import com.emc.storageos.model.host.IpInterfaceRestRep;
import com.emc.storageos.model.host.ProvisionBareMetalHostsParam;
import com.emc.storageos.security.audit.AuditLogManager;
import com.emc.storageos.security.authentication.StorageOSUser;
import com.emc.storageos.security.authorization.ACL;
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.svcs.errorhandling.resources.BadRequestException;
import com.emc.storageos.svcs.errorhandling.resources.InternalException;
import com.emc.storageos.volumecontroller.ArrayAffinityAsyncTask;
import com.emc.storageos.volumecontroller.AsyncTask;
import com.emc.storageos.volumecontroller.BlockController;
import com.emc.storageos.volumecontroller.ControllerException;
import com.emc.storageos.volumecontroller.impl.ControllerUtils;
import com.google.common.base.Function;
/**
* A service that provides APIs for viewing, updating and removing hosts and their
* interfaces by authorized users.
*
*/
@DefaultPermissions(readRoles = { Role.TENANT_ADMIN, Role.SYSTEM_MONITOR, Role.SYSTEM_ADMIN }, writeRoles = {
Role.TENANT_ADMIN }, readAcls = { ACL.ANY })
@Path("/compute/hosts")
public class HostService extends TaskResourceService {
// Logger
protected final static Logger _log = LoggerFactory.getLogger(HostService.class);
private static final String EVENT_SERVICE_TYPE = "host";
private static final String BLADE_RESERVATION_LOCK_NAME = "BLADE_RESERVATION_LOCK";
@Autowired
private ComputeSystemService computeSystemService;
@Autowired
private VirtualArrayService virtualArrayService;
@Autowired
private ClusterService clusterService;
@Autowired
private ComputeElementService computeElementService;
@Autowired
private VPlexBlockServiceApiImpl vplexBlockServiceApiImpl;
@Override
public String getServiceType() {
return EVENT_SERVICE_TYPE;
}
private static class DiscoverJobExec implements AsyncTaskExecutorIntf {
private final ComputeSystemController _controller;
DiscoverJobExec(ComputeSystemController controller) {
_controller = controller;
}
@Override
public void executeTasks(AsyncTask[] tasks) throws ControllerException {
_controller.discover(tasks);
}
@Override
public ResourceOperationTypeEnum getOperation() {
return ResourceOperationTypeEnum.DISCOVER_HOST;
}
}
/**
* Gets the information for one host.
*
* @param id
* the URN of a ViPR Host
* @brief Show Host
* @return All the non-null attributes of the host.
* @throws DatabaseException
* when a DB error occurs.
*/
@GET
@Path("/{id}")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public HostRestRep getHost(@PathParam("id") URI id) throws DatabaseException {
Host host = queryObject(Host.class, id, false);
// check the user permissions
verifyAuthorizedInTenantOrg(host.getTenant(), getUserFromContext());
ComputeElement computeElement = null;
UCSServiceProfile serviceProfile = null;
ComputeSystem computeSystem = null;
if (!NullColumnValueGetter.isNullURI(host.getComputeElement())){
computeElement = queryObject(ComputeElement.class, host.getComputeElement(),false);
}
if (!NullColumnValueGetter.isNullURI(host.getServiceProfile())){
serviceProfile = queryObject(UCSServiceProfile.class, host.getServiceProfile(), false);
}
if (serviceProfile!=null){
computeSystem = queryObject(ComputeSystem.class, serviceProfile.getComputeSystem(), false);
}else if (computeElement !=null){
computeSystem = queryObject(ComputeSystem.class, computeElement.getComputeSystem(), false);
}
return map(host, computeElement, serviceProfile, computeSystem);
}
/**
* Lists the id and name for all the hosts that belong to the given tenant organization.
*
* @param tid
* the URN of a ViPR tenant organization
* @prereq none
* @brief List hosts
* @return a list of hosts that belong to the tenant organization.
* @throws DatabaseException
* when a DB error occurs
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public HostList listHosts(@QueryParam("tenant") final URI tid) throws DatabaseException {
URI tenantId;
StorageOSUser user = getUserFromContext();
if (tid == null || StringUtils.isBlank(tid.toString())) {
tenantId = URI.create(user.getTenantId());
} else {
tenantId = tid;
}
// this call validates the tenant id
TenantOrg tenant = _permissionsHelper.getObjectById(tenantId, TenantOrg.class);
ArgValidator.checkEntity(tenant, tenantId, isIdEmbeddedInURL(tenantId), true);
// check the user permissions for this tenant org
verifyAuthorizedInTenantOrg(tenantId, user);
// get all host children
HostList list = new HostList();
list.setHosts(map(ResourceTypeEnum.HOST, listChildren(tenantId, Host.class, "label", "tenant")));
return list;
}
/**
* Updates one or more of the host attributes. Discovery is initiated
* after the host is updated.
*
* @param id
* the URN of a ViPR Host
* @param updateParam
* the parameter that has the attributes to be
* updated.
* @brief Update Host Attributes
* @return the host discovery async task representation.
*/
@PUT
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.TENANT_ADMIN })
@Path("/{id}")
public TaskResourceRep updateHost(@PathParam("id") URI id,
HostUpdateParam updateParam,
@QueryParam("validate_connection") @DefaultValue("false") final Boolean validateConnection,
@QueryParam("update_exports") @DefaultValue("true") boolean updateExports) {
// update the host
Host host = queryObject(Host.class, id, true);
validateHostData(updateParam, host.getTenant(), host, validateConnection);
boolean hasPendingTasks = hostHasPendingTasks(id);
if (hasPendingTasks) {
throw APIException.badRequests.cannotUpdateHost("another operation is in progress for this host");
}
URI oldClusterURI = host.getCluster();
URI newClusterURI = updateParam.getCluster();
populateHostData(host, updateParam);
if (updateParam.getHostName() != null) {
ComputeSystemHelper.updateInitiatorHostName(_dbClient, host);
}
String taskId = UUID.randomUUID().toString();
Operation op = _dbClient.createTaskOpStatus(Host.class, id,
taskId, ResourceOperationTypeEnum.UPDATE_HOST);
ComputeSystemController controller = getController(ComputeSystemController.class, null);
boolean updateTaskStatus = true;
// We only want to update the export group if we're changing the cluster during a host update
if (newClusterURI != null) {
updateTaskStatus = false;
// VBDU [DONE]: COP-28451, The first if block never gets executed, need to understand the impact.
// This is run when a clustered host is moved out of a cluster
if (updateExports && !NullColumnValueGetter.isNullURI(oldClusterURI)
&& NullColumnValueGetter.isNullURI(newClusterURI)
&& ComputeSystemHelper.isClusterInExport(_dbClient, oldClusterURI)) {
// Remove host from shared export
controller.removeHostsFromExport(Arrays.asList(host.getId()), oldClusterURI, false, updateParam.getVcenterDataCenter(),
taskId);
} else if (updateExports && NullColumnValueGetter.isNullURI(oldClusterURI)
&& !NullColumnValueGetter.isNullURI(newClusterURI)
&& ComputeSystemHelper.isClusterInExport(_dbClient, newClusterURI)) {
// Non-clustered host being added to a cluster
controller.addHostsToExport(Arrays.asList(host.getId()), newClusterURI, taskId, oldClusterURI, false);
} else if (updateExports && !NullColumnValueGetter.isNullURI(oldClusterURI)
&& !NullColumnValueGetter.isNullURI(newClusterURI)
&& !oldClusterURI.equals(newClusterURI)
&& (ComputeSystemHelper.isClusterInExport(_dbClient, oldClusterURI)
|| ComputeSystemHelper.isClusterInExport(_dbClient, newClusterURI))) {
// Clustered host being moved to another cluster
controller.addHostsToExport(Arrays.asList(host.getId()), newClusterURI, taskId, oldClusterURI, false);
} else if (updateExports && !NullColumnValueGetter.isNullURI(oldClusterURI)
&& !NullColumnValueGetter.isNullURI(newClusterURI)
&& oldClusterURI.equals(newClusterURI)
&& ComputeSystemHelper.isClusterInExport(_dbClient, newClusterURI)) {
// Cluster hasn't changed but we should add host to the shared exports for this cluster
controller.addHostsToExport(Arrays.asList(host.getId()), newClusterURI, taskId, oldClusterURI, false);
} else {
updateTaskStatus = true;
ComputeSystemHelper.updateHostAndInitiatorClusterReferences(_dbClient, newClusterURI, host.getId());
}
}
_dbClient.updateObject(host);
auditOp(OperationTypeEnum.UPDATE_HOST, true, null,
host.auditParameters());
return doDiscoverHost(host.getId(), taskId, updateTaskStatus);
}
/**
* Updates the hosts boot volume Id
*
* @param id the URN of host
* @param hostUpdateParam
*
* @return the task.
*/
@PUT
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.TENANT_ADMIN })
@Path("/{id}/update-boot-volume")
public TaskResourceRep updateBootVolume(@PathParam("id") URI id, HostUpdateParam param) {
Host host = queryObject(Host.class, id, true);
boolean hasPendingTasks = hostHasPendingTasks(id);
boolean updateSanBootTargets = param.getUpdateSanBootTargets();
if (hasPendingTasks) {
throw APIException.badRequests.cannotUpdateHost("another operation is in progress for this host");
}
auditOp(OperationTypeEnum.UPDATE_HOST_BOOT_VOLUME, true, null,
host.auditParameters());
String taskId = UUID.randomUUID().toString();
ComputeSystemController controller = getController(ComputeSystemController.class, null);
Operation op = _dbClient.createTaskOpStatus(Host.class, id, taskId,
ResourceOperationTypeEnum.UPDATE_HOST_BOOT_VOLUME);
//The volume being set as the boot volume should be exported to the host and should not be exported to any other initiators.
// The controller call invoked below validates that before setting the volume as the boot volume.
controller.setHostBootVolume(host.getId(), param.getBootVolume(), updateSanBootTargets, taskId);
return toTask(host, taskId, op);
}
/**
* Discovers (refreshes) a host. This is an asynchronous call.
*
* @param id
* The URI of the host.
* @prereq none
* @brief Discover host
* @return TaskResourceRep (asynchronous call)
*/
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/discover")
@CheckPermission(roles = { Role.TENANT_ADMIN })
public TaskResourceRep discoverHost(@PathParam("id") URI id) {
ArgValidator.checkFieldUriType(id, Host.class, "id");
return doDiscoverHost(id, null, true);
}
/**
* Host Discovery
*
* @param host {@link Host} The Host to be discovered.
* @param taskId {@link String} taskId for the host discovery. as new taskId is generated if null passed.
* @param updateTaskStatus if true, mark the task status as completed for non-discovered host
* @return the task used to track the discovery job
*/
protected TaskResourceRep doDiscoverHost(URI hostId, String taskId, boolean updateTaskStatus) {
Host host = queryObject(Host.class, hostId, true);
if (taskId == null) {
taskId = UUID.randomUUID().toString();
}
if (host.getDiscoverable() != null && !host.getDiscoverable()) {
host.setDiscoveryStatus(DataCollectionJobStatus.COMPLETE.name());
_dbClient.updateObject(host);
}
if ((host.getDiscoverable() == null || host.getDiscoverable())) {
ComputeSystemController controller = getController(ComputeSystemController.class, "host");
DiscoveredObjectTaskScheduler scheduler = new DiscoveredObjectTaskScheduler(
_dbClient, new DiscoverJobExec(controller));
ArrayList<AsyncTask> tasks = new ArrayList<AsyncTask>(1);
tasks.add(new AsyncTask(Host.class, host.getId(), taskId));
TaskList taskList = scheduler.scheduleAsyncTasks(tasks);
return taskList.getTaskList().iterator().next();
} else {
// if not discoverable, manually create a ready task
Operation op = new Operation();
op.setResourceType(ResourceOperationTypeEnum.DISCOVER_HOST);
if (updateTaskStatus) {
op.ready("Host is not discoverable");
} else {
op.pending();
}
_dbClient.createTaskOpStatus(Host.class, host.getId(), taskId, op);
return toTask(host, taskId, op);
}
}
/**
* Discovers the array affinity information on all supported storage systems for the given hosts.
* This is an asynchronous call.
*
* @param param
* ArrayAffinityHostParam
* @return the task list
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/discover-array-affinity")
@CheckPermission(roles = { Role.TENANT_ADMIN })
public TaskList discoverArrayAffinityForHosts(ArrayAffinityHostParam param) {
List<URI> hostIds = param.getHosts();
ArgValidator.checkFieldNotEmpty(hostIds, "hosts");
// validate host URIs
for (URI hostId : hostIds) {
ArgValidator.checkFieldUriType(hostId, Host.class, "host");
queryObject(Host.class, hostId, true);
}
return createHostArrayAffinityTasks(hostIds);
}
/**
* Create array affinity tasks for hosts.
*
* @param hostIds
* the hosts whose preferred systems need to be discovered
*/
public TaskList createHostArrayAffinityTasks(List<URI> hostIds) {
TaskList taskList = new TaskList();
String taskId = UUID.randomUUID().toString();
String jobType = "";
Map<URI, List<URI>> providerToSystemsMap = new HashMap<URI, List<URI>>();
Map<URI, String> providerToSystemTypeMap = new HashMap<URI, String>();
List<URI> sysURIs = _dbClient.queryByType(StorageSystem.class, true);
Iterator<StorageSystem> storageSystems = _dbClient.queryIterativeObjects(StorageSystem.class, sysURIs);
while (storageSystems.hasNext()) {
StorageSystem systemObj = storageSystems.next();
if (systemObj == null) {
_log.warn("StorageSystem is no longer in the DB. It could have been deleted or decommissioned");
continue;
}
if (systemObj.deviceIsType(Type.vmax) || systemObj.deviceIsType(Type.vnxblock) || systemObj.deviceIsType(Type.xtremio)) {
if (systemObj.getActiveProviderURI() == null
|| NullColumnValueGetter.getNullURI().equals(systemObj.getActiveProviderURI())) {
_log.info("Skipping {} Job : StorageSystem {} does not have an active provider",
jobType, systemObj.getLabel());
continue;
}
StorageProvider provider = _dbClient.queryObject(StorageProvider.class,
systemObj.getActiveProviderURI());
if (provider == null || provider.getInactive()) {
_log.info("Skipping {} Job : StorageSystem {} does not have a valid active provider",
jobType, systemObj.getLabel());
continue;
}
List<URI> systemIds = providerToSystemsMap.get(provider.getId());
if (systemIds == null) {
systemIds = new ArrayList<URI>();
providerToSystemsMap.put(provider.getId(), systemIds);
providerToSystemTypeMap.put(provider.getId(), systemObj.getSystemType());
}
systemIds.add(systemObj.getId());
} else if (systemObj.deviceIsType(Type.unity)) {
List<URI> systemIds = new ArrayList<URI>();
systemIds.add(systemObj.getId());
providerToSystemsMap.put(systemObj.getId(), systemIds);
providerToSystemTypeMap.put(systemObj.getId(), systemObj.getSystemType());
} else {
_log.info("Skip unsupported system {}, system type {}", systemObj.getLabel(), systemObj.getSystemType());
continue;
}
}
for (Map.Entry<URI, List<URI>> entry : providerToSystemsMap.entrySet()) {
List<URI> systemIds = entry.getValue();
BlockController controller = getController(BlockController.class, providerToSystemTypeMap.get(entry.getKey()));
DiscoveredObjectTaskScheduler scheduler = new DiscoveredObjectTaskScheduler(_dbClient,
new StorageSystemService.ArrayAffinityJobExec(controller));
ArrayList<AsyncTask> tasks = new ArrayList<AsyncTask>();
tasks.add(new ArrayAffinityAsyncTask(StorageSystem.class, systemIds, hostIds, taskId));
taskList.getTaskList().addAll(scheduler.scheduleAsyncTasks(tasks).getTaskList());
}
return taskList;
}
/**
* Validates the create/update host input data
*
* @param hostParam
* the input parameter
* @param host
* the host being updated in case of update operation.
* This parameter must be null for create operations.n
*/
protected void validateHostData(HostParam hostParam, URI tenanUri, Host host, Boolean validateConnection) {
Cluster cluster = null;
VcenterDataCenter dataCenter = null;
Project project = null;
Volume volume = null;
// validate the host type
if (hostParam.getType() != null) {
ArgValidator.checkFieldValueFromEnum(hostParam.getType(), "Type", Host.HostType.class);
}
// validate the project is present, active, and in the same tenant org
if (!NullColumnValueGetter.isNullURI(hostParam.getProject())) {
project = queryObject(Project.class, hostParam.getProject(), true);
if (!project.getTenantOrg().getURI().equals(tenanUri)) {
throw APIException.badRequests.resourcedoesNotBelongToHostTenantOrg("project");
}
}
if (!NullColumnValueGetter.isNullURI(hostParam.getBootVolume())) {
volume = queryObject(Volume.class, hostParam.getBootVolume(), true);
if (!volume.getTenant().getURI().equals(tenanUri)) {
throw APIException.badRequests.resourcedoesNotBelongToHostTenantOrg("boot volume");
}
}
// validate the cluster is present, active, and in the same tenant org
if (!NullColumnValueGetter.isNullURI(hostParam.getCluster())) {
cluster = queryObject(Cluster.class, hostParam.getCluster(),
true);
if (!cluster.getTenant().equals(tenanUri)) {
throw APIException.badRequests.resourcedoesNotBelongToHostTenantOrg("cluster");
}
}
// validate the data center is present, active, and in the same tenant org
if (!NullColumnValueGetter.isNullURI(hostParam.getVcenterDataCenter())) {
dataCenter = queryObject(VcenterDataCenter.class, hostParam.getVcenterDataCenter(),
true);
if (!dataCenter.getTenant().equals(tenanUri)) {
throw APIException.badRequests.resourcedoesNotBelongToHostTenantOrg("data center");
}
}
if (cluster != null) {
if (dataCenter != null) {
// check the cluster and data center are consistent
if (!dataCenter.getId().equals(cluster.getVcenterDataCenter())) {
throw APIException.badRequests.invalidParameterClusterNotInDataCenter(cluster.getLabel(), dataCenter.getLabel());
}
} else if (project != null) {
// check the cluster and data center are consistent
if (!project.getId().equals(cluster.getProject())) {
throw APIException.badRequests.invalidParameterClusterNotInHostProject(cluster.getLabel());
}
}
}
// check the host name is not a duplicate
if (host == null || (hostParam.getHostName() != null &&
!hostParam.getHostName().equals(host.getHostName()))) {
checkDuplicateAltId(Host.class, "hostName", EndpointUtility.changeCase(hostParam.getHostName()), "host");
}
// check the host label is not a duplicate
if (host == null || (hostParam.getName() != null &&
!hostParam.getName().equals(host.getLabel()))) {
checkDuplicateLabel(Host.class, hostParam.getName());
}
// If the host project is being changed, check for active exports
if (host != null && !areEqual(host.getProject(), hostParam.getProject())) {
if (ComputeSystemHelper.isHostInUse(_dbClient, host.getId())) {
throw APIException.badRequests.hostProjectChangeNotAllowed(host.getHostName());
}
}
// Find out if the host should be discoverable by checking input and current values
Boolean discoverable = hostParam.getDiscoverable() == null ? (host == null ? Boolean.FALSE : host.getDiscoverable())
: hostParam.getDiscoverable();
boolean vCenterManaged = host == null ? false : Host.HostType.Esx.name().equals(host.getType())
&& !NullColumnValueGetter.isNullURI(host.getVcenterDataCenter());
// If discoverable, ensure username and password are set in the current host or parameters
if (!vCenterManaged && discoverable != null && discoverable) {
String username = hostParam.getUserName() == null ? (host == null ? null : host.getUsername())
: hostParam.getUserName();
String password = hostParam.getPassword() == null ? (host == null ? null : host.getPassword())
: hostParam.getPassword();
ArgValidator.checkFieldNotNull(username, "username");
ArgValidator.checkFieldNotNull(password, "password");
Host.HostType hostType = Host.HostType.valueOf(
hostParam.getType() == null ? (host == null ? null : host.getType()) : hostParam.getType());
if (hostType != null && hostType == Host.HostType.Windows) {
Integer portNumber = hostParam.getPortNumber() == null ? (host == null ? null : host.getPortNumber())
: hostParam.getPortNumber();
ArgValidator.checkFieldNotNull(portNumber, "port_number");
}
}
if (validateConnection != null && validateConnection == true) {
if (!HostConnectionValidator.isHostConnectionValid(hostParam, host)) {
throw APIException.badRequests.invalidHostConnection();
}
}
}
/**
* Deactivates the host and all its interfaces.
*
* @param id
* the URN of a ViPR Host to be deactivated
* @param detachStorage
* if true, will first detach storage.
* @param detachStorageDeprecated
* Deprecated. Use detachStorage instead.
* @param deactivateBootVolume
* if true, and if the host was provisioned by ViPR the associated boot volume (if exists) will be
* deactivated
* @brief Deactivate Host
* @return OK if deactivation completed successfully
* @throws DatabaseException
* when a DB error occurs
*/
@POST
@Path("/{id}/deactivate")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.TENANT_ADMIN })
public TaskResourceRep deactivateHost(@PathParam("id") URI id,
@DefaultValue("false") @QueryParam("detach_storage") boolean detachStorage,
@DefaultValue("false") @QueryParam("detach-storage") boolean detachStorageDeprecated,
@DefaultValue("true") @QueryParam("deactivate_boot_volume") boolean deactivateBootVolume) throws DatabaseException {
Host host = queryHost(_dbClient, id);
ArgValidator.checkEntity(host, id, true);
boolean hasPendingTasks = hostHasPendingTasks(id);
if (hasPendingTasks) {
throw APIException.badRequests.resourceCannotBeDeleted("Host with another operation in progress");
}
boolean isHostInUse = ComputeSystemHelper.isHostInUse(_dbClient, host.getId());
if (isHostInUse && !(detachStorage || detachStorageDeprecated)) {
throw APIException.badRequests.resourceHasActiveReferences(Host.class.getSimpleName(), id);
}
UCSServiceProfile serviceProfile = null;
if (!NullColumnValueGetter.isNullURI(host.getServiceProfile())){
serviceProfile = _dbClient.queryObject(UCSServiceProfile.class, host.getServiceProfile());
if (serviceProfile!=null && !NullColumnValueGetter.isNullURI(serviceProfile.getComputeSystem())){
ComputeSystem ucs = _dbClient.queryObject(ComputeSystem.class, serviceProfile.getComputeSystem());
if ( ucs!=null && ucs.getDiscoveryStatus().equals(DataCollectionJobStatus.ERROR.name())){
throw APIException.badRequests.resourceCannotBeDeleted("Host has service profile on a Compute System that failed to discover; ");
}
}
}
Collection<URI> hostIds = _dbClient.queryByType(Host.class, true);
Collection<Host> hosts = _dbClient.queryObjectFields(Host.class,
Arrays.asList("label", "uuid", "serviceProfile", "computeElement","registrationStatus", "inactive"), ControllerUtils.getFullyImplementedCollection(hostIds));
for (Host tempHost : hosts){
if (!tempHost.getId().equals(host.getId()) && !tempHost.getInactive()){
if (tempHost.getUuid()!=null && tempHost.getUuid().equals(host.getUuid())) {
throw APIException.badRequests.resourceCannotBeDeleted("Host "+ host.getLabel()+ " shares same uuid "+ host.getUuid() +
" with another active host " + tempHost.getLabel() + " with URI: "+ tempHost.getId().toString()+ " and ");
}
if (!NullColumnValueGetter.isNullURI(host.getComputeElement()) && host.getComputeElement()== tempHost.getComputeElement()){
throw APIException.badRequests.resourceCannotBeDeleted("Host "+ host.getLabel() + " shares same computeElement "+ host.getComputeElement() +
" with another active host " + tempHost.getLabel() + " with URI: "+ tempHost.getId().toString()+ " and ");
}
if (!NullColumnValueGetter.isNullURI(host.getServiceProfile()) && host.getServiceProfile()== tempHost.getServiceProfile()){
throw APIException.badRequests.resourceCannotBeDeleted("Host "+ host.getLabel()+ " shares same serviceProfile "+ host.getServiceProfile() +
" with another active host " + tempHost.getLabel() + " with URI: "+ tempHost.getId().toString()+ " and ");
}
}
}
if (!NullColumnValueGetter.isNullURI(host.getComputeElement()) && NullColumnValueGetter.isNullURI(host.getServiceProfile())){
throw APIException.badRequests.resourceCannotBeDeletedVblock(host.getLabel(), "Host "+ host.getLabel()+ " has a compute element, but no service profile."
+" Please re-discover the Vblock Compute System and retry.");
}
// VBDU [DONE]: COP-28452, Running host deactivate even if initiators == null or list empty seems risky
// If initiators are empty, we will not perform any export updates
String taskId = UUID.randomUUID().toString();
Operation op = _dbClient.createTaskOpStatus(Host.class, host.getId(), taskId,
ResourceOperationTypeEnum.DELETE_HOST);
ComputeSystemController controller = getController(ComputeSystemController.class, null);
List<VolumeDescriptor> bootVolDescriptors = new ArrayList<>();
if(deactivateBootVolume & !NullColumnValueGetter.isNullURI(host.getBootVolumeId())) {
Volume vol = _dbClient.queryObject(Volume.class, host.getBootVolumeId());
if (vol.isVPlexVolume(_dbClient)) {
bootVolDescriptors.addAll(vplexBlockServiceApiImpl.getDescriptorsForVolumesToBeDeleted(
vol.getStorageController(), Arrays.asList(host.getBootVolumeId()), null));
} else {
if (vol.getPool() != null) {
StoragePool storagePool = _dbClient.queryObject(StoragePool.class, vol.getPool());
if (storagePool != null && storagePool.getStorageDevice() != null) {
bootVolDescriptors.add(new VolumeDescriptor(VolumeDescriptor.Type.BLOCK_DATA, storagePool
.getStorageDevice(), host.getBootVolumeId(), null, null));
}
}
}
}
controller.detachHostStorage(host.getId(), true, deactivateBootVolume, bootVolDescriptors, taskId);
if (!NullColumnValueGetter.isNullURI(host.getComputeElement())) {
host.setProvisioningStatus(Host.ProvisioningJobStatus.IN_PROGRESS.toString());
}
_dbClient.persistObject(host);
auditOp(OperationTypeEnum.DELETE_HOST, true, op.getStatus(),
host.auditParameters());
return toTask(host, taskId, op);
}
/**
* Check for pending tasks on the Host
*
* @param hostURI Host ID
* @return true if the host has pending tasks, false otherwise
*/
private boolean hostHasPendingTasks(URI hostURI) {
boolean hasPendingTasks = false;
List<Task> taskList = TaskUtils.findResourceTasks(_dbClient, hostURI);
for (Task task : taskList) {
if (task.isPending()) {
hasPendingTasks = true;
break;
}
}
return hasPendingTasks;
}
/**
* Updates export groups and fileshare exports that are referenced by the given host by removing
* the host reference, initiators and IP interfaces belonging to this host. Volumes are left intact.
*
* @param id
* the URN of a ViPR Host
* @brief Detach storage from Host
* @return OK if detaching completed successfully
* @throws DatabaseException
* when a DB error occurs
*/
@POST
@Path("/{id}/detach-storage")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.TENANT_ADMIN })
public TaskResourceRep detachStorage(@PathParam("id") URI id) throws DatabaseException {
Host host = queryHost(_dbClient, id);
ArgValidator.checkEntity(host, id, true);
boolean hasPendingTasks = hostHasPendingTasks(id);
if (hasPendingTasks) {
throw APIException.badRequests.cannotDetachStorageForHost("another operation is in progress for this host");
}
String taskId = UUID.randomUUID().toString();
Operation op = _dbClient.createTaskOpStatus(Host.class, host.getId(), taskId,
ResourceOperationTypeEnum.DETACH_HOST_STORAGE);
ComputeSystemController controller = getController(ComputeSystemController.class, null);
controller.detachHostStorage(host.getId(), false, false, null, taskId);
return toTask(host, taskId, op);
}
/**
* Creates a new ip interface for a host.
*
* @param id
* the URN of a ViPR Host
* @param createParam
* the details of the interfaces
* @brief Create Host Interface Ip
* @return the details of the host interface, including its id and link,
* when creation completes successfully.
* @throws DatabaseException
* when a database error occurs
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.TENANT_ADMIN })
@Path("/{id}/ip-interfaces")
public IpInterfaceRestRep createIpInterface(@PathParam("id") URI id,
IpInterfaceCreateParam createParam) throws DatabaseException {
Host host = queryObject(Host.class, id, true);
validateIpInterfaceData(createParam, null);
IpInterface ipInterface = new IpInterface();
ipInterface.setHost(host.getId());
ipInterface.setId(URIUtil.createId(IpInterface.class));
populateIpInterface(createParam, ipInterface);
_dbClient.createObject(ipInterface);
auditOp(OperationTypeEnum.CREATE_HOST_IPINTERFACE, true, null,
ipInterface.auditParameters());
return map(ipInterface);
}
/**
* Validates the create/update IP interface operation input data.
*
* @param param
* the input parameter
* @param ipInterface
* the IP interface being updated in case of update operation.
* This parameter must be null for create operations.
*/
public void validateIpInterfaceData(IpInterfaceParam param, IpInterface ipInterface) {
String protocol = param.findProtocol() != null ? param.findProtocol() : ipInterface.getProtocol();
if (!HostInterface.Protocol.IPV4.toString().equals(protocol) && !HostInterface.Protocol.IPV6.toString().equals(protocol)) {
throw APIException.badRequests.invalidIpProtocol();
}
// Validate the passed address matches the protocol
if (param.findIPaddress() != null) { // it can be null on update
String ipAddress = param.findIPaddress();
if (HostInterface.Protocol.IPV4.toString().equals(protocol)) {
ArgValidator.checkFieldValidIPV4(ipAddress, "ipAddress");
} else {
ArgValidator.checkFieldValidIPV6(ipAddress, "ipAddress");
}
}
// last validate that the ip address is unique
if (ipInterface == null || (param.findIPaddress() != null &&
!param.findIPaddress().equalsIgnoreCase(ipInterface.getIpAddress()))) {
checkDuplicateAltId(IpInterface.class, "ipAddress",
EndpointUtility.changeCase(param.findIPaddress()), "IP interface");
}
}
/**
* Gets the id and name for all the interfaces of a host.
*
* @param id
* the URN of a ViPR Host
* @brief List Host Interfaces
* @return a list of interfaces that belong to the host
* @throws DatabaseException
* when a DB error occurs
*/
@GET
@Path("/{id}/ip-interfaces")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public IpInterfaceList getIpInterfaces(@PathParam("id") URI id) throws DatabaseException {
Host host = queryObject(Host.class, id, false);
// check the user permissions
verifyAuthorizedInTenantOrg(host.getTenant(), getUserFromContext());
// get the ip interfaces
IpInterfaceList list = new IpInterfaceList();
List<NamedElementQueryResultList.NamedElement> dataObjects = listChildren(id, IpInterface.class, "ipAddress", "host");
for (NamedElementQueryResultList.NamedElement dataObject : dataObjects) {
list.getIpInterfaces().add(toNamedRelatedResource(ResourceTypeEnum.IPINTERFACE,
dataObject.getId(), dataObject.getName()));
}
return list;
}
/**
* Creates a new initiator for a host.
*
* @param id
* the URN of a ViPR Host
* @param createParam
* the details of the initiator
* @brief Create Host Initiator
* @return the details of the host initiator when creation
* is successfully.
* @throws DatabaseException
* when a database error occurs.
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.TENANT_ADMIN })
@Path("/{id}/initiators")
public TaskResourceRep createInitiator(@PathParam("id") URI id,
InitiatorCreateParam createParam) throws DatabaseException {
Host host = queryObject(Host.class, id, true);
Cluster cluster = null;
validateInitiatorData(createParam, null);
// create and populate the initiator
Initiator initiator = new Initiator();
initiator.setHost(id);
initiator.setHostName(host.getHostName());
if (!NullColumnValueGetter.isNullURI(host.getCluster())) {
cluster = queryObject(Cluster.class, host.getCluster(), false);
initiator.setClusterName(cluster.getLabel());
}
initiator.setId(URIUtil.createId(Initiator.class));
populateInitiator(initiator, createParam);
_dbClient.createObject(initiator);
String taskId = UUID.randomUUID().toString();
Operation op = _dbClient.createTaskOpStatus(Initiator.class, initiator.getId(), taskId,
ResourceOperationTypeEnum.ADD_HOST_INITIATOR);
// if host in use. update export with new initiator
if (ComputeSystemHelper.isHostInUse(_dbClient, host.getId())) {
ComputeSystemController controller = getController(ComputeSystemController.class, null);
controller.addInitiatorsToExport(initiator.getHost(), Arrays.asList(initiator.getId()), taskId);
} else {
// No updates were necessary, so we can close out the task.
_dbClient.ready(Initiator.class, initiator.getId(), taskId);
}
auditOp(OperationTypeEnum.CREATE_HOST_INITIATOR, true, null,
initiator.auditParameters());
return toTask(initiator, taskId, op);
}
/**
* Validates the create/update initiator operation input data.
*
* @param param
* the input parameter
* @param initiator
* the initiator being updated in case of update operation.
* This parameter must be null for create operations.n
*/
public void validateInitiatorData(BaseInitiatorParam param, Initiator initiator) {
String protocol = param.getProtocol() != null ? param.getProtocol() : (initiator != null ? initiator.getProtocol() : null);
String node = param.getNode() != null ? param.getNode() : (initiator != null ? initiator.getInitiatorNode() : null);
String port = param.getPort() != null ? param.getPort() : (initiator != null ? initiator.getInitiatorPort() : null);
ArgValidator.checkFieldValueWithExpected(param == null
|| HostInterface.Protocol.FC.toString().equals(protocol)
|| HostInterface.Protocol.iSCSI.toString().equals(protocol),
"protocol", protocol, HostInterface.Protocol.FC, HostInterface.Protocol.iSCSI);
// Validate the passed node and port based on the protocol.
// Note that for iSCSI the node is optional.
if (HostInterface.Protocol.FC.toString().equals(protocol)) {
// Make sure the node is passed in the request.
ArgValidator.checkFieldNotNull(node, "node");
// Make sure the node is passed in the request.
ArgValidator.checkFieldNotNull(port, "port");
// Make sure the port is a valid WWN.
if (!WWNUtility.isValidWWN(port)) {
throw APIException.badRequests.invalidWwnForFcInitiatorPort();
}
// Make sure the node is a valid WWN.
if (!WWNUtility.isValidWWN(node)) {
throw APIException.badRequests.invalidWwnForFcInitiatorNode();
}
} else {
// Make sure the port is a valid iSCSI port.
if (!iSCSIUtility.isValidIQNPortName(port) && !iSCSIUtility.isValidEUIPortName(port)) {
throw APIException.badRequests.invalidIscsiInitiatorPort();
}
if (param.getNode() != null) {
throw APIException.badRequests.invalidNodeForiScsiPort();
}
}
// last validate that the initiator port is unique
if (initiator == null || (param.getPort() != null &&
!param.getPort().equalsIgnoreCase(initiator.getInitiatorPort()))) {
checkDuplicateAltId(Initiator.class, "iniport", EndpointUtility.changeCase(param.getPort()),
"initiator", "Initiator Port");
}
}
/**
* Gets the id and name for all the host initiators of a host.
*
* @param id
* the URN of a ViPR Host
* @brief List Host Initiators
* @return a list of initiators that belong to the host
* @throws DatabaseException
* when a DB error occurs
*/
@GET
@Path("/{id}/initiators")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public InitiatorList getInitiators(@PathParam("id") URI id) throws DatabaseException {
Host host = queryObject(Host.class, id, false);
// check the user permissions
verifyAuthorizedInTenantOrg(host.getTenant(), getUserFromContext());
// get the initiators
InitiatorList list = new InitiatorList();
List<NamedElementQueryResultList.NamedElement> dataObjects = listChildren(id, Initiator.class, "iniport", "host");
for (NamedElementQueryResultList.NamedElement dataObject : dataObjects) {
list.getInitiators().add(toNamedRelatedResource(ResourceTypeEnum.INITIATOR,
dataObject.getId(), dataObject.getName()));
}
return list;
}
/**
* Populates the interface using values in the parameter
*
* @param param
* the interface creation/update parameter that contains all the attributes
* @param the
* IP interface to be to be populated with data.
*/
public void populateIpInterface(
IpInterfaceParam param, IpInterface ipInterface) {
ipInterface.setIpAddress(param.findIPaddress());
ipInterface.setProtocol(param.findProtocol());
ipInterface.setScopeId(param.getScopeId());
ipInterface.setPrefixLength(param.getPrefixLength());
if (param.getNetmask() != null) {
ipInterface.setNetmask(param.getNetmask().toString());
}
// Set label to ipAddress if not specified on create.
if (ipInterface.getLabel() == null && param.getName() == null) {
ipInterface.setLabel(ipInterface.getIpAddress());
} else if (param.getName() != null) {
ipInterface.setLabel(param.getName());
}
}
/**
* Populates the initiator using values in the parameter
*
* @param param
* the initiator creation/update parameter that contains all the attributes
* @param the
* initiator to be to be populated with data.
*/
public void populateInitiator(Initiator initiator, BaseInitiatorParam param) {
initiator.setInitiatorPort(param.getPort());
initiator.setInitiatorNode(param.getNode());
initiator.setProtocol(param.getProtocol());
// Set label to the initiator port if not specified on create.
if (StringUtils.isEmpty(initiator.getLabel()) && StringUtils.isEmpty(param.getName())) {
initiator.setLabel(initiator.getInitiatorPort());
} else if (param.getName() != null) {
initiator.setLabel(param.getName());
}
}
/**
* Creates a new instance of host.
*
* @param tenant
* the host parent tenant organization
* @param param
* the input parameter containing the host attributes
* @return an instance of {@link Host}
*/
protected TaskResourceRep createNewHost(TenantOrg tenant, HostParam param) {
Host host = new Host();
host.setId(URIUtil.createId(Host.class));
host.setTenant(tenant.getId());
host.setCluster(param.getCluster());
populateHostData(host, param);
if (!NullColumnValueGetter.isNullURI(host.getCluster())) {
if (ComputeSystemHelper.isClusterInExport(_dbClient, host.getCluster())) {
String taskId = UUID.randomUUID().toString();
ComputeSystemController controller = getController(ComputeSystemController.class, null);
controller.addHostsToExport(Arrays.asList(host.getId()), host.getCluster(), taskId, null, false);
} else {
ComputeSystemHelper.updateInitiatorClusterName(_dbClient, host.getCluster(), host.getId());
}
}
host.setRegistrationStatus(RegistrationStatus.REGISTERED.toString());
_dbClient.createObject(host);
auditOp(OperationTypeEnum.CREATE_HOST, true, null, host.auditParameters());
return doDiscoverHost(host.getId(), null, true);
}
/**
* Populate an instance of host with the provided host parameter
*
* @param host
* the host to be populated
* @param param
* the parameter that contains the host attributes.
*/
private void populateHostData(Host host, HostParam param) {
if (param.getName() != null) {
host.setLabel(param.getName());
}
if (param.getHostName() != null) {
host.setHostName(param.getHostName());
}
if (param.getOsVersion() != null) {
host.setOsVersion(param.getOsVersion());
}
if (param.getUserName() != null) {
host.setUsername(param.getUserName());
}
if (param.getPassword() != null) {
host.setPassword(param.getPassword());
}
if (param.getPortNumber() != null) {
host.setPortNumber(param.getPortNumber());
}
if (param.getUseSsl() != null) {
host.setUseSSL(param.getUseSsl());
}
if (param.getType() != null) {
host.setType(param.getType());
}
if (param.getDiscoverable() != null) {
host.setDiscoverable(param.getDiscoverable());
}
// Commented out because host project is not currently used
// if (param.project != null) {
// host.setProject(NullColumnValueGetter.isNullURI(param.project) ?
// NullColumnValueGetter.getNullURI() : param.project);
// }
if (param.getVcenterDataCenter() != null) {
host.setVcenterDataCenter(NullColumnValueGetter.isNullURI(param.getVcenterDataCenter()) ? NullColumnValueGetter.getNullURI()
: param.getVcenterDataCenter());
}
Cluster cluster = null;
// make sure host data is consistent with the cluster
if (!NullColumnValueGetter.isNullURI(param.getCluster())) {
cluster = queryObject(Cluster.class, param.getCluster(), true);
if (!NullColumnValueGetter.isNullURI(cluster.getVcenterDataCenter())) {
host.setVcenterDataCenter(cluster.getVcenterDataCenter());
}
if (!NullColumnValueGetter.isNullURI(cluster.getProject())) {
host.setProject(cluster.getProject());
}
}
if (param.getBootVolume() != null) {
host.setBootVolumeId(NullColumnValueGetter.isNullURI(param.getBootVolume()) ? NullColumnValueGetter
.getNullURI() : param.getBootVolume());
}
}
/**
* Returns the instance of host for the given id. Throws {@link DatabaseException} when id is not a valid URI.
* Throws
* {@link NotFoundException} when the host has
* been delete.
*
* @param dbClient
* an instance of {@link DbClient}
* @param id
* the URN of a ViPR Host to be fetched.
* @return the instance of host for the given id.
* @throws DatabaseException
* when a DB error occurs
*/
protected Host queryHost(DbClient dbClient, URI id) throws DatabaseException {
return queryObject(Host.class, id, false);
}
/**
* Retrieve resource representations based on input ids.
*
* @param param
* POST data containing the id list.
* @brief List data of host resources
* @return list of representations.
*
* @throws DatabaseException
* When an error occurs querying the database.
*/
@POST
@Path("/bulk")
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Override
public HostBulkRep getBulkResources(BulkIdParam param) {
return (HostBulkRep) super.getBulkResources(param);
}
@Override
protected DataObject queryResource(URI id) {
return queryObject(Host.class, id, false);
}
@Override
protected URI getTenantOwner(URI id) {
Host host = queryObject(Host.class, id, false);
return host.getTenant();
}
@SuppressWarnings("unchecked")
@Override
public Class<Host> getResourceClass() {
return Host.class;
}
@Override
protected ResourceTypeEnum getResourceType() {
return ResourceTypeEnum.HOST;
}
@Override
public HostBulkRep queryBulkResourceReps(List<URI> ids) {
Iterator<Host> _dbIterator = _dbClient.queryIterativeObjects(getResourceClass(), ids);
return new HostBulkRep(BulkList.wrapping(_dbIterator, new MapHostWithBladeAndProfile()));
}
@Override
public HostBulkRep queryFilteredBulkResourceReps(List<URI> ids) {
Iterator<Host> _dbIterator = _dbClient.queryIterativeObjects(getResourceClass(), ids);
BulkList.ResourceFilter filter = new BulkList.HostFilter(getUserFromContext(), _permissionsHelper);
return new HostBulkRep(BulkList.wrapping(_dbIterator, new MapHostWithBladeAndProfile(), filter));
}
private class MapHostWithBladeAndProfile implements Function<Host, HostRestRep> {
@Override
public HostRestRep apply(Host host) {
ComputeElement computeElement = null;
UCSServiceProfile serviceProfile = null;
ComputeSystem computeSystem = null;
if (host!=null){
if (!NullColumnValueGetter.isNullURI(host.getComputeElement())){
computeElement = queryObject(ComputeElement.class, host.getComputeElement(),false);
}
if (!NullColumnValueGetter.isNullURI(host.getServiceProfile())){
serviceProfile = queryObject(UCSServiceProfile.class, host.getServiceProfile(), false);
}
if (serviceProfile!=null){
computeSystem = queryObject(ComputeSystem.class, serviceProfile.getComputeSystem(), false);
}else if (computeElement !=null){
computeSystem = queryObject(ComputeSystem.class, computeElement.getComputeSystem(), false);
}
}
HostRestRep rep = map(host,computeElement, serviceProfile, computeSystem);
return rep;
}
}
@Override
protected boolean isZoneLevelResource() {
return false;
}
@Override
protected boolean isSysAdminReadableResource() {
return true;
}
public static class HostResRepFilter<E extends RelatedResourceRep>
extends ResRepFilter<E> {
public HostResRepFilter(StorageOSUser user,
PermissionsHelper permissionsHelper) {
super(user, permissionsHelper);
}
@Override
public boolean isAccessible(E resrep) {
boolean ret = false;
URI id = resrep.getId();
Host obj = _permissionsHelper.getObjectById(id, Host.class);
if (obj == null) {
return false;
}
if (obj.getTenant().toString().equals(_user.getTenantId())) {
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 HostResRepFilter(user, permissionsHelper);
}
/**
* Gets the UnManagedVolumes exposed to a Host.
*
* @param id
* the URI of a ViPR Host
* @return a list of UnManagedVolumes exposed to this host
* @throws DatabaseException
* when a database error occurs
*/
@GET
@Path("/{id}/unmanaged-volumes")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public UnManagedVolumeList getUnmanagedVolumes(@PathParam("id") URI id) throws DatabaseException {
Host host = queryObject(Host.class, id, false);
// check the user permissions
verifyAuthorizedInTenantOrg(host.getTenant(), getUserFromContext());
// get the unmanaged volumes
List<UnManagedVolume> unmanagedVolumes = VolumeIngestionUtil.findUnManagedVolumesForHost(id, _dbClient, _coordinator);
UnManagedVolumeList list = new UnManagedVolumeList();
for (UnManagedVolume volume : unmanagedVolumes) {
list.getUnManagedVolumes()
.add(toRelatedResource(ResourceTypeEnum.UNMANAGED_VOLUMES, volume.getId()));
}
return list;
}
/**
* Gets the UnManagedExportMasks found for a Host.
*
* @param id
* the URI of a ViPR Host
* @return a list of UnManagedExportMasks found for the Host
* @throws DatabaseException
* when a database error occurs
*/
@GET
@Path("/{id}/unmanaged-export-masks")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public UnManagedExportMaskList getUnmanagedExportMasks(@PathParam("id") URI id) throws DatabaseException {
Host host = queryObject(Host.class, id, false);
// check the user permissions
verifyAuthorizedInTenantOrg(host.getTenant(), getUserFromContext());
// get the unmanaged export masks
List<UnManagedExportMask> uems = VolumeIngestionUtil.findUnManagedExportMasksForHost(id, _dbClient);
UnManagedExportMaskList list = new UnManagedExportMaskList();
for (UnManagedExportMask uem : uems) {
list.getUnManagedExportMasks()
.add(toRelatedResource(ResourceTypeEnum.UNMANAGED_EXPORT_MASKS, uem.getId()));
}
return list;
}
/**
* Get list of file system mounts for the specified host.
*
* @param id
* the URN of a host
* @brief List file system mounts
* @return List of file system mounts.
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/filesystem-mounts")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, acls = { ACL.ANY })
public MountInfoList getHostNFSMounts(@PathParam("id") URI id) {
ArgValidator.checkFieldUriType(id, Host.class, "id");
_log.info(String.format("Get list of mounts for host %1$s", id));
Host host = queryObject(Host.class, id, false);
// check the user permissions
verifyAuthorizedInTenantOrg(host.getTenant(), getUserFromContext());
MountInfoList mountList = new MountInfoList();
mountList.setMountList(FileOperationUtils.queryDBHostMounts(id, _dbClient));
return mountList;
}
/**
* Creates a new host for the tenant organization. Discovery is initiated
* after the host is created.
*
* @param createParam
* the parameter that has the type and attribute of the host to
* be created.
* @prereq none
* @brief Create host
* @return the host discovery async task representation.
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.TENANT_ADMIN })
public TaskResourceRep createHost(HostCreateParam createParam,
@QueryParam("validate_connection") @DefaultValue("false") final Boolean validateConnection) {
// This is mostly to validate the tenant
URI tid = createParam.getTenant();
if ((tid == null) || tid.toString().isEmpty()) {
_log.error("The tenant id is missing");
throw APIException.badRequests.requiredParameterMissingOrEmpty("tenant");
}
TenantOrg tenant = _permissionsHelper.getObjectById(tid, TenantOrg.class);
ArgValidator.checkEntity(tenant, tid, isIdEmbeddedInURL(tid), true);
validateHostData(createParam, tid, null, validateConnection);
// Create the host
return createNewHost(tenant, createParam);
}
/**
* Provision bare metal hosts by taking compute elements from the compute
* virtual pool.
*
* @param param
* parameter for multiple host creation
* @brief Provision bare metal hosts
* @return TaskResourceRep (asynchronous call)
* @throws DatabaseException
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/provision-bare-metal")
public TaskList provisionBareMetalHosts(ProvisionBareMetalHostsParam param) throws DatabaseException {
ComputeVirtualPool cvp = _dbClient.queryObject(ComputeVirtualPool.class, param.getComputeVpool());
ArgValidator.checkEntity(cvp, param.getComputeVpool(), false);
VirtualArray varray = _dbClient.queryObject(VirtualArray.class, param.getVarray());
ArgValidator.checkEntity(varray, param.getVarray(), false);
TenantOrg tenant = _dbClient.queryObject(TenantOrg.class, param.getTenant());
ArgValidator.checkEntity(tenant, param.getTenant(), false);
if (!NullColumnValueGetter.isNullURI(param.getCluster())) {
Cluster cluster = _dbClient.queryObject(Cluster.class, param.getCluster());
ArgValidator.checkEntity(cluster, param.getCluster(), false);
}
_log.debug("checking if CVP is accessible");
_permissionsHelper.checkTenantHasAccessToComputeVirtualPool(tenant.getId(), cvp);
validateHostNames(param);
InterProcessLock lock = lockBladeReservation();
List<String> ceList = null;
try {
ceList = takeComputeElementsFromPool(cvp, param.getHostNames().size(), varray, param.getCluster());
} catch (Exception e) {
_log.error("unable to takeComputeElementsFromPool", e);
throw e;
} finally {
unlockBladeReservation(lock);
}
Set<Host> hosts = new HashSet<Host>();
for (int i = 0; i < param.getHostNames().size(); i++) {
Host host = populateHost(tenant, param.getHostNames().get(i), ceList.get(i), param.getCluster(), cvp.getId());
hosts.add(host);
_dbClient.createObject(host);
}
return createHostTasks(hosts, param.getComputeVpool(), param.getVarray());
}
private InterProcessLock lockBladeReservation() {
InterProcessLock lock = _coordinator.getLock(BLADE_RESERVATION_LOCK_NAME);
try {
lock.acquire();
_log.info("acquired BladeReservation lock");
} catch (Exception e) {
_log.error("failed to acquire BladeReservation lock", e);
throw BadRequestException.badRequests.unableToLockBladeReservation();
}
return lock;
}
private void unlockBladeReservation(InterProcessLock lock) {
try {
lock.release();
_log.info("unlocked BladeReservation");
} catch (Exception e) {
_log.error("could not unlock BladeReservation", e);
}
}
private void validateHostNames(ProvisionBareMetalHostsParam param) {
// check that the host names are valid
for (int i = 0; i < param.getHostNames().size(); i++) {
String hostName = param.getHostNames().get(i);
hostName = hostName.trim();
hostName = EndpointUtility.changeCase(hostName);
if (EndpointUtility.isValidHostName(hostName)) {
param.getHostNames().set(i, hostName);
} else {
throw APIException.badRequests.invalidHostName(hostName);
}
}
// check that all host names are unique
Set<String> set = new HashSet<String>();
set.addAll(param.getHostNames());
if (set.size() != param.getHostNames().size()) {
throw APIException.badRequests.invalidHostNamesAreNotUnique();
}
}
private TaskList createHostTasks(Set<Host> hosts, URI cvpUri, URI varray) {
TaskList tl = new TaskList();
Set<AsyncTask> tasks = new HashSet<AsyncTask>();
for (Host host : hosts) {
String taskId = UUID.randomUUID().toString();
AsyncTask task = new AsyncTask(Host.class, host.getId(), taskId);
Operation op = new Operation();
op.setResourceType(ResourceOperationTypeEnum.CREATE_HOST);
_dbClient.createTaskOpStatus(Host.class, host.getId(), task._opId, op);
tasks.add(task);
tl.getTaskList().add(TaskMapper.toTask(host, task._opId, op));
host.setProvisioningStatus(Host.ProvisioningJobStatus.IN_PROGRESS.toString());
auditOp(OperationTypeEnum.CREATE_HOST, true, AuditLogManager.AUDITOP_BEGIN, host.auditParameters());
}
/*
* Persist the IN_PROGRESS ProvisioningJobStatus
*/
_dbClient.persistObject(hosts);
/*
* Dispatch the request to the controller
*/
ComputeController computeController = getController(ComputeController.class, null);
computeController.createHosts(varray, cvpUri, tasks.toArray(new AsyncTask[0]));
return tl;
}
private Map<URI, List<ComputeElement>> findComputeElementsUsedInCluster(URI clusterId) throws DatabaseException {
Map<URI, List<ComputeElement>> computeSystemToComputeElementMap = new HashMap<URI, List<ComputeElement>>();
HostList hostList = clusterService.getClusterHosts(clusterId);
List<NamedRelatedResourceRep> list = hostList.getHosts();
for (NamedRelatedResourceRep hostRep : list) {
HostRestRep host = getHost(hostRep.getId());
RelatedResourceRep computeElement = host.getComputeElement();
if (computeElement == null) {
// this can happen if cluster has hosts that were not provisioned by vipr
continue;
}
ComputeElement ce = _dbClient.queryObject(ComputeElement.class, computeElement.getId());
URI computeSystem = ce.getComputeSystem();
List<ComputeElement> usedComputeElements;
if (computeSystemToComputeElementMap.containsKey(computeSystem)) {
usedComputeElements = computeSystemToComputeElementMap.get(computeSystem);
} else {
usedComputeElements = new ArrayList<ComputeElement>();
}
usedComputeElements.add(ce);
computeSystemToComputeElementMap.put(computeSystem, usedComputeElements);
}
return computeSystemToComputeElementMap;
}
/*
* Returns a map of compute system URI to compute elements available on that compute system
*/
private Map<URI, List<URI>> findComputeElementsMatchingVarrayAndCVP(ComputeVirtualPool cvp, VirtualArray varray) {
Map<URI, List<URI>> computeSystemToComputeElementsMap = new HashMap<URI, List<URI>>();
_log.debug("Look up compute elements for cvp " + cvp.getId());
List<String> cvpCEList = new ArrayList<String>();
if (cvp.getMatchedComputeElements() != null) {
Iterator<String> iter = cvp.getMatchedComputeElements().iterator();
while (iter.hasNext()) {
String uriStr = iter.next();
cvpCEList.add(uriStr);
}
}
// Find all SPTs assigned for this CVP and their corresponding ComputeSystems
Map<URI, URI> cvpTemplatesMap = new HashMap<URI, URI>();
if (cvp.getServiceProfileTemplates() != null) {
for (String templateIdString : cvp.getServiceProfileTemplates()) {
URI templateId = URI.create(templateIdString);
UCSServiceProfileTemplate template = _dbClient.queryObject(UCSServiceProfileTemplate.class, templateId);
if (template.getUpdating() == true) {
if (!computeSystemService.isUpdatingSPTValid(template, _dbClient)) {
throw APIException.badRequests.invalidUpdatingSPT(template.getLabel());
}
StringSet varrayIds = new StringSet();
varrayIds.add(varray.getId().toString());
if (!computeSystemService.isServiceProfileTemplateValidForVarrays(varrayIds, templateId)) {
throw APIException.badRequests.incompatibleSPT(template.getLabel(), varray.getLabel());
}
}
cvpTemplatesMap.put(template.getComputeSystem(), templateId);
}
}
_log.debug("Look up compute systems for virtual array " + varray.getId());
ComputeSystemBulkRep computeSystemBulkRep = virtualArrayService.getComputeSystems(varray.getId());
if (computeSystemBulkRep.getComputeSystems() != null) {
for (ComputeSystemRestRep computeSystemRestRep : computeSystemBulkRep.getComputeSystems()) {
_log.debug("Found compute system " + computeSystemRestRep.getId() + " for virtual array " + varray.getId());
if (!cvpTemplatesMap.containsKey(computeSystemRestRep.getId())) {
_log.info("The CVP has no service profile templates assigned from compute system " + computeSystemRestRep.getName()
+ ". So no blades will be used from this compute system.");
continue;
}
ComputeElementListRestRep computeElementListRestRep = computeSystemService.getComputeElements(computeSystemRestRep.getId());
if (computeElementListRestRep.getList() != null) {
List<URI> computeElementList = new ArrayList<URI>();
for (ComputeElementRestRep computeElementRestRep : computeElementListRestRep.getList()) {
_log.debug("Compute system contains compute element " + computeElementRestRep.getId());
for (String computeElement : cvpCEList) {
if (computeElement.equals(computeElementRestRep.getId().toString())) {
if (computeElementRestRep.getAvailable()
&& computeElementRestRep.getRegistrationStatus().equals(RegistrationStatus.REGISTERED.name())) {
computeElementList.add(computeElementRestRep.getId());
_log.debug("Added compute element " + computeElementRestRep.getId());
} else {
_log.debug("found unavailable compute element" + computeElementRestRep.getId());
}
}
}
}
computeSystemToComputeElementsMap.put(computeSystemRestRep.getId(), computeElementList);
}
}
} else {
throw APIException.badRequests.noComputeSystemsFoundForVarray();
}
return computeSystemToComputeElementsMap;
}
private Map<URI, List<URI>> sortMapByNumberOfElements(Map<URI, List<URI>> computeSystemToComputeElementsMap) {
HashMap<URI, List<URI>> sortedHashMap = new LinkedHashMap<URI, List<URI>>();
Map<URI, Integer> computeSystemToNumComputeElementsMap = new HashMap<URI, Integer>();
for (URI key : computeSystemToComputeElementsMap.keySet()) {
List<URI> computeElements = computeSystemToComputeElementsMap.get(key);
int count = computeElements.size();
computeSystemToNumComputeElementsMap.put(key, count);
}
List<Map.Entry<URI, Integer>> list = new LinkedList<Map.Entry<URI, Integer>>(
computeSystemToNumComputeElementsMap.entrySet());
Collections.sort(list, new Comparator<Map.Entry<URI, Integer>>() {
@Override
public int compare(Map.Entry<URI, Integer> o1, Map.Entry<URI, Integer> o2) {
return o1.getValue().compareTo(o2.getValue());
}
});
for (Iterator<Map.Entry<URI, Integer>> it = list.iterator(); it.hasNext();) {
Map.Entry<URI, Integer> entry = it.next();
URI key = entry.getKey();
sortedHashMap.put(key, computeSystemToComputeElementsMap.get(key));
}
return sortedHashMap;
}
private Map<URI, List<URI>> filterOutBladesFromBadUcs(Map<URI, List<URI>> inputMap){
Map<URI, List<URI>> outputMap = new HashMap<URI,List<URI>>();
for (URI csURI: inputMap.keySet()){
ComputeSystem ucs = _dbClient.queryObject(ComputeSystem.class, csURI);
if (ucs!=null && !ucs.getDiscoveryStatus().equals(DataCollectionJobStatus.ERROR.name())){
outputMap.put(csURI,inputMap.get(csURI));
}else {
_log.warn("Filtering out blades from Compute System "+ ucs.getLabel()+" which failed discovery");
}
}
return outputMap;
}
private List<String> takeComputeElementsFromPool(ComputeVirtualPool cvp, int numHosts, VirtualArray varray, URI clusterId) {
List<URI> selectedCEsList = new ArrayList<URI>();
List<String> bladeSelections = new ArrayList<String>();
// Map of compute systems to compute elements from this Compute system used in this cluster
Map<URI, List<ComputeElement>> usedComputeElementsMap = findComputeElementsUsedInCluster(clusterId);
// Map of compute systems to compute elements from this Compute system that are available in this cvp
Map<URI, List<URI>> computeSystemToComputeElementsMap1 = findComputeElementsMatchingVarrayAndCVP(cvp, varray);
Map<URI, List<URI>> computeSystemToComputeElementsMap = filterOutBladesFromBadUcs(computeSystemToComputeElementsMap1);
int numRequiredCEs = numHosts;
int totalAvailableCEs = 0;
List<URI> availableCEList = new ArrayList<URI>();
// If total # of available CEs less than required, throw exception
for (URI key : computeSystemToComputeElementsMap.keySet()) {
List<URI> computeElements = computeSystemToComputeElementsMap.get(key);
totalAvailableCEs = totalAvailableCEs + computeElements.size();
}
if (totalAvailableCEs < numRequiredCEs) {
throw APIException.badRequests.notEnoughComputeElementsInPool();
}
// First try to pick blades from compute systems already used for this cluster.
if (!usedComputeElementsMap.isEmpty()) {
_log.debug("first try to pick blades from compute systems already used for this cluster");
Set<URI> usedComputeSystems = usedComputeElementsMap.keySet();
for (URI uri : usedComputeSystems) {
_log.debug("Looking in used compute system:" + uri);
availableCEList = computeSystemToComputeElementsMap.get(uri);
if (availableCEList == null) {
continue;
}
if (availableCEList.size() <= numRequiredCEs) {
selectedCEsList.addAll(availableCEList);
numRequiredCEs = numRequiredCEs - availableCEList.size();
_log.debug("Picked all available " + availableCEList.size() + " blades from compute system");
} else {
List<URI> selections = pickBladesByStrafingAlgorithm(availableCEList, numRequiredCEs,
usedComputeElementsMap.get(uri));
selectedCEsList.addAll(selections);
numRequiredCEs = numRequiredCEs - selections.size();
_log.debug("Picked " + selections.size() + " blades from compute system");
}
// Remove this compute system and its elements from map since they have been considered.
computeSystemToComputeElementsMap.remove(uri);
}
}
// If we have required number of hosts, return
if (numRequiredCEs == 0) {
for (URI uri : selectedCEsList) {
bladeSelections.add(uri.toString());
}
setCeUnavailable(bladeSelections);
return bladeSelections;
}
// Else sort compute systems by ascending number of compute elements available.
Map<URI, List<URI>> sortedMap = sortMapByNumberOfElements(computeSystemToComputeElementsMap);
_log.debug("Compute Systems sorted by number of available Compute elements:");
for (URI key : sortedMap.keySet()) {
_log.debug("computeSystem: " + key);
List<URI> computeElements = sortedMap.get(key);
int count = computeElements.size();
if (count == numHosts) {
_log.debug("found required number of blades in compute system:" + key);
selectedCEsList.addAll(computeElements);
numRequiredCEs = 0;
break;
} else if (numHosts < count) {
_log.debug("Taking " + numHosts + " blades from compute system: " + key + " . Need no more.");
// pick n blades from m available blades.
if (availableCEList == null) {
availableCEList = new ArrayList<URI>();
}
availableCEList.addAll(computeElements);
selectedCEsList.addAll(pickBladesByStrafingAlgorithm(availableCEList, numRequiredCEs,
usedComputeElementsMap.get(key)));
numRequiredCEs = numRequiredCEs - numHosts;
break;
}
}
if (numRequiredCEs > 0) {
_log.debug("No single Compute System has enough compute elements. So pick from multiple.");
for (URI key : sortedMap.keySet()) {
_log.debug("computeSystem: " + key);
List<URI> computeElements = sortedMap.get(key);
int count = computeElements.size();
if (numRequiredCEs >= count) {
selectedCEsList.addAll(computeElements);
_log.debug("Taking " + count + " blades from compute system: " + key);
numRequiredCEs = numRequiredCEs - count;
if (numRequiredCEs == 0) {
_log.debug("Need no more");
break;
}
} else {
if (availableCEList == null) {
availableCEList = new ArrayList<URI>();
}
availableCEList.addAll(computeElements);
_log.debug("Pick " + numRequiredCEs + " blades from " + count + " blades on compute system: " + key);
// pick n blades from m available blades.
selectedCEsList.addAll(pickBladesByStrafingAlgorithm(availableCEList, numRequiredCEs, usedComputeElementsMap.get(key)));
numRequiredCEs = 0;
break;
}
}
}
for (URI uri : selectedCEsList) {
bladeSelections.add(uri.toString());
}
setCeUnavailable(bladeSelections);
return bladeSelections;
}
private void setCeUnavailable(List<String> ceUriStrs) {
List<URI> ceUris = URIUtil.toURIList(ceUriStrs);
if (ceUris == null) {
return;
}
List<ComputeElement> ceList = _dbClient.queryObject(ComputeElement.class, ceUris);
for (ComputeElement ce : ceList) {
ce.setAvailable(false);
}
_dbClient.persistObject(ceList);
}
private List<URI> pickBladesByStrafingAlgorithm(List<URI> availableList, int numRequiredBlades, List<ComputeElement> usedCEList)
throws DatabaseException {
if (usedCEList == null) {
usedCEList = new ArrayList<ComputeElement>();
}
_log.debug("In pickBladesByStrafingAlgorithm");
List<URI> selectedCEList = new ArrayList<URI>();
List<ComputeElement> availableCEList = new ArrayList<ComputeElement>();
// Find numChassis and numSlot (M and N)
// Populate M x N matrix of 1s for available blades
// Build chassisUsageMap : chassisUsageCount to Set of chassisIds . sort by key asc
// Build slotUsageMap: slotUsageCount to set of SlotIds. sort by key desc
// Build slotCountMap: slotId to slotCount
// ///Build slot availabilityMap availableSlotsCount to Set of slotIds.
// boolean useUsedSlots = false if usedCEsList is empty
// For each chassisCount in chassisUsageMap, loop through the chassises wit that count
// if useUsedSlots is true, On each of those chassis, find the slot that is available and has maximum usage
// count
// if useUsedSlots is false, on each of those chassis, find the slot that has maximum available slots
for (URI uri : availableList) {
ComputeElement computeElement = _dbClient.queryObject(ComputeElement.class, uri);
_log.debug("computeElement:" + computeElement.getLabel() + " chassisId:" + computeElement.getChassisId() + " slotId:"
+ computeElement.getSlotId());
// ComputeSystem cs = _dbClient.queryObject(ComputeSystem.class,computeElement.getComputeSystem());
availableCEList.add(computeElement);
}
int numChassis = 0;
int numSlot = 0;
for (ComputeElement ce : availableCEList) {
int chassisId = Integer.parseInt(ce.getChassisId());
long slotId = ce.getSlotId();
if (chassisId > numChassis) {
numChassis = chassisId;
}
if (slotId > numSlot) {
numSlot = (int) slotId;
}
}
for (ComputeElement ce : usedCEList) {
int chassisId = Integer.parseInt(ce.getChassisId());
long slotId = ce.getSlotId();
if (chassisId > numChassis) {
numChassis = chassisId;
}
if (slotId > numSlot) {
numSlot = (int) slotId;
}
}
int M = numChassis + 1;
int N = numSlot + 1;
_log.debug("M:" + M + " N:" + N);
int[][] availableBlades = new int[M][N];
Map<String, URI> availableBladesByChassisIdAndSlotId = new HashMap<String, URI>();
for (ComputeElement ce : availableCEList) {
int chassisId = Integer.parseInt(ce.getChassisId());
long l_slotId = ce.getSlotId();
int slotId = (int) l_slotId;
if (ce.getAvailable()) {
availableBlades[chassisId][slotId] = 1;
availableBladesByChassisIdAndSlotId.put(chassisId + "," + slotId, ce.getId());
} else {
availableBlades[chassisId][slotId] = 0;
}
}
int[][] usedBlades = new int[M][N];
for (ComputeElement ce : usedCEList) {
int chassisId = Integer.parseInt(ce.getChassisId());
long l_slotId = ce.getSlotId();
int slotId = (int) l_slotId;
usedBlades[chassisId][slotId] = 1;
}
Map<Integer, Integer> chassisIdToCountMap = new HashMap<Integer, Integer>();
Map<Integer, Set<Integer>> chassisUsageMap = new TreeMap<Integer, Set<Integer>>();
List<Integer> unusedChassisIds = new ArrayList<Integer>();
for (int chassisId = 1; chassisId <= numChassis; chassisId++) {
int chassisUsageCount = 0;
for (int slotId = 1; slotId <= numSlot; slotId++) {
chassisUsageCount = chassisUsageCount + usedBlades[chassisId][slotId];
}
Integer key = Integer.valueOf(chassisUsageCount);
Set<Integer> chassisIdSet = chassisUsageMap.get(key);
if (chassisIdSet == null) {
chassisIdSet = new HashSet<Integer>();
}
chassisIdSet.add(Integer.valueOf(chassisId));
chassisUsageMap.put(chassisUsageCount, chassisIdSet);
chassisIdToCountMap.put(Integer.valueOf(chassisId), Integer.valueOf(chassisUsageCount));
if (chassisUsageCount == 0) {
unusedChassisIds.add(chassisId);
}
_log.debug("chassisId:" + chassisId + " usageCount:" + chassisUsageCount);
}
for (int chassisId : unusedChassisIds) {
_log.debug("unusedChassis:" + chassisId);
}
Map<Integer, Integer> slotIdToCountMap = new HashMap<Integer, Integer>();
NavigableMap<Integer, Set<Integer>> slotUsageMap = new TreeMap<Integer, Set<Integer>>();
for (int slotId = 1; slotId <= numSlot; slotId++) {
int slotUsageCount = 0;
for (int chassisId = 1; chassisId <= numChassis; chassisId++) {
slotUsageCount = slotUsageCount + usedBlades[chassisId][slotId];
}
Integer key = Integer.valueOf(slotUsageCount);
Set<Integer> slotIdSet = slotUsageMap.get(key);
if (slotIdSet == null) {
slotIdSet = new HashSet<Integer>();
}
slotIdSet.add(Integer.valueOf(slotId));
slotUsageMap.put(slotUsageCount, slotIdSet);
slotIdToCountMap.put(Integer.valueOf(slotId), Integer.valueOf(slotUsageCount));
_log.debug("slotId:" + slotId + " usageCount:" + slotUsageCount);
}
// sort the slotUsageMap by its keys in descending order
Map<Integer, Set<Integer>> descSlotUsageMap = slotUsageMap.descendingMap();
boolean useUsedSlots = true;
if (usedCEList.isEmpty()) {
useUsedSlots = false;
}
boolean avoidUsedChassis = true;
if (unusedChassisIds.isEmpty()) {
avoidUsedChassis = false;
}
List<String> selectedBlades = new ArrayList<String>();
while (numRequiredBlades > 0) {
if (!useUsedSlots) {// No slots used so far.
// Now find the slot or slots which are max available
_log.debug("No slots used so far. Find max available slot.");
int selectedSlotId = 0;
int maxSum = 0;
for (int slotId = 1; slotId <= numSlot; slotId++) {
int sum = 0;
for (int chassisId = 1; chassisId <= numChassis; chassisId++) {
sum = sum + availableBlades[chassisId][slotId];
}
if (sum > maxSum) {
selectedSlotId = slotId;
maxSum = sum;
if (sum >= numRequiredBlades) { // Stop when we find first slot that enough free blades to
// satisfy numRequiredBlades
break;
}
}
}
_log.debug("max available slot is:" + selectedSlotId);
// From selected slot pick as many blades as possible
for (int chassisId = 1; chassisId <= numChassis; chassisId++) {
if (availableBlades[chassisId][selectedSlotId] == 1) {
_log.debug("selected blade " + chassisId + "/" + selectedSlotId);
selectedBlades.add(chassisId + "," + selectedSlotId);
availableBlades[chassisId][selectedSlotId] = 0;
numRequiredBlades--;
if (numRequiredBlades == 0) {
break;
}
}
}
useUsedSlots = true;
} else {
Set<Integer> preferredChassisIds = new HashSet<Integer>();
if (avoidUsedChassis) {
_log.debug("Pick from unused chassis first");
preferredChassisIds.addAll(unusedChassisIds);
// Pick blades from the max used slots on the preferred chassis
// Starting with max used slot, loop thru slots to find ones that are available on preferred chassis
Iterator<Integer> iter = descSlotUsageMap.keySet().iterator();
while (iter.hasNext()) {
Integer slotUsageCount = iter.next();
Set<Integer> preferredSlotIds = descSlotUsageMap.get(slotUsageCount);// slotIds with max usage
/*
* This is not part of the blade strafing algorithm in UIM. Hence commenting out.
* Uncomment if this modification to algorithm is required.
* //Order these slots that have same slotUSageCount, in descending order of number of available
* blades on unused
* chassis.
* NavigableMap<Integer,Set<Integer> >slotIdAvailabilityMap = new
* TreeMap<Integer,Set<Integer>>();
* for (int slotId : preferredSlotIds){
* int availabilityCount = 0;
* for (int chassisId : preferredChassisIds){
* if (availableBlades[chassisId][slotId] ==1){
* availabilityCount++;
* }
* }
* Set<Integer> slotIdSet = slotIdAvailabilityMap.get(availabilityCount);
* if (slotIdSet ==null){
* slotIdSet = new HashSet<Integer>();
* }
* slotIdSet.add(slotId);
* slotIdAvailabilityMap.put(availabilityCount,slotIdSet);
* }
*
* Iterator<Integer> iterator = slotIdAvailabilityMap.keySet().iterator();
* while(iterator.hasNext()){
* Integer availabilityCount = iterator.next();
* Set<Integer> mostPreferredSlotIds = slotIdAvailabilityMap.get(availabilityCount);
* for (int slotId : mostPreferredSlotIds){
* Comment the next statement - for(int slotId : preferredSlotIds) when uncommenting this
* section
*/
for (int slotId : preferredSlotIds) {
_log.debug("preferred slotId: " + slotId + " with usage count: " + slotUsageCount);
for (int chassisId : preferredChassisIds) {
if (availableBlades[chassisId][slotId] == 1) {
_log.debug("selected Blade: " + chassisId + "/" + slotId);
selectedBlades.add(chassisId + "," + slotId);
availableBlades[chassisId][slotId] = 0;
numRequiredBlades--;
unusedChassisIds.remove((Integer) chassisId);
Integer currentCount = chassisIdToCountMap.get(chassisId);
chassisIdToCountMap.put(chassisId, currentCount + 1);
// updateChassisUsageMap
chassisUsageMap = updateChassisUsageMap(chassisIdToCountMap);
if (numRequiredBlades == 0) {
break;
}
}
}
if (numRequiredBlades == 0) {
break;
}
}
}
avoidUsedChassis = false;
} else {
_log.debug(" No more blades from unused chassis. Pick from least used chassis.");
Iterator<Integer> iter = chassisUsageMap.keySet().iterator();
while (iter.hasNext()) {
Integer chassisUsageCount = iter.next();
preferredChassisIds = chassisUsageMap.get(chassisUsageCount);// chassisIds with least usage
// Pick blades from preferredChassis
for (int chassisId : preferredChassisIds) {
int count = 0;
_log.debug("preferred chassis :" + chassisId + "with usgae count:" + chassisUsageCount);
for (int slotId = 1; slotId <= numSlot; slotId++) {
if (availableBlades[chassisId][slotId] == 1) {
numRequiredBlades--;
_log.debug("selected blade: " + chassisId + "/" + slotId);
selectedBlades.add(chassisId + "," + slotId);
count++;
if (preferredChassisIds.size() > 1) { // pick one from each if there are multiple
// preferred chassis
break;
}
if (numRequiredBlades == 0) {
break;
}
}
}
if (count > 0) {
unusedChassisIds.remove((Integer) chassisId);
Integer currentCount = chassisIdToCountMap.get(chassisId);
chassisIdToCountMap.put(chassisId, currentCount + count);
// updateChassisUsageMap
chassisUsageMap = updateChassisUsageMap(chassisIdToCountMap);
}
if (numRequiredBlades == 0) {
break;
}
}
if (numRequiredBlades == 0) {
break;
}
}
}
}
}
StringBuffer sbuf = new StringBuffer();
sbuf.append("Selected Blades are:\n");
for (String blade : selectedBlades) {
sbuf.append("Blade " + blade);
URI ceURI = availableBladesByChassisIdAndSlotId.get(blade);
selectedCEList.add(ceURI);
}
_log.debug(sbuf.toString());
return selectedCEList;
}
private NavigableMap<Integer, Set<Integer>> updateChassisUsageMap(Map<Integer, Integer> chassisIdToCountMap) {
NavigableMap<Integer, Set<Integer>> chassisUsageMap = new TreeMap<Integer, Set<Integer>>();
for (Map.Entry<Integer, Integer> entry : chassisIdToCountMap.entrySet()) {
int chassisId = entry.getKey();
int chassisCount = entry.getValue();
Set<Integer> chassisNumberSet = chassisUsageMap.get(chassisCount);
if (chassisNumberSet == null) {
chassisNumberSet = new HashSet<Integer>();
}
chassisNumberSet.add(chassisId);
chassisUsageMap.put(chassisCount, chassisNumberSet);
}
return chassisUsageMap;
}
private Host populateHost(TenantOrg t, String name, String ceId, URI clusterId, URI cvpId) {
URI ceUri = URI.create(ceId);
Host host = new Host();
host.setId(URIUtil.createId(Host.class));
host.setTenant(t.getId());
host.setHostName(name);
host.setLabel(name);
host.setType(Host.HostType.No_OS.name());
host.setDiscoveryStatus(DiscoveredDataObject.DataCollectionJobStatus.CREATED.name());
host.setDiscoverable(false);
host.setComputeElement(ceUri);
host.setComputeVirtualPoolId(cvpId);
if (clusterId != null) {
host.setCluster(clusterId);
}
return host;
}
/**
* Install operating system on the host.
*
* @param hostId
* host URI
* @param param
* OS install data
* @brief Install operating system on the host
* @return TaskResourceRep (asynchronous call)
*/
@PUT
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/os-install")
public TaskResourceRep osInstall(@PathParam("id") URI hostId, OsInstallParam param) {
// validate params
ArgValidator.checkFieldUriType(hostId, Host.class, "id");
ArgValidator.checkFieldNotNull(param.getComputeImage(), "compute_image");
// get data
ComputeImage img = queryObject(ComputeImage.class, param.getComputeImage(), true);
ArgValidator.checkEntity(img, param.getComputeImage(),
isIdEmbeddedInURL(param.getComputeImage()));
if (!ComputeImageStatus.AVAILABLE.name().equals(img.getComputeImageStatus())) {
throw APIException.badRequests.invalidParameterComputeImageIsNotAvailable(img.getId());
}
ArgValidator.checkFieldNotEmpty(param.getHostIp(), "host_ip");
Host host = queryObject(Host.class, hostId, true);
ArgValidator.checkEntity(host, hostId, isIdEmbeddedInURL(hostId));
// COP-28718 Fixed by making sure that the host we are installing OS does not cause an IP conflict
// by throwing appropriate exception.
verifyHostForDuplicateIP(host, param);
// only support os install on hosts with compute elements
if (NullColumnValueGetter.isNullURI(host.getComputeElement())) {
throw APIException.badRequests.invalidParameterHostHasNoComputeElement();
}
if (!host.getType().equals(Host.HostType.No_OS.name()) && !param.getForceInstallation()) {
throw APIException.badRequests.invalidParameterHostAlreadyHasOs(host.getType());
}
if (!StringUtils.isNotBlank(param.getRootPassword())) {
throw APIException.badRequests.hostPasswordNotSet();
} else {
host.setPassword(param.getRootPassword());
host.setUsername("root");
}
// check that CS has os install network
ComputeElement ce = queryObject(ComputeElement.class, host.getComputeElement(), true);
ArgValidator.checkEntity(ce, host.getComputeElement(), isIdEmbeddedInURL(host.getComputeElement()));
if (ce.getUuid() == null) {
throw APIException.badRequests.computeElementHasNoUuid();
}
ComputeSystem cs = queryObject(ComputeSystem.class, ce.getComputeSystem(), true);
ArgValidator.checkEntity(cs, ce.getComputeSystem(), isIdEmbeddedInURL(ce.getComputeSystem()));
verifyImagePresentOnImageServer(cs, img);
if (!StringUtils.isNotBlank(cs.getOsInstallNetwork())) {
throw APIException.badRequests.osInstallNetworkNotSet();
}
if (!cs.getVlans().contains(cs.getOsInstallNetwork())) {
throw APIException.badRequests.osInstallNetworkNotValid(cs.getOsInstallNetwork());
}
// check that there is no os install in progress for this host
URIQueryResultList jobUriList = new URIQueryResultList();
_dbClient.queryByConstraint(ContainmentConstraint.Factory.getComputeImageJobsByHostConstraint(host.getId()),
jobUriList);
Iterator<URI> iterator = jobUriList.iterator();
while (iterator.hasNext()) {
ComputeImageJob existingJob = _dbClient.queryObject(ComputeImageJob.class, iterator.next());
if (!existingJob.getInactive() && existingJob.getJobStatus().equals(ComputeImageJob.JobStatus.CREATED.name())) {
throw APIException.badRequests.osInstallAlreadyInProgress();
}
}
// openssl passwd -1 (MD5 encryption of password)
String passwordHash = Md5Crypt.md5Crypt(host.getPassword().getBytes());
// create session
ComputeImageJob job = new ComputeImageJob();
job.setId(URIUtil.createId(ComputeImageJob.class));
job.setComputeImageId(img.getId());
job.setHostId(host.getId());
job.setPasswordHash(passwordHash);
job.setHostName(param.getHostName());
job.setHostIp(param.getHostIp());
job.setNetmask(param.getNetmask());
job.setGateway(param.getGateway());
job.setNtpServer(param.getNtpServer());
job.setDnsServers(param.getDnsServers());
job.setManagementNetwork(param.getManagementNetwork());
job.setPxeBootIdentifier(ImageServerUtils.uuidFromString(host.getUuid()).toString());
job.setComputeImageServerId(cs.getComputeImageServer());
// volume id is optional
if (!NullColumnValueGetter.isNullURI(param.getVolume())
|| !NullColumnValueGetter.isNullURI(host.getBootVolumeId())) {
Volume vol = null;
if (!NullColumnValueGetter.isNullURI(param.getVolume())) {
vol = queryObject(Volume.class, param.getVolume(), true);
host.setBootVolumeId(vol.getId());
} else {
vol = queryObject(Volume.class, host.getBootVolumeId(), true);
}
job.setVolumeId(vol.getId());
StorageSystem st = queryObject(StorageSystem.class, vol.getStorageController(), true);
// VMAX and VNX volumes use UUID to identify volumes
// XtremIO uses some other ID type (e.g. 514f0c5dc9600016)
if (st != null && DiscoveredDataObject.Type.xtremio.name().equals(st.getSystemType())) {
_log.info("xtremio volume id {}", vol.getNativeId());
job.setBootDevice(vol.getNativeId());
} else {
_log.info("volume id {}", vol.getWWN());
job.setBootDevice(ImageServerUtils.uuidFromString(vol.getWWN()).toString());
}
}
host.setProvisioningStatus(ProvisioningJobStatus.IN_PROGRESS.toString());
_dbClient.persistObject(host);
_dbClient.createObject(job);
// create task
String taskId = UUID.randomUUID().toString();
Operation op = new Operation();
op.setResourceType(ResourceOperationTypeEnum.INSTALL_OPERATING_SYSTEM);
_dbClient.createTaskOpStatus(Host.class, host.getId(), taskId, op);
ImageServerController controller = getController(ImageServerController.class, null);
AsyncTask task = new AsyncTask(Host.class, host.getId(), taskId);
try {
controller.installOperatingSystem(task, job.getId());
} catch (InternalException e) {
_log.error("Did not install OS due to controller error", e);
job.setJobStatus(ComputeImageJob.JobStatus.FAILED.name());
_dbClient.persistObject(job);
_dbClient.error(Host.class, host.getId(), taskId, e);
}
return toTask(host, taskId, op);
}
/**
* Method to check if the selected image is present on the
* ComputeImageServer which is associated with the CoputeSystem
*
* @param cs
* {@link ComputeSystem}
* @param img
* {@link ComputeImage} instance selected
* @throws APIException
*/
private void verifyImagePresentOnImageServer(ComputeSystem cs,
ComputeImage img) throws APIException {
URI imageServerURI = cs.getComputeImageServer();
_log.info("Verify if selected image {} exists on imageServer {}",
img.getLabel(), imageServerURI);
if (NullColumnValueGetter.isNullURI(imageServerURI)) {
_log.info(
"Compute system {} does not have an image server associated with it. Cannot proceed with OS install.",
img.getLabel());
throw APIException.badRequests
.noImageServerAssociatedToComputeSystem(cs.getLabel());
} else {
ComputeImageServer imageServer = queryObject(
ComputeImageServer.class, imageServerURI, true);
StringSet computeImagesSet = imageServer.getComputeImages();
if (computeImagesSet == null
|| !computeImagesSet.contains(img.getId().toString())) {
_log.info("Selected image {} does not exist on imageServer {}",
img.getLabel(), imageServer.getLabel());
throw APIException.badRequests
.imageNotPresentOnComputeImageServer(img.getLabel(),
imageServer.getLabel());
}
_log.info("Selected image {} exists on imageServer {}",
img.getLabel(), imageServer.getLabel());
}
}
/**
* Verifies the host being installed does not conflict with existing host IPs.
* @param host {@link Host} host being newly provisioned.
* @param param {@link OsInstallParam}
*
* @throws APIException
*/
private void verifyHostForDuplicateIP(Host host, OsInstallParam param) throws APIException {
Collection<URI> ipInterfaceURIS = _dbClient.queryByType(IpInterface.class, true);
Collection<IpInterface> ipInterfaces = _dbClient.queryObjectFields(IpInterface.class,
Arrays.asList("ipAddress", "host"), ControllerUtils.getFullyImplementedCollection(ipInterfaceURIS));
if (CollectionUtils.isNotEmpty(ipInterfaces) && StringUtils.isNotEmpty(param.getHostIp())) {
_log.info("Validating host {} for duplicate IPs.", host.getLabel());
for (IpInterface ipInterface : ipInterfaces) {
if (ipInterface.getIpAddress() != null && ipInterface.getIpAddress().equals(param.getHostIp())) {
if (!NullColumnValueGetter.isNullURI(ipInterface.getHost())) {
Host hostWithSameIp = _dbClient.queryObject(Host.class, ipInterface.getHost());
if (hostWithSameIp != null) {
_log.error("Found duplicate IP {} for existing host {}. Host with same IP exists already.",
param.getHostIp(), hostWithSameIp.getLabel());
throw APIException.badRequests.hostWithDuplicateIP(host.getLabel(), param.getHostIp(),
hostWithSameIp.getLabel());
} else {
// If there is a valid IpInterface object, but the host inside it is no longer in the
// database, then we take this opportunity to clear out the object before it causes more
// issues.
_log.warn(
"Found duplicate IP {} for non-existent host URI {}. Deleting IP Interface.",
param.getHostIp(), ipInterface.getHost().toString());
_dbClient.markForDeletion(ipInterface);
}
} else {
_log.error(
"Found duplicate IP {} for IpInterface {}. IpInterface with same IP exists already.",
param.getHostIp(), ipInterface.getId().toString());
throw APIException.badRequests.hostWithDuplicateIP(host.getLabel(), param.getHostIp(),
ipInterface.getLabel());
}
}
}
}
}
}