5c21913a666e6d5a2750a369a29b2a97188d1f87
[so.git] / adapters / mso-adapter-utils / src / main / java / org / openecomp / mso / openstack / utils / MsoNeutronUtils.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.ArrayList;
26 import java.util.Calendar;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30
31 import org.openecomp.mso.cloud.CloudConfig;
32 import org.openecomp.mso.cloud.CloudConfigFactory;
33 import org.openecomp.mso.cloud.CloudIdentity;
34 import org.openecomp.mso.cloud.CloudSite;
35 import org.openecomp.mso.logger.MessageEnum;
36 import org.openecomp.mso.logger.MsoAlarmLogger;
37 import org.openecomp.mso.logger.MsoLogger;
38 import org.openecomp.mso.openstack.beans.NetworkInfo;
39 import org.openecomp.mso.openstack.exceptions.MsoAdapterException;
40 import org.openecomp.mso.openstack.exceptions.MsoCloudSiteNotFound;
41 import org.openecomp.mso.openstack.exceptions.MsoException;
42 import org.openecomp.mso.openstack.exceptions.MsoIOException;
43 import org.openecomp.mso.openstack.exceptions.MsoNetworkAlreadyExists;
44 import org.openecomp.mso.openstack.exceptions.MsoNetworkNotFound;
45 import org.openecomp.mso.openstack.exceptions.MsoOpenstackException;
46 import org.openecomp.mso.openstack.exceptions.MsoTenantNotFound;
47 import com.woorea.openstack.base.client.OpenStackBaseException;
48 import com.woorea.openstack.base.client.OpenStackConnectException;
49 import com.woorea.openstack.base.client.OpenStackRequest;
50 import com.woorea.openstack.base.client.OpenStackResponseException;
51 import com.woorea.openstack.keystone.Keystone;
52 import com.woorea.openstack.keystone.model.Access;
53 import com.woorea.openstack.keystone.utils.KeystoneUtils;
54 import com.woorea.openstack.quantum.Quantum;
55 import com.woorea.openstack.quantum.model.Network;
56 import com.woorea.openstack.quantum.model.Networks;
57 import com.woorea.openstack.quantum.model.Segment;
58 import com.woorea.openstack.keystone.model.Authentication;
59
60 public class MsoNeutronUtils extends MsoCommonUtils
61 {
62         // Cache Neutron 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 "tenantId:cloudId"
67         private static Map<String,NeutronCacheEntry> neutronClientCache = new HashMap<String,NeutronCacheEntry>();
68
69         // Fetch cloud configuration each time (may be cached in CloudConfig class)
70         private CloudConfig cloudConfig;
71
72         private static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA);
73         private String msoPropID;
74         
75         public enum NetworkType {
76                 BASIC, PROVIDER, MULTI_PROVIDER
77         };
78
79         public MsoNeutronUtils(String msoPropID, CloudConfigFactory cloudConfigFactory) {
80                 cloudConfig = cloudConfigFactory.getCloudConfig();
81                 this.msoPropID = msoPropID;
82         }
83
84         /**
85          * Create a network with the specified parameters in the given cloud/tenant.
86          *
87          * If a network already exists with the same name, an exception will be thrown.  Note that
88          * this is an MSO-imposed restriction.  Openstack does not require uniqueness on network names.
89          * <p>
90          * @param cloudSiteId The cloud identifier (may be a region) in which to create the network.
91          * @param tenantId The tenant in which to create the network
92          * @param type The type of network to create (Basic, Provider, Multi-Provider)
93          * @param networkName The network name to create
94          * @param provider The provider network name (for Provider or Multi-Provider networks)
95          * @param vlans A list of VLAN segments for the network (for Provider or Multi-Provider networks)
96          * @return a NetworkInfo object which describes the newly created network
97          * @throws MsoNetworkAlreadyExists Thrown if a network with the same name already exists
98          * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
99          * @throws MsoCloudSiteNotFound Thrown if the cloudSite is invalid or unknown
100          */
101         public NetworkInfo createNetwork (String cloudSiteId, String tenantId, NetworkType type, String networkName, String provider, List<Integer> vlans)
102                 throws MsoException, MsoNetworkAlreadyExists, MsoCloudSiteNotFound
103         {
104                 // Obtain the cloud site information where we will create the stack
105                 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId);
106                 if (cloudSite == null) {
107                         throw new MsoCloudSiteNotFound(cloudSiteId);
108                 }
109
110                 Quantum neutronClient = getNeutronClient (cloudSite, tenantId);
111
112                 // Check if a network already exists with this name
113                 // Openstack will allow duplicate name, so require explicit check
114                 Network network = findNetworkByName (neutronClient, networkName);
115
116                 if (network != null) {
117                         // Network already exists.  Throw an exception
118                         LOGGER.error(MessageEnum.RA_NETWORK_ALREADY_EXIST, networkName, cloudSiteId, tenantId, "Openstack", "", MsoLogger.ErrorCode.DataError, "Network already exists");
119                         throw new MsoNetworkAlreadyExists (networkName, tenantId, cloudSiteId);
120                 }
121
122                 // Does not exist, create a new one
123                 network = new Network();
124                 network.setName(networkName);
125                 network.setAdminStateUp(true);
126
127                 if (type == NetworkType.PROVIDER) {
128                         if (provider != null && vlans != null && vlans.size() > 0) {
129                                 network.setProviderPhysicalNetwork (provider);
130                                 network.setProviderNetworkType("vlan");
131                                 network.setProviderSegmentationId (vlans.get(0));
132                         }
133                 } else if (type == NetworkType.MULTI_PROVIDER) {
134                         if (provider != null && vlans != null && vlans.size() > 0) {
135                                 List<Segment> segments = new ArrayList<Segment>(vlans.size());
136                                 for (int vlan : vlans) {
137                                         Segment segment = new Segment();
138                                         segment.setProviderPhysicalNetwork (provider);
139                                         segment.setProviderNetworkType("vlan");
140                                         segment.setProviderSegmentationId (vlan);
141
142                                         segments.add(segment);
143                                 }
144                                 network.setSegments(segments);
145                         }
146                 }
147
148                 try {
149                         OpenStackRequest<Network> request = neutronClient.networks().create(network);
150                         Network newNetwork = executeAndRecordOpenstackRequest(request);
151                         return new NetworkInfo(newNetwork);
152                 }
153                 catch (OpenStackBaseException e) {
154                         // Convert Neutron exception to an MsoOpenstackException
155                         MsoException me = neutronExceptionToMsoException (e, "CreateNetwork");
156                         throw me;
157                 }
158                 catch (RuntimeException e) {
159                         // Catch-all
160                         MsoException me = runtimeExceptionToMsoException(e, "CreateNetwork");
161                         throw me;
162                 }
163         }
164
165
166         /**
167          * Query for a network with the specified name or ID in the given cloud.  If the network exists,
168          * return an NetworkInfo object.  If not, return null.
169          * <p>
170          * Whenever possible, the network ID should be used as it is much more efficient.  Query by
171          * name requires retrieval of all networks for the tenant and search for matching name.
172          * <p>
173          * @param networkNameOrId The network to query
174          * @param tenantId The Openstack tenant to look in for the network
175          * @param cloudSiteId The cloud identifier (may be a region) in which to query the network.
176          * @return a NetworkInfo object describing the queried network, or null if not found
177          * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
178          * @throws MsoCloudSiteNotFound
179          */
180         public NetworkInfo queryNetwork (String networkNameOrId, String tenantId, String cloudSiteId)
181                 throws MsoException, MsoCloudSiteNotFound
182         {
183                 LOGGER.debug("In queryNetwork");
184
185                 // Obtain the cloud site information
186                 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId);
187                 if (cloudSite == null) {
188                         throw new MsoCloudSiteNotFound(cloudSiteId);
189                 }
190
191                 Quantum neutronClient = getNeutronClient (cloudSite, tenantId);
192
193                 // Check if the network exists and return its info
194                 try {
195                         Network network = findNetworkByNameOrId (neutronClient, networkNameOrId);
196                         if (network == null) {
197                                 LOGGER.debug ("Query Network: " + networkNameOrId + " not found in tenant " + tenantId);
198                                 return null;
199                         }
200                         return new NetworkInfo(network);
201                 }
202                 catch (OpenStackBaseException e) {
203                         // Convert Neutron exception to an MsoOpenstackException
204                         MsoException me = neutronExceptionToMsoException (e, "QueryNetwork");
205                         throw me;
206                 }
207                 catch (RuntimeException e) {
208                         // Catch-all
209                         MsoException me = runtimeExceptionToMsoException(e, "QueryNetwork");
210                         throw me;
211                 }
212         }
213
214         /**
215          * Delete the specified Network (by ID) in the given cloud.
216          * If the network does not exist, success is returned.
217          * <p>
218          * @param networkNameOrId The name or Openstack ID of the network to delete
219          * @param cloudId The cloud identifier (may be a region) from which to delete the network.
220          * @return true if the network was deleted, false if the network did not exist
221          * @throws MsoOpenstackException If the Openstack API call returns an exception, this local
222          * exception will be thrown.
223          * @throws MsoCloudSiteNotFound
224          */
225         public boolean deleteNetwork (String networkId, String tenantId, String cloudSiteId)
226                 throws MsoException, MsoCloudSiteNotFound
227         {
228                 // Obtain the cloud site information where we will create the stack
229                 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId);
230                 if (cloudSite == null) {
231                         throw new MsoCloudSiteNotFound(cloudSiteId);
232                 }
233
234                 Quantum neutronClient = getNeutronClient (cloudSite, tenantId);
235
236                 try {
237                         // Check that the network exists.
238                         Network network = findNetworkById (neutronClient, networkId);
239                         if (network == null) {
240                                 LOGGER.info(MessageEnum.RA_DELETE_NETWORK_EXC, networkId, cloudSiteId, tenantId, "Openstack", "");
241                                 return false;
242                         }
243
244                         OpenStackRequest<Void> request = neutronClient.networks().delete(network.getId());
245                         executeAndRecordOpenstackRequest(request);
246
247                         LOGGER.debug ("Deleted Network " + network.getId() + " (" + network.getName() + ")");
248                 }
249                 catch (OpenStackBaseException e) {
250                         // Convert Neutron exception to an MsoOpenstackException
251                         MsoException me = neutronExceptionToMsoException (e, "Delete Network");
252                         throw me;
253                 }
254                 catch (RuntimeException e) {
255                         // Catch-all
256                         MsoException me = runtimeExceptionToMsoException(e, "DeleteNetwork");
257                         throw me;
258                 }
259
260                 return true;
261         }
262
263
264         /**
265          * Update a network with the specified parameters in the given cloud/tenant.
266          *
267          * Specifically, this call is intended to update the VLAN segments on a
268          * multi-provider network.  The provider segments will be replaced with the
269          * supplied list of VLANs.
270          * <p>
271          * Note that updating the 'segments' array is not normally supported by Neutron.
272          * This method relies on a Platform Orchestration extension (using SDN controller
273          * to manage the virtual networking).
274          *
275          * @param cloudSiteId The cloud site ID (may be a region) in which to update the network.
276          * @param the Openstack ID of the tenant in which to update the network
277          * @param networkId The unique Openstack ID of the network to be updated
278          * @param type The network type (Basic, Provider, Multi-Provider)
279          * @param provider The provider network name.  This should not change.
280          * @param vlans The list of VLAN segments to replace
281          * @return a NetworkInfo object which describes the updated network
282          * @throws MsoNetworkNotFound Thrown if the requested network does not exist
283          * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
284          * @throws MsoCloudSiteNotFound
285          */
286         public NetworkInfo updateNetwork (String cloudSiteId, String tenantId, String networkId, NetworkType type, String provider, List<Integer> vlans)
287                 throws MsoException, MsoNetworkNotFound, MsoCloudSiteNotFound
288         {
289                 // Obtain the cloud site information where we will create the stack
290                 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId);
291                 if (cloudSite == null) {
292                         throw new MsoCloudSiteNotFound(cloudSiteId);
293                 }
294                 Quantum neutronClient = getNeutronClient (cloudSite, tenantId);
295
296                 // Check that the network exists
297                 Network network = findNetworkById (neutronClient, networkId);
298
299                 if (network == null) {
300                         // Network not found.  Throw an exception
301                         LOGGER.error(MessageEnum.RA_NETWORK_NOT_FOUND, networkId, cloudSiteId, tenantId, "Openstack", "", MsoLogger.ErrorCode.DataError, "Network not found");
302                         throw new MsoNetworkNotFound (networkId, tenantId, cloudSiteId);
303                 }
304
305                 // Overwrite the properties to be updated
306                 if (type == NetworkType.PROVIDER) {
307                         if (provider != null && vlans != null && vlans.size() > 0) {
308                                 network.setProviderPhysicalNetwork (provider);
309                                 network.setProviderNetworkType("vlan");
310                                 network.setProviderSegmentationId (vlans.get(0));
311                         }
312                 } else if (type == NetworkType.MULTI_PROVIDER) {
313                         if (provider != null && vlans != null && vlans.size() > 0) {
314                                 List<Segment> segments = new ArrayList<Segment>(vlans.size());
315                                 for (int vlan : vlans) {
316                                         Segment segment = new Segment();
317                                         segment.setProviderPhysicalNetwork (provider);
318                                         segment.setProviderNetworkType("vlan");
319                                         segment.setProviderSegmentationId (vlan);
320
321                                         segments.add(segment);
322                                 }
323                                 network.setSegments(segments);
324                         }
325                 }
326
327                 try {
328                         OpenStackRequest<Network> request = neutronClient.networks().update(network);
329                         Network newNetwork = executeAndRecordOpenstackRequest(request);
330                         return new NetworkInfo(newNetwork);
331                 }
332                 catch (OpenStackBaseException e) {
333                         // Convert Neutron exception to an MsoOpenstackException
334                         MsoException me = neutronExceptionToMsoException (e, "UpdateNetwork");
335                         throw me;
336                 }
337                 catch (RuntimeException e) {
338                         // Catch-all
339                         MsoException me = runtimeExceptionToMsoException(e, "UpdateNetwork");
340                         throw me;
341                 }
342         }
343
344
345         // -------------------------------------------------------------------
346         // PRIVATE UTILITY FUNCTIONS FOR USE WITHIN THIS CLASS
347
348         /**
349          * Get a Neutron (Quantum) client for the Openstack Network service.
350          * This requires a 'member'-level userId + password, which will be retrieved from
351          * properties based on the specified cloud Id.  The tenant in which to operate
352          * must also be provided.
353          * <p>
354          * On successful authentication, the Quantum object will be cached for the
355          * tenantID + cloudId so that it can be reused without reauthenticating with
356          *  Openstack every time.
357          *
358          * @param cloudSite - a cloud site definition
359          * @param tenantId - Openstack tenant ID
360          * @return an authenticated Quantum object
361          */
362         private Quantum getNeutronClient (CloudSite cloudSite, String tenantId)
363                 throws MsoTenantNotFound, MsoException
364         {
365                 String cloudId = cloudSite.getId();
366
367                 // Check first in the cache of previously authorized clients
368                 String cacheKey = cloudId + ":" + tenantId;
369                 if (neutronClientCache.containsKey(cacheKey)) {
370                         if (! neutronClientCache.get(cacheKey).isExpired()) {
371                                 LOGGER.debug ("Using Cached HEAT Client for " + cacheKey);
372                                 Quantum neutronClient = neutronClientCache.get(cacheKey).getNeutronClient();
373                                 return neutronClient;
374                         }
375                         else {
376                                 // Token is expired.  Remove it from cache.
377                                 neutronClientCache.remove(cacheKey);
378                                 LOGGER.debug ("Expired Cached Neutron Client for " + cacheKey);
379                         }
380                 }
381
382                 // Obtain an MSO token for the tenant from the identity service
383                 CloudIdentity cloudIdentity = cloudSite.getIdentityService();
384                 Keystone keystoneTenantClient = new Keystone (cloudIdentity.getKeystoneUrl(cloudId, msoPropID));
385                 Access access = null;
386                 try {
387                         Authentication credentials = cloudIdentity.getAuthentication ();
388                         OpenStackRequest<Access> request = keystoneTenantClient.tokens().authenticate(credentials).withTenantId(tenantId);
389                         access = executeAndRecordOpenstackRequest(request);
390                 }
391                 catch (OpenStackResponseException e) {
392                         if (e.getStatus() == 401) {
393                                 // Authentication error.
394                                 String error = "Authentication Failure: tenant=" + tenantId + ",cloud=" + cloudIdentity.getId();
395                                 alarmLogger .sendAlarm("MsoAuthenticationError", MsoAlarmLogger.CRITICAL, error);
396                                 throw new MsoAdapterException(error);
397                         }
398                         else {
399                                 MsoException me = keystoneErrorToMsoException(e, "TokenAuth");
400                                 throw me;
401                         }
402                 }
403                 catch (OpenStackConnectException e) {
404                         // Connection to Openstack failed
405                         MsoIOException me = new MsoIOException (e.getMessage(), e);
406                         me.addContext("TokenAuth");
407                         throw me;
408                 }
409                 catch (RuntimeException e) {
410                         // Catch-all
411                         MsoException me = runtimeExceptionToMsoException(e, "TokenAuth");
412                         throw me;
413                 }
414
415                 String region = cloudSite.getRegionId();
416                 String neutronUrl = null;
417                 try {
418                         neutronUrl = KeystoneUtils.findEndpointURL(access.getServiceCatalog(), "network", region, "public");
419                         if (! neutronUrl.endsWith("/")) {
420                 neutronUrl += "/v2.0/";
421             }
422                 } catch (RuntimeException e) {
423                         // This comes back for not found (probably an incorrect region ID)
424                         String error = "Network service not found: region=" + region + ",cloud=" + cloudIdentity.getId();
425                         alarmLogger.sendAlarm("MsoConfigurationError", MsoAlarmLogger.CRITICAL, error);
426                         throw new MsoAdapterException (error, e);
427                 }
428
429                 Quantum neutronClient = new Quantum(neutronUrl);
430                 neutronClient.token(access.getToken().getId());
431
432                 neutronClientCache.put(cacheKey, new NeutronCacheEntry(neutronUrl, access.getToken().getId(), access.getToken().getExpires()));
433                 LOGGER.debug ("Caching Neutron Client for " + cacheKey);
434
435                 return neutronClient;
436         }
437
438         /**
439          * Forcibly expire a Neutron client from the cache.  This call is for use by
440          * the KeystoneClient in case where a tenant is deleted.  In that case,
441          * all cached credentials must be purged so that fresh authentication is
442          * done on subsequent calls.
443          * <p>
444          * @param tenantName
445          * @param cloudId
446          */
447         public static void expireNeutronClient (String tenantId, String cloudId) {
448                 String cacheKey = cloudId + ":" + tenantId;
449                 if (neutronClientCache.containsKey(cacheKey)) {
450                         neutronClientCache.remove(cacheKey);
451                         LOGGER.debug ("Deleted Cached Neutron Client for " + cacheKey);
452                 }
453         }
454
455
456         /*
457          * Find a tenant (or query its existence) by its Name or Id.  Check first against the
458          * ID.  If that fails, then try by name.
459          *
460          * @param adminClient an authenticated Keystone object
461          * @param tenantName the tenant name or ID to query
462          * @return a Tenant object or null if not found
463          */
464         public Network findNetworkByNameOrId (Quantum neutronClient, String networkNameOrId)
465         {
466                 if (networkNameOrId == null) {
467             return null;
468         }
469
470                 Network network = findNetworkById(neutronClient, networkNameOrId);
471
472                 if (network == null) {
473             network = findNetworkByName(neutronClient, networkNameOrId);
474         }
475
476                 return network;
477         }
478
479         /*
480          * Find a network (or query its existence) by its Id.
481          *
482          * @param neutronClient an authenticated Quantum object
483          * @param networkId the network ID to query
484          * @return a Network object or null if not found
485          */
486         private static Network findNetworkById (Quantum neutronClient, String networkId)
487         {
488                 if (networkId == null) {
489             return null;
490         }
491
492                 try {
493                         OpenStackRequest<Network> request = neutronClient.networks().show(networkId);
494                         Network network = executeAndRecordOpenstackRequest(request);
495                         return network;
496                 }
497                 catch (OpenStackResponseException e) {
498                         if (e.getStatus() == 404) {
499                                 return null;
500                         } else {
501                                 LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "OpenStack", "Openstack Error, GET Network By ID (" + networkId + "): " + e, "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception in Openstack");
502                                 throw e;
503                         }
504                 }
505         }
506
507         /*
508          * Find a network (or query its existence) by its Name.  This method avoids an
509          * initial lookup by ID when it's known that we have the network Name.
510          *
511          * Neutron does not support 'name=*' query parameter for Network query (show).
512          * The only way to query by name is to retrieve all networks and look for the
513          * match.  While inefficient, this capability will be provided as it is needed
514          * by MSO, but should be avoided in favor of ID whenever possible.
515          *
516          * TODO:
517          * Network names are not required to be unique, though MSO will attempt to enforce
518          * uniqueness.  This call probably needs to return an error (instead of returning
519          * the first match).
520          *
521          * @param neutronClient an authenticated Quantum object
522          * @param networkName the network name to query
523          * @return a Network object or null if not found
524          */
525         public Network findNetworkByName (Quantum neutronClient, String networkName)
526         {
527                 if (networkName == null) {
528             return null;
529         }
530
531                 try {
532                         OpenStackRequest<Networks> request = neutronClient.networks().list();
533                         Networks networks = executeAndRecordOpenstackRequest(request);
534                         for (Network network : networks.getList()) {
535                                 if (network.getName().equals(networkName)) {
536                                         LOGGER.debug ("Found match on network name: " + networkName);
537                                         return network;
538                                 }
539                         }
540                         LOGGER.debug ("findNetworkByName - no match found for " + networkName);
541                         return null;
542                 }
543                 catch (OpenStackResponseException e) {
544                         if (e.getStatus() == 404) {
545                                 return null;
546                         } else {
547                                 LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "OpenStack", "Openstack Error, GET Network By Name (" + networkName + "): " + e, "OpenStack", "", MsoLogger.ErrorCode.DataError, "Exception in OpenStack");
548                                 throw e;
549                         }
550                 }
551         }
552
553
554         /*
555          * An entry in the Neutron Client Cache.  It saves the Neutron client object
556          * along with the token expiration.  After this interval, this cache
557          * item will no longer be used.
558          */
559         private static class NeutronCacheEntry implements Serializable
560         {
561                 private static final long serialVersionUID = 1L;
562
563                 private String neutronUrl;
564                 private String token;
565                 private Calendar expires;
566
567                 public NeutronCacheEntry (String neutronUrl, String token, Calendar expires) {
568                         this.neutronUrl = neutronUrl;
569                         this.token = token;
570                         this.expires = expires;
571                 }
572
573                 public Quantum getNeutronClient () {
574                         Quantum neutronClient = new Quantum(neutronUrl);
575                         neutronClient.token(token);
576                         return neutronClient;
577                 }
578
579                 public boolean isExpired() {
580                         if (expires == null) {
581                 return true;
582             }
583
584             return System.currentTimeMillis() > expires.getTimeInMillis();
585                 }
586         }
587
588         /**
589          * Clean up the Neutron client cache to remove expired entries.
590          */
591         public static void neutronCacheCleanup () {
592                 for (String cacheKey : neutronClientCache.keySet()) {
593                         if (neutronClientCache.get(cacheKey).isExpired()) {
594                                 neutronClientCache.remove(cacheKey);
595                                 LOGGER.debug ("Cleaned Up Cached Neutron Client for " + cacheKey);
596                         }
597                 }
598         }
599
600         /**
601          * Reset the Neutron client cache.
602          * This may be useful if cached credentials get out of sync.
603          */
604         public static void neutronCacheReset () {
605                 neutronClientCache = new HashMap<String,NeutronCacheEntry>();
606         }
607 }