2 * ============LICENSE_START=======================================================
\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 * Modifications Copyright © 2018 IBM.
\r
10 * ================================================================================
\r
11 * Modifications Copyright (C) 2019 Ericsson
\r
12 * =============================================================================
\r
13 * Licensed under the Apache License, Version 2.0 (the "License");
\r
14 * you may not use this file except in compliance with the License.
\r
15 * You may obtain a copy of the License at
\r
17 * http://www.apache.org/licenses/LICENSE-2.0
\r
19 * Unless required by applicable law or agreed to in writing, software
\r
20 * distributed under the License is distributed on an "AS IS" BASIS,
\r
21 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
22 * See the License for the specific language governing permissions and
\r
23 * limitations under the License.
\r
25 * ============LICENSE_END=========================================================
\r
28 package org.onap.appc.adapter.iaas.impl;
\r
30 import java.util.ArrayList;
\r
31 import java.util.Calendar;
\r
32 import java.util.Date;
\r
33 import java.util.HashMap;
\r
34 import java.util.HashSet;
\r
35 import java.util.List;
\r
36 import java.util.Map;
\r
37 import java.util.Properties;
\r
38 import java.util.Set;
\r
39 import java.util.concurrent.locks.Lock;
\r
40 import java.util.concurrent.locks.ReentrantReadWriteLock;
\r
41 import java.util.regex.Matcher;
\r
42 import java.util.regex.Pattern;
\r
43 import com.att.cdp.exceptions.ContextConnectionException;
\r
44 import com.att.cdp.exceptions.ZoneException;
\r
45 import com.att.cdp.openstack.util.ExceptionMapper;
\r
46 import com.att.cdp.pal.util.Time;
\r
47 import com.att.cdp.zones.ContextFactory;
\r
48 import com.att.cdp.zones.spi.RequestState;
\r
49 import com.woorea.openstack.base.client.OpenStackBaseException;
\r
50 import com.woorea.openstack.base.client.OpenStackClientConnector;
\r
51 import com.woorea.openstack.base.client.OpenStackSimpleTokenProvider;
\r
52 import com.woorea.openstack.keystone.v3.Keystone;
\r
53 import com.woorea.openstack.keystone.v3.api.TokensResource;
\r
54 import com.woorea.openstack.keystone.v3.model.Authentication;
\r
55 import com.woorea.openstack.keystone.v3.model.Authentication.Identity;
\r
56 import com.woorea.openstack.keystone.v3.model.Authentication.Scope;
\r
57 import com.woorea.openstack.keystone.v3.model.Token;
\r
58 import com.woorea.openstack.keystone.v3.model.Token.Project;
\r
59 import com.woorea.openstack.keystone.v3.model.Token.Service;
\r
60 import com.woorea.openstack.keystone.v3.model.Token.Service.Endpoint;
\r
63 * This class is used to capture and cache the service catalog for a specific OpenStack provider.
\r
65 * This is needed because the way the servers are represented in the ECOMP product is as their fully qualified URL's.
\r
66 * This is very problematic, because we cant identify their region from the URL, URL's change, and we cant identify the
\r
67 * versions of the service implementations. In otherwords, the URL does not provide us enough information.
\r
70 * The zone abstraction layer is designed to detect the versions of the services dynamically, and step up or down to
\r
71 * match those reported versions. In order to do that, we need to know before hand what region we are accessing (since
\r
72 * the supported versions may be different by regions). We will need to authenticate to the identity service in order to
\r
73 * do this, plus we have to duplicate the code supporting proxies and trusted hosts that exists in the abstraction
\r
74 * layer, but that cant be helped.
\r
77 * What we do to circumvent this is connect to the provider using the lowest supported identity api, and read the entire
\r
78 * service catalog into this object. Then, we parse the vm URL to extract the host and port and match that to the
\r
79 * compute services defined in the catalog. When we find a compute service that has the same host name and port,
\r
80 * whatever region that service is supporting is the region for that server.
\r
83 * While we really only need to do this for compute nodes, there is no telling what other situations may arise where the
\r
84 * full service catalog may be needed. Also, there is very little additional cost (additional RAM) associated with
\r
85 * caching the full service catalog since there is no way to list only a portion of it.
\r
88 public class ServiceCatalogV3 extends ServiceCatalog {
\r
91 * The project that we are accessing
\r
93 private Project project;
\r
96 * A map of endpoints for each service organized by service type
\r
98 private Map<String /* Service Type */, List<Service.Endpoint>> serviceEndpoints;
\r
101 * A map of service types that are published
\r
103 private Map<String /* Service Type */, Service> serviceTypes;
\r
106 * The Openstack Access object that manages the authenticated token and access control
\r
108 private Token token;
\r
111 * A "token provider" that manages the authentication token that we obtain when logging in
\r
113 private OpenStackSimpleTokenProvider tokenProvider;
\r
118 public ServiceCatalogV3(String identityURL, String projectIdentifier, String principal, String credential,
\r
119 String domain, Properties properties) {
\r
120 super(identityURL, projectIdentifier, principal, credential, domain, properties);
\r
127 public void init() throws ZoneException {
\r
128 rwLock = new ReentrantReadWriteLock();
\r
129 serviceTypes = new HashMap<>();
\r
130 serviceEndpoints = new HashMap<>();
\r
131 regions = new HashSet<>();
\r
132 Class<?> connectorClass;
\r
133 OpenStackClientConnector connector;
\r
135 connectorClass = Class.forName(CLIENT_CONNECTOR_CLASS);
\r
136 connector = (OpenStackClientConnector) connectorClass.newInstance();
\r
137 } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
\r
138 logger.error("An error occurred when initializing ServiceCatalogV3", e);
\r
141 Keystone keystone = getKeystone(identityURL, connector);
\r
143 String proxyHost = properties.getProperty(ContextFactory.PROPERTY_PROXY_HOST);
\r
144 String proxyPort = properties.getProperty(ContextFactory.PROPERTY_PROXY_PORT);
\r
145 String trustedHosts = properties.getProperty(ContextFactory.PROPERTY_TRUSTED_HOSTS, ""); //$NON-NLS-1$
\r
146 if (proxyHost != null && proxyHost.length() > 0) {
\r
147 keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.PROXY_HOST, proxyHost);
\r
148 keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.PROXY_PORT, proxyPort);
\r
150 if (trustedHosts != null) {
\r
151 keystone.getProperties().setProperty(com.woorea.openstack.common.client.Constants.TRUST_HOST_LIST,
\r
156 Identity identity = Identity.password(domain, principal, credential);
\r
159 Scope scope = initScope();
\r
161 Authentication authentication = new Authentication();
\r
162 authentication.setIdentity(identity);
\r
163 authentication.setScope(scope);
\r
165 TokensResource tokens = keystone.tokens();
\r
166 TokensResource.Authenticate authenticate = tokens.authenticate(authentication);
\r
169 * We have to set up the TrackRequest TLS collection for the ExceptionMapper
\r
172 RequestState.put(RequestState.PROVIDER, "OpenStackProvider");
\r
173 RequestState.put(RequestState.TENANT, projectIdentifier);
\r
174 RequestState.put(RequestState.PRINCIPAL, principal);
\r
177 token = authenticate.execute();
\r
178 // Ensure that token is not null before accessing
\r
179 // local expiration or the internal details of token
\r
180 if (token == null) {
\r
181 throw new NullPointerException("Access token is null. Failed to init ServiceCatalogV3");
\r
183 expiresLocal = getLocalExpiration(token);
\r
184 project = token.getProject();
\r
185 tokenProvider = new OpenStackSimpleTokenProvider(token.getId());
\r
186 keystone.setTokenProvider(tokenProvider);
\r
187 parseServiceCatalog(token.getCatalog());
\r
188 } catch (OpenStackBaseException e) {
\r
189 ExceptionMapper.mapException(e);
\r
190 } catch (Exception e) {
\r
191 throw new ContextConnectionException(e);
\r
195 private Scope initScope() {
\r
196 if (projectIdentifier.length() == 32 && projectIdentifier.matches("[0-9a-fA-F]+")) { //$NON-NLS-1$
\r
197 return Scope.project(projectIdentifier);
\r
199 return Scope.project(domain, projectIdentifier);
\r
207 public List<Service.Endpoint> getEndpoints(String serviceType) {
\r
208 Lock readLock = rwLock.readLock();
\r
211 return serviceEndpoints.get(serviceType);
\r
221 public String getProjectId() {
\r
222 Lock readLock = rwLock.readLock();
\r
225 return project.getId();
\r
235 public String getProjectName() {
\r
236 return getProject().getName();
\r
243 public Set<String> getRegions() {
\r
244 Lock readLock = rwLock.readLock();
\r
257 public List<String> getServiceTypes() {
\r
258 Lock readLock = rwLock.readLock();
\r
261 ArrayList<String> result = new ArrayList<>();
\r
262 result.addAll(serviceTypes.keySet());
\r
273 public String getVMRegion(VMURL url) {
\r
274 String region = null;
\r
275 Pattern urlPattern = Pattern.compile("[^:]+://([^:/]+)(?::([0-9]+)).*");
\r
278 for (Endpoint endpoint : getEndpoints(ServiceCatalog.COMPUTE_SERVICE)) {
\r
279 String endpointUrl = endpoint.getUrl();
\r
280 Matcher matcher = urlPattern.matcher(endpointUrl);
\r
281 if (validateUrl(url, matcher)) {
\r
282 region = endpoint.getRegion();
\r
290 private boolean validateUrl(VMURL url, Matcher matcher) {
\r
291 return matcher.matches()
\r
292 && url.getHost().equals(matcher.group(1))
\r
293 && (url.getPort() == null || url.getPort().equals(matcher.group(2)));
\r
300 public boolean isServicePublished(String serviceType) {
\r
301 Lock readLock = rwLock.readLock();
\r
304 return serviceTypes.containsKey(serviceType);
\r
314 public String toString() {
\r
316 StringBuilder builder = new StringBuilder();
\r
317 Lock lock = rwLock.readLock();
\r
320 builder.append(String.format("Service Catalog: tenant %s, id[%s]%n", project.getName(), //$NON-NLS-1$
\r
322 if (regions != null && !regions.isEmpty()) {
\r
323 builder.append(String.format("%d regions:%n", regions.size())); //$NON-NLS-1$
\r
324 for (String region : regions) {
\r
325 //$NON-NLS-1$ //$NON-NLS-2$
\r
332 builder.append(String.format("%d services:%n", serviceEndpoints.size())); //$NON-NLS-1$
\r
334 for (Map.Entry<String, List<Service.Endpoint>> entry : serviceEndpoints.entrySet()) {
\r
335 Service service = serviceTypes.get(entry.getKey());
\r
337 builder.append(String.format("\t%s - %d endpoints%n", service.getType(), //$NON-NLS-1$
\r
338 entry.getValue().size()));
\r
340 for (Service.Endpoint endpoint : entry.getValue()) {
\r
342 .append(String.format("\t\tRegion [%s], public URL [%s]%n", endpoint.getRegion(), //$NON-NLS-1$
\r
343 endpoint.getUrl()));
\r
350 return builder.toString();
\r
354 * Parses the service catalog and caches the results
\r
356 * @param services The list of services published by this provider
\r
358 private void parseServiceCatalog(List<Service> services) {
\r
359 Lock lock = rwLock.writeLock();
\r
362 serviceTypes.clear();
\r
363 serviceEndpoints.clear();
\r
366 for (Service service : services) {
\r
367 String type = service.getType();
\r
368 serviceTypes.put(type, service);
\r
369 addRegions(service, type);
\r
376 private void addRegions(Service service, String type) {
\r
377 List<Endpoint> endpoints = service.getEndpoints();
\r
378 for (Endpoint endpoint : endpoints) {
\r
379 serviceEndpoints.computeIfAbsent(type, val -> new ArrayList<>());
\r
380 serviceEndpoints.get(type).add(endpoint);
\r
382 String region = endpoint.getRegion();
\r
383 if (!regions.contains(region)) {
\r
384 regions.add(region);
\r
390 * Computes the local time when the access token will expire, after which we will need to re-login to access the
\r
393 * @param accessKey The access key used to access the provider
\r
394 * @return The local time the key expires
\r
396 private static long getLocalExpiration(Token accessToken) {
\r
397 Date now = Time.getCurrentUTCDate();
\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
404 return now.getTime();
\r
407 public Project getProject() {
\r
408 Lock readLock = rwLock.readLock();
\r
417 protected Keystone getKeystone(String identityURL, OpenStackClientConnector connector) {
\r
418 return new Keystone(identityURL, connector);
\r