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