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