aboutsummaryrefslogtreecommitdiffstats
path: root/adapters/mso-adapter-utils/src/main/java/org/openecomp/mso/openstack/utils/MsoNeutronUtils.java
blob: f3281e3f1c65e941fe09fc3e5eea559660673252 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
/*-
 * ============LICENSE_START=======================================================
 * OPENECOMP - MSO
 * ================================================================================
 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
 * ================================================================================
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ============LICENSE_END=========================================================
 */

package org.openecomp.mso.openstack.utils;


import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.openecomp.mso.cloud.CloudConfig;
import org.openecomp.mso.cloud.CloudConfigFactory;
import org.openecomp.mso.cloud.CloudIdentity;
import org.openecomp.mso.cloud.CloudSite;
import org.openecomp.mso.logger.MessageEnum;
import org.openecomp.mso.logger.MsoAlarmLogger;
import org.openecomp.mso.logger.MsoLogger;
import org.openecomp.mso.openstack.beans.NetworkInfo;
import org.openecomp.mso.openstack.exceptions.MsoAdapterException;
import org.openecomp.mso.openstack.exceptions.MsoCloudSiteNotFound;
import org.openecomp.mso.openstack.exceptions.MsoException;
import org.openecomp.mso.openstack.exceptions.MsoIOException;
import org.openecomp.mso.openstack.exceptions.MsoNetworkAlreadyExists;
import org.openecomp.mso.openstack.exceptions.MsoNetworkNotFound;
import org.openecomp.mso.openstack.exceptions.MsoOpenstackException;
import org.openecomp.mso.openstack.exceptions.MsoTenantNotFound;
import com.woorea.openstack.base.client.OpenStackBaseException;
import com.woorea.openstack.base.client.OpenStackConnectException;
import com.woorea.openstack.base.client.OpenStackRequest;
import com.woorea.openstack.base.client.OpenStackResponseException;
import com.woorea.openstack.keystone.Keystone;
import com.woorea.openstack.keystone.model.Access;
import com.woorea.openstack.keystone.utils.KeystoneUtils;
import com.woorea.openstack.quantum.Quantum;
import com.woorea.openstack.quantum.model.Network;
import com.woorea.openstack.quantum.model.Networks;
import com.woorea.openstack.quantum.model.Segment;
import com.woorea.openstack.keystone.model.Authentication;

public class MsoNeutronUtils extends MsoCommonUtils
{
	// Cache Neutron Clients statically.  Since there is just one MSO user, there is no
	// benefit to re-authentication on every request (or across different flows).  The
	// token will be used until it expires.
	//
	// The cache key is "tenantId:cloudId"
	private static Map<String,NeutronCacheEntry> neutronClientCache = new HashMap<String,NeutronCacheEntry>();

	// Fetch cloud configuration each time (may be cached in CloudConfig class)
	private CloudConfig cloudConfig;

