Merge of new rebased code
[appc.git] / appc-adapters / appc-iaas-adapter / appc-iaas-adapter-bundle / src / main / java / org / openecomp / appc / adapter / iaas / impl / TenantCache.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * openECOMP : APP-C
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights
6  *                                              reserved.
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  * 
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  * 
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.openecomp.appc.adapter.iaas.impl;
23
24 import java.io.IOException;
25 import java.util.HashMap;
26 import java.util.Map;
27 import java.util.Properties;
28 import java.util.regex.Matcher;
29 import java.util.regex.Pattern;
30
31 import org.openecomp.appc.Constants;
32 import org.openecomp.appc.configuration.Configuration;
33 import org.openecomp.appc.configuration.ConfigurationFactory;
34 import org.openecomp.appc.i18n.Msg;
35 import org.openecomp.appc.pool.Allocator;
36 import org.openecomp.appc.pool.Destructor;
37 import org.openecomp.appc.pool.Pool;
38 import org.openecomp.appc.pool.PoolSpecificationException;
39 import com.att.cdp.exceptions.ContextConnectionException;
40 import com.att.cdp.exceptions.ZoneException;
41 import com.att.cdp.zones.Context;
42 import com.att.cdp.zones.ContextFactory;
43 import com.att.cdp.zones.Provider;
44 import com.att.eelf.configuration.EELFLogger;
45 import com.att.eelf.configuration.EELFManager;
46 import com.woorea.openstack.connector.JaxRs20Connector;
47 //import com.sun.jersey.api.client.ClientHandlerException;
48 import com.woorea.openstack.keystone.model.Access.Service.Endpoint;
49
50 /**
51  * This class maintains a cache of tenants within a specific provider.
52  * <p>
53  * Providers may be multi-tenant, such as OpenStack, where the available services and resources vary from one tenant to
54  * another. Therefore, the provider cache maintains a cache of tenants and the service catalogs for each, as well as the
55  * credentials used to access the tenants, and a pool of Context objects for each tenant. The context pool allows use of
56  * the CDP abstraction layer to access the services of the provider within the specific tenant.
57  * </p>
58  */
59 public class TenantCache implements Allocator<Context>, Destructor<Context> {
60
61     public static final String POOL_PROVIDER_NAME = "pool.provider.name";
62     public static final String POOL_TENANT_NAME = "pool.tenant.name";
63     //public static final String CLIENT_CONNECTOR_CLASS = "com.woorea.openstack.connector.JerseyConnector";
64     public static final String CLIENT_CONNECTOR_CLASS = "com.woorea.openstack.connector.JaxRs20Connector";
65     /**
66      * The provider we are part of
67      */
68     private ProviderCache provider;
69
70     /**
71      * The password used to authenticate
72      */
73     private String password;
74
75     /**
76      * The context pools by region used to access this tenant
77      */
78     private Map<String /* region */, Pool<Context>> pools = new HashMap<>();
79
80     /**
81      * The tenant id
82      */
83     private String tenantId;
84
85     /**
86      * The tenant name
87      */
88     private String tenantName;
89
90     /**
91      * The user id used to authenticate
92      */
93     private String userid;
94
95     /**
96      * The configuration of this adapter
97      */
98     private Configuration configuration;
99
100     /**
101      * The service catalog for this provider
102      */
103     private ServiceCatalog catalog;
104
105     /**
106      * Set to true when the cache has been initialized
107      */
108     private boolean initialized;
109
110     /**
111      * The logger to use
112      */
113     private EELFLogger logger;
114
115     /**
116      * Construct the cache of tenants for the specified provider
117      *
118      * @param provider
119      *            The provider
120      */
121     public TenantCache(ProviderCache provider) {
122         configuration = ConfigurationFactory.getConfiguration();
123         logger = EELFManager.getInstance().getLogger(getClass());
124         this.provider = provider;
125         configuration = ConfigurationFactory.getConfiguration();
126     }
127
128     /**
129      * @return True when the cache has been initialized. A tenant cache is initialized when the service catalog for the
130      *         tenant on the specified provider has been loaded and processed.
131      */
132     public boolean isInitialized() {
133         return initialized;
134     }
135
136     /**
137      * Initializes the tenant cache.
138      * <p>
139      * This method authenticates to the provider and obtains the service catalog. For the service catalog we can
140      * determine all supported regions for this provider, as well as all published services and their endpoints. We will
141      * cache and maintain a copy of the service catalog for later queries.
142      * </p>
143      * <p>
144      * Once the catalog has been obtained, we create a context pool for each region defined. The context allows access
145      * to services of a single region only, so we need a separate context by region. It is possible to operate on
146      * resources that span regions, but to do so will require acquiring a context for each region of interest.
147      * </p>
148      * <p>
149      * The context pool maintains the reusable context objects and allocates them as needed. This class is registered as
150      * the allocator and destructor for the pool, so that we can create a new context when needed, and close it when no
151      * longer used.
152      * </p>
153      */
154     public void initialize() {
155         logger.debug("Initializing TenantCache");
156
157         int min = configuration.getIntegerProperty(Constants.PROPERTY_MIN_POOL_SIZE);
158         int max = configuration.getIntegerProperty(Constants.PROPERTY_MAX_POOL_SIZE);
159         int delay = configuration.getIntegerProperty(Constants.PROPERTY_RETRY_DELAY);
160         int limit = configuration.getIntegerProperty(Constants.PROPERTY_RETRY_LIMIT);
161
162         String url = provider.getIdentityURL();
163         String tenant = tenantName == null ? tenantId : tenantName;
164         Properties properties = configuration.getProperties();
165
166         int attempt = 1;
167         while (attempt <= limit) {
168             try {
169                 catalog = new ServiceCatalog(url, tenant, userid, password, properties);
170                 tenantId = catalog.getTenantId();
171                 tenantName = catalog.getTenantName();
172
173                 for (String region : catalog.getRegions()) {
174                     try {
175                         Pool<Context> pool = new Pool<>(min, max);
176                         pool.setProperty(ContextFactory.PROPERTY_IDENTITY_URL, url);
177                         pool.setProperty(ContextFactory.PROPERTY_TENANT, tenantName);
178                         pool.setProperty(ContextFactory.PROPERTY_CLIENT_CONNECTOR_CLASS, CLIENT_CONNECTOR_CLASS);
179                         pool.setProperty(ContextFactory.PROPERTY_RETRY_DELAY,
180                             configuration.getProperty(Constants.PROPERTY_RETRY_DELAY));
181                         pool.setProperty(ContextFactory.PROPERTY_RETRY_LIMIT,
182                             configuration.getProperty(Constants.PROPERTY_RETRY_LIMIT));
183                         pool.setProperty(ContextFactory.PROPERTY_REGION, region);
184                         if (properties.getProperty(ContextFactory.PROPERTY_TRUSTED_HOSTS) != null) {
185                             pool.setProperty(ContextFactory.PROPERTY_TRUSTED_HOSTS,
186                                 properties.getProperty(ContextFactory.PROPERTY_TRUSTED_HOSTS));
187                         }
188                         pool.setAllocator(this);
189                         pool.setDestructor(this);
190                         pools.put(region, pool);
191                         logger.debug(String.format("Put pool for region %s", region));
192                     } catch (PoolSpecificationException e) {
193                         logger.error("Error creating pool", e);
194                         e.printStackTrace();
195                     }
196                 }
197                 initialized = true;
198                 break;
199             } catch (ContextConnectionException e) {
200                 attempt++;
201                 if (attempt <= limit) {
202                     logger.error(Msg.CONNECTION_FAILED_RETRY, provider.getProviderName(), url, tenantName, tenantId, e.getMessage(), Integer.toString(delay), Integer.toString(attempt),
203                             Integer.toString(limit));
204
205                     try {
206                         Thread.sleep(delay * 1000L);
207                     } catch (InterruptedException ie) {
208                         // ignore
209                     }
210                 }
211             } catch ( ZoneException e) {
212                 logger.error(e.getMessage());
213                 break;
214             }
215         }
216
217         if (!initialized) {
218             logger.error(Msg.CONNECTION_FAILED, provider.getProviderName(), url);
219         }
220     }
221
222     /**
223      * This method accepts a fully qualified compute node URL and uses that to determine which region of the provider
224      * hosts that compute node.
225      *
226      * @param url
227      *            The parsed URL of the compute node
228      * @return The region name, or null if no region of this tenant hosts that compute node.
229      */
230     public String determineRegion(VMURL url) {
231         logger.debug(String.format("Attempting to determine VM region for %s", url));
232         String region = null;
233         Pattern urlPattern = Pattern.compile("[^:]+://([^:/]+)(?::([0-9]+)).*");
234
235         if (url != null) {
236             for (Endpoint endpoint : catalog.getEndpoints(ServiceCatalog.COMPUTE_SERVICE)) {
237                 String endpointUrl = endpoint.getPublicURL();
238                 Matcher matcher = urlPattern.matcher(endpointUrl);
239                 if (matcher.matches()) {
240                     if (url.getHost().equals(matcher.group(1))) {
241                         if (url.getPort() != null) {
242                             if (!url.getPort().equals(matcher.group(2))) {
243                                 continue;
244                             }
245                         }
246
247                         region = endpoint.getRegion();
248                         break;
249                     }
250                 }
251             }
252         }
253         logger.debug(String.format("Region for %s is %s", url, region));
254         return region;
255     }
256
257     /**
258      * @return the value of provider
259      */
260     public ProviderCache getProvider() {
261         return provider;
262     }
263
264     /**
265      * @param provider
266      *            the value for provider
267      */
268     public void setProvider(ProviderCache provider) {
269         this.provider = provider;
270     }
271
272     /**
273      * @return the value of password
274      */
275     public String getPassword() {
276         return password;
277     }
278
279     /**
280      * @param password
281      *            the value for password
282      */
283     public void setPassword(String password) {
284         this.password = password;
285     }
286
287     /**
288      * @return the value of tenantId
289      */
290     public String getTenantId() {
291         return tenantId;
292     }
293
294     /**
295      * @param tenantId
296      *            the value for tenantId
297      */
298     public void setTenantId(String tenantId) {
299         this.tenantId = tenantId;
300     }
301
302     /**
303      * @return the value of tenantName
304      */
305     public String getTenantName() {
306         return tenantName;
307     }
308
309     /**
310      * @param tenantName
311      *            the value for tenantName
312      */
313     public void setTenantName(String tenantName) {
314         this.tenantName = tenantName;
315     }
316
317     /**
318      * @return the value of userid
319      */
320     public String getUserid() {
321         return userid;
322     }
323
324     /**
325      * @param userid
326      *            the value for userid
327      */
328     public void setUserid(String userid) {
329         this.userid = userid;
330     }
331
332     /**
333      * @return the value of pools
334      */
335     public Map<String, Pool<Context>> getPools() {
336         return pools;
337     }
338
339     /**
340      * @see org.openecomp.appc.pool.Allocator#allocate(org.openecomp.appc.pool.Pool)
341      */
342     @SuppressWarnings("unchecked")
343     @Override
344     public Context allocate(Pool<Context> pool) {
345         logger.debug("Allocationg context for pool");
346         Class<? extends Provider> providerClass;
347         try {
348             providerClass = (Class<? extends Provider>) Class.forName("com.att.cdp.openstack.OpenStackProvider");
349             // String providerType = provider.getProviderType();
350
351             // Context context = ContextFactory.getContext(providerType, pool.getProperties());
352             Context context = ContextFactory.getContext(providerClass, pool.getProperties());
353             context.login(userid, password);
354             return context;
355         } catch (IllegalStateException | IllegalArgumentException | ZoneException | ClassNotFoundException e) {
356             logger.debug("Failed to allocate context for pool", e);
357             e.printStackTrace();
358         }
359         return null;
360     }
361
362     /**
363      * @see org.openecomp.appc.pool.Destructor#destroy(java.lang.Object, org.openecomp.appc.pool.Pool)
364      */
365     @Override
366     public void destroy(Context context, Pool<Context> pool) {
367         try {
368             context.close();
369         } catch (IOException e) {
370             e.printStackTrace();
371         }
372     }
373
374     /**
375      * @return the service catalog for this provider
376      */
377     public ServiceCatalog getServiceCatalog() {
378         return catalog;
379     }
380 }