ac244b167550249f9f7bd4df0fbaedcad6ba2296
[appc.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP : APPC
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Copyright (C) 2017 Amdocs
8  * =============================================================================
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  * 
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  * 
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  * 
21  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22  * ============LICENSE_END=========================================================
23  */
24
25 package org.onap.appc.adapter.iaas.impl;
26
27 import com.att.cdp.exceptions.ContextConnectionException;
28 import com.att.cdp.exceptions.ZoneException;
29 import com.att.cdp.zones.Context;
30 import com.att.cdp.zones.ContextFactory;
31 import com.att.cdp.zones.Provider;
32 import com.att.eelf.configuration.EELFLogger;
33 import com.att.eelf.configuration.EELFManager;
34 import java.io.IOException;
35 import java.util.HashMap;
36 import java.util.Map;
37 import java.util.Properties;
38 import org.onap.appc.Constants;
39 import org.onap.appc.configuration.Configuration;
40 import org.onap.appc.configuration.ConfigurationFactory;
41 import org.onap.appc.i18n.Msg;
42 import org.onap.appc.pool.Allocator;
43 import org.onap.appc.pool.Destructor;
44 import org.onap.appc.pool.Pool;
45 import org.onap.appc.pool.PoolSpecificationException;
46
47 /**
48  * This class maintains a cache of tenants within a specific provider.
49  * <p>
50  * Providers may be multi-tenant, such as OpenStack, where the available services and resources vary from one tenant to
51  * another. Therefore, the provider cache maintains a cache of tenants and the service catalogs for each, as well as the
52  * credentials used to access the tenants, and a pool of Context objects for each tenant. The context pool allows use of
53  * the CDP abstraction layer to access the services of the provider within the specific tenant.
54  * </p>
55  */
56 public class TenantCache implements Allocator<Context>, Destructor<Context> {
57
58     public static final String POOL_PROVIDER_NAME = "pool.provider.name";
59     public static final String POOL_TENANT_NAME = "pool.tenant.name";
60     public static final String CLIENT_CONNECTOR_CLASS = "com.woorea.openstack.connector.JaxRs20Connector";
61     /**
62      * The domain to use to authenticate
63      */
64     private String domain;
65
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 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         catalog = ServiceCatalogFactory.getServiceCatalog(url, tenant, userid, password, domain, properties);
166
167         if (catalog == null) {
168             logger.error(Msg.IAAS_UNSUPPORTED_IDENTITY_SERVICE, url);
169             return;
170         }
171
172         int attempt = 1;
173         while (attempt <= limit) {
174             try {
175                 catalog.init();
176                 tenantId = catalog.getProjectId();
177                 tenantName = catalog.getProjectName();
178                 createPools(min, max, url, properties);
179                 initialized = true;
180                 break;
181             } catch (ContextConnectionException e) {
182                 if (++attempt <= limit) {
183                     logger.error(Msg.CONNECTION_FAILED_RETRY, provider.getProviderName(), url, tenantName, tenantId,
184                             e.getMessage(), Integer.toString(delay), Integer.toString(attempt),
185                             Integer.toString(limit));
186                     sleep(delay);
187                 }
188             } catch (ZoneException e) {
189                 logger.error("An error occurred when initializing cache", e);
190                 break;
191             }
192         }
193
194         if (!initialized) {
195             logger.error(Msg.CONNECTION_FAILED, provider.getProviderName(), url);
196         }
197     }
198
199     private void createPools(int min, int max, String url, Properties properties) {
200         for (String region : catalog.getRegions()) {
201             try {
202                 Pool<Context> pool = new Pool<>(min, max);
203                 pool.setProperty(ContextFactory.PROPERTY_IDENTITY_URL, url);
204                 pool.setProperty(ContextFactory.PROPERTY_TENANT, tenantName);
205                 pool.setProperty(ContextFactory.PROPERTY_CLIENT_CONNECTOR_CLASS, CLIENT_CONNECTOR_CLASS);
206                 pool.setProperty(ContextFactory.PROPERTY_RETRY_DELAY,
207                         configuration.getProperty(Constants.PROPERTY_RETRY_DELAY));
208                 pool.setProperty(ContextFactory.PROPERTY_RETRY_LIMIT,
209                         configuration.getProperty(Constants.PROPERTY_RETRY_LIMIT));
210                 pool.setProperty(ContextFactory.PROPERTY_REGION, region);
211                 if (properties.getProperty(ContextFactory.PROPERTY_TRUSTED_HOSTS) != null) {
212                     pool.setProperty(ContextFactory.PROPERTY_TRUSTED_HOSTS,
213                             properties.getProperty(ContextFactory.PROPERTY_TRUSTED_HOSTS));
214                 }
215                 pool.setAllocator(this);
216                 pool.setDestructor(this);
217                 pools.put(region, pool);
218                 logger.debug(String.format("Put pool for region %s", region));
219             } catch (PoolSpecificationException e) {
220                 logger.error("Error creating pool", e);
221             }
222         }
223     }
224
225     private void sleep(int delay) {
226         try {
227             Thread.sleep(delay * 1000L);
228         } catch (InterruptedException ie) {
229             // ignore
230         }
231     }
232
233     /**
234      * This method accepts a fully qualified compute node URL and uses that to determine which region of the provider
235      * hosts that compute node.
236      *
237      * @param url The parsed URL of the compute node
238      * @return The region name, or null if no region of this tenant hosts that compute node.
239      */
240     public String determineRegion(VMURL url) {
241         logger.debug(String.format("Attempting to determine VM region for %s", url));
242         String region = catalog.getVMRegion(url);
243         logger.debug(String.format("Region for %s is %s", url, region));
244         return region;
245     }
246
247     /**
248      * @return the value of the domain
249      */
250     public String getDomain() {
251         return domain;
252     }
253
254     /**
255      * @param domain the value for domain
256      */
257     public void setDomain(String domain) {
258         this.domain = domain;
259     }
260
261     /**
262      * @return the value of provider
263      */
264     public ProviderCache getProvider() {
265         return provider;
266     }
267
268     /**
269      * @param provider the value for provider
270      */
271     public void setProvider(ProviderCache provider) {
272         this.provider = provider;
273     }
274
275     /**
276      * @return the value of password
277      */
278     public String getPassword() {
279         return password;
280     }
281
282     /**
283      * @param password the value for password
284      */
285     public void setPassword(String password) {
286         this.password = password;
287     }
288
289     /**
290      * @return the value of tenantId
291      */
292     public String getTenantId() {
293         return tenantId;
294     }
295
296     /**
297      * @param tenantId 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 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 the value for userid
326      */
327     public void setUserid(String userid) {
328         this.userid = userid;
329     }
330
331     /**
332      * @return the value of pools
333      */
334     public Map<String, Pool<Context>> getPools() {
335         return pools;
336     }
337
338     /**
339      * @see org.onap.appc.pool.Allocator#allocate(org.onap.appc.pool.Pool)
340      */
341     @SuppressWarnings("unchecked")
342     @Override
343     public Context allocate(Pool<Context> pool) {
344         logger.debug("Allocationg context for pool");
345         Class<? extends Provider> providerClass;
346         try {
347             providerClass = (Class<? extends Provider>) Class.forName("com.att.cdp.openstack.OpenStackProvider");
348             Context context = ContextFactory.getContext(providerClass, pool.getProperties());
349             context.login(userid, password);
350             return context;
351         } catch (IllegalStateException | IllegalArgumentException | ZoneException | ClassNotFoundException e) {
352             logger.debug("Failed to allocate context for pool", e);
353         }
354         return null;
355     }
356
357     /**
358      * @see org.onap.appc.pool.Destructor#destroy(java.lang.Object, org.onap.appc.pool.Pool)
359      */
360     @Override
361     public void destroy(Context context, Pool<Context> pool) {
362         try {
363             context.close();
364         } catch (IOException e) {
365             logger.error("An error occurred when destroying cache", e);
366         }
367     }
368
369     /**
370      * @return the service catalog for this provider
371      */
372     public ServiceCatalog getServiceCatalog() {
373         return catalog;
374     }
375 }