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