/* * Copyright 2016 Intel Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.emc.storageos.api.service.impl.resource; import com.emc.storageos.api.mapper.DbObjectMapper; import com.emc.storageos.api.service.impl.resource.utils.OpenStackSynchronizationTask; import com.emc.storageos.db.client.URIUtil; import com.emc.storageos.db.client.constraint.PrefixConstraint; import com.emc.storageos.db.client.constraint.URIQueryResultList; import com.emc.storageos.db.client.model.*; import com.emc.storageos.keystone.restapi.KeystoneApiClient; import com.emc.storageos.keystone.restapi.model.response.KeystoneTenant; import com.emc.storageos.keystone.restapi.model.response.TenantListRestResp; import com.emc.storageos.keystone.restapi.utils.KeystoneUtils; import com.emc.storageos.model.ResourceTypeEnum; import com.emc.storageos.model.keystone.OSTenantRestRep; import com.emc.storageos.model.keystone.OSTenantListRestRep; import com.emc.storageos.model.keystone.OpenStackTenantListParam; import com.emc.storageos.model.keystone.OpenStackTenantParam; import com.emc.storageos.security.authentication.StorageOSUser; import com.emc.storageos.security.authorization.CheckPermission; import com.emc.storageos.security.authorization.DefaultPermissions; import com.emc.storageos.security.authorization.Role; import com.emc.storageos.svcs.errorhandling.resources.APIException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; /** * API for manipulating OpenStack Keystone service. */ @Path("/v2/keystone") @DefaultPermissions(readRoles = { Role.SECURITY_ADMIN }, writeRoles = { Role.SECURITY_ADMIN }) public class KeystoneService extends TaskResourceService { private static final Logger _log = LoggerFactory.getLogger(KeystoneService.class); private KeystoneUtils _keystoneUtils; private OpenStackSynchronizationTask _openStackSynchronizationTask; private AuthnConfigurationService _authService; public void setAuthService(AuthnConfigurationService authService) { this._authService = authService; } public void setOpenStackSynchronizationTask(OpenStackSynchronizationTask openStackSynchronizationTask) { this._openStackSynchronizationTask = openStackSynchronizationTask; } public void setKeystoneUtils(KeystoneUtils keystoneUtils) { this._keystoneUtils = keystoneUtils; } /** * Get a list of OpenStack Tenants. * Uses data from Keystone Authentication Provider to connect Keystone and retrieve Tenants information. * * @brief Show OpenStack Tenants. * @return OpenStack Tenants details. * @see TenantListRestResp */ @GET @Path("/tenants") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SECURITY_ADMIN }) public TenantListRestResp listOpenstackTenants() { _log.debug("Keystone Service - listOpenstackTenants"); StorageOSUser user = getUserFromContext(); if (!_permissionsHelper.userHasGivenRoleInAnyTenant(user, Role.SECURITY_ADMIN, Role.TENANT_ADMIN)) { throw APIException.forbidden.insufficientPermissionsForUser(user.getName()); } AuthnProvider keystoneProvider = _keystoneUtils.getKeystoneProvider(); // Get OpenStack Tenants only when Keystone Provider exists. if (keystoneProvider != null) { KeystoneApiClient keystoneApiClient = _keystoneUtils.getKeystoneApi(keystoneProvider.getManagerDN(), keystoneProvider.getServerUrls(), keystoneProvider.getManagerPassword()); List<KeystoneTenant> OSTenantList = new ArrayList<>(Arrays.asList(keystoneApiClient.getKeystoneTenants().getTenants())); TenantListRestResp response = new TenantListRestResp(); response.setOpenstackTenants(OSTenantList); return response; } throw APIException.internalServerErrors.targetIsNullOrEmpty("Keystone Authentication Provider"); } /** * Get OpenStack Tenant with given ID. * Uses data from Keystone Authentication Provider to connect Keystone and retrieve Tenant information. * * @param id OpenStack Tenant ID. * @brief Show OpenStack Tenant. * @return OpenStack Tenant details. * @see TenantListRestResp */ @GET @Path("/tenants/{id}") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SECURITY_ADMIN }) public OpenStackTenantParam getOpenstackTenant(@PathParam("id") URI id) { if (id == null) { throw APIException.internalServerErrors.targetIsNullOrEmpty("Tenant ID"); } _log.debug("Keystone Service - getOpenstackTenant with id: {}", id.toString()); List<KeystoneTenant> tenants = listOpenstackTenants().getOpenstackTenants(); for (KeystoneTenant tenant : tenants) { if (tenant.getId().equals(id.toString())) { return mapToOpenstackParam(tenant); } } throw APIException.notFound.unableToFindEntityInURL(id); } /** * Creates representation of OpenStack Tenants in CoprHD. * * @param param OpenStackTenantListParam OpenStack Tenants representation with all necessary elements. * @brief Creates representation of OpenStack Tenants in CoprHD. * @return Newly created Tenants. * @see */ @POST @Path("/tenants") @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SECURITY_ADMIN }) public OSTenantListRestRep createOpenStackTenants(OpenStackTenantListParam param) { _log.debug("Keystone Service - createOpenStackTenants"); if (param.getOpenstackTenants() == null || param.getOpenstackTenants().isEmpty()) { throw APIException.internalServerErrors.targetIsNullOrEmpty("Tenant list param"); } List<OSTenant> openstackTenants = new ArrayList<>(); for (OpenStackTenantParam openStackTenantParam : param.getOpenstackTenants()) { openstackTenants.add(prepareOpenstackTenant(openStackTenantParam)); } if (!openstackTenants.isEmpty()) { _dbClient.createObject(openstackTenants); } AuthnProvider keystoneProvider = _keystoneUtils.getKeystoneProvider(); if (keystoneProvider == null) { throw APIException.internalServerErrors.targetIsNullOrEmpty("Keystone Authentication Provider"); } if (keystoneProvider.getAutoRegCoprHDNImportOSProjects()) { if (_openStackSynchronizationTask.getSynchronizationTask() == null) { // Do not create Tenants and Projects once synchronization task is running. _authService.createTenantsAndProjectsForAutomaticKeystoneRegistration(); _openStackSynchronizationTask.startSynchronizationTask(_openStackSynchronizationTask.getTaskInterval(keystoneProvider)); } } return map(openstackTenants); } /** * Updates representation of OpenStack Tenants in CoprHD. * Creates Tenants and Projects for new Tenants and deletes them for excluded Tenants. * * @param param OpenStackTenantListParam OpenStack Tenants representation with all necessary elements for update. * @brief Updates representation of OpenStack Tenants in CoprHD. * @return Updated Tenants. * @see */ @PUT @Path("/ostenants") @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SECURITY_ADMIN }) public OSTenantListRestRep updateOpenstackTenants(OSTenantListRestRep param) { _log.debug("Keystone Service - updateOpenstackTenants"); if (param.getOSTenantsRestRep() == null || param.getOSTenantsRestRep().isEmpty()) { throw APIException.internalServerErrors.targetIsNullOrEmpty("Tenant list param"); } OSTenantListRestRep resp = new OSTenantListRestRep(); List<OSTenant> tenantsToUpdate = new ArrayList<>(); List<OSTenant> tenantsToDelete = new ArrayList<>(); OSTenant osTenant; for (OSTenantRestRep tenant : param.getOSTenantsRestRep()) { osTenant = _dbClient.queryObject(OSTenant.class, tenant.getId()); if (!osTenant.getExcluded().equals(tenant.getExcluded())) { // Tenant changed from included to excluded. Mark for deletion related Tenant and Project. if (!osTenant.getExcluded()) { tenantsToDelete.add(osTenant); } else { tenantsToUpdate.add(osTenant); } osTenant.setExcluded(tenant.getExcluded()); resp.getOSTenantsRestRep().add(mapToCoprhdOsTenant(osTenant)); } } if (!tenantsToUpdate.isEmpty()) { // Create Tenant and Project for included Tenants. for (OSTenant tenant : tenantsToUpdate) { if (_keystoneUtils.getCoprhdTenantWithOpenstackId(tenant.getOsId()) == null) { _authService.createTenantAndProjectForOpenstackTenant(tenant); } } } tenantsToUpdate.addAll(tenantsToDelete); if (!tenantsToUpdate.isEmpty()) { _dbClient.updateObject(tenantsToUpdate); } if (!tenantsToDelete.isEmpty()) { for (OSTenant tenant : tenantsToDelete) { TenantOrg tenantOrg = _keystoneUtils.getCoprhdTenantWithOpenstackId(tenant.getOsId()); if (tenantOrg != null && !TenantOrg.isRootTenant(tenantOrg)) { URIQueryResultList uris = new URIQueryResultList(); _dbClient.queryByConstraint( PrefixConstraint.Factory.getTagsPrefixConstraint( Project.class, tenant.getOsId(), tenantOrg.getId()), uris); for (URI projectUri : uris) { Project project = _dbClient.queryObject(Project.class, projectUri); ArgValidator.checkReference(Project.class, project.getId(), checkForDelete(project)); _dbClient.markForDeletion(project); } ArgValidator.checkReference(TenantOrg.class, tenantOrg.getId(), checkForDelete(tenantOrg)); _dbClient.markForDeletion(tenantOrg); } } } return resp; } /** * Gets a list of CoprHd representation of OpenStack Tenants (OSTenant). * * @brief Show CoprHD OpenStack Tenants. * @return CoprHD OpenStack Tenants details. * @see OSTenantListRestRep */ @GET @Path("/ostenants") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SECURITY_ADMIN }) public OSTenantListRestRep listCoprhdOsTenants() { _log.debug("Keystone Service - listCoprhdOsTenants"); List<OSTenant> tenants = getOsTenantsFromCoprhdDb(); return map(tenants); } private List<OSTenant> getOsTenantsFromCoprhdDb() { List<URI> osTenantURIs = _dbClient.queryByType(OSTenant.class, true); return _dbClient.queryObject(OSTenant.class, osTenantURIs); } /** * Gets a CoprHd representation of OpenStack Tenant (OSTenant) with given ID. * * @param id CoprHD Tenant ID. * @brief Show CoprHD Tenant. * @return CoprHD Tenant details. * @see OSTenantRestRep */ @GET @Path("/ostenants/{id}") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SECURITY_ADMIN }) public OSTenantRestRep getCoprhdOsTenant(@PathParam("id") URI id) { if (id == null) { throw APIException.internalServerErrors.targetIsNullOrEmpty("Tenant ID"); } _log.debug("Keystone Service - getCoprhdOsTenant with id: {}", id.toString()); OSTenant osTenant = _dbClient.queryObject(OSTenant.class, id); if (osTenant != null) { return mapToCoprhdOsTenant(osTenant); } throw APIException.notFound.unableToFindEntityInURL(id); } /** * Synchronize CoprHD and OpenStack Tenants in CoprHD database. * * @brief Updates CoprHD OSTenant objects. * @return Response code 200 * @see OSTenant */ @PUT @Path("/ostenants/sync") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SECURITY_ADMIN }) public Response synchronizeOpenstackTenants() { List<KeystoneTenant> openstackTenantsList = listOpenstackTenants().getOpenstackTenants(); List<OSTenant> coprhdOsTenantsList = getOsTenantsFromCoprhdDb(); for (Iterator<KeystoneTenant> i = openstackTenantsList.iterator(); i.hasNext();) { KeystoneTenant tenant = i.next(); for (Iterator<OSTenant> j = coprhdOsTenantsList.iterator(); j.hasNext();) { OSTenant osTenant = j.next(); // Remove Tenant from both list once match is found. if (osTenant.getOsId().equals(tenant.getId())) { j.remove(); i.remove(); } } } // Remove OSTenant from CoprHD when related Tenant was removed in OpenStack. for (OSTenant osTenant : coprhdOsTenantsList) { _dbClient.removeObject(osTenant); } // Create OOSTenant in CoprHD for every new Tenant in OpenStack. for (KeystoneTenant keystoneTenant : openstackTenantsList) { OSTenant tenant = _keystoneUtils.mapToOsTenant(keystoneTenant); tenant.setId(URIUtil.createId(OSTenant.class)); _dbClient.createObject(tenant); } return Response.ok().build(); } private OSTenant prepareOpenstackTenant(OpenStackTenantParam param) { OSTenant openstackTenant = new OSTenant(); openstackTenant.setId(URIUtil.createId(OSTenant.class)); openstackTenant.setName(param.getName()); openstackTenant.setDescription(_keystoneUtils.getProperTenantDescription(param.getDescription())); openstackTenant.setEnabled(param.getEnabled()); openstackTenant.setExcluded(param.getExcluded()); openstackTenant.setOsId(param.getOsId()); return openstackTenant; } private OSTenantListRestRep map(List<OSTenant> tenants) { OSTenantListRestRep response = new OSTenantListRestRep(); List<OSTenantRestRep> OSTenantsRestRep = new ArrayList<>(); for (OSTenant osTenant : tenants) { OSTenantsRestRep.add(mapToCoprhdOsTenant(osTenant)); } response.setOSTenantsRestRep(OSTenantsRestRep); return response; } private OpenStackTenantParam mapToOpenstackParam(KeystoneTenant from) { OpenStackTenantParam to = new OpenStackTenantParam(); to.setOsId(from.getId()); to.setDescription(from.getDescription()); to.setEnabled(Boolean.parseBoolean(from.getEnabled())); to.setExcluded(false); to.setName(from.getName()); return to; } private OSTenantRestRep mapToCoprhdOsTenant(OSTenant from) { OSTenantRestRep to = new OSTenantRestRep(); DbObjectMapper.mapDataObjectFields(from, to); to.setExcluded(from.getExcluded()); to.setDescription(from.getDescription()); to.setOsId(from.getOsId()); to.setEnabled(from.getEnabled()); to.setName(from.getName()); return to; } @Override protected DataObject queryResource(URI id) { return null; } @Override protected URI getTenantOwner(URI id) { return null; } @Override protected ResourceTypeEnum getResourceType() { return null; } }