First part of onap rename
[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.woorea.openstack.base.client.OpenStackBaseException;\r
34 import com.woorea.openstack.base.client.OpenStackClientConnector;\r
35 import com.woorea.openstack.base.client.OpenStackSimpleTokenProvider;\r
36 import com.woorea.openstack.keystone.v3.Keystone;\r
37 import com.woorea.openstack.keystone.v3.api.TokensResource;\r
38 import com.woorea.openstack.keystone.v3.model.Authentication;\r
39 import com.woorea.openstack.keystone.v3.model.Authentication.Identity;\r
40 import com.woorea.openstack.keystone.v3.model.Authentication.Scope;\r
41 import com.woorea.openstack.keystone.v3.model.Token;\r
42 import com.woorea.openstack.keystone.v3.model.Token.Project;\r
43 import com.woorea.openstack.keystone.v3.model.Token.Service;\r
44 import com.woorea.openstack.keystone.v3.model.Token.Service.Endpoint;\r
45 import java.util.ArrayList;\r
46 import java.util.Calendar;\r
47 import java.util.Date;\r
48 import java.util.HashMap;\r
49 import java.util.HashSet;\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.concurrent.locks.ReentrantReadWriteLock;\r
56 import java.util.regex.Matcher;\r
57 import java.util.regex.Pattern;\r
58 \r
59 /**\r
60  * This class is used to capture and cache the service catalog for a specific OpenStack provider.\r
61  * <p>\r
62  * This is needed because the way the servers are represented in the ECOMP product is as their fully qualified URL's.\r
63  * This is very problematic, because we cant identify their region from the URL, URL's change, and we cant identify the\r
64  * versions of the service implementations. In otherwords, the URL does not provide us enough information.\r
65  * </p>\r
66  * <p>\r
67  * The zone abstraction layer is designed to detect the versions of the services dynamically, and step up or down to\r
68  * match those reported versions. In order to do that, we need to know before hand what region we are accessing (since\r
69  * the supported versions may be different by regions). We will need to authenticate to the identity service in order to\r
70  * do this, plus we have to duplicate the code supporting proxies and trusted hosts that exists in the abstraction\r
71  * layer, but that cant be helped.\r
72  * </p>\r
73  * <p>\r
74  * What we do to circumvent this is connect to the provider using the lowest supported identity api, and read the entire\r
75  * service catalog into this object. Then, we parse the vm URL to extract the host and port and match that to the\r
76  * compute services defined in the catalog. When we find a compute service that has the same host name and port,\r
77  * whatever region that service is supporting is the region for that server.\r
78  * </p>\r
79  * <p>\r
80  * While we really only need to do this for compute nodes, there is no telling what other situations may arise where the\r
81  * full service catalog may be needed. Also, there is very little additional cost (additional RAM) associated with\r
82  * caching the full service catalog since there is no way to list only a portion of it.\r
83  * </p>\r
84  */\r
85 public class ServiceCatalogV3 extends ServiceCatalog {\r
86 \r
87     /**\r
88      * The project that we are accessing\r
89      */\r
90     private Project project;\r
91 \r
92     /**\r
93      * A map of endpoints for each service organized by service type\r
94      */\r
95     private Map<String /* Service Type */, List<Service.Endpoint>> serviceEndpoints;\r
96 \r
97     /**\r
98      * A map of service types that are published\r
99      */\r
100     private Map<String /* Service Type */, Service> serviceTypes;\r
101 \r
102     /**\r
103      * The Openstack Access object that manages the authenticated token and access control\r
104      */\r
105     private Token token;\r
106 \r
107     /**\r
108      * A "token provider" that manages the authentication token that we obtain when logging in\r
109      */\r
110     private OpenStackSimpleTokenProvider tokenProvider;\r
111 \r
112     /**\r
113      * {@inheritDoc}\r
114      */\r
115     public ServiceCatalogV3(String identityURL, String projectIdentifier, String principal, String credential,\r
116             String domain, Properties properties) {\r
117         super(identityURL, projectIdentifier, principal, credential, domain, properties);\r
118     }\r
119 \r
120     /**\r
121      * {@inheritDoc}\r
122      */\r
123     @Override\r
124     public void init() throws ZoneException {\r
125         rwLock = new ReentrantReadWriteLock();\r
126         serviceTypes = new HashMap<>();\r
127         serviceEndpoints = new HashMap<>();\r
128         regions = new HashSet<>();\r
129         Class<?> connectorClass;\r
130         OpenStackClientConnector connector;\r
131         try {\r
132             connectorClass = Class.forName(CLIENT_CONNECTOR_CLASS);\r
133             connector = (OpenStackClientConnector) connectorClass.newInstance();\r
134         } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {\r
135             e.printStackTrace();\r
136             return;\r
137         }\r
138         Keystone keystone = new Keystone(identityURL, connector);\r
139 \r
140         String proxyHost = properties.getProperty(ContextFactory.PROPERTY_PROXY_HOST);\r
141         String proxyPort = properties.getProperty(ContextFactory.PROPERTY_PROXY_PORT);\r
142         String trustedHosts = properties.getProperty(ContextFactory.PROPERTY_TRUSTED_HOSTS, ""); //$NON-NLS-1$\r
143         if (proxyHost != null && proxyHost.length() > 0) {\r
144             keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.PROXY_HOST, proxyHost);\r
145             keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.PROXY_PORT, proxyPort);\r
146         }\r
147         if (trustedHosts != null) {\r
148             keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.TRUST_HOST_LIST,\r
149                     trustedHosts);\r
150         }\r
151 \r
152         // create identity\r
153         Identity identity = Identity.password(domain, principal, credential);\r
154 \r
155         // create scope\r
156         Scope scope = null;\r
157         if (projectIdentifier.length() == 32 && projectIdentifier.matches("[0-9a-fA-F]+")) { //$NON-NLS-1$\r
158             // authenticate = authenticate.withTenantId(projectIdentifier);\r
159             scope = Scope.project(projectIdentifier);\r
160         } else {\r
161             // authenticate = authenticate.withTenantName(projectIdentifier);\r
162             scope = Scope.project(domain, projectIdentifier);\r
163         }\r
164 \r
165         Authentication authentication = new Authentication();\r
166         authentication.setIdentity(identity);\r
167         authentication.setScope(scope);\r
168 \r
169         TokensResource tokens = keystone.tokens();\r
170         TokensResource.Authenticate authenticate = tokens.authenticate(authentication);\r
171 \r
172         /*\r
173          * We have to set up the TrackRequest TLS collection for the ExceptionMapper\r
174          */\r
175         trackRequest();\r
176         RequestState.put(RequestState.PROVIDER, "OpenStackProvider");\r
177         RequestState.put(RequestState.TENANT, projectIdentifier);\r
178         RequestState.put(RequestState.PRINCIPAL, principal);\r
179 \r
180         try {\r
181             token = authenticate.execute();\r
182             expiresLocal = getLocalExpiration(token);\r
183             project = token.getProject();\r
184             tokenProvider = new OpenStackSimpleTokenProvider(token.getId());\r
185             keystone.setTokenProvider(tokenProvider);\r
186             parseServiceCatalog(token.getCatalog());\r
187         } catch (OpenStackBaseException e) {\r
188             ExceptionMapper.mapException(e);\r
189         } catch (Exception ex) {\r
190             throw new ContextConnectionException(ex.getMessage());\r
191         }\r
192     }\r
193 \r
194     /**\r
195      * {@inheritDoc}\r
196      */\r
197     @Override\r
198     public List<Service.Endpoint> getEndpoints(String serviceType) {\r
199         Lock readLock = rwLock.readLock();\r
200         readLock.lock();\r
201         try {\r
202             return serviceEndpoints.get(serviceType);\r
203         } finally {\r
204             readLock.unlock();\r
205         }\r
206     }\r
207 \r
208     /**\r
209      * {@inheritDoc}\r
210      */\r
211     @Override\r
212     public String getProjectId() {\r
213         Lock readLock = rwLock.readLock();\r
214         readLock.lock();\r
215         try {\r
216             return project.getId();\r
217         } finally {\r
218             readLock.unlock();\r
219         }\r
220     }\r
221 \r
222     /**\r
223      * {@inheritDoc}\r
224      */\r
225     @Override\r
226     public String getProjectName() {\r
227         Lock readLock = rwLock.readLock();\r
228         readLock.lock();\r
229         try {\r
230             return project.getName();\r
231         } finally {\r
232             readLock.unlock();\r
233         }\r
234     }\r
235 \r
236     /**\r
237      * {@inheritDoc}\r
238      */\r
239     @Override\r
240     public Set<String> getRegions() {\r
241         Lock readLock = rwLock.readLock();\r
242         readLock.lock();\r
243         try {\r
244             return regions;\r
245         } finally {\r
246             readLock.unlock();\r
247         }\r
248     }\r
249 \r
250     /**\r
251      * {@inheritDoc}\r
252      */\r
253     @Override\r
254     public List<String> getServiceTypes() {\r
255         Lock readLock = rwLock.readLock();\r
256         readLock.lock();\r
257         try {\r
258             ArrayList<String> result = new ArrayList<>();\r
259             result.addAll(serviceTypes.keySet());\r
260             return result;\r
261         } finally {\r
262             readLock.unlock();\r
263         }\r
264     }\r
265 \r
266     /**\r
267      * {@inheritDoc}\r
268      */\r
269     @Override\r
270     public String getVMRegion(VMURL url) {\r
271         String region = null;\r
272         Pattern urlPattern = Pattern.compile("[^:]+://([^:/]+)(?::([0-9]+)).*");\r
273 \r
274         if (url != null) {\r
275             for (Endpoint endpoint : getEndpoints(ServiceCatalog.COMPUTE_SERVICE)) {\r
276                 String endpointUrl = endpoint.getUrl();\r
277                 Matcher matcher = urlPattern.matcher(endpointUrl);\r
278                 if (matcher.matches()) {\r
279                     if (url.getHost().equals(matcher.group(1))) {\r
280                         if (url.getPort() != null) {\r
281                             if (!url.getPort().equals(matcher.group(2))) {\r
282                                 continue;\r
283                             }\r
284                         }\r
285 \r
286                         region = endpoint.getRegion();\r
287                         break;\r
288                     }\r
289                 }\r
290             }\r
291         }\r
292         return region;\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                     builder.append("\t" + region + "\n"); //$NON-NLS-1$ //$NON-NLS-2$\r
325                 }\r
326             }\r
327             builder.append(String.format("%d services:\n", serviceEndpoints.size())); //$NON-NLS-1$\r
328             for (String serviceType : serviceEndpoints.keySet()) {\r
329                 List<Service.Endpoint> endpoints = serviceEndpoints.get(serviceType);\r
330                 Service service = serviceTypes.get(serviceType);\r
331 \r
332                 builder.append(String.format("\t%s - %d endpoints\n", service.getType(), //$NON-NLS-1$\r
333                         endpoints.size()));\r
334                 for (Service.Endpoint endpoint : endpoints) {\r
335                     builder.append(String.format("\t\tRegion [%s], public URL [%s]\n", endpoint.getRegion(), //$NON-NLS-1$\r
336                             endpoint.getUrl()));\r
337                 }\r
338             }\r
339         } finally {\r
340             lock.unlock();\r
341         }\r
342 \r
343         return builder.toString();\r
344     }\r
345 \r
346     /**\r
347      * Parses the service catalog and caches the results\r
348      * \r
349      * @param services The list of services published by this provider\r
350      */\r
351     private void parseServiceCatalog(List<Service> services) {\r
352         Lock lock = rwLock.writeLock();\r
353         lock.lock();\r
354         try {\r
355             serviceTypes.clear();\r
356             serviceEndpoints.clear();\r
357             regions.clear();\r
358 \r
359             for (Service service : services) {\r
360                 String type = service.getType();\r
361                 serviceTypes.put(type, service);\r
362 \r
363                 List<Service.Endpoint> endpoints = service.getEndpoints();\r
364                 for (Service.Endpoint endpoint : endpoints) {\r
365                     List<Service.Endpoint> endpointList = serviceEndpoints.get(type);\r
366                     if (endpointList == null) {\r
367                         endpointList = new ArrayList<>();\r
368                         serviceEndpoints.put(type, endpointList);\r
369                     }\r
370                     endpointList.add(endpoint);\r
371 \r
372                     String region = endpoint.getRegion();\r
373                     if (!regions.contains(region)) {\r
374                         regions.add(region);\r
375                     }\r
376                 }\r
377             }\r
378         } finally {\r
379             lock.unlock();\r
380         }\r
381     }\r
382 \r
383     /**\r
384      * Computes the local time when the access token will expire, after which we will need to re-login to access the\r
385      * provider.\r
386      * \r
387      * @param accessKey The access key used to access the provider\r
388      * @return The local time the key expires\r
389      */\r
390     private static long getLocalExpiration(Token accessToken) {\r
391         Date now = Time.getCurrentUTCDate();\r
392         if (accessToken != null) {\r
393             Calendar issued = accessToken.getIssuedAt();\r
394             Calendar expires = accessToken.getExpiresAt();\r
395             if (issued != null && expires != null) {\r
396                 long tokenLife = expires.getTimeInMillis() - issued.getTimeInMillis();\r
397                 return now.getTime() + tokenLife;\r
398             }\r
399         }\r
400         return now.getTime();\r
401     }\r
402 }\r