759c116e058b9081ba95cced80cb137ed7d9d660
[so.git] / adapters / mso-adapter-utils / src / main / java / org / onap / so / 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.onap.so.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 import java.util.Optional;
29
30 import org.onap.so.db.catalog.beans.CloudIdentity;
31 import org.onap.so.db.catalog.beans.CloudSite;
32 import org.onap.so.cloud.authentication.AuthenticationMethodFactory;
33 import org.onap.so.logger.MessageEnum;
34 import org.onap.so.logger.MsoAlarmLogger;
35 import org.onap.so.logger.MsoLogger;
36 import org.onap.so.openstack.beans.MsoTenant;
37 import org.onap.so.openstack.exceptions.MsoAdapterException;
38 import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
39 import org.onap.so.openstack.exceptions.MsoException;
40 import org.onap.so.openstack.exceptions.MsoOpenstackException;
41 import org.onap.so.openstack.exceptions.MsoTenantAlreadyExists;
42 import org.springframework.beans.factory.annotation.Autowired;
43 import org.springframework.stereotype.Component;
44
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.Authentication;
52 import com.woorea.openstack.keystone.model.Metadata;
53 import com.woorea.openstack.keystone.model.Role;
54 import com.woorea.openstack.keystone.model.Roles;
55 import com.woorea.openstack.keystone.model.Tenant;
56 import com.woorea.openstack.keystone.model.User;
57 import com.woorea.openstack.keystone.utils.KeystoneUtils;
58
59 @Component
60 public class MsoKeystoneUtils extends MsoTenantUtils {
61
62     // Cache the Keystone Clients statically. Since there is just one MSO user, there is no
63     // benefit to re-authentication on every request (or across different flows). The
64     // token will be used until it expires.
65     //
66     // The cache key is "cloudId"
67     private static Map <String, KeystoneCacheEntry> adminClientCache = new HashMap<>();
68
69         private static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA, MsoKeystoneUtils.class);
70         
71         @Autowired
72     private AuthenticationMethodFactory authenticationMethodFactory;
73         
74         @Autowired
75         private MsoHeatUtils msoHeatUtils;
76         
77         @Autowired
78         private MsoNeutronUtils msoNeutronUtils;
79         
80         @Autowired
81         private MsoTenantUtilsFactory tenantUtilsFactory;
82     /**
83      * Create a tenant with the specified name in the given cloud. If the tenant already exists,
84      * an Exception will be thrown. The MSO User will also be added to the "member" list of
85      * the new tenant to perform subsequent Nova/Heat commands in the tenant. If the MSO User
86      * association fails, the entire transaction will be rolled back.
87      * <p>
88      * For the AIC Cloud (DCP/LCP): it is not clear that cloudId is needed, as all admin
89      * requests go to the centralized identity service in DCP. However, if some artifact
90      * must exist in each local LCP instance as well, then it will be needed to access the
91      * correct region.
92      * <p>
93      *
94      * @param tenantName The tenant name to create
95      * @param cloudSiteId The cloud identifier (may be a region) in which to create the tenant.
96      * @return the tenant ID of the newly created tenant
97      * @throws MsoTenantAlreadyExists Thrown if the requested tenant already exists
98      * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
99      */
100     public String createTenant (String tenantName,
101                                 String cloudSiteId,
102                                 Map <String, String> metadata,
103                                 boolean backout) throws MsoException {
104         // Obtain the cloud site information where we will create the tenant
105         Optional<CloudSite> cloudSiteOpt = cloudConfig.getCloudSite(cloudSiteId);
106         if (!cloudSiteOpt.isPresent()) {
107                 LOGGER.error(MessageEnum.RA_CREATE_TENANT_ERR, "MSOCloudSite not found", "", "", MsoLogger.ErrorCode.DataError, "MSOCloudSite not found");
108             throw new MsoCloudSiteNotFound (cloudSiteId);
109         }
110         Keystone keystoneAdminClient = getKeystoneAdminClient(cloudSiteOpt.get());
111         Tenant tenant = null;
112         try {
113             // Check if the tenant already exists
114             tenant = findTenantByName (keystoneAdminClient, tenantName);
115
116             if (tenant != null) {
117                 // Tenant already exists. Throw an exception
118                 LOGGER.error(MessageEnum.RA_TENANT_ALREADY_EXIST, tenantName, cloudSiteId, "", "", MsoLogger.ErrorCode.DataError, "Tenant already exists");
119                 throw new MsoTenantAlreadyExists (tenantName, cloudSiteId);
120             }
121
122             // Does not exist, create a new one
123             tenant = new Tenant ();
124             tenant.setName (tenantName);
125             tenant.setDescription ("SDN Tenant (via MSO)");
126             tenant.setEnabled (true);
127
128             OpenStackRequest <Tenant> request = keystoneAdminClient.tenants ().create (tenant);
129             tenant = executeAndRecordOpenstackRequest (request);
130         } catch (OpenStackBaseException e) {
131             // Convert Keystone OpenStackResponseException to MsoOpenstackException
132             throw keystoneErrorToMsoException (e, "CreateTenant");
133         } catch (RuntimeException e) {
134             // Catch-all
135             throw runtimeExceptionToMsoException (e, "CreateTenant");
136         }
137
138         // Add MSO User to the tenant as a member and
139         // apply tenant metadata if supported by the cloud site
140         try {
141             CloudIdentity cloudIdentity = cloudSiteOpt.get().getIdentityService();
142
143             User msoUser = findUserByNameOrId (keystoneAdminClient, cloudIdentity.getMsoId ());
144             Role memberRole = findRoleByNameOrId (keystoneAdminClient, cloudIdentity.getMemberRole ());
145             
146             if(msoUser != null && memberRole != null) {
147                     OpenStackRequest <Void> request = keystoneAdminClient.tenants ().addUser (tenant.getId (),
148                                                                                               msoUser.getId (),
149                                                                                               memberRole.getId ());
150                     executeAndRecordOpenstackRequest (request);
151             }
152
153             if (cloudIdentity.getTenantMetadata () && metadata != null && !metadata.isEmpty ()) {
154                 Metadata tenantMetadata = new Metadata ();
155                 tenantMetadata.setMetadata (metadata);
156
157                 OpenStackRequest <Metadata> metaRequest = keystoneAdminClient.tenants ()
158                                                                              .createOrUpdateMetadata (tenant.getId (),
159                                                                                                       tenantMetadata);
160                 executeAndRecordOpenstackRequest (metaRequest);
161             }
162         } catch (Exception e) {
163             // Failed to attach MSO User to the new tenant. Can't operate without access,
164             // so roll back the tenant.
165                 if (!backout)
166                 {
167                         LOGGER.warn(MessageEnum.RA_CREATE_TENANT_ERR, "Create Tenant errored, Tenant deletion suppressed", "Openstack", "", MsoLogger.ErrorCode.DataError, "Create Tenant error, Tenant deletion suppressed");
168                 }
169                 else
170                 {
171                         try {
172                                 OpenStackRequest <Void> request = keystoneAdminClient.tenants ().delete (tenant.getId ());
173                                 executeAndRecordOpenstackRequest (request);
174                         } catch (Exception e2) {
175                                 // Just log this one. We will report the original exception.
176                                 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);
177                         }
178                 }
179                 
180
181             // Propagate the original exception on user/role/tenant mapping
182             if (e instanceof OpenStackBaseException) {
183                 // Convert Keystone Exception to MsoOpenstackException
184                 throw keystoneErrorToMsoException ((OpenStackBaseException) e, "CreateTenantUser");
185             } else {
186                 MsoAdapterException me = new MsoAdapterException (e.getMessage (), e);
187                 me.addContext ("CreateTenantUser");
188                 throw me;
189             }
190         }
191         return tenant.getId ();
192     }
193
194     /**
195      * Query for a tenant by ID in the given cloud. If the tenant exists,
196      * return an MsoTenant object. If not, return null.
197      * <p>
198      * For the AIC Cloud (DCP/LCP): it is not clear that cloudId is needed, as all admin
199      * requests go to the centralized identity service in DCP. However, if some artifact
200      * must exist in each local LCP instance as well, then it will be needed to access the
201      * correct region.
202      * <p>
203      *
204      * @param tenantId The Openstack ID of the tenant to query
205      * @param cloudSiteId The cloud identifier (may be a region) in which to query the tenant.
206      * @return the tenant properties of the queried tenant, or null if not found
207      * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
208      */
209     public MsoTenant queryTenant (String tenantId, String cloudSiteId) throws MsoException {
210         // Obtain the cloud site information where we will query the tenant
211         CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
212                 () -> new MsoCloudSiteNotFound(cloudSiteId));
213
214         Keystone keystoneAdminClient = getKeystoneAdminClient (cloudSite);
215
216         // Check if the tenant exists and return its Tenant Id
217         try {
218             Tenant tenant = findTenantById (keystoneAdminClient, tenantId);
219             if (tenant == null) {
220                 return null;
221             }
222
223             Map <String, String> metadata = new HashMap <String, String> ();
224             if (cloudSite.getIdentityService().getTenantMetadata ()) {
225                 OpenStackRequest <Metadata> request = keystoneAdminClient.tenants ().showMetadata (tenant.getId ());
226                 Metadata tenantMetadata = executeAndRecordOpenstackRequest (request);
227                 if (tenantMetadata != null) {
228                     metadata = tenantMetadata.getMetadata ();
229                 }
230             }
231             return new MsoTenant (tenant.getId (), tenant.getName (), metadata);
232         } catch (OpenStackBaseException e) {
233             // Convert Keystone OpenStackResponseException to MsoOpenstackException
234             throw keystoneErrorToMsoException (e, "QueryTenant");
235         } catch (RuntimeException e) {
236             // Catch-all
237             throw runtimeExceptionToMsoException (e, "QueryTenant");
238         }
239     }
240
241     /**
242      * Query for a tenant with the specified name in the given cloud. If the tenant exists,
243      * return an MsoTenant object. If not, return null. This query is useful if the client
244      * knows it has the tenant name, skipping an initial lookup by ID that would always fail.
245      * <p>
246      * For the AIC Cloud (DCP/LCP): it is not clear that cloudId is needed, as all admin
247      * requests go to the centralized identity service in DCP. However, if some artifact
248      * must exist in each local LCP instance as well, then it will be needed to access the
249      * correct region.
250      * <p>
251      *
252      * @param tenantName The name of the tenant to query
253      * @param cloudSiteId The cloud identifier (may be a region) in which to query the tenant.
254      * @return the tenant properties of the queried tenant, or null if not found
255      * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
256      */
257     public MsoTenant queryTenantByName (String tenantName, String cloudSiteId) throws MsoException {
258         // Obtain the cloud site information where we will query the tenant
259         CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
260                 () -> new MsoCloudSiteNotFound(cloudSiteId));
261         Keystone keystoneAdminClient = getKeystoneAdminClient (cloudSite);
262
263         try {
264             Tenant tenant = findTenantByName (keystoneAdminClient, tenantName);
265             if (tenant == null) {
266                 return null;
267             }
268
269             Map <String, String> metadata = new HashMap <String, String> ();
270             if (cloudSite.getIdentityService().getTenantMetadata ()) {
271                 OpenStackRequest <Metadata> request = keystoneAdminClient.tenants ().showMetadata (tenant.getId ());
272                 Metadata tenantMetadata = executeAndRecordOpenstackRequest (request);
273                 if (tenantMetadata != null) {
274                     metadata = tenantMetadata.getMetadata ();
275                 }
276             }
277             return new MsoTenant (tenant.getId (), tenant.getName (), metadata);
278         } catch (OpenStackBaseException e) {
279             // Convert Keystone OpenStackResponseException to MsoOpenstackException
280             throw keystoneErrorToMsoException (e, "QueryTenantName");
281         } catch (RuntimeException e) {
282             // Catch-all
283             throw runtimeExceptionToMsoException (e, "QueryTenantName");
284         }
285     }
286
287     /**
288      * Delete the specified Tenant (by ID) in the given cloud. This method returns true or
289      * false, depending on whether the tenant existed and was successfully deleted, or if
290      * the tenant already did not exist. Both cases are treated as success (no Exceptions).
291      * <p>
292      * Note for the AIC Cloud (DCP/LCP): all admin requests go to the centralized identity
293      * service in DCP. So deleting a tenant from one cloudSiteId will remove it from all
294      * sites managed by that identity service.
295      * <p>
296      *
297      * @param tenantId The Openstack ID of the tenant to delete
298      * @param cloudSiteId The cloud identifier from which to delete the tenant.
299      * @return true if the tenant was deleted, false if the tenant did not exist.
300      * @throws MsoOpenstackException If the Openstack API call returns an exception.
301      */
302     public boolean deleteTenant (String tenantId, String cloudSiteId) throws MsoException {
303         // Obtain the cloud site information where we will query the tenant
304         CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(
305                 () -> new MsoCloudSiteNotFound(cloudSiteId));
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);
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         Optional<CloudSite> cloudSite = cloudConfig.getCloudSite (cloudSiteId);
352         if (!cloudSite.isPresent()) {
353             throw new MsoCloudSiteNotFound (cloudSiteId);
354         }
355         Keystone keystoneAdminClient = getKeystoneAdminClient (cloudSite.get());
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);
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         MsoTenantUtils tenantUtils = tenantUtilsFactory.getTenantUtilsByServerType(cloudIdentity.getIdentityServerType());
421         final String keystoneUrl = tenantUtils.getKeystoneUrl(region, cloudIdentity);
422         Keystone keystone = new Keystone(keystoneUrl);
423
424         // Must authenticate against the 'admin' tenant to get the services endpoints
425         Access access = null;
426         String token = null;
427         try {
428                 Authentication credentials = authenticationMethodFactory.getAuthenticationFor(cloudIdentity);
429             OpenStackRequest <Access> request = keystone.tokens ()
430                                                         .authenticate (credentials)
431                                                         .withTenantName (adminTenantName);
432             access = executeAndRecordOpenstackRequest (request);
433             token = access.getToken ().getId ();
434         } catch (OpenStackResponseException e) {
435             if (e.getStatus () == 401) {
436                 // Authentication error. Can't access admin tenant - something is mis-configured
437                 String error = "MSO Authentication Failed for " + cloudIdentity.getId ();
438                 alarmLogger.sendAlarm ("MsoAuthenticationError", MsoAlarmLogger.CRITICAL, error);
439                 throw new MsoAdapterException (error);
440             } else {
441                 throw keystoneErrorToMsoException (e, "TokenAuth");
442             }
443         } catch (OpenStackConnectException e) {
444             // Connection to Openstack failed
445             throw keystoneErrorToMsoException (e, "TokenAuth");
446         }
447
448         // Get the Identity service URL. Throws runtime exception if not found per region.
449         String adminUrl = null;
450         try {
451                 // TODO:  FOR TESTING!!!!
452                 adminUrl = KeystoneUtils.findEndpointURL (access.getServiceCatalog (), "identity", region, "public");
453                 adminUrl = adminUrl.replaceFirst("5000", "35357");
454         } catch (RuntimeException e) {
455             String error = "Identity service not found: region=" + region + ",cloud=" + cloudIdentity.getId ();
456             alarmLogger.sendAlarm ("MsoConfigurationError", MsoAlarmLogger.CRITICAL, error);
457             LOGGER.error(MessageEnum.IDENTITY_SERVICE_NOT_FOUND, region, cloudIdentity.getId(), "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception in findEndpointURL");
458             throw new MsoAdapterException (error, e);
459         }
460
461         // A new Keystone object is required for the new URL. Use the auth token from above.
462         // Note: this doesn't go back to Openstack, it's just a local object.
463         keystone = new Keystone (adminUrl);
464         keystone.token (token);
465
466         // Cache to avoid re-authentication for every call.
467         KeystoneCacheEntry cacheEntry = new KeystoneCacheEntry (adminUrl, token, access.getToken ().getExpires ());
468         adminClientCache.put (cloudId, cacheEntry);
469
470         return keystone;
471     }
472
473     /*
474      * Find a tenant (or query its existance) by its Name or Id. Check first against the
475      * ID. If that fails, then try by name.
476      *
477      * @param adminClient an authenticated Keystone object
478      *
479      * @param tenantName the tenant name or ID to query
480      *
481      * @return a Tenant object or null if not found
482      */
483     public Tenant findTenantByNameOrId (Keystone adminClient, String tenantNameOrId) {
484         if (tenantNameOrId == null) {
485             return null;
486         }
487
488         Tenant tenant = findTenantById (adminClient, tenantNameOrId);
489         if (tenant == null) {
490             tenant = findTenantByName (adminClient, tenantNameOrId);
491         }
492
493         return tenant;
494     }
495
496     /*
497      * Find a tenant (or query its existance) by its Id.
498      *
499      * @param adminClient an authenticated Keystone object
500      *
501      * @param tenantName the tenant ID to query
502      *
503      * @return a Tenant object or null if not found
504      */
505     private Tenant findTenantById (Keystone adminClient, String tenantId) {
506         if (tenantId == null) {
507             return null;
508         }
509
510         try {
511             OpenStackRequest <Tenant> request = adminClient.tenants ().show (tenantId);
512             return executeAndRecordOpenstackRequest (request);
513         } catch (OpenStackResponseException e) {
514             if (e.getStatus () == 404) {
515                 return null;
516             } else {
517                 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");
518                 throw e;
519             }
520         }
521     }
522
523     /*
524      * Find a tenant (or query its existance) by its Name. This method avoids an
525      * initial lookup by ID when it's known that we have the tenant Name.
526      *
527      * @param adminClient an authenticated Keystone object
528      *
529      * @param tenantName the tenant name to query
530      *
531      * @return a Tenant object or null if not found
532      */
533     public Tenant findTenantByName (Keystone adminClient, String tenantName) {
534         if (tenantName == null) {
535             return null;
536         }
537
538         try {
539             OpenStackRequest <Tenant> request = adminClient.tenants ().show ("").queryParam ("name", tenantName);
540             return executeAndRecordOpenstackRequest (request);
541         } catch (OpenStackResponseException e) {
542             if (e.getStatus () == 404) {
543                 return null;
544             } else {
545                 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");
546                 throw e;
547             }
548         }
549     }
550
551     /*
552      * Look up an Openstack User by Name or Openstack ID. Check the ID first, and if that
553      * fails, try the Name.
554      *
555      * @param adminClient an authenticated Keystone object
556      *
557      * @param userName the user name or ID to query
558      *
559      * @return a User object or null if not found
560      */
561     private User findUserByNameOrId (Keystone adminClient, String userNameOrId) {
562         if (userNameOrId == null) {
563             return null;
564         }
565
566         try {
567             OpenStackRequest <User> request = adminClient.users ().show (userNameOrId);
568             return executeAndRecordOpenstackRequest (request);
569         } catch (OpenStackResponseException e) {
570             if (e.getStatus () == 404) {
571                 // Not found by ID. Search for name
572                 return findUserByName (adminClient, userNameOrId);
573             } else {
574                 LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "Openstack Error, GET User (" + userNameOrId + "): " + e, "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception in Openstack GET User");
575                 throw e;
576             }
577         }
578     }
579
580     /*
581      * Look up an Openstack User by Name. This avoids initial Openstack query by ID
582      * if we know we have the User Name.
583      *
584      * @param adminClient an authenticated Keystone object
585      *
586      * @param userName the user name to query
587      *
588      * @return a User object or null if not found
589      */
590     public User findUserByName (Keystone adminClient, String userName) {
591         if (userName == null) {
592             return null;
593         }
594
595         try {
596             OpenStackRequest <User> request = adminClient.users ().show ("").queryParam ("name", userName);
597             return executeAndRecordOpenstackRequest (request);
598         } catch (OpenStackResponseException e) {
599             if (e.getStatus () == 404) {
600                 return null;
601             } else {
602                 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");
603                 throw e;
604             }
605         }
606     }
607
608     /*
609      * Look up an Openstack Role by Name or Id. There is no direct query for Roles, so
610      * need to retrieve a full list from Openstack and look for a match. By default,
611      * Openstack should have a "_member_" role for normal VM-level privileges and an
612      * "admin" role for expanded privileges (e.g. administer tenants, users, and roles).
613      * <p>
614      *
615      * @param adminClient an authenticated Keystone object
616      *
617      * @param roleNameOrId the Role name or ID to look up
618      *
619      * @return a Role object
620      */
621     private  Role findRoleByNameOrId (Keystone adminClient, String roleNameOrId) {
622         if (roleNameOrId == null) {
623             return null;
624         }
625
626         // Search by name or ID. Must search in list
627         OpenStackRequest <Roles> request = adminClient.roles ().list ();
628         Roles roles = executeAndRecordOpenstackRequest (request);
629
630         for (Role role : roles) {
631             if (roleNameOrId.equals (role.getName ()) || roleNameOrId.equals (role.getId ())) {
632                 return role;
633             }
634         }
635
636         return null;
637     }
638
639     private static class KeystoneCacheEntry implements Serializable {
640
641         private static final long serialVersionUID = 1L;
642
643         private String keystoneUrl;
644         private String token;
645         private Calendar expires;
646
647         public KeystoneCacheEntry (String url, String token, Calendar expires) {
648             this.keystoneUrl = url;
649             this.token = token;
650             this.expires = expires;
651         }
652
653         public Keystone getKeystoneClient () {
654             Keystone keystone = new Keystone (keystoneUrl);
655             keystone.token (token);
656             return keystone;
657         }
658
659         public boolean isExpired () {
660             // adding arbitrary guard timer of 5 minutes
661             return expires == null || System.currentTimeMillis() > (expires.getTimeInMillis() - 300000);
662         }
663     }
664
665         @Override
666         public String getKeystoneUrl(String regionId, CloudIdentity cloudIdentity) throws MsoException {
667                 return cloudIdentity.getIdentityUrl();
668         }
669 }