Corrected condition to pick region
[appc.git] / appc-adapters / appc-iaas-adapter / appc-iaas-adapter-bundle / src / main / java / org / onap / 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.att.eelf.configuration.EELFLogger;\r
34 import com.att.eelf.configuration.EELFManager;\r
35 import com.woorea.openstack.base.client.OpenStackBaseException;\r
36 import com.woorea.openstack.base.client.OpenStackClientConnector;\r
37 import com.woorea.openstack.base.client.OpenStackSimpleTokenProvider;\r
38 import com.woorea.openstack.keystone.Keystone;\r
39 import com.woorea.openstack.keystone.api.TokensResource;\r
40 import com.woorea.openstack.keystone.model.Access;\r
41 import com.woorea.openstack.keystone.model.Access.Service;\r
42 import com.woorea.openstack.keystone.model.Access.Service.Endpoint;\r
43 import com.woorea.openstack.keystone.model.Authentication;\r
44 import com.woorea.openstack.keystone.model.Tenant;\r
45 import com.woorea.openstack.keystone.model.authentication.UsernamePassword;\r
46 \r
47 import java.util.ArrayList;\r
48 import java.util.Calendar;\r
49 import java.util.Date;\r
50 import java.util.HashMap;\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.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 ServiceCatalogV2 extends ServiceCatalog {\r
86 \r
87     protected static final EELFLogger loggerV2 = EELFManager.getInstance().getLogger(ServiceCatalogV2.class);\r
88 \r
89     /**\r
90      * The Openstack Access object that manages the authenticated token and access control\r
91      */\r
92     private Access access;\r
93 \r
94     /**\r
95      * A map of endpoints for each service organized by service type\r
96      */\r
97     private Map<String /* Service Type */, List<Service.Endpoint>> serviceEndpoints;\r
98 \r
99     /**\r
100      * A map of service types that are published\r
101      */\r
102     private Map<String /* Service Type */, Service> serviceTypes;\r
103 \r
104     /**\r
105      * The tenant that we are accessing\r
106      */\r
107     private Tenant tenant;\r
108 \r
109     /**\r
110      * A "token provider" that manages the authentication token that we obtain when logging in\r
111      */\r
112     private OpenStackSimpleTokenProvider tokenProvider;\r
113 \r
114     public ServiceCatalogV2(String identityURL, String tenantIdentifier, String principal, String credential,\r
115             Properties properties) {\r
116         super(identityURL, tenantIdentifier, principal, credential, null, properties);\r
117     }\r
118 \r
119     @Override\r
120     public void init() throws ZoneException {\r
121         serviceTypes = new HashMap<>();\r
122         serviceEndpoints = new HashMap<>();\r
123         Class<?> connectorClass;\r
124         OpenStackClientConnector connector;\r
125         try {\r
126             connectorClass = Class.forName(CLIENT_CONNECTOR_CLASS);\r
127             connector = (OpenStackClientConnector) connectorClass.newInstance();\r
128         } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {\r
129             loggerV2.error(e.getMessage());\r
130             return;\r
131         }\r
132         Keystone keystone = new Keystone(identityURL, connector);\r
133 \r
134         String proxyHost = properties.getProperty(ContextFactory.PROPERTY_PROXY_HOST);\r
135         String proxyPort = properties.getProperty(ContextFactory.PROPERTY_PROXY_PORT);\r
136         String trustedHosts = properties.getProperty(ContextFactory.PROPERTY_TRUSTED_HOSTS, ""); //$NON-NLS-1$\r
137         if (proxyHost != null && proxyHost.length() > 0) {\r
138             keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.PROXY_HOST, proxyHost);\r
139             keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.PROXY_PORT, proxyPort);\r
140         }\r
141         if (trustedHosts != null) {\r
142             keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.TRUST_HOST_LIST,\r
143                     trustedHosts);\r
144         }\r
145 \r
146         Authentication authentication = new UsernamePassword(principal, credential);\r
147         TokensResource tokens = keystone.tokens();\r
148         TokensResource.Authenticate authenticate = tokens.authenticate(authentication);\r
149         if (projectIdentifier.length() == 32 && projectIdentifier.matches("[0-9a-fA-F]+")) { //$NON-NLS-1$\r
150             authenticate = authenticate.withTenantId(projectIdentifier);\r
151         } else {\r
152             authenticate = authenticate.withTenantName(projectIdentifier);\r
153         }\r
154 \r
155         /*\r
156          * We have to set up the TrackRequest TLS collection for the ExceptionMapper\r
157          */\r
158         trackRequest();\r
159         RequestState.put(RequestState.PROVIDER, "OpenStackProvider");\r
160         RequestState.put(RequestState.TENANT, projectIdentifier);\r
161         RequestState.put(RequestState.PRINCIPAL, principal);\r
162 \r
163         try {\r
164             access = authenticate.execute();\r
165             expiresLocal = getLocalExpiration(access);\r
166             tenant = access.getToken().getTenant();\r
167             tokenProvider = new OpenStackSimpleTokenProvider(access.getToken().getId());\r
168             keystone.setTokenProvider(tokenProvider);\r
169             parseServiceCatalog(access.getServiceCatalog());\r
170         } catch (OpenStackBaseException e) {\r
171             ExceptionMapper.mapException(e);\r
172         } catch (Exception ex) {\r
173             throw new ContextConnectionException(ex.getMessage());\r
174         }\r
175     }\r
176 \r
177     /**\r
178      * {@inheritDoc}\r
179      */\r
180     @Override\r
181     public List<Service.Endpoint> getEndpoints(String serviceType) {\r
182         Lock readLock = rwLock.readLock();\r
183         readLock.lock();\r
184         try {\r
185             return serviceEndpoints.get(serviceType);\r
186         } finally {\r
187             readLock.unlock();\r
188         }\r
189     }\r
190 \r
191     /**\r
192      * {@inheritDoc}\r
193      */\r
194     @Override\r
195     public String getProjectId() {\r
196         Lock readLock = rwLock.readLock();\r
197         readLock.lock();\r
198         try {\r
199             return tenant.getId();\r
200         } finally {\r
201             readLock.unlock();\r
202         }\r
203     }\r
204 \r
205     /**\r
206      * {@inheritDoc}\r
207      */\r
208     @Override\r
209     public String getProjectName() {\r
210         Lock readLock = rwLock.readLock();\r
211         readLock.lock();\r
212         try {\r
213             return tenant.getName();\r
214         } finally {\r
215             readLock.unlock();\r
216         }\r
217     }\r
218 \r
219     /**\r
220      * {@inheritDoc}\r
221      */\r
222     @Override\r
223     public Set<String> getRegions() {\r
224         Lock readLock = rwLock.readLock();\r
225         readLock.lock();\r
226         try {\r
227             return regions;\r
228         } finally {\r
229             readLock.unlock();\r
230         }\r
231     }\r
232 \r
233     /**\r
234      * {@inheritDoc}\r
235      */\r
236     @Override\r
237     public List<String> getServiceTypes() {\r
238         Lock readLock = rwLock.readLock();\r
239         readLock.lock();\r
240         try {\r
241             ArrayList<String> result = new ArrayList<>();\r
242             result.addAll(serviceTypes.keySet());\r
243             return result;\r
244         } finally {\r
245             readLock.unlock();\r
246         }\r
247     }\r
248 \r
249     /**\r
250      * {@inheritDoc}\r
251      */\r
252     @Override\r
253     public String getVMRegion(VMURL url) {\r
254         String region = null;\r
255         if (url == null) {\r
256             return region;\r
257         }\r
258 \r
259         Pattern urlPattern = Pattern.compile("[^:]+://([^:/]+)(?::([0-9]+)).*");\r
260 \r
261         for (Endpoint endpoint : getEndpoints(ServiceCatalog.COMPUTE_SERVICE)) {\r
262             String endpointUrl = endpoint.getPublicURL();\r
263             Matcher matcher = urlPattern.matcher(endpointUrl);\r
264             if (!matcher.matches() ||\r
265                 !url.getHost().equals(matcher.group(1)) ||\r
266                 (url.getPort() != null && !url.getPort().equals(matcher.group(2))) ) {\r
267                 continue;\r
268             }\r
269 \r
270             region = endpoint.getRegion();\r
271             break;\r
272         }\r
273 \r
274         return region;\r
275     }\r
276 \r
277     /**\r
278      * {@inheritDoc}\r
279      */\r
280     @Override\r
281     public boolean isServicePublished(String serviceType) {\r
282         Lock readLock = rwLock.readLock();\r
283         readLock.lock();\r
284         try {\r
285             return serviceTypes.containsKey(serviceType);\r
286         } finally {\r
287             readLock.unlock();\r
288         }\r
289     }\r
290 \r
291     /**\r
292      * {@inheritDoc}\r
293      */\r
294     @Override\r
295     public String toString() {\r
296 \r
297         StringBuilder builder = new StringBuilder();\r
298         Lock lock = rwLock.readLock();\r
299         lock.lock();\r
300         try {\r
301             builder.append(String.format("Service Catalog: tenant %s, id[%s], description[%s]%n", tenant.getName(), //$NON-NLS-1$\r
302                     tenant.getId(), tenant.getDescription()));\r
303             if (regions != null && !regions.isEmpty()) {\r
304                 builder.append(String.format("%d regions:%n", regions.size())); //$NON-NLS-1$\r
305                 for (String region : regions) {\r
306                     builder.append("\t" + region + "%n"); //$NON-NLS-1$ //$NON-NLS-2$\r
307                 }\r
308             }\r
309             builder.append(String.format("%d services:%n", serviceEndpoints.size())); //$NON-NLS-1$\r
310             for (String serviceType : serviceEndpoints.keySet()) {\r
311                 List<Service.Endpoint> endpoints = serviceEndpoints.get(serviceType);\r
312                 Service service = serviceTypes.get(serviceType);\r
313 \r
314                 builder.append(String.format("\t%s [%s] - %d endpoints%n", service.getType(), service.getName(), //$NON-NLS-1$\r
315                         endpoints.size()));\r
316                 for (Service.Endpoint endpoint : endpoints) {\r
317                     builder.append(String.format("\t\tRegion [%s], public URL [%s]%n", endpoint.getRegion(), //$NON-NLS-1$\r
318                             endpoint.getPublicURL()));\r
319                 }\r
320             }\r
321         } finally {\r
322             lock.unlock();\r
323         }\r
324 \r
325         return builder.toString();\r
326     }\r
327 \r
328     /**\r
329      * Parses the service catalog and caches the results\r
330      * \r
331      * @param services The list of services published by this provider\r
332      */\r
333     private void parseServiceCatalog(List<Service> services) {\r
334         Lock lock = rwLock.writeLock();\r
335         lock.lock();\r
336         try {\r
337             serviceTypes.clear();\r
338             serviceEndpoints.clear();\r
339             regions.clear();\r
340 \r
341             for (Service service : services) {\r
342                 String type = service.getType();\r
343                 serviceTypes.put(type, service);\r
344 \r
345                 List<Service.Endpoint> endpoints = service.getEndpoints();\r
346                 for (Service.Endpoint endpoint : endpoints) {\r
347                     List<Service.Endpoint> endpointList = serviceEndpoints.get(type);\r
348                     if (endpointList == null) {\r
349                         endpointList = new ArrayList<>();\r
350                         serviceEndpoints.put(type, endpointList);\r
351                     }\r
352                     endpointList.add(endpoint);\r
353 \r
354                     String region = endpoint.getRegion();\r
355                     if (!regions.contains(region)) {\r
356                         regions.add(region);\r
357                     }\r
358                 }\r
359             }\r
360         } finally {\r
361             lock.unlock();\r
362         }\r
363     }\r
364 \r
365     /**\r
366      * Computes the local time when the access token will expire, after which we will need to re-login to access the\r
367      * provider.\r
368      * \r
369      * @param accessKey The access key used to access the provider\r
370      * @return The local time the key expires\r
371      */\r
372     private static long getLocalExpiration(Access accessKey) {\r
373         Date now = Time.getCurrentUTCDate();\r
374         if (accessKey != null && accessKey.getToken() != null) {\r
375             Calendar issued = accessKey.getToken().getIssued_at();\r
376             Calendar expires = accessKey.getToken().getExpires();\r
377             if (issued != null && expires != null) {\r
378                 long tokenLife = expires.getTimeInMillis() - issued.getTimeInMillis();\r
379                 return now.getTime() + tokenLife;\r
380             }\r
381         }\r
382         return now.getTime();\r
383     }\r
384 }\r