4ef6fbd247fb1a239d6dc5feeb4b11967abbf26d
[vid.git] / vid-app-common / src / main / java / org / onap / vid / aai / AaiClient.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * VID
4  * ================================================================================
5  * Copyright (C) 2017 - 2019 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.vid.aai;
22
23 import static java.util.Collections.emptyList;
24 import static java.util.stream.Collectors.toMap;
25 import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
26 import static org.apache.commons.lang3.StringUtils.equalsIgnoreCase;
27 import static org.apache.commons.lang3.StringUtils.isEmpty;
28
29 import com.fasterxml.jackson.databind.JsonNode;
30 import com.fasterxml.jackson.databind.ObjectMapper;
31 import java.io.IOException;
32 import java.io.UnsupportedEncodingException;
33 import java.net.URI;
34 import java.net.URLEncoder;
35 import java.util.LinkedHashMap;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.UUID;
39 import java.util.function.Function;
40 import javax.inject.Inject;
41 import javax.ws.rs.WebApplicationException;
42 import javax.ws.rs.core.Response;
43 import org.apache.commons.lang3.StringUtils;
44 import org.apache.http.HttpStatus;
45 import org.apache.http.client.utils.URIBuilder;
46 import org.jetbrains.annotations.NotNull;
47 import org.json.simple.JSONArray;
48 import org.json.simple.JSONObject;
49 import org.json.simple.parser.JSONParser;
50 import org.onap.portalsdk.core.logging.logic.EELFLoggerDelegate;
51 import org.onap.vid.aai.exceptions.InvalidAAIResponseException;
52 import org.onap.vid.aai.model.AaiGetAicZone.AicZones;
53 import org.onap.vid.aai.model.AaiGetInstanceGroupsByCloudRegion;
54 import org.onap.vid.aai.model.AaiGetNetworkCollectionDetails.AaiGetNetworkCollectionDetails;
55 import org.onap.vid.aai.model.AaiGetNetworkCollectionDetails.AaiGetNetworkCollectionDetailsHelper;
56 import org.onap.vid.aai.model.AaiGetNetworkCollectionDetails.AaiGetRelatedInstanceGroupsByVnfId;
57 import org.onap.vid.aai.model.AaiGetNetworkCollectionDetails.CloudRegion;
58 import org.onap.vid.aai.model.AaiGetNetworkCollectionDetails.InstanceGroup;
59 import org.onap.vid.aai.model.AaiGetNetworkCollectionDetails.Network;
60 import org.onap.vid.aai.model.AaiGetOperationalEnvironments.OperationalEnvironmentList;
61 import org.onap.vid.aai.model.AaiGetPnfResponse;
62 import org.onap.vid.aai.model.AaiGetPnfs.Pnf;
63 import org.onap.vid.aai.model.AaiGetServicesRequestModel.GetServicesAAIRespone;
64 import org.onap.vid.aai.model.AaiGetTenatns.GetTenantsResponse;
65 import org.onap.vid.aai.model.CustomQuerySimpleResult;
66 import org.onap.vid.aai.model.GetServiceModelsByDistributionStatusResponse;
67 import org.onap.vid.aai.model.LogicalLinkResponse;
68 import org.onap.vid.aai.model.OwningEntityResponse;
69 import org.onap.vid.aai.model.PortDetailsTranslator;
70 import org.onap.vid.aai.model.ProjectResponse;
71 import org.onap.vid.aai.model.Properties;
72 import org.onap.vid.aai.model.ResourceType;
73 import org.onap.vid.aai.model.ServiceRelationships;
74 import org.onap.vid.aai.model.SimpleResult;
75 import org.onap.vid.aai.util.AAIRestInterface;
76 import org.onap.vid.aai.util.CacheProvider;
77 import org.onap.vid.aai.util.VidObjectMapperType;
78 import org.onap.vid.exceptions.GenericUncheckedException;
79 import org.onap.vid.model.SubscriberList;
80 import org.onap.vid.model.probes.ErrorMetadata;
81 import org.onap.vid.model.probes.ExternalComponentStatus;
82 import org.onap.vid.model.probes.HttpRequestMetadata;
83 import org.onap.vid.utils.Logging;
84 import org.onap.vid.utils.Unchecked;
85 import org.springframework.http.HttpMethod;
86 import org.springframework.web.util.UriUtils;
87
88 /**
89
90  * Created by Oren on 7/4/17.
91  */
92 public class AaiClient implements AaiClientInterface {
93
94
95     private static final String QUERY_FORMAT_RESOURCE = "query?format=resource";
96     private static final String SERVICE_SUBSCRIPTIONS_PATH = "/service-subscriptions/service-subscription/";
97     private static final String MODEL_INVARIANT_ID = "&model-invariant-id=";
98     private static final String QUERY_FORMAT_SIMPLE = "query?format=simple";
99     private static final String BUSINESS_CUSTOMER = "/business/customers/customer/";
100     private static final String SERVICE_INSTANCE = "/service-instances/service-instance/";
101     private static final String BUSINESS_CUSTOMERS_CUSTOMER = "business/customers/customer/";
102
103     protected String fromAppId = "VidAaiController";
104
105     private PortDetailsTranslator portDetailsTranslator;
106
107     private final AAIRestInterface restController;
108
109     private final CacheProvider cacheProvider;
110
111     ObjectMapper objectMapper = new ObjectMapper();
112
113     /**
114      * The logger
115      */
116     EELFLoggerDelegate logger = EELFLoggerDelegate.getLogger(AaiClient.class);
117
118     private static final String GET_SERVICE_MODELS_RESPONSE_BODY = "{\"start\" : \"service-design-and-creation/models/\", \"query\" : \"query/serviceModels-byDistributionStatus?distributionStatus=DISTRIBUTION_COMPLETE_OK\"}";
119
120     @Inject
121     public AaiClient(AAIRestInterface restController, PortDetailsTranslator portDetailsTranslator, CacheProvider cacheProvider) {
122         this.restController = restController;
123         this.portDetailsTranslator = portDetailsTranslator;
124         this.cacheProvider = cacheProvider;
125     }
126
127
128     private static String checkForNull(String local) {
129         if (local != null)
130             return local;
131         else
132             return "";
133
134     }
135
136     @Override
137     public AaiResponse getServicesByOwningEntityId(List<String> owningEntityIds){
138         Response resp = doAaiGet(getUrlFromLIst("business/owning-entities?", "owning-entity-id=", owningEntityIds), false);
139         return processAaiResponse(resp, OwningEntityResponse.class, null);
140     }
141
142     @Override
143     public AaiResponse getServicesByProjectNames(List<String> projectNames){
144         Response resp = doAaiGet(getUrlFromLIst("business/projects?", "project-name=",  projectNames), false);
145         return processAaiResponse(resp, ProjectResponse.class, null);
146     }
147
148     @Override
149     public AaiResponse getServiceModelsByDistributionStatus() {
150         return getFromCache("getServiceModelsByDistributionStatus", this::getServiceModelsByDistributionStatusNonCached,
151                 true, "Failed to get service models by distribution status");
152     }
153
154     private AaiResponse getServiceModelsByDistributionStatusNonCached(boolean propagateExceptions) {
155         GetServiceModelsByDistributionStatusResponse response = typedAaiRest(QUERY_FORMAT_RESOURCE, GetServiceModelsByDistributionStatusResponse.class,
156                 GET_SERVICE_MODELS_RESPONSE_BODY, HttpMethod.PUT, propagateExceptions);
157         return new AaiResponse(response, "", HttpStatus.SC_OK);
158     }
159
160     @Override
161     public AaiResponse getNetworkCollectionDetails(String serviceInstanceId) {
162         Response resp = doAaiPut(QUERY_FORMAT_RESOURCE, "{\"start\": [\"nodes/service-instances/service-instance/" + serviceInstanceId + "\"],\"query\": \"query/network-collection-ByServiceInstance\"}\n", false);
163         AaiResponse<AaiGetNetworkCollectionDetailsHelper> aaiResponse = processAaiResponse(resp, AaiGetNetworkCollectionDetailsHelper.class, null, VidObjectMapperType.FASTERXML);
164         return getNetworkCollectionDetailsResponse(aaiResponse);
165     }
166
167     @Override
168     public AaiResponse getInstanceGroupsByCloudRegion(String cloudOwner, String cloudRegionId, String networkFunction) {
169         Response resp = doAaiPut(QUERY_FORMAT_RESOURCE,
170                 "{\"start\": [\"cloud-infrastructure/cloud-regions/cloud-region/" + encodePathSegment(cloudOwner) + "/" + encodePathSegment(cloudRegionId) + "\"]," +
171                         "\"query\": \"query/instance-groups-byCloudRegion?type=L3-NETWORK&role=SUB-INTERFACE&function=" + encodePathSegment(networkFunction) + "\"}\n", false);
172         return processAaiResponse(resp, AaiGetInstanceGroupsByCloudRegion.class, null, VidObjectMapperType.FASTERXML);
173     }
174
175     private AaiResponse getNetworkCollectionDetailsResponse(AaiResponse<AaiGetNetworkCollectionDetailsHelper> aaiResponse){
176         if(aaiResponse.getHttpCode() == 200) {
177             ObjectMapper om = objectMapper;
178             AaiGetNetworkCollectionDetails aaiGetNetworkCollectionDetails = new AaiGetNetworkCollectionDetails();
179             try {
180                 for (int i = 0; i < aaiResponse.getT().getResults().size(); i++) {
181                     LinkedHashMap<String, Object> temp = ((LinkedHashMap) aaiResponse.getT().getResults().get(i));
182                     if (temp.get("service-instance") != null)
183                         aaiGetNetworkCollectionDetails.getResults().setServiceInstance(om.readValue(om.writeValueAsString(temp.get("service-instance")), org.onap.vid.aai.model.AaiGetNetworkCollectionDetails.ServiceInstance.class));
184                     else if (temp.get("collection") != null)
185                         aaiGetNetworkCollectionDetails.getResults().setCollection(om.readValue(om.writeValueAsString(temp.get("collection")), org.onap.vid.aai.model.AaiGetNetworkCollectionDetails.Collection.class));
186                     else if (temp.get("instance-group") != null)
187                         aaiGetNetworkCollectionDetails.getResults().setInstanceGroup(om.readValue(om.writeValueAsString(temp.get("instance-group")), InstanceGroup.class));
188                     else if (temp.get("l3-network") != null)
189                         aaiGetNetworkCollectionDetails.getResults().getNetworks().add(om.readValue(om.writeValueAsString(temp.get("l3-network")), Network.class));
190                 }
191                 return new AaiResponse(aaiGetNetworkCollectionDetails, null, HttpStatus.SC_OK);
192             }
193             catch (com.fasterxml.jackson.databind.JsonMappingException e) {
194                 logger.error(EELFLoggerDelegate.errorLogger, "AAI response parsing Error",  e);
195                 return new AaiResponse(e.getCause(), "AAI response parsing Error" , HttpStatus.SC_INTERNAL_SERVER_ERROR);
196             }
197             catch (Exception e) {
198                 logger.error(EELFLoggerDelegate.errorLogger,"Exception in aai response parsing", e);
199                 return new AaiResponse(e.getCause(), "Got " + aaiResponse.getHttpCode() + " from a&ai" , aaiResponse.getHttpCode());
200             }
201         }
202         return aaiResponse;
203     }
204
205     @Override
206     public AaiResponse getPNFData(String globalCustomerId, String serviceType, String modelVersionId, String modelInvariantId, String cloudRegion, String equipVendor, String equipModel) {
207         String siQuery = BUSINESS_CUSTOMER + globalCustomerId + SERVICE_SUBSCRIPTIONS_PATH + encodePathSegment(serviceType) + "/service-instances?model-version-id=" + modelVersionId + MODEL_INVARIANT_ID + modelInvariantId;
208         String pnfQuery = "query/pnf-fromModel-byRegion?cloudRegionId=" + encodePathSegment(cloudRegion) + "&equipVendor=" + encodePathSegment(equipVendor) + "&equipModel=" + encodePathSegment(equipModel);
209         String payload = "{\"start\":\"" + siQuery + "\",\"query\":\"" + pnfQuery + "\"}";
210         Response resp = doAaiPut(QUERY_FORMAT_SIMPLE, payload, false);
211         return processAaiResponse(resp, AaiGetPnfResponse.class, null);
212     }
213
214
215     @Override
216     public AaiResponse<Pnf> getSpecificPnf(String pnfId) {
217         Response resp = doAaiGet("network/pnfs/pnf/"+pnfId, false);
218         return processAaiResponse(resp, Pnf.class, null);
219     }
220
221
222     public AaiResponse getInstanceGroupsByVnfInstanceId(String vnfInstanceId){
223         Response resp = doAaiGet("network/generic-vnfs/generic-vnf/" + vnfInstanceId + "?depth=0", false);
224         return processAaiResponse(resp, AaiGetRelatedInstanceGroupsByVnfId.class , null, null);
225     }
226
227
228     @Override
229     public List<PortDetailsTranslator.PortDetails> getPortMirroringSourcePorts(String configurationID) {
230         String payload = "{\"start\":\"/network/configurations/configuration/" + configurationID + "\",\"query\":\"query/pserver-fromConfiguration\"}";
231         Response resp = doAaiPut(QUERY_FORMAT_SIMPLE, payload, false);
232         resp.bufferEntity(); // avoid later "Entity input stream has already been closed" problems
233         String rawPayload = resp.readEntity(String.class);
234         AaiResponse<CustomQuerySimpleResult> aaiResponse = processAaiResponse(resp, CustomQuerySimpleResult.class, rawPayload);
235         return portDetailsTranslator.extractPortDetails(aaiResponse, rawPayload);
236     }
237
238
239
240     public AaiResponse getServiceInstance(String globalCustomerId, String serviceType, String serviceInstanceId) {
241         String getServiceInstancePath = BUSINESS_CUSTOMERS_CUSTOMER + globalCustomerId+ SERVICE_SUBSCRIPTIONS_PATH +serviceType+ SERVICE_INSTANCE +serviceInstanceId;
242         Response resp = doAaiGet(getServiceInstancePath , false);
243         return processAaiResponse(resp, ServiceRelationships.class, null);
244     }
245
246     @Override
247     public AaiResponse getLogicalLink(String link) {
248         Response resp = doAaiGet("network/logical-links/logical-link/" + link , false);
249         return processAaiResponse(resp, LogicalLinkResponse.class, null);
250     }
251
252     @Override
253     public boolean isNodeTypeExistsByName(String name, ResourceType type) {
254         if (isEmpty(name)) {
255             throw new GenericUncheckedException("Empty resource-name provided to searchNodeTypeByName; request is rejected as this will cause full resources listing");
256         }
257
258         URI path = Unchecked.toURI(String.format( // e.g. GET /aai/v$/nodes/vf-modules?vf-module-name={vf-module-name}
259                 "nodes/%s?%s=%s",
260                 type.getAaiFormat(),
261                 type.getNameFilter(),
262                 encodePathSegment(name)
263         ));
264         final ResponseWithRequestInfo responseWithRequestInfo = restController.RestGet(fromAppId, UUID.randomUUID().toString(), path, false, true);
265
266         return isResourceExistByStatusCode(responseWithRequestInfo);
267     }
268
269     public Map<String, Properties> getCloudRegionAndTenantByVnfId(String vnfId) {
270         String start = "/network/generic-vnfs/generic-vnf/" + vnfId;
271         String query = "/query/cloud-region-fromVnf";
272
273         String payload = "{\"start\":[\"" + start + "\"],\"query\":\"" + query + "\"}";
274         CustomQuerySimpleResult result = typedAaiRest(QUERY_FORMAT_SIMPLE, CustomQuerySimpleResult.class, payload, HttpMethod.PUT, false);
275
276         return result.getResults().stream()
277                 .filter(res -> StringUtils.equals(res.getNodeType(), "tenant") ||
278                         StringUtils.equals(res.getNodeType(), "cloud-region"))
279                 .collect(toMap(SimpleResult::getNodeType, SimpleResult::getProperties));
280     }
281
282     private boolean isResourceExistByStatusCode(ResponseWithRequestInfo responseWithRequestInfo) {
283         // 200 - is found
284         // 404 - resource not found
285         Response.Status statusInfo = responseWithRequestInfo.getResponse().getStatusInfo().toEnum();
286         switch (statusInfo) {
287             case OK:
288                 return true;
289             case NOT_FOUND:
290                 return false;
291             default:
292                 throw new GenericUncheckedException("Unexpected response-code (only OK and NOT_FOUND are expected): " +
293                         responseWithRequestInfo.getResponse().getStatusInfo());
294         }
295     }
296
297     @Override
298     public <T> T typedAaiGet(URI uri, Class<T> clz) {
299         return typedAaiRest(uri, clz, null, HttpMethod.GET, false);
300     }
301
302     public <T> T typedAaiRest(String path, Class<T> clz, String payload, HttpMethod method, boolean propagateExceptions) {
303         return typedAaiRest(Unchecked.toURI(path), clz, payload, method, propagateExceptions);
304     }
305
306
307     public <T> T typedAaiRest(URI path, Class<T> clz, String payload, HttpMethod method, boolean propagateExceptions) {
308         ResponseWithRequestInfo responseWithRequestInfo;
309         try {
310             responseWithRequestInfo = restController.doRest(fromAppId, UUID.randomUUID().toString(), path, payload, method, false, propagateExceptions);
311         } catch (Exception e) {
312             responseWithRequestInfo = handleExceptionFromRestCall(propagateExceptions, "doAai"+method.name(), e);
313         }
314
315         final AaiResponseWithRequestInfo<T> aaiResponse = processAaiResponse(responseWithRequestInfo, clz, VidObjectMapperType.FASTERXML, true);
316
317         if (aaiResponse.getAaiResponse().getHttpCode() > 399 || aaiResponse.getAaiResponse().getT() == null) {
318             throw new ExceptionWithRequestInfo(aaiResponse.getHttpMethod(),
319                     aaiResponse.getRequestedUrl(),
320                     aaiResponse.getRawData(),
321                     responseWithRequestInfo.getResponse().getStatus(),
322                     new InvalidAAIResponseException(aaiResponse.getAaiResponse()));
323         }
324
325         return aaiResponse.getAaiResponse().getT();
326     }
327
328
329     private String getUrlFromLIst(String url, String paramKey, List<String> params){
330         int i = 0;
331         for(String param: params){
332             i ++;
333             url = url.concat(paramKey);
334             String encodedParam= param;
335             try {
336                 encodedParam= URLEncoder.encode(param, "UTF-8");
337             } catch (UnsupportedEncodingException e) {
338                 String methodName = "getUrlFromList";
339                 logger.error(EELFLoggerDelegate.errorLogger, methodName, e);
340                 logger.debug(EELFLoggerDelegate.debugLogger, methodName, e);
341             }
342             url = url.concat(encodedParam);
343             if(i != params.size()){
344                 url = url.concat("&");
345             }
346         }
347         return url;
348     }
349
350
351
352     @Override
353     public AaiResponse<SubscriberList> getAllSubscribers() {
354         return getFromCache("getAllSubscribers", this::getAllSubscribersNonCached, true, "Failed to get all subscribers");
355     }
356
357     private <K> AaiResponse getFromCache(String cacheName, Function<K, AaiResponse> function, K argument, String errorMessage) {
358         try {
359             return cacheProvider
360                     .aaiClientCacheFor(cacheName, function)
361                     .get(argument);
362         } catch (ExceptionWithRequestInfo exception) {
363             logger.error(errorMessage, exception);
364             return new AaiResponse(null, exception.getRawData(), exception.getHttpCode());
365         }
366         catch (Exception exception) {
367             logger.error(errorMessage, exception);
368             return new AaiResponse(null, exception.getMessage(), HttpStatus.SC_INTERNAL_SERVER_ERROR);
369         }
370     }
371
372     private AaiResponse<SubscriberList> getAllSubscribersNonCached(boolean propagateExceptions) {
373         AaiResponse<SubscriberList> aaiResponse = getAllSubscribers(propagateExceptions).getAaiResponse();
374         if (propagateExceptions && (aaiResponse.getT() == null || aaiResponse.getT().customer == null || aaiResponse.getT().customer.isEmpty())) {
375             throw new GenericUncheckedException("Failed to get Subscribers data. The data is null or empty.");
376         } else {
377             return aaiResponse;
378         }
379     }
380
381     AaiResponseWithRequestInfo<SubscriberList> getAllSubscribers(boolean propagateExceptions){
382         String depth = "0";
383         ResponseWithRequestInfo aaiGetResult = doAaiGet("business/customers?subscriber-type=INFRA&depth=" + depth, false, propagateExceptions);
384         AaiResponseWithRequestInfo<SubscriberList> responseWithRequestInfo = processAaiResponse(aaiGetResult, SubscriberList.class, propagateExceptions);
385         responseWithRequestInfo.setRequestedUrl(aaiGetResult.getRequestUrl());
386         responseWithRequestInfo.setHttpMethod(aaiGetResult.getRequestHttpMethod());
387         return responseWithRequestInfo;
388     }
389
390
391     @Override
392     public AaiResponse getAllAicZones() {
393         Response resp = doAaiGet("network/zones", false);
394         return processAaiResponse(resp, AicZones.class, null);
395     }
396
397     @Override
398     public AaiResponse getVNFData(String globalSubscriberId, String serviceType) {
399         String payload = "{\"start\": [\"business/customers/customer/" + globalSubscriberId + SERVICE_SUBSCRIPTIONS_PATH + encodePathSegment(serviceType) +"/service-instances\"]," +
400                 "\"query\": \"query/vnf-topology-fromServiceInstance\"}";
401         Response resp = doAaiPut(QUERY_FORMAT_SIMPLE, payload, false);
402         return processAaiResponse(resp, AaiGetVnfResponse.class, null);
403     }
404
405     @Override
406     public AaiResponse getVNFData(String globalSubscriberId, String serviceType, String serviceInstanceId) {
407         String payload = "{\"start\": [\"/business/customers/customer/" + globalSubscriberId + SERVICE_SUBSCRIPTIONS_PATH + encodePathSegment(serviceType) + SERVICE_INSTANCE + serviceInstanceId + "\"],       \"query\": \"query/vnf-topology-fromServiceInstance\"}";
408         Response resp = doAaiPut(QUERY_FORMAT_SIMPLE, payload, false);
409         return processAaiResponse(resp, AaiGetVnfResponse.class, null);
410     }
411
412     @Override
413     public Response getVersionByInvariantId(List<String> modelInvariantId) {
414         if (modelInvariantId.isEmpty()) {
415             throw new GenericUncheckedException("Zero invariant-ids provided to getVersionByInvariantId; request is rejected as this will cause full models listing");
416         }
417
418         StringBuilder sb = new StringBuilder();
419         for (String id : modelInvariantId){
420             sb.append(MODEL_INVARIANT_ID);
421             sb.append(id);
422
423         }
424         return doAaiGet("service-design-and-creation/models?depth=2"+ sb.toString(), false);
425     }
426
427     @Override
428     public AaiResponse getSubscriberData(String subscriberId) {
429         String depth = "2";
430         AaiResponse subscriberDataResponse;
431         Response resp = doAaiGet(BUSINESS_CUSTOMERS_CUSTOMER + subscriberId + "?depth=" + depth, false);
432         subscriberDataResponse = processAaiResponse(resp, Services.class, null);
433         return subscriberDataResponse;
434     }
435
436     @Override
437     public AaiResponse getServices() {
438         Response resp = doAaiGet("service-design-and-creation/services", false);
439         return processAaiResponse(resp, GetServicesAAIRespone.class, null);
440     }
441
442     @Override
443     public AaiResponse getOperationalEnvironments(String operationalEnvironmentType, String operationalEnvironmentStatus) {
444         String url = "cloud-infrastructure/operational-environments";
445         URIBuilder urlBuilder  = new URIBuilder();
446         if (operationalEnvironmentType != null)
447             urlBuilder.addParameter("operational-environment-type", operationalEnvironmentType);
448         if (operationalEnvironmentStatus != null)
449             urlBuilder.addParameter("operational-environment-status", operationalEnvironmentStatus);
450         url += urlBuilder.toString();
451         Response resp = doAaiGet(url, false);
452         return processAaiResponse(resp, OperationalEnvironmentList.class, null);
453     }
454
455     @Override
456     public AaiResponse getTenants(String globalCustomerId, String serviceType) {
457         if ((globalCustomerId == null || globalCustomerId.isEmpty()) || ((serviceType == null) || (serviceType.isEmpty())) ){
458             return buildAaiResponseForGetTenantsFailure(" Failed to retrieve LCP Region & Tenants from A&AI, Subscriber ID or Service Type is missing.");
459         }
460         try {
461             return cacheProvider
462                     .aaiClientCacheFor("getTenants", this::getTenantsByKey)
463                     .get(CacheProvider.compileKey(globalCustomerId, serviceType));
464         }
465         catch (ParsingGetTenantsResponseFailure exception) {
466             logger.error("Failed to get tenants ", exception);
467             return buildAaiResponseForGetTenantsFailure(exception.getMessage());
468         }
469     }
470
471     @Override
472     public AaiResponse getNodeTemplateInstances(String globalCustomerId, String serviceType, String modelVersionId, String modelInvariantId, String cloudRegion) {
473
474         String siQuery = BUSINESS_CUSTOMER + globalCustomerId + SERVICE_SUBSCRIPTIONS_PATH + encodePathSegment(serviceType) + "/service-instances?model-version-id=" + modelVersionId + MODEL_INVARIANT_ID + modelInvariantId;
475         String vnfQuery = "query/queryvnfFromModelbyRegion?cloudRegionId=" + encodePathSegment(cloudRegion);
476         String payload1 = "{\"start\":\"" + siQuery + "\",\"query\":\"" + vnfQuery + "\"}";
477
478         Response resp1 = doAaiPut(QUERY_FORMAT_SIMPLE, payload1, false);
479         AaiResponse aaiResponse1 = processAaiResponse(resp1, AaiGetVnfResponse.class, null);
480         logger.debug(EELFLoggerDelegate.debugLogger, "getNodeTemplateInstances AAI's response: {}", aaiResponse1);
481         return aaiResponse1;
482     }
483
484     @Override
485     public AaiResponse<JsonNode> getCloudRegionAndSourceByPortMirroringConfigurationId(String configurationId) {
486         final String start = "[\"network/configurations/configuration/" + configurationId + "\"]";
487         final String query = "\"query/cloud-region-and-source-FromConfiguration\"";
488         String payload = "{\"start\":" + start + ",\"query\":" + query + "}";
489
490         Response response = doAaiPut("query?format=simple&nodesOnly=true", payload, false);
491         AaiResponse<JsonNode> aaiResponse = processAaiResponse(response, JsonNode.class, null);
492
493         logger.debug(EELFLoggerDelegate.debugLogger, "getNodeTemplateInstances AAI's response: {}", aaiResponse);
494         return aaiResponse;
495     }
496
497     private <T> AaiResponseWithRequestInfo<T> processAaiResponse(ResponseWithRequestInfo responseWithRequestInfo, Class<? extends T> classType, boolean propagateExceptions) {
498         return processAaiResponse(responseWithRequestInfo, classType, VidObjectMapperType.CODEHAUS, propagateExceptions);
499     }
500
501     private <T> AaiResponseWithRequestInfo<T> processAaiResponse(ResponseWithRequestInfo responseWithRequestInfo, Class<? extends T> classType, VidObjectMapperType omType, boolean propagateExceptions) {
502         String responseBody = null;
503         Integer responseHttpCode = null;
504         try {
505             Response response = responseWithRequestInfo.getResponse();
506             responseHttpCode = (response != null) ? response.getStatus() : null;
507             responseBody = (response != null) ? response.readEntity(String.class) : null;
508             AaiResponse<T> processedAaiResponse = processAaiResponse(response, classType, responseBody, omType, propagateExceptions);
509             return new AaiResponseWithRequestInfo<>(responseWithRequestInfo.getRequestHttpMethod(), responseWithRequestInfo.getRequestUrl(), processedAaiResponse,
510                     responseBody);
511         } catch (Exception e) {
512             throw new ExceptionWithRequestInfo(responseWithRequestInfo.getRequestHttpMethod(),
513                     responseWithRequestInfo.getRequestUrl(), responseBody, responseHttpCode, e);
514         }
515     }
516
517     private AaiResponse processAaiResponse(Response resp, Class classType, String responseBody) {
518         return processAaiResponse(resp, classType, responseBody, VidObjectMapperType.CODEHAUS);
519     }
520
521     private <T> AaiResponse<T> processAaiResponse(Response resp, Class<? extends T> classType, String responseBody, VidObjectMapperType omType) {
522         return processAaiResponse(resp, classType, responseBody, omType, false);
523     }
524
525     private  <T> AaiResponse<T> processAaiResponse(Response resp, Class<? extends T> classType, String responseBody, VidObjectMapperType omType, boolean propagateExceptions) {
526         AaiResponse<T> subscriberDataResponse;
527         if (resp == null) {
528             subscriberDataResponse = new AaiResponse<>(null, null, HttpStatus.SC_INTERNAL_SERVER_ERROR);
529             logger.debug(EELFLoggerDelegate.debugLogger, "Invalid response from AAI");
530         } else {
531             logger.debug(EELFLoggerDelegate.debugLogger, "getSubscribers() resp=" + resp.getStatusInfo().toString());
532             if (resp.getStatus() != HttpStatus.SC_OK) {
533                 subscriberDataResponse = processFailureResponse(resp,responseBody);
534             } else {
535                 subscriberDataResponse = processOkResponse(resp, classType, responseBody, omType, propagateExceptions);
536             }
537         }
538         return subscriberDataResponse;
539     }
540
541     private AaiResponse processFailureResponse(Response resp, String responseBody) {
542         logger.debug(EELFLoggerDelegate.debugLogger, "Invalid response from AAI");
543         String rawData;
544         if (responseBody != null) {
545             rawData = responseBody;
546         } else {
547             rawData = resp.readEntity(String.class);
548         }
549         return new AaiResponse<>(null, rawData, resp.getStatus());
550     }
551
552     private <T> AaiResponse<T> processOkResponse(Response resp, Class<? extends T> classType, String responseBody, VidObjectMapperType omType, boolean propagateExceptions) {
553         AaiResponse<T> subscriberDataResponse;
554         String finalResponse = null;
555         try {
556             if (responseBody != null) {
557                 finalResponse = responseBody;
558             } else {
559                 finalResponse = resp.readEntity(String.class);
560             }
561
562             if(omType == VidObjectMapperType.CODEHAUS)
563                 subscriberDataResponse = parseCodeHausObject(classType, finalResponse);
564             else
565                 subscriberDataResponse = parseFasterXmlObject(classType, finalResponse);
566
567         } catch(Exception e){
568             if (propagateExceptions) {
569                 throw new GenericUncheckedException(e);
570             } else {
571                 subscriberDataResponse = new AaiResponse<>(null, null, HttpStatus.SC_INTERNAL_SERVER_ERROR);
572                 logger.error("Failed to parse aai response: \"{}\" to class {}", finalResponse, classType, e);
573             }
574         }
575         return subscriberDataResponse;
576     }
577
578     private <T> AaiResponse<T> parseFasterXmlObject(Class<? extends T> classType, String finalResponse) throws IOException {
579         return new AaiResponse<>((objectMapper.readValue(finalResponse, classType)), null, HttpStatus.SC_OK);
580     }
581
582     private <T> AaiResponse<T> parseCodeHausObject(Class<? extends T> classType, String finalResponse) throws IOException {
583         return new AaiResponse<>((objectMapper.readValue(finalResponse, classType)), null, HttpStatus.SC_OK);
584     }
585
586     @Override
587     public Response doAaiGet(String uri, boolean xml) {
588         return doAaiGet(uri, xml, false).getResponse();
589     }
590
591
592     public ResponseWithRequestInfo doAaiGet(String uri, boolean xml, boolean propagateExceptions) {
593         return doAaiGet(Unchecked.toURI(uri), xml, propagateExceptions);
594     }
595
596     public ResponseWithRequestInfo doAaiGet(URI uri, boolean xml, boolean propagateExceptions) {
597         String methodName = "doAaiGet";
598         logger.debug(EELFLoggerDelegate.debugLogger, methodName + " start");
599
600         ResponseWithRequestInfo resp;
601         try {
602             resp = restController.RestGet(fromAppId, UUID.randomUUID().toString(), uri, xml, propagateExceptions);
603
604         } catch (Exception e) {
605             resp = handleExceptionFromRestCall(propagateExceptions, methodName, e);
606         }
607         return resp;
608     }
609
610     @NotNull
611     protected ResponseWithRequestInfo handleExceptionFromRestCall(boolean propagateExceptions, String methodName, Exception e) {
612         ResponseWithRequestInfo resp;
613         if (propagateExceptions) {
614             throw (e instanceof RuntimeException) ? (RuntimeException)e : new GenericUncheckedException(e);
615         } else {
616             final Exception actual =
617                     e instanceof ExceptionWithRequestInfo ? (Exception) e.getCause() : e;
618
619             final String message =
620                     actual instanceof WebApplicationException ? ((WebApplicationException) actual).getResponse().readEntity(String.class) : e.toString();
621
622             //ToDo: change parameter of requestUrl to real url from doRest function
623             resp = new ResponseWithRequestInfo(null, null, org.springframework.http.HttpMethod.GET);
624             logger.info(EELFLoggerDelegate.errorLogger, methodName + message);
625             logger.debug(EELFLoggerDelegate.debugLogger, methodName + message);
626         }
627         return resp;
628     }
629
630     private String parseForTenantsByServiceSubscription(String relatedToKey, String resp) {
631         String tenantList = "";
632
633         try {
634             JSONParser jsonParser = new JSONParser();
635
636             JSONObject jsonObject = (JSONObject) jsonParser.parse(resp);
637
638             return parseServiceSubscriptionObjectForTenants(relatedToKey, jsonObject);
639         } catch (Exception ex) {
640             logger.debug(EELFLoggerDelegate.debugLogger, "parseForTenantsByServiceSubscription error while parsing tenants by service subscription", ex);
641         }
642         return tenantList;
643     }
644
645     protected Response doAaiPut(String uri, String payload, boolean xml) {
646         return doAaiPut(uri, payload, xml, false).getResponse();
647     }
648
649     protected ResponseWithRequestInfo doAaiPut(String uri, String payload, boolean xml, boolean propagateExceptions) {
650         String methodName = "doAaiPut";
651         logger.debug(EELFLoggerDelegate.debugLogger, methodName + " start");
652
653         ResponseWithRequestInfo resp;
654         try {
655
656             resp = restController.RestPut(fromAppId, uri, payload, xml, propagateExceptions);
657
658         } catch (Exception e) {
659             resp = handleExceptionFromRestCall(propagateExceptions, methodName, e);
660         }
661         return resp;
662     }
663
664
665     private String parseServiceSubscriptionObjectForTenants(String relatedToKey, JSONObject jsonObject) {
666         JSONArray tenantArray = new JSONArray();
667         boolean bconvert = false;
668         try {
669             JSONObject relationShipListsObj = (JSONObject) jsonObject.get("relationship-list");
670             if (relationShipListsObj != null) {
671                 JSONArray rShipArray = (JSONArray) relationShipListsObj.get("relationship");
672                 for (Object innerObj : defaultIfNull(rShipArray, emptyList())) {
673                     if (innerObj != null) {
674                         bconvert = parseTenant(relatedToKey, tenantArray, bconvert, (JSONObject) innerObj);
675                     }
676                 }
677             }
678         } catch (NullPointerException ex) {
679             logger.debug(EELFLoggerDelegate.debugLogger, "parseServiceSubscriptionObjectForTenants. error while parsing service subscription object for tenants", ex);
680         }
681
682         if (bconvert)
683             return tenantArray.toJSONString();
684         else
685             return "";
686
687     }
688
689     private static boolean parseTenant(String relatedToKey, JSONArray tenantArray, boolean bconvert, JSONObject inner1Obj) {
690         String relatedTo = checkForNull((String) inner1Obj.get("related-to"));
691         if (relatedTo.equalsIgnoreCase(relatedToKey)) {
692             JSONObject tenantNewObj = new JSONObject();
693
694             String relatedLink = checkForNull((String) inner1Obj.get("related-link"));
695             tenantNewObj.put("link", relatedLink);
696
697             JSONArray rDataArray = (JSONArray) inner1Obj.get("relationship-data");
698             for (Object innerObj : defaultIfNull(rDataArray, emptyList())) {
699                 parseRelationShip(tenantNewObj, (JSONObject) innerObj);
700             }
701
702             JSONArray relatedTPropArray = (JSONArray) inner1Obj.get("related-to-property");
703             for (Object innerObj : defaultIfNull(relatedTPropArray, emptyList())) {
704                 parseRelatedTProp(tenantNewObj, (JSONObject) innerObj);
705             }
706             bconvert = true;
707             tenantArray.add(tenantNewObj);
708         }
709         return bconvert;
710     }
711
712     private static void parseRelatedTProp(JSONObject tenantNewObj, JSONObject innerObj) {
713         if (innerObj == null)
714             return;
715
716         String propKey = checkForNull((String) innerObj.get("property-key"));
717         String propVal = checkForNull((String) innerObj.get("property-value"));
718         if (equalsIgnoreCase(propKey, "tenant.tenant-name")) {
719             tenantNewObj.put("tenantName", propVal);
720         }
721     }
722
723     private static void parseRelationShip(JSONObject tenantNewObj, JSONObject inner2Obj) {
724         if (inner2Obj == null)
725             return;
726
727         String rShipKey = checkForNull((String) inner2Obj.get("relationship-key"));
728         String rShipVal = checkForNull((String) inner2Obj.get("relationship-value"));
729         if (equalsIgnoreCase(rShipKey, "cloud-region.cloud-owner")) {
730             tenantNewObj.put("cloudOwner", rShipVal);
731         } else if (equalsIgnoreCase(rShipKey, "cloud-region.cloud-region-id")) {
732             tenantNewObj.put("cloudRegionID", rShipVal);
733         } else if (equalsIgnoreCase(rShipKey, "tenant.tenant-id")) {
734             tenantNewObj.put("tenantID", rShipVal);
735         }
736     }
737
738     private static String encodePathSegment(String segmentToEncode) {
739         return UriUtils.encodePathSegment(segmentToEncode, "UTF-8");
740     }
741
742     @Override
743     public ExternalComponentStatus probeComponent(){
744         long startTime = System.currentTimeMillis();
745         try {
746             AaiResponseWithRequestInfo<SubscriberList> responseWithRequestInfo = getAllSubscribers(true);
747             AaiResponse<SubscriberList> aaiResponse = responseWithRequestInfo.getAaiResponse();
748             long duration = System.currentTimeMillis() - startTime;
749
750             SubscriberList subscribersList = (aaiResponse != null) ? aaiResponse.getT() : null;
751             boolean isAvailable = subscribersList != null && subscribersList.customer != null && !subscribersList.customer.isEmpty();
752
753             HttpRequestMetadata metadata = new HttpRequestMetadata(
754                     responseWithRequestInfo.getHttpMethod(),
755                     (aaiResponse != null) ? aaiResponse.getHttpCode() : 0,
756                     responseWithRequestInfo.getRequestedUrl(),
757                     responseWithRequestInfo.getRawData(),
758                     isAvailable ? "OK" : "No subscriber received",
759                     duration
760             );
761             return new ExternalComponentStatus(ExternalComponentStatus.Component.AAI, isAvailable, metadata);
762
763         } catch (ExceptionWithRequestInfo e) {
764             long duration = System.currentTimeMillis() - startTime;
765             return new ExternalComponentStatus(ExternalComponentStatus.Component.AAI, false,
766                     new HttpRequestMetadata(e, duration));
767         } catch (Exception e) {
768             long duration = System.currentTimeMillis() - startTime;
769             return new ExternalComponentStatus(ExternalComponentStatus.Component.AAI, false,
770                     new ErrorMetadata(Logging.exceptionToDescription(e), duration));
771         }
772     }
773
774     @Override
775     public String getCloudOwnerByCloudRegionId(String cloudRegionId) {
776         return cacheProvider
777                 .aaiClientCacheFor("getCloudOwnerByCloudRegionId", this::getCloudOwnerByCloudRegionIdNonCached)
778                 .get(cloudRegionId);
779     }
780
781
782     @Override
783     public GetTenantsResponse getHomingDataByVfModule(String vnfInstanceId, String vfModuleId) {
784
785         if (isEmpty(vnfInstanceId)|| isEmpty(vfModuleId)){
786             throw new GenericUncheckedException("Failed to retrieve homing data associated to vfModule from A&AI, VNF InstanceId or VF Module Id is missing.");
787         }
788         Response resp = doAaiGet("network/generic-vnfs/generic-vnf/" + vnfInstanceId +"/vf-modules/vf-module/"+ vfModuleId, false);
789         String responseAsString = parseForTenantsByServiceSubscription("vserver",resp.readEntity(String.class));
790         if (isEmpty(responseAsString)){
791             throw new GenericUncheckedException( String.format("A&AI has no homing data associated to vfModule '%s' of vnf '%s'", vfModuleId, vnfInstanceId));
792         }
793         else {
794             AaiResponse aaiResponse = processAaiResponse(resp, GetTenantsResponse[].class, responseAsString);
795             return ((GetTenantsResponse[])aaiResponse.getT())[0];
796         }
797     }
798
799     @Override
800     public void resetCache(String cacheName) {
801         cacheProvider.resetCache(cacheName);
802     }
803
804     String getCloudOwnerByCloudRegionIdNonCached(String cloudRegionId) {
805         String uri = "cloud-infrastructure/cloud-regions?cloud-region-id=" + encodePathSegment(cloudRegionId);
806
807         final CloudRegion.Collection cloudRegionCollection =
808                 typedAaiGet(Unchecked.toURI(uri), CloudRegion.Collection.class);
809
810         return cloudRegionCollection
811                 .getCloudRegions().stream()
812                 .map(CloudRegion::getCloudOwner)
813                 // from here we assure that the cloud owner is given, and not null
814                 // and non-empty, and that if more than one cloud-owner is given -
815                 // it is only a single value.
816                 // exception is thrown if none or more than a single values are
817                 // given.
818                 .filter(StringUtils::isNotEmpty)
819                 .distinct()
820                 .reduce((a, b) -> {
821                     // will be invoked only if distinct() leaves more than a single element
822                     throw new GenericUncheckedException("Conflicting cloud-owner found for " + cloudRegionId + ": '" + a + "' / '" + b + "'");
823                 })
824                 .orElseThrow(() -> new GenericUncheckedException("No cloud-owner found for " + cloudRegionId));
825     }
826
827     private AaiResponse getTenantsByKey(String key) {
828         String[] args = CacheProvider.decompileKey(key);
829         String globalCustomerId = safeGetFromArray(args, 0);
830         String serviceType = safeGetFromArray(args, 1);
831         return getTenantsNonCached(globalCustomerId, serviceType);
832     }
833
834     AaiResponse getTenantsNonCached(String globalCustomerId, String serviceType) {
835         String url = BUSINESS_CUSTOMERS_CUSTOMER + globalCustomerId + SERVICE_SUBSCRIPTIONS_PATH + serviceType;
836
837         Response resp = doAaiGet(url, false);
838         String responseAsString = parseForTenantsByServiceSubscription("tenant",resp.readEntity(String.class));
839         if (isEmpty(responseAsString)){
840            throw new ParsingGetTenantsResponseFailure(String.format("A&AI has no LCP Region & Tenants associated to subscriber '%s' and service type '%s'", globalCustomerId, serviceType));
841         }
842         else {
843             return processAaiResponse(resp, GetTenantsResponse[].class, responseAsString);
844         }
845     }
846
847     public static class ParsingGetTenantsResponseFailure extends GenericUncheckedException {
848
849         public ParsingGetTenantsResponseFailure(String message) {
850             super(message);
851         }
852     }
853
854     @NotNull
855     private AaiResponse<String> buildAaiResponseForGetTenantsFailure(String errorText) {
856         return new AaiResponse<>(null, String.format("{\"statusText\":\"%s\"}", errorText), HttpStatus.SC_INTERNAL_SERVER_ERROR);
857     }
858
859     private static String safeGetFromArray(String[] array, int i) {
860         if (i < 0 || i >= array.length) {
861             return null;
862         } else {
863             return array[i];
864         }
865     }
866 }