2fea0b1a242d9f8c3ca1766b55d0fa7fb45c8fe2
[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 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  * ECOMP is a trademark and service mark of AT&T Intellectual Property.\r
22  * ============LICENSE_END=========================================================\r
23  */\r
24 \r
25 package org.onap.appc.adapter.iaas.impl;\r
26 \r
27 import com.att.cdp.exceptions.ContextConnectionException;\r
28 import com.att.cdp.exceptions.ZoneException;\r
29 import com.att.cdp.openstack.util.ExceptionMapper;\r
30 import com.att.cdp.pal.util.Time;\r
31 import com.att.cdp.zones.ContextFactory;\r
32 import com.att.cdp.zones.spi.RequestState;\r
33 import com.google.common.collect.Lists;\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.v3.Keystone;\r
38 import com.woorea.openstack.keystone.v3.api.TokensResource;\r
39 import com.woorea.openstack.keystone.v3.model.Authentication;\r
40 import com.woorea.openstack.keystone.v3.model.Authentication.Identity;\r
41 import com.woorea.openstack.keystone.v3.model.Authentication.Scope;\r
42 import com.woorea.openstack.keystone.v3.model.Token;\r
43 import com.woorea.openstack.keystone.v3.model.Token.Project;\r
44 import com.woorea.openstack.keystone.v3.model.Token.Service;\r
45 import com.woorea.openstack.keystone.v3.model.Token.Service.Endpoint;\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.HashSet;\r
51 import java.util.List;\r
52 import java.util.Map;\r
53 import java.util.Properties;\r
54 import java.util.Set;\r
55 import java.util.concurrent.locks.Lock;\r
56 import java.util.concurrent.locks.ReentrantReadWriteLock;\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 ServiceCatalogV3 extends ServiceCatalog {\r
87 \r
88     /**\r
89      * The project that we are accessing\r
90      */\r
91     private Project project;\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 Openstack Access object that manages the authenticated token and access control\r
105      */\r
106     private Token token;\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     /**\r
114      * {@inheritDoc}\r
115      */\r
116     public ServiceCatalogV3(String identityURL, String projectIdentifier, String principal, String credential,\r
117         String domain, Properties properties) {\r
118         super(identityURL, projectIdentifier, principal, credential, domain, properties);\r
119     }\r
120 \r
121     /**\r
122      * {@inheritDoc}\r
123      */\r
124     @Override\r
125     public void init() throws ZoneException {\r
126         rwLock = new ReentrantReadWriteLock();\r
127         serviceTypes = new HashMap<>();\r
128         serviceEndpoints = new HashMap<>();\r
129         regions = new HashSet<>();\r
130         Class<?> connectorClass;\r
131         OpenStackClientConnector connector;\r
132         try {\r
133             connectorClass = Class.forName(CLIENT_CONNECTOR_CLASS);\r
134             connector = (OpenStackClientConnector) connectorClass.newInstance();\r
135         } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {\r
136             logger.error("An error occurred when initializing ServiceCatalogV3", e);\r
137             return;\r
138         }\r
139         Keystone keystone = new Keystone(identityURL, connector);\r
140 \r
141         String proxyHost = properties.getProperty(ContextFactory.PROPERTY_PROXY_HOST);\r
142         String proxyPort = properties.getProperty(ContextFactory.PROPERTY_PROXY_PORT);\r
143         String trustedHosts = properties.getProperty(ContextFactory.PROPERTY_TRUSTED_HOSTS, ""); //$NON-NLS-1$\r
144         if (proxyHost != null && proxyHost.length() > 0) {\r
145             keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.PROXY_HOST, proxyHost);\r
146             keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.PROXY_PORT, proxyPort);\r
147         }\r
148         if (trustedHosts != null) {\r
149             keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.TRUST_HOST_LIST,\r
150                 trustedHosts);\r
151         }\r
152 \r
153         // create identity\r
154         Identity identity = Identity.password(domain, principal, credential);\r
155 \r
156         // create scope\r
157         Scope scope = initScope();\r
158 \r
159         Authentication authentication = new Authentication();\r
160         authentication.setIdentity(identity);\r
161         authentication.setScope(scope);\r
162 \r
163         TokensResource tokens = keystone.tokens();\r
164         TokensResource.Authenticate authenticate = tokens.authenticate(authentication);\r
165 \r
166         /*\r
167          * We have to set up the TrackRequest TLS collection for the ExceptionMapper\r
168          */\r
169         trackRequest();\r
170         RequestState.put(RequestState.PROVIDER, "OpenStackProvider");\r
171         RequestState.put(RequestState.TENANT, projectIdentifier);\r
172         RequestState.put(RequestState.PRINCIPAL, principal);\r
173 \r
174         try {\r
175             token = authenticate.execute();\r
176             expiresLocal = getLocalExpiration(token);\r
177             project = token.getProject();\r
178             tokenProvider = new OpenStackSimpleTokenProvider(token.getId());\r
179             keystone.setTokenProvider(tokenProvider);\r
180             parseServiceCatalog(token.getCatalog());\r
181         } catch (OpenStackBaseException e) {\r
182             ExceptionMapper.mapException(e);\r
183         } catch (Exception e) {\r
184             throw new ContextConnectionException(e);\r
185         }\r
186     }\r
187 \r
188     private Scope initScope() {\r
189         if (projectIdentifier.length() == 32 && projectIdentifier.matches("[0-9a-fA-F]+")) { //$NON-NLS-1$\r
190             return Scope.project(projectIdentifier);\r
191         } else {\r
192             return Scope.project(domain, projectIdentifier);\r
193         }\r
194     }\r
195 \r
196     /**\r
197      * {@inheritDoc}\r
198      */\r
199     @Override\r
200     public List<Service.Endpoint> getEndpoints(String serviceType) {\r
201         Lock readLock = rwLock.readLock();\r
202         readLock.lock();\r
203         try {\r
204             return serviceEndpoints.get(serviceType);\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 getProjectId() {\r
215         Lock readLock = rwLock.readLock();\r
216         readLock.lock();\r
217         try {\r
218             return project.getId();\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 String getProjectName() {\r
229         Lock readLock = rwLock.readLock();\r
230         readLock.lock();\r
231         try {\r
232             return project.getName();\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 Set<String> getRegions() {\r
243         Lock readLock = rwLock.readLock();\r
244         readLock.lock();\r
245         try {\r
246             return regions;\r
247         } finally {\r
248             readLock.unlock();\r
249         }\r
250     }\r
251 \r
252     /**\r
253      * {@inheritDoc}\r
254      */\r
255     @Override\r
256     public List<String> getServiceTypes() {\r
257         Lock readLock = rwLock.readLock();\r
258         readLock.lock();\r
259         try {\r
260             ArrayList<String> result = new ArrayList<>();\r
261             result.addAll(serviceTypes.keySet());\r
262             return result;\r
263         } finally {\r
264             readLock.unlock();\r
265         }\r
266     }\r
267 \r
268     /**\r
269      * {@inheritDoc}\r
270      */\r
271     @Override\r
272     public String getVMRegion(VMURL url) {\r
273         String region = null;\r
274         Pattern urlPattern = Pattern.compile("[^:]+://([^:/]+)(?::([0-9]+)).*");\r
275 \r
276         if (url != null) {\r
277             for (Endpoint endpoint : getEndpoints(ServiceCatalog.COMPUTE_SERVICE)) {\r
278                 String endpointUrl = endpoint.getUrl();\r
279                 Matcher matcher = urlPattern.matcher(endpointUrl);\r
280                 if (validateUrl(url, matcher)) {\r
281                     region = endpoint.getRegion();\r
282                     break;\r
283                 }\r
284             }\r
285         }\r
286         return region;\r
287     }\r
288 \r
289     private boolean validateUrl(VMURL url, Matcher matcher) {\r
290         return matcher.matches()\r
291             && url.getHost().equals(matcher.group(1))\r
292             && (url.getPort() == null || url.getPort().equals(matcher.group(2)));\r
293     }\r
294 \r
295     /**\r
296      * {@inheritDoc}\r
297      */\r
298     @Override\r
299     public boolean isServicePublished(String serviceType) {\r
300         Lock readLock = rwLock.readLock();\r
301         readLock.lock();\r
302         try {\r
303             return serviceTypes.containsKey(serviceType);\r
304         } finally {\r
305             readLock.unlock();\r
306         }\r
307     }\r
308 \r
309     /**\r
310      * {@inheritDoc}\r
311      */\r
312     @Override\r
313     public String toString() {\r
314 \r
315         StringBuilder builder = new StringBuilder();\r
316         Lock lock = rwLock.readLock();\r
317         lock.lock();\r
318         try {\r
319             builder.append(String.format("Service Catalog: tenant %s, id[%s]%n", project.getName(), //$NON-NLS-1$\r
320                 project.getId()));\r
321             if (regions != null && !regions.isEmpty()) {\r
322                 builder.append(String.format("%d regions:%n", regions.size())); //$NON-NLS-1$\r
323                 for (String region : regions) {\r
324                     //$NON-NLS-1$ //$NON-NLS-2$\r
325                     builder\r
326                         .append("\t")\r
327                         .append(region)\r
328                         .append("%n");\r
329                 }\r
330             }\r
331             builder.append(String.format("%d services:%n", serviceEndpoints.size())); //$NON-NLS-1$\r
332 \r
333             for (Map.Entry<String, List<Service.Endpoint>> entry : serviceEndpoints.entrySet()) {\r
334                 Service service = serviceTypes.get(entry.getKey());\r
335 \r
336                 builder.append(String.format("\t%s - %d endpoints%n", service.getType(), //$NON-NLS-1$\r
337                     entry.getValue().size()));\r
338 \r
339                 for (Service.Endpoint endpoint : entry.getValue()) {\r
340                     builder\r
341                         .append(String.format("\t\tRegion [%s], public URL [%s]%n", endpoint.getRegion(), //$NON-NLS-1$\r
342                             endpoint.getUrl()));\r
343                 }\r
344             }\r
345         } finally {\r
346             lock.unlock();\r
347         }\r
348 \r
349         return builder.toString();\r
350     }\r
351 \r
352     /**\r
353      * Parses the service catalog and caches the results\r
354      *\r
355      * @param services The list of services published by this provider\r
356      */\r
357     private void parseServiceCatalog(List<Service> services) {\r
358         Lock lock = rwLock.writeLock();\r
359         lock.lock();\r
360         try {\r
361             serviceTypes.clear();\r
362             serviceEndpoints.clear();\r
363             regions.clear();\r
364 \r
365             for (Service service : services) {\r
366                 String type = service.getType();\r
367                 serviceTypes.put(type, service);\r
368                 addRegions(service, type);\r
369             }\r
370         } finally {\r
371             lock.unlock();\r
372         }\r
373     }\r
374 \r
375     private void addRegions(Service service, String type) {\r
376         List<Endpoint> endpoints = service.getEndpoints();\r
377         for (Endpoint endpoint : endpoints) {\r
378             serviceEndpoints.computeIfAbsent(type, val -> new ArrayList<>());\r
379             serviceEndpoints.get(type).add(endpoint);\r
380 \r
381             String region = endpoint.getRegion();\r
382             if (!regions.contains(region)) {\r
383                 regions.add(region);\r
384             }\r
385         }\r
386     }\r
387 \r
388     /**\r
389      * Computes the local time when the access token will expire, after which we will need to re-login to access the\r
390      * provider.\r
391      *\r
392      * @param accessKey The access key used to access the provider\r
393      * @return The local time the key expires\r
394      */\r
395     private static long getLocalExpiration(Token accessToken) {\r
396         Date now = Time.getCurrentUTCDate();\r
397         if (accessToken != null) {\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         }\r
405         return now.getTime();\r
406     }\r
407 }