package org.ovirt.engine.api.restapi.resource; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toList; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Objects; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.ovirt.engine.api.model.Action; import org.ovirt.engine.api.model.CreationStatus; import org.ovirt.engine.api.model.Fault; import org.ovirt.engine.api.model.HostStorage; import org.ovirt.engine.api.model.LogicalUnit; import org.ovirt.engine.api.model.StorageDomain; import org.ovirt.engine.api.model.StorageDomainStatus; import org.ovirt.engine.api.model.StorageDomainType; import org.ovirt.engine.api.resource.AssignedDiskProfilesResource; import org.ovirt.engine.api.resource.AssignedPermissionsResource; import org.ovirt.engine.api.resource.DiskSnapshotsResource; import org.ovirt.engine.api.resource.FilesResource; import org.ovirt.engine.api.resource.ImagesResource; import org.ovirt.engine.api.resource.StorageDomainDisksResource; import org.ovirt.engine.api.resource.StorageDomainResource; import org.ovirt.engine.api.resource.StorageDomainServerConnectionsResource; import org.ovirt.engine.api.resource.StorageDomainTemplatesResource; import org.ovirt.engine.api.resource.StorageDomainVmsResource; import org.ovirt.engine.api.restapi.util.ParametersHelper; import org.ovirt.engine.api.restapi.util.StorageDomainHelper; import org.ovirt.engine.core.common.VdcObjectType; import org.ovirt.engine.core.common.action.ExtendSANStorageDomainParameters; import org.ovirt.engine.core.common.action.ProcessOvfUpdateForStorageDomainCommandParameters; import org.ovirt.engine.core.common.action.ReduceSANStorageDomainDevicesCommandParameters; import org.ovirt.engine.core.common.action.RemoveStorageDomainParameters; import org.ovirt.engine.core.common.action.StorageDomainManagementParameter; import org.ovirt.engine.core.common.action.StorageDomainParametersBase; import org.ovirt.engine.core.common.action.VdcActionParametersBase; import org.ovirt.engine.core.common.action.VdcActionType; import org.ovirt.engine.core.common.businessentities.StorageDomainSharedStatus; import org.ovirt.engine.core.common.businessentities.StorageDomainStatic; import org.ovirt.engine.core.common.businessentities.VdsStatic; import org.ovirt.engine.core.common.businessentities.storage.StorageType; import org.ovirt.engine.core.common.queries.GetPermissionsForObjectParameters; import org.ovirt.engine.core.common.queries.IdQueryParameters; import org.ovirt.engine.core.common.queries.NameQueryParameters; import org.ovirt.engine.core.common.queries.StorageDomainsAndStoragePoolIdQueryParameters; import org.ovirt.engine.core.common.queries.VdcQueryType; import org.ovirt.engine.core.compat.Guid; public class BackendStorageDomainResource extends AbstractBackendActionableResource<StorageDomain, org.ovirt.engine.core.common.businessentities.StorageDomain> implements StorageDomainResource { public static final String DESTROY = "destroy"; public static final String FORMAT = "format"; public static final String HOST = "host"; private final BackendStorageDomainsResource parent; public BackendStorageDomainResource(String id, BackendStorageDomainsResource parent) { super(id, StorageDomain.class, org.ovirt.engine.core.common.businessentities.StorageDomain.class); this.parent = parent; } BackendStorageDomainsResource getParent() { return parent; } @Override public StorageDomain get() { StorageDomain storageDomain = performGet(VdcQueryType.GetStorageDomainById, new IdQueryParameters(guid)); return addLinks(storageDomain, getLinksToExclude(storageDomain)); } @Override public StorageDomain update(StorageDomain incoming) { QueryIdResolver<Guid> storageDomainResolver = new QueryIdResolver<>(VdcQueryType.GetStorageDomainById, IdQueryParameters.class); org.ovirt.engine.core.common.businessentities.StorageDomain entity = getEntity(storageDomainResolver, true); StorageDomain model = map(entity, new StorageDomain()); StorageType storageType = entity.getStorageType(); if (storageType != null) { switch (storageType) { case ISCSI: case FCP: extendStorageDomain(incoming, model, storageType); break; default: break; } } return addLinks(performUpdate(incoming, entity, model, storageDomainResolver, VdcActionType.UpdateStorageDomain, new UpdateParametersProvider()), new String[] { "templates", "vms" }); } @Override public Response updateOvfStore(Action action) { ProcessOvfUpdateForStorageDomainCommandParameters params = new ProcessOvfUpdateForStorageDomainCommandParameters(); params.setStorageDomainId(guid); return performAction(VdcActionType.ProcessOvfUpdateForStorageDomain, params); } @Override public Response reduceLuns(Action action) { List<LogicalUnit> reducedLuns = action.getLogicalUnits().getLogicalUnits(); List<String> lunIds = reducedLuns.stream().map(LogicalUnit::getId).collect(toList()); ReduceSANStorageDomainDevicesCommandParameters parameters = new ReduceSANStorageDomainDevicesCommandParameters(guid, lunIds); return performAction(VdcActionType.ReduceSANStorageDomainDevices, parameters); } @Override public Response remove() { String host = ParametersHelper.getParameter(httpHeaders, uriInfo, HOST); if (host == null) { Fault fault = new Fault(); fault.setReason("host parameter is missing"); throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(fault).build()); } get(); Guid hostId = getHostId(host); boolean destroy = ParametersHelper.getBooleanParameter(httpHeaders, uriInfo, DESTROY, true, false); boolean format = ParametersHelper.getBooleanParameter(httpHeaders, uriInfo, FORMAT, true, false); if (destroy) { StorageDomainParametersBase parameters = new StorageDomainParametersBase(guid); parameters.setVdsId(hostId); return performAction(VdcActionType.ForceRemoveStorageDomain, parameters); } else { RemoveStorageDomainParameters parameters = new RemoveStorageDomainParameters(guid); parameters.setVdsId(hostId); parameters.setDoFormat(format); return performAction(VdcActionType.RemoveStorageDomain, parameters); } } private Guid getHostId(String host) { try { return Guid.createGuidFromString(host); } catch (IllegalArgumentException exception) { VdsStatic entity = getEntity( VdsStatic.class, VdcQueryType.GetVdsStaticByName, new NameQueryParameters(host), host ); if (entity != null) { return entity.getId(); } return Guid.Empty; } } @Override public Response isAttached(Action action) { validateParameters(action, "host.id|name"); Guid hostId = getHostId(action); org.ovirt.engine.core.common.businessentities.StorageDomain storageDomainToAttach = getEntity( org.ovirt.engine.core.common.businessentities.StorageDomain.class, VdcQueryType.GetStorageDomainById, new IdQueryParameters(guid), guid.toString() ); StorageDomainsAndStoragePoolIdQueryParameters parameters = new StorageDomainsAndStoragePoolIdQueryParameters(storageDomainToAttach, null, hostId); parameters.setCheckStoragePoolStatus(false); List<StorageDomainStatic> attachedStorageDomains = getEntity( List.class, VdcQueryType.GetStorageDomainsWithAttachedStoragePoolGuid, parameters, guid.toString(), true ); // This is an atypical action, as it doesn't invoke a backend action, but a query. As a result we need to // create and populate the returned action object so that it looks like a real action result. Action result = new Action(); result.setIsAttached(!attachedStorageDomains.isEmpty()); result.setStatus(CreationStatus.COMPLETE.value()); return Response.ok().entity(result).build(); } @Override public Response refreshLuns(Action action) { List<LogicalUnit> incomingLuns; if (action.isSetLogicalUnits()) { incomingLuns = action.getLogicalUnits().getLogicalUnits(); } else { incomingLuns = Collections.emptyList(); } ExtendSANStorageDomainParameters params = createParameters(guid, incomingLuns, false); return performAction(VdcActionType.RefreshLunsSize, params); } @Override public FilesResource getFilesResource() { return inject(new BackendFilesResource(id)); } public static synchronized boolean isIsoDomain(StorageDomain storageDomain) { StorageDomainType type = storageDomain.getType(); return type != null && type == StorageDomainType.ISO ? true : false; } public static synchronized boolean isIsoDomain(org.ovirt.engine.core.common.businessentities.StorageDomain storageDomain) { org.ovirt.engine.core.common.businessentities.StorageDomainType type = storageDomain.getStorageDomainType(); return type != null && type == org.ovirt.engine.core.common.businessentities.StorageDomainType.ISO ? true : false; } public static synchronized boolean isExportDomain(StorageDomain storageDomain) { StorageDomainType type = storageDomain.getType(); return type != null && type == StorageDomainType.EXPORT ? true : false; } public static synchronized boolean isImageDomain(StorageDomain storageDomain) { StorageDomainType type = storageDomain.getType(); return type != null && type == StorageDomainType.IMAGE; } public static synchronized String[] getLinksToExclude(StorageDomain storageDomain) { return isIsoDomain(storageDomain) ? new String[] { "templates", "vms", "disks", "images" } : isExportDomain(storageDomain) ? new String[] { "files", "images" } : isImageDomain(storageDomain) ? new String[] { "templates", "vms", "files", "disks", "storageconnections" } : new String[] { "files", "images" }; } /** * if user added new LUNs - extend the storage domain. */ private void extendStorageDomain(StorageDomain incoming, StorageDomain storageDomain, StorageType storageType) { if (incoming.getStorage() == null) { // LUNs info was not supplied in the request so no need to check whether to extend return; } List<LogicalUnit> existingLuns; if (storageDomain.isSetStorage() && storageDomain.getStorage().isSetVolumeGroup() && storageDomain.getStorage().getVolumeGroup().isSetLogicalUnits()) { existingLuns = storageDomain.getStorage().getVolumeGroup().getLogicalUnits().getLogicalUnits(); } else { existingLuns = Collections.emptyList(); } List<LogicalUnit> incomingLuns = getIncomingLuns(incoming.getStorage()); List<LogicalUnit> newLuns = findNewLuns(existingLuns, incomingLuns); boolean overrideLuns = incoming.getStorage().isSetOverrideLuns() ? incoming.getStorage().isOverrideLuns() : false; if (!newLuns.isEmpty()) { // If there are new LUNs, this means the user wants to extend the storage domain. addLunsToStorageDomain(newLuns, overrideLuns); // Remove the new LUNs from the incoming LUns before update, since they have already been dealt with. incomingLuns.removeAll(newLuns); } } private void addLunsToStorageDomain(List<LogicalUnit> newLuns, boolean overrideLuns) { ExtendSANStorageDomainParameters params = createParameters(guid, newLuns, overrideLuns); performAction(VdcActionType.ExtendSANStorageDomain, params); } @Override public AssignedPermissionsResource getPermissionsResource() { return inject(new BackendAssignedPermissionsResource(guid, VdcQueryType.GetPermissionsForObject, new GetPermissionsForObjectParameters(guid), StorageDomain.class, VdcObjectType.Storage)); } @Override protected StorageDomain map(org.ovirt.engine.core.common.businessentities.StorageDomain entity, StorageDomain template) { return parent.map(entity, template); } @Override protected StorageDomain deprecatedPopulate(StorageDomain model, org.ovirt.engine.core.common.businessentities.StorageDomain entity) { if (StorageDomainSharedStatus.Unattached.equals(entity.getStorageDomainSharedStatus())) { model.setStatus(StorageDomainStatus.UNATTACHED); } else { model.setStatus(null); } return super.deprecatedPopulate(model, entity); } private List<LogicalUnit> getIncomingLuns(HostStorage storage) { // user may pass the LUNs under Storage, or Storage-->VolumeGroup; both are supported. if (!storage.isSetLogicalUnits() || !storage.getLogicalUnits().isSetLogicalUnits()) { if (storage.isSetVolumeGroup() && storage.getVolumeGroup().isSetLogicalUnits() && storage.getVolumeGroup().getLogicalUnits().isSetLogicalUnits()) { return storage.getVolumeGroup().getLogicalUnits().getLogicalUnits(); } else { return new ArrayList<>(); } } else { return storage.getLogicalUnits().getLogicalUnits(); } } private ExtendSANStorageDomainParameters createParameters(Guid storageDomainId, List<LogicalUnit> newLuns, boolean force) { ExtendSANStorageDomainParameters params = new ExtendSANStorageDomainParameters(); params.setStorageDomainId(storageDomainId); ArrayList<String> lunIds = newLuns.stream().map(LogicalUnit::getId).collect(toCollection(ArrayList::new)); params.setLunIds(lunIds); params.setForce(force); return params; } private List<LogicalUnit> findNewLuns(List<LogicalUnit> existingLuns, List<LogicalUnit> incomingLuns) { List<LogicalUnit> newLuns = new LinkedList<>(); for (LogicalUnit incomingLun : incomingLuns) { boolean found = false; for (LogicalUnit existingLun : existingLuns) { if (lunsEqual(incomingLun, existingLun)) { found = true; break; } } if (!found) { newLuns.add(incomingLun); } } return newLuns; } private boolean lunsEqual(LogicalUnit firstLun, LogicalUnit secondLun) { return Objects.equals(firstLun.getId(), secondLun.getId()); } protected class UpdateParametersProvider implements ParametersProvider<StorageDomain, org.ovirt.engine.core.common.businessentities.StorageDomain> { @Override public VdcActionParametersBase getParameters(StorageDomain incoming, org.ovirt.engine.core.common.businessentities.StorageDomain entity) { // save SD type before mapping org.ovirt.engine.core.common.businessentities.StorageDomainType currentType = entity.getStorageStaticData() == null ? null : entity.getStorageStaticData().getStorageDomainType(); StorageDomainStatic updated = getMapper(modelType, StorageDomainStatic.class).map( incoming, entity.getStorageStaticData()); // if SD type was 'Master', and user gave 'Data', they are the same, this is not a real update, so exchange // data back to master. if (currentType == org.ovirt.engine.core.common.businessentities.StorageDomainType.Master && updated.getStorageDomainType() == org.ovirt.engine.core.common.businessentities.StorageDomainType.Data) { updated.setStorageDomainType(org.ovirt.engine.core.common.businessentities.StorageDomainType.Master); } return new StorageDomainManagementParameter(updated); } } @Override public StorageDomainTemplatesResource getTemplatesResource() { return inject(new BackendStorageDomainTemplatesResource(guid)); } @Override public StorageDomainVmsResource getVmsResource() { return inject(new BackendStorageDomainVmsResource(guid)); } @Override public StorageDomainDisksResource getDisksResource() { return inject(new BackendStorageDomainDisksResource(guid)); } @Override public StorageDomainServerConnectionsResource getStorageConnectionsResource() { return inject(new BackendStorageDomainServerConnectionsResource(guid)); } @Override public ImagesResource getImagesResource() { return inject(new BackendStorageDomainImagesResource(guid)); } @Override public DiskSnapshotsResource getDiskSnapshotsResource() { return inject(new BackendStorageDomainDiskSnapshotsResource(guid)); } @Override public AssignedDiskProfilesResource getDiskProfilesResource() { return inject(new BackendAssignedDiskProfilesResource(id)); } @Override protected StorageDomain addParents(StorageDomain model) { StorageDomainHelper.addAttachedDataCenterReferences(this, model); return model; } }