2  * ============LICENSE_START=======================================================
 
   4  * ================================================================================
 
   5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
 
   6  * Copyright (C) 2017 Amdocs
 
   7  * ================================================================================
 
   8  * Licensed under the Apache License, Version 2.0 (the "License");
 
   9  * you may not use this file except in compliance with the License.
 
  10  * You may obtain a copy of the License at
 
  12  *      http://www.apache.org/licenses/LICENSE-2.0
 
  14  * Unless required by applicable law or agreed to in writing, software
 
  15  * distributed under the License is distributed on an "AS IS" BASIS,
 
  16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
  17  * See the License for the specific language governing permissions and
 
  18  * limitations under the License.
 
  19  * ============LICENSE_END=========================================================
 
  20  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
 
  23 package org.openecomp.appc.adapter.iaas.impl;
 
  25 import java.net.NoRouteToHostException;
 
  26 import java.net.SocketException;
 
  27 import java.util.ArrayList;
 
  28 import java.util.Calendar;
 
  29 import java.util.Date;
 
  30 import java.util.HashMap;
 
  31 import java.util.HashSet;
 
  32 import java.util.List;
 
  34 import java.util.Properties;
 
  36 import java.util.concurrent.locks.Lock;
 
  37 import java.util.concurrent.locks.ReadWriteLock;
 
  38 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
  40 import com.att.cdp.exceptions.ContextConnectionException;
 
  41 import com.att.cdp.exceptions.ZoneException;
 
  42 import com.att.cdp.openstack.util.ExceptionMapper;
 
  43 import com.att.cdp.pal.util.Time;
 
  44 import com.att.cdp.zones.ContextFactory;
 
  45 import com.att.cdp.zones.spi.AbstractService;
 
  46 import com.att.cdp.zones.spi.RequestState;
 
  47 import com.att.cdp.zones.spi.AbstractService.State;
 
  49 import com.woorea.openstack.base.client.OpenStackBaseException;
 
  50 import com.woorea.openstack.base.client.OpenStackClientConnector;
 
  51 import com.woorea.openstack.base.client.OpenStackResponseException;
 
  52 import com.woorea.openstack.base.client.OpenStackSimpleTokenProvider;
 
  53 import com.woorea.openstack.keystone.Keystone;
 
  54 import com.woorea.openstack.keystone.api.TokensResource;
 
  55 import com.woorea.openstack.keystone.model.Access;
 
  56 import com.woorea.openstack.keystone.model.Access.Service;
 
  57 import com.woorea.openstack.keystone.model.Access.Service.Endpoint;
 
  58 import com.woorea.openstack.keystone.model.Authentication;
 
  59 import com.woorea.openstack.keystone.model.Tenant;
 
  60 import com.woorea.openstack.keystone.model.authentication.UsernamePassword;
 
  63  * This class is used to capture and cache the service catalog for a specific OpenStack provider.
 
  65  * This is needed because the way the servers are represented in the ECOMP product is as their fully qualified URL's.
 
  66  * This is very problematic, because we cant identify their region from the URL, URL's change, and we cant identify the
 
  67  * versions of the service implementations. In otherwords, the URL does not provide us enough information.
 
  70  * The zone abstraction layer is designed to detect the versions of the services dynamically, and step up or down to
 
  71  * match those reported versions. In order to do that, we need to know before hand what region we are accessing (since
 
  72  * the supported versions may be different by regions). We will need to authenticate to the identity service in order to
 
  73  * do this, plus we have to duplicate the code supporting proxies and trusted hosts that exists in the abstraction
 
  74  * layer, but that cant be helped.
 
  77  * What we do to circumvent this is connect to the provider using the lowest supported identity api, and read the entire
 
  78  * service catalog into this object. Then, we parse the vm URL to extract the host and port and match that to the
 
  79  * compute services defined in the catalog. When we find a compute service that has the same host name and port,
 
  80  * whatever region that service is supporting is the region for that server.
 
  83  * While we really only need to do this for compute nodes, there is no telling what other situations may arise where the
 
  84  * full service catalog may be needed. Also, there is very little additional cost (additional RAM) associated with
 
  85  * caching the full service catalog since there is no way to list only a portion of it.
 
  88 public class ServiceCatalog {
 
  91      * The service name for the compute service endpoint
 
  93     public static final String COMPUTE_SERVICE = "compute"; //$NON-NLS-1$
 
  96      * The service name for the identity service endpoint
 
  98     public static final String IDENTITY_SERVICE = "identity"; //$NON-NLS-1$
 
 101      * The service name for the compute service endpoint
 
 103     public static final String IMAGE_SERVICE = "image"; //$NON-NLS-1$
 
 106      * The service name for the network service endpoint
 
 108     public static final String NETWORK_SERVICE = "network"; //$NON-NLS-1$
 
 111      * The service name for the orchestration service endpoint
 
 113     public static final String ORCHESTRATION_SERVICE = "orchestration"; //$NON-NLS-1$
 
 116      * The service name for the volume service endpoint
 
 118     public static final String VOLUME_SERVICE = "volume"; //$NON-NLS-1$
 
 121      * The service name for the persistent object service endpoint
 
 123     public static final String OBJECT_SERVICE = "object-store"; //$NON-NLS-1$
 
 126      * The service name for the metering service endpoint
 
 128     public static final String METERING_SERVICE = "metering"; //$NON-NLS-1$
 
 131      * The Openstack Access object that manages the authenticated token and access control
 
 133     private Access access;
 
 136      * The time (local) that the token expires and we need to re-authenticate
 
 138     @SuppressWarnings("unused")
 
 139     private long expiresLocal;
 
 142      * The set of all regions that have been defined
 
 144     private Set<String> regions;
 
 147      * The read/write lock used to protect the cache contents
 
 149     private ReadWriteLock rwLock;
 
 152      * A map of endpoints for each service organized by service type
 
 154     private Map<String /* Service Type */, List<Service.Endpoint>> serviceEndpoints;
 
 157      * A map of service types that are published
 
 159     private Map<String /* Service Type */, Service> serviceTypes;
 
 162      * The tenant that we are accessing
 
 164     private Tenant tenant;
 
 167      * A "token provider" that manages the authentication token that we obtain when logging in
 
 169     private OpenStackSimpleTokenProvider tokenProvider;
 
 171     public static final String CLIENT_CONNECTOR_CLASS = "com.woorea.openstack.connector.JaxRs20Connector";
 
 174      * Create the ServiceCatalog cache and load it from the specified provider
 
 177      *            The identity service URL to connect to
 
 178      * @param tenantIdentifier
 
 179      *            The name or id of the tenant to authenticate with. If the ID is a UUID format (32-character
 
 180      *            hexadecimal string), then the authentication is done using the tenant ID, otherwise it is done using
 
 183      *            The user id to authenticate to the provider
 
 185      *            The password to authenticate to the provider
 
 187      *            Additional properties used to configure the connection, such as proxy and trusted hosts lists
 
 188      * @throws ZoneException
 
 189      * @throws ClassNotFoundException
 
 190      * @throws IllegalAccessException
 
 191      * @throws InstantiationException
 
 193     public ServiceCatalog(String identityURL, String tenantIdentifier, String principal, String credential,
 
 194                           Properties properties) throws ZoneException {
 
 195         rwLock = new ReentrantReadWriteLock();
 
 196         serviceTypes = new HashMap<>();
 
 197         serviceEndpoints = new HashMap<>();
 
 198         regions = new HashSet<>();
 
 200         Class<?> connectorClass;
 
 201         OpenStackClientConnector connector;
 
 203             connectorClass = Class.forName(CLIENT_CONNECTOR_CLASS);
 
 204             connector = (OpenStackClientConnector) connectorClass.newInstance();
 
 205         } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
 
 209         Keystone keystone = new Keystone(identityURL, connector);
 
 211         String proxyHost = properties.getProperty(ContextFactory.PROPERTY_PROXY_HOST);
 
 212         String proxyPort = properties.getProperty(ContextFactory.PROPERTY_PROXY_PORT);
 
 213         String trustedHosts = properties.getProperty(ContextFactory.PROPERTY_TRUSTED_HOSTS, ""); //$NON-NLS-1$
 
 214         if (proxyHost != null && proxyHost.length() > 0) {
 
 215             keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.PROXY_HOST, proxyHost);
 
 216             keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.PROXY_PORT, proxyPort);
 
 218         if (trustedHosts != null) {
 
 219             keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.TRUST_HOST_LIST,
 
 223         Authentication authentication = new UsernamePassword(principal, credential);
 
 224         TokensResource tokens = keystone.tokens();
 
 225         TokensResource.Authenticate authenticate = tokens.authenticate(authentication);
 
 226         if (tenantIdentifier.length() == 32 && tenantIdentifier.matches("[0-9a-fA-F]+")) { //$NON-NLS-1$
 
 227             authenticate = authenticate.withTenantId(tenantIdentifier);
 
 229             authenticate = authenticate.withTenantName(tenantIdentifier);
 
 233          * We have to set up the TrackRequest TLS collection for the ExceptionMapper
 
 236         RequestState.put(RequestState.PROVIDER, "OpenStackProvider");
 
 237         RequestState.put(RequestState.TENANT, tenantIdentifier);
 
 238         RequestState.put(RequestState.PRINCIPAL, principal);
 
 241             access = authenticate.execute();
 
 242             expiresLocal = getLocalExpiration(access);
 
 243             tenant = access.getToken().getTenant();
 
 244             tokenProvider = new OpenStackSimpleTokenProvider(access.getToken().getId());
 
 245             keystone.setTokenProvider(tokenProvider);
 
 246             parseServiceCatalog(access.getServiceCatalog());
 
 247         } catch (OpenStackBaseException e) {
 
 248             ExceptionMapper.mapException(e);
 
 249         } catch (Exception ex) {
 
 250             throw new ContextConnectionException(ex.getMessage());
 
 255      * Returns the list of service endpoints for the published service type
 
 258      *            The service type to obtain the endpoints for
 
 259      * @return The list of endpoints for the service type, or null if none exist
 
 261     public List<Service.Endpoint> getEndpoints(String serviceType) {
 
 262         Lock readLock = rwLock.readLock();
 
 265             return serviceEndpoints.get(serviceType);
 
 272      * Computes the local time when the access token will expire, after which we will need to re-login to access the
 
 276      *            The access key used to access the provider
 
 277      * @return The local time the key expires
 
 279     private static long getLocalExpiration(Access accessKey) {
 
 280         Date now = Time.getCurrentUTCDate();
 
 281         if (accessKey != null && accessKey.getToken() != null) {
 
 282             Calendar issued = accessKey.getToken().getIssued_at();
 
 283             Calendar expires = accessKey.getToken().getExpires();
 
 284             if (issued != null && expires != null) {
 
 285                 long tokenLife = expires.getTimeInMillis() - issued.getTimeInMillis();
 
 286                 return now.getTime() + tokenLife;
 
 289         return now.getTime();
 
 293      * @return The set of all regions that are defined
 
 295     public Set<String> getRegions() {
 
 296         Lock readLock = rwLock.readLock();
 
 306      * @return A list of service types that are published
 
 308     public List<String> getServiceTypes() {
 
 309         Lock readLock = rwLock.readLock();
 
 312             ArrayList<String> result = new ArrayList<>();
 
 313             result.addAll(serviceTypes.keySet());
 
 321      * @return The tenant id
 
 323     public String getTenantId() {
 
 324         Lock readLock = rwLock.readLock();
 
 327             return tenant.getId();
 
 334      * @return The tenant name
 
 336     public String getTenantName() {
 
 337         Lock readLock = rwLock.readLock();
 
 340             return tenant.getName();
 
 347      * Returns an indication if the specified service type is published by this provider
 
 350      *            The service type to check for
 
 351      * @return True if a service of that type is published
 
 353     public boolean isServicePublished(String serviceType) {
 
 354         Lock readLock = rwLock.readLock();
 
 357             return serviceTypes.containsKey(serviceType);
 
 364      * Parses the service catalog and caches the results
 
 367      *            The list of services published by this provider
 
 369     private void parseServiceCatalog(List<Service> services) {
 
 370         Lock lock = rwLock.writeLock();
 
 373             serviceTypes.clear();
 
 374             serviceEndpoints.clear();
 
 377             for (Service service : services) {
 
 378                 String type = service.getType();
 
 379                 serviceTypes.put(type, service);
 
 381                 List<Service.Endpoint> endpoints = service.getEndpoints();
 
 382                 for (Service.Endpoint endpoint : endpoints) {
 
 383                     List<Service.Endpoint> endpointList = serviceEndpoints.get(type);
 
 384                     if (endpointList == null) {
 
 385                         endpointList = new ArrayList<>();
 
 386                         serviceEndpoints.put(type, endpointList);
 
 388                     endpointList.add(endpoint);
 
 390                     String region = endpoint.getRegion();
 
 391                     if (!regions.contains(region)) {
 
 402      * This method is used to provide a diagnostic listing of the service catalog
 
 404      * @see java.lang.Object#toString()
 
 407     public String toString() {
 
 409         StringBuilder builder = new StringBuilder();
 
 410         Lock lock = rwLock.readLock();
 
 413             builder.append(String.format("Service Catalog: tenant %s, id[%s], description[%s]\n", tenant.getName(), //$NON-NLS-1$
 
 414                 tenant.getId(), tenant.getDescription()));
 
 415             if (regions != null && !regions.isEmpty()) {
 
 416                 builder.append(String.format("%d regions:\n", regions.size())); //$NON-NLS-1$
 
 417                 for (String region : regions) {
 
 418                     builder.append("\t" + region + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
 
 421             builder.append(String.format("%d services:\n", serviceEndpoints.size())); //$NON-NLS-1$
 
 422             for (String serviceType : serviceEndpoints.keySet()) {
 
 423                 List<Endpoint> endpoints = serviceEndpoints.get(serviceType);
 
 424                 Service service = serviceTypes.get(serviceType);
 
 426                 builder.append(String.format("\t%s [%s] - %d endpoints\n", service.getType(), service.getName(), //$NON-NLS-1$
 
 428                 for (Endpoint endpoint : endpoints) {
 
 429                     builder.append(String.format("\t\tRegion [%s], public URL [%s]\n", endpoint.getRegion(), //$NON-NLS-1$
 
 430                         endpoint.getPublicURL()));
 
 437         return builder.toString();
 
 441      * Initializes the request state for the current requested service.
 
 443      * This method is used to track requests made to the various service implementations and to provide additional
 
 444      * information for diagnostic purposes. The <code>RequestState</code> class stores the state in thread-local storage
 
 445      * and is available to all code on that thread.
 
 448      * This method first obtains the stack trace and scans the stack backward for the call to this method. It then backs
 
 449      * up one more call and assumes that method is the request that we are "tracking".
 
 453      *            A variable argument list of additional state values that the caller wants to add to the request state
 
 454      *            thread-local object to track the context.
 
 456     protected void trackRequest(State... states) {
 
 457         RequestState.clear();
 
 459         for (State state : states) {
 
 460             RequestState.put(state.getName(), state.getValue());
 
 463         Thread currentThread = Thread.currentThread();
 
 464         StackTraceElement[] stack = currentThread.getStackTrace();
 
 465         if (stack != null && stack.length > 0) {
 
 467             StackTraceElement element = null;
 
 468             for (; index < stack.length; index++) {
 
 469                 element = stack[index];
 
 470                 if ("trackRequest".equals(element.getMethodName())) {  //$NON-NLS-1$
 
 476             if (index < stack.length) {
 
 477                 element = stack[index];
 
 478                 RequestState.put(RequestState.METHOD, element.getMethodName());
 
 479                 RequestState.put(RequestState.CLASS, element.getClassName());
 
 480                 RequestState.put(RequestState.LINE_NUMBER, Integer.toString(element.getLineNumber()));
 
 481                 RequestState.put(RequestState.THREAD, currentThread.getName());
 
 482                 // RequestState.put(RequestState.PROVIDER, context.getProvider().getName());
 
 483                 // RequestState.put(RequestState.TENANT, context.getTenantName());
 
 484                 // RequestState.put(RequestState.PRINCIPAL, context.getPrincipal());