null ptr check
[appc.git] / appc-adapters / appc-iaas-adapter / appc-iaas-adapter-bundle / src / main / java / org / onap / appc / adapter / iaas / impl / ServiceCatalogV2.java
1 /*-\r
2  * ============LICENSE_START=======================================================\r
3  * ONAP : APPC\r
4  * ================================================================================\r
5  * Copyright (C) 2017-2018 AT&T Intellectual Property. All rights reserved.\r
6  * ================================================================================\r
7  * Copyright (C) 2017 Amdocs\r
8  * =============================================================================\r
9  * Licensed under the Apache License, Version 2.0 (the "License");\r
10  * you may not use this file except in compliance with the License.\r
11  * You may obtain a copy of the License at\r
12  * \r
13  *      http://www.apache.org/licenses/LICENSE-2.0\r
14  * \r
15  * Unless required by applicable law or agreed to in writing, software\r
16  * distributed under the License is distributed on an "AS IS" BASIS,\r
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
18  * See the License for the specific language governing permissions and\r
19  * limitations under the License.\r
20  * \r
21  * ============LICENSE_END=========================================================\r
22  */\r
23 \r
24 package org.onap.appc.adapter.iaas.impl;\r
25 \r
26 import com.att.cdp.exceptions.ContextConnectionException;\r
27 import com.att.cdp.exceptions.ZoneException;\r
28 import com.att.cdp.openstack.util.ExceptionMapper;\r
29 import com.att.cdp.pal.util.Time;\r
30 import com.att.cdp.zones.ContextFactory;\r
31 import com.att.cdp.zones.spi.RequestState;\r
32 import com.att.eelf.configuration.EELFLogger;\r
33 import com.att.eelf.configuration.EELFManager;\r
34 import com.woorea.openstack.base.client.OpenStackBaseException;\r
35 import com.woorea.openstack.base.client.OpenStackClientConnector;\r
36 import com.woorea.openstack.base.client.OpenStackSimpleTokenProvider;\r
37 import com.woorea.openstack.keystone.Keystone;\r
38 import com.woorea.openstack.keystone.api.TokensResource;\r
39 import com.woorea.openstack.keystone.model.Access;\r
40 import com.woorea.openstack.keystone.model.Access.Service;\r
41 import com.woorea.openstack.keystone.model.Access.Service.Endpoint;\r
42 import com.woorea.openstack.keystone.model.Authentication;\r
43 import com.woorea.openstack.keystone.model.Tenant;\r
44 import com.woorea.openstack.keystone.model.authentication.UsernamePassword;\r
45 \r
46 import java.util.ArrayList;\r
47 import java.util.Calendar;\r
48 import java.util.Date;\r
49 import java.util.HashMap;\r
50 import java.util.List;\r
51 import java.util.Map;\r
52 import java.util.Properties;\r
53 import java.util.Set;\r
54 import java.util.concurrent.locks.Lock;\r
55 import java.util.regex.Matcher;\r
56 import java.util.regex.Pattern;\r
57 \r
58 /**\r
59  * This class is used to capture and cache the service catalog for a specific OpenStack provider.\r
60  * <p>\r
61  * This is needed because the way the servers are represented in the ECOMP product is as their fully qualified URL's.\r
62  * This is very problematic, because we cant identify their region from the URL, URL's change, and we cant identify the\r
63  * versions of the service implementations. In otherwords, the URL does not provide us enough information.\r
64  * </p>\r
65  * <p>\r
66  * The zone abstraction layer is designed to detect the versions of the services dynamically, and step up or down to\r
67  * match those reported versions. In order to do that, we need to know before hand what region we are accessing (since\r
68  * the supported versions may be different by regions). We will need to authenticate to the identity service in order to\r
69  * do this, plus we have to duplicate the code supporting proxies and trusted hosts that exists in the abstraction\r
70  * layer, but that cant be helped.\r
71  * </p>\r
72  * <p>\r
73  * What we do to circumvent this is connect to the provider using the lowest supported identity api, and read the entire\r
74  * service catalog into this object. Then, we parse the vm URL to extract the host and port and match that to the\r
75  * compute services defined in the catalog. When we find a compute service that has the same host name and port,\r
76  * whatever region that service is supporting is the region for that server.\r
77  * </p>\r
78  * <p>\r
79  * While we really only need to do this for compute nodes, there is no telling what other situations may arise where the\r
80  * full service catalog may be needed. Also, there is very little additional cost (additional RAM) associated with\r
81  * caching the full service catalog since there is no way to list only a portion of it.\r
82  * </p>\r
83  */\r
84 public class ServiceCatalogV2 extends ServiceCatalog {\r
85 \r
86     protected static final EELFLogger loggerV2 = EELFManager.getInstance().getLogger(ServiceCatalogV2.class);\r
87 \r
88     /**\r
89      * The Openstack Access object that manages the authenticated token and access control\r
90      */\r
91     private Access access;\r
92 \r
93     /**\r
94      * A map of endpoints for each service organized by service type\r
95      */\r
96     private Map<String /* Service Type */, List<Service.Endpoint>> serviceEndpoints;\r
97 \r
98     /**\r
99      * A map of service types that are published\r
100      */\r
101     private Map<String /* Service Type */, Service> serviceTypes;\r
102 \r
103     /**\r
104      * The tenant that we are accessing\r
105      */\r
106     private Tenant tenant;\r
107 \r
108     /**\r
109      * A "token provider" that manages the authentication token that we obtain when logging in\r
110      */\r
111     private OpenStackSimpleTokenProvider tokenProvider;\r
112 \r
113     public ServiceCatalogV2(String identityURL, String tenantIdentifier, String principal, String credential,\r
114             Properties properties) {\r
115         super(identityURL, tenantIdentifier, principal, credential, null, properties);\r
116     }\r
117 \r
118     @Override\r
119     public void init() throws ZoneException {\r
120         serviceTypes = new HashMap<>();\r
121         serviceEndpoints = new HashMap<>();\r
122         Class<?> connectorClass;\r
123         OpenStackClientConnector connector;\r
124         try {\r
125             connectorClass = Class.forName(CLIENT_CONNECTOR_CLASS);\r
126             connector = (OpenStackClientConnector) connectorClass.newInstance();\r
127         } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {\r
128             loggerV2.error(e.getMessage());\r
129             return;\r
130         }\r
131         Keystone keystone = new Keystone(identityURL, connector);\r
132 \r
133         String proxyHost = properties.getProperty(ContextFactory.PROPERTY_PROXY_HOST);\r
134         String proxyPort = properties.getProperty(ContextFactory.PROPERTY_PROXY_PORT);\r
135         String trustedHosts = properties.getProperty(ContextFactory.PROPERTY_TRUSTED_HOSTS, ""); //$NON-NLS-1$\r
136         if (proxyHost != null && proxyHost.length() > 0) {\r
137             keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.PROXY_HOST, proxyHost);\r
138             keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.PROXY_PORT, proxyPort);\r
139         }\r
140         if (trustedHosts != null) {\r
141             keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.TRUST_HOST_LIST,\r
142                     trustedHosts);\r
143         }\r
144 \r
145         Authentication authentication = new UsernamePassword(principal, credential);\r
146         TokensResource tokens = keystone.tokens();\r
147         TokensResource.Authenticate authenticate = tokens.authenticate(authentication);\r
148         if (projectIdentifier.length() == 32 && projectIdentifier.matches("[0-9a-fA-F]+")) { //$NON-NLS-1$\r
149             authenticate = authenticate.withTenantId(projectIdentifier);\r
150         } else {\r
151             authenticate = authenticate.withTenantName(projectIdentifier);\r
152         }\r
153 \r
154         /*\r
155          * We have to set up the TrackRequest TLS collection for the ExceptionMapper\r
156          */\r
157         trackRequest();\r
158         RequestState.put(RequestState.PROVIDER, "OpenStackProvider");\r
159         RequestState.put(RequestState.TENANT, projectIdentifier);\r
160         RequestState.put(RequestState.PRINCIPAL, principal);\r
161 \r
162         try {\r
163             access = authenticate.execute();\r
164             //Ensure that access or the access token is not null before\r
165             //checking local expiration or accessing the tenant information\r
166             if (access == null || access.getToken() == null) {\r
167                 throw new NullPointerException("The access key used to access the provider or access token is null." +\r
168                         "Failed to init ServiceCatalogV2");\r
169             }\r
170             expiresLocal = getLocalExpiration(access);\r
171             tenant = access.getToken().getTenant();\r
172             tokenProvider = new OpenStackSimpleTokenProvider(access.getToken().getId());\r
173             keystone.setTokenProvider(tokenProvider);\r
174             parseServiceCatalog(access.getServiceCatalog());\r
175         } catch (OpenStackBaseException e) {\r
176             ExceptionMapper.mapException(e);\r
177         } catch (Exception ex) {\r
178             throw new ContextConnectionException(ex.getMessage());\r
179         }\r
180     }\r
181 \r
182     /**\r
183      * {@inheritDoc}\r
184      */\r
185     @Override\r
186     public List<Service.Endpoint> getEndpoints(String serviceType) {\r
187         Lock readLock = rwLock.readLock();\r
188         readLock.lock();\r
189         try {\r
190             return serviceEndpoints.get(serviceType);\r
191         } finally {\r
192             readLock.unlock();\r
193         }\r
194     }\r
195 \r
196     /**\r
197      * {@inheritDoc}\r
198      */\r
199     @Override\r
200     public String getProjectId() {\r
201         Lock readLock = rwLock.readLock();\r
202         readLock.lock();\r
203         try {\r
204             return tenant.getId();\r
205         } finally {\r
206             readLock.unlock();\r
207         }\r
208     }\r
209 \r
210     /**\r
211      * {@inheritDoc}\r
212      */\r
213     @Override\r
214     public String getProjectName() {\r
215         Lock readLock = rwLock.readLock();\r
216         readLock.lock();\r
217         try {\r
218             return tenant.getName();\r
219         } finally {\r
220             readLock.unlock();\r
221         }\r
222     }\r
223 \r
224     /**\r
225      * {@inheritDoc}\r
226      */\r
227     @Override\r
228     public Set<String> getRegions() {\r
229         Lock readLock = rwLock.readLock();\r
230         readLock.lock();\r
231         try {\r
232             return regions;\r
233         } finally {\r
234             readLock.unlock();\r
235         }\r
236     }\r
237 \r
238     /**\r
239      * {@inheritDoc}\r
240      */\r
241     @Override\r
242     public List<String> getServiceTypes() {\r
243         Lock readLock = rwLock.readLock();\r
244         readLock.lock();\r
245         try {\r
246             ArrayList<String> result = new ArrayList<>();\r
247             result.addAll(serviceTypes.keySet());\r
248             return result;\r
249         } finally {\r
250             readLock.unlock();\r
251         }\r
252     }\r
253 \r
254     /**\r
255      * {@inheritDoc}\r
256      */\r
257     @Override\r
258     public String getVMRegion(VMURL url) {\r
259         String region = null;\r
260         if (url == null) {\r
261             return region;\r
262         }\r
263 \r
264         Pattern urlPattern = Pattern.compile("[^:]+://([^:/]+)(?::([0-9]+)).*");\r
265 \r
266         for (Endpoint endpoint : getEndpoints(ServiceCatalog.COMPUTE_SERVICE)) {\r
267             String endpointUrl = endpoint.getPublicURL();\r
268             Matcher matcher = urlPattern.matcher(endpointUrl);\r
269             if (!matcher.matches() ||\r
270                 !url.getHost().equals(matcher.group(1)) ||\r
271                 (url.getPort() != null && !url.getPort().equals(matcher.group(2))) ) {\r
272                 continue;\r
273             }\r
274 \r
275             region = endpoint.getRegion();\r
276             break;\r
277         }\r
278 \r
279         return region;\r
280     }\r
281 \r
282     /**\r
283      * {@inheritDoc}\r
284      */\r
285     @Override\r
286     public boolean isServicePublished(String serviceType) {\r
287         Lock readLock = rwLock.readLock();\r
288         readLock.lock();\r
289         try {\r
290             return serviceTypes.containsKey(serviceType);\r
291         } finally {\r
292             readLock.unlock();\r
293         }\r
294     }\r
295 \r
296     /**\r
297      * {@inheritDoc}\r
298      */\r
299     @Override\r
300     public String toString() {\r
301 \r
302         StringBuilder builder = new StringBuilder();\r
303         Lock lock = rwLock.readLock();\r
304         lock.lock();\r
305         try {\r
306             builder.append(String.format("Service Catalog: tenant %s, id[%s], description[%s]%n", tenant.getName(), //$NON-NLS-1$\r
307                     tenant.getId(), tenant.getDescription()));\r
308             if (regions != null && !regions.isEmpty()) {\r
309                 builder.append(String.format("%d regions:%n", regions.size())); //$NON-NLS-1$\r
310                 for (String region : regions) {\r
311                     builder.append("\t" + region + "%n"); //$NON-NLS-1$ //$NON-NLS-2$\r
312                 }\r
313             }\r
314             builder.append(String.format("%d services:%n", serviceEndpoints.size())); //$NON-NLS-1$\r
315             for (String serviceType : serviceEndpoints.keySet()) {\r
316                 List<Service.Endpoint> endpoints = serviceEndpoints.get(serviceType);\r
317                 Service service = serviceTypes.get(serviceType);\r
318 \r
319                 builder.append(String.format("\t%s [%s] - %d endpoints%n", service.getType(), service.getName(), //$NON-NLS-1$\r
320                         endpoints.size()));\r
321                 for (Service.Endpoint endpoint : endpoints) {\r
322                     builder.append(String.format("\t\tRegion [%s], public URL [%s]%n", endpoint.getRegion(), //$NON-NLS-1$\r
323                             endpoint.getPublicURL()));\r
324                 }\r
325             }\r
326         } finally {\r
327             lock.unlock();\r
328         }\r
329 \r
330         return builder.toString();\r
331     }\r
332 \r
333     /**\r
334      * Parses the service catalog and caches the results\r
335      * \r
336      * @param services The list of services published by this provider\r
337      */\r
338     private void parseServiceCatalog(List<Service> services) {\r
339         Lock lock = rwLock.writeLock();\r
340         lock.lock();\r
341         try {\r
342             serviceTypes.clear();\r
343             serviceEndpoints.clear();\r
344             regions.clear();\r
345 \r
346             for (Service service : services) {\r
347                 String type = service.getType();\r
348                 serviceTypes.put(type, service);\r
349 \r
350                 List<Service.Endpoint> endpoints = service.getEndpoints();\r
351                 for (Service.Endpoint endpoint : endpoints) {\r
352                     List<Service.Endpoint> endpointList = serviceEndpoints.get(type);\r
353                     if (endpointList == null) {\r
354                         endpointList = new ArrayList<>();\r
355                         serviceEndpoints.put(type, endpointList);\r
356                     }\r
357                     endpointList.add(endpoint);\r
358 \r
359                     String region = endpoint.getRegion();\r
360                     if (!regions.contains(region)) {\r
361                         regions.add(region);\r
362                     }\r
363                 }\r
364             }\r
365         } finally {\r
366             lock.unlock();\r
367         }\r
368     }\r
369 \r
370     /**\r
371      * Computes the local time when the access token will expire, after which we will need to re-login to access the\r
372      * provider.\r
373      * \r
374      * @param accessKey The access key used to access the provider\r
375      * @return The local time the key expires\r
376      */\r
377     private static long getLocalExpiration(Access accessKey) {\r
378         Date now = Time.getCurrentUTCDate();\r
379         Calendar issued = accessKey.getToken().getIssued_at();\r
380         Calendar expires = accessKey.getToken().getExpires();\r
381         if (issued != null && expires != null) {\r
382             long tokenLife = expires.getTimeInMillis() - issued.getTimeInMillis();\r
383             return now.getTime() + tokenLife;\r
384         }\r
385         return now.getTime();\r
386     }\r
387 }\r