From 025301d08b061482c1f046d562bf017c8cbcfe8d Mon Sep 17 00:00:00 2001 From: ChrisC Date: Tue, 31 Jan 2017 11:40:03 +0100 Subject: Initial OpenECOMP MSO commit Change-Id: Ia6a7574859480717402cc2f22534d9973a78fa6d Signed-off-by: ChrisC --- .../mso/openstack/utils/MsoKeystoneUtils.java | 684 +++++++++++++++++++++ 1 file changed, 684 insertions(+) create mode 100644 adapters/mso-adapter-utils/src/main/java/org/openecomp/mso/openstack/utils/MsoKeystoneUtils.java (limited to 'adapters/mso-adapter-utils/src/main/java/org/openecomp/mso/openstack/utils/MsoKeystoneUtils.java') diff --git a/adapters/mso-adapter-utils/src/main/java/org/openecomp/mso/openstack/utils/MsoKeystoneUtils.java b/adapters/mso-adapter-utils/src/main/java/org/openecomp/mso/openstack/utils/MsoKeystoneUtils.java new file mode 100644 index 0000000000..82203d044c --- /dev/null +++ b/adapters/mso-adapter-utils/src/main/java/org/openecomp/mso/openstack/utils/MsoKeystoneUtils.java @@ -0,0 +1,684 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.openstack.utils; + + +import java.io.Serializable; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; + +import org.openecomp.mso.cloud.CloudConfig; +import org.openecomp.mso.cloud.CloudConfigFactory; +import org.openecomp.mso.cloud.CloudIdentity; +import org.openecomp.mso.cloud.CloudSite; +import org.openecomp.mso.logger.MsoAlarmLogger; +import org.openecomp.mso.logger.MsoLogger; +import org.openecomp.mso.logger.MessageEnum; +import org.openecomp.mso.openstack.beans.MsoTenant; +import org.openecomp.mso.openstack.exceptions.MsoAdapterException; +import org.openecomp.mso.openstack.exceptions.MsoCloudSiteNotFound; +import org.openecomp.mso.openstack.exceptions.MsoException; +import org.openecomp.mso.openstack.exceptions.MsoOpenstackException; +import org.openecomp.mso.openstack.exceptions.MsoTenantAlreadyExists; +import org.openecomp.mso.properties.MsoJavaProperties; +import org.openecomp.mso.properties.MsoPropertiesException; +import org.openecomp.mso.properties.MsoPropertiesFactory; +import com.woorea.openstack.base.client.OpenStackBaseException; +import com.woorea.openstack.base.client.OpenStackConnectException; +import com.woorea.openstack.base.client.OpenStackRequest; +import com.woorea.openstack.base.client.OpenStackResponseException; +import com.woorea.openstack.keystone.Keystone; +import com.woorea.openstack.keystone.model.Access; +import com.woorea.openstack.keystone.model.Metadata; +import com.woorea.openstack.keystone.model.Role; +import com.woorea.openstack.keystone.model.Roles; +import com.woorea.openstack.keystone.model.Tenant; +import com.woorea.openstack.keystone.model.User; +import com.woorea.openstack.keystone.utils.KeystoneUtils; +import com.woorea.openstack.keystone.model.Authentication; + +public class MsoKeystoneUtils extends MsoTenantUtils { + + // Cache the Keystone Clients statically. Since there is just one MSO user, there is no + // benefit to re-authentication on every request (or across different flows). The + // token will be used until it expires. + // + // The cache key is "cloudId" + private static Map adminClientCache = new HashMap (); + + private static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA); + String msoPropID; + + public MsoKeystoneUtils (String msoPropID) { + super(msoPropID); + this.msoPropID = msoPropID; + LOGGER.debug("MsoKeyStoneUtils:" + msoPropID); + } + + /** + * Create a tenant with the specified name in the given cloud. If the tenant already exists, + * an Exception will be thrown. The MSO User will also be added to the "member" list of + * the new tenant to perform subsequent Nova/Heat commands in the tenant. If the MSO User + * association fails, the entire transaction will be rolled back. + *

+ * For the AIC Cloud (DCP/LCP): it is not clear that cloudId is needed, as all admin + * requests go to the centralized identity service in DCP. However, if some artifact + * must exist in each local LCP instance as well, then it will be needed to access the + * correct region. + *

+ * + * @param tenantName The tenant name to create + * @param cloudId The cloud identifier (may be a region) in which to create the tenant. + * @return the tenant ID of the newly created tenant + * @throws MsoTenantAlreadyExists Thrown if the requested tenant already exists + * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception + */ + public String createTenant (String tenantName, + String cloudSiteId, + Map metadata, + boolean backout) throws MsoException { + // Obtain the cloud site information where we will create the tenant + CloudSite cloudSite = cloudConfig.getCloudSite (cloudSiteId); + if (cloudSite == null) { + LOGGER.error(MessageEnum.RA_CREATE_TENANT_ERR, "MSOCloudSite not found", "", "", MsoLogger.ErrorCode.DataError, "MSOCloudSite not found"); + throw new MsoCloudSiteNotFound (cloudSiteId); + } + Keystone keystoneAdminClient = getKeystoneAdminClient (cloudSite); + + Tenant tenant = null; + try { + // Check if the tenant already exists + tenant = findTenantByName (keystoneAdminClient, tenantName); + + if (tenant != null) { + // Tenant already exists. Throw an exception + LOGGER.error(MessageEnum.RA_TENANT_ALREADY_EXIST, tenantName, cloudSiteId, "", "", MsoLogger.ErrorCode.DataError, "Tenant already exists"); + throw new MsoTenantAlreadyExists (tenantName, cloudSiteId); + } + + // Does not exist, create a new one + tenant = new Tenant (); + tenant.setName (tenantName); + tenant.setDescription ("SDN Tenant (via MSO)"); + tenant.setEnabled (true); + + OpenStackRequest request = keystoneAdminClient.tenants ().create (tenant); + tenant = executeAndRecordOpenstackRequest (request, msoProps); + } catch (OpenStackBaseException e) { + // Convert Keystone OpenStackResponseException to MsoOpenstackException + throw keystoneErrorToMsoException (e, "CreateTenant"); + } catch (RuntimeException e) { + // Catch-all + throw runtimeExceptionToMsoException (e, "CreateTenant"); + } + + // Add MSO User to the tenant as a member and + // apply tenant metadata if supported by the cloud site + try { + CloudIdentity cloudIdentity = cloudSite.getIdentityService (); + + User msoUser = findUserByNameOrId (keystoneAdminClient, cloudIdentity.getMsoId ()); + Role memberRole = findRoleByNameOrId (keystoneAdminClient, cloudIdentity.getMemberRole ()); + + OpenStackRequest request = keystoneAdminClient.tenants ().addUser (tenant.getId (), + msoUser.getId (), + memberRole.getId ()); + executeAndRecordOpenstackRequest (request, msoProps); + + if (cloudIdentity.hasTenantMetadata () && metadata != null && !metadata.isEmpty ()) { + Metadata tenantMetadata = new Metadata (); + tenantMetadata.setMetadata (metadata); + + OpenStackRequest metaRequest = keystoneAdminClient.tenants () + .createOrUpdateMetadata (tenant.getId (), + tenantMetadata); + executeAndRecordOpenstackRequest (metaRequest, msoProps); + } + } catch (Exception e) { + // Failed to attach MSO User to the new tenant. Can't operate without access, + // so roll back the tenant. + if (!backout) + { + LOGGER.warn(MessageEnum.RA_CREATE_TENANT_ERR, "Create Tenant errored, Tenant deletion suppressed", "Openstack", "", MsoLogger.ErrorCode.DataError, "Create Tenant error, Tenant deletion suppressed"); + } + else + { + try { + OpenStackRequest request = keystoneAdminClient.tenants ().delete (tenant.getId ()); + executeAndRecordOpenstackRequest (request, msoProps); + } catch (Exception e2) { + // Just log this one. We will report the original exception. + LOGGER.error (MessageEnum.RA_CREATE_TENANT_ERR, "Nested exception rolling back tenant", "Openstack", "", MsoLogger.ErrorCode.DataError, "Create Tenant error, Nested exception rolling back tenant", e2); + } + } + + + // Propagate the original exception on user/role/tenant mapping + if (e instanceof OpenStackBaseException) { + // Convert Keystone Exception to MsoOpenstackException + throw keystoneErrorToMsoException ((OpenStackBaseException) e, "CreateTenantUser"); + } else { + MsoAdapterException me = new MsoAdapterException (e.getMessage (), e); + me.addContext ("CreateTenantUser"); + throw me; + } + } + return tenant.getId (); + } + + /** + * Query for a tenant by ID in the given cloud. If the tenant exists, + * return an MsoTenant object. If not, return null. + *

+ * For the AIC Cloud (DCP/LCP): it is not clear that cloudId is needed, as all admin + * requests go to the centralized identity service in DCP. However, if some artifact + * must exist in each local LCP instance as well, then it will be needed to access the + * correct region. + *

+ * + * @param tenantId The Openstack ID of the tenant to query + * @param cloudSiteId The cloud identifier (may be a region) in which to query the tenant. + * @return the tenant properties of the queried tenant, or null if not found + * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception + */ + public MsoTenant queryTenant (String tenantId, String cloudSiteId) throws MsoException, MsoCloudSiteNotFound { + // Obtain the cloud site information where we will query the tenant + CloudSite cloudSite = cloudConfig.getCloudSite (cloudSiteId); + if (cloudSite == null) { + throw new MsoCloudSiteNotFound (cloudSiteId); + } + + Keystone keystoneAdminClient = getKeystoneAdminClient (cloudSite); + + // Check if the tenant exists and return its Tenant Id + try { + Tenant tenant = findTenantById (keystoneAdminClient, tenantId); + if (tenant == null) { + return null; + } + + Map metadata = new HashMap (); + if (cloudSite.getIdentityService ().hasTenantMetadata ()) { + OpenStackRequest request = keystoneAdminClient.tenants ().showMetadata (tenant.getId ()); + Metadata tenantMetadata = executeAndRecordOpenstackRequest (request, msoProps); + if (tenantMetadata != null) { + metadata = tenantMetadata.getMetadata (); + } + } + return new MsoTenant (tenant.getId (), tenant.getName (), metadata); + } catch (OpenStackBaseException e) { + // Convert Keystone OpenStackResponseException to MsoOpenstackException + throw keystoneErrorToMsoException (e, "QueryTenant"); + } catch (RuntimeException e) { + // Catch-all + throw runtimeExceptionToMsoException (e, "QueryTenant"); + } + } + + /** + * Query for a tenant with the specified name in the given cloud. If the tenant exists, + * return an MsoTenant object. If not, return null. This query is useful if the client + * knows it has the tenant name, skipping an initial lookup by ID that would always fail. + *

+ * For the AIC Cloud (DCP/LCP): it is not clear that cloudId is needed, as all admin + * requests go to the centralized identity service in DCP. However, if some artifact + * must exist in each local LCP instance as well, then it will be needed to access the + * correct region. + *

+ * + * @param tenantName The name of the tenant to query + * @param cloudSiteId The cloud identifier (may be a region) in which to query the tenant. + * @return the tenant properties of the queried tenant, or null if not found + * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception + */ + public MsoTenant queryTenantByName (String tenantName, String cloudSiteId) throws MsoException { + // Obtain the cloud site information where we will query the tenant + CloudSite cloudSite = cloudConfig.getCloudSite (cloudSiteId); + if (cloudSite == null) { + throw new MsoCloudSiteNotFound (cloudSiteId); + } + Keystone keystoneAdminClient = getKeystoneAdminClient (cloudSite); + + try { + Tenant tenant = findTenantByName (keystoneAdminClient, tenantName); + if (tenant == null) { + return null; + } + + Map metadata = new HashMap (); + if (cloudSite.getIdentityService ().hasTenantMetadata ()) { + OpenStackRequest request = keystoneAdminClient.tenants ().showMetadata (tenant.getId ()); + Metadata tenantMetadata = executeAndRecordOpenstackRequest (request, msoProps); + if (tenantMetadata != null) { + metadata = tenantMetadata.getMetadata (); + } + } + return new MsoTenant (tenant.getId (), tenant.getName (), metadata); + } catch (OpenStackBaseException e) { + // Convert Keystone OpenStackResponseException to MsoOpenstackException + throw keystoneErrorToMsoException (e, "QueryTenantName"); + } catch (RuntimeException e) { + // Catch-all + throw runtimeExceptionToMsoException (e, "QueryTenantName"); + } + } + + /** + * Delete the specified Tenant (by ID) in the given cloud. This method returns true or + * false, depending on whether the tenant existed and was successfully deleted, or if + * the tenant already did not exist. Both cases are treated as success (no Exceptions). + *

+ * Note for the AIC Cloud (DCP/LCP): all admin requests go to the centralized identity + * service in DCP. So deleting a tenant from one cloudSiteId will remove it from all + * sites managed by that identity service. + *

+ * + * @param tenantId The Openstack ID of the tenant to delete + * @param cloudSiteId The cloud identifier from which to delete the tenant. + * @return true if the tenant was deleted, false if the tenant did not exist. + * @throws MsoOpenstackException If the Openstack API call returns an exception. + */ + public boolean deleteTenant (String tenantId, String cloudSiteId) throws MsoException { + // Obtain the cloud site information where we will query the tenant + CloudSite cloudSite = cloudConfig.getCloudSite (cloudSiteId); + if (cloudSite == null) { + throw new MsoCloudSiteNotFound (cloudSiteId); + } + Keystone keystoneAdminClient = getKeystoneAdminClient (cloudSite); + + try { + // Check that the tenant exists. Also, need the ID to delete + Tenant tenant = findTenantById (keystoneAdminClient, tenantId); + if (tenant == null) { + LOGGER.error(MessageEnum.RA_TENANT_NOT_FOUND, tenantId, cloudSiteId, "", "", MsoLogger.ErrorCode.DataError, "Tenant not found"); + return false; + } + + OpenStackRequest request = keystoneAdminClient.tenants ().delete (tenant.getId ()); + executeAndRecordOpenstackRequest (request, msoProps); + LOGGER.debug ("Deleted Tenant " + tenant.getId () + " (" + tenant.getName () + ")"); + + // Clear any cached clients. Not really needed, ID will not be reused. + MsoHeatUtils.expireHeatClient (tenant.getId (), cloudSiteId); + MsoNeutronUtils.expireNeutronClient (tenant.getId (), cloudSiteId); + } catch (OpenStackBaseException e) { + // Convert Keystone OpenStackResponseException to MsoOpenstackException + throw keystoneErrorToMsoException (e, "Delete Tenant"); + } catch (RuntimeException e) { + // Catch-all + throw runtimeExceptionToMsoException (e, "DeleteTenant"); + } + + return true; + } + + /** + * Delete the specified Tenant (by Name) in the given cloud. This method returns true or + * false, depending on whether the tenant existed and was successfully deleted, or if + * the tenant already did not exist. Both cases are treated as success (no Exceptions). + *

+ * Note for the AIC Cloud (DCP/LCP): all admin requests go to the centralized identity + * service in DCP. So deleting a tenant from one cloudSiteId will remove it from all + * sites managed by that identity service. + *

+ * + * @param tenantName The name of the tenant to delete + * @param cloudSiteId The cloud identifier from which to delete the tenant. + * @return true if the tenant was deleted, false if the tenant did not exist. + * @throws MsoOpenstackException If the Openstack API call returns an exception. + */ + public boolean deleteTenantByName (String tenantName, String cloudSiteId) throws MsoException { + // Obtain the cloud site information where we will query the tenant + CloudSite cloudSite = cloudConfig.getCloudSite (cloudSiteId); + if (cloudSite == null) { + throw new MsoCloudSiteNotFound (cloudSiteId); + } + Keystone keystoneAdminClient = getKeystoneAdminClient (cloudSite); + + try { + // Need the Tenant ID to delete (can't directly delete by name) + Tenant tenant = findTenantByName (keystoneAdminClient, tenantName); + if (tenant == null) { + // OK if tenant already doesn't exist. + LOGGER.error(MessageEnum.RA_TENANT_NOT_FOUND, tenantName, cloudSiteId, "", "", MsoLogger.ErrorCode.DataError, "Tenant not found"); + return false; + } + + // Execute the Delete. It has no return value. + OpenStackRequest request = keystoneAdminClient.tenants ().delete (tenant.getId ()); + executeAndRecordOpenstackRequest (request, msoProps); + + LOGGER.debug ("Deleted Tenant " + tenant.getId () + " (" + tenant.getName () + ")"); + + // Clear any cached clients. Not really needed, ID will not be reused. + MsoHeatUtils.expireHeatClient (tenant.getId (), cloudSiteId); + MsoNeutronUtils.expireNeutronClient (tenant.getId (), cloudSiteId); + } catch (OpenStackBaseException e) { + // Note: It doesn't seem to matter if tenant doesn't exist, no exception is thrown. + // Convert Keystone OpenStackResponseException to MsoOpenstackException + throw keystoneErrorToMsoException (e, "DeleteTenant"); + } catch (RuntimeException e) { + // Catch-all + throw runtimeExceptionToMsoException (e, "DeleteTenant"); + } + + return true; + } + + // ------------------------------------------------------------------- + // PRIVATE UTILITY FUNCTIONS FOR USE WITHIN THIS CLASS + + /* + * Get a Keystone Admin client for the Openstack Identity service. + * This requires an 'admin'-level userId + password along with an 'admin' tenant + * in the target cloud. These values will be retrieved from properties based + * on the specified cloud ID. + *

+ * On successful authentication, the Keystone object will be cached for the cloudId + * so that it can be reused without going back to Openstack every time. + * + * @param cloudId + * + * @return an authenticated Keystone object + */ + public Keystone getKeystoneAdminClient (CloudSite cloudSite) throws MsoException { + CloudIdentity cloudIdentity = cloudSite.getIdentityService (); + + String cloudId = cloudIdentity.getId (); + String adminTenantName = cloudIdentity.getAdminTenant (); + String region = cloudSite.getRegionId (); + + // Check first in the cache of previously authorized clients + KeystoneCacheEntry entry = adminClientCache.get (cloudId); + if (entry != null) { + if (!entry.isExpired ()) { + return entry.getKeystoneClient (); + } else { + // Token is expired. Remove it from cache. + adminClientCache.remove (cloudId); + } + } + + Keystone keystone = new Keystone (cloudIdentity.getKeystoneUrl (region, msoPropID)); + + // Must authenticate against the 'admin' tenant to get the services endpoints + Access access = null; + String token = null; + try { + Authentication credentials = cloudIdentity.getAuthentication (); + OpenStackRequest request = keystone.tokens () + .authenticate (credentials) + .withTenantName (adminTenantName); + access = executeAndRecordOpenstackRequest (request, msoProps); + token = access.getToken ().getId (); + } catch (OpenStackResponseException e) { + if (e.getStatus () == 401) { + // Authentication error. Can't access admin tenant - something is mis-configured + String error = "MSO Authentication Failed for " + cloudIdentity.getId (); + alarmLogger.sendAlarm ("MsoAuthenticationError", MsoAlarmLogger.CRITICAL, error); + throw new MsoAdapterException (error); + } else { + throw keystoneErrorToMsoException (e, "TokenAuth"); + } + } catch (OpenStackConnectException e) { + // Connection to Openstack failed + throw keystoneErrorToMsoException (e, "TokenAuth"); + } + + // Get the Identity service URL. Throws runtime exception if not found per region. + String adminUrl = null; + try { + adminUrl = KeystoneUtils.findEndpointURL (access.getServiceCatalog (), "identity", region, "admin"); + } catch (RuntimeException e) { + String error = "Identity service not found: region=" + region + ",cloud=" + cloudIdentity.getId (); + alarmLogger.sendAlarm ("MsoConfigurationError", MsoAlarmLogger.CRITICAL, error); + LOGGER.error(MessageEnum.IDENTITY_SERVICE_NOT_FOUND, region, cloudIdentity.getId(), "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception in findEndpointURL"); + throw new MsoAdapterException (error, e); + } + // The following is needed for the MT lab. + if ("MT".equals (cloudSite.getId ())) { + adminUrl = adminUrl.replace ("controller", "mtdnj02bh01wt.bvoip.labs.att.com"); + } + + // A new Keystone object is required for the new URL. Use the auth token from above. + // Note: this doesn't go back to Openstack, it's just a local object. + keystone = new Keystone (adminUrl); + keystone.token (token); + + // Cache to avoid re-authentication for every call. + KeystoneCacheEntry cacheEntry = new KeystoneCacheEntry (adminUrl, token, access.getToken ().getExpires ()); + adminClientCache.put (cloudId, cacheEntry); + + return keystone; + } + + /* + * Find a tenant (or query its existance) by its Name or Id. Check first against the + * ID. If that fails, then try by name. + * + * @param adminClient an authenticated Keystone object + * + * @param tenantName the tenant name or ID to query + * + * @return a Tenant object or null if not found + */ + public Tenant findTenantByNameOrId (Keystone adminClient, String tenantNameOrId) { + if (tenantNameOrId == null) { + return null; + } + + Tenant tenant = findTenantById (adminClient, tenantNameOrId); + if (tenant == null) { + tenant = findTenantByName (adminClient, tenantNameOrId); + } + + return tenant; + } + + /* + * Find a tenant (or query its existance) by its Id. + * + * @param adminClient an authenticated Keystone object + * + * @param tenantName the tenant ID to query + * + * @return a Tenant object or null if not found + */ + private Tenant findTenantById (Keystone adminClient, String tenantId) { + if (tenantId == null) { + return null; + } + + try { + OpenStackRequest request = adminClient.tenants ().show (tenantId); + return executeAndRecordOpenstackRequest (request, msoProps); + } catch (OpenStackResponseException e) { + if (e.getStatus () == 404) { + return null; + } else { + LOGGER.error(MessageEnum.RA_CONNECTION_EXCEPTION, "Openstack Error, GET Tenant by Id (" + tenantId + "): " + e, "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception in Openstack GET tenant by Id"); + throw e; + } + } + } + + /* + * Find a tenant (or query its existance) by its Name. This method avoids an + * initial lookup by ID when it's known that we have the tenant Name. + * + * @param adminClient an authenticated Keystone object + * + * @param tenantName the tenant name to query + * + * @return a Tenant object or null if not found + */ + public Tenant findTenantByName (Keystone adminClient, String tenantName) { + if (tenantName == null) { + return null; + } + + try { + OpenStackRequest request = adminClient.tenants ().show ("").queryParam ("name", tenantName); + return executeAndRecordOpenstackRequest (request, msoProps); + } catch (OpenStackResponseException e) { + if (e.getStatus () == 404) { + return null; + } else { + LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "Openstack Error, GET Tenant By Name (" + tenantName + "): " + e, "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception in Openstack GET Tenant By Name"); + throw e; + } + } + } + + /* + * Look up an Openstack User by Name or Openstack ID. Check the ID first, and if that + * fails, try the Name. + * + * @param adminClient an authenticated Keystone object + * + * @param userName the user name or ID to query + * + * @return a User object or null if not found + */ + private User findUserByNameOrId (Keystone adminClient, String userNameOrId) { + if (userNameOrId == null) { + return null; + } + + try { + OpenStackRequest request = adminClient.users ().show (userNameOrId); + return executeAndRecordOpenstackRequest (request, msoProps); + } catch (OpenStackResponseException e) { + if (e.getStatus () == 404) { + // Not found by ID. Search for name + return findUserByName (adminClient, userNameOrId); + } else { + LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "Openstack Error, GET User (" + userNameOrId + "): " + e, "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception in Openstack GET User"); + throw e; + } + } + } + + /* + * Look up an Openstack User by Name. This avoids initial Openstack query by ID + * if we know we have the User Name. + * + * @param adminClient an authenticated Keystone object + * + * @param userName the user name to query + * + * @return a User object or null if not found + */ + public User findUserByName (Keystone adminClient, String userName) { + if (userName == null) { + return null; + } + + try { + OpenStackRequest request = adminClient.users ().show ("").queryParam ("name", userName); + return executeAndRecordOpenstackRequest (request, msoProps); + } catch (OpenStackResponseException e) { + if (e.getStatus () == 404) { + return null; + } else { + LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "Openstack Error, GET User By Name (" + userName + "): " + e, "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception in Openstack GET User By Name"); + throw e; + } + } + } + + /* + * Look up an Openstack Role by Name or Id. There is no direct query for Roles, so + * need to retrieve a full list from Openstack and look for a match. By default, + * Openstack should have a "_member_" role for normal VM-level privileges and an + * "admin" role for expanded privileges (e.g. administer tenants, users, and roles). + *

+ * + * @param adminClient an authenticated Keystone object + * + * @param roleNameOrId the Role name or ID to look up + * + * @return a Role object + */ + private Role findRoleByNameOrId (Keystone adminClient, String roleNameOrId) { + if (roleNameOrId == null) { + return null; + } + + // Search by name or ID. Must search in list + OpenStackRequest request = adminClient.roles ().list (); + Roles roles = executeAndRecordOpenstackRequest (request, msoProps); + + for (Role role : roles) { + if (roleNameOrId.equals (role.getName ()) || roleNameOrId.equals (role.getId ())) { + return role; + } + } + + return null; + } + + private static class KeystoneCacheEntry implements Serializable { + + private static final long serialVersionUID = 1L; + + private String keystoneUrl; + private String token; + private Calendar expires; + + public KeystoneCacheEntry (String url, String token, Calendar expires) { + this.keystoneUrl = url; + this.token = token; + this.expires = expires; + } + + public Keystone getKeystoneClient () { + Keystone keystone = new Keystone (keystoneUrl); + keystone.token (token); + return keystone; + } + + public boolean isExpired () { + return Calendar.getInstance ().after (expires); + } + } + + /** + * Clean up the Admin client cache to remove expired entries. + */ + public static void adminCacheCleanup () { + for (String cacheKey : adminClientCache.keySet ()) { + if (adminClientCache.get (cacheKey).isExpired ()) { + adminClientCache.remove (cacheKey); + LOGGER.debug ("Cleaned Up Cached Admin Client for " + cacheKey); + } + } + } + + /** + * Reset the Admin client cache. + * This may be useful if cached credentials get out of sync. + */ + public static void adminCacheReset () { + adminClientCache = new HashMap (); + } +} -- cgit 1.2.3-korg