Moving all files to root directory
[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.sun.jersey.api.client.ClientHandlerException;
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;
61
62 /**
63  * This class is used to capture and cache the service catalog for a specific OpenStack provider.
64  * <p>
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.
68  * </p>
69  * <p>
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.
75  * </p>
76  * <p>
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.
81  * </p>
82  * <p>
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.
86  * </p>
87  */
88 public class ServiceCatalog {
89
90     /**
91      * The service name for the compute service endpoint
92      */
93     public static final String COMPUTE_SERVICE = "compute"; //$NON-NLS-1$
94
95     /**
96      * The service name for the identity service endpoint
97      */
98     public static final String IDENTITY_SERVICE = "identity"; //$NON-NLS-1$
99
100     /**
101      * The service name for the compute service endpoint
102      */
103     public static final String IMAGE_SERVICE = "image"; //$NON-NLS-1$
104
105     /**
106      * The service name for the network service endpoint
107      */
108     public static final String NETWORK_SERVICE = "network"; //$NON-NLS-1$
109
110     /**
111      * The service name for the orchestration service endpoint
112      */
113     public static final String ORCHESTRATION_SERVICE = "orchestration"; //$NON-NLS-1$
114
115     /**
116      * The service name for the volume service endpoint
117      */
118     public static final String VOLUME_SERVICE = "volume"; //$NON-NLS-1$
119
120     /**
121      * The service name for the persistent object service endpoint
122      */
123     public static final String OBJECT_SERVICE = "object-store"; //$NON-NLS-1$
124
125     /**
126      * The service name for the metering service endpoint
127      */
128     public static final String METERING_SERVICE = "metering"; //$NON-NLS-1$
129
130     /**
131      * The Openstack Access object that manages the authenticated token and access control
132      */
133     private Access access;
134
135     /**
136      * The time (local) that the token expires and we need to re-authenticate
137      */
138     @SuppressWarnings("unused")
139     private long expiresLocal;
140
141     /**
142      * The set of all regions that have been defined
143      */
144     private Set<String> regions;
145
146     /**
147      * The read/write lock used to protect the cache contents
148      */
149     private ReadWriteLock rwLock;
150
151     /**
152      * A map of endpoints for each service organized by service type
153      */
154     private Map<String /* Service Type */, List<Service.Endpoint>> serviceEndpoints;
155
156     /**
157      * A map of service types that are published
158      */
159     private Map<String /* Service Type */, Service> serviceTypes;
160
161     /**
162      * The tenant that we are accessing
163      */
164     private Tenant tenant;
165
166     /**
167      * A "token provider" that manages the authentication token that we obtain when logging in
168      */
169     private OpenStackSimpleTokenProvider tokenProvider;
170
171     public static final String CLIENT_CONNECTOR_CLASS = "com.woorea.openstack.connector.JerseyConnector";
172
173     /**
174      * Create the ServiceCatalog cache and load it from the specified provider
175      * 
176      * @param identityURL
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
181      *            the name.
182      * @param principal
183      *            The user id to authenticate to the provider
184      * @param credential
185      *            The password to authenticate to the provider
186      * @param properties
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
192      */
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<>();
199
200         Class<?> connectorClass;
201         OpenStackClientConnector connector;
202         try {
203             connectorClass = Class.forName(CLIENT_CONNECTOR_CLASS);
204             connector = (OpenStackClientConnector) connectorClass.newInstance();
205         } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
206             e.printStackTrace();
207             return;
208         }
209         Keystone keystone = new Keystone(identityURL, connector);
210
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);
217         }
218         if (trustedHosts != null) {
219             keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.TRUST_HOST_LIST,
220                 trustedHosts);
221         }
222
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);
228         } else {
229             authenticate = authenticate.withTenantName(tenantIdentifier);
230         }
231
232         /*
233          * We have to set up the TrackRequest TLS collection for the ExceptionMapper
234          */
235         trackRequest();
236         RequestState.put(RequestState.PROVIDER, "OpenStackProvider");
237         RequestState.put(RequestState.TENANT, tenantIdentifier);
238         RequestState.put(RequestState.PRINCIPAL, principal);
239
240         try {
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());
251         }
252     }
253
254     /**
255      * Returns the list of service endpoints for the published service type
256      * 
257      * @param serviceType
258      *            The service type to obtain the endpoints for
259      * @return The list of endpoints for the service type, or null if none exist
260      */
261     public List<Service.Endpoint> getEndpoints(String serviceType) {
262         Lock readLock = rwLock.readLock();
263         readLock.lock();
264         try {
265             return serviceEndpoints.get(serviceType);
266         } finally {
267             readLock.unlock();
268         }
269     }
270
271     /**
272      * Computes the local time when the access token will expire, after which we will need to re-login to access the
273      * provider.
274      * 
275      * @param accessKey
276      *            The access key used to access the provider
277      * @return The local time the key expires
278      */
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;
287             }
288         }
289         return now.getTime();
290     }
291
292     /**
293      * @return The set of all regions that are defined
294      */
295     public Set<String> getRegions() {
296         Lock readLock = rwLock.readLock();
297         readLock.lock();
298         try {
299             return regions;
300         } finally {
301             readLock.unlock();
302         }
303     }
304
305     /**
306      * @return A list of service types that are published
307      */
308     public List<String> getServiceTypes() {
309         Lock readLock = rwLock.readLock();
310         readLock.lock();
311         try {
312             ArrayList<String> result = new ArrayList<>();
313             result.addAll(serviceTypes.keySet());
314             return result;
315         } finally {
316             readLock.unlock();
317         }
318     }
319
320     /**
321      * @return The tenant id
322      */
323     public String getTenantId() {
324         Lock readLock = rwLock.readLock();
325         readLock.lock();
326         try {
327             return tenant.getId();
328         } finally {
329             readLock.unlock();
330         }
331     }
332
333     /**
334      * @return The tenant name
335      */
336     public String getTenantName() {
337         Lock readLock = rwLock.readLock();
338         readLock.lock();
339         try {
340             return tenant.getName();
341         } finally {
342             readLock.unlock();
343         }
344     }
345
346     /**
347      * Returns an indication if the specified service type is published by this provider
348      * 
349      * @param serviceType
350      *            The service type to check for
351      * @return True if a service of that type is published
352      */
353     public boolean isServicePublished(String serviceType) {
354         Lock readLock = rwLock.readLock();
355         readLock.lock();
356         try {
357             return serviceTypes.containsKey(serviceType);
358         } finally {
359             readLock.unlock();
360         }
361     }
362
363     /**
364      * Parses the service catalog and caches the results
365      * 
366      * @param services
367      *            The list of services published by this provider
368      */
369     private void parseServiceCatalog(List<Service> services) {
370         Lock lock = rwLock.writeLock();
371         lock.lock();
372         try {
373             serviceTypes.clear();
374             serviceEndpoints.clear();
375             regions.clear();
376
377             for (Service service : services) {
378                 String type = service.getType();
379                 serviceTypes.put(type, service);
380
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);
387                     }
388                     endpointList.add(endpoint);
389
390                     String region = endpoint.getRegion();
391                     if (!regions.contains(region)) {
392                         regions.add(region);
393                     }
394                 }
395             }
396         } finally {
397             lock.unlock();
398         }
399     }
400
401     /**
402      * This method is used to provide a diagnostic listing of the service catalog
403      * 
404      * @see java.lang.Object#toString()
405      */
406     @Override
407     public String toString() {
408
409         StringBuilder builder = new StringBuilder();
410         Lock lock = rwLock.readLock();
411         lock.lock();
412         try {
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$
419                 }
420             }
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);
425
426                 builder.append(String.format("\t%s [%s] - %d endpoints\n", service.getType(), service.getName(), //$NON-NLS-1$
427                     endpoints.size()));
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()));
431                 }
432             }
433         } finally {
434             lock.unlock();
435         }
436
437         return builder.toString();
438     }
439
440     /**
441      * Initializes the request state for the current requested service.
442      * <p>
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.
446      * </p>
447      * <p>
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".
450      * </p>
451      * 
452      * @param states
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.
455      */
456     protected void trackRequest(State... states) {
457         RequestState.clear();
458
459         for (State state : states) {
460             RequestState.put(state.getName(), state.getValue());
461         }
462
463         Thread currentThread = Thread.currentThread();
464         StackTraceElement[] stack = currentThread.getStackTrace();
465         if (stack != null && stack.length > 0) {
466             int index = 0;
467             StackTraceElement element = null;
468             for (; index < stack.length; index++) {
469                 element = stack[index];
470                 if ("trackRequest".equals(element.getMethodName())) {  //$NON-NLS-1$
471                     break;
472                 }
473             }
474             index++;
475
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());
485             }
486         }
487     }
488 }