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