2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Copyright (C) 2017 Amdocs
8 * =============================================================================
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
21 * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22 * ============LICENSE_END=========================================================
25 package org.openecomp.appc.adapter.iaas.impl;
27 import java.net.NoRouteToHostException;
28 import java.net.SocketException;
29 import java.util.ArrayList;
30 import java.util.Calendar;
31 import java.util.Date;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.List;
36 import java.util.Properties;
38 import java.util.concurrent.locks.Lock;
39 import java.util.concurrent.locks.ReadWriteLock;
40 import java.util.concurrent.locks.ReentrantReadWriteLock;
42 import com.att.cdp.exceptions.ContextConnectionException;
43 import com.att.cdp.exceptions.ZoneException;
44 import com.att.cdp.openstack.util.ExceptionMapper;
45 import com.att.cdp.pal.util.Time;
46 import com.att.cdp.zones.ContextFactory;
47 import com.att.cdp.zones.spi.AbstractService;
48 import com.att.cdp.zones.spi.RequestState;
49 import com.att.cdp.zones.spi.AbstractService.State;
51 import com.woorea.openstack.base.client.OpenStackBaseException;
52 import com.woorea.openstack.base.client.OpenStackClientConnector;
53 import com.woorea.openstack.base.client.OpenStackResponseException;
54 import com.woorea.openstack.base.client.OpenStackSimpleTokenProvider;
55 import com.woorea.openstack.keystone.Keystone;
56 import com.woorea.openstack.keystone.api.TokensResource;
57 import com.woorea.openstack.keystone.model.Access;
58 import com.woorea.openstack.keystone.model.Access.Service;
59 import com.woorea.openstack.keystone.model.Access.Service.Endpoint;
60 import com.woorea.openstack.keystone.model.Authentication;
61 import com.woorea.openstack.keystone.model.Tenant;
62 import com.woorea.openstack.keystone.model.authentication.UsernamePassword;
65 * This class is used to capture and cache the service catalog for a specific OpenStack provider.
67 * This is needed because the way the servers are represented in the ECOMP product is as their fully qualified URL's.
68 * This is very problematic, because we cant identify their region from the URL, URL's change, and we cant identify the
69 * versions of the service implementations. In otherwords, the URL does not provide us enough information.
72 * The zone abstraction layer is designed to detect the versions of the services dynamically, and step up or down to
73 * match those reported versions. In order to do that, we need to know before hand what region we are accessing (since
74 * the supported versions may be different by regions). We will need to authenticate to the identity service in order to
75 * do this, plus we have to duplicate the code supporting proxies and trusted hosts that exists in the abstraction
76 * layer, but that cant be helped.
79 * What we do to circumvent this is connect to the provider using the lowest supported identity api, and read the entire
80 * service catalog into this object. Then, we parse the vm URL to extract the host and port and match that to the
81 * compute services defined in the catalog. When we find a compute service that has the same host name and port,
82 * whatever region that service is supporting is the region for that server.
85 * While we really only need to do this for compute nodes, there is no telling what other situations may arise where the
86 * full service catalog may be needed. Also, there is very little additional cost (additional RAM) associated with
87 * caching the full service catalog since there is no way to list only a portion of it.
90 public class ServiceCatalog {
93 * The service name for the compute service endpoint
95 public static final String COMPUTE_SERVICE = "compute"; //$NON-NLS-1$
98 * The service name for the identity service endpoint
100 public static final String IDENTITY_SERVICE = "identity"; //$NON-NLS-1$
103 * The service name for the compute service endpoint
105 public static final String IMAGE_SERVICE = "image"; //$NON-NLS-1$
108 * The service name for the network service endpoint
110 public static final String NETWORK_SERVICE = "network"; //$NON-NLS-1$
113 * The service name for the orchestration service endpoint
115 public static final String ORCHESTRATION_SERVICE = "orchestration"; //$NON-NLS-1$
118 * The service name for the volume service endpoint
120 public static final String VOLUME_SERVICE = "volume"; //$NON-NLS-1$
123 * The service name for the persistent object service endpoint
125 public static final String OBJECT_SERVICE = "object-store"; //$NON-NLS-1$
128 * The service name for the metering service endpoint
130 public static final String METERING_SERVICE = "metering"; //$NON-NLS-1$
133 * The Openstack Access object that manages the authenticated token and access control
135 private Access access;
138 * The time (local) that the token expires and we need to re-authenticate
140 @SuppressWarnings("unused")
141 private long expiresLocal;
144 * The set of all regions that have been defined
146 private Set<String> regions;
149 * The read/write lock used to protect the cache contents
151 private ReadWriteLock rwLock;
154 * A map of endpoints for each service organized by service type
156 private Map<String /* Service Type */, List<Service.Endpoint>> serviceEndpoints;
159 * A map of service types that are published
161 private Map<String /* Service Type */, Service> serviceTypes;
164 * The tenant that we are accessing
166 private Tenant tenant;
169 * A "token provider" that manages the authentication token that we obtain when logging in
171 private OpenStackSimpleTokenProvider tokenProvider;
173 public static final String CLIENT_CONNECTOR_CLASS = "com.woorea.openstack.connector.JaxRs20Connector";
176 * Create the ServiceCatalog cache and load it from the specified provider
179 * The identity service URL to connect to
180 * @param tenantIdentifier
181 * The name or id of the tenant to authenticate with. If the ID is a UUID format (32-character
182 * hexadecimal string), then the authentication is done using the tenant ID, otherwise it is done using
185 * The user id to authenticate to the provider
187 * The password to authenticate to the provider
189 * Additional properties used to configure the connection, such as proxy and trusted hosts lists
190 * @throws ZoneException
191 * @throws ClassNotFoundException
192 * @throws IllegalAccessException
193 * @throws InstantiationException
195 public ServiceCatalog(String identityURL, String tenantIdentifier, String principal, String credential,
196 Properties properties) throws ZoneException {
197 rwLock = new ReentrantReadWriteLock();
198 serviceTypes = new HashMap<>();
199 serviceEndpoints = new HashMap<>();
200 regions = new HashSet<>();
202 Class<?> connectorClass;
203 OpenStackClientConnector connector;
205 connectorClass = Class.forName(CLIENT_CONNECTOR_CLASS);
206 connector = (OpenStackClientConnector) connectorClass.newInstance();
207 } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
211 Keystone keystone = new Keystone(identityURL, connector);
213 String proxyHost = properties.getProperty(ContextFactory.PROPERTY_PROXY_HOST);
214 String proxyPort = properties.getProperty(ContextFactory.PROPERTY_PROXY_PORT);
215 String trustedHosts = properties.getProperty(ContextFactory.PROPERTY_TRUSTED_HOSTS, ""); //$NON-NLS-1$
216 if (proxyHost != null && proxyHost.length() > 0) {
217 keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.PROXY_HOST, proxyHost);
218 keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.PROXY_PORT, proxyPort);
220 if (trustedHosts != null) {
221 keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.TRUST_HOST_LIST,
225 Authentication authentication = new UsernamePassword(principal, credential);
226 TokensResource tokens = keystone.tokens();
227 TokensResource.Authenticate authenticate = tokens.authenticate(authentication);
228 if (tenantIdentifier.length() == 32 && tenantIdentifier.matches("[0-9a-fA-F]+")) { //$NON-NLS-1$
229 authenticate = authenticate.withTenantId(tenantIdentifier);
231 authenticate = authenticate.withTenantName(tenantIdentifier);
235 * We have to set up the TrackRequest TLS collection for the ExceptionMapper
238 RequestState.put(RequestState.PROVIDER, "OpenStackProvider");
239 RequestState.put(RequestState.TENANT, tenantIdentifier);
240 RequestState.put(RequestState.PRINCIPAL, principal);
243 access = authenticate.execute();
244 expiresLocal = getLocalExpiration(access);
245 tenant = access.getToken().getTenant();
246 tokenProvider = new OpenStackSimpleTokenProvider(access.getToken().getId());
247 keystone.setTokenProvider(tokenProvider);
248 parseServiceCatalog(access.getServiceCatalog());
249 } catch (OpenStackBaseException e) {
250 ExceptionMapper.mapException(e);
251 } catch (Exception ex) {
252 throw new ContextConnectionException(ex.getMessage());
257 * Returns the list of service endpoints for the published service type
260 * The service type to obtain the endpoints for
261 * @return The list of endpoints for the service type, or null if none exist
263 public List<Service.Endpoint> getEndpoints(String serviceType) {
264 Lock readLock = rwLock.readLock();
267 return serviceEndpoints.get(serviceType);
274 * Computes the local time when the access token will expire, after which we will need to re-login to access the
278 * The access key used to access the provider
279 * @return The local time the key expires
281 private static long getLocalExpiration(Access accessKey) {
282 Date now = Time.getCurrentUTCDate();
283 if (accessKey != null && accessKey.getToken() != null) {
284 Calendar issued = accessKey.getToken().getIssued_at();
285 Calendar expires = accessKey.getToken().getExpires();
286 if (issued != null && expires != null) {
287 long tokenLife = expires.getTimeInMillis() - issued.getTimeInMillis();
288 return now.getTime() + tokenLife;
291 return now.getTime();
295 * @return The set of all regions that are defined
297 public Set<String> getRegions() {
298 Lock readLock = rwLock.readLock();
308 * @return A list of service types that are published
310 public List<String> getServiceTypes() {
311 Lock readLock = rwLock.readLock();
314 ArrayList<String> result = new ArrayList<>();
315 result.addAll(serviceTypes.keySet());
323 * @return The tenant id
325 public String getTenantId() {
326 Lock readLock = rwLock.readLock();
329 return tenant.getId();
336 * @return The tenant name
338 public String getTenantName() {
339 Lock readLock = rwLock.readLock();
342 return tenant.getName();
349 * Returns an indication if the specified service type is published by this provider
352 * The service type to check for
353 * @return True if a service of that type is published
355 public boolean isServicePublished(String serviceType) {
356 Lock readLock = rwLock.readLock();
359 return serviceTypes.containsKey(serviceType);
366 * Parses the service catalog and caches the results
369 * The list of services published by this provider
371 private void parseServiceCatalog(List<Service> services) {
372 Lock lock = rwLock.writeLock();
375 serviceTypes.clear();
376 serviceEndpoints.clear();
379 for (Service service : services) {
380 String type = service.getType();
381 serviceTypes.put(type, service);
383 List<Service.Endpoint> endpoints = service.getEndpoints();
384 for (Service.Endpoint endpoint : endpoints) {
385 List<Service.Endpoint> endpointList = serviceEndpoints.get(type);
386 if (endpointList == null) {
387 endpointList = new ArrayList<>();
388 serviceEndpoints.put(type, endpointList);
390 endpointList.add(endpoint);
392 String region = endpoint.getRegion();
393 if (!regions.contains(region)) {
404 * This method is used to provide a diagnostic listing of the service catalog
406 * @see java.lang.Object#toString()
409 public String toString() {
411 StringBuilder builder = new StringBuilder();
412 Lock lock = rwLock.readLock();
415 builder.append(String.format("Service Catalog: tenant %s, id[%s], description[%s]\n", tenant.getName(), //$NON-NLS-1$
416 tenant.getId(), tenant.getDescription()));
417 if (regions != null && !regions.isEmpty()) {
418 builder.append(String.format("%d regions:\n", regions.size())); //$NON-NLS-1$
419 for (String region : regions) {
420 builder.append("\t" + region + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
423 builder.append(String.format("%d services:\n", serviceEndpoints.size())); //$NON-NLS-1$
424 for (String serviceType : serviceEndpoints.keySet()) {
425 List<Endpoint> endpoints = serviceEndpoints.get(serviceType);
426 Service service = serviceTypes.get(serviceType);
428 builder.append(String.format("\t%s [%s] - %d endpoints\n", service.getType(), service.getName(), //$NON-NLS-1$
430 for (Endpoint endpoint : endpoints) {
431 builder.append(String.format("\t\tRegion [%s], public URL [%s]\n", endpoint.getRegion(), //$NON-NLS-1$
432 endpoint.getPublicURL()));
439 return builder.toString();
443 * Initializes the request state for the current requested service.
445 * This method is used to track requests made to the various service implementations and to provide additional
446 * information for diagnostic purposes. The <code>RequestState</code> class stores the state in thread-local storage
447 * and is available to all code on that thread.
450 * This method first obtains the stack trace and scans the stack backward for the call to this method. It then backs
451 * up one more call and assumes that method is the request that we are "tracking".
455 * A variable argument list of additional state values that the caller wants to add to the request state
456 * thread-local object to track the context.
458 protected void trackRequest(State... states) {
459 RequestState.clear();
461 for (State state : states) {
462 RequestState.put(state.getName(), state.getValue());
465 Thread currentThread = Thread.currentThread();
466 StackTraceElement[] stack = currentThread.getStackTrace();
467 if (stack != null && stack.length > 0) {
469 StackTraceElement element = null;
470 for (; index < stack.length; index++) {
471 element = stack[index];
472 if ("trackRequest".equals(element.getMethodName())) { //$NON-NLS-1$
478 if (index < stack.length) {
479 element = stack[index];
480 RequestState.put(RequestState.METHOD, element.getMethodName());
481 RequestState.put(RequestState.CLASS, element.getClassName());
482 RequestState.put(RequestState.LINE_NUMBER, Integer.toString(element.getLineNumber()));
483 RequestState.put(RequestState.THREAD, currentThread.getName());
484 // RequestState.put(RequestState.PROVIDER, context.getProvider().getName());
485 // RequestState.put(RequestState.TENANT, context.getTenantName());
486 // RequestState.put(RequestState.PRINCIPAL, context.getPrincipal());