	private static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA);
	private String msoPropID;
	
	public enum NetworkType {
		BASIC, PROVIDER, MULTI_PROVIDER
	};

	public MsoNeutronUtils(String msoPropID, CloudConfigFactory cloudConfigFactory) {
		cloudConfig = cloudConfigFactory.getCloudConfig();
		this.msoPropID = msoPropID;
	}

	/**
	 * Create a network with the specified parameters in the given cloud/tenant.
	 *
	 * If a network already exists with the same name, an exception will be thrown.  Note that
	 * this is an MSO-imposed restriction.  Openstack does not require uniqueness on network names.
	 * <p>
	 * @param cloudSiteId The cloud identifier (may be a region) in which to create the network.
	 * @param tenantId The tenant in which to create the network
	 * @param type The type of network to create (Basic, Provider, Multi-Provider)
	 * @param networkName The network name to create
	 * @param provider The provider network name (for Provider or Multi-Provider networks)
	 * @param vlans A list of VLAN segments for the network (for Provider or Multi-Provider networks)
	 * @return a NetworkInfo object which describes the newly created network
	 * @throws MsoNetworkAlreadyExists Thrown if a network with the same name already exists
	 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
	 * @throws MsoCloudSiteNotFound Thrown if the cloudSite is invalid or unknown
	 */
	public NetworkInfo createNetwork (String cloudSiteId, String tenantId, NetworkType type, String networkName, String provider, List<Integer> vlans)
		throws MsoException, MsoNetworkAlreadyExists, MsoCloudSiteNotFound
	{
		// Obtain the cloud site information where we will create the stack
		CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId);
		if (cloudSite == null) {
			throw new MsoCloudSiteNotFound(cloudSiteId);
		}

		Quantum neutronClient = getNeutronClient (cloudSite, tenantId);

		// Check if a network already exists with this name
		// Openstack will allow duplicate name, so require explicit check
		Network network = findNetworkByName (neutronClient, networkName);

		if (network != null) {
			// Network already exists.  Throw an exception
			LOGGER.error(MessageEnum.RA_NETWORK_ALREADY_EXIST, networkName, cloudSiteId, tenantId, "Openstack", "", MsoLogger.ErrorCode.DataError, "Network already exists");
			throw new MsoNetworkAlreadyExists (networkName, tenantId, cloudSiteId);
		}

		// Does not exist, create a new one
		network = new Network();
		network.setName(networkName);
		network.setAdminStateUp(true);

		if (type == NetworkType.PROVIDER) {
			if (provider != null && vlans != null && vlans.size() > 0) {
				network.setProviderPhysicalNetwork (provider);
				network.setProviderNetworkType("vlan");
				network.setProviderSegmentationId (vlans.get(0));
			}
		} else if (type == NetworkType.MULTI_PROVIDER) {
			if (provider != null && vlans != null && vlans.size() > 0) {
				List<Segment> segments = new ArrayList<Segment>(vlans.size());
				for (int vlan : vlans) {
					Segment segment = new Segment();
					segment.setProviderPhysicalNetwork (provider);
					segment.setProviderNetworkType("vlan");
					segment.setProviderSegmentationId (vlan);

					segments.add(segment);
				}
				network.setSegments(segments);
			}
		}

		try {
			OpenStackRequest<Network> request = neutronClient.networks().create(network);
			Network newNetwork = executeAndRecordOpenstackRequest(request);
			return new NetworkInfo(newNetwork);
		}
		catch (OpenStackBaseException e) {
			// Convert Neutron exception to an MsoOpenstackException
			MsoException me = neutronExceptionToMsoException (e, "CreateNetwork");
			throw me;
		}
		catch (RuntimeException e) {
			// Catch-all
			MsoException me = runtimeExceptionToMsoException(e, "CreateNetwork");
			throw me;
		}
	}


	/**
	 * Query for a network with the specified name or ID in the given cloud.  If the network exists,
	 * return an NetworkInfo object.  If not, return null.
	 * <p>
	 * Whenever possible, the network ID should be used as it is much more efficient.  Query by
	 * name requires retrieval of all networks for the tenant and search for matching name.
	 * <p>
	 * @param networkNameOrId The network to query
	 * @param tenantId The Openstack tenant to look in for the network
	 * @param cloudSiteId The cloud identifier (may be a region) in which to query the network.
	 * @return a NetworkInfo object describing the queried network, or null if not found
	 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
	 * @throws MsoCloudSiteNotFound
	 */
	public NetworkInfo queryNetwork (String networkNameOrId, String tenantId, String cloudSiteId)
		throws MsoException, MsoCloudSiteNotFound
	{
		LOGGER.debug("In queryNetwork");

		// Obtain the cloud site information
		CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId);
		if (cloudSite == null) {
			throw new MsoCloudSiteNotFound(cloudSiteId);
		}

		Quantum neutronClient = getNeutronClient (cloudSite, tenantId);

		// Check if the network exists and return its info
		try {
			Network network = findNetworkByNameOrId (neutronClient, networkNameOrId);
			if (network == null) {
				LOGGER.debug ("Query Network: " + networkNameOrId + " not found in tenant " + tenantId);
				return null;
			}
			return new NetworkInfo(network);
		}
		catch (OpenStackBaseException e) {
			// Convert Neutron exception to an MsoOpenstackException
			MsoException me = neutronExceptionToMsoException (e, "QueryNetwork");
			throw me;
		}
		catch (RuntimeException e) {
			// Catch-all
			MsoException me = runtimeExceptionToMsoException(e, "QueryNetwork");
			throw me;
		}
	}

	/**
	 * Delete the specified Network (by ID) in the given cloud.
	 * If the network does not exist, success is returned.
	 * <p>
	 * @param networkNameOrId The name or Openstack ID of the network to delete
	 * @param cloudId The cloud identifier (may be a region) from which to delete the network.
	 * @return true if the network was deleted, false if the network did not exist
	 * @throws MsoOpenstackException If the Openstack API call returns an exception, this local
	 * exception will be thrown.
	 * @throws MsoCloudSiteNotFound
	 */
	public boolean deleteNetwork (String networkId, String tenantId, String cloudSiteId)
		throws MsoException, MsoCloudSiteNotFound
	{
		// Obtain the cloud site information where we will create the stack
		CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId);
		if (cloudSite == null) {
			throw new MsoCloudSiteNotFound(cloudSiteId);
		}

		Quantum neutronClient = getNeutronClient (cloudSite, tenantId);

		try {
			// Check that the network exists.
			Network network = findNetworkById (neutronClient, networkId);
			if (network == null) {
				LOGGER.info(MessageEnum.RA_DELETE_NETWORK_EXC, networkId, cloudSiteId, tenantId, "Openstack", "");
				return false;
			}

			OpenStackRequest<Void> request = neutronClient.networks().delete(network.getId());
			executeAndRecordOpenstackRequest(request);

			LOGGER.debug ("Deleted Network " + network.getId() + " (" + network.getName() + ")");
		}
		catch (OpenStackBaseException e) {
			// Convert Neutron exception to an MsoOpenstackException
			MsoException me = neutronExceptionToMsoException (e, "Delete Network");
			throw me;
		}
		catch (RuntimeException e) {
			// Catch-all
			MsoException me = runtimeExceptionToMsoException(e, "DeleteNetwork");
			throw me;
		}

		return true;
	}


	/**
	 * Update a network with the specified parameters in the given cloud/tenant.
	 *
	 * Specifically, this call is intended to update the VLAN segments on a
	 * multi-provider network.  The provider segments will be replaced with the
	 * supplied list of VLANs.
	 * <p>
	 * Note that updating the 'segments' array is not normally supported by Neutron.
	 * This method relies on a Platform Orchestration extension (using SDN controller
	 * to manage the virtual networking).
	 *
	 * @param cloudSiteId The cloud site ID (may be a region) in which to update the network.
	 * @param the Openstack ID of the tenant in which to update the network
	 * @param networkId The unique Openstack ID of the network to be updated
	 * @param type The network type (Basic, Provider, Multi-Provider)
	 * @param provider The provider network name.  This should not change.
	 * @param vlans The list of VLAN segments to replace
	 * @return a NetworkInfo object which describes the updated network
	 * @throws MsoNetworkNotFound Thrown if the requested network does not exist
	 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception
	 * @throws MsoCloudSiteNotFound
	 */
	public NetworkInfo updateNetwork (String cloudSiteId, String tenantId, String networkId, NetworkType type, String provider, List<Integer> vlans)
		throws MsoException, MsoNetworkNotFound, MsoCloudSiteNotFound
	{
		// Obtain the cloud site information where we will create the stack
		CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId);
		if (cloudSite == null) {
			throw new MsoCloudSiteNotFound(cloudSiteId);
		}
		Quantum neutronClient = getNeutronClient (cloudSite, tenantId);

		// Check that the network exists
		Network network = findNetworkById (neutronClient, networkId);

		if (network == null) {
			// Network not found.  Throw an exception
			LOGGER.error(MessageEnum.RA_NETWORK_NOT_FOUND, networkId, cloudSiteId, tenantId, "Openstack", "", MsoLogger.ErrorCode.DataError, "Network not found");
			throw new MsoNetworkNotFound (networkId, tenantId, cloudSiteId);
		}

		// Overwrite the properties to be updated
		if (type == NetworkType.PROVIDER) {
			if (provider != null && vlans != null && vlans.size() > 0) {
				network.setProviderPhysicalNetwork (provider);
				network.setProviderNetworkType("vlan");
				network.setProviderSegmentationId (vlans.get(0));
			}
		} else if (type == NetworkType.MULTI_PROVIDER) {
			if (provider != null && vlans != null && vlans.size() > 0) {
				List<Segment> segments = new ArrayList<Segment>(vlans.size());
				for (int vlan : vlans) {
					Segment segment = new Segment();
					segment.setProviderPhysicalNetwork (provider);
					segment.setProviderNetworkType("vlan");
					segment.setProviderSegmentationId (vlan);

					segments.add(segment);
				}
				network.setSegments(segments);
			}
		}

		try {
			OpenStackRequest<Network> request = neutronClient.networks().update(network);
			Network newNetwork = executeAndRecordOpenstackRequest(request);
			return new NetworkInfo(newNetwork);
		}
		catch (OpenStackBaseException e) {
			// Convert Neutron exception to an MsoOpenstackException
			MsoException me = neutronExceptionToMsoException (e, "UpdateNetwork");
			throw me;
		}
		catch (RuntimeException e) {
			// Catch-all
			MsoException me = runtimeExceptionToMsoException(e, "UpdateNetwork");
			throw me;
		}
	}


	// -------------------------------------------------------------------
	// PRIVATE UTILITY FUNCTIONS FOR USE WITHIN THIS CLASS

	/**
	 * Get a Neutron (Quantum) client for the Openstack Network service.
	 * This requires a 'member'-level userId + password, which will be retrieved from
	 * properties based on the specified cloud Id.  The tenant in which to operate
	 * must also be provided.
	 * <p>
	 * On successful authentication, the Quantum object will be cached for the
	 * tenantID + cloudId so that it can be reused without reauthenticating with
	 *  Openstack every time.
	 *
	 * @param cloudSite - a cloud site definition
	 * @param tenantId - Openstack tenant ID
	 * @return an authenticated Quantum object
	 */
	private Quantum getNeutronClient (CloudSite cloudSite, String tenantId)
		throws MsoTenantNotFound, MsoException
	{
		String cloudId = cloudSite.getId();

		// Check first in the cache of previously authorized clients
		String cacheKey = cloudId + ":" + tenantId;
		if (neutronClientCache.containsKey(cacheKey)) {
			if (! neutronClientCache.get(cacheKey).isExpired()) {
				LOGGER.debug ("Using Cached HEAT Client for " + cacheKey);
				Quantum neutronClient = neutronClientCache.get(cacheKey).getNeutronClient();
				return neutronClient;
			}
			else {
				// Token is expired.  Remove it from cache.
				neutronClientCache.remove(cacheKey);
				LOGGER.debug ("Expired Cached Neutron Client for " + cacheKey);
			}
		}

		// Obtain an MSO token for the tenant from the identity service
		CloudIdentity cloudIdentity = cloudSite.getIdentityService();
		Keystone keystoneTenantClient = new Keystone (cloudIdentity.getKeystoneUrl(cloudId, msoPropID));
		Access access = null;
		try {
			Authentication credentials = cloudIdentity.getAuthentication ();
			OpenStackRequest<Access> request = keystoneTenantClient.tokens().authenticate(credentials).withTenantId(tenantId);
			access = executeAndRecordOpenstackRequest(request);
		}
		catch (OpenStackResponseException e) {
			if (e.getStatus() == 401) {
				// Authentication error.
				String error = "Authentication Failure: tenant=" + tenantId + ",cloud=" + cloudIdentity.getId();
				alarmLogger .sendAlarm("MsoAuthenticationError", MsoAlarmLogger.CRITICAL, error);
				throw new MsoAdapterException(error);
			}
			else {
				MsoException me = keystoneErrorToMsoException(e, "TokenAuth");
				throw me;
			}
		}
		catch (OpenStackConnectException e) {
			// Connection to Openstack failed
			MsoIOException me = new MsoIOException (e.getMessage(), e);
			me.addContext("TokenAuth");
			throw me;
		}
		catch (RuntimeException e) {
			// Catch-all
			MsoException me = runtimeExceptionToMsoException(e, "TokenAuth");
			throw me;
		}

		String region = cloudSite.getRegionId();
		String neutronUrl = null;
		try {
			neutronUrl = KeystoneUtils.findEndpointURL(access.getServiceCatalog(), "network", region, "public");
			if (! neutronUrl.endsWith("/")) {
                neutronUrl += "/v2.0/";
            }
		} catch (RuntimeException e) {
			// This comes back for not found (probably an incorrect region ID)
			String error = "Network service not found: region=" + region + ",cloud=" + cloudIdentity.getId();
			alarmLogger.sendAlarm("MsoConfigurationError", MsoAlarmLogger.CRITICAL, error);
			throw new MsoAdapterException (error, e);
		}
		// This is needed for testing in the MT cloud
		if (cloudId.equals("MT")) {
            neutronUrl = neutronUrl.replace("controller", "mtdnj02bh01wt.bvoip.labs.att.com");
        }

		Quantum neutronClient = new Quantum(neutronUrl);
		neutronClient.token(access.getToken().getId());

		neutronClientCache.put(cacheKey, new NeutronCacheEntry(neutronUrl, access.getToken().getId(), access.getToken().getExpires()));
		LOGGER.debug ("Caching Neutron Client for " + cacheKey);

		return neutronClient;
	}

	/**
	 * Forcibly expire a Neutron client from the cache.  This call is for use by
	 * the KeystoneClient in case where a tenant is deleted.  In that case,
	 * all cached credentials must be purged so that fresh authentication is
	 * done on subsequent calls.
	 * <p>
	 * @param tenantName
	 * @param cloudId
	 */
	public static void expireNeutronClient (String tenantId, String cloudId) {
		String cacheKey = cloudId + ":" + tenantId;
		if (neutronClientCache.containsKey(cacheKey)) {
			neutronClientCache.remove(cacheKey);
			LOGGER.debug ("Deleted Cached Neutron Client for " + cacheKey);
		}
	}


	/*
	 * Find a tenant (or query its existence) by its Name or Id.  Check first against the
	 * ID.  If that fails, then try by name.
	 *
	 * @param adminClient an authenticated Keystone object
	 * @param tenantName the tenant name or ID to query
	 * @return a Tenant object or null if not found
	 */
	public Network findNetworkByNameOrId (Quantum neutronClient, String networkNameOrId)
	{
		if (networkNameOrId == null) {
            return null;
        }

		Network network = findNetworkById(neutronClient, networkNameOrId);

		if (network == null) {
            network = findNetworkByName(neutronClient, networkNameOrId);
        }

		return network;
	}

	/*
	 * Find a network (or query its existence) by its Id.
	 *
	 * @param neutronClient an authenticated Quantum object
	 * @param networkId the network ID to query
	 * @return a Network object or null if not found
	 */
	private static Network findNetworkById (Quantum neutronClient, String networkId)
	{
		if (networkId == null) {
            return null;
        }

		try {
			OpenStackRequest<Network> request = neutronClient.networks().show(networkId);
			Network network = executeAndRecordOpenstackRequest(request);
			return network;
		}
		catch (OpenStackResponseException e) {
			if (e.getStatus() == 404) {
				return null;
			} else {
				LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "OpenStack", "Openstack Error, GET Network By ID (" + networkId + "): " + e, "Openstack", "", MsoLogger.ErrorCode.DataError, "Exception in Openstack");
				throw e;
			}
		}
	}

	/*
	 * Find a network (or query its existence) by its Name.  This method avoids an
	 * initial lookup by ID when it's known that we have the network Name.
	 *
	 * Neutron does not support 'name=*' query parameter for Network query (show).
	 * The only way to query by name is to retrieve all networks and look for the
	 * match.  While inefficient, this capability will be provided as it is needed
	 * by MSO, but should be avoided in favor of ID whenever possible.
	 *
	 * TODO:
	 * Network names are not required to be unique, though MSO will attempt to enforce
	 * uniqueness.  This call probably needs to return an error (instead of returning
	 * the first match).
	 *
	 * @param neutronClient an authenticated Quantum object
	 * @param networkName the network name to query
	 * @return a Network object or null if not found
	 */
	public Network findNetworkByName (Quantum neutronClient, String networkName)
	{
		if (networkName == null) {
            return null;
        }

		try {
			OpenStackRequest<Networks> request = neutronClient.networks().list();
			Networks networks = executeAndRecordOpenstackRequest(request);
			for (Network network : networks.getList()) {
				if (network.getName().equals(networkName)) {
					LOGGER.debug ("Found match on network name: " + networkName);
					return network;
				}
			}
			LOGGER.debug ("findNetworkByName - no match found for " + networkName);
			return null;
		}
		catch (OpenStackResponseException e) {
			if (e.getStatus() == 404) {
				return null;
			} else {
				LOGGER.error (MessageEnum.RA_CONNECTION_EXCEPTION, "OpenStack", "Openstack Error, GET Network By Name (" + networkName + "): " + e, "OpenStack", "", MsoLogger.ErrorCode.DataError, "Exception in OpenStack");
				throw e;
			}
		}
	}


	/*
	 * An entry in the Neutron Client Cache.  It saves the Neutron client object
	 * along with the token expiration.  After this interval, this cache
	 * item will no longer be used.
	 */
	private static class NeutronCacheEntry implements Serializable
	{
		private static final long serialVersionUID = 1L;

		private String neutronUrl;
		private String token;
		private Calendar expires;

		public NeutronCacheEntry (String neutronUrl, String token, Calendar expires) {
			this.neutronUrl = neutronUrl;
			this.token = token;
			this.expires = expires;
		}

		public Quantum getNeutronClient () {
			Quantum neutronClient = new Quantum(neutronUrl);
			neutronClient.token(token);
			return neutronClient;
		}

		public boolean isExpired() {
			if (expires == null) {
                return true;
            }

            return System.currentTimeMillis() > expires.getTimeInMillis();
		}
	}

	/**
	 * Clean up the Neutron client cache to remove expired entries.
	 */
	public static void neutronCacheCleanup () {
		for (String cacheKey : neutronClientCache.keySet()) {
			if (neutronClientCache.get(cacheKey).isExpired()) {
				neutronClientCache.remove(cacheKey);
				LOGGER.debug ("Cleaned Up Cached Neutron Client for " + cacheKey);
			}
		}
	}

	/**
	 * Reset the Neutron client cache.
	 * This may be useful if cached credentials get out of sync.
	 */
	public static void neutronCacheReset () {
		neutronClientCache = new HashMap<String,NeutronCacheEntry>();
	}
}