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