Merge "Reorder modifiers"
[so.git] / adapters / mso-adapter-utils / src / main / java / org / openecomp / mso / openstack / utils / MsoKeystoneUtils.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - SO
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.openecomp.mso.openstack.utils;
22
23
24 import java.io.Serializable;
25 import java.util.Calendar;
26 import java.util.HashMap;
27 import java.util.Map;
28
29 import java.util.Optional;
30
31 import org.openecomp.mso.cloud.CloudConfigFactory;
32 import org.openecomp.mso.cloud.CloudIdentity;
33 import org.openecomp.mso.cloud.CloudSite;
34 import org.openecomp.mso.logger.MsoAlarmLogger;
35 import org.openecomp.mso.logger.MsoLogger;
36 import org.openecomp.mso.logger.MessageEnum;
37 import org.openecomp.mso.openstack.beans.MsoTenant;
38 import org.openecomp.mso.openstack.exceptions.MsoAdapterException;
39 import org.openecomp.mso.openstack.exceptions.MsoCloudSiteNotFound;
40 import org.openecomp.mso.openstack.exceptions.MsoException;
41 import org.openecomp.mso.openstack.exceptions.MsoOpenstackException;
42 import org.openecomp.mso.openstack.exceptions.MsoTenantAlreadyExists;
43 import com.woorea.openstack.base.client.OpenStackBaseException;
44 import com.woorea.openstack.base.client.OpenStackConnectException;
45 import com.woorea.openstack.base.client.OpenStackRequest;
46 import com.woorea.openstack.base.client.OpenStackResponseException;
47 import com.woorea.openstack.keystone.Keystone;
48 import com.woorea.openstack.keystone.model.Access;
49 import com.woorea.openstack.keystone.model.Metadata;
50 import com.woorea.openstack.keystone.model.Role;
51 import com.woorea.openstack.keystone.model.Roles;
52 import com.woorea.openstack.keystone.model.Tenant;
53 import com.woorea.openstack.keystone.model.User;
54 import com.woorea.openstack.keystone.utils.KeystoneUtils;
55 import com.woorea.openstack.keystone.model.Authentication;
56
57 public class MsoKeystoneUtils extends MsoTenantUtils {
58
59     // Cache the Keystone Clients statically. Since there is just one MSO user, there is no
60     // benefit to re-authentication on every request (or across different flows). The
61     // token will be used until it expires.
62     //
63     // The cache key is "cloudId"
64     private static Map <String, KeystoneCacheEntry> adminClientCache = new HashMap<>();
65
66         private static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA);
67         String msoPropID;
68         
69     public MsoKeystoneUtils(String msoPropID, CloudConfigFactory cloudConfigFactory) {
70                 super(msoPropID, cloudConfigFactory);
71                 this.msoPropID = msoPropID;
72                 LOGGER.debug("MsoKeyStoneUtils:" + msoPropID);
73         }
74
75     /**
76      * Create a tenant with the specified name in the given cloud. If the tenant already exists,
77      * an Exception will be thrown. The MSO User will also be added to the "member" list of
78      * the new tenant to perform subsequent Nova/Heat commands in the tenant. If the MSO User
79      * association fails, the entire transaction will be rolled back.
80      * <p>
81      * For the AIC Cloud (DCP/LCP): it is not clear that cloudId is needed, as all admin
82      * requests go to the centralized identity service in DCP. However, if some artifact
83      * must exist in each local LCP instance as well, then it will be needed to access the
84      * correct region.
85      * <p>
86      *
87      * @param tenantName The tenant name to create
88      * @param cloudSiteId The cloud identifier (may be a region) in which to create the tenant.
89      * @return the tenant ID of the newly created tenant
90      * @throws MsoTenantAlreadyExists Thrown if the requested tenant already exists
91      * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
92      */
93     public String createTenant (String tenantName,
94                                 String cloudSiteId,
95                                 Map <String, String> metadata,
96                                 boolean backout) throws MsoException {
97         // Obtain the cloud site information where we will create the tenant
98         Optional<CloudSite> cloudSiteOpt = getCloudConfigFactory().getCloudConfig().getCloudSite(cloudSiteId);
99         if (!cloudSiteOpt.isPresent()) {
100                 LOGGER.error(MessageEnum.RA_CREATE_TENANT_ERR, "MSOCloudSite not found", "", "", MsoLogger.ErrorCode.DataError, "MSOCloudSite not found");
101             throw new MsoCloudSiteNotFound (cloudSiteId);
102         }
103         Keystone keystoneAdminClient = getKeystoneAdminClient(cloudSiteOpt.get());
104         Tenant tenant = null;
105         try {
106             // Check if the tenant already exists
107             tenant = findTenantByName (keystoneAdminClient, tenantName);
108
109             if (tenant != null) {
110                 // Tenant already exists. Throw an exception
111                 LOGGER.error(MessageEnum.RA_TENANT_ALREADY_EXIST, tenantName, cloudSiteId, "", "", MsoLogger.ErrorCode.DataError, "Tenant already exists");
112                 throw new MsoTenantAlreadyExists (tenantName, cloudSiteId);
113             }
114
115             // Does not exist, create a new one
116             tenant = new Tenant ();
117             tenant.setName (tenantName);
118             tenant.setDescription ("SDN Tenant (via MSO)");
119             tenant.setEnabled (true);
120
121             OpenStackRequest <Tenant> request = keystoneAdminClient.tenants ().create (tenant);
122             tenant = executeAndRecordOpenstackRequest (request, msoProps);
123         } catch (OpenStackBaseException e) {
124             // Convert Keystone OpenStackResponseException to MsoOpenstackException
125             throw keystoneErrorToMsoException (e, "CreateTenant");
126         } catch (RuntimeException e) {
127             // Catch-all
128             throw runtimeExceptionToMsoException (e, "CreateTenant");
129         }
130
131         // Add MSO User to the tenant as a member and
132         // apply tenant metadata if supported by the cloud site
133         try {
134             CloudIdentity cloudIdentity = cloudSiteOpt.get().getIdentityService ();
135
136             User msoUser = findUserByNameOrId (keystoneAdminClient, cloudIdentity.getMsoId ());
137             Role memberRole = findRoleByNameOrId (keystoneAdminClient, cloudIdentity.getMemberRole ());
138
139             OpenStackRequest <Void> request = keystoneAdminClient.tenants ().addUser (tenant.getId (),
140                                                                                       msoUser.getId (),
141                                                                                       memberRole.getId ());
142             executeAndRecordOpenstackRequest (request, msoProps);
143
144             if (cloudIdentity.hasTenantMetadata () && metadata != null && !metadata.isEmpty ()) {
145                 Metadata tenantMetadata = new Metadata ();
146                 tenantMetadata.setMetadata (metadata);
147
148                 OpenStackRequest <Metadata> metaRequest = keystoneAdminClient.tenants ()
149                                                                              .createOrUpdateMetadata (tenant.getId (),
150                                                                                                       tenantMetadata);
151                 executeAndRecordOpenstackRequest (metaRequest, msoProps);
152             }
153         } catch (Exception e) {
154             // Failed to attach MSO User to the new tenant. Can't operate without access,
155             // so roll back the tenant.
156                 if (!backout)
157                 {
158                         LOGGER.warn(MessageEnum.RA_CREATE_TENANT_ERR, "Create Tenant errored, Tenant deletion suppressed", "Openstack", "", MsoLogger.ErrorCode.DataError, "Create Tenant error, Tenant deletion suppressed");
159                 }
160                 else
161                 {
162                         try {
163                                 OpenStackRequest <Void> request = keystoneAdminClient.tenants ().delete (tenant.getId ());
164                                 executeAndRecordOpenstackRequest (request, msoProps);
165                         } catch (Exception e2) {
166                                 // Just log this one. We will report the original exception.
167                                 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);
168                         }
169                 }
170                 
171
172             // Propagate the original exception on user/role/tenant mapping
173             if (e instanceof OpenStackBaseException) {
174                 // Convert Keystone Exception to MsoOpenstackException
175                 throw keystoneErrorToMsoException ((OpenStackBaseException) e, "CreateTenantUser");
176             } else {
177                 MsoAdapterException me = new MsoAdapterException (e.getMessage (), e);
178                 me.addContext ("CreateTenantUser");
179                 throw me;
180             }
181         }
182         return tenant.getId ();
183     }
184
185     /**
186      * Query for a tenant by ID in the given cloud. If the tenant exists,
187      * return an MsoTenant object. If not, return null.
188      * <p>
189      * For the AIC Cloud (DCP/LCP): it is not clear that cloudId is needed, as all admin
190      * requests go to the centralized identity service in DCP. However, if some artifact
191      * must exist in each local LCP instance as well, then it will be needed to access the
192      * correct region.
193      * <p>
194      *
195      * @param tenantId The Openstack ID of the tenant to query
196      * @param cloudSiteId The cloud identifier (may be a region) in which to query the tenant.
197      * @return the tenant properties of the queried tenant, or null if not found
198      * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
199      */
200     public MsoTenant queryTenant (String tenantId, String cloudSiteId) throws MsoException {
201         // Obtain the cloud site information where we will query the tenant
202         CloudSite cloudSite = getCloudConfigFactory().getCloudConfig().getCloudSite(cloudSiteId).orElseThrow(
203                 () -> new MsoCloudSiteNotFound(cloudSiteId));
204
205         Keystone keystoneAdminClient = getKeystoneAdminClient (cloudSite);
206
207         // Check if the tenant exists and return its Tenant Id
208         try {
209             Tenant tenant = findTenantById (keystoneAdminClient, tenantId);
210             if (tenant == null) {
211                 return null;
212             }
213
214             Map <String, String> metadata = new HashMap<>();
215             if (cloudSite.getIdentityService ().hasTenantMetadata ()) {
216                 OpenStackRequest <Metadata> request = keystoneAdminClient.tenants ().showMetadata (tenant.getId ());
217                 Metadata tenantMetadata = executeAndRecordOpenstackRequest (request, msoProps);
218                 if (tenantMetadata != null) {
219                     metadata = tenantMetadata.getMetadata ();
220                 }
221             }
222             return new MsoTenant (tenant.getId (), tenant.getName (), metadata);
223         } catch (OpenStackBaseException e) {
224             // Convert Keystone OpenStackResponseException to MsoOpenstackException
225             throw keystoneErrorToMsoException (e, "QueryTenant");
226         } catch (RuntimeException e) {
227             // Catch-all
228             throw runtimeExceptionToMsoException (e, "QueryTenant");
229         }
230     }
231
232     /**
233      * Query for a tenant with the specified name in the given cloud. If the tenant exists,
234      * return an MsoTenant object. If not, return null. This query is useful if the client
235      * knows it has the tenant name, skipping an initial lookup by ID that would always fail.
236      * <p>
237      * For the AIC Cloud (DCP/LCP): it is not clear that cloudId is needed, as all admin
238      * requests go to the centralized identity service in DCP. However, if some artifact
239      * must exist in each local LCP instance as well, then it will be needed to access the
240      * correct region.
241      * <p>
242      *
243      * @param tenantName The name of the tenant to query
244      * @param cloudSiteId The cloud identifier (may be a region) in which to query the tenant.
245      * @return the tenant properties of the queried tenant, or null if not found
246      * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
247      */
248     public MsoTenant queryTenantByName (String tenantName, String cloudSiteId) throws MsoException {
249         // Obtain the cloud site information where we will query the tenant
250         CloudSite cloudSite = getCloudConfigFactory().getCloudConfig().getCloudSite(cloudSiteId).orElseThrow(
251                 () -> new MsoCloudSiteNotFound(cloudSiteId));
252         Keystone keystoneAdminClient = getKeystoneAdminClient (cloudSite);
253
254         try {
255             Tenant tenant = findTenantByName (keystoneAdminClient, tenantName);
256             if (tenant == null) {
257                 return null;
258             }
259
260             Map <String, String> metadata = new HashMap<>();
261             if (cloudSite.getIdentityService ().hasTenantMetadata ()) {
262                 OpenStackRequest <Metadata> request = keystoneAdminClient.tenants ().showMetadata (tenant.getId ());
263                 Metadata tenantMetadata = executeAndRecordOpenstackRequest (request, msoProps);
264                 if (tenantMetadata != null) {
265                     metadata = tenantMetadata.getMetadata ();
266                 }
267             }
268             return new MsoTenant (tenant.getId (), tenant.getName (), metadata);
269         } catch (OpenStackBaseException e) {
270             // Convert Keystone OpenStackResponseException to MsoOpenstackException
271             throw keystoneErrorToMsoException (e, "QueryTenantName");
272         } catch (RuntimeException e) {
273             // Catch-all
274             throw runtimeExceptionToMsoException (e, "QueryTenantName");
275         }
276     }
277
278     /**
279      * Delete the specified Tenant (by ID) in the given cloud. This method returns true or
280      * false, depending on whether the tenant existed and was successfully deleted, or if
281      * the tenant already did not exist. Both cases are treated as success (no Exceptions).
282      * <p>
283      * Note for the AIC Cloud (DCP/LCP): all admin requests go to the centralized identity
284      * service in DCP. So deleting a tenant from one cloudSiteId will remove it from all
285      * sites managed by that identity service.
286      * <p>
287      *
288      * @param tenantId The Openstack ID of the tenant to delete
289      * @param cloudSiteId The cloud identifier from which to delete the tenant.
290      * @return true if the tenant was deleted, false if the tenant did not exist.
291      * @throws MsoOpenstackException If the Openstack API call returns an exception.
292      */
293     public boolean deleteTenant (String tenantId, String cloudSiteId) throws MsoException {
294         // Obtain the cloud site information where we will query the tenant
295         CloudSite cloudSite = getCloudConfigFactory().getCloudConfig().getCloudSite(cloudSiteId).orElseThrow(
296                 () -> new MsoCloudSiteNotFound(cloudSiteId));
297         Keystone keystoneAdminClient = getKeystoneAdminClient (cloudSite);
298
299         try {
300             // Check that the tenant exists. Also, need the ID to delete
301             Tenant tenant = findTenantById (keystoneAdminClient, tenantId);
302             if (tenant == null) {
303                 LOGGER.error(MessageEnum.RA_TENANT_NOT_FOUND, tenantId, cloudSiteId, "", "", MsoLogger.ErrorCode.DataError, "Tenant not found");
304                 return false;
305             }
306
307             OpenStackRequest <Void> request = keystoneAdminClient.tenants ().delete (tenant.getId ());
308             executeAndRecordOpenstackRequest (request, msoProps);
309             LOGGER.debug ("Deleted Tenant " + tenant.getId () + " (" + tenant.getName () + ")");
310
311             // Clear any cached clients. Not really needed, ID will not be reused.
312             MsoHeatUtils.expireHeatClient (tenant.getId (), cloudSiteId);
313             MsoNeutronUtils.expireNeutronClient (tenant.getId (), cloudSiteId);
314         } catch (OpenStackBaseException e) {
315             // Convert Keystone OpenStackResponseException to MsoOpenstackException
316             throw keystoneErrorToMsoException (e, "Delete Tenant");
317         } catch (RuntimeException e) {
318             // Catch-all
319             throw runtimeExceptionToMsoException (e, "DeleteTenant");
320         }
321
322         return true;
323     }
324
325     // -------------------------------------------------------------------
326     // PRIVATE UTILITY FUNCTIONS FOR USE WITHIN THIS CLASS
327
328     /*
329      * Get a Keystone Admin client for the Openstack Identity service.
330      * This requires an 'admin'-level userId + password along with an 'admin' tenant
331      * in the target cloud. These values will be retrieved from properties based
332      * on the specified cloud ID.
333      * <p>
334      * On successful authentication, the Keystone object will be cached for the cloudId
335      * so that it can be reused without going back to Openstack every time.
336      *
337      * @param cloudId
338      *
339      * @return an authenticated Keystone object
340      */
341     public Keystone getKeystoneAdminClient (CloudSite cloudSite) throws MsoException {
342         CloudIdentity cloudIdentity = cloudSite.getIdentityService ();
343
344         String cloudId = cloudIdentity.getId ();
345         String adminTenantName = cloudIdentity.getAdminTenant ();
346         String region = cloudSite.getRegionId ();
347
348         // Check first in the cache of previously authorized clients
349         KeystoneCacheEntry entry = adminClientCache.get (cloudId);
350         if (entry != null) {
351             if (!entry.isExpired ()) {
352                 return entry.getKeystoneClient ();
353             } else {
354                 // Token is expired. Remove it from cache.
355                 adminClientCache.remove (cloudId);
356             }
357         }
358
359         Keystone keystone = new Keystone (cloudIdentity.getKeystoneUrl (region, msoPropID));
360
361         // Must authenticate against the 'admin' tenant to get the services endpoints
362         Access access = null;
363         String token = null;
364         try {
365                 Authentication credentials = cloudIdentity.getAuthentication ();
366             OpenStackRequest <Access> request = keystone.tokens ()
367                                                         .authenticate (credentials)
368                                                         .withTenantName (adminTenantName);
369             access = executeAndRecordOpenstackRequest (request, msoProps);
370             token = access.getToken ().getId ();
371         } catch (OpenStackResponseException e) {
372             if (e.getStatus () == 401) {
373                 // Authentication error. Can't access admin tenant - something is mis-configured
374                 String error = "MSO Authentication Failed for " + cloudIdentity.getId ();
375                 alarmLogger.sendAlarm ("MsoAuthenticationError", MsoAlarmLogger.CRITICAL, error);
376                 throw new MsoAdapterException (error);
377             } else {
378                 throw keystoneErrorToMsoException (e, "TokenAuth");
379             }
380         } catch (OpenStackConnectException e) {
381             // Connection to Openstack failed
382             throw keystoneErrorToMsoException (e, "TokenAuth");
383         }
384
385         // Get the Identity service URL. Throws runtime exception if not found per region.
386         String adminUrl = null;
387         try {
388                 // TODO:  FOR TESTING!!!!
389                 adminUrl = KeystoneUtils.findEndpointURL (access.getServiceCatalog (), "identity", region, "public");
390                 adminUrl = adminUrl.replaceFirst("5000", "35357");
391         } catch (RuntimeException e) {
392             String error = "Identity service not found: region=" + region + ",cloud=" + cloudIdentity.getId ();
393             alarmLogger.sendAlarm ("MsoConfigurationError", MsoAlarmLogger.CRITICAL, error);
394             LOGGER.error(MessageEnum.IDENTITY_SERVICE_NOT_FOUND, region, cloudIdentity.getId(), "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception in findEndpointURL");
395             throw new MsoAdapterException (error, e);
396         }
397
398         // A new Keystone object is required for the new URL. Use the auth token from above.
399         // Note: this doesn't go back to Openstack, it's just a local object.
400         keystone = new Keystone (adminUrl);
401         keystone.token (token);
402
403         // Cache to avoid re-authentication for every call.
404         KeystoneCacheEntry cacheEntry = new KeystoneCacheEntry (adminUrl, token, access.getToken ().getExpires ());
405         adminClientCache.put (cloudId, cacheEntry);
406
407         return keystone;
408     }
409
410     /*
411      * Find a tenant (or query its existance) by its Id.
412      *
413      * @param adminClient an authenticated Keystone object
414      *
415      * @param tenantName the tenant ID to query
416      *
417      * @return a Tenant object or null if not found
418      */
419     private Tenant findTenantById (Keystone adminClient, String tenantId) {
420         if (tenantId == null) {
421             return null;
422         }
423
424         try {
425             OpenStackRequest <Tenant> request = adminClient.tenants ().show (tenantId);
426             return executeAndRecordOpenstackRequest (request, msoProps);
427         } catch (OpenStackResponseException e) {
428             if (e.getStatus () == 404) {
429                 return null;
430             } else {
431                 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");
432                 throw e;
433             }
434         }
435     }
436
437     /*
438      * Find a tenant (or query its existance) by its Name. This method avoids an
439      * initial lookup by ID when it's known that we have the tenant Name.
440      *
441      * @param adminClient an authenticated Keystone object
442      *
443      * @param tenantName the tenant name to query
444      *
445      * @return a Tenant object or null if not found
446      */
447     public Tenant findTenantByName (Keystone adminClient, String tenantName) {
448         if (tenantName == null) {
449             return null;
450         }
451
452         try {
453             OpenStackRequest <Tenant> request = adminClient.tenants ().show ("").queryParam ("name", tenantName);
454             return executeAndRecordOpenstackRequest (request, msoProps);
455         } catch (OpenStackResponseException e) {
456             if (e.getStatus () == 404) {
457                 return null;
458             } else {
459                 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");
460                 throw e;
461             }
462         }
463     }
464
465     /*
466      * Look up an Openstack User by Name or Openstack ID. Check the ID first, and if that
467      * fails, try the Name.
468      *
469      * @param adminClient an authenticated Keystone object
470      *
471      * @param userName the user name or ID to query
472      *
473      * @return a User object or null if not found
474      */
475     private User findUserByNameOrId (Keystone adminClient, String userNameOrId) {
476         if (userNameOrId == null) {
477             return null;
478         }
479
480         try {
481             OpenStackRequest <User> request = adminClient.users ().show (userNameOrId);
482             return executeAndRecordOpenstackRequest (request, msoProps);
483         } catch (OpenStackResponseException e) {
484             if (e.getStatus () == 404) {
485                 // Not found by ID. Search for name
486                 return findUserByName (adminClient, userNameOrId);
487             } else {
488                 LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "Openstack Error, GET User (" + userNameOrId + "): " + e, "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception in Openstack GET User");
489                 throw e;
490             }
491         }
492     }
493
494     /*
495      * Look up an Openstack User by Name. This avoids initial Openstack query by ID
496      * if we know we have the User Name.
497      *
498      * @param adminClient an authenticated Keystone object
499      *
500      * @param userName the user name to query
501      *
502      * @return a User object or null if not found
503      */
504     public User findUserByName (Keystone adminClient, String userName) {
505         if (userName == null) {
506             return null;
507         }
508
509         try {
510             OpenStackRequest <User> request = adminClient.users ().show ("").queryParam ("name", userName);
511             return executeAndRecordOpenstackRequest (request, msoProps);
512         } catch (OpenStackResponseException e) {
513             if (e.getStatus () == 404) {
514                 return null;
515             } else {
516                 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");
517                 throw e;
518             }
519         }
520     }
521
522     /*
523      * Look up an Openstack Role by Name or Id. There is no direct query for Roles, so
524      * need to retrieve a full list from Openstack and look for a match. By default,
525      * Openstack should have a "_member_" role for normal VM-level privileges and an
526      * "admin" role for expanded privileges (e.g. administer tenants, users, and roles).
527      * <p>
528      *
529      * @param adminClient an authenticated Keystone object
530      *
531      * @param roleNameOrId the Role name or ID to look up
532      *
533      * @return a Role object
534      */
535     private  Role findRoleByNameOrId (Keystone adminClient, String roleNameOrId) {
536         if (roleNameOrId == null) {
537             return null;
538         }
539
540         // Search by name or ID. Must search in list
541         OpenStackRequest <Roles> request = adminClient.roles ().list ();
542         Roles roles = executeAndRecordOpenstackRequest (request, msoProps);
543
544         for (Role role : roles) {
545             if (roleNameOrId.equals (role.getName ()) || roleNameOrId.equals (role.getId ())) {
546                 return role;
547             }
548         }
549
550         return null;
551     }
552
553     private static class KeystoneCacheEntry implements Serializable {
554
555         private static final long serialVersionUID = 1L;
556
557         private String keystoneUrl;
558         private String token;
559         private Calendar expires;
560
561         public KeystoneCacheEntry (String url, String token, Calendar expires) {
562             this.keystoneUrl = url;
563             this.token = token;
564             this.expires = expires;
565         }
566
567         public Keystone getKeystoneClient () {
568             Keystone keystone = new Keystone (keystoneUrl);
569             keystone.token (token);
570             return keystone;
571         }
572
573         public boolean isExpired () {
574             // adding arbitrary guard timer of 5 minutes
575             return expires == null || System.currentTimeMillis() > (expires.getTimeInMillis() - 300000);
576
577         }
578     }
579
580     /**
581      * Clean up the Admin client cache to remove expired entries.
582      */
583     public static void adminCacheCleanup () {
584         for (String cacheKey : adminClientCache.keySet ()) {
585             if (adminClientCache.get (cacheKey).isExpired ()) {
586                 adminClientCache.remove (cacheKey);
587                 LOGGER.debug ("Cleaned Up Cached Admin Client for " + cacheKey);
588             }
589         }
590     }
591
592     /**
593      * Reset the Admin client cache.
594      * This may be useful if cached credentials get out of sync.
595      */
596     public static void adminCacheReset () {
597         adminClientCache = new HashMap<>();
598     }
599
600         @Override
601         public String getKeystoneUrl(String regionId, String msoPropID, CloudIdentity cloudIdentity) {
602                 return cloudIdentity.getIdentityUrl();
603         }
604 }