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