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