Merge of new rebased code
[appc.git] / appc-adapters / appc-iaas-adapter / appc-iaas-adapter-bundle / src / main / java / org / openecomp / appc / adapter / iaas / impl / ServiceCatalog.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * openECOMP : APP-C
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights
6  *                                              reserved.
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
11  * 
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  * 
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  */
21
22 package org.openecomp.appc.adapter.iaas.impl;
23
24 import java.net.NoRouteToHostException;
25 import java.net.SocketException;
26 import java.util.ArrayList;
27 import java.util.Calendar;
28 import java.util.Date;
29 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Properties;
34 import java.util.Set;
35 import java.util.concurrent.locks.Lock;
36 import java.util.concurrent.locks.ReadWriteLock;
37 import java.util.concurrent.locks.ReentrantReadWriteLock;
38
39 import com.att.cdp.exceptions.ContextConnectionException;
40 import com.att.cdp.exceptions.ZoneException;
41 import com.att.cdp.openstack.util.ExceptionMapper;
42 import com.att.cdp.pal.util.Time;
43 import com.att.cdp.zones.ContextFactory;
44 import com.att.cdp.zones.spi.AbstractService;
45 import com.att.cdp.zones.spi.RequestState;
46 import com.att.cdp.zones.spi.AbstractService.State;
47
48 import com.woorea.openstack.base.client.OpenStackBaseException;
49 import com.woorea.openstack.base.client.OpenStackClientConnector;
50 import com.woorea.openstack.base.client.OpenStackResponseException;
51 import com.woorea.openstack.base.client.OpenStackSimpleTokenProvider;
52 import com.woorea.openstack.keystone.Keystone;
53 import com.woorea.openstack.keystone.api.TokensResource;
54 import com.woorea.openstack.keystone.model.Access;
55 import com.woorea.openstack.keystone.model.Access.Service;
56 import com.woorea.openstack.keystone.model.Access.Service.Endpoint;
57 import com.woorea.openstack.keystone.model.Authentication;
58 import com.woorea.openstack.keystone.model.Tenant;
59 import com.woorea.openstack.keystone.model.authentication.UsernamePassword;
60
61 /**
62  * This class is used to capture and cache the service catalog for a specific OpenStack provider.
63  * <p>
64  * This is needed because the way the servers are represented in the ECOMP product is as their fully qualified URL's.
65  * This is very problematic, because we cant identify their region from the URL, URL's change, and we cant identify the
66  * versions of the service implementations. In otherwords, the URL does not provide us enough information.
67  * </p>
68  * <p>
69  * The zone abstraction layer is designed to detect the versions of the services dynamically, and step up or down to
70  * match those reported versions. In order to do that, we need to know before hand what region we are accessing (since
71  * the supported versions may be different by regions). We will need to authenticate to the identity service in order to
72  * do this, plus we have to duplicate the code supporting proxies and trusted hosts that exists in the abstraction
73  * layer, but that cant be helped.
74  * </p>
75  * <p>
76  * What we do to circumvent this is connect to the provider using the lowest supported identity api, and read the entire
77  * service catalog into this object. Then, we parse the vm URL to extract the host and port and match that to the
78  * compute services defined in the catalog. When we find a compute service that has the same host name and port,
79  * whatever region that service is supporting is the region for that server.
80  * </p>
81  * <p>
82  * While we really only need to do this for compute nodes, there is no telling what other situations may arise where the
83  * full service catalog may be needed. Also, there is very little additional cost (additional RAM) associated with
84  * caching the full service catalog since there is no way to list only a portion of it.
85  * </p>
86  */
87 public class ServiceCatalog {
88
89     /**
90      * The service name for the compute service endpoint
91      */
92     public static final String COMPUTE_SERVICE = "compute"; //$NON-NLS-1$
93
94     /**
95      * The service name for the identity service endpoint
96      */
97     public static final String IDENTITY_SERVICE = "identity"; //$NON-NLS-1$
98
99     /**
100      * The service name for the compute service endpoint
101      */
102     public static final String IMAGE_SERVICE = "image"; //$NON-NLS-1$
103
104     /**
105      * The service name for the network service endpoint
106      */
107     public static final String NETWORK_SERVICE = "network"; //$NON-NLS-1$
108
109     /**
110      * The service name for the orchestration service endpoint
111      */
112     public static final String ORCHESTRATION_SERVICE = "orchestration"; //$NON-NLS-1$
113
114     /**
115      * The service name for the volume service endpoint
116      */
117     public static final String VOLUME_SERVICE = "volume"; //$NON-NLS-1$
118
119     /**
120      * The service name for the persistent object service endpoint
121      */
122     public static final String OBJECT_SERVICE = "object-store"; //$NON-NLS-1$
123
124     /**
125      * The service name for the metering service endpoint
126      */
127     public static final String METERING_SERVICE = "metering"; //$NON-NLS-1$
128
129     /**
130      * The Openstack Access object that manages the authenticated token and access control
131      */
132     private Access access;
133
134     /**
135      * The time (local) that the token expires and we need to re-authenticate
136      */
137     @SuppressWarnings("unused")
138     private long expiresLocal;
139
140     /**
141      * The set of all regions that have been defined
142      */
143     private Set<String> regions;
144
145     /**
146      * The read/write lock used to protect the cache contents
147      */
148     private ReadWriteLock rwLock;
149
150     /**
151      * A map of endpoints for each service organized by service type
152      */
153     private Map<String /* Service Type */, List<Service.Endpoint>> serviceEndpoints;
154
155     /**
156      * A map of service types that are published
157      */
158     private Map<String /* Service Type */, Service> serviceTypes;
159
160     /**
161      * The tenant that we are accessing
162      */
163     private Tenant tenant;
164
165     /**
166      * A "token provider" that manages the authentication token that we obtain when logging in
167      */
168     private OpenStackSimpleTokenProvider tokenProvider;
169
170     public static final String CLIENT_CONNECTOR_CLASS = "com.woorea.openstack.connector.JaxRs20Connector";
171
172     /**
173      * Create the ServiceCatalog cache and load it from the specified provider
174      * 
175      * @param identityURL
176      *            The identity service URL to connect to
177      * @param tenantIdentifier
178      *            The name or id of the tenant to authenticate with. If the ID is a UUID format (32-character
179      *            hexadecimal string), then the authentication is done using the tenant ID, otherwise it is done using
180      *            the name.
181      * @param principal
182      *            The user id to authenticate to the provider
183      * @param credential
184      *            The password to authenticate to the provider
185      * @param properties
186      *            Additional properties used to configure the connection, such as proxy and trusted hosts lists
187      * @throws ZoneException
188      * @throws ClassNotFoundException
189      * @throws IllegalAccessException
190      * @throws InstantiationException
191      */
192     public ServiceCatalog(String identityURL, String tenantIdentifier, String principal, String credential,
193                           Properties properties) throws ZoneException {
194         rwLock = new ReentrantReadWriteLock();
195         serviceTypes = new HashMap<>();
196         serviceEndpoints = new HashMap<>();
197         regions = new HashSet<>();
198
199         Class<?> connectorClass;
200         OpenStackClientConnector connector;
201         try {
202             connectorClass = Class.forName(CLIENT_CONNECTOR_CLASS);
203             connector = (OpenStackClientConnector) connectorClass.newInstance();
204         } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
205             e.printStackTrace();
206             return;
207         }
208         Keystone keystone = new Keystone(identityURL, connector);
209
210         String proxyHost = properties.getProperty(ContextFactory.PROPERTY_PROXY_HOST);
211         String proxyPort = properties.getProperty(ContextFactory.PROPERTY_PROXY_PORT);
212         String trustedHosts = properties.getProperty(ContextFactory.PROPERTY_TRUSTED_HOSTS, ""); //$NON-NLS-1$
213         if (proxyHost != null && proxyHost.length() > 0) {
214             keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.PROXY_HOST, proxyHost);
215             keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.PROXY_PORT, proxyPort);
216         }
217         if (trustedHosts != null) {
218             keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.TRUST_HOST_LIST,
219                 trustedHosts);
220         }
221
222         Authentication authentication = new UsernamePassword(principal, credential);
223         TokensResource tokens = keystone.tokens();
224         TokensResource.Authenticate authenticate = tokens.authenticate(authentication);
225         if (tenantIdentifier.length() == 32 && tenantIdentifier.matches("[0-9a-fA-F]+")) { //$NON-NLS-1$
226             authenticate = authenticate.withTenantId(tenantIdentifier);
227         } else {
228             authenticate = authenticate.withTenantName(tenantIdentifier);
229         }
230
231         /*
232          * We have to set up the TrackRequest TLS collection for the ExceptionMapper
233          */
234         trackRequest();
235         RequestState.put(RequestState.PROVIDER, "OpenStackProvider");
236         RequestState.put(RequestState.TENANT, tenantIdentifier);
237         RequestState.put(RequestState.PRINCIPAL, principal);
238
239         try {
240             access = authenticate.execute();
241             expiresLocal = getLocalExpiration(access);
242             tenant = access.getToken().getTenant();
243             tokenProvider = new OpenStackSimpleTokenProvider(access.getToken().getId());
244             keystone.setTokenProvider(tokenProvider);
245             parseServiceCatalog(access.getServiceCatalog());
246         } catch (OpenStackBaseException e) {
247             ExceptionMapper.mapException(e);
248         } catch (Exception ex) {
249             throw new ContextConnectionException(ex.getMessage());
250         }
251     }
252
253     /**
254      * Returns the list of service endpoints for the published service type
255      * 
256      * @param serviceType
257      *            The service type to obtain the endpoints for
258      * @return The list of endpoints for the service type, or null if none exist
259      */
260     public List<Service.Endpoint> getEndpoints(String serviceType) {
261         Lock readLock = rwLock.readLock();
262         readLock.lock();
263         try {
264             return serviceEndpoints.get(serviceType);
265         } finally {
266             readLock.unlock();
267         }
268     }
269
270     /**
271      * Computes the local time when the access token will expire, after which we will need to re-login to access the
272      * provider.
273      * 
274      * @param accessKey
275      *            The access key used to access the provider
276      * @return The local time the key expires
277      */
278     private static long getLocalExpiration(Access accessKey) {
279         Date now = Time.getCurrentUTCDate();
280         if (accessKey != null && accessKey.getToken() != null) {
281             Calendar issued = accessKey.getToken().getIssued_at();
282             Calendar expires = accessKey.getToken().getExpires();
283             if (issued != null && expires != null) {
284                 long tokenLife = expires.getTimeInMillis() - issued.getTimeInMillis();
285                 return now.getTime() + tokenLife;
286             }
287         }
288         return now.getTime();
289     }
290
291     /**
292      * @return The set of all regions that are defined
293      */
294     public Set<String> getRegions() {
295         Lock readLock = rwLock.readLock();
296         readLock.lock();
297         try {
298             return regions;
299         } finally {
300             readLock.unlock();
301         }
302     }
303
304     /**
305      * @return A list of service types that are published
306      */
307     public List<String> getServiceTypes() {
308         Lock readLock = rwLock.readLock();
309         readLock.lock();
310         try {
311             ArrayList<String> result = new ArrayList<>();
312             result.addAll(serviceTypes.keySet());
313             return result;
314         } finally {
315             readLock.unlock();
316         }
317     }
318
319     /**
320      * @return The tenant id
321      */
322     public String getTenantId() {
323         Lock readLock = rwLock.readLock();
324         readLock.lock();
325         try {
326             return tenant.getId();
327         } finally {
328             readLock.unlock();
329         }
330     }
331
332     /**
333      * @return The tenant name
334      */
335     public String getTenantName() {
336         Lock readLock = rwLock.readLock();
337         readLock.lock();
338         try {
339             return tenant.getName();
340         } finally {
341             readLock.unlock();
342         }
343     }
344
345     /**
346      * Returns an indication if the specified service type is published by this provider
347      * 
348      * @param serviceType
349      *            The service type to check for
350      * @return True if a service of that type is published
351      */
352     public boolean isServicePublished(String serviceType) {
353         Lock readLock = rwLock.readLock();
354         readLock.lock();
355         try {
356             return serviceTypes.containsKey(serviceType);
357         } finally {
358             readLock.unlock();
359         }
360     }
361
362     /**
363      * Parses the service catalog and caches the results
364      * 
365      * @param services
366      *            The list of services published by this provider
367      */
368     private void parseServiceCatalog(List<Service> services) {
369         Lock lock = rwLock.writeLock();
370         lock.lock();
371         try {
372             serviceTypes.clear();
373             serviceEndpoints.clear();
374             regions.clear();
375
376             for (Service service : services) {
377                 String type = service.getType();
378                 serviceTypes.put(type, service);
379
380                 List<Service.Endpoint> endpoints = service.getEndpoints();
381                 for (Service.Endpoint endpoint : endpoints) {
382                     List<Service.Endpoint> endpointList = serviceEndpoints.get(type);
383                     if (endpointList == null) {
384                         endpointList = new ArrayList<>();
385                         serviceEndpoints.put(type, endpointList);
386                     }
387                     endpointList.add(endpoint);
388
389                     String region = endpoint.getRegion();
390                     if (!regions.contains(region)) {
391                         regions.add(region);
392                     }
393                 }
394             }
395         } finally {
396             lock.unlock();
397         }
398     }
399
400     /**
401      * This method is used to provide a diagnostic listing of the service catalog
402      * 
403      * @see java.lang.Object#toString()
404      */
405     @Override
406     public String toString() {
407
408         StringBuilder builder = new StringBuilder();
409         Lock lock = rwLock.readLock();
410         lock.lock();
411         try {
412             builder.append(String.format("Service Catalog: tenant %s, id[%s], description[%s]\n", tenant.getName(), //$NON-NLS-1$
413                 tenant.getId(), tenant.getDescription()));
414             if (regions != null && !regions.isEmpty()) {
415                 builder.append(String.format("%d regions:\n", regions.size())); //$NON-NLS-1$
416                 for (String region : regions) {
417                     builder.append("\t" + region + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
418                 }
419             }
420             builder.append(String.format("%d services:\n", serviceEndpoints.size())); //$NON-NLS-1$
421             for (String serviceType : serviceEndpoints.keySet()) {
422                 List<Endpoint> endpoints = serviceEndpoints.get(serviceType);
423                 Service service = serviceTypes.get(serviceType);
424
425                 builder.append(String.format("\t%s [%s] - %d endpoints\n", service.getType(), service.getName(), //$NON-NLS-1$
426                     endpoints.size()));
427                 for (Endpoint endpoint : endpoints) {
428                     builder.append(String.format("\t\tRegion [%s], public URL [%s]\n", endpoint.getRegion(), //$NON-NLS-1$
429                         endpoint.getPublicURL()));
430                 }
431             }
432         } finally {
433             lock.unlock();
434         }
435
436         return builder.toString();
437     }
438
439     /**
440      * Initializes the request state for the current requested service.
441      * <p>
442      * This method is used to track requests made to the various service implementations and to provide additional
443      * information for diagnostic purposes. The <code>RequestState</code> class stores the state in thread-local storage
444      * and is available to all code on that thread.
445      * </p>
446      * <p>
447      * This method first obtains the stack trace and scans the stack backward for the call to this method. It then backs
448      * up one more call and assumes that method is the request that we are "tracking".
449      * </p>
450      * 
451      * @param states
452      *            A variable argument list of additional state values that the caller wants to add to the request state
453      *            thread-local object to track the context.
454      */
455     protected void trackRequest(State... states) {
456         RequestState.clear();
457
458         for (State state : states) {
459             RequestState.put(state.getName(), state.getValue());
460         }
461
462         Thread currentThread = Thread.currentThread();
463         StackTraceElement[] stack = currentThread.getStackTrace();
464         if (stack != null && stack.length > 0) {
465             int index = 0;
466             StackTraceElement element = null;
467             for (; index < stack.length; index++) {
468                 element = stack[index];
469                 if ("trackRequest".equals(element.getMethodName())) {  //$NON-NLS-1$
470                     break;
471                 }
472             }
473             index++;
474
475             if (index < stack.length) {
476                 element = stack[index];
477                 RequestState.put(RequestState.METHOD, element.getMethodName());
478                 RequestState.put(RequestState.CLASS, element.getClassName());
479                 RequestState.put(RequestState.LINE_NUMBER, Integer.toString(element.getLineNumber()));
480                 RequestState.put(RequestState.THREAD, currentThread.getName());
481                 // RequestState.put(RequestState.PROVIDER, context.getProvider().getName());
482                 // RequestState.put(RequestState.TENANT, context.getTenantName());
483                 // RequestState.put(RequestState.PRINCIPAL, context.getPrincipal());
484             }
485         }
486     }
487 }