/* * 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.keystone.restapi.utils; import com.emc.storageos.cinder.CinderConstants; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.model.*; import com.emc.storageos.keystone.KeystoneConstants; import com.emc.storageos.keystone.restapi.KeystoneApiClient; import com.emc.storageos.keystone.restapi.KeystoneRestClientFactory; import com.emc.storageos.keystone.restapi.model.response.*; import com.emc.storageos.model.tenant.TenantCreateParam; import com.emc.storageos.model.tenant.UserMappingAttributeParam; import com.emc.storageos.model.tenant.UserMappingParam; import com.emc.storageos.svcs.errorhandling.resources.APIException; import com.emc.vipr.model.sys.ipreconfig.ClusterIpv4Setting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URI; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Keystone API Utils class. */ public class KeystoneUtils { private static final Logger _log = LoggerFactory.getLogger(KeystoneUtils.class); public static final String OPENSTACK_CINDER_V2_NAME = "cinderv2"; public static final String OPENSTACK_CINDER_V1_NAME = "cinder"; public static final String OPENSTACK_TENANT_ID = "tenant_id"; public static final String OPENSTACK_DEFAULT_REGION = "RegionOne"; public static final String TENANT_ID = "tenant_id"; public static final String VALUES = "values"; // Default excluded option for OSTenant. private static final boolean DEFAULT_EXCLUDED_TENANT_OPTION = false; private KeystoneRestClientFactory _keystoneApiFactory; private Properties _ovfProperties; private DbClient _dbClient; public void setDbClient(DbClient dbClient) { this._dbClient = dbClient; } public void setKeystoneFactory(KeystoneRestClientFactory factory) { this._keystoneApiFactory = factory; } public void setOvfProperties(Properties ovfProps) { _ovfProperties = ovfProps; } /** * Delete endpoint for the service with given ID. * * @param keystoneApi KeystoneApiClient. * @param serviceId OpenStack service ID. */ public void deleteKeystoneEndpoint(KeystoneApiClient keystoneApi, String serviceId) { _log.debug("START - deleteKeystoneEndpoint"); if (serviceId != null) { // Get Keystone endpoints from Keystone API. EndpointResponse endpoints = keystoneApi.getKeystoneEndpoints(); // Find endpoint to delete. EndpointV2 endpointToDelete = findEndpoint(endpoints, serviceId); // Do not execute delete call when endpoint does not exist. if (endpointToDelete != null) { // Delete endpoint using Keystone API. keystoneApi.deleteKeystoneEndpoint(endpointToDelete.getId()); _log.debug("Keystone endpoint deleted"); } } _log.debug("END - deleteKeystoneEndpoint"); } /** * Retrieves OpenStack endpoint related to given service ID. * * @param serviceId Service ID. * @return OpenStack endpoint for the given service ID. */ public EndpointV2 findEndpoint(EndpointResponse response, String serviceId) { _log.debug("START - findEndpoint"); if (serviceId == null) { _log.error("serviceId is null"); throw APIException.internalServerErrors.targetIsNullOrEmpty("Service id"); } for (EndpointV2 endpoint : response.getEndpoints()) { if (endpoint.getServiceId().equals(serviceId)) { _log.debug("END - findEndpoint"); return endpoint; } } _log.warn("Missing endpoint for service {}", serviceId); // Return null if there is no endpoints for given service. return null; } /** * Retrieves OpenStack service ID with given service name. * * @param keystoneApi Keystone Api client. * @param serviceName Name of a service to retrieve. * @return ID of service with given name. */ public String findServiceId(KeystoneApiClient keystoneApi, String serviceName) { _log.debug("START - findServiceId"); if (serviceName == null) { _log.error("serviceName is null"); throw APIException.internalServerErrors.targetIsNullOrEmpty("Service name"); } // Get Keystone services from Keystone API. ServiceResponse services = keystoneApi.getKeystoneServices(); for (ServiceV2 service : services.getServices()) { if (service.getName().equals(serviceName)) { _log.debug("END - findServiceId"); return service.getId(); } } _log.warn("Missing service {}", serviceName); // Return null if service is missing. return null; } /** * Retrieves URI from server urls. * * @param serverUrls Set of strings representing server urls. * @return First URI from server urls param. */ public URI retrieveUriFromServerUrls(Set<String> serverUrls) { URI authUri = null; for (String uri : serverUrls) { authUri = URI.create(uri); break; // There will be single URL only } return authUri; } /** * Get region for the service with given ID. * * @param keystoneApi KeystoneApiClient. * @param serviceId OpenStack service ID. */ public String getRegionForService(KeystoneApiClient keystoneApi, String serviceId) { _log.debug("START - getRegionForService"); if (serviceId == null) { _log.error("serviceId is null"); throw APIException.internalServerErrors.targetIsNullOrEmpty("Service id"); } // Get Keystone endpoints from Keystone API. EndpointResponse endpoints = keystoneApi.getKeystoneEndpoints(); // Find endpoint for the service. EndpointV2 endpoint = findEndpoint(endpoints, serviceId); // Return null if endpoint is null, otherwise return region name. if (endpoint != null) { _log.debug("END - getRegionForService"); // Return region name. return endpoint.getRegion(); } _log.warn("Endpoint missing for a service with ID: {}", serviceId); return null; } /** * Get Keystone API client. * * @param authUri URI pointing to Keystone server. * @param username OpenStack username. * @param usernamePassword OpenStack password. * @param tenantName OpenStack tenantname. * @return keystoneApi KeystoneApiClient. */ public KeystoneApiClient getKeystoneApi(URI authUri, String username, String usernamePassword, String tenantName) { // Get Keystone API Client. KeystoneApiClient keystoneApi = (KeystoneApiClient) _keystoneApiFactory.getRESTClient( authUri, username, usernamePassword); keystoneApi.setTenantName(tenantName); return keystoneApi; } /** * Retrieves username and tenantname from the AuthnProvider. * * @param managerDN of a Authentication Provider. * @return StringMap containing username and tenantname keys with values. */ private StringMap getUsernameAndTenant(String managerDN) { String username = managerDN.split(",")[0].split("=")[1]; String tenantName = managerDN.split(",")[1].split("=")[1]; StringMap map = new StringMap(); map.put(CinderConstants.USERNAME, username); map.put(CinderConstants.TENANTNAME, tenantName); return map; } /** * Get Keystone API client. * * @param managerDN of an Authentication Provider. * @param serverUrls of an Authentication Provider * @param managerPassword of an Authentication Provider * @return keystoneApi KeystoneApiClient. */ public KeystoneApiClient getKeystoneApi(String managerDN, StringSet serverUrls, String managerPassword) { URI authUri = retrieveUriFromServerUrls(serverUrls); StringMap usernameAndTenantMap = getUsernameAndTenant(managerDN); String username = usernameAndTenantMap.get(CinderConstants.USERNAME); String tenantName = usernameAndTenantMap.get(CinderConstants.TENANTNAME); // Get Keystone API Client. return getKeystoneApi(authUri, username, managerPassword, tenantName); } /** * Register CoprHD in Keystone. * Creates an endpoint pointing to CoprHd instead to Cinder. * * @param managerDN of an Authentication Provider. * @param serverUrls of an Authentication Provider * @param managerPassword of an Authentication Provider */ public void registerCoprhdInKeystone(String managerDN, StringSet serverUrls, String managerPassword) { _log.debug("START - register CoprHD in Keystone"); // Create a new KeystoneAPI. KeystoneApiClient keystoneApi = getKeystoneApi(managerDN, serverUrls, managerPassword); // Find Id of cinderv2 service. String cinderv2ServiceId = findServiceId(keystoneApi, KeystoneUtils.OPENSTACK_CINDER_V2_NAME); // Find Id of cinderv1 service. String cinderServiceId = findServiceId(keystoneApi, KeystoneUtils.OPENSTACK_CINDER_V1_NAME); // Create service when cinderv2 service is missing. if (cinderv2ServiceId == null) { ServiceV2 service = prepareNewCinderService(true); CreateServiceResponse response = keystoneApi.createKeystoneService(service); cinderv2ServiceId = response.getService().getId(); } else { // Delete old endpoint for cinderv2 service. deleteKeystoneEndpoint(keystoneApi, cinderv2ServiceId); } // Create service when cinder service is missing. if (cinderServiceId == null) { ServiceV2 service = prepareNewCinderService(false); CreateServiceResponse response = keystoneApi.createKeystoneService(service); cinderServiceId = response.getService().getId(); } else { // Delete old endpoint for cinderv1 service. deleteKeystoneEndpoint(keystoneApi, cinderServiceId); } // Get region name for a cinderv2 service. String region = getRegionForService(keystoneApi, cinderv2ServiceId); // Set default region in case when endpoint is not present. if (region == null) { region = KeystoneUtils.OPENSTACK_DEFAULT_REGION; } // Prepare new endpoint for cinderv2 service. EndpointV2 newEndpointV2 = prepareNewCinderEndpoint(region, cinderv2ServiceId, true); // Prepare new endpoint for cinderv1 service. EndpointV2 newEndpointV1 = prepareNewCinderEndpoint(region, cinderServiceId, false); // Create a new endpoint pointing to CoprHD for cinderv2 using Keystone API. keystoneApi.createKeystoneEndpoint(newEndpointV2); // Create a new endpoint pointing to CoprHD for cinderv1 using Keystone API. keystoneApi.createKeystoneEndpoint(newEndpointV1); _log.debug("END - register CoprHD in Keystone"); } /** * Prepare a new service (cinder or cinderv2). * * @param isCinderv2 Boolean that holds information about version of a service. * @return Service filled with necessary information. */ public ServiceV2 prepareNewCinderService(Boolean isCinderv2) { ServiceV2 service = new ServiceV2(); if (isCinderv2) { service.setName(KeystoneUtils.OPENSTACK_CINDER_V2_NAME); service.setType(CinderConstants.SERVICE_TYPE_VOLUMEV2); } else { service.setName(KeystoneUtils.OPENSTACK_CINDER_V1_NAME); service.setType(CinderConstants.SERVICE_TYPE_VOLUME); } service.setDescription(CinderConstants.SERVICE_DESCRIPTION); return service; } /** * Prepare a new endpoint for cinder or cinderv2 service. * * @param region Region assigned to the endpoint. * @param serviceId Cinder service ID. * @param isCinderv2 Boolean that holds information about version of a service. * @return Endpoint filled with necessary information. */ public EndpointV2 prepareNewCinderEndpoint(String region, String serviceId, Boolean isCinderv2) { String url = ""; String clusterVIP = getVIP(); if (clusterVIP == null) { _log.error("Could not retrieve cluster Virtual IP"); throw APIException.internalServerErrors.targetIsNullOrEmpty("Virtual IP"); } _log.debug("Cluster VIP: {}", clusterVIP); // Checks whether url should point to cinderv2 or to cinder service. if (isCinderv2) { url = CinderConstants.HTTPS_URL + clusterVIP + CinderConstants.COPRHD_URL_V2; } else { url = CinderConstants.HTTPS_URL + clusterVIP + CinderConstants.COPRHD_URL_V1; } EndpointV2 endpoint = new EndpointV2(); endpoint.setRegion(region); endpoint.setServiceId(serviceId); endpoint.setPublicURL(url); endpoint.setAdminURL(url); endpoint.setInternalURL(url); return endpoint; } /** * Returns Virtual IP of CoprHD. * * @return CoprHD VIP. */ public String getVIP() { // Get cluster Virtual IP. Map<String, String> ovfprops = (Map) _ovfProperties; ClusterIpv4Setting ipv4Setting = new ClusterIpv4Setting(); ipv4Setting.loadFromPropertyMap(ovfprops); return ipv4Setting.getNetworkVip(); } /** * Populate or Modify the keystone token * in authentication provider. * * @param managerDN of an Authentication Provider. * @param serverUrls of an Authentication Provider * @param password of an Authentication Provider * * @return StringMap containing keystone authentication keys. */ public StringMap populateKeystoneToken(StringSet serverUrls, String managerDN, String password) { URI authUri = retrieveUriFromServerUrls(serverUrls); StringMap usernameAndTenantMap = getUsernameAndTenant(managerDN); String username = usernameAndTenantMap.get(CinderConstants.USERNAME); String tenantName = usernameAndTenantMap.get(CinderConstants.TENANTNAME); KeystoneApiClient keystoneApi = getKeystoneApi(authUri, username, password, tenantName); keystoneApi.authenticate_keystone(); StringMap keystoneAuthKeys = new StringMap(); keystoneAuthKeys.put(KeystoneConstants.AUTH_TOKEN, keystoneApi.getAuthToken()); return keystoneAuthKeys; } /** * Delete endpoints for cinder service. * * @param managerDN of an Authentication Provider. * @param serverUrls of an Authentication Provider * @param managerPassword of an Authentication Provider */ public void deleteCinderEndpoints(String managerDN, StringSet serverUrls, String managerPassword) { // Create a new KeystoneAPI. KeystoneApiClient keystoneApi = getKeystoneApi(managerDN, serverUrls, managerPassword); // Get a cinderv2 service id. String serviceIdV2 = findServiceId(keystoneApi, KeystoneUtils.OPENSTACK_CINDER_V2_NAME); // Get a cinderv1 service id. String serviceIdV1 = findServiceId(keystoneApi, KeystoneUtils.OPENSTACK_CINDER_V1_NAME); // Delete endpoint when cinderv2 service exist. if (serviceIdV2 != null) { // Delete endpoint for cinderv2 service. deleteKeystoneEndpoint(keystoneApi, serviceIdV2); } // Delete endpoint when cinder service exist. if (serviceIdV1 != null) { // Delete endpoint for cinder service. deleteKeystoneEndpoint(keystoneApi, serviceIdV1); } } /** * Retrieves Keystone Authentication Provider from CoprHD. * * @return Keystone Authentication Provider. */ public AuthnProvider getKeystoneProvider() { List<URI> authnProviderURI = _dbClient.queryByType(AuthnProvider.class, true); Iterator<AuthnProvider> providerIter = _dbClient.queryIterativeObjects(AuthnProvider.class, authnProviderURI); // Iterate over Providers and return Keystone Provider. while (providerIter.hasNext()) { AuthnProvider provider = providerIter.next(); if (AuthnProvider.ProvidersType.keystone.toString().equalsIgnoreCase(provider.getMode())) { return provider; } } // Return null whether Keystone Provider is missing in CoprHD. return null; } /** * Retrieves OpenStack Tenants from Keystone. * * @return List of OpenStack Tenants. */ public List<KeystoneTenant> getOpenStackTenants() { AuthnProvider keystoneProvider = getKeystoneProvider(); if (keystoneProvider == null) { throw APIException.internalServerErrors.targetIsNullOrEmpty("Keystone provider"); } // Get Keystone API client. KeystoneApiClient keystoneApiClient = getKeystoneApi(keystoneProvider.getManagerDN(), keystoneProvider.getServerUrls(), keystoneProvider.getManagerPassword()); // Get OpenStack Tenants. // You cannot remove or add elements dynamically to Arrays (Arrays.asList) that is why this needs to be wrapped in a new list. return new ArrayList<>(Arrays.asList(keystoneApiClient.getKeystoneTenants().getTenants())); } /** * Retrieves OpenStack Tenant with given id. * * @param id Tenant ID. * * @return OpenStack Tenant. */ public KeystoneTenant getTenantWithId(String id) { AuthnProvider keystoneProvider = getKeystoneProvider(); if (keystoneProvider == null) { throw APIException.internalServerErrors.targetIsNullOrEmpty("Keystone provider"); } // Get Keystone API client. KeystoneApiClient keystoneApiClient = getKeystoneApi(keystoneProvider.getManagerDN(), keystoneProvider.getServerUrls(), keystoneProvider.getManagerPassword()); for (KeystoneTenant tenant : keystoneApiClient.getKeystoneTenants().getTenants()) { if (tenant.getId().equals(id)) { return tenant; } } return null; } /** * Retrieves CoprHD Tenants with OpenStack ID parameter. * * @return List of CoprHD Tenants. */ public List<TenantOrg> getCoprhdTenantsWithOpenStackId() { List<URI> coprhdTenantsURI = _dbClient.queryByType(TenantOrg.class, true); Iterator<TenantOrg> tenantsIter = _dbClient.queryIterativeObjects(TenantOrg.class, coprhdTenantsURI); List<TenantOrg> tenants = new ArrayList<>(); // Iterate over CoprHD Tenants and get only those that contain tenant_id parameter. while (tenantsIter.hasNext()) { TenantOrg tenant = tenantsIter.next(); if (getCoprhdTenantUserMapping(tenant) != null) { tenants.add(tenant); } } return tenants; } /** * Retrieves CoprHD Tenant with given OpenStack ID. * * @return CoprHD Tenant. */ public TenantOrg getCoprhdTenantWithOpenstackId(String id) { if (id != null && !id.isEmpty()) { List<TenantOrg> tenants = getCoprhdTenantsWithOpenStackId(); for (TenantOrg tenant : tenants) { String userMapping = getCoprhdTenantUserMapping(tenant); if (userMapping != null && userMapping.contains(id)) { return tenant; } } } return null; } /** * Retrieves UserMapping having "tenant_id" as attribute in CoprHD Tenant. * * @param tenant CoprHD Tenant. * @return User Mapping for given Tenant. */ public String getCoprhdTenantUserMapping(TenantOrg tenant) { StringSetMap userMappings = tenant.getUserMappings(); // Ignore root Tenant. if (!TenantOrg.isRootTenant(tenant) && userMappings != null) { // Return mapping that contains tenant_id. for (AbstractChangeTrackingSet<String> userMappingSet : userMappings.values()) { for (String mapping : userMappingSet) { if (mapping.contains(TENANT_ID)) { return mapping; } } } } // Return null whether Tenant has no mapping with tenant_id parameter. return null; } /** * Retrieves OpenStack Tenant ID from UserMapping. * UserMapping string contains 3 main subgroups: domain, attributes and groups. Openstack tenant id is located in attributes subgroup * assigned to 'values' string. Method splits userMapping string and looks for 'values' and then retrieves tenant id from it. * * @param userMapping CoprHD UserMapping. * @return OpenStack Tenant ID. */ public String getTenantIdFromUserMapping(String userMapping) { // Create split pattern. Pattern p = Pattern.compile("\\[(.*?)\\]"); // Apply pattern. Matcher m = p.matcher(userMapping); String result; while (m.find()) { if (m.group().contains(VALUES)) { result = m.group().split(":")[1]; return result.substring(2, result.length() - 2); } } return ""; } /** * Finds CoprHD representation of OpenStack Tenant with given OS ID. * * @param tenantId OpenStack Tenant ID. * @return CoprHD Tenant. */ public OSTenant findOpenstackTenantInCoprhd(String tenantId) { List<URI> osTenantURI = _dbClient.queryByType(OSTenant.class, true); Iterator<OSTenant> osTenantIter = _dbClient.queryIterativeObjects(OSTenant.class, osTenantURI); while (osTenantIter.hasNext()) { OSTenant osTenant = osTenantIter.next(); if (osTenant.getOsId().equals(tenantId)) { return osTenant; } } return null; } /** * Prepares userMappings with OpenStack Tenant ID for CoprHD Tenant. * * @param openstackId OpenStack Tenant ID. * @return UserMappings. */ public List<UserMappingParam> prepareUserMappings(String openstackId) { AuthnProvider provider = getKeystoneProvider(); // Create mapping rules List<UserMappingParam> userMappings = new ArrayList<>(); List<String> values = new ArrayList<>(); values.add(openstackId); List<UserMappingAttributeParam> attributes = new ArrayList<>(); attributes.add(new UserMappingAttributeParam(KeystoneUtils.OPENSTACK_TENANT_ID, values)); userMappings.add(new UserMappingParam(provider.getDomains().iterator().next(), attributes, new ArrayList<String>())); return userMappings; } /** * Prepares TenantCreateParam class filled with information from given Tenant. * * @param tenant OpenStack Tenant. * @return TenantCreateParam. */ public TenantCreateParam prepareTenantParam(KeystoneTenant tenant) { TenantCreateParam param = new TenantCreateParam(CinderConstants.TENANT_NAME_PREFIX + " " + tenant.getName(), prepareUserMappings(tenant.getId())); param.setDescription(getProperTenantDescription(tenant.getDescription())); return param; } /** * Tags Project with given Openstack Tenant ID. * * @param projectId CoprHD Project ID. * @param osTenantId OpenStack Tenant ID. * @param coprhdTenantId CoprHD Tenant ID. * @return Updated project. */ public Project tagProjectWithOpenstackId(URI projectId, String osTenantId, String coprhdTenantId) { // Tag project with OpenStack tenant_id Project project = _dbClient.queryObject(Project.class, projectId); if (project != null) { ScopedLabelSet tagSet = new ScopedLabelSet(); ScopedLabel tagLabel = new ScopedLabel(coprhdTenantId, osTenantId); tagSet.add(tagLabel); project.setTag(tagSet); _dbClient.updateObject(project); return project; } throw APIException.internalServerErrors.targetIsNullOrEmpty("Project with id: " + projectId); } /** * Maps Openstack KeystoneTenant to OSTenant. * * @param tenant OpenStack Tenant to map. * @return OSTenant. */ public OSTenant mapToOsTenant(KeystoneTenant tenant) { if (tenant != null) { OSTenant osTenant = new OSTenant(); osTenant.setOsId(tenant.getId()); // Check whether description is null or empty. osTenant.setDescription(getProperTenantDescription(tenant.getDescription())); osTenant.setName(tenant.getName()); osTenant.setEnabled(Boolean.parseBoolean(tenant.getEnabled())); osTenant.setExcluded(DEFAULT_EXCLUDED_TENANT_OPTION); return osTenant; } throw APIException.internalServerErrors.targetIsNullOrEmpty("KeystoneTenant"); } /** * Checks whether given Tenant description is null or empty and returns appropriate description. * * @param description Tenant description. * @return String. */ public String getProperTenantDescription(String description) { if (description != null && !description.isEmpty()) { return description; } return CinderConstants.TENANT_NAME_PREFIX; } }