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